feat: replace SSE streaming with background job polling for cost recalculation - #5006
Conversation
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 46 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughReplaces the SSE-based recalculation flow with a dialog-driven background job workflow. Adds a job status type, polling endpoint, recalculation dialog, and logs page/header wiring to start jobs, poll status, and refresh on completion. ChangesRecalculate costs job workflow
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant RecalculateCostDialog
participant LogsHeaderView
participant logsApi
User->>RecalculateCostDialog: Open dialog, select mode
RecalculateCostDialog->>LogsHeaderView: onConfirm(mode)
LogsHeaderView->>logsApi: startRecalculateCostJob(filters, mode)
logsApi-->>LogsHeaderView: 202/409 RecalcJobStatus
LogsHeaderView->>logsApi: useGetRecalculateCostStatusQuery(id)
logsApi-->>LogsHeaderView: job status snapshot
LogsHeaderView->>LogsHeaderView: show success/failure toast, refresh logs/stats
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
5e348d6 to
3d43fb2
Compare
36910d0 to
c49e018
Compare
3d43fb2 to
d7cd79a
Compare
c49e018 to
ea50175
Compare
d7cd79a to
4077e3a
Compare
Confidence Score: 4/5The polling and toast orchestration in logsHeaderView.tsx has tricky React effect ordering interactions that leave two known behavioral gaps unaddressed from earlier review rounds. The refactor is architecturally clean and the dialog's guard logic is correct. However, the status-reaction effect (line 147) does not gate on the absence of a poll error, which means a transient HTTP error can overwrite the error toast with a stale loading message. Additionally, a job whose server-side state has been reset to "idle" (e.g., after a service restart before the first poll) is not treated as a terminal condition, leaving the UI polling indefinitely. ui/app/workspace/logs/views/logsHeaderView.tsx — specifically the interplay between the error effect (line 128) and the status-reaction effect (line 147), and the terminal-state check that omits "idle". Important Files Changed
Reviews (12): Last reviewed commit: "feat(logs-ui): recalculate-cost dialog w..." | Re-trigger Greptile |
|
Caution Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted. Error details |
4077e3a to
8429b4f
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
ui/app/workspace/logs/views/logsHeaderView.tsx (2)
61-65: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winConsider
skipPollingIfUnfocusedfor consistency.Every other polling query in this feature (
useGetLogsQuery,useGetLogsStatsQuery,useGetLogsHistogramQueryinpage.tsx) setsskipPollingIfUnfocused: true. This new job-status poll omits it, so it keeps polling every 2s even when the tab is backgrounded.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ui/app/workspace/logs/views/logsHeaderView.tsx` around lines 61 - 65, The new polling call in logsHeaderView’s useGetRecalculateCostStatusQuery is missing the same unfocused-tab behavior used by the other polling queries in page.tsx. Update the job-status polling options in logsHeaderView to include skipPollingIfUnfocused: true alongside the existing pollingInterval and skip settings so the recalculation status query stops polling when the tab is backgrounded.
121-153: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winEffect deps include unstable
fetchLogs/fetchStatsreferences.
fetchLogsandfetchStatsare passed down frompage.tsxas new inline arrow functions on every render, so including them in this effect's dependency array causes it to re-run (and re-issuetoast.loading(...)) far more often than the actual 2s poll cadence, on any unrelated parent re-render.As per coding guidelines, "Avoid unnecessary or unstable dependencies in hooks... keep dependency arrays accurate and minimal."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ui/app/workspace/logs/views/logsHeaderView.tsx` around lines 121 - 153, The `useEffect` in `LogsHeaderView` is depending on unstable `fetchLogs` and `fetchStats` props, causing the toast/update effect to re-run on unrelated parent renders. Remove these inline callback references from the dependency list by making the callbacks stable in the parent `page.tsx` (for example with `useCallback`) or by otherwise ensuring the effect only depends on the actual polling state it uses, then keep the effect dependencies minimal and accurate.ui/app/workspace/logs/views/recalculateCostDialog.tsx (1)
60-71: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd
data-testidto the new interactive controls.The mode-selection buttons and the Cancel/Recalculate footer buttons are new interactive elements with no
data-testid, so E2E specs can't reliably target them.As per coding guidelines, "If you add new interactive elements, add
data-testid."🧪 Proposed fix
<Button variant="outline" size="sm" onClick={() => onOpenChange(false)}> + data-testid="recalculate-cost-cancel-btn" Cancel </Button> - <Button size="sm" onClick={() => onConfirm(mode)} disabled={confirmDisabled}> + <Button data-testid="recalculate-cost-confirm-btn" size="sm" onClick={() => onConfirm(mode)} disabled={confirmDisabled}> Recalculate </Button>And on
RecalculateModeOption's<button>, accept/forward adata-testidprop (e.g.recalculate-cost-mode-missing,recalculate-cost-mode-all).Also applies to: 99-106, 124-142
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ui/app/workspace/logs/views/recalculateCostDialog.tsx` around lines 60 - 71, The new interactive controls in RecalculateCostDialog and RecalculateModeOption need stable test selectors. Update RecalculateModeOption to accept and forward a data-testid on its button, then pass distinct values for the “missing” and “all” mode options from recalculateCostDialog.tsx. Also add data-testid attributes to the Cancel and Recalculate footer buttons in the same dialog so E2E tests can target all new actions reliably.ui/lib/store/apis/logsApi.ts (1)
368-376: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRemove
recalculateLogCostsfromui/lib/store/apis/logsApi.ts— no call sites show up outside the endpoint definition and generated hook export, so it only adds confusion about how cost recalculation starts.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ui/lib/store/apis/logsApi.ts` around lines 368 - 376, The cost-recalculation API entry should be removed because it is no longer used to start recalculation and only adds confusion. Delete the `recalculateLogCosts` endpoint from the `logsApi` builder definition and remove any related generated hook/export references tied to that endpoint, while keeping `getRecalculateCostStatus` intact for polling the background job status.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@ui/app/workspace/logs/views/logsHeaderView.tsx`:
- Around line 61-65: The recalculation status polling in logsHeaderView is only
reading data, so query failures can leave activeRecalcJobId stuck forever.
Update useGetRecalculateCostStatusQuery to also capture the error state (for
example alongside recalcJobStatus), then in the terminal-status effect for the
recalculation flow, treat a persistent recalcJobError as a failure: show
toast.error with a clear message and clear activeRecalcJobId so the loading
state cannot persist indefinitely.
- Around line 121-153: The loading toast in logsHeaderView’s useEffect is never
dismissed if the component unmounts while recalcJobStatus is still in progress,
leaving “Recalculating log costs…” stuck on screen. Add a cleanup path in the
same effect for the toast id used in the progress state, so it is
dismissed/reset on unmount or when the effect is torn down, while keeping the
existing success/failure handling for activeRecalcJobId and recalcJobStatus.
---
Nitpick comments:
In `@ui/app/workspace/logs/views/logsHeaderView.tsx`:
- Around line 61-65: The new polling call in logsHeaderView’s
useGetRecalculateCostStatusQuery is missing the same unfocused-tab behavior used
by the other polling queries in page.tsx. Update the job-status polling options
in logsHeaderView to include skipPollingIfUnfocused: true alongside the existing
pollingInterval and skip settings so the recalculation status query stops
polling when the tab is backgrounded.
- Around line 121-153: The `useEffect` in `LogsHeaderView` is depending on
unstable `fetchLogs` and `fetchStats` props, causing the toast/update effect to
re-run on unrelated parent renders. Remove these inline callback references from
the dependency list by making the callbacks stable in the parent `page.tsx` (for
example with `useCallback`) or by otherwise ensuring the effect only depends on
the actual polling state it uses, then keep the effect dependencies minimal and
accurate.
In `@ui/app/workspace/logs/views/recalculateCostDialog.tsx`:
- Around line 60-71: The new interactive controls in RecalculateCostDialog and
RecalculateModeOption need stable test selectors. Update RecalculateModeOption
to accept and forward a data-testid on its button, then pass distinct values for
the “missing” and “all” mode options from recalculateCostDialog.tsx. Also add
data-testid attributes to the Cancel and Recalculate footer buttons in the same
dialog so E2E tests can target all new actions reliably.
In `@ui/lib/store/apis/logsApi.ts`:
- Around line 368-376: The cost-recalculation API entry should be removed
because it is no longer used to start recalculation and only adds confusion.
Delete the `recalculateLogCosts` endpoint from the `logsApi` builder definition
and remove any related generated hook/export references tied to that endpoint,
while keeping `getRecalculateCostStatus` intact for polling the background job
status.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: d5266447-e8f5-4b3a-8644-2fae1b88e904
📒 Files selected for processing (5)
ui/app/workspace/logs/page.tsxui/app/workspace/logs/views/logsHeaderView.tsxui/app/workspace/logs/views/recalculateCostDialog.tsxui/lib/store/apis/logsApi.tsui/lib/types/logs.ts
8429b4f to
7d2975f
Compare
7d2975f to
5babf22
Compare
ea50175 to
9db256b
Compare
5babf22 to
a9c404a
Compare
9db256b to
e14b9eb
Compare
53af499 to
6247490
Compare
2c53c61 to
05b4663
Compare
6247490 to
98e1996
Compare
05b4663 to
965fe69
Compare
98e1996 to
3a04068
Compare
965fe69 to
7ef8fb9
Compare
3a04068 to
439b154
Compare
Merge activity
|
| useEffect(() => { | ||
| if (!activeRecalcJobId || !recalcJobStatus) return; | ||
| const toastId = "logs-recalculate-costs"; | ||
|
|
||
| if (recalcJobStatus.status === "completed" || recalcJobStatus.status === "failed") { | ||
| if (recalcJobStatus.status === "failed") { | ||
| toast.error("Cost recalculation failed", { | ||
| id: toastId, | ||
| description: recalcJobStatus.last_error || recalcJobStatus.message || "The job did not complete", | ||
| }); | ||
| } else { | ||
| toast.success("Cost recalculation complete", { | ||
| id: toastId, | ||
| description: recalcJobStatus.message || `${recalcJobStatus.updated} updated, ${recalcJobStatus.skipped} skipped`, | ||
| duration: 5000, | ||
| }); | ||
| } | ||
| setActiveRecalcJobId(null); | ||
| void fetchLogs(); | ||
| void fetchStats(); | ||
| return; | ||
| } | ||
|
|
||
| toast.promise(recalculatePromise, { | ||
| const total = recalcJobStatus.total || 0; | ||
| const processed = total > 0 ? Math.min(recalcJobStatus.processed, total) : recalcJobStatus.processed; | ||
| toast.loading("Recalculating log costs...", { | ||
| id: toastId, | ||
| loading: "Recalculating log costs...", | ||
| success: (response) => ({ | ||
| message: `Recalculated costs for ${response.updated} logs`, | ||
| description: `${response.updated} logs updated, ${response.skipped} logs skipped, ${response.remaining} logs remaining`, | ||
| duration: 5000, | ||
| }), | ||
| error: (err) => getErrorMessage(err), | ||
| description: | ||
| total > 0 | ||
| ? `${processed}/${total} checked, ${recalcJobStatus.updated} updated, ${recalcJobStatus.skipped} skipped` | ||
| : `${recalcJobStatus.processed} checked, ${recalcJobStatus.updated} updated, ${recalcJobStatus.skipped} skipped`, | ||
| }); | ||
|
|
||
| try { | ||
| await recalculatePromise; | ||
| await fetchLogs(); | ||
| await fetchStats(); | ||
| } catch {} | ||
| }, [filters, fetchLogs, fetchStats]); | ||
| }, [activeRecalcJobId, recalcJobStatus, fetchLogs, fetchStats]); |
There was a problem hiding this comment.
"idle" status is never treated as a terminal polling state
RecalcJobStatus.status includes "idle" as a valid value, but the status effect only exits polling on "completed" or "failed". If the server returns {status: "idle"} for a queried job ID — for example when a job has been cleaned up after a server restart or TTL expiry between the initial POST and the first status poll — neither the error effect (no HTTP error) nor the status effect (not a terminal string) will call setActiveRecalcJobId(null). Polling continues at 2-second intervals indefinitely, the loading toast never resolves, and the menu item stays permanently disabled for the session.
Adding "idle" to the terminal check (or treating it as an error state) gives the UI an escape path without relying solely on the HTTP-error path.
The base branch was changed.
Extract a RecalculateCostDialog that shows the missing-cost count via a lazy stats query, and poll job status with an RTK query pollingInterval instead of a hand-rolled loop.
439b154 to
570fb54
Compare
…lculation (#5006) ## Summary Replaces the SSE-based streaming approach for cost recalculation with a background job model. Instead of holding an open HTTP stream until recalculation finishes, the UI now enqueues a job, receives a job ID, and polls a status endpoint until the job reaches a terminal state. A new confirmation dialog lets users choose between recalculating only logs missing a cost or all logs matching the current filters. ## Changes - **Background job polling**: `startRecalculateCostJob` replaces `recalculateCostsWithProgress`. It POSTs to the recalculate endpoint and handles `202 Accepted` (new job started) and `409 Conflict` (job already running), returning the job ID in both cases. A `useGetRecalculateCostStatusQuery` hook polls `/logs/recalculate-cost/status` every 2 seconds while a job ID is active, stopping automatically on a terminal status (`completed` or `failed`). - **RecalculateCostDialog**: A new confirmation dialog (`recalculateCostDialog.tsx`) presents two modes — "Missing cost only" and "All selected logs". On open it lazily fetches the count of logs missing a cost for the current filters and displays it inline. The confirm button is disabled when the missing-cost count is zero or still loading. - **`RecalcJobStatus` type**: Replaces `RecalculateCostProgress` and `RecalculateCostResponse` for the new job-based API shape, covering `idle | pending | running | completed | failed` statuses with progress counters and error fields. - **`totalLogs` prop**: Passed down from the logs page to the header view and into the dialog so the "All selected logs" mode can display the count without an extra fetch. - **`buildFilterParams` exported**: Made public so it can be reused by the lazy stats query inside the dialog. ## Type of change - [ ] Bug fix - [x] Feature - [x] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [x] UI (React) - [ ] Docs ## How to test 1. Open the Logs page and apply any filters or time window. 2. Click the **⋮** (more actions) menu and select **Recalculate costs**. 3. Verify the confirmation dialog opens showing the count of logs missing a cost. 4. Switch to "All selected logs" and confirm the total log count is displayed. 5. Confirm with either mode and verify: - A loading toast appears immediately. - The toast updates with progress (`processed/total checked, N updated, N skipped`). - On completion, a success toast appears with the final counts. - If a recalculation is already running, a "already running" toast appears and polling attaches to the existing job. 6. Trigger a failure scenario and confirm an error toast is shown. ```sh cd ui pnpm i pnpm build ``` ## Screenshots/Recordings _Add before/after screenshots of the recalculate costs dialog and toast progression._ ## Breaking changes - [ ] Yes - [x] No ## Related issues ## Security considerations No new auth surfaces. The recalculate endpoint already requires credentials; the polling status endpoint follows the same auth model. ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [ ] I added/updated tests where appropriate - [ ] I updated documentation where needed - [ ] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable
…lculation (maximhq#5006) ## Summary Replaces the SSE-based streaming approach for cost recalculation with a background job model. Instead of holding an open HTTP stream until recalculation finishes, the UI now enqueues a job, receives a job ID, and polls a status endpoint until the job reaches a terminal state. A new confirmation dialog lets users choose between recalculating only logs missing a cost or all logs matching the current filters. ## Changes - **Background job polling**: `startRecalculateCostJob` replaces `recalculateCostsWithProgress`. It POSTs to the recalculate endpoint and handles `202 Accepted` (new job started) and `409 Conflict` (job already running), returning the job ID in both cases. A `useGetRecalculateCostStatusQuery` hook polls `/logs/recalculate-cost/status` every 2 seconds while a job ID is active, stopping automatically on a terminal status (`completed` or `failed`). - **RecalculateCostDialog**: A new confirmation dialog (`recalculateCostDialog.tsx`) presents two modes — "Missing cost only" and "All selected logs". On open it lazily fetches the count of logs missing a cost for the current filters and displays it inline. The confirm button is disabled when the missing-cost count is zero or still loading. - **`RecalcJobStatus` type**: Replaces `RecalculateCostProgress` and `RecalculateCostResponse` for the new job-based API shape, covering `idle | pending | running | completed | failed` statuses with progress counters and error fields. - **`totalLogs` prop**: Passed down from the logs page to the header view and into the dialog so the "All selected logs" mode can display the count without an extra fetch. - **`buildFilterParams` exported**: Made public so it can be reused by the lazy stats query inside the dialog. ## Type of change - [ ] Bug fix - [x] Feature - [x] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [x] UI (React) - [ ] Docs ## How to test 1. Open the Logs page and apply any filters or time window. 2. Click the **⋮** (more actions) menu and select **Recalculate costs**. 3. Verify the confirmation dialog opens showing the count of logs missing a cost. 4. Switch to "All selected logs" and confirm the total log count is displayed. 5. Confirm with either mode and verify: - A loading toast appears immediately. - The toast updates with progress (`processed/total checked, N updated, N skipped`). - On completion, a success toast appears with the final counts. - If a recalculation is already running, a "already running" toast appears and polling attaches to the existing job. 6. Trigger a failure scenario and confirm an error toast is shown. ```sh cd ui pnpm i pnpm build ``` ## Screenshots/Recordings _Add before/after screenshots of the recalculate costs dialog and toast progression._ ## Breaking changes - [ ] Yes - [x] No ## Related issues ## Security considerations No new auth surfaces. The recalculate endpoint already requires credentials; the polling status endpoint follows the same auth model. ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [ ] I added/updated tests where appropriate - [ ] I updated documentation where needed - [ ] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable
…lculation (maximhq#5006) ## Summary Replaces the SSE-based streaming approach for cost recalculation with a background job model. Instead of holding an open HTTP stream until recalculation finishes, the UI now enqueues a job, receives a job ID, and polls a status endpoint until the job reaches a terminal state. A new confirmation dialog lets users choose between recalculating only logs missing a cost or all logs matching the current filters. ## Changes - **Background job polling**: `startRecalculateCostJob` replaces `recalculateCostsWithProgress`. It POSTs to the recalculate endpoint and handles `202 Accepted` (new job started) and `409 Conflict` (job already running), returning the job ID in both cases. A `useGetRecalculateCostStatusQuery` hook polls `/logs/recalculate-cost/status` every 2 seconds while a job ID is active, stopping automatically on a terminal status (`completed` or `failed`). - **RecalculateCostDialog**: A new confirmation dialog (`recalculateCostDialog.tsx`) presents two modes — "Missing cost only" and "All selected logs". On open it lazily fetches the count of logs missing a cost for the current filters and displays it inline. The confirm button is disabled when the missing-cost count is zero or still loading. - **`RecalcJobStatus` type**: Replaces `RecalculateCostProgress` and `RecalculateCostResponse` for the new job-based API shape, covering `idle | pending | running | completed | failed` statuses with progress counters and error fields. - **`totalLogs` prop**: Passed down from the logs page to the header view and into the dialog so the "All selected logs" mode can display the count without an extra fetch. - **`buildFilterParams` exported**: Made public so it can be reused by the lazy stats query inside the dialog. ## Type of change - [ ] Bug fix - [x] Feature - [x] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [x] UI (React) - [ ] Docs ## How to test 1. Open the Logs page and apply any filters or time window. 2. Click the **⋮** (more actions) menu and select **Recalculate costs**. 3. Verify the confirmation dialog opens showing the count of logs missing a cost. 4. Switch to "All selected logs" and confirm the total log count is displayed. 5. Confirm with either mode and verify: - A loading toast appears immediately. - The toast updates with progress (`processed/total checked, N updated, N skipped`). - On completion, a success toast appears with the final counts. - If a recalculation is already running, a "already running" toast appears and polling attaches to the existing job. 6. Trigger a failure scenario and confirm an error toast is shown. ```sh cd ui pnpm i pnpm build ``` ## Screenshots/Recordings _Add before/after screenshots of the recalculate costs dialog and toast progression._ ## Breaking changes - [ ] Yes - [x] No ## Related issues ## Security considerations No new auth surfaces. The recalculate endpoint already requires credentials; the polling status endpoint follows the same auth model. ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [ ] I added/updated tests where appropriate - [ ] I updated documentation where needed - [ ] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable

Summary
Replaces the SSE-based streaming approach for cost recalculation with a background job model. Instead of holding an open HTTP stream until recalculation finishes, the UI now enqueues a job, receives a job ID, and polls a status endpoint until the job reaches a terminal state. A new confirmation dialog lets users choose between recalculating only logs missing a cost or all logs matching the current filters.
Changes
startRecalculateCostJobreplacesrecalculateCostsWithProgress. It POSTs to the recalculate endpoint and handles202 Accepted(new job started) and409 Conflict(job already running), returning the job ID in both cases. AuseGetRecalculateCostStatusQueryhook polls/logs/recalculate-cost/statusevery 2 seconds while a job ID is active, stopping automatically on a terminal status (completedorfailed).recalculateCostDialog.tsx) presents two modes — "Missing cost only" and "All selected logs". On open it lazily fetches the count of logs missing a cost for the current filters and displays it inline. The confirm button is disabled when the missing-cost count is zero or still loading.RecalcJobStatustype: ReplacesRecalculateCostProgressandRecalculateCostResponsefor the new job-based API shape, coveringidle | pending | running | completed | failedstatuses with progress counters and error fields.totalLogsprop: Passed down from the logs page to the header view and into the dialog so the "All selected logs" mode can display the count without an extra fetch.buildFilterParamsexported: Made public so it can be reused by the lazy stats query inside the dialog.Type of change
Affected areas
How to test
processed/total checked, N updated, N skipped).cd ui pnpm i pnpm buildScreenshots/Recordings
Add before/after screenshots of the recalculate costs dialog and toast progression.
Breaking changes
Related issues
Security considerations
No new auth surfaces. The recalculate endpoint already requires credentials; the polling status endpoint follows the same auth model.
Checklist
docs/contributing/README.mdand followed the guidelines