From 10849a8b6d68d1798364ae237b8a97ec535073d0 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Fri, 25 Apr 2025 11:49:05 -0700 Subject: [PATCH] refactor(all_keys_table.tsx): refactor to simplify update logic --- .../src/components/all_keys_table.tsx | 120 +++++++++++++++--- .../src/components/constants.tsx | 4 +- .../components/key_team_helpers/key_list.tsx | 9 +- .../src/components/networking.tsx | 5 + .../src/components/view_key_table.tsx | 2 +- 5 files changed, 113 insertions(+), 27 deletions(-) diff --git a/ui/litellm-dashboard/src/components/all_keys_table.tsx b/ui/litellm-dashboard/src/components/all_keys_table.tsx index 6ecd802b659..ede3931e448 100644 --- a/ui/litellm-dashboard/src/components/all_keys_table.tsx +++ b/ui/litellm-dashboard/src/components/all_keys_table.tsx @@ -1,5 +1,5 @@ "use client"; -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useCallback, useRef } from "react"; import { ColumnDef, Row } from "@tanstack/react-table"; import { DataTable } from "./view_logs/table"; import { Select, SelectItem } from "@tremor/react" @@ -9,13 +9,16 @@ import { Tooltip } from "antd"; import { Team, KeyResponse } from "./key_team_helpers/key_list"; import FilterComponent from "./common_components/filter"; import { FilterOption } from "./common_components/filter"; -import { Organization, userListCall } from "./networking"; +import { keyListCall, Organization, userListCall } from "./networking"; import { createTeamSearchFunction } from "./key_team_helpers/team_search_fn"; import { createOrgSearchFunction } from "./key_team_helpers/organization_search_fn"; import { useFilterLogic } from "./key_team_helpers/filter_logic"; import { Setter } from "@/types"; import { updateExistingKeys } from "@/utils/dataUtils"; - +import { debounce } from "lodash"; +import { defaultPageSize } from "./constants"; +import { fetchAllTeams } from "./key_team_helpers/filter_helpers"; +import { fetchAllOrganizations } from "./key_team_helpers/filter_helpers"; interface AllKeysTableProps { keys: KeyResponse[]; setKeys: Setter; @@ -90,6 +93,14 @@ const TeamFilter = ({ * AllKeysTable – a new table for keys that mimics the table styling used in view_logs. * The team selector and filtering have been removed so that all keys are shown. */ + +export interface FilterState { + 'Team ID': string; + 'Organization ID': string; + 'Key Alias': string; + [key: string]: string; +} + export function AllKeysTable({ keys, setKeys, @@ -111,26 +122,93 @@ export function AllKeysTable({ }: AllKeysTableProps) { const [selectedKeyId, setSelectedKeyId] = useState(null); const [userList, setUserList] = useState([]); - + const lastSearchTimestamp = useRef(0); + const [filteredKeys, setFilteredKeys] = useState([]); + const [allTeams, setAllTeams] = useState([]); + const [allOrganizations, setAllOrganizations] = useState([]); + // Use the filter logic hook - const { - filters, - filteredKeys, - allKeyAliases, - allTeams, - allOrganizations, - handleFilterChange, - handleFilterReset - } = useFilterLogic({ - keys, - teams, - organizations, - accessToken, - setSelectedTeam, - setCurrentOrg, - setSelectedKeyAlias + useEffect(() => { + const loadAllFilterData = async () => { + + // Load all teams - no organization filter needed here + const teamsData = await fetchAllTeams(accessToken); + if (teamsData.length > 0) { + setAllTeams(teamsData); + } + + // Load all organizations + const orgsData = await fetchAllOrganizations(accessToken); + if (orgsData.length > 0) { + setAllOrganizations(orgsData); + } + + // Load all keys + debouncedSearch(filters); + }; + + if (accessToken) { + loadAllFilterData(); + } + }, [accessToken]); + + const debouncedSearch = useCallback( + debounce(async (filters: FilterState) => { + if (!accessToken || !userRole || !userID) { + return; + } + + const currentTimestamp = Date.now(); + lastSearchTimestamp.current = currentTimestamp; + + try { + // Make the API call using userListCall with all filter parameters + const data = await keyListCall( + accessToken, + filters["Organization ID"] || null, + filters["Team ID"] || null, + filters["Key Alias"] || null, + filters["User ID"] || null, + 1, // Reset to first page when searching + defaultPageSize + ); + + // Only update state if this is the most recent search + if (currentTimestamp === lastSearchTimestamp.current) { + if (data) { + setFilteredKeys(data.keys); + console.log("called from debouncedSearch filters:", JSON.stringify(filters)); + console.log("called from debouncedSearch data:", JSON.stringify(data)); + } + } + } catch (error) { + console.error("Error searching users:", error); + } + }, 300), + [accessToken, userRole, userID] + ); + + const [filters, setFilters] = useState({ + 'Team ID': '', + 'Organization ID': '', + 'Key Alias': '' }); + const handleFilterChange = (key: keyof FilterState, value: string) => { + const newFilters = { ...filters, [key]: value }; + setFilters(newFilters); + console.log("called from handleFilterChange - newFilters:", JSON.stringify(newFilters)); + debouncedSearch(newFilters); + }; + + const handleFilterReset = () => { + const resetFilters = Object.keys(filters).reduce((acc, key) => { + acc[key] = ''; + return acc; + }, {} as FilterState); + setFilters(resetFilters); + }; + useEffect(() => { if (accessToken) { const user_IDs = keys.map(key => key.user_id).filter(id => id !== null); @@ -208,7 +286,7 @@ export function AllKeysTable({ accessorKey: "team_id", // Change to access the team_id cell: ({ row, getValue }) => { const teamId = getValue() as string; - const team = allTeams?.find(t => t.team_id === teamId); + const team = teams?.find(t => t.team_id === teamId); return team?.team_alias || "Unknown"; }, }, diff --git a/ui/litellm-dashboard/src/components/constants.tsx b/ui/litellm-dashboard/src/components/constants.tsx index 81ba55784a2..a26ac677d1f 100644 --- a/ui/litellm-dashboard/src/components/constants.tsx +++ b/ui/litellm-dashboard/src/components/constants.tsx @@ -12,4 +12,6 @@ export const useBaseUrl = () => { }, []); // Removed router dependency return baseUrl; -}; \ No newline at end of file +}; + +export const defaultPageSize = 25; \ No newline at end of file diff --git a/ui/litellm-dashboard/src/components/key_team_helpers/key_list.tsx b/ui/litellm-dashboard/src/components/key_team_helpers/key_list.tsx index 98d8e0e499a..c4d4c35b3f8 100644 --- a/ui/litellm-dashboard/src/components/key_team_helpers/key_list.tsx +++ b/ui/litellm-dashboard/src/components/key_team_helpers/key_list.tsx @@ -133,10 +133,11 @@ const useKeyList = ({ const data = await keyListCall( accessToken, - currentOrg?.organization_id || null, - selectedTeam?.team_id || "", - selectedKeyAlias, - params.page as number || 1, + null, + null, + null, + null, + 1, 50, ); console.log("data", data); diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 63a4d6f101b..e575b6b38bb 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -2606,6 +2606,7 @@ export const keyListCall = async ( organizationID: string | null, teamID: string | null, selectedKeyAlias: string | null, + userID: string | null, page: number, pageSize: number, ) => { @@ -2629,6 +2630,10 @@ export const keyListCall = async ( queryParams.append('key_alias', selectedKeyAlias) } + if (userID) { + queryParams.append('user_id', userID.toString()); + } + if (page) { queryParams.append('page', page.toString()); } diff --git a/ui/litellm-dashboard/src/components/view_key_table.tsx b/ui/litellm-dashboard/src/components/view_key_table.tsx index 715bf6b7f72..c2d742f27ca 100644 --- a/ui/litellm-dashboard/src/components/view_key_table.tsx +++ b/ui/litellm-dashboard/src/components/view_key_table.tsx @@ -171,7 +171,7 @@ const ViewKeyTable: React.FC = ({ // NEW: Declare filter states for team and key alias. const [teamFilter, setTeamFilter] = useState(selectedTeam?.team_id || ""); - const [keyAliasFilter, setKeyAliasFilter] = useState(""); + // Keep the team filter in sync with the incoming prop. useEffect(() => {