diff --git a/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx b/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx index 65de44bb309..5347f202757 100644 --- a/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx +++ b/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx @@ -203,8 +203,7 @@ beforeEach(() => { "User ID": "user-1", "User Email": "user@example.com", "User Role": "user", - "Sort By": "created_at", - "Sort Order": "desc", + "Key Hash": "", }, allTeams: [mockTeam], allOrganizations: [mockOrganization], @@ -322,8 +321,7 @@ it("should show 'No keys found' message when filteredKeys is empty", () => { "Organization ID": "", "Key Alias": "", "User ID": "", - "Sort By": "created_at", - "Sort Order": "desc", + "Key Hash": "", }, allTeams: [mockTeam], allOrganizations: [mockOrganization], @@ -370,8 +368,7 @@ it("should handle models with more than 3 entries to trigger expansion UI", () = "Organization ID": "", "Key Alias": "", "User ID": "", - "Sort By": "created_at", - "Sort Order": "desc", + "Key Hash": "", }, allTeams: [mockTeam], allOrganizations: [mockOrganization], @@ -518,8 +515,7 @@ it("should display 'Default Proxy Admin' for user_id when value is 'default_user "Organization ID": "", "Key Alias": "", "User ID": "", - "Sort By": "created_at", - "Sort Order": "desc", + "Key Hash": "", }, allTeams: [mockTeam], allOrganizations: [mockOrganization], @@ -568,8 +564,7 @@ it("should display 'Default Proxy Admin' for created_by when value is 'default_u "Organization ID": "", "Key Alias": "", "User ID": "", - "Sort By": "created_at", - "Sort Order": "desc", + "Key Hash": "", }, allTeams: [mockTeam], allOrganizations: [mockOrganization], @@ -626,8 +621,7 @@ it("should display created_by_user email in 'Created By' column when available", "Organization ID": "", "Key Alias": "", "User ID": "", - "Sort By": "created_at", - "Sort Order": "desc", + "Key Hash": "", }, allTeams: [mockTeam], allOrganizations: [mockOrganization], @@ -681,8 +675,7 @@ it("should display created_by_user alias over email when both available", async "Organization ID": "", "Key Alias": "", "User ID": "", - "Sort By": "created_at", - "Sort Order": "desc", + "Key Hash": "", }, allTeams: [mockTeam], allOrganizations: [mockOrganization], @@ -731,8 +724,7 @@ it("should render table without crashing when models is null", async () => { "Organization ID": "", "Key Alias": "", "User ID": "", - "Sort By": "created_at", - "Sort Order": "desc", + "Key Hash": "", }, allTeams: [mockTeam], allOrganizations: [mockOrganization], @@ -782,8 +774,7 @@ it("should render table without crashing when models is undefined", async () => "Organization ID": "", "Key Alias": "", "User ID": "", - "Sort By": "created_at", - "Sort Order": "desc", + "Key Hash": "", }, allTeams: [mockTeam], allOrganizations: [mockOrganization], @@ -868,8 +859,7 @@ it("should display 'Unknown' for last_active when value is null", async () => { "Organization ID": "", "Key Alias": "", "User ID": "", - "Sort By": "created_at", - "Sort Order": "desc", + "Key Hash": "", }, allTeams: [mockTeam], allOrganizations: [mockOrganization], @@ -916,7 +906,7 @@ describe("pagination display – total count and page count", () => { } as any); mockUseFilterLogic.mockReturnValue({ - filters: { "Team ID": "", "Organization ID": "", "Key Alias": "", "User ID": "", "Sort By": "created_at", "Sort Order": "desc" }, + filters: { "Team ID": "", "Organization ID": "", "Key Alias": "", "User ID": "", "Key Hash": "" }, allTeams: [mockTeam], allOrganizations: [mockOrganization], handleFilterChange: vi.fn(), @@ -947,7 +937,7 @@ describe("pagination display – total count and page count", () => { } as any); mockUseFilterLogic.mockReturnValue({ - filters: { "Team ID": "", "Organization ID": "", "Key Alias": "aaaaa", "User ID": "", "Sort By": "created_at", "Sort Order": "desc" }, + filters: { "Team ID": "", "Organization ID": "", "Key Alias": "aaaaa", "User ID": "", "Key Hash": "" }, allTeams: [mockTeam], allOrganizations: [mockOrganization], handleFilterChange: vi.fn(), @@ -964,7 +954,7 @@ describe("pagination display – total count and page count", () => { it("should pass current filter values into useKeys so remount/refetch keeps the filter applied", async () => { mockUseFilterLogic.mockReturnValue({ - filters: { "Team ID": "", "Organization ID": "", "Key Alias": "aaaaa", "User ID": "", "Sort By": "created_at", "Sort Order": "desc" }, + filters: { "Team ID": "", "Organization ID": "", "Key Alias": "aaaaa", "User ID": "", "Key Hash": "" }, allTeams: [mockTeam], allOrganizations: [mockOrganization], handleFilterChange: vi.fn(), @@ -983,6 +973,26 @@ describe("pagination display – total count and page count", () => { ); }); }); + + it("should forward the Key Hash filter into useKeys", async () => { + mockUseFilterLogic.mockReturnValue({ + filters: { "Team ID": "", "Organization ID": "", "Key Alias": "", "User ID": "", "Key Hash": "hash-xyz" }, + allTeams: [mockTeam], + allOrganizations: [mockOrganization], + handleFilterChange: vi.fn(), + handleFilterReset: vi.fn(), + }); + + renderWithProviders(); + + await waitFor(() => { + expect(mockUseKeys).toHaveBeenCalledWith( + expect.any(Number), + expect.any(Number), + expect.objectContaining({ keyHash: "hash-xyz" }), + ); + }); + }); }); describe("refetch button", () => { diff --git a/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx b/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx index 2dfc6f45f29..059fc462866 100644 --- a/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx +++ b/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx @@ -105,6 +105,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo debouncedFilters["Organization ID"], debouncedFilters["Key Alias"], debouncedFilters["User ID"], + debouncedFilters["Key Hash"], ]); const { @@ -121,6 +122,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo organizationID: debouncedFilters["Organization ID"] || undefined, selectedKeyAlias: debouncedFilters["Key Alias"] || undefined, userID: debouncedFilters["User ID"] || undefined, + keyHash: debouncedFilters["Key Hash"] || undefined, }); const [expandedAccordions, setExpandedAccordions] = useState>({}); @@ -700,7 +702,11 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo options={filterOptions} onApplyFilters={handleFilterChange} initialValues={filters} - onResetFilters={handleFilterReset} + onResetFilters={() => { + handleFilterReset(); + setSorting([{ id: "created_at", desc: true }]); + onSortChange?.("created_at", "desc"); + }} /> diff --git a/ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.test.tsx b/ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.test.tsx index 0158f159cba..1105052265c 100644 --- a/ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.test.tsx +++ b/ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.test.tsx @@ -17,8 +17,7 @@ const DEFAULT_FILTERS = { "Organization ID": "", "Key Alias": "", "User ID": "", - "Sort By": "created_at", - "Sort Order": "desc", + "Key Hash": "", }; describe("useFilterLogic – filter state management", () => { @@ -42,6 +41,16 @@ describe("useFilterLogic – filter state management", () => { expect(result.current.filters["Key Alias"]).toBe("my-alias"); }); + it("records the Key Hash filter when handleFilterChange is called", () => { + const { result } = renderHook(() => useFilterLogic(defaultProps)); + + act(() => { + result.current.handleFilterChange({ "Key Hash": "abc123" }); + }); + + expect(result.current.filters["Key Hash"]).toBe("abc123"); + }); + it("preserves filter state across re-renders (regression: bug where results reset on remount)", () => { const { result, rerender } = renderHook(() => useFilterLogic(defaultProps)); diff --git a/ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.tsx b/ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.tsx index 3f762d26b7c..52630bf708f 100644 --- a/ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.tsx +++ b/ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.tsx @@ -10,8 +10,7 @@ export interface FilterState { "Key Alias": string; [key: string]: string; "User ID": string; - "Sort By": string; - "Sort Order": string; + "Key Hash": string; } const DEFAULT_FILTERS: FilterState = { @@ -19,8 +18,7 @@ const DEFAULT_FILTERS: FilterState = { "Organization ID": "", "Key Alias": "", "User ID": "", - "Sort By": "created_at", - "Sort Order": "desc", + "Key Hash": "", }; export function useFilterLogic({ @@ -71,8 +69,7 @@ export function useFilterLogic({ "Organization ID": newFilters["Organization ID"] || "", "Key Alias": newFilters["Key Alias"] || "", "User ID": newFilters["User ID"] || "", - "Sort By": newFilters["Sort By"] || "created_at", - "Sort Order": newFilters["Sort Order"] || "desc", + "Key Hash": newFilters["Key Hash"] || "", }); };