Merge pull request #22157 from BerriAI/litellm_paginated_key_alias

[Feature] UI - Paginated Key Alias Select
This commit is contained in:
yuneng-jiang 2026-02-26 12:09:10 -08:00 committed by GitHub
commit 719b7fd013
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 609 additions and 90 deletions

View File

@ -0,0 +1,177 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { renderHook, waitFor } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import React, { ReactNode } from "react";
import { useInfiniteKeyAliases } from "./useKeyAliases";
import type { PaginatedKeyAliasResponse } from "@/components/networking";
// Mock networking module
vi.mock("@/components/networking", () => ({
keyAliasesCall: vi.fn(),
}));
// Mock useAuthorized hook
const mockUseAuthorized = vi.fn();
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
default: () => mockUseAuthorized(),
}));
// Mock console methods to avoid noise
vi.spyOn(console, "log").mockImplementation(() => {});
vi.spyOn(console, "error").mockImplementation(() => {});
import { keyAliasesCall } from "@/components/networking";
const mockKeyAliasesCall = vi.mocked(keyAliasesCall);
const mockPage1: PaginatedKeyAliasResponse = {
aliases: ["alias-1", "alias-2"],
total_count: 3,
current_page: 1,
total_pages: 2,
size: 2,
};
const mockPage2: PaginatedKeyAliasResponse = {
aliases: ["alias-3"],
total_count: 3,
current_page: 2,
total_pages: 2,
size: 2,
};
const createWrapper = () => {
const queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },
},
});
return ({ children }: { children: ReactNode }) =>
React.createElement(QueryClientProvider, { client: queryClient }, children);
};
describe("useInfiniteKeyAliases", () => {
beforeEach(() => {
vi.clearAllMocks();
mockUseAuthorized.mockReturnValue({ accessToken: "test-token" });
mockKeyAliasesCall.mockResolvedValue(mockPage1);
});
it("should fetch the first page of key aliases", async () => {
const wrapper = createWrapper();
const { result } = renderHook(() => useInfiniteKeyAliases(), { wrapper });
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(mockKeyAliasesCall).toHaveBeenCalledWith("test-token", 1, 50, undefined);
expect(result.current.data?.pages[0]).toEqual(mockPage1);
});
it("should pass custom size parameter", async () => {
const wrapper = createWrapper();
renderHook(() => useInfiniteKeyAliases(25), { wrapper });
await waitFor(() => {
expect(mockKeyAliasesCall).toHaveBeenCalledWith("test-token", 1, 25, undefined);
});
});
it("should pass search parameter when provided", async () => {
const wrapper = createWrapper();
renderHook(() => useInfiniteKeyAliases(50, "my-alias"), { wrapper });
await waitFor(() => {
expect(mockKeyAliasesCall).toHaveBeenCalledWith("test-token", 1, 50, "my-alias");
});
});
it("should not fetch when accessToken is not available", () => {
mockUseAuthorized.mockReturnValue({ accessToken: null });
const wrapper = createWrapper();
const { result } = renderHook(() => useInfiniteKeyAliases(), { wrapper });
expect(result.current.isFetching).toBe(false);
expect(mockKeyAliasesCall).not.toHaveBeenCalled();
});
it("should expose hasNextPage when more pages are available", async () => {
const wrapper = createWrapper();
const { result } = renderHook(() => useInfiniteKeyAliases(), { wrapper });
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(result.current.hasNextPage).toBe(true);
});
it("should return hasNextPage false when on last page", async () => {
const singlePage: PaginatedKeyAliasResponse = {
aliases: ["alias-1"],
total_count: 1,
current_page: 1,
total_pages: 1,
size: 50,
};
mockKeyAliasesCall.mockResolvedValue(singlePage);
const wrapper = createWrapper();
const { result } = renderHook(() => useInfiniteKeyAliases(), { wrapper });
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(result.current.hasNextPage).toBe(false);
});
it("should fetch the next page when fetchNextPage is called", async () => {
mockKeyAliasesCall
.mockResolvedValueOnce(mockPage1)
.mockResolvedValueOnce(mockPage2);
const wrapper = createWrapper();
const { result } = renderHook(() => useInfiniteKeyAliases(2), { wrapper });
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
result.current.fetchNextPage();
await waitFor(() => {
expect(result.current.data?.pages).toHaveLength(2);
});
expect(mockKeyAliasesCall).toHaveBeenCalledWith("test-token", 2, 2, undefined);
expect(result.current.data?.pages[1]).toEqual(mockPage2);
});
it("should include search in query key so search changes refetch from page 1", async () => {
const wrapper = createWrapper();
const { result, rerender } = renderHook(
({ search }: { search?: string }) => useInfiniteKeyAliases(50, search),
{ wrapper, initialProps: { search: undefined } },
);
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
mockKeyAliasesCall.mockResolvedValue({
aliases: ["search-result"],
total_count: 1,
current_page: 1,
total_pages: 1,
size: 50,
});
rerender({ search: "search-result" });
await waitFor(() => {
expect(mockKeyAliasesCall).toHaveBeenCalledWith("test-token", 1, 50, "search-result");
});
});
});

View File

@ -0,0 +1,37 @@
import { useInfiniteQuery } from "@tanstack/react-query";
import { createQueryKeys } from "../common/queryKeysFactory";
import { keyAliasesCall, type PaginatedKeyAliasResponse } from "@/components/networking";
import useAuthorized from "../useAuthorized";
const infiniteKeyAliasKeys = createQueryKeys("infiniteKeyAliases");
export const useInfiniteKeyAliases = (
size: number = 50,
search?: string,
) => {
const { accessToken } = useAuthorized();
return useInfiniteQuery<PaginatedKeyAliasResponse>({
queryKey: infiniteKeyAliasKeys.list({
filters: {
size,
...(search && { search }),
},
}),
queryFn: async ({ pageParam }) => {
return await keyAliasesCall(
accessToken!,
pageParam as number,
size,
search,
);
},
initialPageParam: 1,
getNextPageParam: (lastPage) => {
if (lastPage.current_page < lastPage.total_pages) {
return lastPage.current_page + 1;
}
return undefined;
},
enabled: Boolean(accessToken),
});
};

View File

@ -0,0 +1,255 @@
import { screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { renderWithProviders } from "../../../../tests/test-utils";
import { PaginatedKeyAliasSelect } from "./PaginatedKeyAliasSelect";
const mockFetchNextPage = vi.fn();
vi.mock("@/app/(dashboard)/hooks/keys/useKeyAliases", () => ({
useInfiniteKeyAliases: vi.fn(),
}));
vi.mock("@tanstack/react-pacer/debouncer", async () => {
const React = await vi.importActual<typeof import("react")>("react");
return {
useDebouncedState: (initial: string) => {
const [value, setValue] = React.useState(initial);
return [value, setValue];
},
};
});
import { useInfiniteKeyAliases } from "@/app/(dashboard)/hooks/keys/useKeyAliases";
const mockUseInfiniteKeyAliases = vi.mocked(useInfiniteKeyAliases);
const mockPagesWithAliases = {
pages: [
{
aliases: ["alias-1", "alias-2"],
total_count: 2,
current_page: 1,
total_pages: 1,
size: 50,
},
],
};
const mockEmptyPages = {
pages: [{ aliases: [], total_count: 0, current_page: 1, total_pages: 1, size: 50 }],
};
describe("PaginatedKeyAliasSelect", () => {
const mockOnChange = vi.fn();
const defaultHookReturn = {
data: mockPagesWithAliases,
fetchNextPage: mockFetchNextPage,
hasNextPage: false,
isFetchingNextPage: false,
isLoading: false,
};
beforeEach(() => {
vi.clearAllMocks();
mockUseInfiniteKeyAliases.mockReturnValue(defaultHookReturn as any);
});
it("should render", () => {
renderWithProviders(<PaginatedKeyAliasSelect onChange={mockOnChange} />);
expect(screen.getByRole("combobox")).toBeInTheDocument();
expect(screen.getByText("Select a key alias")).toBeInTheDocument();
});
it("should display custom placeholder when provided", () => {
renderWithProviders(
<PaginatedKeyAliasSelect onChange={mockOnChange} placeholder="Choose alias" />,
);
expect(screen.getByText("Choose alias")).toBeInTheDocument();
});
it("should display alias options when data is loaded", async () => {
renderWithProviders(<PaginatedKeyAliasSelect onChange={mockOnChange} />);
const combobox = screen.getByRole("combobox");
await userEvent.click(combobox);
await waitFor(() => {
expect(screen.getByRole("option", { name: "alias-1" })).toBeInTheDocument();
expect(screen.getByRole("option", { name: "alias-2" })).toBeInTheDocument();
});
});
it("should call onChange when user selects an alias", async () => {
const user = userEvent.setup({ delay: null });
renderWithProviders(<PaginatedKeyAliasSelect onChange={mockOnChange} />);
const combobox = screen.getByRole("combobox");
await user.click(combobox);
const option = await screen.findByTitle("alias-1");
await user.click(option);
await waitFor(() => {
expect(mockOnChange).toHaveBeenCalledWith("alias-1");
});
});
it("should show loading state when isLoading is true", () => {
mockUseInfiniteKeyAliases.mockReturnValue({
...defaultHookReturn,
isLoading: true,
} as any);
renderWithProviders(<PaginatedKeyAliasSelect onChange={mockOnChange} />);
expect(screen.getByRole("combobox")).toHaveAttribute("aria-expanded", "false");
});
it("should pass pageSize to useInfiniteKeyAliases", () => {
renderWithProviders(<PaginatedKeyAliasSelect onChange={mockOnChange} pageSize={25} />);
expect(mockUseInfiniteKeyAliases).toHaveBeenCalledWith(25, undefined);
});
it("should pass search to useInfiniteKeyAliases when user types", async () => {
const user = userEvent.setup();
renderWithProviders(<PaginatedKeyAliasSelect onChange={mockOnChange} />);
const combobox = screen.getByRole("combobox");
await user.click(combobox);
await user.keyboard("my-alias");
await waitFor(() => {
expect(mockUseInfiniteKeyAliases).toHaveBeenCalledWith(50, "my-alias");
});
});
it("should have scroll container for infinite loading when hasNextPage is true", async () => {
mockUseInfiniteKeyAliases.mockReturnValue({
...defaultHookReturn,
hasNextPage: true,
isFetchingNextPage: false,
} as any);
renderWithProviders(<PaginatedKeyAliasSelect onChange={mockOnChange} />);
const combobox = screen.getByRole("combobox");
await userEvent.click(combobox);
await waitFor(() => {
expect(screen.getByRole("option", { name: "alias-1" })).toBeInTheDocument();
});
const scrollableContainer = document.querySelector(
".ant-select-dropdown .rc-virtual-list-holder",
);
expect(scrollableContainer).toBeInTheDocument();
});
it("should deduplicate aliases with the same value across pages", async () => {
mockUseInfiniteKeyAliases.mockReturnValue({
...defaultHookReturn,
data: {
pages: [
{
aliases: ["alias-1", "alias-1"],
total_count: 2,
current_page: 1,
total_pages: 1,
size: 50,
},
],
},
} as any);
renderWithProviders(<PaginatedKeyAliasSelect onChange={mockOnChange} />);
const combobox = screen.getByRole("combobox");
await userEvent.click(combobox);
await waitFor(() => {
const options = screen.queryAllByRole("option", { name: "alias-1" });
expect(options.length).toBe(1);
});
});
it("should skip empty aliases", async () => {
mockUseInfiniteKeyAliases.mockReturnValue({
...defaultHookReturn,
data: {
pages: [
{
aliases: ["valid-alias", "", null],
total_count: 3,
current_page: 1,
total_pages: 1,
size: 50,
},
],
},
} as any);
renderWithProviders(<PaginatedKeyAliasSelect onChange={mockOnChange} />);
const combobox = screen.getByRole("combobox");
await userEvent.click(combobox);
await waitFor(() => {
expect(screen.getByRole("option", { name: "valid-alias" })).toBeInTheDocument();
const allOptions = screen.queryAllByRole("option");
expect(allOptions.length).toBe(1);
});
});
it("should respect allowClear prop", () => {
renderWithProviders(
<PaginatedKeyAliasSelect value="alias-1" onChange={mockOnChange} allowClear={false} />,
);
expect(screen.getByRole("combobox")).toBeInTheDocument();
});
it("should respect disabled prop", () => {
renderWithProviders(<PaginatedKeyAliasSelect onChange={mockOnChange} disabled />);
const combobox = screen.getByRole("combobox");
expect(combobox.closest(".ant-select")).toHaveClass("ant-select-disabled");
});
it("should not call fetchNextPage when hasNextPage is false", async () => {
mockUseInfiniteKeyAliases.mockReturnValue({
...defaultHookReturn,
hasNextPage: false,
} as any);
renderWithProviders(<PaginatedKeyAliasSelect onChange={mockOnChange} />);
await userEvent.click(screen.getByRole("combobox"));
await waitFor(() => {
expect(screen.getByRole("option", { name: "alias-1" })).toBeInTheDocument();
});
expect(mockFetchNextPage).not.toHaveBeenCalled();
});
it("should show no aliases found when data is empty", async () => {
mockUseInfiniteKeyAliases.mockReturnValue({
...defaultHookReturn,
data: mockEmptyPages,
} as any);
renderWithProviders(<PaginatedKeyAliasSelect onChange={mockOnChange} />);
const combobox = screen.getByRole("combobox");
await userEvent.click(combobox);
await waitFor(() => {
expect(screen.getByText("No key aliases found")).toBeInTheDocument();
});
});
});

View File

@ -0,0 +1,106 @@
import { useInfiniteKeyAliases } from "@/app/(dashboard)/hooks/keys/useKeyAliases";
import { LoadingOutlined } from "@ant-design/icons";
import { useDebouncedState } from "@tanstack/react-pacer/debouncer";
import { Select } from "antd";
import { useMemo, useState, type UIEvent } from "react";
export interface PaginatedKeyAliasSelectProps {
value?: string;
onChange?: (value: string) => void;
placeholder?: string;
style?: React.CSSProperties;
pageSize?: number;
allowClear?: boolean;
disabled?: boolean;
}
const SCROLL_THRESHOLD = 0.8;
const DEBOUNCE_MS = 300;
export const PaginatedKeyAliasSelect = ({
value,
onChange,
placeholder = "Select a key alias",
style,
pageSize = 50,
allowClear = true,
disabled = false,
}: PaginatedKeyAliasSelectProps) => {
const [searchInput, setSearchInput] = useState("");
const [debouncedSearch, setDebouncedSearch] = useDebouncedState("", {
wait: DEBOUNCE_MS,
});
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
isLoading,
} = useInfiniteKeyAliases(pageSize, debouncedSearch || undefined);
const options = useMemo(() => {
if (!data?.pages) return [];
const seen = new Set<string>();
const result: { label: string; value: string }[] = [];
for (const page of data.pages) {
for (const alias of page.aliases) {
if (!alias || seen.has(alias)) continue;
seen.add(alias);
result.push({ label: alias, value: alias });
}
}
return result;
}, [data]);
const handlePopupScroll = (e: UIEvent<HTMLDivElement>) => {
const target = e.currentTarget;
const scrollRatio =
(target.scrollTop + target.clientHeight) / target.scrollHeight;
if (scrollRatio >= SCROLL_THRESHOLD && hasNextPage && !isFetchingNextPage) {
fetchNextPage();
}
};
const handleSearch = (value: string) => {
setSearchInput(value);
setDebouncedSearch(value);
};
const handleChange = (v: string | null) => {
onChange?.(v ?? "");
};
return (
<Select
value={value || undefined}
onChange={handleChange}
placeholder={placeholder}
style={{ width: "100%", ...style }}
allowClear={allowClear}
disabled={disabled}
showSearch
filterOption={false}
onSearch={handleSearch}
searchValue={searchInput}
onPopupScroll={handlePopupScroll}
loading={isLoading}
notFoundContent={isLoading ? <LoadingOutlined spin /> : "No key aliases found"}
options={options}
popupRender={(menu) => (
<>
{menu}
{isFetchingNextPage && (
<div style={{ textAlign: "center", padding: 8 }}>
<LoadingOutlined spin />
</div>
)}
</>
)}
/>
);
};

View File

@ -28,7 +28,6 @@ vi.mock("./networking", async (importOriginal) => {
// Mock filter helpers
vi.mock("./key_team_helpers/filter_helpers", () => ({
fetchAllKeyAliases: vi.fn().mockResolvedValue(["test-key-alias"]),
fetchAllTeams: vi.fn().mockResolvedValue([
{
team_id: "team-1",
@ -195,7 +194,6 @@ beforeEach(() => {
"Sort Order": "desc",
},
filteredKeys: [mockKey],
allKeyAliases: ["test-key-alias"],
allTeams: [mockTeam],
allOrganizations: [mockOrganization],
handleFilterChange: vi.fn(),
@ -304,7 +302,6 @@ it("should show 'No keys found' message when filteredKeys is empty", () => {
"Sort Order": "desc",
},
filteredKeys: [],
allKeyAliases: [],
allTeams: [mockTeam],
allOrganizations: [mockOrganization],
handleFilterChange: vi.fn(),
@ -342,7 +339,6 @@ it("should handle models with more than 3 entries to trigger expansion UI", () =
"Sort Order": "desc",
},
filteredKeys: [keyWithManyModels],
allKeyAliases: ["test-key-alias"],
allTeams: [mockTeam],
allOrganizations: [mockOrganization],
handleFilterChange: vi.fn(),
@ -478,7 +474,6 @@ it("should display 'Default Proxy Admin' for user_id when value is 'default_user
"Sort Order": "desc",
},
filteredKeys: [keyWithDefaultUserId],
allKeyAliases: ["test-key-alias"],
allTeams: [mockTeam],
allOrganizations: [mockOrganization],
handleFilterChange: vi.fn(),
@ -518,7 +513,6 @@ it("should display 'Default Proxy Admin' for created_by when value is 'default_u
"Sort Order": "desc",
},
filteredKeys: [keyWithDefaultCreatedBy],
allKeyAliases: ["test-key-alias"],
allTeams: [mockTeam],
allOrganizations: [mockOrganization],
handleFilterChange: vi.fn(),
@ -561,7 +555,6 @@ it("should render table without crashing when models is null", async () => {
"Sort Order": "desc",
},
filteredKeys: [keyWithNullModels],
allKeyAliases: ["test-key-alias"],
allTeams: [mockTeam],
allOrganizations: [mockOrganization],
handleFilterChange: vi.fn(),
@ -602,7 +595,6 @@ it("should render table without crashing when models is undefined", async () =>
"Sort Order": "desc",
},
filteredKeys: [keyWithUndefinedModels],
allKeyAliases: ["test-key-alias"],
allTeams: [mockTeam],
allOrganizations: [mockOrganization],
handleFilterChange: vi.fn(),
@ -678,7 +670,6 @@ it("should display 'Unknown' for last_active when value is null", async () => {
"Sort Order": "desc",
},
filteredKeys: [keyWithNullLastActive],
allKeyAliases: ["test-key-alias"],
allTeams: [mockTeam],
allOrganizations: [mockOrganization],
handleFilterChange: vi.fn(),

View File

@ -29,6 +29,7 @@ import { Popover, Skeleton, Tooltip } from "antd";
import React, { useEffect, useMemo, useState } from "react";
import { getModelDisplayName } from "../key_team_helpers/fetch_available_models_team_key";
import { useFilterLogic } from "../key_team_helpers/filter_logic";
import { PaginatedKeyAliasSelect } from "../KeyAliasSelect/PaginatedKeyAliasSelect/PaginatedKeyAliasSelect";
import { KeyResponse, Team } from "../key_team_helpers/key_list";
import FilterComponent, { FilterOption } from "../molecules/filter";
import { Organization } from "../networking";
@ -90,7 +91,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
// Use the filter logic hook
const { filters, filteredKeys, allKeyAliases, allTeams, allOrganizations, handleFilterChange, handleFilterReset } =
const { filters, filteredKeys, allTeams, allOrganizations, handleFilterChange, handleFilterReset } =
useFilterLogic({
keys: keys?.keys || [],
teams,
@ -509,19 +510,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
{
name: "Key Alias",
label: "Key Alias",
isSearchable: true,
searchFn: async (searchText) => {
const filteredKeyAliases = allKeyAliases.filter((key) => {
return key.toLowerCase().includes(searchText.toLowerCase());
});
return filteredKeyAliases.map((key) => {
return {
label: key,
value: key,
};
});
},
customComponent: PaginatedKeyAliasSelect,
},
{
name: "User ID",

View File

@ -1,26 +1,7 @@
import { teamListCall, organizationListCall, keyAliasesCall } from "../networking"
import { teamListCall, organizationListCall } from "../networking"
import { Team } from "./key_list";
import { Organization } from "../networking";
/**
* Fetches all key aliases via the dedicated /key/aliases endpoint
* @param accessToken The access token for API authentication
* @returns Array of all unique key aliases
*/
export const fetchAllKeyAliases = async (accessToken: string | null): Promise<string[]> => {
if (!accessToken) return [];
try {
const { aliases } = await keyAliasesCall(accessToken as unknown as string);
// Defensive dedupe & null-guard
return Array.from(new Set((aliases || []).filter(Boolean)));
} catch (error) {
console.error("Error fetching all key aliases:", error);
return [];
}
};
/**
* Fetches all teams across all pages
* @param accessToken The access token for API authentication

View File

@ -2,8 +2,7 @@ import { useCallback, useEffect, useState, useRef } from "react";
import { KeyResponse } from "../key_team_helpers/key_list";
import { keyListCall, Organization } from "../networking";
import { Team } from "../key_team_helpers/key_list";
import { useQuery } from "@tanstack/react-query";
import { fetchAllKeyAliases, fetchAllOrganizations, fetchAllTeams } from "./filter_helpers";
import { fetchAllOrganizations, fetchAllTeams } from "./filter_helpers";
import { debounce } from "lodash";
import { defaultPageSize } from "../constants";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
@ -122,16 +121,6 @@ export function useFilterLogic({
}
}, [accessToken]);
const queryAllKeysQuery = useQuery({
queryKey: ["allKeys"],
queryFn: async () => {
if (!accessToken) throw new Error("Access token required");
return await fetchAllKeyAliases(accessToken);
},
enabled: !!accessToken,
});
const allKeyAliases = queryAllKeysQuery.data || [];
// Update teams and organizations when props change
useEffect(() => {
if (teams && teams.length > 0) {
@ -185,7 +174,6 @@ export function useFilterLogic({
return {
filters,
filteredKeys,
allKeyAliases,
allTeams,
allOrganizations,
handleFilterChange,

View File

@ -3329,13 +3329,33 @@ export const keyListCall = async (
}
};
export const keyAliasesCall = async (accessToken: string): Promise<{ aliases: string[] }> => {
export interface PaginatedKeyAliasResponse {
aliases: string[];
total_count: number;
current_page: number;
total_pages: number;
size: number;
}
export const keyAliasesCall = async (
accessToken: string,
page: number = 1,
size: number = 50,
search?: string,
): Promise<PaginatedKeyAliasResponse> => {
/**
* Get all key aliases from proxy
* Get key aliases from proxy with pagination and optional search
*/
try {
const params = new URLSearchParams(
Object.entries({
page: String(page),
size: String(size),
...(search ? { search } : {}),
}),
);
let url = proxyBaseUrl ? `${proxyBaseUrl}/key/aliases` : `/key/aliases`;
console.log("in keyAliasesCall");
url = `${url}?${params}`;
const response = await fetch(url, {
method: "GET",
@ -3354,7 +3374,7 @@ export const keyAliasesCall = async (accessToken: string): Promise<{ aliases: st
const data = await response.json();
console.log("/key/aliases API Response:", data);
return data; // { aliases: string[] }
return data;
} catch (error) {
console.error("Failed to fetch key aliases:", error);
throw error;

View File

@ -13,7 +13,6 @@ vi.mock("./log_filter_logic", () => ({
filters: {},
filteredLogs: { data: [], total: 0, page: 1, page_size: 50, total_pages: 1 },
allTeams: [],
allKeyAliases: [],
handleFilterChange: vi.fn(),
handleFilterReset: mockHandleFilterResetFromHook,
})),
@ -37,7 +36,6 @@ vi.mock("../networking", async (importOriginal) => {
});
vi.mock("../key_team_helpers/filter_helpers", () => ({
fetchAllKeyAliases: vi.fn().mockResolvedValue([]),
fetchAllTeams: vi.fn().mockResolvedValue([]),
}));

View File

@ -11,8 +11,8 @@ import { Button, Tag, Tooltip } from "antd";
import { internalUserRoles } from "../../utils/roles";
import DeletedKeysPage from "../DeletedKeysPage/DeletedKeysPage";
import DeletedTeamsPage from "../DeletedTeamsPage/DeletedTeamsPage";
import { fetchAllKeyAliases } from "../key_team_helpers/filter_helpers";
import { KeyResponse, Team } from "../key_team_helpers/key_list";
import { PaginatedKeyAliasSelect } from "../KeyAliasSelect/PaginatedKeyAliasSelect/PaginatedKeyAliasSelect";
import { PaginatedModelSelect } from "../ModelSelect/PaginatedModelSelect/PaginatedModelSelect";
import FilterComponent, { FilterOption } from "../molecules/filter";
import { allEndUsersCall, keyInfoV1Call, uiSpendLogsCall } from "../networking";
@ -242,7 +242,6 @@ export default function SpendLogsTable({
filteredLogs,
hasBackendFilters,
allTeams: hookAllTeams,
allKeyAliases,
handleFilterChange,
handleFilterReset: handleFilterResetFromHook,
} = useLogFilterLogic({
@ -424,16 +423,7 @@ export default function SpendLogsTable({
{
name: "Key Alias",
label: "Key Alias",
isSearchable: true,
searchFn: async (searchText: string) => {
if (!accessToken) return [];
const keyAliases = await fetchAllKeyAliases(accessToken);
const filtered = keyAliases.filter((alias) => alias.toLowerCase().includes(searchText.toLowerCase()));
return filtered.map((alias) => ({
label: alias,
value: alias,
}));
},
customComponent: PaginatedKeyAliasSelect,
},
{
name: "End User",

View File

@ -11,7 +11,6 @@ vi.mock("../networking", () => ({
}));
vi.mock("@/components/key_team_helpers/filter_helpers", () => ({
fetchAllKeyAliases: vi.fn().mockResolvedValue([]),
fetchAllTeams: vi.fn().mockResolvedValue([]),
}));
@ -82,7 +81,7 @@ describe("useLogFilterLogic", () => {
const wrapper = ({ children }: { children: ReactNode }) =>
React.createElement(QueryClientProvider, { client: queryClient }, children);
it("should return filters, filteredLogs, allKeyAliases, allTeams, handleFilterChange, and handleFilterReset", () => {
it("should return filters, filteredLogs, allTeams, handleFilterChange, and handleFilterReset", () => {
const { result } = renderHook(
() =>
useLogFilterLogic({
@ -94,7 +93,6 @@ describe("useLogFilterLogic", () => {
expect(result.current.filters).toBeDefined();
expect(result.current.filteredLogs).toBeDefined();
expect(result.current.allKeyAliases).toBeDefined();
expect(result.current).toHaveProperty("allTeams");
expect(result.current.handleFilterChange).toBeDefined();
expect(result.current.handleFilterReset).toBeDefined();

View File

@ -3,7 +3,7 @@ import { useCallback, useEffect, useState, useRef, useMemo } from "react";
import { uiSpendLogsCall } from "../networking";
import { Team } from "../key_team_helpers/key_list";
import { useQuery } from "@tanstack/react-query";
import { fetchAllKeyAliases, fetchAllTeams } from "../../components/key_team_helpers/filter_helpers";
import { fetchAllTeams } from "../../components/key_team_helpers/filter_helpers";
import { debounce } from "lodash";
import { defaultPageSize } from "../constants";
import { PaginatedResponse } from ".";
@ -132,16 +132,6 @@ export function useLogFilterLogic({
return () => debouncedSearch.cancel();
}, [debouncedSearch]);
const queryAllKeysQuery = useQuery({
queryKey: ["allKeys"],
queryFn: async () => {
if (!accessToken) throw new Error("Access token required");
return await fetchAllKeyAliases(accessToken);
},
enabled: !!accessToken,
});
const allKeyAliases = queryAllKeysQuery.data || [];
// Determine when backend filters are active (server-side filtering)
const hasBackendFilters = useMemo(
() =>
@ -310,7 +300,6 @@ export function useLogFilterLogic({
filters,
filteredLogs,
hasBackendFilters,
allKeyAliases,
allTeams,
handleFilterChange,
handleFilterReset,

File diff suppressed because one or more lines are too long