fix(ui): virtual keys filter silently resets after navigating away an… - #26972
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Greptile SummaryThis PR fixes a data/UI desync on the Virtual Keys page where applied filters stayed visible in the filter chips but the table silently reverted to all keys whenever React Query refetched in the background. The root cause was that
Confidence Score: 5/5Safe to merge — the change is well-scoped to the Virtual Keys UI layer, correctly routes filter state through the React Query cache key, and is covered by updated unit tests. Moving filter values into the useKeys cache key means any refetch or remount uses the same (page, sort, filter) tuple, eliminating drift between filter chips and table data. Removed client-side filtering code and tests are correctly deleted rather than masked. No correctness issues found. No files require special attention.
|
| Filename | Overview |
|---|---|
| ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.tsx | Stripped to a pure state holder: removes debouncedSearch, filteredKeys, filteredTotalCount, and the [keys, filters] sync effect; adds "Key Hash" to FilterState; handleFilterChange no longer accepts skipDebounce |
| ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx | Feeds debounced filter values (300 ms) into useKeys so the cache key tracks filter state; drives table from keys?.keys; resets pagination to page 1 on filter change; reset handler now also restores sort state |
| ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.test.tsx | Rewritten against the new contract: replaces API-call assertions with pure state checks; adds regression test confirming filter values survive re-renders |
| ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx | Per-test mockUseKeys now supplies the key data directly; adds two assertions that filter values (Key Alias, Key Hash) are forwarded into useKeys; removes filteredTotalCount tests for deleted functionality |
Reviews (2): Last reviewed commit: "fix(ui): reset virtual keys sort on filt..." | Re-trigger Greptile
- BerriAI/litellm#26972 merge-after-nits: virtual keys filter routed through React Query cache - BerriAI/litellm#26970 needs-discussion: Venice AI additions contaminated by databricks row churn + capability-flag drop - BerriAI/litellm#26968 merge-after-nits: tighten router-settings-override fallback validation + mock-testing strip
|
🤖 litellm-agent: Squash-merged into staging branch Triage Summary Merge Confidence: 5/5 ✅ READY All checks green. Greptile 5/5, no blocking pattern findings, no CircleCI runs (OSS-typical). |
|
🤖 litellm-agent: Reverted from staging branch |
|
merge reverted @Bytechoreographer can you please refile and include proof of fix working as expected (screen recording works) |
@krrish-berri-2 The former one is not fixed. When I changed to other tabs and then came back, the filter failed to work.
|

PR: fix(ui): virtual keys filter silently resets after navigating away and back
Relevant issues
Pre-Submission checklist
npm run testpasses for affected files (30/30)@greptileaiand get Confidence Score ≥ 4/5 before requesting maintainer reviewType
🐛 Bug Fix
Changes
Root cause
On the Virtual Keys page, applying a filter (e.g. Key Alias) worked on
first click — but after navigating to another page and coming back, the
filter chips still showed the selected values while the table reverted to
all keys, as if the filter had never been applied.
The page ran two parallel data paths for the same list:
Timeline of the bug:
"foo"into Key Alias →debouncedSearchfires a filteredkeyListCall→filteredKeys = server result✓ the table shows the filtered rows.useKeyseventually re-runs (staleTime: 30sexpiry,storageevent,window re-focus, remount).
keysprop arrives → the[keys, filters]effect re-runs →result = [...keys](unfiltered) → Key Alias and User ID have noclient-side filter branch, so
setFilteredKeys(result)overwrites theserver-filtered list with the full page.
filtersstate is untouched, so the FilterComponent still shows"foo"— but the table displays everything.
Reproduce:
"foo") → rows filter correctly ✓"foo"but the table now lists all keys ✗Fix
Route filter values through the main
useKeysquery so React Query's cachekey tracks them. Same (page, filter, sort) tuple → same cache entry, which
means any remount or refetch naturally pulls the filtered page — the UI and
the data can no longer drift apart.
const { data: keys, ... } = useKeys(page, size, { sortBy, sortOrder, expand: "user", + teamID: debouncedFilters["Team ID"] || undefined, + organizationID: debouncedFilters["Organization ID"] || undefined, + selectedKeyAlias: debouncedFilters["Key Alias"] || undefined, + userID: debouncedFilters["User ID"] || undefined, }); - const { filters, filteredKeys, filteredTotalCount, ... } = useFilterLogic({ - keys: keys?.keys || [], teams, organizations, - }); + const { filters, ... } = useFilterLogic({ teams, organizations }); - data: filteredKeys, + data: keys?.keys ?? [], - const totalCount = filteredTotalCount ?? keys?.total_count ?? 0; + const totalCount = keys?.total_count ?? 0;A 300 ms debounce sits between
filtersand the values fed touseKeys—FilterComponentfiresonApplyFilterson every keystroke of the textinputs (Key Alias, User ID), so without debouncing each character would
trigger its own request. Pagination resets to page 1 when the debounced
filter values change, so a narrowed result set doesn't leave the user
stranded on an out-of-range page.
useFilterLogicis reduced to a pure state holder:filters,handleFilterChange,handleFilterReset, plus the existingallTeams/allOrganizationsbootstrap. ThedebouncedSearchbranch,the
[keys, filters]effect,filteredKeys, andfilteredTotalCountare all removed.
Files changed
ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.tsxdebouncedSearch, the[keys, filters]effect,filteredKeys, andfilteredTotalCount; remove thekeysprop;handleFilterChangenow just updates stateui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsxuseKeysoptions; drive the table fromkeys?.keys; reset pagination to page 1 on filter change; remove the redundantskipDebouncehop on the sort handlerui/litellm-dashboard/src/components/key_team_helpers/filter_logic.test.tsxui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsxuseKeysinstead offilteredKeys; replace the twofilteredTotalCountpagination cases with one server-filtered-total case and one assertion that filter values are forwarded intouseKeys