Skip to content

feat(dashboards): configurable auto-refresh interval - #511

Merged
Makisuo merged 4 commits into
mainfrom
feat/dashboard-auto-refresh-interval
Aug 17, 2026
Merged

feat(dashboards): configurable auto-refresh interval#511
Makisuo merged 4 commits into
mainfrom
feat/dashboard-auto-refresh-interval

Conversation

@Makisuo

@Makisuo Makisuo commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

What

A Grafana-style auto-refresh cadence dropdown next to Reload, on both the signed-in dashboard and the share page. Options come from the existing closed set: Off / 5s / 10s / 30s / 1m / 5m / 15m.

Why

Dashboards only refreshed when someone clicked Reload, so a board left on a wall monitor went stale.

Most of this already existed

A previous change landed the persistence and refresh machinery but never shipped the control. All of the following had zero callers before this PR:

  • refreshIntervalSeconds on the dashboard document + its literal set (packages/widgets/src/dashboard/shared/variables.ts)
  • the refresh_interval_seconds v2 wire field and its API read/write
  • updateDashboardRefreshInterval in use-dashboard-store.ts
  • the "Auto-refresh set to Ns" version-history label
  • PageRefreshProvider's autoRefreshMs / autoRefreshPaused props

So the diff is mostly wiring plus one new component. No fetch-layer changes were needed: PageRefreshProvider.reload() already bumps refreshVersion, which unsnaps the window off the 15s cache grid and fans out to every mounted tile atom.

Behaviour

Resolution per viewer: ?refresh= → the board's saved default → off.

  • Picking a cadence always writes ?refresh=, so a read-only viewer can start or silence auto-refresh without dirtying the document.
  • In edit mode it additionally saves the board's default — the one path that cuts a version.
  • ?refresh=0 is a real value: a viewer can silence a board that auto-refreshes for everyone else.
  • Ticks pause while editing or previewing a version, and the pre-existing document.hidden guard keeps an idle tab from polling.

Reviewer notes

refresh is a number, not a string. Written as a string, TanStack JSON-quotes it to preserve string-ness and the URL reads ?refresh=%225%22 — ugly for a link people paste around. The schema accepts both arms and anything outside the literal set falls back rather than failing the route, so a hand-edited ?refresh=abc or ?refresh=1 is safe. This makes refresh the only numeric control param, which is why pickDashboardControlParams needs the explicit numeric branch — the string-only loop would otherwise silently drop it on the next navigate.

Share-page window bug fixed along the way. resolveShareWindow's result was memoized immutably, so a relative share would have re-fetched an identical window on every tick forever. It now re-resolves unsnapped once refreshed, matching dashboard-time-range-atoms.ts:77.

Redaction. Single-widget shares now carry refreshIntervalSeconds too; only the whole-board branch did. It is a numeric literal from a closed set, so it leaks nothing a viewer could not infer from watching the tile update.

Verification

Ran against local web + API with real data:

  • Ticks fire on cadence and the window advances — the chart's axis moved from 04:56 AM–01:28 PM to 05:02 AM–01:34 PM.
  • ?refresh=5 survives the edit-mode toggle.
  • Picking 30s in edit mode persisted, cut a version reading "Auto-refresh set to 30s", and a bare URL then loads at 30s.
  • The preview pane reported document.hidden === true and fired nothing until visibility was stubbed, confirming the hidden-tab guard.
  • The share link inherited 30s from the board, ?refresh=5 overrode it, and ?embed=true drew no control while still polling.

Tests: 22 web (3 new, and I restored 3 pre-existing page-refresh-context tests I had clobbered mid-session — that file's diff is now purely additive) + 163 widgets passing. bun typecheck and oxlint clean.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Dashboards only refreshed when a viewer clicked Reload, so a board left
on a wall monitor went stale. Add a Grafana-style cadence dropdown next
to Reload, on both the signed-in board and the share page.

Nearly all the machinery already existed with no callers: the
`refreshIntervalSeconds` document field and its closed literal set, the
v2 wire field, the version-history label, `updateDashboardRefreshInterval`,
and `PageRefreshProvider`'s `autoRefreshMs`/`autoRefreshPaused` props.
This wires them up and adds the control.

Resolution is `?refresh=` (per viewer) → the board's saved default → off.
Picking a cadence always writes the param, so a read-only viewer can
start or silence auto-refresh without dirtying the document; in edit mode
it additionally saves the board default, the one path that cuts a version.
Ticks pause while editing or previewing a version, and the existing
hidden-tab guard keeps an idle board from polling.

`refresh` is written as a number so URLs read `?refresh=30` rather than
the `?refresh="30"` TanStack emits to preserve string-ness; the schema
accepts both, and anything outside the literal set falls back instead of
failing the route.

On the share page the resolved window was memoized immutably, so a
relative share would have re-fetched an identical window forever. It now
re-resolves unsnapped on each tick, matching the signed-in board. Single
-widget shares also keep the cadence through redaction, which previously
only the whole-board branch carried.
The trigger was a bare clock icon, sitting next to a time-range picker
that carries a clock icon of its own — so it read as a second time
control rather than as auto-refresh.

Three changes, all about naming the thing:

- The glyph is now the same reload arrow as the Reload button beside it,
  so the pair reads as one control: reload now, or reload every N.
- The trigger is always labelled "Auto", not just when a cadence is set.
  An icon-only button left the viewer guessing what it did.
- The menu carries an "Auto-refresh" group label, so the control explains
  itself on open — including for screen readers, and for a viewer who
  arrived straight from a `?refresh=` link.

Adds a render test. Base UI's GroupLabel throws production error #31
outside a Group — a full error-boundary crash, and only reachable by
actually opening the menu — so a menu with a label needs a render test
rather than a type check.
Reload and the cadence dropdown were two separate outline buttons sitting
next to each other, which is what made the cadence half ambiguous — an
adjacent control is just another control, and beside the time-range
picker it read as a second time control.

Grafana attaches them, and attachment is what carries the meaning: "30s"
welded to "Reload" can only be read one way. So the two halves are now
one split button sharing a seam, and the cadence half is back to showing
just the interval (or "Off") rather than explaining itself with a word.

`RefreshControls` takes the reload action as a prop and `PageRefreshControls`
binds it to the page refresh context, because the share page drives its
own refresh and has no such context. `ReloadControls` stays as-is for
traces, metrics, service-map and infra, which have no cadence to offer.
react-grid-layout's `containerPadding` defaults to `margin`, so the grid
padded its own outside edge with a full gutter on top of whatever padding
the page had already applied. Tiles ended up 12px inside everything
stacked above them — section headers, the page title, and on a shared
board the time-range label and refresh controls sitting directly over a
left edge that did not line up with them.

The gutter belongs between tiles; the surrounding layout owns the outer
padding. Horizontal container padding is now zero, so a tile's left edge
meets its container. Vertical keeps the margin, leaving the gap under a
section header exactly as it was.

Tiles gain a gutter of width on each side as a result, which is the
correct amount of room and not a resize: the column count and every
stored layout are untouched.
@Makisuo
Makisuo merged commit a757ae5 into main Aug 17, 2026
109 of 114 checks passed
@Makisuo
Makisuo deleted the feat/dashboard-auto-refresh-interval branch August 17, 2026 16:26
@Makisuo
Makisuo deployed to pr-preview August 17, 2026 16:26 — with GitHub Actions Active
@github-actions

Copy link
Copy Markdown

🍁 Maple PR preview

Note

Preview resources were removed when this pull request closed.

Final commit 21eae25 · View workflow run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant