Retire the Superset reader setup and the ATOMA dashboard (PD-13)
ATOMA's users use the Petrolyst app; Superset keeps admins, editors and the app's embedded dashboards. Removed: the "ATOMA Reader" role and its rules (petrolyst_reader.py, ReaderAccessMixin, reader.css, the head template, the SQL-hiding patch), the atoma-reader mapping (it now maps to nothing in Superset), the iframe allowance for the Map tab, and the "atoma" dashboard with its Fleet/Map tab, D0 and dashboard.css. The bundle holds atoma-overview and atoma-site only (byte-identical to before); the guest role keeps the reader's 12 permissions in roles/atoma_guest.py. Brand files now name petrolyst-web. SPEC marks the retired parts as history. Checks (local): bundle checks pass; image rebuilt and healthy; import with column drift 0 and unchanged embedding ids; the production config loads with OAuth, embedding off, no reader hook; admin sees atoma-site with its tabs and filter column; the app's browser tests pass 10/10. shellcheck not run (not installed). Not deployed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1b8e57adb4
commit
da3f803947
22 files changed
+164
-702
No files matched your search
@@ -1,10 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate the ATOMA dashboard as one Superset 6.1 import bundle.
|
||||
"""Generate ATOMA's dashboards for the Petrolyst app as one Superset 6.1 import bundle.
|
||||
|
||||
Implements SPEC.md in this folder: the "ATOMA" dashboard with a Map tab (the
|
||||
fleet-map view of petrolyst-platform, embedded as an iframe) and an Insights tab,
|
||||
one dataset per public.v_* view and every chart. Nothing in the bundle names
|
||||
a site.
|
||||
Implements SPEC.md in this folder: the two dashboards the Petrolyst app embeds, "ATOMA · Overview"
|
||||
(atoma-overview) and "ATOMA · Site" (atoma-site, the four detail tabs), one dataset per
|
||||
public.v_* view and every chart. Nothing in the bundle names a site.
|
||||
|
||||
build_bundle.py --database-uuid <uuid of "Fuel (Trino)"> [--out atoma-dashboards.zip]
|
||||
|
||||
@@ -17,9 +16,8 @@ unchanged).
|
||||
|
||||
Before writing, the bundle is checked: every column a chart or filter uses
|
||||
exists in its dataset, every chart in a Site filter's scope has site_label, the
|
||||
fleet map's address names this dashboard, its Site filter and the Insights tab,
|
||||
every layout id that dashboard.css or reader.css names exists, and no row is wider
|
||||
than 12. Requires PyYAML.
|
||||
Overview sums recorded values only and money only per currency, and no row is
|
||||
wider than 12. Requires PyYAML.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -43,36 +41,11 @@ DATABASE_NAME = "Fuel (Trino)"
|
||||
DATABASE_URI = "trino://superset@trino:8080/fuel" # as superset-init registers it
|
||||
CATALOG, SCHEMA = "fuel", "public"
|
||||
|
||||
SLUG = "atoma"
|
||||
SITE_FILTER = "NATIVE_FILTER-site"
|
||||
PERIOD_FILTER = "NATIVE_FILTER-period"
|
||||
FLEET_TAB = "TAB-fleet"
|
||||
SITE_TAB = "TAB-site"
|
||||
# The tabs' names: the lenses Map and Insights (owner, 29 Oct); readers see them as a switch
|
||||
# in the navbar (assets/branding/reader.css).
|
||||
FLEET_TAB_TITLE = "Map"
|
||||
SITE_TAB_TITLE = "Insights"
|
||||
|
||||
# SPEC §3: the Map tab is the fleet-map view of petrolyst-platform, served on the same
|
||||
# origin under /views (superset_config.py lets the iframe through the Markdown sanitiser).
|
||||
# Its parameters name where "Site details" leads: this dashboard, its Site filter and the
|
||||
# Insights tab, with that tab's name for the map's key. The view builds the native_filters
|
||||
# link from them.
|
||||
# The operator's logo in the map's bottom right corner: a file in assets/branding/, served by
|
||||
# Superset under /static/assets/branding/ (same origin, so the view's CSP allows it).
|
||||
OPERATOR_LOGO = "atoma_logo.svg"
|
||||
# The dashboard's own CSS (the Insights tab's inner tabs as section buttons), and the
|
||||
# readers' stylesheet, which names this layout's ids too (the lens switch, the map's size).
|
||||
DASHBOARD_CSS = HERE / "dashboard.css"
|
||||
READER_CSS = HERE.parent.parent / "assets" / "branding" / "reader.css"
|
||||
FLEET_MAP_SRC = (f"/views/fleet-map/?detail=/superset/dashboard/{SLUG}/"
|
||||
f"&filter={SITE_FILTER}&tab={SITE_TAB}&tab_label={SITE_TAB_TITLE}"
|
||||
f"&logo=/static/assets/branding/{OPERATOR_LOGO}&logo_alt=ATOMA")
|
||||
FLEET_MAP = (f'<iframe src="{FLEET_MAP_SRC}" title="Fleet map" loading="lazy" '
|
||||
'style="display:block;width:100%;height:100%;border:0"></iframe>')
|
||||
|
||||
# SPEC §3: neutral hues, no red or green, keyed by v_site.baseline_status_label. The
|
||||
# fleet-map view uses the same colours (petrolyst-platform views/src/shared/status.ts).
|
||||
# Petrolyst app's map uses the same colours (petrolyst-platform app/src/app/core/status.ts).
|
||||
STATUS_COLOURS = {
|
||||
"Approved baseline": "#009688",
|
||||
"Eligible - not yet approved": "#3987e5",
|
||||
@@ -96,8 +69,8 @@ MAIN_DTTM = {
|
||||
"v_electricity_cost": "bill_month",
|
||||
}
|
||||
|
||||
# Calculated columns: (name, Trino SQL, type, label). The fleet-map view's read service
|
||||
# (petrolyst-platform services/read, site_status) shows the same two texts.
|
||||
# Calculated columns: (name, Trino SQL, type, label). The Petrolyst server's site_status
|
||||
# (petrolyst-platform server/) shows the same two texts.
|
||||
CALCULATED = {
|
||||
"v_site": [
|
||||
("lph_text", "COALESCE(CAST(lph_estimate AS varchar), 'not available')", "VARCHAR", "L/h"),
|
||||
@@ -588,9 +561,6 @@ def detail_charts() -> list[Chart]:
|
||||
return [
|
||||
# Handlebars would render these as cards, but it compiles templates with
|
||||
# eval, which Superset's own CSP blocks. One-row tables need no CSP change.
|
||||
table("D0", "Site", "v_site", [
|
||||
"site_code", "site_name", "province", "pilot_status", "baseline_status_text",
|
||||
"location_status"]),
|
||||
table("S1", "Fuel per running hour", "v_site", [
|
||||
"lph_text", "lph_method", "lph_window_start", "lph_window_end", "baseline_status_text",
|
||||
"baseline_validity_note", "baseline_decision_ref", "baseline_conditions",
|
||||
@@ -799,7 +769,6 @@ TAB_NOTES = {
|
||||
"the map or the sites list. The Viewing period does not apply to this tab.",
|
||||
}
|
||||
|
||||
DETAIL_HEADER = [("D0", 12, 24)]
|
||||
DETAIL_TABS = [
|
||||
("summary", "Summary", [
|
||||
[("MARKDOWN:notes-summary", 12, 17)],
|
||||
@@ -982,8 +951,8 @@ def base_metadata(layout: Layout, filters: list[dict[str, Any]], cross_filters:
|
||||
"map_label_colors": {},
|
||||
"color_scheme_domain": [],
|
||||
"cross_filters_enabled": cross_filters,
|
||||
# Filters in Superset's left column (owner, 29 Oct). Readers don't see it on the Fleet
|
||||
# tab (assets/branding/reader.css), so their map gets the full width.
|
||||
# Filters in Superset's left column (owner, 29 Oct); the Petrolyst app hides the column
|
||||
# when it embeds a dashboard and sets the filters itself.
|
||||
"filter_bar_orientation": "VERTICAL",
|
||||
"native_filter_configuration": filters,
|
||||
"chart_configuration": {},
|
||||
@@ -1021,30 +990,12 @@ def build(database_uuid: str, columns_path: Path) -> dict[str, Any]:
|
||||
for view in sorted(views):
|
||||
files[f"datasets/Fuel_Trino/{view}.yaml"] = dataset_yaml(view, views[view], database_uuid)
|
||||
|
||||
charts = {c.key: c for c in detail_charts()}
|
||||
|
||||
# SPEC §2: [Map] [Insights]; the site header and the four detail tabs live in
|
||||
# Insights, so the Map tab shows the map only.
|
||||
layout = Layout("ATOMA", charts)
|
||||
layout.markdown["fleet-map"] = FLEET_MAP
|
||||
layout.markdown.update({f"notes-{slug}": text for slug, text in TAB_NOTES.items()})
|
||||
main_tabs = layout.add("TABS-main", "TABS", "GRID_ID", {})
|
||||
layout.rows([[("MARKDOWN:fleet-map", 12, 100)]], layout.tab(FLEET_TAB, FLEET_TAB_TITLE, main_tabs), "fleet")
|
||||
site_tab = layout.tab(SITE_TAB, SITE_TAB_TITLE, main_tabs)
|
||||
layout.rows([DETAIL_HEADER], site_tab, "header")
|
||||
layout.tabs(DETAIL_TABS, site_tab, "TABS-detail")
|
||||
metadata = base_metadata(layout, detail_filters(layout.ids), cross_filters=False)
|
||||
metadata["label_colors"].update(STOCK_STATUS_COLOURS)
|
||||
|
||||
files["dashboards/ATOMA.yaml"] = dashboard_yaml("atoma", "ATOMA", SLUG, layout, metadata,
|
||||
DASHBOARD_CSS.read_text())
|
||||
dashboards = [(layout, charts, metadata, (DASHBOARD_CSS, READER_CSS))]
|
||||
|
||||
# PD-12: the Petrolyst app embeds the Overview and one site dashboard, whose four detail
|
||||
# tabs are Superset's own (owner, 29 Oct). The app shows the site header and chooses site
|
||||
# and period, so these have no D0 and no CSS.
|
||||
# and period. Admins open the same dashboards in Superset, with the filter column.
|
||||
overview = {c.key: c for c in overview_charts()}
|
||||
all_charts = {**charts, **overview}
|
||||
all_charts = {**{c.key: c for c in detail_charts()}, **overview}
|
||||
dashboards: list[tuple[Layout, dict[str, Chart], dict[str, Any]]] = []
|
||||
for key, title in [(OVERVIEW_SLUG, "Overview"), (SITE_SLUG, "Site")]:
|
||||
page = Layout(f"ATOMA · {title}", all_charts)
|
||||
page.markdown.update({f"notes-{k}": v for k, v in TAB_NOTES.items()})
|
||||
@@ -1061,7 +1012,7 @@ def build(database_uuid: str, columns_path: Path) -> dict[str, Any]:
|
||||
page_metadata["label_colors"].update(STOCK_STATUS_COLOURS)
|
||||
files[f"dashboards/{key}.yaml"] = dashboard_yaml(key, f"ATOMA · {title}", key, page,
|
||||
page_metadata)
|
||||
dashboards.append((page, placed, page_metadata, ()))
|
||||
dashboards.append((page, placed, page_metadata))
|
||||
|
||||
for chart in all_charts.values():
|
||||
name = re.sub(r"\W+", "_", f"{chart.key}_{chart.title}").strip("_")
|
||||
@@ -1072,7 +1023,7 @@ def build(database_uuid: str, columns_path: Path) -> dict[str, Any]:
|
||||
|
||||
|
||||
def check(files: dict[str, Any], views: dict[str, list[tuple[str, str]]],
|
||||
dashboards: list[tuple[Layout, dict[str, Chart], dict[str, Any], tuple[Path, ...]]],
|
||||
dashboards: list[tuple[Layout, dict[str, Chart], dict[str, Any]]],
|
||||
overview: dict[str, Chart]) -> None:
|
||||
problems = []
|
||||
columns = {v: {c for c, _ in cols} | {c[0] for c in CALCULATED.get(v, [])}
|
||||
@@ -1083,11 +1034,7 @@ def check(files: dict[str, Any], views: dict[str, list[tuple[str, str]]],
|
||||
for view, col in MAIN_DTTM.items():
|
||||
if col not in columns.get(view, set()):
|
||||
problems.append(f"{view}: main time column {col} missing")
|
||||
for layout, charts, metadata, stylesheets in dashboards:
|
||||
for css in stylesheets:
|
||||
for ref in sorted(set(re.findall(r"#([A-Z]+-[\w-]+)", css.read_text()))):
|
||||
if ref not in layout.nodes:
|
||||
problems.append(f"{css.name}: #{ref} is not in the layout")
|
||||
for layout, charts, metadata in dashboards:
|
||||
placed = set(layout.ids)
|
||||
if placed != set(charts):
|
||||
problems.append(f"charts not placed or unknown: {sorted(placed ^ set(charts))}")
|
||||
@@ -1104,26 +1051,6 @@ def check(files: dict[str, Any], views: dict[str, list[tuple[str, str]]],
|
||||
for key in in_scope:
|
||||
if col not in columns.get(charts[key].view, set()):
|
||||
problems.append(f"{f['id']}: {key} ({charts[key].view}) lacks {col}")
|
||||
# The fleet map's "Site details" link is built from its iframe address.
|
||||
fleet = layout.nodes.get("MARKDOWN-fleet-map")
|
||||
if fleet is not None:
|
||||
filter_ids = {f["id"] for f in metadata["native_filter_configuration"]}
|
||||
for needle, what in [(f"detail=/superset/dashboard/{SLUG}/", "this dashboard"),
|
||||
(f"filter={SITE_FILTER}", "the Site filter"),
|
||||
(f"tab={SITE_TAB}", "the Insights tab"),
|
||||
(f"tab_label={SITE_TAB_TITLE}", "the Insights tab's name")]:
|
||||
if needle not in fleet["meta"]["code"]:
|
||||
problems.append(f"fleet map: its address does not name {what}")
|
||||
if SITE_FILTER not in filter_ids or SITE_TAB not in layout.nodes:
|
||||
problems.append("fleet map: the Site filter or the Insights tab is missing")
|
||||
if not (HERE.parent.parent / "assets" / "branding" / OPERATOR_LOGO).is_file():
|
||||
problems.append(f"fleet map: assets/branding/{OPERATOR_LOGO} is missing")
|
||||
if metadata.get("filter_bar_orientation") != "VERTICAL":
|
||||
problems.append("fleet map: the filters must sit in the left column")
|
||||
if f'data-node-key="{FLEET_TAB}"' not in READER_CSS.read_text():
|
||||
problems.append(f"fleet map: reader.css does not hide the filters on {FLEET_TAB}")
|
||||
if fleet["meta"]["width"] != 12:
|
||||
problems.append("fleet map: reader.css stretches it, so it must span all 12 columns")
|
||||
# SPEC §10: the Overview sums recorded values only, and money only per currency.
|
||||
for chart in overview.values():
|
||||
metrics = chart.params.get("metrics") or [chart.params["metric"]]
|
||||
|
||||
Reference in new issue
Block a user