refactor: unify SSE+SWR data flow with useSSECacheSync hook#1719
refactor: unify SSE+SWR data flow with useSSECacheSync hook#1719
Conversation
…nflict popups Replace per-component dual SSE+SWR coordination (isPaused, manual cache sync, data merge) with a shared useSSECacheSync hook that pushes SSE data into SWR's cache. This fixes false "external changes" popups after saves, stale data on SSE reconnect, and remote node SSE timeouts.
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis pull request refactors frontend data-fetching patterns across multiple components to use a unified SSE-with-polling-fallback mechanism. A new Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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 |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
ui/src/features/dag-runs/components/dag-run-details/DAGRunDetailsPanel.tsx (1)
139-143:⚠️ Potential issue | 🟠 MajorAvoid replacing panel content with
LoadingIndicatoron transient revalidation.Line 139 now gates rendering on
!data, so the panel can blank out and show a full loading indicator during updates/switches instead of retaining stale content.💡 Proposed fix (keep last renderable data)
- const data = isSubDAGRun ? subDAGQuery.data : dagRunQuery.data; + const data = isSubDAGRun ? subDAGQuery.data : dagRunQuery.data; + const lastDataRef = React.useRef<typeof data>(undefined); + if (data) { + lastDataRef.current = data; + } + const displayData = data ?? lastDataRef.current; @@ - if (!data) { + if (!displayData) { return ( <div className="flex items-center justify-center h-full"> <LoadingIndicator /> </div> ); } @@ - dagRun={data.dagRunDetails} + dagRun={displayData.dagRunDetails}Based on learnings: Applies to
ui/**/*.{ts,tsx}: "NEVER use full-page loading overlays or LoadingIndicator components that hide content; instead show stale data while fetching updates".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ui/src/features/dag-runs/components/dag-run-details/DAGRunDetailsPanel.tsx` around lines 139 - 143, The component DAGRunDetailsPanel currently returns a full-screen LoadingIndicator when data is falsy, causing the panel to blank during transient revalidation; instead preserve and render the last known data while fetching and only show the LoadingIndicator when there is genuinely no prior data. Change the render guard around data in DAGRunDetailsPanel to keep stale data: track previousData (via a useRef or useState) or use the query hook's isLoading/isFetching flags, render previousData when data is undefined but previousData exists, and only render the full LoadingIndicator when neither data nor previousData exist; remove any logic that unmounts panel content on transient revalidation.ui/src/features/dags/components/dag-details/DAGDetailsPanel.tsx (1)
166-170:⚠️ Potential issue | 🟠 MajorDo not replace the details panel with
LoadingIndicator.Line 169 hides panel content behind a loading state. Preserve stale content and use inline loading cues during refreshes instead of swapping out the panel.
As per coding guidelines
ui/**/*.{ts,tsx}: “NEVER use full-page loading overlays or LoadingIndicator components that hide content; instead show stale data while fetching updates”.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ui/src/features/dags/components/dag-details/DAGDetailsPanel.tsx` around lines 166 - 170, The current DAGDetailsPanel returns a full-panel LoadingIndicator when data?.dag is missing, which hides stale content; instead modify DAGDetailsPanel to never replace the panel with a full-page loader—render the existing panel UI even when data?.dag is undefined or being refreshed and show inline refresh/loading cues (e.g., a small spinner or skeleton in the header/fields) while fetching. Locate the early-return that checks data?.dag and remove it; ensure the component renders the same container/structure and conditionally displays inline loading state based on the fetch status (use the existing data, a stale fallback, or isFetching flag) so the panel content remains visible during updates.ui/src/pages/dags/index.tsx (1)
314-348:⚠️ Potential issue | 🟠 MajorAvoid hiding the panel behind
LoadingIndicatoron missing data.Line 347 fully replaces the list content with a loading indicator. Keep stale/previous list content visible and use inline loading affordances instead of swapping out the panel content.
As per coding guidelines
ui/**/*.{ts,tsx}: “NEVER use full-page loading overlays or LoadingIndicator components that hide content; instead show stale data while fetching updates”.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ui/src/pages/dags/index.tsx` around lines 314 - 348, The current conditional replaces the entire panel with <LoadingIndicator /> when data is missing; instead always render the existing panel (DAGErrors and DAGTable) and only show an inline loading affordance when isLoading is true. Concretely, remove the outer ternary that returns <LoadingIndicator /> and render DAGErrors and DAGTable unconditionally (using data.dags || [] and data.errors || [] as now), and add an inline spinner or disabled/loading state inside DAGTable or adjacent to its header when isLoading is true; if data can be undefined during fetch, keep previous data in local state (e.g., a retainedDags/ref) or render empty arrays rather than hiding the panel so stale content remains visible while fetching.
🧹 Nitpick comments (1)
ui/src/features/dags/components/dag-editor/DAGSpec.tsx (1)
185-187: Await post-save revalidation to reduce stale flashes.Line 186 triggers revalidation but does not await completion. Awaiting here gives a cleaner post-save state transition.
Proposed patch
- mutateSpec(); + await mutateSpec();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ui/src/features/dags/components/dag-editor/DAGSpec.tsx` around lines 185 - 187, The code calls mutateSpec() to revalidate the SWR cache but does not await it, causing potential stale UI flashes; update the post-save flow (where mutateSpec() is invoked, e.g., in the save handler or function that performs the save) to await the revalidation by using await mutateSpec() (or return the promise) so the UI waits for the server-side revalidation to complete before transitioning out of the saving state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@ui/src/features/dags/components/dag-editor/DAGSpec.tsx`:
- Around line 101-105: The SSE cache transform currently forces spec to an empty
string which overwrites the existing cached spec when the SSE payload omits it;
update the transform passed to useSSECacheSync (the call using sseResult and
mutateSpec) so that spec is only set when sseData.spec is defined (e.g., leave
it undefined or omit the field when sseData.spec === undefined) to preserve the
cached value and avoid false external-modification conflicts in
useContentEditor; reference the useSSECacheSync call and the sseData.spec field
from the DAGSSEResponse shape when making this change.
In `@ui/src/hooks/useSSECacheSync.ts`:
- Around line 39-52: The effect only re-maps SSE data when the data reference
changes, so when transform changes the SWR cache can become stale; inside the
useEffect that references prevDataRef, add a prevTransformRef
(useRef<TransformType | null>) and update it alongside prevDataRef, and change
the condition to also trigger when prevTransformRef.current !== transform (or
when transform is non-null and differs) so you recompute cacheData = transform ?
transform(sseResult.data) : sseResult.data and call mutate; also add transform
to the dependency list (it already is) and ensure you set
prevTransformRef.current = transform after mutating to avoid repeated work.
---
Outside diff comments:
In `@ui/src/features/dag-runs/components/dag-run-details/DAGRunDetailsPanel.tsx`:
- Around line 139-143: The component DAGRunDetailsPanel currently returns a
full-screen LoadingIndicator when data is falsy, causing the panel to blank
during transient revalidation; instead preserve and render the last known data
while fetching and only show the LoadingIndicator when there is genuinely no
prior data. Change the render guard around data in DAGRunDetailsPanel to keep
stale data: track previousData (via a useRef or useState) or use the query
hook's isLoading/isFetching flags, render previousData when data is undefined
but previousData exists, and only render the full LoadingIndicator when neither
data nor previousData exist; remove any logic that unmounts panel content on
transient revalidation.
In `@ui/src/features/dags/components/dag-details/DAGDetailsPanel.tsx`:
- Around line 166-170: The current DAGDetailsPanel returns a full-panel
LoadingIndicator when data?.dag is missing, which hides stale content; instead
modify DAGDetailsPanel to never replace the panel with a full-page loader—render
the existing panel UI even when data?.dag is undefined or being refreshed and
show inline refresh/loading cues (e.g., a small spinner or skeleton in the
header/fields) while fetching. Locate the early-return that checks data?.dag and
remove it; ensure the component renders the same container/structure and
conditionally displays inline loading state based on the fetch status (use the
existing data, a stale fallback, or isFetching flag) so the panel content
remains visible during updates.
In `@ui/src/pages/dags/index.tsx`:
- Around line 314-348: The current conditional replaces the entire panel with
<LoadingIndicator /> when data is missing; instead always render the existing
panel (DAGErrors and DAGTable) and only show an inline loading affordance when
isLoading is true. Concretely, remove the outer ternary that returns
<LoadingIndicator /> and render DAGErrors and DAGTable unconditionally (using
data.dags || [] and data.errors || [] as now), and add an inline spinner or
disabled/loading state inside DAGTable or adjacent to its header when isLoading
is true; if data can be undefined during fetch, keep previous data in local
state (e.g., a retainedDags/ref) or render empty arrays rather than hiding the
panel so stale content remains visible while fetching.
---
Nitpick comments:
In `@ui/src/features/dags/components/dag-editor/DAGSpec.tsx`:
- Around line 185-187: The code calls mutateSpec() to revalidate the SWR cache
but does not await it, causing potential stale UI flashes; update the post-save
flow (where mutateSpec() is invoked, e.g., in the save handler or function that
performs the save) to await the revalidation by using await mutateSpec() (or
return the promise) so the UI waits for the server-side revalidation to complete
before transitioning out of the saving state.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
internal/service/frontend/sse/handler.gointernal/service/frontend/sse/proxy.goui/src/features/dag-runs/components/dag-run-details/DAGRunDetailsPanel.tsxui/src/features/dags/components/dag-details/DAGDetailsModal.tsxui/src/features/dags/components/dag-details/DAGDetailsPanel.tsxui/src/features/dags/components/dag-editor/DAGSpec.tsxui/src/features/dags/components/dag-execution/DAGExecutionHistory.tsxui/src/hooks/useLastValidData.tsui/src/hooks/useSSECacheSync.tsui/src/pages/dag-runs/index.tsxui/src/pages/dags/dag/index.tsxui/src/pages/dags/index.tsxui/src/pages/docs/components/DocEditor.tsxui/src/pages/docs/index.tsxui/src/pages/index.tsxui/src/pages/queues/index.tsx
💤 Files with no reviewable changes (1)
- ui/src/hooks/useLastValidData.ts
Add centralized SSEManager singleton that manages EventSource instances with ref-counted deduplication, a cap of 3 concurrent connections with LRU eviction, and a 15s connect timeout for browser-queued connections. Evicted or timed-out connections gracefully fall back to SWR polling via the existing sseFallbackOptions mechanism.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1719 +/- ##
=======================================
Coverage 69.52% 69.52%
=======================================
Files 396 396
Lines 43783 43783
=======================================
Hits 30440 30440
+ Misses 10894 10891 -3
- Partials 2449 2452 +3 see 9 files with indirect coverage changes Continue to review full report in Codecov by Sentry.
🚀 New features to boost your workflow:
|
Replace per-component dual SSE+SWR coordination (isPaused, manual cache sync, data merge) with a shared useSSECacheSync hook that pushes SSE data into SWR's cache. This fixes false "external changes" popups after saves, stale data on SSE reconnect, and remote node SSE timeouts.
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes & Improvements