diff --git a/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/EntityUsage/EntityUsage.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/EntityUsage/EntityUsage.test.tsx index 666172947d1..5bb48a78437 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/EntityUsage/EntityUsage.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/EntityUsage/EntityUsage.test.tsx @@ -65,10 +65,19 @@ vi.mock("@/components/EntityUsageExport/EntityUsageExportModal", () => ({ })); vi.mock("@/components/EntityUsageExport", () => ({ - UsageExportHeader: ({ filterLabel, filterSlot }: { filterLabel?: string; filterSlot?: ReactNode }) => ( + UsageExportHeader: ({ + filterLabel, + filterSlot, + showFilters, + }: { + filterLabel?: string; + filterSlot?: ReactNode; + showFilters?: boolean; + }) => (
Usage Export Header {filterLabel} + {`show-filters:${showFilters === true}`} {filterSlot}
), @@ -739,6 +748,26 @@ describe("EntityUsage", () => { }); }); + it("should still request the filter when the caller's tag scope is empty", async () => { + render(); + + await waitFor(() => { + expect(mockTagDailyActivityCall).toHaveBeenCalled(); + }); + + expect(screen.getByText("show-filters:true")).toBeInTheDocument(); + }); + + it("should not request the filter while the entity list is still unresolved", async () => { + render(); + + await waitFor(() => { + expect(mockTagDailyActivityCall).toHaveBeenCalled(); + }); + + expect(screen.getByText("show-filters:false")).toBeInTheDocument(); + }); + it("should display Agent Activity tab for team entity type", async () => { render(); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/EntityUsage/EntityUsage.tsx b/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/EntityUsage/EntityUsage.tsx index 9501fa7a9a1..ef3943e5b71 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/EntityUsage/EntityUsage.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/EntityUsage/EntityUsage.tsx @@ -661,7 +661,7 @@ const EntityUsage: React.FC = ({ dateValue={dateValue} entityType={entityType} spendData={spendData} - showFilters={filterSlot === undefined && entityList !== null && entityList.length > 0} + showFilters={filterSlot === undefined && entityList !== null} filterSlot={filterSlot} filterLabel={getFilterLabel(entityType)} filterPlaceholder={getFilterPlaceholder(entityType)} diff --git a/ui/litellm-dashboard/src/components/EntityUsageExport/UsageExportHeader.test.tsx b/ui/litellm-dashboard/src/components/EntityUsageExport/UsageExportHeader.test.tsx index 27985c3db28..6c824459c08 100644 --- a/ui/litellm-dashboard/src/components/EntityUsageExport/UsageExportHeader.test.tsx +++ b/ui/litellm-dashboard/src/components/EntityUsageExport/UsageExportHeader.test.tsx @@ -84,4 +84,41 @@ describe("UsageExportHeader", () => { expect(screen.getByTestId("custom-filter")).toBeInTheDocument(); expect(screen.queryByRole("combobox")).not.toBeInTheDocument(); }); + + it("should keep the filter visible and disabled with an explanation when the caller has no options", () => { + renderWithProviders( + , + ); + + expect(screen.getByText("Filter by tag")).toBeInTheDocument(); + const input = screen.getByPlaceholderText("No tags with usage in this range"); + expect(input).toBeDisabled(); + expect(screen.queryByPlaceholderText("Select tag to filter...")).not.toBeInTheDocument(); + }); + + it("should leave the filter enabled with its normal placeholder when options exist", () => { + renderWithProviders( + , + ); + + const input = screen.getByPlaceholderText("Select tag to filter..."); + expect(input).toBeEnabled(); + expect(screen.queryByPlaceholderText("No tags with usage in this range")).not.toBeInTheDocument(); + }); }); diff --git a/ui/litellm-dashboard/src/components/EntityUsageExport/UsageExportHeader.tsx b/ui/litellm-dashboard/src/components/EntityUsageExport/UsageExportHeader.tsx index e694f905d8c..0d040e7c30b 100644 --- a/ui/litellm-dashboard/src/components/EntityUsageExport/UsageExportHeader.tsx +++ b/ui/litellm-dashboard/src/components/EntityUsageExport/UsageExportHeader.tsx @@ -54,9 +54,11 @@ const UsageExportHeader: React.FC = ({ const anchor = useComboboxAnchor(); const [isExportModalOpen, setIsExportModalOpen] = useState(false); - const hasFilters = filterSlot != null || (showFilters && filterOptions.length > 0); + const hasFilters = filterSlot != null || showFilters; const optionValues = filterOptions.map((option) => option.value); 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`; const filterList = ( @@ -74,6 +76,7 @@ const UsageExportHeader: React.FC = ({ const builtInFilter = ( onFiltersChange?.(next)} @@ -88,7 +91,10 @@ const UsageExportHeader: React.FC = ({ )) } - + {selectedFilters.length > 0 && } {filterList}