fix(ui): stop usage pagination from under-reporting wide date ranges - #36022
fix(ui): stop usage pagination from under-reporting wide date ranges#36022devin-ai-integration[bot] wants to merge 2 commits into
Conversation
|
|
Greptile SummaryThis PR merges duplicate dates across paginated daily-activity responses, exposes incomplete and failed fetch state, and disables exports while later pages are loading. It also adds failure banners and focused tests, but the export guard does not cover the first-page loading window.
Confidence Score: 4/5The first-page loading gap must be fixed before merging because users can still export empty or stale usage data for the currently selected range. The merge and later-page failure handling are sound, but Files Needing Attention: ui/litellm-dashboard/src/app/(dashboard)/usage/_components/hooks/usePaginatedDailyActivity.ts, ui/litellm-dashboard/src/app/(dashboard)/usage/_components/hooks/mergeDailyActivity.ts
|
| Filename | Overview |
|---|---|
| ui/litellm-dashboard/src/app/(dashboard)/usage/_components/hooks/usePaginatedDailyActivity.ts | Integrates date-aware merging and failure state, but omits initial loading from incomplete, leaving export enabled with empty or stale data. |
| ui/litellm-dashboard/src/app/(dashboard)/usage/_components/hooks/mergeDailyActivity.ts | Correctly sums every current SpendMetrics field and recursively merges all daily breakdown buckets; it also adds comments prohibited by repository guidance. |
| ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/EntityUsage/EntityUsage.tsx | Displays partial-data failures and wires incomplete state into export disabling, inheriting the hook’s first-page loading gap. |
| ui/litellm-dashboard/src/components/EntityUsageExport/UsageExportHeader.tsx | Adds disabled-button and tooltip support correctly, with no secondary guard if the supplied disabled state is false. |
| ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/UsagePageView.tsx | Distinguishes failed pagination from user cancellation in the partial-data banner. |
| ui/litellm-dashboard/src/app/(dashboard)/usage/_components/hooks/usePaginatedDailyActivity.test.ts | Covers split dates and failed later pages but does not test export-related completeness during the first-page loading window. |
Reviews (1): Last reviewed commit: "fix(ui): stop usage pagination from unde..." | Re-trigger Greptile
| }, [enabled, fetchFn, argsKey]); | ||
|
|
||
| return { data, loading, isFetchingMore, progress, cancelled, cancel }; | ||
| const incomplete = isFetchingMore || cancelled || failed; |
There was a problem hiding this comment.
Initial loading leaves export enabled
When the initial page is loading after mount or a range or filter change, incomplete remains false while the hook retains empty or previous data, so Export Data stays enabled and can download a file that does not represent the current selection.
| const incomplete = isFetchingMore || cancelled || failed; | |
| const incomplete = loading || isFetchingMore || cancelled || failed; |
There was a problem hiding this comment.
Good catch, incomplete now includes loading, so export is blocked during the first page too, with a regression test
| }); | ||
|
|
||
| /** | ||
| * Combine daily activity pages into one series with a single entry per date. | ||
| * | ||
| * The backend paginates over raw spend rows, so a date whose rows straddle a |
There was a problem hiding this comment.
New comments violate repository guidance
This explanatory block, along with the new field comments in usePaginatedDailyActivity.ts and prop comment in UsageExportHeader.tsx, violates the repository rule against adding comments unless the user explicitly requested them, adding cleanup work before the change satisfies project conventions.
Context Used: CLAUDE.md (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Right, removed the doc block plus the field and prop comments in the hook and export header
Merge daily activity pages by date and block the CSV export until the whole range is loaded. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
56c9edf to
fe131b8
Compare
|
Verified in the browser against a seeded 6 month range: pages now merge per date, and export stays blocked while incomplete. After the fix (fe131b8): 2026-06-25 merged to $35.16, total $12,951.12Daily CSV: 364 rows, 182 distinct dates, spend sum 12951.12, split dates 2026-06-25 = 35.16, 2026-05-14 = 35.16, 2026-02-20 = 47.16 Before (base 5277dab): the same date reports only $23.37Base daily CSV has 370 rows: each of the 3 boundary dates appears twice per team with partial spend. Export Data was also clickable mid load. |
Also drop the explanatory comments the repo guidelines disallow. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
Re-verified at 45ad441: Export Data is greyed from the first page fetch, before the pagination banner shows. |
|
@gerptile review |
|
@BugBot review |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 45ad441. Configure here.
| (acc, key) => | ||
| a[key] === undefined && b[key] === undefined ? acc : { ...acc, [key]: (a[key] ?? 0) + (b[key] ?? 0) }, | ||
| {} as SpendMetrics, | ||
| ); |
There was a problem hiding this comment.
Merge drops daily flat cost
High Severity
METRIC_KEYS omits flat_cost, so addMetrics rebuilds day and breakdown metrics without it. When a date straddles pages, chart Flat Cost and CSV Flat Cost ($) rows go to zero even though total_flat_cost in metadata still sums correctly, so wide Team Usage ranges under-report PTU flat cost on the exact split days this PR aims to fix.
Reviewed by Cursor Bugbot for commit 45ad441. Configure here.
| failed | ||
| ? "Spend data failed to load for the whole range, so an export would under-report. Reload the page first." | ||
| : "Spend data is still loading, so an export would under-report. Wait for it to finish." | ||
| } |
There was a problem hiding this comment.
Cancelled export shows loading reason
Low Severity
incomplete is true when the user cancels, so Export stays disabled, but exportDisabledReason only special-cases failed and otherwise says spend data is still loading and to wait. After Stop, nothing is loading, so the tooltip steers users to wait for a fetch that will never finish.
Reviewed by Cursor Bugbot for commit 45ad441. Configure here.


TLDR
Problem this solves:
How it solves it:
User Flow
Before: an admin reviewing six months of team spend sees one day counted twice at partial amounts, and the CSV they hand to finance is short
After: the same range shows one row per day at the full amount, and Export waits for the whole range
Relevant issues
Linear ticket
Resolves LIT-5045
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
Shared setup: a live proxy on localhost:4000 with six months of team spend seeded into
LiteLLM_DailyTeamSpend(2026-02-05 through 2026-08-05, 2 teams, 4 keys each, 3 models, 4368 rows over 182 distinct dates,SUM(spend)= 12951.12). The endpoint paginates raw rows, so a date lands on two different pages, each carrying only its share:Before (5277dab)
Folding those five live page payloads the way the hook did on the merge base,
[...accumulatedResults, ...pageData.results]:After (45ad441)
Same five payloads through
mergeDailyResultsas shipped here:SUM(spend)and the accumulated metadataentitiesandapi_key_breakdownbuckets summed tooBrowser run of both revisions, with the screenshots and the screen recording, is in the Slack thread: https://berriaillm.slack.com/archives/C0BE49SAUE6/p1787012100873179?thread_ts=1787012100.873179&cid=C0BE49SAUE6 (Before captured at 5277dab, After re-run at the current tip 45ad441). The same screenshots are also in a comment on this PR
Reviewer steps to reproduce it yourself (proxy on :4000,
npm run devinui/litellm-dashboardon :3000):/team/daily/activitypage request in devtools and confirm the red banner appears instead of the totals looking finalType
🐛 Bug Fix
Changes
mergeDailyActivity.tsis the new piece. It folds an incoming page into the accumulated series by date, addingmetricsand recursively merging every breakdown bucket (models,model_groups,mcp_servers,providers,entities,endpoints,api_keys, and the nestedapi_key_breakdown) so a split date ends up identical to what a single unpaginated query would have returned:usePaginatedDailyActivityruns each page through that instead of concatenating, and reports two new flags:failedwhen a page fetch throws, andincompleteforisFetchingMore || cancelled || failed.UsageExportHeadertakesexportDisabledplus a reason surfaced as atitleon the wrapper, andEntityUsagepassesincompleteinto it, so the export can no longer run against a half loaded range.EntityUsageandUsagePageViewalso render the partial-data banner as an error whenfailedis set, using the sharedAlertcomponent the usage pages now use.Tests cover the merge in isolation (
mergeDailyActivity.test.ts), the hook end to end over a date straddling two pages plus the failure path (usePaginatedDailyActivity.test.ts), and the blocked export (UsageExportHeader.test.tsx).Link to Devin session: https://app.devin.ai/sessions/d0a988ce9c1542b0a5cec9f531c2e0a4
Note
Cursor Bugbot is generating a summary for commit 45ad441. Configure here.