Refetch dashboard panels on the shared polling cadence

PanelPreview only refetched its PromQL series when panel.promql,
chart_type, or range bounds changed, so panels went stale under a
fixed range or between rolling-preset ticks that didn't move the
window. Add a POLL_MS-driven tick folded into the same fetch effect
(alongside the existing deps) so it refreshes on the same cadence as
App.tsx's log/timeline polling without double-fetching when a range
change and a tick coincide. POLL_MS is now shared via
webapp/src/lib/polling.ts instead of living only in App.tsx.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
This commit is contained in:
Hugh Kaznowski
2026-09-05 19:33:56 +01:00
co-authored by Claude Sonnet 5
parent e6e5bc8f50
commit 68a18a286f
3 changed files with 20 additions and 4 deletions
+1 -2
View File
@@ -19,8 +19,7 @@ import {
type TimelineBucket,
} from './lib/api'
import { DocsNavProvider } from './lib/docsNav'
const POLL_MS = 2000
import { POLL_MS } from './lib/polling'
/// Reads the routing params `PopOutButton` writes: `section` picks which primary section to render full-page, and `startMs`/`endMs` carry over the range for sections with no `LogTimeline` of their own to pick one.
function parsePopOutQuery(): { section: PopOutSectionId | null; range: TimeRange | null } {
+15 -2
View File
@@ -14,6 +14,7 @@ import {
type Panel,
type PromqlSeries,
} from '../lib/api'
import { POLL_MS } from '../lib/polling'
import {
CollapseToggle,
PopOutButton,
@@ -40,11 +41,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<PromqlSeries[]>([])
const [error, setError] = useState<string | null>(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()) {
@@ -71,7 +83,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 <div className="rounded border border-danger-border bg-danger-bg/50 px-2 py-1 text-xs text-danger-text">{error}</div>
+4
View File
@@ -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