fix(app): prevent dashboard edits from clobbering each other#2574
fix(app): prevent dashboard edits from clobbering each other#2574rachit367 wants to merge 3 commits into
Conversation
useUpdateDashboard had no optimistic cache update, so the dashboard read from the ['dashboards'] query cache stayed stale until refetch. Two consecutive setDashboard(produce(dashboard, ...)) calls both derived from the same pre-mutation snapshot, so the second PATCH overwrote the first and the earlier change was lost on reload. Optimistically update the dashboards cache in onMutate (rollback in onError, invalidate in onSettled) so a following produce reads the latest state. Fixes hyperdxio#2216
|
@rachit367 is attempting to deploy a commit to the HyperDX Team on Vercel. A member of the Team first needs to authorize it. |
🦋 Changeset detectedLatest commit: 3bc9c36 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Greptile SummaryThis PR fixes a silent data-loss race condition in remote dashboard saves by adding standard TanStack Query optimistic updates to
Confidence Score: 5/5Safe to merge — the change is a well-scoped addition of standard optimistic-update hooks with no modifications to the mutation logic, API contract, or local-mode path. The fix is minimal and mechanical: three lifecycle callbacks added to one useMutation call, each doing exactly what the TanStack Query documentation prescribes. The snapshot is correctly captured before the optimistic write and restored on error; onSettled ensures the cache is reconciled with the server regardless of outcome. No files require special attention. Important Files Changed
Reviews (3): Last reviewed commit: "refactor(app): type the onError rollback..." | Re-trigger Greptile |
Deep Review✅ No critical issues found. The change correctly applies the standard TanStack Query optimistic-update pattern and genuinely fixes the primary #2216 failure (permanent silent loss on reload). The findings below are residual-race and test-coverage recommendations; the server-side partial 🟡 P2 — recommended
🔵 P3 nitpicks (5)
Pre-existing (not introduced by this diff; do not block merge):
Reviewers (10): correctness, testing, maintainability, project-standards, julik-frontend-races, kieran-typescript, reliability, adversarial, agent-native, learnings-researcher. Testing gaps: No test interleaves two genuinely in-flight mutations with an |
Address review feedback on the hyperdxio#2216 fix: - back the mock query client with an in-memory cache and add a test that drives two sequential onMutate calls, proving both edits survive instead of the second clobbering the first - assert setQueryData targets the ['dashboards'] key - cover the empty-cache path so onMutate no longer writes undefined (guarded in the implementation)
|
Thanks for the thorough review. Addressed the test-coverage feedback (@greptile-apps, deep-review):
On the same-tick batching note (P2): that's a real but separate edge — the reachable reported scenario is two edits as distinct user actions (separate renders), which this resolves. Deriving each PATCH from 5 tests pass, existing 42 dashboard tests still green, tsc + eslint clean. |
|
The changes look solid. The implementation is clean and correct, and the test suite now genuinely exercises the fix rather than just testing wiring. Implementation ( Tests: The three-part coverage is exactly right:
One minor observation: the rollback in Otherwise this is ready to go. The fix is correct, minimal, and well-tested. |
Match the typed setQueryData<Dashboard[]> used in onMutate, per review.
|
Thanks @greptile-apps! Added the |
|
Good change — adding |
Fixes #2216
Why
Two
setDashboard(...)calls in quick succession on a remote dashboard (e.g. toggle bordered on group A, then add a tab to group B) can leave the backend with only the second mutation's effect. The first change appears to save in the UI but is lost on reload — silent data loss.Root cause
setDashboardcallsupdateDashboard.mutate(newDashboard)with no optimistic cache update, so the component keeps readingdashboardfrom the['dashboards']React Query cache untilinvalidateQueries -> refetchcompletes. Two consecutivesetDashboard(produce(dashboard, ...))calls bothproducefrom the same pre-mutation snapshot, so the secondPATCHbody derives from state that doesn't include the first mutation's change and overwrites it.Fix
Add optimistic updating to
useUpdateDashboard(the maintainer-suggested Option 1, the smallest change and the standard TanStack Query pattern):onMutate: cancel in-flight['dashboards']queries, snapshot the current cache, and merge the pending change into the cached dashboard so the nextproducereads up-to-date state.onError: roll the cache back to the snapshot.onSettled: invalidate['dashboards']to reconcile with the server (replaces the previousonSuccessinvalidate so it also runs after a rollback).Tests
New
dashboard.optimistic.test.ts:onMutatewrites the pending change into the cache (and leaves sibling dashboards untouched);onErrorrestores the previous snapshot;onSettledinvalidates.All 3 pass; the existing 42 dashboard tests still pass;
tsc --noEmitand eslint are clean on the changes. Changeset included.