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,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"&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"]]
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user