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 118371aa9ac..d2247e05eaf 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 @@ -525,6 +525,53 @@ describe("UsagePage", () => { expect(screen.getByText("Top Virtual Keys")).toBeInTheDocument(); }); + it("should not crash when a breakdown entry is missing its metrics (partial/large dataset)", async () => { + const malformedSpendData = { + ...mockSpendData, + results: [ + { + ...mockSpendData.results[0], + breakdown: { + ...mockSpendData.results[0].breakdown, + api_keys: { + ...mockSpendData.results[0].breakdown.api_keys, + "sk-broken": { + metadata: { key_alias: "Broken Key", tags: [] }, + }, + }, + models: { + ...mockSpendData.results[0].breakdown.models, + "broken-model": { + metadata: {}, + api_key_breakdown: {}, + }, + }, + model_groups: { + ...mockSpendData.results[0].breakdown.model_groups, + "broken-group": { + metadata: {}, + api_key_breakdown: {}, + }, + }, + providers: { + ...mockSpendData.results[0].breakdown.providers, + "broken-provider": {}, + }, + }, + }, + ], + }; + mockUserDailyActivityAggregatedCall.mockResolvedValue(malformedSpendData as any); + + renderWithProviders(); + + await waitFor(() => { + expect(mockUserDailyActivityAggregatedCall).toHaveBeenCalled(); + }); + + expect(screen.getByText("Top Virtual Keys")).toBeInTheDocument(); + }); + it("should render the daily spend and top models charts with cyan bars", async () => { const { container } = renderWithProviders(); 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 c742b3af7e9..a26890fe730 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 @@ -262,6 +262,7 @@ const UsagePage: React.FC = ({ teams, organizations }) => { const modelSpend: { [key: string]: MetricWithMetadata } = {}; userSpendData.results.forEach((day) => { Object.entries(day.breakdown.models || {}).forEach(([model, metrics]) => { + if (!metrics?.metrics) return; if (!modelSpend[model]) { modelSpend[model] = { metrics: { @@ -308,6 +309,7 @@ const UsagePage: React.FC = ({ teams, organizations }) => { const modelGroupSpend: { [key: string]: MetricWithMetadata } = {}; userSpendData.results.forEach((day) => { Object.entries(day.breakdown.model_groups || {}).forEach(([modelGroup, metrics]) => { + if (!metrics?.metrics) return; if (!modelGroupSpend[modelGroup]) { modelGroupSpend[modelGroup] = { metrics: { @@ -356,6 +358,7 @@ const UsagePage: React.FC = ({ teams, organizations }) => { const providerSpendMap: { [key: string]: MetricWithMetadata } = {}; userSpendData.results.forEach((day) => { Object.entries(day.breakdown.providers || {}).forEach(([provider, metrics]) => { + if (!metrics?.metrics) return; if (!providerSpendMap[provider]) { providerSpendMap[provider] = { metrics: { @@ -401,6 +404,7 @@ const UsagePage: React.FC = ({ teams, organizations }) => { const keySpend: { [key: string]: KeyMetricWithMetadata } = {}; userSpendData.results.forEach((day) => { Object.entries(day.breakdown.api_keys || {}).forEach(([key, metrics]) => { + if (!metrics?.metrics) return; if (!keySpend[key]) { keySpend[key] = { metrics: { diff --git a/ui/litellm-dashboard/src/components/activity_metrics.tsx b/ui/litellm-dashboard/src/components/activity_metrics.tsx index a3fff08faae..da3475a4d0d 100644 --- a/ui/litellm-dashboard/src/components/activity_metrics.tsx +++ b/ui/litellm-dashboard/src/components/activity_metrics.tsx @@ -452,6 +452,7 @@ export const processActivityData = ( dailyActivity.results.forEach((day) => { Object.entries(day.breakdown[key] || {}).forEach(([model, modelData]) => { + if (!modelData?.metrics) return; if (!modelMetrics[model]) { modelMetrics[model] = { label: @@ -513,6 +514,7 @@ export const processActivityData = ( const modelData = day.breakdown[key]?.[model]; if (modelData && "api_key_breakdown" in modelData) { Object.entries(modelData.api_key_breakdown || {}).forEach(([apiKey, keyData]) => { + if (!keyData?.metrics) return; if (!apiKeyBreakdown[apiKey]) { apiKeyBreakdown[apiKey] = { api_key: apiKey, @@ -549,7 +551,7 @@ export const processActivityData = ( Object.entries(day.breakdown.models || {}).forEach(([modelName, modelData]) => { if (modelData && "api_key_breakdown" in modelData) { const keyDataForModel = modelData.api_key_breakdown?.[apiKeyHash]; - if (keyDataForModel) { + if (keyDataForModel?.metrics) { if (!modelBreakdown[modelName]) { modelBreakdown[modelName] = { model: modelName,