feat(dashboards): configurable auto-refresh interval - #511
Merged
Conversation
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.
🍁 Maple PR previewNote Preview resources were removed when this pull request closed. Final commit |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
refreshIntervalSecondson the dashboard document + its literal set (packages/widgets/src/dashboard/shared/variables.ts)refresh_interval_secondsv2 wire field and its API read/writeupdateDashboardRefreshIntervalinuse-dashboard-store.tsPageRefreshProvider'sautoRefreshMs/autoRefreshPausedpropsSo the diff is mostly wiring plus one new component. No fetch-layer changes were needed:
PageRefreshProvider.reload()already bumpsrefreshVersion, 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.?refresh=, so a read-only viewer can start or silence auto-refresh without dirtying the document.?refresh=0is a real value: a viewer can silence a board that auto-refreshes for everyone else.document.hiddenguard keeps an idle tab from polling.Reviewer notes
refreshis 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=abcor?refresh=1is safe. This makesrefreshthe only numeric control param, which is whypickDashboardControlParamsneeds 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, matchingdashboard-time-range-atoms.ts:77.Redaction. Single-widget shares now carry
refreshIntervalSecondstoo; 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:
04:56 AM–01:28 PMto05:02 AM–01:34 PM.?refresh=5survives the edit-mode toggle.document.hidden === trueand fired nothing until visibility was stubbed, confirming the hidden-tab guard.?refresh=5overrode it, and?embed=truedrew no control while still polling.Tests: 22 web (3 new, and I restored 3 pre-existing
page-refresh-contexttests I had clobbered mid-session — that file's diff is now purely additive) + 163 widgets passing.bun typecheckand oxlint clean.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.