fix(ui): reset virtual keys sort on filter reset and wire key hash filter

Two follow-ups on top of the filter-persistence fix:

- Reset Filters now also resets the column sort. The sort state lives in
  the table's local `sorting`, separate from `filters` — clicking Reset
  cleared the chips but left the column header arrow and the useKeys
  sort_by/sort_order untouched. Wrap onResetFilters to setSorting and
  call onSortChange with the defaults (created_at desc).
- Wire the "Key Hash" filter end-to-end. The chip was rendered but
  handleFilterChange dropped the value and useKeys never received it,
  so typing into Key Hash did nothing. Add it to FilterState/
  DEFAULT_FILTERS/handleFilterChange and forward debouncedFilters
  ["Key Hash"] into useKeys.
- Drop the now-dead "Sort By"/"Sort Order" entries from FilterState —
  the sort handler no longer writes them and Reset's old behavior of
  resetting them was a no-op.
- Tests: forward Key Hash assertion in VirtualKeysTable.test.tsx,
  Key Hash handleFilterChange test in filter_logic.test.tsx, and
  update mock filter shapes accordingly.

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bytechoreographer 2026-05-06 19:36:05 +08:00
parent f301297125
commit cb65102669
4 changed files with 54 additions and 32 deletions

View file

@ -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(<VirtualKeysTable {...defaultMockProps} />);
await waitFor(() => {
expect(mockUseKeys).toHaveBeenCalledWith(
expect.any(Number),
expect.any(Number),
expect.objectContaining({ keyHash: "hash-xyz" }),
);
});
});
});
describe("refetch button", () => {

View file

@ -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<Record<string, boolean>>({});
@ -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");
}}
/>
</div>

View file

@ -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));

View file

@ -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"] || "",
});
};