Give both summaries a modified time, and the dashboard list a footprint
Home's "recently modified" order was a proxy — drafts first, then the version counter, then the name. Both summaries now carry `updated_at`, read as the mtime of the working copy: every write in the store commits immediately, so a file's mtime is its commit time, and `git log -1 -- <path>` costs ~990ms across this instance's 15 documents (it walks the history back to the last commit touching each one, so it is slowest for the stalest) against ~1.8ms for the stats. No cache needed. `DashboardSummary` also carries `footprint`: each widget as its type plus the placement a panel resolves, which is the whole of what the mosaic draws. That drops the document fetch per tile and with it the cap of eight, past which tiles showed a name and nothing else. Verified against this instance's 8 dashboards: the blocks are identical to what the old client-side derivation produced. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CL9zvnnvcp1mvA8o7impxk
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
"""Dashboards: documents beside the flows, and the values their widgets move."""
|
||||
|
||||
from datetime import UTC, datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from fluksio.flow.dashboards import (
|
||||
DashboardDef,
|
||||
DashboardNotFound,
|
||||
DashboardStore,
|
||||
Placement,
|
||||
SettingDef,
|
||||
WidgetDef,
|
||||
default_dashboard,
|
||||
@@ -389,6 +392,53 @@ def test_a_querying_chart_keeps_no_ring():
|
||||
assert widget.history_points == 0
|
||||
|
||||
|
||||
def test_a_summary_carries_the_footprint_of_the_document(store: DashboardStore):
|
||||
"""The mosaic draws from the list, so the shapes have to survive it."""
|
||||
saved = store.write(
|
||||
DashboardDef(
|
||||
name="house",
|
||||
columns=8,
|
||||
widgets=[
|
||||
WidgetDef(
|
||||
id="temp",
|
||||
type="chart",
|
||||
layout={"lg": Placement(x=2, y=1, w=6, h=4)},
|
||||
),
|
||||
# Only a narrower breakpoint, and none at all: both resolve the
|
||||
# way a panel resolves them.
|
||||
WidgetDef(id="power", type="gauge", layout={"md": Placement(y=5, w=2)}),
|
||||
WidgetDef(id="lamp", type="switch"),
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
(summary,) = store.list()
|
||||
|
||||
assert summary.columns == saved.columns
|
||||
assert [(f.type, f.x, f.y, f.w, f.h) for f in summary.footprint] == [
|
||||
("chart", 2, 1, 6, 4),
|
||||
("gauge", 0, 5, 2, 2),
|
||||
("switch", 0, 0, 3, 2),
|
||||
]
|
||||
|
||||
|
||||
def test_a_summary_carries_when_the_working_copy_was_written(store: DashboardStore):
|
||||
published = store.write(default_dashboard("house"))
|
||||
document = store.root / "house" / "dashboard.json"
|
||||
|
||||
assert store.list()[0].updated_at == datetime.fromtimestamp(
|
||||
document.stat().st_mtime, UTC
|
||||
)
|
||||
|
||||
# An unpublished edit is the working copy, so it is what the time is of.
|
||||
store.write_draft(published.model_copy(update={"title": "Kitchen"}), 1)
|
||||
draft = store.root / "house" / "dashboard.draft.json"
|
||||
|
||||
assert store.list()[0].updated_at == datetime.fromtimestamp(
|
||||
draft.stat().st_mtime, UTC
|
||||
)
|
||||
|
||||
|
||||
def test_a_dashboard_is_cut_into_a_sane_number_of_columns():
|
||||
for columns in (0, 49):
|
||||
with pytest.raises(ValueError):
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import os
|
||||
import subprocess
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
@@ -56,6 +58,24 @@ def test_saving_unchanged_content_does_nothing(store: FlowStore):
|
||||
assert commit_count(store) == commits
|
||||
|
||||
|
||||
def test_a_flow_is_modified_when_only_its_node_code_is(store: FlowStore):
|
||||
"""Node code is saved without touching the flow document."""
|
||||
store.write_flow(a_flow())
|
||||
# Backdated so the answer can only have come from the node file.
|
||||
os.utime(store.root / "heating" / "flow.json", (0, 0))
|
||||
|
||||
store.write_node_source(
|
||||
"heating", "sensor", "def process():\n return {'a': 1}\n"
|
||||
)
|
||||
|
||||
updated = store.updated_at("heating")
|
||||
assert updated is not None and updated > datetime.fromtimestamp(0, UTC)
|
||||
|
||||
|
||||
def test_a_flow_that_is_not_there_has_no_modified_time(store: FlowStore):
|
||||
assert store.updated_at("nope") is None
|
||||
|
||||
|
||||
def test_missing_flow_is_reported(store: FlowStore):
|
||||
with pytest.raises(FlowNotFound):
|
||||
store.read_flow("nope")
|
||||
|
||||
Reference in New Issue
Block a user