From 360267533f4ad0d711d9da45c305ca24e6096f2f Mon Sep 17 00:00:00 2001 From: Yujong Lee Date: Fri, 2 Oct 2026 23:39:47 +0000 Subject: [PATCH] fix(ui): resolve TanStack Query lint findings Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../_components/GuardrailDetail.tsx | 2 +- .../hooks/mcpServers/useMCPServerHealth.ts | 9 ++++- .../hooks/models/useModels.test.ts | 24 +++++++++++ .../app/(dashboard)/hooks/models/useModels.ts | 22 ++++++++-- .../(dashboard)/hooks/teams/useTeams.test.ts | 40 ++++++++++++++++++- .../app/(dashboard)/hooks/teams/useTeams.ts | 19 +++++++-- .../lens/_components/ActivityScope.tsx | 14 +++---- .../mcp-servers/_components/mcp_tools.tsx | 2 +- .../users/_components/view_users.tsx | 22 +++++----- .../add_model/AutoRouterAvailability.tsx | 10 ++++- .../src/components/chat/LogsPanel.tsx | 31 +++++++------- .../src/components/chat/UsagePanel.tsx | 2 +- .../components/view_logs/RequestLogsPanel.tsx | 2 +- .../view_logs/log_filter_logic.test.tsx | 3 ++ .../components/view_logs/log_filter_logic.tsx | 3 ++ 15 files changed, 159 insertions(+), 46 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailDetail.tsx b/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailDetail.tsx index 86b596d4bcd..6f5087faad2 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailDetail.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailDetail.tsx @@ -40,7 +40,7 @@ export function GuardrailDetail({ guardrailId, onBack, accessToken = null, start error: detailError, } = useGuardrailsUsageDetail(guardrailId, { accessToken, startDate, endDate }); const { data: logsData, isLoading: logsLoading } = useQuery({ - queryKey: ["guardrails-usage-logs", guardrailId, logsPage, logsPageSize], + queryKey: ["guardrails-usage-logs", guardrailId, logsPage, logsPageSize, startDate, endDate], queryFn: () => getGuardrailsUsageLogs(accessToken!, { guardrailId, diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/mcpServers/useMCPServerHealth.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/mcpServers/useMCPServerHealth.ts index 9ad8a6f43fa..340531058b8 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/mcpServers/useMCPServerHealth.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/mcpServers/useMCPServerHealth.ts @@ -52,7 +52,14 @@ export const useMCPServerHealth = () => { ); return { - ...query, + data: query.data, + error: query.error, + isError: query.isError, + isFetching: query.isFetching, + isLoading: query.isLoading, + isSuccess: query.isSuccess, + refetch: query.refetch, + status: query.status, recheckServerHealth, recheckingServerIds, }; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/models/useModels.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/models/useModels.test.ts index 6489bc2171d..1a9bb69254d 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/models/useModels.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/models/useModels.test.ts @@ -621,6 +621,30 @@ describe("useSelectedTeamModels", () => { expect(modelAvailableCall).toHaveBeenCalledTimes(1); }); + it("keeps team model results in separate cache entries", async () => { + const teamOneResponse = { data: [{ ...mockProxyModel, id: "team-1-model" }] }; + const teamTwoResponse = { data: [{ ...mockProxyModel, id: "team-2-model" }] }; + (modelAvailableCall as any).mockResolvedValueOnce(teamOneResponse).mockResolvedValueOnce(teamTwoResponse); + + const { result, rerender } = renderHook(({ teamID }: { teamID: string }) => useSelectedTeamModels(teamID), { + wrapper, + initialProps: { teamID: "team-1" }, + }); + + await waitFor(() => expect(result.current.data).toEqual(teamOneResponse)); + + rerender({ teamID: "team-2" }); + + await waitFor(() => expect(result.current.data).toEqual(teamTwoResponse)); + expect(modelAvailableCall).toHaveBeenCalledTimes(2); + expect( + queryClient + .getQueriesData({ queryKey: ["selectedTeamModels", "list"] }) + .map(([, data]) => data?.data[0]?.id) + .sort(), + ).toEqual(["team-1-model", "team-2-model"]); + }); + it("should handle error when modelAvailableCall fails", async () => { const errorMessage = "Failed to fetch team models"; const testError = new Error(errorMessage); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/models/useModels.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/models/useModels.ts index b5cc329d4c9..11d8a54412c 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/models/useModels.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/models/useModels.ts @@ -247,7 +247,12 @@ export const useModelHub = () => { export const useAllProxyModels = () => { const { accessToken, userId, userRole } = useAuthorized(); return useQuery({ - queryKey: allProxyModelsKeys.list({}), + queryKey: allProxyModelsKeys.list({ + filters: { + userId: userId ?? "", + userRole: userRole ?? "", + }, + }), queryFn: async () => await modelAvailableCall(accessToken!, userId!, userRole!, true, null, true, false, "expand"), enabled: Boolean(accessToken && userId && userRole), }); @@ -256,7 +261,12 @@ export const useAllProxyModels = () => { export const useUserModels = (): UseQueryResult => { const { accessToken, userId, userRole } = useAuthorized(); return useQuery({ - queryKey: userModelsKeys.list({}), + queryKey: userModelsKeys.list({ + filters: { + userId: userId ?? "", + userRole: userRole ?? "", + }, + }), queryFn: async () => { const response = await modelAvailableCall(accessToken!, userId!, userRole!); return response["data"].map((model: { id: string }) => model.id); @@ -268,7 +278,13 @@ export const useUserModels = (): UseQueryResult => { export const useSelectedTeamModels = (teamID: string | null) => { const { accessToken, userId, userRole } = useAuthorized(); return useQuery({ - queryKey: selectedTeamModelsKeys.list({}), + queryKey: selectedTeamModelsKeys.list({ + filters: { + teamID: teamID ?? "", + userId: userId ?? "", + userRole: userRole ?? "", + }, + }), queryFn: async () => await modelAvailableCall(accessToken!, userId!, userRole!, true, teamID!), enabled: Boolean(accessToken && userId && userRole && teamID), }); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/teams/useTeams.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/teams/useTeams.test.ts index 56e45216ec5..2f68aae7323 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/teams/useTeams.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/teams/useTeams.test.ts @@ -116,6 +116,36 @@ describe("useTeams", () => { expect(fetchTeams).toHaveBeenCalledTimes(1); }); + it("keeps team list results scoped to the authorized user and role", async () => { + const otherUserTeams = [mockTeams[1]]; + vi.mocked(fetchTeams).mockResolvedValueOnce(mockTeams).mockResolvedValueOnce(otherUserTeams); + + const { result, rerender } = renderHook(() => useTeams(), { wrapper }); + + await waitFor(() => expect(result.current.data).toEqual(mockTeams)); + + mockUseAuthorized.mockReturnValue({ + accessToken: "test-access-token", + userId: "another-user-id", + userRole: "Internal User", + token: "test-token", + userEmail: "test@example.com", + premiumUser: false, + disabledPersonalKeyCreation: null, + showSSOBanner: false, + }); + rerender(); + + await waitFor(() => expect(result.current.data).toEqual(otherUserTeams)); + expect(fetchTeams).toHaveBeenCalledTimes(2); + expect( + queryClient + .getQueriesData({ queryKey: ["teams", "list"] }) + .map(([, teams]) => teams?.[0]?.team_id) + .sort(), + ).toEqual(["team-1", "team-2"]); + }); + it("should handle error when fetchTeams fails", async () => { const errorMessage = "Failed to fetch teams"; const testError = new Error(errorMessage); @@ -398,7 +428,10 @@ describe("useTeam", () => { }); it("should use initialData from teams list cache when available", async () => { - queryClient.setQueryData(["teams", "list", { params: {} }], mockTeams); + queryClient.setQueryData( + ["teams", "list", { params: { filters: { userId: "test-user-id", userRole: "Admin" } } }], + mockTeams, + ); const { result } = renderHook(() => useTeam("team-1"), { wrapper }); @@ -413,7 +446,10 @@ describe("useTeam", () => { }); it("should return undefined initialData when teamId is not in cache", () => { - queryClient.setQueryData(["teams", "list", { params: {} }], mockTeams); + queryClient.setQueryData( + ["teams", "list", { params: { filters: { userId: "test-user-id", userRole: "Admin" } } }], + mockTeams, + ); const { result } = renderHook(() => useTeam("non-existent-team"), { wrapper }); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/teams/useTeams.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/teams/useTeams.ts index 7d1d035b4d4..a662f7c0725 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/teams/useTeams.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/teams/useTeams.ts @@ -121,7 +121,12 @@ export const teamKeys = createQueryKeys("teams"); export const useTeams = (): UseQueryResult => { const { accessToken, userId, userRole } = useAuthorized(); return useQuery({ - queryKey: teamKeys.list({}), + queryKey: teamKeys.list({ + filters: { + userId: userId ?? "", + userRole: userRole ?? "", + }, + }), queryFn: async () => await fetchTeams(accessToken!, userId, userRole, null), enabled: Boolean(accessToken), }); @@ -159,7 +164,7 @@ export const useAllTeams = (): UseQueryResult => { }; export const useTeam = (teamId?: string) => { - const { accessToken } = useAuthorized(); + const { accessToken, userId, userRole } = useAuthorized(); const queryClient = useQueryClient(); return useQuery({ queryKey: teamKeys.detail(teamId!), @@ -177,7 +182,14 @@ export const useTeam = (teamId?: string) => { initialData: () => { if (!teamId) return undefined; - const teams = queryClient.getQueryData(teamKeys.list({})); + const teams = queryClient.getQueryData( + teamKeys.list({ + filters: { + userId: userId ?? "", + userRole: userRole ?? "", + }, + }), + ); return teams?.find((team) => team.team_id === teamId); }, @@ -202,6 +214,7 @@ export const useInfiniteTeams = (pageSize: number = 50, search?: string, organiz ...(search && { search }), ...(organizationId && { organizationId }), ...(userId && { userId }), + isAdmin: isAdmin ? "true" : "false", }, }), queryFn: async ({ pageParam }) => { diff --git a/ui/litellm-dashboard/src/app/(dashboard)/lens/_components/ActivityScope.tsx b/ui/litellm-dashboard/src/app/(dashboard)/lens/_components/ActivityScope.tsx index 91a74abe8e1..22e302332c7 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/lens/_components/ActivityScope.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/lens/_components/ActivityScope.tsx @@ -112,15 +112,15 @@ export function ActivityScope({ }, }); }; - const discoveryScope: ActivitySelection = { - source: value.source, - service: "", - filters: [], - lookback_hours: value.lookback_hours, - }; const discoveryOptions = { queryKey: ["lens-activity-options", value.source, value.lookback_hours, asOf, accessToken], - queryFn: () => load(discoveryScope), + queryFn: () => + load({ + source: value.source, + service: "", + filters: [], + lookback_hours: value.lookback_hours, + }), staleTime: 60000, enabled: validWindow, }; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/mcp_tools.tsx b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/mcp_tools.tsx index a7af5c978f9..ef13f5c7f7a 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/mcp_tools.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/mcp_tools.tsx @@ -155,7 +155,7 @@ const MCPToolsViewer = ({ error: mcpToolsError, refetch: refetchTools, } = useQuery({ - queryKey: ["mcpTools", serverId, passthroughHeaders, oauthToken], + queryKey: ["mcpTools", serverId, passthroughHeaders, oauthToken, userID], queryFn: async () => { if (!accessToken) throw new Error("Access Token required"); const result = await listMCPTools(accessToken, serverId, buildCustomHeaders()); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/users/_components/view_users.tsx b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/view_users.tsx index 20ce22b6444..72c3d48b30f 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/users/_components/view_users.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/view_users.tsx @@ -244,17 +244,19 @@ const ViewUserDashboard: React.FC = ({ return await userListCall( accessToken, - userIdFilter ? [userIdFilter] : null, - pagination.pageIndex + 1, - pagination.pageSize, + userListQueryFilters.userId ? [userListQueryFilters.userId] : null, + userListQueryFilters.page, + userListQueryFilters.pageSize, null, - userRoleFilter ?? null, - teamFilter ?? null, - ssoUserIdFilter ?? null, - sortBy, - sortOrder, - orgAdminOrgIds ? orgAdminOrgIds.map((o) => o.organization_id) : null, - searchFilter, + userListQueryFilters.role ?? null, + userListQueryFilters.team ?? null, + userListQueryFilters.ssoUserId ?? null, + userListQueryFilters.sortBy, + userListQueryFilters.sortOrder, + userListQueryFilters.orgAdminOrgIds + ? userListQueryFilters.orgAdminOrgIds.map((organization) => organization.organization_id) + : null, + userListQueryFilters.search, ); }, enabled: Boolean(accessToken && token && userRole && userID), diff --git a/ui/litellm-dashboard/src/components/add_model/AutoRouterAvailability.tsx b/ui/litellm-dashboard/src/components/add_model/AutoRouterAvailability.tsx index 135ebb958db..bbc37d0de09 100644 --- a/ui/litellm-dashboard/src/components/add_model/AutoRouterAvailability.tsx +++ b/ui/litellm-dashboard/src/components/add_model/AutoRouterAvailability.tsx @@ -30,7 +30,11 @@ export const useAutoRouterAvailability = (accessToken: string, body: Request, en queryFn: ({ signal }) => apiClient.post("/auto_router/availability", { accessToken, - body: { ...body, complexity_router_config: JSON.parse(debounced) }, + body: { + team_id: body.team_id, + saved_model_id: body.saved_model_id, + complexity_router_config: JSON.parse(debounced), + }, signal, }), enabled: enabled && Boolean(accessToken), @@ -53,8 +57,10 @@ export const useAutoRouterAvailability = (accessToken: string, body: Request, en return query.data.error ?? null; }; return { - ...query, + data: query.data, + isError: query.isError, isPending: query.isPending || (query.isFetching && !query.isFetchedAfterMount), + refetch: query.refetch, isChecking, saveBlockedReason: saveBlockedReason(), }; diff --git a/ui/litellm-dashboard/src/components/chat/LogsPanel.tsx b/ui/litellm-dashboard/src/components/chat/LogsPanel.tsx index e967efde6a4..234fb42513a 100644 --- a/ui/litellm-dashboard/src/components/chat/LogsPanel.tsx +++ b/ui/litellm-dashboard/src/components/chat/LogsPanel.tsx @@ -246,20 +246,19 @@ const LogsPanel: React.FC = ({ accessToken, userId }) => { const [page, setPage] = useState(1); const [selectedLog, setSelectedLog] = useState(null); - const startDate = getStartMoment(timeRange).utc().format("YYYY-MM-DD HH:mm:ss"); - const endDate = moment().utc().format("YYYY-MM-DD HH:mm:ss"); - - const logsCallOptions = { - accessToken, - start_date: startDate, - end_date: endDate, - page, - page_size: PAGE_SIZE, - params: { user_id: userId, sort_by: "startTime", sort_order: "desc" as const }, - }; const logsQueryOptions = { queryKey: [LOGS_QUERY_KEY, accessToken, userId, timeRange, page], - queryFn: () => uiSpendLogsCall(logsCallOptions), + queryFn: () => { + const logsCallOptions = { + accessToken, + start_date: getStartMoment(timeRange).utc().format("YYYY-MM-DD HH:mm:ss"), + end_date: moment().utc().format("YYYY-MM-DD HH:mm:ss"), + page, + page_size: PAGE_SIZE, + params: { user_id: userId, sort_by: "startTime", sort_order: "desc" }, + }; + return uiSpendLogsCall(logsCallOptions); + }, enabled: !!accessToken && !!userId, placeholderData: keepPreviousData, }; @@ -270,10 +269,14 @@ const LogsPanel: React.FC = ({ accessToken, userId }) => { const totalPages = logs?.total_pages ?? 0; const total = logs?.total ?? 0; - const detailStartDate = selectedLog ? moment(selectedLog.startTime).utc().format("YYYY-MM-DD HH:mm:ss") : ""; const { data: detailData, isLoading: isDetailLoading } = useQuery({ queryKey: [LOGS_QUERY_KEY, "detail", accessToken, selectedLog?.request_id, selectedLog?.startTime], - queryFn: () => uiSpendLogDetailsCall(accessToken, selectedLog!.request_id, detailStartDate), + queryFn: () => + uiSpendLogDetailsCall( + accessToken, + selectedLog!.request_id, + moment(selectedLog!.startTime).utc().format("YYYY-MM-DD HH:mm:ss"), + ), enabled: !!accessToken && !!selectedLog, }); const details = detailData as LogDetails | undefined; diff --git a/ui/litellm-dashboard/src/components/chat/UsagePanel.tsx b/ui/litellm-dashboard/src/components/chat/UsagePanel.tsx index e839890a357..2e50cf69df9 100644 --- a/ui/litellm-dashboard/src/components/chat/UsagePanel.tsx +++ b/ui/litellm-dashboard/src/components/chat/UsagePanel.tsx @@ -91,11 +91,11 @@ const TIME_RANGE_OPTIONS: { value: TimeRange; label: string }[] = [ const UsagePanel: React.FC = ({ accessToken, userId }) => { const [timeRange, setTimeRange] = useState("30d"); - const { start, end } = getDateRange(timeRange); const { data, isLoading } = useQuery({ queryKey: [USAGE_QUERY_KEY, accessToken, userId, timeRange], queryFn: () => { + const { start, end } = getDateRange(timeRange); const request = { accessToken, startTime: start, diff --git a/ui/litellm-dashboard/src/components/view_logs/RequestLogsPanel.tsx b/ui/litellm-dashboard/src/components/view_logs/RequestLogsPanel.tsx index 0b5e5ff9616..c623875b92e 100644 --- a/ui/litellm-dashboard/src/components/view_logs/RequestLogsPanel.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/RequestLogsPanel.tsx @@ -132,7 +132,7 @@ export default function RequestLogsPanel({ accessToken, token, userRole, userID, const { data: selectedKeyInfo } = useQuery(keyInfoQueryOptions); const urlLogQueryOptions: UseQueryOptions = { - queryKey: ["logs", "byId", urlLogId, accessToken], + queryKey: ["logs", "byId", urlLogId, accessToken, startTime, endTime, isCustomDate], queryFn: async () => { if (urlLogId === null) return null; const window = formatLogsWindow(startTime, endTime, isCustomDate); diff --git a/ui/litellm-dashboard/src/components/view_logs/log_filter_logic.test.tsx b/ui/litellm-dashboard/src/components/view_logs/log_filter_logic.test.tsx index baf713b1537..a4579daf37d 100644 --- a/ui/litellm-dashboard/src/components/view_logs/log_filter_logic.test.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/log_filter_logic.test.tsx @@ -161,6 +161,9 @@ describe("useLogFilterLogic", () => { ["startTime", { startTime: "2025-02-02T00:00:00" }], ["columnFilters", { columnFilters: [{ id: LOG_FILTER_IDS.TEAM_ID, value: "team-2" }] }], ["excludeInternalHealthChecks", { excludeInternalHealthChecks: true }], + ["token", { token: "other-token" }], + ["userRole", { userRole: "Internal User" }], + ["userID", { userID: "user-2" }], ])("refetches when %s changes", async (_label, nextProps) => { const { rerender } = renderHook((props: HookOverrides) => useLogFilterLogic({ ...defaultProps, ...props }), { wrapper, diff --git a/ui/litellm-dashboard/src/components/view_logs/log_filter_logic.tsx b/ui/litellm-dashboard/src/components/view_logs/log_filter_logic.tsx index 8b2d9f22c15..e5183925a4b 100644 --- a/ui/litellm-dashboard/src/components/view_logs/log_filter_logic.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/log_filter_logic.tsx @@ -155,6 +155,9 @@ export function useLogFilterLogic({ sortOrder, excludeInternalHealthChecks, sessionCursor, + token, + userRole, + userID, ], queryFn: async () => { if (!accessToken || !token || !userRole || !userID) {