Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
* The team selector and filtering have been removed so that all keys are shown.
*/

export function VirtualKeysTable({ teams, organizations, onSortChange, currentSort }: VirtualKeysTableProps) {

Check warning on line 55 in ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Function 'VirtualKeysTable' has a complexity of 24. Maximum allowed is 20
const { data: fetchedOrganizations } = useOrganizations();
const resolvedOrganizations = fetchedOrganizations ?? organizations ?? [];
const [selectedKey, setSelectedKey] = useState<KeyResponse | null>(null);
Expand Down Expand Up @@ -94,9 +94,9 @@
});
const [expandedAccordions, setExpandedAccordions] = useState<Record<string, boolean>>({});

// Use the filter logic hook

const keyList = useMemo(() => keys?.keys ?? [], [keys]);
// Stable reference: `|| []` creates a new array literal on every render, which
// causes the useEffect in useFilterLogic to fire on every render → infinite loop.
const keysList = useMemo(() => keys?.keys ?? [], [keys?.keys]);

const {
filters,
Expand All @@ -107,7 +107,7 @@
handleFilterChange,
handleFilterReset,
} = useFilterLogic({
keys: keyList,
keys: keysList,
teams,
organizations,
});
Expand Down Expand Up @@ -586,7 +586,7 @@
},
},
],
[teams, resolvedOrganizations],

Check warning on line 589 in ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

React Hook useMemo has a missing dependency: 'expandedAccordions'. Either include it or remove the dependency array
);

const filterOptions: FilterOption[] = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,62 @@
};

const defaultProps = {
keys: [mockKey] as any[],

Check warning on line 23 in ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.test.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Unexpected any. Specify a different type
teams: [],
organizations: [],
};

const makeApiResponse = (overrides: { keys?: any[]; total_count?: number; total_pages?: number } = {}) => ({

Check warning on line 28 in ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.test.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Unexpected any. Specify a different type
keys: overrides.keys ?? [mockKey],
total_count: overrides.total_count ?? 1,
current_page: 1,
total_pages: overrides.total_pages ?? 1,
});

describe("useFilterLogic – stability", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(keyListCall).mockResolvedValue(makeApiResponse());
});

it("should not enter an infinite render loop when keys prop is re-rendered with a new empty-array reference", async () => {
// Regression: callers that write `keys?.keys || []` produce a fresh `[]`
// on every render (when keys is undefined/null). The useEffect([keys, filters])
// must not treat every new-reference empty array as a change that requires
// another setFilteredKeys call, which would re-render the consumer, which
// would produce yet another new `[]`, ad infinitum.
const { result, rerender } = renderHook(({ keys }) => useFilterLogic({ keys, teams: [], organizations: [] }), {
initialProps: { keys: [] as any[] },

Check warning on line 48 in ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.test.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Unexpected any. Specify a different type
});

// Simulate the || [] pattern: each rerender gets a brand-new [] literal
act(() => {
rerender({ keys: [] });
rerender({ keys: [] });
rerender({ keys: [] });
});

// If we reach here the hook did not loop.
// filteredKeys should reflect the empty input.
expect(result.current.filteredKeys).toEqual([]);
});
Comment thread
Bytechoreographer marked this conversation as resolved.

it("should update filteredKeys when keys prop changes from empty to populated", async () => {
const { result, rerender } = renderHook(({ keys }) => useFilterLogic({ keys, teams: [], organizations: [] }), {
initialProps: { keys: [] as any[] },

Check warning on line 65 in ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.test.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Unexpected any. Specify a different type
});

expect(result.current.filteredKeys).toEqual([]);

act(() => {
rerender({ keys: [mockKey as any] });

Check warning on line 71 in ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.test.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Unexpected any. Specify a different type
});

expect(result.current.filteredKeys).toHaveLength(1);
expect(result.current.filteredKeys[0]).toBe(mockKey);
});
});

describe("useFilterLogic – filteredTotalCount", () => {
beforeEach(() => {
vi.clearAllMocks();
Expand Down
Loading