Make a dashboard its widgets: drop the pages and sections nobody drew

This commit is contained in:
2026-08-25 12:30:22 +02:00
parent 2840cc8e2b
commit 7ff29ca939
17 changed files with 273 additions and 497 deletions
+5 -16
View File
@@ -243,7 +243,7 @@ def _dashboard_with(
) -> None:
"""A published dashboard carrying these widgets."""
saved = _dashboard(client, headers, name)
saved["pages"] = [{"id": "main", "sections": [{"id": "main", "widgets": widgets}]}]
saved["widgets"] = widgets
written = client.put(f"{DASHBOARDS}/{name}", headers=headers, json=saved)
assert written.status_code == 200, written.text
published = client.post(
@@ -706,22 +706,11 @@ def test_a_panels_socket_carries_only_what_it_draws(
)
saved = _dashboard(client, superuser_token_headers, "panel_socket")
saved["pages"] = [
saved["widgets"] = [
{
"id": "main",
"title": "Overview",
"sections": [
{
"id": "main",
"widgets": [
{
"id": "w1",
"type": "stat",
"config": {"message": "house.kitchen.temperature"},
}
],
}
],
"id": "w1",
"type": "stat",
"config": {"message": "house.kitchen.temperature"},
}
]
written = client.put(
+52 -25
View File
@@ -6,8 +6,6 @@ from fluksio.flow.dashboards import (
DashboardDef,
DashboardNotFound,
DashboardStore,
PageDef,
SectionDef,
SettingDef,
WidgetDef,
default_dashboard,
@@ -40,7 +38,7 @@ def test_a_dashboard_survives_a_round_trip(store: DashboardStore):
read = store.read("house")
assert read.name == "house"
assert [p.id for p in read.pages] == ["main"]
assert read.widgets == []
assert read.version == saved.version
@@ -138,20 +136,10 @@ def test_the_deepest_chart_decides_how_much_past_is_kept(store: DashboardStore):
store.write(
DashboardDef(
name="house",
pages=[
PageDef(
id="main",
sections=[
SectionDef(id="a", widgets=[chart("heating.temp", 400)]),
SectionDef(
id="b",
widgets=[
chart("heating.temp", 900),
chart("solar.watts", 100),
],
),
],
)
widgets=[
chart("heating.temp", 400),
chart("heating.temp", 900),
chart("solar.watts", 100),
],
)
)
@@ -163,14 +151,7 @@ def test_a_chart_cannot_ask_for_an_unbounded_series(store: DashboardStore):
store.write(
DashboardDef(
name="house",
pages=[
PageDef(
id="main",
sections=[
SectionDef(id="a", widgets=[chart("heating.temp", 10**9)])
],
)
],
widgets=[chart("heating.temp", 10**9)],
)
)
@@ -514,3 +495,49 @@ def test_a_chart_of_runs_must_say_which_runs_and_which_metric():
runs_chart(metric="study.loss")
with pytest.raises(ValueError, match="between 1 and 5"):
runs_chart(metric="study.loss", flow="study", latest=9)
def test_a_document_written_as_pages_is_read_as_one_grid():
"""Stored dashboards live in each installation's repository.
So the old shape is normalised on the way in rather than migrated, and a
placed second section keeps its arrangement instead of piling onto the
first — which is how the viewer always drew it.
"""
old = {
"name": "house",
"pages": [
{
"id": "main",
"sections": [
{
"id": "a",
"widgets": [
{
"id": "top",
"type": "stat",
"layout": {"lg": {"x": 0, "y": 0, "w": 3, "h": 2}},
}
],
},
{
"id": "b",
"widgets": [
{
"id": "under",
"type": "stat",
"layout": {"lg": {"x": 0, "y": 1, "w": 3, "h": 2}},
}
],
},
],
}
],
}
read = DashboardDef.model_validate(old)
assert [w.id for w in read.widgets] == ["top", "under"]
# The first section is two rows deep, so the second one starts under it.
assert read.widgets[1].layout["lg"].y == 3
assert "pages" not in read.model_dump()
+19 -31
View File
@@ -8,8 +8,6 @@ control or another flow, it pulses a node that did nothing at all.
from fluksio.flow.dashboards import (
DashboardDef,
DashboardStore,
PageDef,
SectionDef,
WidgetDef,
)
from fluksio.flow.events import EventBus
@@ -104,35 +102,25 @@ def test_the_widgets_wired_into_a_flow_are_reported(tmp_path):
DashboardDef(
name="panel",
title="Panel",
pages=[
PageDef(
id="main",
sections=[
SectionDef(
id="main",
widgets=[
WidgetDef(
id="setpoint",
type="slider",
title="Setpoint",
config={"target": "house.setpoint"},
),
WidgetDef(
id="reading",
type="stat",
title="Reading",
config={"message": "house.temp"},
),
# Another flow's message: not this flow's business.
WidgetDef(
id="elsewhere",
type="stat",
config={"message": "garage.temp"},
),
],
)
],
)
widgets=[
WidgetDef(
id="setpoint",
type="slider",
title="Setpoint",
config={"target": "house.setpoint"},
),
WidgetDef(
id="reading",
type="stat",
title="Reading",
config={"message": "house.temp"},
),
# Another flow's message: not this flow's business.
WidgetDef(
id="elsewhere",
type="stat",
config={"message": "garage.temp"},
),
],
)
)