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 3a97e54edfc..a9ab0f17f40 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 @@ -908,7 +908,7 @@ const UsagePage: React.FC = ({ teams, organizations }) => { - + diff --git a/ui/litellm-dashboard/src/components/UsagePage/components/KeyActivityPanel.test.tsx b/ui/litellm-dashboard/src/components/UsagePage/components/KeyActivityPanel.test.tsx index 693ac20a360..830139143e9 100644 --- a/ui/litellm-dashboard/src/components/UsagePage/components/KeyActivityPanel.test.tsx +++ b/ui/litellm-dashboard/src/components/UsagePage/components/KeyActivityPanel.test.tsx @@ -68,4 +68,14 @@ describe("KeyActivityPanel", () => { expect(screen.getByLabelText("Search keys")).toHaveValue(""); expect(screen.getByTestId("rendered-keys")).toHaveTextContent("hash-alicehash-bob"); }); + + it("says how many keys the proxy left out when only the top spenders were loaded", () => { + render(); + expect(screen.getByRole("note")).toHaveTextContent("Only the 2 highest-spend keys of 3,000 are loaded"); + }); + + it("shows no truncation note when every key is loaded", () => { + render(); + expect(screen.queryByRole("note")).not.toBeInTheDocument(); + }); }); diff --git a/ui/litellm-dashboard/src/components/UsagePage/components/KeyActivityPanel.tsx b/ui/litellm-dashboard/src/components/UsagePage/components/KeyActivityPanel.tsx index 8287a04d0c7..8b2141f8528 100644 --- a/ui/litellm-dashboard/src/components/UsagePage/components/KeyActivityPanel.tsx +++ b/ui/litellm-dashboard/src/components/UsagePage/components/KeyActivityPanel.tsx @@ -2,6 +2,7 @@ import { Search, X } from "lucide-react"; import React, { useMemo, useState } from "react"; import { ActivityMetrics } from "@/components/activity_metrics"; +import type { ApiKeyTruncation } from "@/components/EntityUsageExport/exportBlockedReason"; import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "@/components/ui/input-group"; import { filterKeyActivity } from "../keyActivityFilter"; @@ -10,9 +11,14 @@ import type { ModelActivityData } from "../types"; interface KeyActivityPanelProps { keyMetrics: Record; hidePromptCachingMetrics?: boolean; + apiKeyTruncation?: ApiKeyTruncation; } -const KeyActivityPanel: React.FC = ({ keyMetrics, hidePromptCachingMetrics = false }) => { +const KeyActivityPanel: React.FC = ({ + keyMetrics, + hidePromptCachingMetrics = false, + apiKeyTruncation, +}) => { const [query, setQuery] = useState(""); const filtered = useMemo(() => filterKeyActivity(keyMetrics, query), [keyMetrics, query]); const totalKeys = Object.keys(keyMetrics).length; @@ -43,6 +49,12 @@ const KeyActivityPanel: React.FC = ({ keyMetrics, hidePro Showing {shownKeys.toLocaleString()} of {totalKeys.toLocaleString()} keys + {apiKeyTruncation !== undefined && ( + + Only the {apiKeyTruncation.limit.toLocaleString()} highest-spend keys of{" "} + {apiKeyTruncation.total.toLocaleString()} are loaded + + )} {isFiltering && totalKeys > 0 && shownKeys === 0 ? (