diff --git a/ui/litellm-dashboard/src/components/view_logs/index.tsx b/ui/litellm-dashboard/src/components/view_logs/index.tsx index a14a263a3fe..e632b8da339 100644 --- a/ui/litellm-dashboard/src/components/view_logs/index.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/index.tsx @@ -259,7 +259,7 @@ export default function SpendLogsTable({ if (!accessToken) return; try { - const response = await keyListCall(accessToken, null, null, keyAlias, null, null, currentPage, pageSize); + const response = await keyListCall(accessToken, null, null, keyAlias, null, null, 1, pageSize); const selectedKey = response.keys.find((key: any) => key.key_alias === keyAlias); @@ -270,7 +270,7 @@ export default function SpendLogsTable({ console.error("Error fetching key hash for alias:", error); } }, - [accessToken, currentPage, pageSize], + [accessToken, pageSize], ); const handleFilterReset = useCallback(() => { 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 da4822d0189..0b9cd59b9aa 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 @@ -570,6 +570,63 @@ describe("useLogFilterLogic", () => { ); }); + it("should refetch when startTime changes and backend filters are active", async () => { + vi.mocked(uiSpendLogsCall).mockResolvedValue( + createPaginatedResponse([createLogEntry()]), + ); + const logs = createPaginatedResponse([createLogEntry()]); + const { result, rerender } = renderHook( + (props: { startTime?: string }) => + useLogFilterLogic({ ...defaultProps, logs, ...props }), + { wrapper, initialProps: { startTime: "2025-01-01T00:00:00Z" } }, + ); + + act(() => { + result.current.handleFilterChange({ "Key Alias": "alias-1" }); + }); + + await waitFor(() => expect(uiSpendLogsCall).toHaveBeenCalledTimes(1), { + timeout: 500, + }); + + rerender({ startTime: "2025-01-02T00:00:00Z" }); + + await waitFor(() => expect(uiSpendLogsCall).toHaveBeenCalledTimes(2), { + timeout: 500, + }); + expect(uiSpendLogsCall).toHaveBeenLastCalledWith( + expect.objectContaining({ + start_date: "2025-01-02 00:00:00", + }), + ); + }); + + it("should refetch when isCustomDate changes and backend filters are active", async () => { + vi.mocked(uiSpendLogsCall).mockResolvedValue( + createPaginatedResponse([createLogEntry()]), + ); + const logs = createPaginatedResponse([createLogEntry()]); + const { result, rerender } = renderHook( + (props: { isCustomDate?: boolean }) => + useLogFilterLogic({ ...defaultProps, logs, ...props }), + { wrapper, initialProps: { isCustomDate: false } }, + ); + + act(() => { + result.current.handleFilterChange({ "Key Alias": "alias-1" }); + }); + + await waitFor(() => expect(uiSpendLogsCall).toHaveBeenCalledTimes(1), { + timeout: 500, + }); + + rerender({ isCustomDate: true }); + + await waitFor(() => expect(uiSpendLogsCall).toHaveBeenCalledTimes(2), { + timeout: 500, + }); + }); + it("should not call setCurrentPage when handleFilterChange receives identical filters", async () => { const setCurrentPage = vi.fn(); const logs = createPaginatedResponse([createLogEntry()]); 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 d9323b03afb..58e86cb005d 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 @@ -158,12 +158,15 @@ export function useLogFilterLogic({ [filters], ); - // Refetch when sort or page changes (backend filters use their own fetch, not the main query) + // Refetch when sort, page, or time range changes (backend filters use their own fetch, not the main query) useEffect(() => { if (hasBackendFilters && accessToken) { + // Cancel any pending debounced search to prevent it from overwriting this page's results + debouncedSearch.cancel(); performSearch(filters, currentPage); } - }, [sortBy, sortOrder, currentPage]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [sortBy, sortOrder, currentPage, startTime, endTime, isCustomDate]); // Compute client-side filtered logs directly from incoming logs and filters const clientDerivedFilteredLogs: PaginatedResponse = useMemo(() => {