Pin a chart to the last few runs of a flow
This commit is contained in:
@@ -33,6 +33,10 @@ DASHBOARD_DIR = "_dashboards"
|
||||
#: A chart cannot ask for an unbounded series; this is the ceiling.
|
||||
HISTORY_CAP = 5000
|
||||
|
||||
#: How many runs one pinned chart puts side by side. The client's own
|
||||
#: ``MAX_SERIES``: the palette is five steps, and a sixth line repeats one.
|
||||
RUN_LINES = 5
|
||||
|
||||
#: How many readings one bar draws. Mirrored in the client
|
||||
#: (``ui/core/config.ts``, ``MAX_ROWS``).
|
||||
#:
|
||||
@@ -207,6 +211,17 @@ class WidgetDef(BaseModel):
|
||||
"""A chart that asks a flow for its series instead of reading the ring."""
|
||||
return self.type == "chart" and self.config.get("source") == "query"
|
||||
|
||||
@property
|
||||
def _runs_chart(self) -> bool:
|
||||
"""A chart pinned to runs: it reads the run tables, not the engine.
|
||||
|
||||
The other way round from opening a dashboard against a run — that is a
|
||||
way of looking at a page, this is a tile that always shows the last
|
||||
few runs of something, which is what a wall panel over a lab bench
|
||||
wants.
|
||||
"""
|
||||
return self.type == "chart" and self.config.get("source") == "runs"
|
||||
|
||||
@property
|
||||
def inner_bindings(self) -> Bindings:
|
||||
"""A bar's nested readings, as documents written before rows carry them.
|
||||
@@ -245,6 +260,10 @@ class WidgetDef(BaseModel):
|
||||
@property
|
||||
def messages(self) -> list[str]:
|
||||
"""Every message name this widget reads."""
|
||||
if self._runs_chart:
|
||||
# Its metric is a run's recorded series, which no live message
|
||||
# carries — nothing for the engine to route or to keep.
|
||||
return []
|
||||
if self._query_chart:
|
||||
name = self.config.get("message")
|
||||
return [str(name)] if name else []
|
||||
@@ -280,7 +299,7 @@ class WidgetDef(BaseModel):
|
||||
Nothing, for a chart that queries: the answer carries its own past, so
|
||||
asking the engine to keep a ring as well would store it twice.
|
||||
"""
|
||||
if self.type != "chart" or self._query_chart:
|
||||
if self.type != "chart" or self._query_chart or self._runs_chart:
|
||||
return 0
|
||||
points = int((self.config.get("history") or {}).get("points") or 0)
|
||||
return min(points, HISTORY_CAP)
|
||||
@@ -304,6 +323,21 @@ class WidgetDef(BaseModel):
|
||||
@model_validator(mode="after")
|
||||
def _check_binding(self) -> WidgetDef:
|
||||
"""Refuse a widget wired to a message it cannot carry."""
|
||||
if self._runs_chart:
|
||||
runs = self.config.get("runs") or {}
|
||||
if not runs.get("metric"):
|
||||
raise ValueError("a chart of runs must name the metric it draws")
|
||||
if not (runs.get("ids") or runs.get("group") or runs.get("flow")):
|
||||
raise ValueError(
|
||||
"a chart of runs must say which: a flow, a sweep, or run ids"
|
||||
)
|
||||
latest = int(runs.get("latest") or 1)
|
||||
if not 1 <= latest <= RUN_LINES:
|
||||
raise ValueError(f"a chart draws between 1 and {RUN_LINES} runs")
|
||||
if len(runs.get("ids") or []) > RUN_LINES:
|
||||
raise ValueError(f"a chart draws at most {RUN_LINES} runs")
|
||||
return self
|
||||
|
||||
if self._query_chart:
|
||||
for key, want in (("dtype", "series"), ("request_dtype", "record")):
|
||||
bound = str(self.config.get(key) or "")
|
||||
@@ -818,6 +852,7 @@ def results_dashboard(flow: FlowDef) -> DashboardDef:
|
||||
|
||||
__all__ = [
|
||||
"BAR_ROWS",
|
||||
"RUN_LINES",
|
||||
"COLOR_DTYPES",
|
||||
"DASHBOARD_DIR",
|
||||
"HISTORY_CAP",
|
||||
|
||||
Reference in New Issue
Block a user