From 2e391dcfd25ff31714e5658c448a58ec7efa43fe Mon Sep 17 00:00:00 2001
From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Date: Thu, 16 Jul 2026 15:39:46 +0000
Subject: [PATCH] fix(ui): guard usage breakdown aggregation against entries
missing metrics
---
.../components/UsagePageView.test.tsx | 47 +++++++++++++++++++
.../_components/components/UsagePageView.tsx | 4 ++
.../src/components/activity_metrics.tsx | 4 +-
3 files changed, 54 insertions(+), 1 deletion(-)
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 cf122137f91..c39db5b93a6 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
@@ -596,6 +596,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 d2f75609d18..cffc3c17a7a 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
@@ -252,6 +252,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: {
@@ -298,6 +299,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: {
@@ -346,6 +348,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: {
@@ -391,6 +394,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 54ac5ae0ee6..97634bd6d0d 100644
--- a/ui/litellm-dashboard/src/components/activity_metrics.tsx
+++ b/ui/litellm-dashboard/src/components/activity_metrics.tsx
@@ -369,6 +369,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:
@@ -430,6 +431,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,
@@ -466,7 +468,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,