litellm/ui/litellm-dashboard/src/utils
tin-berri 0c50286a55
feat(ui): add per-key Savings tab to key detail page (#37693)
* feat(ui): add per-key Savings tab to key detail page

Adds a "Savings" tab to the key detail view, showing the same four metrics
and time-series chart as the proxy-wide Cost Optimization view, but scoped
to a single API key.

For org admins, the tab shows the key's full savings across all requests.
Non-admins see only their own requests on the key, with a scope note
explaining the limitation.

Root cause: userDailyActivityCall and userDailyActivityAggregatedCall
never forwarded an api_key query parameter to the backend, even though
both handlers already accept and filter by it.

Changes:

- networking.tsx: Add optional apiKey param to both daily activity call
  wrappers (appended to variadic options tuple for backward compatibility).

- costOptimizationUtils.ts: Extract shared metrics helpers (compressionOf,
  cachingOf, autorouterOf, savedTokensOf, cacheHitRatio) and shortDate
  so both UsageTab and KeySavingsTab use the same formulas and prevent
  divergence.

- useDailyActivityRange.ts: Refactor into useScopedDailyActivityRange(
  accessToken, scope: {userId, apiKey?}) for reuse-by-parameter unbundling.
  Role resolution stays at the entry point (useDailyActivityRange), not in
  a scoped caller. Update test expectations for new 6-arg tuple.

- UsageTab.tsx: Simplify by importing extracted helpers and SummaryCard
  component instead of defining them inline. No behavioral change.

- key_info_view.tsx: Insert "Savings" tab trigger between "Overview" and
  "Settings"; wire TabsContent to new KeySavingsTab component with lazy
  mounting (no keepMounted) to defer daily-activity fetch until tab opened.

- NEW: components/shared/SummaryCard.tsx — Shared presenter for four-tile
  summary row (label + value + hint + optional info popover). Extracted
  from UsageTab so both surfaces show identical tile layout without CSS
  divergence.

- NEW: components/templates/KeySavingsTab.tsx — Per-key view with admin/
  non-admin scope branching, empty-state messaging, same chart toggles
  and info popovers as UsageTab.

- NEW: components/templates/KeySavingsTab.test.tsx — 7 tests covering mount,
  loading state, empty state, scoping, and scope-note visibility.

Authorization: No new permission check. Both backends gate api_key filter
by the same user role check that governs the request itself. Non-admins
must send their own user_id and can only see their own keys.

Tests: 6121 pass (1 pre-existing failure unrelated to this change).

Prior art / collision note:
- PR #37570 (budgets tab) lands in same TabsList hunks as "Savings" tab,
  but different tab names so conflict trivial if both merge.
- PR #37659 (my own) adds progress/cancelled/cancel to DailyActivityRange,
  but this PR uses stable three-field interface from staging.

* fix(ui): scope spend view by the backend's admin-view contract, not all_admin_roles

Greptile flagged org admin handling on the key savings tab. The live bug it
described does not fire today: useAuthorized supplies session-role labels and
all_admin_roles only carries the raw org_admin spelling, so an org admin was
already scoped. That safety was accidental, so replace the predicate with
spendScopeUserId / hasProxyWideSpendView in utils/roles.ts, mirroring the
backend's user_api_key_has_admin_view (proxy admin and admin viewer only, org
admin excluded in both spellings), and use it in both useDailyActivityRange
and KeySavingsTab

Reclassify the KeySavingsTab render test as an integration test per the
repo's unit/integration split, move scope-resolution coverage to roles.test.ts
as a full role matrix, use real session-role values instead of raw ones, and
assert tile totals against non-empty metrics. Replace the nested ternary in
the chart body (frontend-lint error) with flat conditional rendering

* fix(ui): show auto-router savings as the fourth key-savings tile

Cache hit rate had displaced auto-router savings from the fourth slot,
diverging from the org-wide Cost Optimization page's tile order. Match
it: Total / Compression / Prompt caching / Auto-router, with cache hit
rate as a fifth tile.

* fix(ui): drop cache hit rate from the key savings tiles

Keep the four tiles this page is meant to show: total, compression,
prompt caching, and auto-router savings.

* fix(ui): stop an empty api_key from widening a key-scoped activity read

The paginated and aggregated daily-activity wrappers disagreed on an
empty filter value: the paginated one appended it, the aggregated one
coerced it to undefined with || and dropped it. Since the aggregated
call is the one tried first, an empty key hash would have silently
turned a key-scoped read into a proxy-wide one and reported every
key's savings as this key's. Use ?? so both send the filter through
and it matches nothing instead.

* style(ui): satisfy prettier and the inline-object lint rule in key savings tests

* refactor(ui): drop the cacheHitRatio extraction left over from the removed tile

* fix(ui): pass daily-activity filters raw so both transports agree at the null boundary

* refactor(ui): share the savings tiles and totals between both surfaces

The per-key Savings tab and the proxy-wide Cost Optimization tab carried a byte-identical
four-tile block, three long metric-definition strings included, and five identical useMemo
totals. Both now render SavingsTiles and total through useSavingsTotals, so the donut cannot
slice numbers the tile above it disagrees with.

* docs(ui): say request, not mount, in the savings tab comment

The comment claimed mounting eagerly would fire the rollup sweep, which reads as a claim about
the bundle. Only the request is deferred; the module ships with the key page either way.

* test(ui): pin the daily-activity args array against the real caller signatures

The sibling unit test mocks networking, so it checks the positional array against itself and
stays green when the array and a networking signature drift apart. Swapping user_id and api_key
in the aggregated signature alone passes there and fails here on user_id=hash-abc.

* style(ui): hoist the daily-activity query options out of the call argument

The four-property object literal tripped local/no-large-inline-object-arg. The violation predates
this branch, which only moved the line into the annotated range, and the rule count drops 550 to 549.
2026-08-21 14:50:02 -07:00
..
budgetUtils.ts Show absolute date in Budget Reset column 2026-04-23 15:57:22 -07:00
capabilities.test.ts fix(proxy): let org admins view their organization's usage (#37235) 2026-08-18 14:44:36 -07:00
capabilities.ts fix(proxy): let org admins view their organization's usage (#37235) 2026-08-18 14:44:36 -07:00
cookieUtils.test.ts test(ui): split the vitest suite into unit, component, integration and type projects (#37488) 2026-08-19 20:40:14 +00:00
cookieUtils.ts fix(ui): break logout redirect loop across origins (#29360) 2026-05-30 19:02:29 -07:00
dataUtils.test.ts test(ui): split the vitest suite into unit, component, integration and type projects (#37488) 2026-08-19 20:40:14 +00:00
dataUtils.ts refactor(ui): codemod every toast call site onto lib/toast and delete the antd-era facades (#37253) 2026-08-18 18:24:49 +00:00
debounceConstants.ts refactor(ui): standardize debounce waits behind shared DEBOUNCE_WAIT_MS constant (#33040) 2026-07-13 12:19:57 -07:00
entityLinks.ts feat(ui): link key info header to its user, creator, team, and organization (#37187) 2026-08-17 10:17:28 -07:00
errorPatterns.ts added and ran prettier autoformatter 2025-10-04 18:19:48 -07:00
errorUtils.test.ts Add unit tests for 5 previously untested UI dashboard files 2026-03-16 12:53:20 -07:00
errorUtils.ts style(ui): run prettier --write across the dashboard (#29622) 2026-06-04 11:37:54 -07:00
jwtUtils.test.ts useAuthorized refactor 2026-02-04 16:37:45 -08:00
jwtUtils.ts useAuthorized refactor 2026-02-04 16:37:45 -08:00
keyExpiryUtils.test.ts fix(ui): require new expiration when regenerating an expired key (#29838) 2026-06-06 09:18:19 -07:00
keyExpiryUtils.ts fix(ui): require new expiration when regenerating an expired key (#29838) 2026-06-06 09:18:19 -07:00
keyUpdateUtils.test.ts Key Max Budget Removal Error Fix 2025-10-17 12:04:58 -07:00
keyUpdateUtils.ts Key Max Budget Removal Error Fix 2025-10-17 12:04:58 -07:00
licenseUtils.test.ts feat(ui): show exact license expiration date in usage cards 2026-07-15 16:52:21 -07:00
licenseUtils.ts feat(ui): show exact license expiration date in usage cards 2026-07-15 16:52:21 -07:00
localStorageUtils.test.ts test(ui): split the vitest suite into unit, component, integration and type projects (#37488) 2026-08-19 20:40:14 +00:00
localStorageUtils.ts feat: hide new badges 2025-12-31 10:42:34 -08:00
maskedSecretUtils.ts fix(ui): stop credential edit from persisting the masked api key (#33797) 2026-07-17 18:25:24 -07:00
mcpHeaderUtils.test.ts feat(mcp): Add tool call and tool list support via UI for Oauth mcps (#28454) 2026-05-22 09:04:04 -07:00
mcpHeaderUtils.ts fix(mcp): load MCP tool configuration tools via the OBO/passthrough-aware GET path (#29960) 2026-06-08 19:58:51 -07:00
mcpTokenStore.test.ts test(ui): split the vitest suite into unit, component, integration and type projects (#37488) 2026-08-19 20:40:14 +00:00
mcpTokenStore.ts refactor(ui): route MCP session tokens through the shared storage helper 2026-08-04 14:15:29 -07:00
mcpToolCrudClassification.test.ts Add unit tests for 5 previously untested UI dashboard files 2026-03-16 12:53:20 -07:00
mcpToolCrudClassification.ts feat(ui): group MCP tools by CRUD risk category in allowlist panels (#23403) 2026-03-11 21:15:25 -07:00
migratedPages.test.ts feat(ui): migrate api-keys landing to App Router path route (#30699) 2026-06-18 17:38:07 -07:00
migratedPages.ts feat(spend): track prompt compression saved tokens in daily spend aggregates (#33810) 2026-07-18 17:47:54 -07:00
modelPermissions.test.ts feat(ui): let team admins create auto-routers; authorize models by team, not created_by 2026-07-30 00:18:45 -07:00
modelPermissions.ts feat(ui): let team admins create auto-routers; authorize models by team, not created_by 2026-07-30 00:18:45 -07:00
pkce.ts Extract shared PKCE helpers into src/utils/pkce.ts 2026-04-16 10:35:52 -07:00
promptCacheUsage.test.ts feat(ui): show provider prompt cache tokens in chat response metrics (#36827) 2026-08-13 16:58:14 -07:00
promptCacheUsage.ts feat(ui): show provider prompt cache tokens in chat response metrics (#36827) 2026-08-13 16:58:14 -07:00
proxyUtils.test.ts style(ui): run prettier --write across the dashboard (#29622) 2026-06-04 11:37:54 -07:00
proxyUtils.ts added and ran prettier autoformatter 2025-10-04 18:19:48 -07:00
ptuDatetime.test.ts feat(ptu): PTU inputs on the model form and flat cost on the Usage page (#35393) 2026-08-10 11:05:06 -07:00
ptuDatetime.ts feat(ptu): PTU inputs on the model form and flat cost on the Usage page (#35393) 2026-08-10 11:05:06 -07:00
ptuModelInfo.test.ts feat(ptu): gate PTU flat-cost attribution behind an opt-in env var (#36138) 2026-08-10 12:23:20 -07:00
ptuModelInfo.ts feat(ptu): gate PTU flat-cost attribution behind an opt-in env var (#36138) 2026-08-10 12:23:20 -07:00
ptuValidation.test.ts fix(ptu): stop per-token billing on a PTU-configured deployment (#36829) 2026-08-13 20:16:12 -07:00
ptuValidation.ts refactor(ui): move the model info edit form off antd Form (#37392) 2026-08-18 17:40:49 -07:00
returnUrlUtils.test.ts test(ui): split the vitest suite into unit, component, integration and type projects (#37488) 2026-08-19 20:40:14 +00:00
returnUrlUtils.ts fix(ui): navigate to /ui/login/ with trailing slash via hard navigation (#33561) 2026-07-16 12:18:55 -07:00
roles.test.ts feat(ui): add per-key Savings tab to key detail page (#37693) 2026-08-21 14:50:02 -07:00
roles.ts feat(ui): add per-key Savings tab to key detail page (#37693) 2026-08-21 14:50:02 -07:00
secureStorage.ts style(ui): run prettier --write across the dashboard (#29622) 2026-06-04 11:37:54 -07:00
tabRoutes.test.ts refactor(ui): extract shared tab-routing helpers and adopt them in Models + Endpoints (#34435) 2026-07-23 16:36:25 -07:00
tabRoutes.ts refactor(ui): extract shared tab-routing helpers and adopt them in Models + Endpoints (#34435) 2026-07-23 16:36:25 -07:00
teamUtils.test.ts Deprecate useTeam in favor of react query 2025-12-23 20:15:42 -08:00
teamUtils.ts Deprecate useTeam in favor of react query 2025-12-23 20:15:42 -08:00
textUtils.test.ts Adding unit testing coverage 2026-01-03 17:25:46 -08:00
textUtils.ts [Feature] UI - Add LiteLLM Params to Edit Model (#16496) 2025-11-11 18:52:11 -08:00