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:
Nasim SadatandClaude Opus 5.5 committed 2026-10-29 21:10:19 +01:00
1 parent 1b8e57adb4
commit da3f803947
22 files changed
+164 -702

No files matched your search

+13 -25
View File
@@ -3,16 +3,11 @@
Superset dashboards as code and the operator scripts that import them. Not baked into the
image, so changes here do not rebuild or restart Superset.
- `atoma/`: the ATOMA dashboard, with a Map tab (the fleet map) and an Insights tab (one site's
details), shown to readers as a lens switch in the navbar. `SPEC.md` is
the design; `build_bundle.py` generates the import bundle and `import_bundle.py` imports it
(overwriting the objects the bundle defines). `dashboard.css` is the dashboard's own CSS,
which the bundle carries.
The Fleet tab is an iframe of the fleet-map view from the petrolyst-platform repository. It
needs that view and its read service served on Superset's own origin under `/views`, and the
image's `superset_config.py` with the iframe setting (SPEC.md §6). Without them the tab stays
empty, and the Site details tab works as before.
- `atoma/`: ATOMA's two dashboards for the Petrolyst app (PD-12, PD-13; SPEC.md §10):
`atoma-overview` (fleet totals) and `atoma-site` (one site, its four sections as tabs). The
app embeds them; admins open them in Superset, with the filter column. `SPEC.md` is the
design; `build_bundle.py` generates the import bundle and `import_bundle.py` imports it
(overwriting the objects the bundle defines).
Build and import:
@@ -25,23 +20,16 @@ image, so changes here do not rebuild or restart Superset.
Exit status 2 means a dataset's columns differ from its view: regenerate `atoma/columns.tsv`
(command in its header), rebuild and import again.
The import attaches the role "ATOMA Reader" to the dashboard (a bundle cannot carry roles),
so the role must exist first: `superset-init` creates it (`petrolyst-insights/roles/`). The
Fleet tab shows ATOMA's logo from `petrolyst-insights/assets/branding/atoma_logo.svg`, in the
image.
An import never deletes. Where they exist, delete these by hand after importing: the
dashboards from before 28 Oct ("ATOMA - Sites", "ATOMA - Site Detail") and their charts M1,
M3, M4 and M5; the ATOMA dashboard (slug `atoma`, retired 29 Oct with PD-13) and its chart D0
"Site"; the one-tab dashboards `atoma-site-summary`, `-fuel`, `-runtime` and `-baseline`
(replaced by `atoma-site` on 29 Oct); and the role "ATOMA Reader".
An import never deletes. On an instance that still has the two dashboards from before
28 Oct 2026 ("ATOMA - Sites" and "ATOMA - Site Detail") and their charts M1, M3, M4 and M5,
delete those by hand after importing.
The bundle also carries the Petrolyst app's dashboards (PD-12, SPEC.md §10): `atoma-overview`
(fleet totals) and `atoma-site` (one site, its four sections as tabs; it replaced
`atoma-site-summary`, `-fuel`, `-runtime` and `-baseline` on 29 Oct: delete those by hand
where they exist).
They get no role; the app shows them with guest passes (see "Embedding" in the parent
README; local stack only). Add `--embedded-out FILE --embed-domain <app origin>` to the
The dashboards get no role; the app shows them with guest passes (see "Embedding" in the
parent README; local stack only). Add `--embedded-out FILE --embed-domain <app origin>` to the
import to make them embeddable from the app's origin; FILE then receives
`{slug: embedding id}` for the app service. The ids stay the same across imports. Local
`{slug: embedding id}` for the Petrolyst server. The ids stay the same across imports. Local
stack:
docker exec fmdb-scratch-superset /app/.venv/bin/python /tmp/import_bundle.py /tmp/atoma-dashboards.zip \
+36 -26
View File
@@ -1,13 +1,17 @@
# ATOMA dashboard: Fleet map and Site details
# ATOMA dashboards: Overview and Site, for the Petrolyst app
**Status: built and tested on a local Superset (P2, 24 Oct 2026); merged into one dashboard with
the fleet-map view on 28 Oct 2026 (§9); not imported live.**
- `build_bundle.py` generates the dashboard, every dataset and every chart from this
**Status (29 Oct 2026, PD-13): two dashboards, `atoma-overview` and `atoma-site` (§10), built and
tested on a local Superset; not imported live.** The Petrolyst app embeds them, and admins open
them in Superset with the filter column.
- `build_bundle.py` generates the dashboards, every dataset and every chart from this
specification, as one Superset import bundle; `import_bundle.py` imports it (§6).
- The local tests changed parts of this design. Each change is marked *(P2)* and §8 lists them.
- The merge of 28 Oct (owner's decisions PD-5 and PD-6, `Petrolist/.context/design-decisions.md`)
replaced the Sites Map dashboard by a Fleet tab holding the fleet-map view of petrolyst-platform.
Changed parts are marked *(28 Oct)*; §9 lists the results.
- **Retired 29 Oct (PD-13):** the "ATOMA" dashboard (slug `atoma`) with its Map tab (the React
fleet-map view of petrolyst-platform) and Insights tab, its D0 header row, and the Superset
reader setup ("ATOMA Reader", `reader.css`). ATOMA's users use the Petrolyst app. §2, §3, §9 and
the readers' part of §6 describe them and stay as history. §4's data rules and four tabs are
current: they are `atoma-site`'s tabs, without D0 (the app shows the site's header).
- Earlier changes are marked *(P2)* (local tests of 24 Oct, §8) and *(28 Oct)* (one dashboard
with the fleet-map view, §9).
- Nothing is imported into the live Superset without the owner's go-ahead (phase P3).
**Datasets.** The `public.v_*` views of
@@ -20,8 +24,7 @@ the fleet-map view on 28 Oct 2026 (§9); not imported live.**
- the site-specific facts left out here (positions, distances, pilot sites) are there too;
- Q-numbers refer to its §7, and phases P0–P5 to its §8.
One dashboard (two until 28 Oct) replaces the earlier tab-per-site design; nothing in it is
specific to a site.
Nothing in the dashboards is specific to a site.
## 1. Capabilities of the installed Superset 6.1.0
@@ -53,7 +56,7 @@ still holds: the view builds the same `?native_filters=` link.
No custom frontend and no patch are needed.
## 2. Navigation flow
## 2. Navigation flow *(retired, PD-13: history)*
*(28 Oct)* One dashboard, two top-level tabs. *(29 Oct)* They are named **Map** (`TAB-fleet`) and
**Insights** (`TAB-site`), the dashboard's lenses; earlier text in this document calls them Fleet and
@@ -93,7 +96,7 @@ Until 28 Oct there were two dashboards: "ATOMA - Sites" (slug `atoma-sites`: a d
filters, a sites list) and "ATOMA - Site Detail" (slug `atoma-site-detail`), linked by "Open site
detail" and "← Sites map".
## 3. Fleet tab: the fleet-map view *(28 Oct)*
## 3. Fleet tab: the fleet-map view *(28 Oct; retired, PD-13: history)*
The Fleet tab holds one Markdown block with an iframe of the fleet-map view, a page of
petrolyst-platform (`views/src/fleet-map/`) served under `/views/` on Superset's own origin. The
@@ -135,7 +138,10 @@ thousands of sites stay fast.
**Sites without an accepted location** are not plotted. The key counts them, and the Site filter
in Site details lists every site, placed or not.
## 4. Site details tab (any site)
## 4. Site details: the four tabs of `atoma-site` (any site)
*(PD-13)* What follows about D0 (the site header row) and the Fleet tab is history: the app shows
the site's header, and its Map lens replaced the Fleet tab.
**Filters**
- **Site** (`NATIVE_FILTER-site`):
@@ -248,10 +254,12 @@ is grouped or filtered by `metric` and `population_key` (both in `v_interval`),
across methods or generator sets. The site's L/h figure itself comes only from a baseline version
(`v_site`), never from this metric. There is no AVG metric on ratio columns.
**Site details link** *(28 Oct)*. The fleet-map view builds it (petrolyst-platform
`views/src/shared/deepLink.ts`) from its iframe address: the dashboard path, the Site filter's id
and the tab. The rison shape is the one the sites list used until 28 Oct, confirmed in P2, and the
view's tests compare it with that list's Trino output:
**Site details link** *(28 Oct; history since PD-13)*. The fleet-map view built it
(petrolyst-platform `views/src/shared/deepLink.ts`, retired) from its iframe address: the
dashboard path, the Site filter's id and the tab. The Petrolyst app uses the same rison shape for
the Site filter's preset (`app/src/app/core/site-filter.ts`). The rison shape is the one the sites
list used until 28 Oct, confirmed in P2, and the view's tests compared it with that list's Trino
output:
```
/superset/dashboard/atoma/?native_filters=<url-encoded:>
@@ -312,7 +320,8 @@ appears on every Superset page, including the stock dashboard list: a capability
same sign-in *(28 Oct: not built yet; locally it has none)*. No Gamma role until the
ATOMA-facing edit.
**ATOMA's readers** *(owner, 29 Oct)*. The Superset role **"ATOMA Reader"** (Keycloak client role
**ATOMA's readers** *(owner, 29 Oct; retired the same day, PD-13: readers use the Petrolyst
app; kept as history)*. The Superset role **"ATOMA Reader"** (Keycloak client role
`atoma-reader`) reads the ATOMA dashboard and nothing else.
- **The role** (`petrolyst-insights/roles/atoma_reader.py`): exactly 12 permissions, set by
`superset-init` whenever the image changes, and reset by hand with the command in the README.
@@ -357,6 +366,9 @@ ATOMA-facing edit.
## 7. Acceptance checks
*(PD-13)* Written for the retired ATOMA dashboard. Where they name D0 or the fleet map, the app's
site header and Map lens take their place (petrolyst-platform's browser tests).
Run in P2 against a local Superset, and repeated as a smoke test in P3.
1. **New site, no new objects.** Count dashboards, charts and datasets in the metadata database.
@@ -425,7 +437,7 @@ The marker palette is unchanged from §3. The dataviz palette check flags slate
grey, which is intended for "insufficient evidence", and amber at 2.99:1 contrast. The label is
always also shown as text, in the legend, the tooltip and M3.
## 9. Fleet map results (local, 28 Oct 2026)
## 9. Fleet map results (local, 28 Oct 2026) *(history)*
Local Superset 6.1.0 rebuilt with the iframe setting, behind a local Traefik that serves Superset
and the petrolyst-platform read service on one origin (as on the server); the fuel-db of P2.
@@ -547,11 +559,11 @@ These changes have not had an independent inspection (the owner skipped Codex).
unchanged), sign-in through Keycloak, the server's routing of `/views`, sign-in for the read
service, thousands of sites, and dark mode.
## 10. The Petrolyst app's dashboards *(PD-12, local trial, 29 Oct 2026)*
## 10. The Petrolyst app's dashboards *(PD-12, PD-13; local, 29 Oct 2026)*
The Petrolyst app (petrolyst-platform `app/`, PD-12) shows Superset dashboards inside its
Insights lens, with guest passes from its app service. `build_bundle.py` builds them beside the
unchanged ATOMA dashboard:
Insights lens, with guest passes from the Petrolyst server (petrolyst-platform `server/`).
`build_bundle.py` builds them (since PD-13 the only dashboards in the bundle):
| Slug | Content | Filters (set by the app; Superset's filter column is hidden) |
|---|---|---|
@@ -569,11 +581,9 @@ unchanged ATOMA dashboard:
recorded values across sites (`OVERVIEW_METRICS`); per-site rates (L/h, L/day) are never
averaged; money is grouped by `currency_text`, a missing currency is its own row, and no
total crosses currencies. `build_bundle.py` checks both.
- They carry no CSS; `dashboard.css` and `reader.css` are checked against the ATOMA dashboard
only.
- They carry no CSS.
- `import_bundle.py --embedded-out FILE --embed-domain URL` makes them embeddable from the app's
origin and writes `{slug: embedding id}` for the app service; the reader role stays on the
ATOMA dashboard only.
origin and writes `{slug: embedding id}` for the Petrolyst server.
- Embedding is switched on in the local stack only (`.local/fmdb-scratch`,
`superset_config_local.py`); production keeps it off until PD-12's open points are settled.
- **Checked (local):** a self-signed guest pass opens its own dashboard and nothing else (another
@@ -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"&amp;filter={SITE_FILTER}&amp;tab={SITE_TAB}&amp;tab_label={SITE_TAB_TITLE}"
f"&amp;logo=/static/assets/branding/{OPERATOR_LOGO}&amp;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"]]
@@ -1,33 +0,0 @@
/* The ATOMA dashboard's own CSS, for everyone who opens it (build_bundle.py puts it into the
bundle and checks that every #id here is in the layout).
Site details' inner tabs (TABS-detail) as a row of section buttons, so they read as a level
below Fleet | Site details (owner, 29 Oct). Aligned with the content's left edge.
Translucent colours: right in the light and the dark theme. */
#TABS-detail > .ant-tabs-nav {
margin: 0 16px 16px;
}
#TABS-detail > .ant-tabs-nav::before {
border-bottom: 0;
}
#TABS-detail > .ant-tabs-nav .ant-tabs-ink-bar {
display: none;
}
#TABS-detail > .ant-tabs-nav .ant-tabs-nav-list {
gap: 4px;
padding: 4px;
border-radius: 10px;
background: rgba(127, 127, 127, 0.1);
}
#TABS-detail > .ant-tabs-nav .ant-tabs-tab {
margin: 0 !important;
padding: 6px 14px !important;
border-radius: 8px !important;
font-size: 13px;
}
#TABS-detail > .ant-tabs-nav .ant-tabs-tab-active {
background: rgba(0, 150, 136, 0.14) !important;
}
#TABS-detail > .ant-tabs-nav .ant-tabs-tab-active .ant-tabs-tab-btn {
font-weight: 600;
}
@@ -1,4 +1,4 @@
"""Import the ATOMA dashboard bundle into Superset, overwriting what it defines.
"""Import the ATOMA dashboards bundle into Superset, overwriting what it defines.
Runs inside the Superset container, with the image's Python:
@@ -17,15 +17,10 @@ live view and any drift is reported (exit status 2).
The database is never created or changed: the bundle's database uuid must
already exist under the name "Fuel (Trino)", otherwise nothing is imported.
A bundle cannot carry a dashboard's roles, so afterwards the role "ATOMA Reader"
is attached to the ATOMA dashboard: with DASHBOARD_RBAC that is how ATOMA's
readers see it. The role comes from roles/atoma_reader.py (run by superset-init);
without it nothing is imported.
The Petrolyst app's dashboards (PD-12: atoma-overview, atoma-site) get no role:
the app shows them with guest passes. With --embedded-out FILE --embed-domain URL,
each of them is made embeddable from that origin (its embedding id stays the same
across imports), and FILE receives {slug: embedding id} for the app service:
The dashboards (atoma-overview, atoma-site) get no role: admins see them in Superset, and
the Petrolyst app shows them with guest passes (PD-12). With --embedded-out FILE
--embed-domain URL, each of them is made embeddable from that origin (its embedding id stays
the same across imports), and FILE receives {slug: embedding id} for the Petrolyst server:
docker exec <container> /app/.venv/bin/python /tmp/import_bundle.py /tmp/atoma-dashboards.zip \
--user <username> --embedded-out /tmp/embedded-dashboards.json --embed-domain http://127.0.0.1:8098
@@ -41,14 +36,12 @@ import zipfile
import yaml
DATABASE_NAME = "Fuel (Trino)"
READER_ROLE = "ATOMA Reader"
READER_SLUG = "atoma" # the readers' dashboard; the others are the app's (PD-12)
EMBEDDED_PREFIX = "atoma-"
STEPS = [("datasets", "SqlaTable"), ("charts", "Slice"), ("dashboards", "Dashboard")]
def main() -> int:
parser = argparse.ArgumentParser(description="Import the ATOMA dashboard bundle.")
parser = argparse.ArgumentParser(description="Import the ATOMA dashboards bundle.")
parser.add_argument("bundle", nargs="?")
parser.add_argument("--user", help="existing Superset user who will own new objects")
parser.add_argument("--show-database", action="store_true",
@@ -85,10 +78,6 @@ def main() -> int:
if user is None:
print(f"no Superset user {args.user!r}", file=sys.stderr)
return 1
reader_role = security_manager.find_role(READER_ROLE)
if reader_role is None:
print(f'no role "{READER_ROLE}": run roles/atoma_reader.py first', file=sys.stderr)
return 1
with zipfile.ZipFile(args.bundle) as bundle:
contents = get_contents_from_bundle(bundle)
databases = [yaml.safe_load(t) for p, t in contents.items() if p.startswith("databases/")]
@@ -115,17 +104,13 @@ def main() -> int:
if path.startswith("dashboards/"):
dashboard = db.session.query(Dashboard).filter_by(
uuid=yaml.safe_load(text)["uuid"]).one()
if dashboard.slug == READER_SLUG:
if reader_role not in dashboard.roles:
dashboard.roles.append(reader_role)
elif dashboard.slug.startswith(EMBEDDED_PREFIX) and args.embedded_out:
if dashboard.slug.startswith(EMBEDDED_PREFIX) and args.embedded_out:
from superset.daos.dashboard import EmbeddedDashboardDAO
config = EmbeddedDashboardDAO.upsert(dashboard, [args.embed_domain])
db.session.flush()
embedded[dashboard.slug] = str(config.uuid)
db.session.commit()
print(f'dashboards: "{READER_SLUG}" readable for "{READER_ROLE}"')
if args.embedded_out:
with open(args.embedded_out, "w") as out:
json.dump(dict(sorted(embedded.items())), out, indent=2)