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 f2dc56af9c5..7d9bee735b4 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
@@ -610,6 +610,42 @@ describe("UsagePage", () => {
expect(screen.getByTestId("entity-usage")).toHaveAttribute("data-entity-list", "[]");
});
+ it("should drop the previous range's tags as soon as the range changes", async () => {
+ mockTagListCall.mockResolvedValue({ "old-range-tag": { name: "old-range-tag" } } as never);
+
+ renderWithProviders();
+
+ act(() => {
+ fireEvent.change(screen.getByTestId("usage-view-select"), { target: { value: "tag" } });
+ });
+
+ await waitFor(() => {
+ expect(screen.getByTestId("entity-usage")).toHaveAttribute(
+ "data-entity-list",
+ JSON.stringify([{ label: "old-range-tag", value: "old-range-tag" }]),
+ );
+ });
+
+ let resolveNewRange: (tags: Record) => void = () => {};
+ mockTagListCall.mockReturnValue(
+ new Promise((resolve) => {
+ resolveNewRange = resolve;
+ }) as ReturnType,
+ );
+
+ act(() => {
+ fireEvent.click(screen.getByTestId("pick-a-different-range"));
+ });
+
+ expect(screen.getByTestId("entity-usage")).toHaveAttribute("data-entity-list", "null");
+
+ await act(async () => {
+ resolveNewRange({});
+ });
+
+ expect(screen.getByTestId("entity-usage")).toHaveAttribute("data-entity-list", "[]");
+ });
+
it("should show tag usage selector option for internal users", async () => {
mockUseAuthorized.mockReturnValue({
isLoading: false,
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 0d1e557a833..19d7a752938 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
@@ -96,7 +96,7 @@ const UsagePage: React.FC = ({ teams, organizations }) => {
to: initialToDate,
});
- const [allTags, setAllTags] = useState(null);
+ const [fetchedTags, setFetchedTags] = useState | null>(null);
const { data: customers = [] } = useCustomers();
const { data: agentsResponse } = useAgents();
const { data: currentUser } = useCurrentUser();
@@ -138,6 +138,12 @@ const UsagePage: React.FC = ({ teams, organizations }) => {
const startTime = useMemo(() => (dateValue.from ? new Date(dateValue.from) : null), [dateValue.from]);
const endTime = useMemo(() => (dateValue.to ? new Date(dateValue.to) : null), [dateValue.to]);
+ // Stamped and selected during render like the request tiles below: the tag
+ // filter reads "no tags" from an empty list, so a list left over from the
+ // previous range would state that about a range nobody has measured yet.
+ const currentTagRangeKey = fetchedRangeKey(startTime, endTime);
+ const allTags = selectForRange(fetchedTags, currentTagRangeKey);
+
useEffect(() => {
if (!accessToken) return;
let cancelled = false;
@@ -145,12 +151,13 @@ const UsagePage: React.FC = ({ teams, organizations }) => {
try {
const tags = await tagListCall(accessToken, startTime, endTime);
if (cancelled) return;
- setAllTags(
- Object.values(tags).map((tag: Tag) => ({
+ setFetchedTags({
+ rangeKey: currentTagRangeKey,
+ value: Object.values(tags).map((tag: Tag) => ({
label: tag.name,
value: tag.name,
})),
- );
+ });
} catch (e) {
if (!cancelled) {
console.error("Failed to fetch tag list", e);
@@ -160,7 +167,7 @@ const UsagePage: React.FC = ({ teams, organizations }) => {
return () => {
cancelled = true;
};
- }, [accessToken, startTime, endTime]);
+ }, [accessToken, startTime, endTime, currentTagRangeKey]);
// Everything the request tiles read is stamped with the range it answers and
// selected during render, rather than cleared in an effect. An effect runs