diff --git a/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/UsagePageView.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/UsagePageView.test.tsx index 7d9bee735b4..26d595f4d74 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/UsagePageView.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/UsagePageView.test.tsx @@ -754,6 +754,19 @@ describe("UsagePage", () => { }); }); + it("should withhold the customer list while it is still loading", async () => { + mockUseCustomers.mockReturnValue({ data: undefined, isLoading: true, error: null } as any); + + renderWithProviders(); + + act(() => { + fireEvent.change(screen.getByTestId("usage-view-select"), { target: { value: "customer" } }); + }); + + const entityUsage = await screen.findByTestId("entity-usage"); + expect(entityUsage).toHaveAttribute("data-entity-list", "null"); + }); + it("should show agent usage view for admins", async () => { mockUseAgents.mockReturnValue({ data: { agents: mockAgents }, diff --git a/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/UsagePageView.tsx b/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/UsagePageView.tsx index 19d7a752938..cbdfc8f39e6 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/UsagePageView.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/UsagePageView.tsx @@ -97,7 +97,9 @@ const UsagePage: React.FC = ({ teams, organizations }) => { }); const [fetchedTags, setFetchedTags] = useState | null>(null); - const { data: customers = [] } = useCustomers(); + // No [] default: an unresolved query must stay undefined so the customer + // filter reads as loading rather than as a range with no customers. + const { data: customers } = useCustomers(); const { data: agentsResponse } = useAgents(); const { data: currentUser } = useCurrentUser(); const isAdmin = all_admin_roles.includes(userRole || ""); diff --git a/ui/litellm-dashboard/src/components/EntityUsageExport/UsageExportHeader.test.tsx b/ui/litellm-dashboard/src/components/EntityUsageExport/UsageExportHeader.test.tsx index 6c824459c08..52fc7605d90 100644 --- a/ui/litellm-dashboard/src/components/EntityUsageExport/UsageExportHeader.test.tsx +++ b/ui/litellm-dashboard/src/components/EntityUsageExport/UsageExportHeader.test.tsx @@ -104,6 +104,28 @@ describe("UsageExportHeader", () => { expect(screen.queryByPlaceholderText("Select tag to filter...")).not.toBeInTheDocument(); }); + it("should stay usable when a carried-over selection outlives its options", async () => { + const user = userEvent.setup(); + const onFiltersChange = vi.fn(); + renderWithProviders( + , + ); + + expect(screen.getByPlaceholderText("No tags with usage in this range")).toBeEnabled(); + + await user.click(screen.getByRole("button", { name: "Clear Filter by tag" })); + expect(onFiltersChange).toHaveBeenCalledWith([]); + }); + it("should leave the filter enabled with its normal placeholder when options exist", () => { renderWithProviders( = ({ const labelOf = (value: string) => filterOptions.find((option) => option.value === value)?.label ?? value; const hasNoOptions = filterOptions.length === 0; const emptyPlaceholder = `No ${entityType}s with usage in this range`; + // A selection carried over from a range that did have options still scopes + // the data below, so the control has to stay usable long enough to clear it. + const isFilterDisabled = hasNoOptions && selectedFilters.length === 0; const filterList = ( @@ -76,7 +79,7 @@ const UsageExportHeader: React.FC = ({ const builtInFilter = ( onFiltersChange?.(next)}