diff --git a/tickets/023-dashboards-refresh-with-ui.md b/tickets/023-dashboards-refresh-with-ui.md deleted file mode 100644 index 5c286cd..0000000 --- a/tickets/023-dashboards-refresh-with-ui.md +++ /dev/null @@ -1,11 +0,0 @@ -# Dashboards should refresh when the rest of the UI refreshes - -`App.tsx` polls logs/timeline every `POLL_MS` (2000ms — `src/App.tsx`) and, for rolling range presets, recomputes `range.startMs`/`range.endMs` on each tick. Dashboard panels (`PanelPreview` in `Dashboards.tsx`) only refetch their PromQL series when `panel.promql`, `panel.chart_type`, `range.startMs`, or `range.endMs` change (`useEffect` dependency array, `Dashboards.tsx` ~line 78). So a dashboard panel only refreshes if the rolling range's bounds actually move; with a fixed/absolute range selected, or between rolling-preset ticks where the resolved bounds happen not to change, panels go stale even though the rest of the UI (log table, timeline) is actively re-polling. - -Dashboards should refresh on the same cadence as the rest of the app, not only when their `range` prop happens to change value. - -## Acceptance criteria -- Dashboard panels (`PanelPreview`, and by extension `AllDashboardsView`) refetch their PromQL series on the same polling cadence as `App.tsx`'s log/timeline polling, independent of whether `range.startMs`/`range.endMs` literally changed. -- This applies both to the normal `Dashboards` section and the popped-out dashboard view, if it polls independently. -- No duplicate/overlapping fetches are introduced (e.g. a range change and the periodic tick firing at the same time shouldn't double-fetch). -- Existing refetch-on-range/query/chart-type-change behavior is preserved. diff --git a/webapp/src/App.tsx b/webapp/src/App.tsx index fe4915c..23fbca1 100644 --- a/webapp/src/App.tsx +++ b/webapp/src/App.tsx @@ -20,8 +20,7 @@ import { } from './lib/api' import { DocsNavProvider } from './lib/docsNav' import { useSettingsPanel } from './lib/useSettingsPanel' - -const POLL_MS = 2000 +import { POLL_MS } from './lib/polling' // Height (px) the log viewer had before it became resizable — kept as the default so existing // layouts don't jump on first load after this change. diff --git a/webapp/src/components/Dashboards.tsx b/webapp/src/components/Dashboards.tsx index 4b5ec57..d18a078 100644 --- a/webapp/src/components/Dashboards.tsx +++ b/webapp/src/components/Dashboards.tsx @@ -14,6 +14,7 @@ import { type Panel, type PromqlSeries, } from '../lib/api' +import { POLL_MS } from '../lib/polling' import { CollapseToggle, handleSectionHeaderKeyDown, @@ -44,11 +45,22 @@ function newPanelId(): string { return `panel-${Date.now()}-${Math.random().toString(36).slice(2, 8)}` } -/// Fetches the panel's PromQL series on query/chart-type/range change; a malformed query must render as an inline error rather than crash the page. +/// Fetches the panel's PromQL series on query/chart-type/range change, and also refetches on +/// the same cadence as the rest of the app's polling (`POLL_MS`) so a panel doesn't go stale +/// between rolling-preset ticks or under a fixed range. `tick` is the only thing that interval +/// touches; it's folded into this single effect's dependency array (rather than driving a +/// second, independent fetch effect) so a range change landing on the same tick still results +/// in exactly one fetch instead of two racing ones. function PanelPreview({ panel, range }: { panel: Panel; range: TimeRange | null }) { const [series, setSeries] = useState([]) const [error, setError] = useState(null) const [loading, setLoading] = useState(false) + const [tick, setTick] = useState(0) + + useEffect(() => { + const id = setInterval(() => setTick((t) => t + 1), POLL_MS) + return () => clearInterval(id) + }, []) useEffect(() => { if (!panel.promql.trim()) { @@ -75,7 +87,8 @@ function PanelPreview({ panel, range }: { panel: Panel; range: TimeRange | null return () => { cancelled = true } - }, [panel.promql, panel.chart_type, range?.startMs, range?.endMs]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [panel.promql, panel.chart_type, range?.startMs, range?.endMs, tick]) if (error) { return
{error}
diff --git a/webapp/src/lib/polling.ts b/webapp/src/lib/polling.ts new file mode 100644 index 0000000..95ec77a --- /dev/null +++ b/webapp/src/lib/polling.ts @@ -0,0 +1,4 @@ +/// Shared polling cadence for the whole app — the log/timeline poll in `App.tsx` and any +/// other section that should refresh on the same rhythm (e.g. dashboard panels) import this +/// instead of hardcoding their own interval, so they can never drift apart. +export const POLL_MS = 2000