fix(dashboard): only the visible page auto-refreshes (#1419) - #1474
Conversation
Every page a user had visited kept polling, so query load scaled with browsing history instead of with what was on screen. Measured on Chart Reference, same page and same 18 visible widgets both times: only page 1 opened 18 tiles 16 POSTs / 40s after touring 6 pages 86 tiles 77 POSTs / 40s 4.81x the database traffic for an identical view. That load is refresh-tier work against the customer's database — the exact category the scheduler exists to shed — so one user browsing a large dashboard could crowd out interactive queries for everyone on that connector. `isActive` was already computed, already used two lines above for the `hidden` class and one line below to gate `onLayoutChange`. It just never gated the refresh. Pages stay mounted, which is deliberate: tab switches remain instant and do not re-query. They simply stop polling while hidden. Scope: this is part one of the issue. The unbounded mount cache — 131 tiles and 87 canvases after 11 tabs, 146.5 MB peak heap — is a separate behaviour change with a state-loss edge case around unsaved form input, and is tracked separately. The E2E was verified in both directions. Against the unfixed build it fails with "7 queries after touring vs 2 on a fresh load", on the initial run and the retry; with the fix it passes. An earlier version of it passed either way: all three pages shared one query, and use-widget-query keys on (connection, database, query, params, staleTime) with no widget id, so TanStack collapsed them into a single fetch. The pages now query distinct data, and the reason is commented in the fixture. Refs #1419 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
WalkthroughDashboard refresh polling now runs only for the active visible page. Mounted hidden pages stop polling, while edit mode disables polling for all pages. Unit and end-to-end tests cover navigation, visibility, and query request counts. ChangesHidden page polling
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related issues
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…-gate-refresh-on-active # Conflicts: # CHANGELOG.md
|



Refs #1419 — part one of two. Not
Closes: the unbounded mount cache is deliberately left out, see Scope.The problem
Every dashboard page a user had ever visited kept auto-refreshing. Query load scaled with browsing history rather than with what was on screen. Sitting on the same page, with the identical 18 visible widgets:
/api/queryPOSTs in 40s4.81× the database traffic for the same view. Tour all 20 pages of Chart Reference and leave the tab open and ~230 widgets keep polling.
This is not a rendering defect — it is load the product generates against the customer's database for no user-visible benefit, and it is refresh-tier work, the exact category the query scheduler exists to shed. One user exploring a large dashboard could crowd out interactive queries for everyone else on that connector.
The fix
One condition.
isActivewas already computed, already used two lines above for thehiddenclass and one line below to gateonLayoutChange— it just never gated the refresh.Pages stay mounted, which is deliberate: tab switches stay instant and don't re-query. They simply stop polling while hidden. On return, the page refreshes on the next tick, and within the 5-minute
staleTimeit renders from cache with no network call at all.Scope: part two is separate
The issue's second defect — the mount cache that never evicts (131 tiles, 87 canvases, 146.5 MB peak heap after 11 tabs) — is not here. It is a behaviour change with a real state-loss edge case: widget-local React state does not survive unmount, so a user mid-way through a Form widget would lose their typed input to a tab switch. Layout edits and parameter values are safe (workspace state and Zustand stores respectively), but form input is not.
That deserves its own review and its own E2E. This half removes the entire 4.8× on its own and carries essentially no risk, so it should not wait.
The E2E was verified in both directions
Against the unfixed build it fails, on the initial run and the retry:
With the fix it passes. That check mattered, because the first version of this test passed against the bug:
The fixture now gives each page a distinct query, and the reason is commented there so nobody "simplifies" it back.
Worth knowing independently: identical widgets anywhere on a dashboard share one query and one refresh. That softens this bug for dashboards that repeat a query across pages — the 4.81× measurement holds because Chart Reference's pages genuinely differ.
Tests
DashboardWorkspace: only the active page carries the interval; returning to page 1 leaves exactly one poller among three mounted pages; the count never depends on how many pages were toured; edit mode stays off throughout. The first three fail today, reporting['30000','30000','30000']./api/queryPOSTs over a fixed window on page 1, tours the other pages, returns, and counts again over an identical window. Same visible view, same count.Verification
appunit 64/64 in the affected file · targeted E2E green (27s) · typecheck + lint cleanNote
e2e/global-setup.tsskipsnext buildwhenever.next/BUILD_IDexists, so a cached build from another branch is used silently. Every E2E run above needed an explicitrm .next/BUILD_IDfirst — otherwise it tests stale code and reports a meaningless pass. Worth its own fix; the auto-detect should key on the current commit rather than on a file existing.🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Tests