From 79b24c8f259df982c9fd0d59ea20181e602670a0 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 10 Feb 2026 14:11:55 -0800 Subject: [PATCH] remove stack from charts --- .../src/components/activity_metrics.test.tsx | 155 ++++++++++++++++++ .../src/components/activity_metrics.tsx | 1 - 2 files changed, 155 insertions(+), 1 deletion(-) diff --git a/ui/litellm-dashboard/src/components/activity_metrics.test.tsx b/ui/litellm-dashboard/src/components/activity_metrics.test.tsx index 02738a4666..308b4b395f 100644 --- a/ui/litellm-dashboard/src/components/activity_metrics.test.tsx +++ b/ui/litellm-dashboard/src/components/activity_metrics.test.tsx @@ -34,8 +34,25 @@ vi.mock("antd", () => { ); PanelComponent.displayName = "Collapse.Panel"; CollapseComponent.Panel = PanelComponent; + const TableComponent = ({ dataSource, columns }: { dataSource?: unknown[]; columns?: { title: string }[] }) => ( + + + + {columns?.map((col, i) => ( + + ))} + + + + {dataSource?.map((_, i) => ( + + ))} + +
{col.title}
+ ); return { Collapse: CollapseComponent, + Table: TableComponent, }; }); @@ -95,6 +112,7 @@ const MOCK_TEAMS: Team[] = [ created_at: "2025-01-01", keys: [], members_with_roles: [], + spend: 0, }, { team_id: "team2", @@ -108,6 +126,7 @@ const MOCK_TEAMS: Team[] = [ created_at: "2025-01-01", keys: [], members_with_roles: [], + spend: 0, }, ]; @@ -141,6 +160,7 @@ const createMockModelActivityData = (label: string, overrides: Partial { expect(tokenElements.length).toBeGreaterThan(0); }); + it("should not display Top Virtual Keys section when model has no top_api_keys", () => { + render(); + expect(screen.queryByText("Top Virtual Keys by Spend")).not.toBeInTheDocument(); + }); + it("should display top API keys section when present", () => { const modelWithTopKeys: Record = { "gpt-4": { @@ -329,6 +355,58 @@ describe("ActivityMetrics", () => { expect(screen.getByText(/Team: team1/)).toBeInTheDocument(); }); + it("should display Model Usage when model has top_models", () => { + const modelWithTopModels: Record = { + "gpt-4": { + ...mockModelMetrics["gpt-4"], + top_models: [ + { + model: "gpt-4", + spend: 100.5, + requests: 100, + successful_requests: 95, + failed_requests: 5, + tokens: 50000, + }, + ], + }, + }; + + render(); + expect(screen.getByRole("heading", { name: "Model Usage" })).toBeInTheDocument(); + }); + + it("should display Spend per day in model section", () => { + render(); + expect(screen.getByText("Spend per day")).toBeInTheDocument(); + }); + + it("should display Requests per day in model section", () => { + render(); + expect(screen.getByText("Requests per day")).toBeInTheDocument(); + }); + + it("should display Success vs Failed Requests in model section", () => { + render(); + expect(screen.getByText("Success vs Failed Requests")).toBeInTheDocument(); + }); + + it("should sort empty string model key last in collapse order", () => { + const modelsWithEmptyKey: Record = { + "gpt-4": { ...mockModelMetrics["gpt-4"] }, + "": { + ...createMockModelActivityData(""), + label: "Unknown", + }, + }; + + render(); + const headings = screen.getAllByRole("heading", { level: 2 }); + const gpt4Index = headings.findIndex((h) => h.textContent?.includes("GPT-4")); + const unknownIndex = headings.findIndex((h) => h.textContent?.includes("Unknown")); + expect(gpt4Index).toBeLessThan(unknownIndex); + }); + it("should display average tokens per successful request", () => { render(); const avgTokensElements = screen.getAllByText(/avg per successful request/); @@ -962,6 +1040,73 @@ describe("processActivityData", () => { expect(result["gpt-4"].top_api_keys[4].spend).toBe(16.0); }); + it("should return empty object when results array is empty", () => { + const result = processActivityData({ results: [] }, "models"); + expect(result).toEqual({}); + }); + + it("should populate top_models for api_keys when models breakdown contains api_key_breakdown for that key", () => { + const dailyActivityWithModelsForKey: { results: DailyData[] } = { + results: [ + { + date: "2025-01-01", + metrics: EMPTY_SPEND_METRICS, + breakdown: { + models: { + "gpt-4": { + metrics: EMPTY_SPEND_METRICS, + metadata: {}, + api_key_breakdown: { + "api-key-hash-1": { + metrics: { + spend: 60.0, + prompt_tokens: 18000, + completion_tokens: 12000, + total_tokens: 30000, + api_requests: 60, + successful_requests: 57, + failed_requests: 3, + cache_read_input_tokens: 0, + cache_creation_input_tokens: 0, + }, + metadata: { key_alias: "key-alias-1", team_id: "team1" }, + }, + }, + }, + }, + model_groups: {}, + mcp_servers: {}, + providers: {}, + api_keys: { + "api-key-hash-1": { + metrics: { + spend: 60.0, + prompt_tokens: 18000, + completion_tokens: 12000, + total_tokens: 30000, + api_requests: 60, + successful_requests: 57, + failed_requests: 3, + cache_read_input_tokens: 0, + cache_creation_input_tokens: 0, + }, + metadata: { key_alias: "key-alias-1", team_id: "team1" }, + }, + }, + entities: {}, + }, + }, + ], + }; + + const result = processActivityData(dailyActivityWithModelsForKey, "api_keys", MOCK_TEAMS); + + expect(result["api-key-hash-1"].top_models).toHaveLength(1); + expect(result["api-key-hash-1"].top_models[0].model).toBe("gpt-4"); + expect(result["api-key-hash-1"].top_models[0].spend).toBe(60.0); + expect(result["api-key-hash-1"].top_models[0].requests).toBe(60); + }); + it("should not process api_key_breakdown when key is api_keys", () => { const dailyActivityWithBreakdown: { results: DailyData[] } = { results: [ @@ -1130,4 +1275,14 @@ describe("formatKeyLabel", () => { const result = formatKeyLabel(modelData, "actual-key", MOCK_TEAMS); expect(result).toBe("key-hash-actual-key (team: Test Team 1)"); }); + + it("should return key_alias with team_id when teams array is empty", () => { + const modelData = createMockKeyMetricWithMetadata({ + key_alias: "my-key", + team_id: "team1", + }); + + const result = formatKeyLabel(modelData, "my-key", []); + expect(result).toBe("my-key (team_id: team1)"); + }); }); diff --git a/ui/litellm-dashboard/src/components/activity_metrics.tsx b/ui/litellm-dashboard/src/components/activity_metrics.tsx index 61006fa614..2337d41d8d 100644 --- a/ui/litellm-dashboard/src/components/activity_metrics.tsx +++ b/ui/litellm-dashboard/src/components/activity_metrics.tsx @@ -148,7 +148,6 @@ const ModelSection = ({ categories={["metrics.successful_requests", "metrics.failed_requests"]} colors={["green", "red"]} valueFormatter={valueFormatter} - stack customTooltip={CustomTooltip} showLegend={false} />