mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-21 00:21:49 +00:00
fix(ui): fall back to user id in Top Virtual Keys user column
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
e93fe60512
commit
88799f6f80
7 changed files with 200 additions and 89 deletions
|
|
@ -643,6 +643,24 @@ def test_key_metadata_includes_recovered_user_email():
|
|||
assert meta.user_email == "alice@example.com"
|
||||
|
||||
|
||||
def test_key_metadata_includes_user_id_without_user_email():
|
||||
from litellm.proxy.management_endpoints.common_daily_activity import _key_metadata
|
||||
|
||||
meta = _key_metadata(
|
||||
{
|
||||
"dirty-key": {
|
||||
"key_alias": "batch-worker",
|
||||
"team_id": "team-1",
|
||||
"user_id": "user-123",
|
||||
}
|
||||
},
|
||||
"dirty-key",
|
||||
)
|
||||
|
||||
assert meta.user_id == "user-123"
|
||||
assert meta.user_email is None
|
||||
|
||||
|
||||
def test_update_breakdown_metrics_includes_user_email():
|
||||
from litellm.proxy.management_endpoints.common_daily_activity import update_breakdown_metrics
|
||||
from litellm.types.proxy.management_endpoints.common_daily_activity import BreakdownMetrics
|
||||
|
|
|
|||
|
|
@ -5,7 +5,39 @@ import type { ReactNode } from "react";
|
|||
import { useInfiniteUsers } from "@/app/(dashboard)/hooks/users/useUsers";
|
||||
import useTeams from "@/app/(dashboard)/hooks/useTeams";
|
||||
import * as networking from "@/components/networking";
|
||||
import type { DailyData, KeyMetadata, KeyMetricWithMetadata, SpendMetrics } from "@/components/UsagePage/types";
|
||||
import EntityUsage from "./EntityUsage";
|
||||
import { getGlobalTopKeys, getTopAPIKeys } from "./entityUsageAggregations";
|
||||
|
||||
const emptySpendMetrics: SpendMetrics = {
|
||||
spend: 0,
|
||||
prompt_tokens: 0,
|
||||
completion_tokens: 0,
|
||||
total_tokens: 0,
|
||||
api_requests: 0,
|
||||
successful_requests: 0,
|
||||
failed_requests: 0,
|
||||
cache_read_input_tokens: 0,
|
||||
cache_creation_input_tokens: 0,
|
||||
};
|
||||
|
||||
const createKeyMetrics = (spend: number, metadata: KeyMetadata): KeyMetricWithMetadata => ({
|
||||
metrics: { ...emptySpendMetrics, spend },
|
||||
metadata,
|
||||
});
|
||||
|
||||
const createDailyData = (date: string, apiKeys: Record<string, KeyMetricWithMetadata>): DailyData => ({
|
||||
date,
|
||||
metrics: { ...emptySpendMetrics },
|
||||
breakdown: {
|
||||
models: {},
|
||||
model_groups: {},
|
||||
mcp_servers: {},
|
||||
providers: {},
|
||||
api_keys: apiKeys,
|
||||
entities: {},
|
||||
},
|
||||
});
|
||||
|
||||
beforeAll(() => {
|
||||
if (typeof window !== "undefined" && !window.ResizeObserver) {
|
||||
|
|
@ -44,11 +76,11 @@ vi.mock("../EndpointUsage/EndpointUsage", () => ({
|
|||
}));
|
||||
|
||||
vi.mock("@/components/UsagePage/components/EntityUsage/TopKeyView", () => ({
|
||||
default: ({ topKeys }: { topKeys: { api_key: string; user_email: string | null; spend: number }[] }) => (
|
||||
default: ({ topKeys }: { topKeys: { api_key: string; user?: string | null; spend: number }[] }) => (
|
||||
<div>
|
||||
<span>Top Keys</span>
|
||||
<span>
|
||||
{`top-keys:${topKeys.map((row) => `${row.api_key}=${row.spend}=${row.user_email ?? "-"}`).join("|")}`}
|
||||
{`top-keys:${topKeys.map((row) => `${row.api_key}=${row.spend}=${row.user ?? "-"}`).join("|")}`}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
|
|
@ -433,6 +465,41 @@ describe("EntityUsage", () => {
|
|||
);
|
||||
});
|
||||
|
||||
describe("top key aggregations", () => {
|
||||
it("sums, sorts, limits, and carries email attribution for global top keys", () => {
|
||||
const results = [
|
||||
createDailyData("2025-01-01", {
|
||||
"key-low": createKeyMetrics(10, { key_alias: "Low", team_id: null, user_email: "low@example.com" }),
|
||||
"key-high": createKeyMetrics(25, { key_alias: "High", team_id: null, user_email: "high@example.com" }),
|
||||
}),
|
||||
createDailyData("2025-01-02", {
|
||||
"key-low": createKeyMetrics(30, { key_alias: "Low", team_id: null, user_email: "low@example.com" }),
|
||||
}),
|
||||
];
|
||||
|
||||
expect(getGlobalTopKeys(results, 1)).toEqual([
|
||||
{
|
||||
api_key: "key-low",
|
||||
key_alias: "Low",
|
||||
user: "low@example.com",
|
||||
tags: [],
|
||||
spend: 40,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("falls back to user ID attribution for global and entity top keys", () => {
|
||||
const results = [
|
||||
createDailyData("2025-01-01", {
|
||||
"key-123": createKeyMetrics(12.5, { key_alias: "User ID key", team_id: null, user_id: "user-123" }),
|
||||
}),
|
||||
];
|
||||
|
||||
expect(getGlobalTopKeys(results, 5)[0]?.user).toBe("user-123");
|
||||
expect(getTopAPIKeys(results, 5)[0]?.user).toBe("user-123");
|
||||
});
|
||||
});
|
||||
|
||||
it("should render with tag entity type and display spend metrics", async () => {
|
||||
render(<EntityUsage {...defaultProps} />);
|
||||
|
||||
|
|
|
|||
|
|
@ -86,6 +86,56 @@ export const getTopAgents = (results: ExtendedDailyData[], topAgentsLimit: numbe
|
|||
.slice(0, topAgentsLimit);
|
||||
};
|
||||
|
||||
export const getGlobalTopKeys = (results: DailyData[], topKeysLimit: number): TopKeyItem[] => {
|
||||
const keySpend: { [key: string]: KeyMetricWithMetadata } = {};
|
||||
results.forEach((day) => {
|
||||
Object.entries(day.breakdown.api_keys || {}).forEach(([key, metrics]) => {
|
||||
if (!keySpend[key]) {
|
||||
keySpend[key] = {
|
||||
metrics: {
|
||||
spend: 0,
|
||||
prompt_tokens: 0,
|
||||
completion_tokens: 0,
|
||||
total_tokens: 0,
|
||||
api_requests: 0,
|
||||
successful_requests: 0,
|
||||
failed_requests: 0,
|
||||
cache_read_input_tokens: 0,
|
||||
cache_creation_input_tokens: 0,
|
||||
},
|
||||
metadata: {
|
||||
key_alias: metrics.metadata.key_alias,
|
||||
team_id: null,
|
||||
user_id: metrics.metadata.user_id,
|
||||
user_email: metrics.metadata.user_email,
|
||||
tags: metrics.metadata.tags || [],
|
||||
},
|
||||
};
|
||||
}
|
||||
keySpend[key].metrics.spend += metrics.metrics.spend;
|
||||
keySpend[key].metrics.prompt_tokens += metrics.metrics.prompt_tokens;
|
||||
keySpend[key].metrics.completion_tokens += metrics.metrics.completion_tokens;
|
||||
keySpend[key].metrics.total_tokens += metrics.metrics.total_tokens;
|
||||
keySpend[key].metrics.api_requests += metrics.metrics.api_requests;
|
||||
keySpend[key].metrics.successful_requests += metrics.metrics.successful_requests;
|
||||
keySpend[key].metrics.failed_requests += metrics.metrics.failed_requests;
|
||||
keySpend[key].metrics.cache_read_input_tokens += metrics.metrics.cache_read_input_tokens || 0;
|
||||
keySpend[key].metrics.cache_creation_input_tokens += metrics.metrics.cache_creation_input_tokens || 0;
|
||||
});
|
||||
});
|
||||
|
||||
return Object.entries(keySpend)
|
||||
.map(([api_key, metrics]) => ({
|
||||
api_key,
|
||||
key_alias: keyActivityLabel(metrics.metadata),
|
||||
user: metrics.metadata.user_email ?? metrics.metadata.user_id ?? null,
|
||||
tags: metrics.metadata.tags || [],
|
||||
spend: metrics.metrics.spend,
|
||||
}))
|
||||
.sort((a, b) => b.spend - a.spend)
|
||||
.slice(0, topKeysLimit);
|
||||
};
|
||||
|
||||
export const getTopAPIKeys = (results: ExtendedDailyData[], topKeysLimit: number): TopKeyItem[] => {
|
||||
const keySpend: { [key: string]: KeyMetricWithMetadata } = {};
|
||||
results.forEach((day) => {
|
||||
|
|
@ -120,6 +170,7 @@ export const getTopAPIKeys = (results: ExtendedDailyData[], topKeysLimit: number
|
|||
metadata: {
|
||||
key_alias: metrics.metadata.key_alias,
|
||||
team_id: metrics.metadata.team_id || null,
|
||||
user_id: metrics.metadata.user_id,
|
||||
user_email: metrics.metadata.user_email,
|
||||
tags: tagDictionary[key] || [],
|
||||
},
|
||||
|
|
@ -141,7 +192,7 @@ export const getTopAPIKeys = (results: ExtendedDailyData[], topKeysLimit: number
|
|||
.map(([api_key, metrics]) => ({
|
||||
api_key,
|
||||
key_alias: keyActivityLabel(metrics.metadata),
|
||||
user_email: metrics.metadata.user_email ?? null,
|
||||
user: metrics.metadata.user_email ?? metrics.metadata.user_id ?? null,
|
||||
tags: metrics.metadata.tags || [],
|
||||
spend: metrics.metrics.spend,
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -46,8 +46,7 @@ import { Tag } from "@/components/tag_management/types";
|
|||
import UserAgentActivity from "@/components/user_agent_activity";
|
||||
import ViewUserSpend from "@/components/view_user_spend";
|
||||
import { usePaginatedDailyActivity } from "../hooks/usePaginatedDailyActivity";
|
||||
import { keyActivityLabel } from "@/components/UsagePage/keyActivityLabel";
|
||||
import { DailyData, KeyMetricWithMetadata, MetricWithMetadata } from "@/components/UsagePage/types";
|
||||
import { DailyData, MetricWithMetadata } from "@/components/UsagePage/types";
|
||||
import { valueFormatterSpend } from "@/components/UsagePage/utils/value_formatters";
|
||||
import {
|
||||
fetchedRangeKey,
|
||||
|
|
@ -64,6 +63,7 @@ import ModelViewToggle, { ModelViewType } from "./ModelViewToggle";
|
|||
import SpendByProvider from "./EntityUsage/SpendByProvider";
|
||||
import { TOP_MODEL_LIMITS } from "./EntityUsage/TopModelView";
|
||||
import TopKeyView, { type TopKeyItem } from "@/components/UsagePage/components/EntityUsage/TopKeyView";
|
||||
import { getGlobalTopKeys } from "./EntityUsage/entityUsageAggregations";
|
||||
import UsageAIChatPanel from "./UsageAIChatPanel";
|
||||
import { UsageOption, UsageViewSelect } from "./UsageViewSelect/UsageViewSelect";
|
||||
|
||||
|
|
@ -422,54 +422,10 @@ const UsagePage: React.FC<UsagePageProps> = ({ teams, organizations }) => {
|
|||
}, [userSpendData.results]);
|
||||
|
||||
// Calculate top API keys from the breakdown data
|
||||
const topKeys = useMemo<TopKeyItem[]>(() => {
|
||||
const keySpend: { [key: string]: KeyMetricWithMetadata } = {};
|
||||
userSpendData.results.forEach((day) => {
|
||||
Object.entries(day.breakdown.api_keys || {}).forEach(([key, metrics]) => {
|
||||
if (!keySpend[key]) {
|
||||
keySpend[key] = {
|
||||
metrics: {
|
||||
spend: 0,
|
||||
prompt_tokens: 0,
|
||||
completion_tokens: 0,
|
||||
total_tokens: 0,
|
||||
api_requests: 0,
|
||||
successful_requests: 0,
|
||||
failed_requests: 0,
|
||||
cache_read_input_tokens: 0,
|
||||
cache_creation_input_tokens: 0,
|
||||
},
|
||||
metadata: {
|
||||
key_alias: metrics.metadata.key_alias,
|
||||
team_id: null,
|
||||
user_email: metrics.metadata.user_email,
|
||||
tags: metrics.metadata.tags || [],
|
||||
},
|
||||
};
|
||||
}
|
||||
keySpend[key].metrics.spend += metrics.metrics.spend;
|
||||
keySpend[key].metrics.prompt_tokens += metrics.metrics.prompt_tokens;
|
||||
keySpend[key].metrics.completion_tokens += metrics.metrics.completion_tokens;
|
||||
keySpend[key].metrics.total_tokens += metrics.metrics.total_tokens;
|
||||
keySpend[key].metrics.api_requests += metrics.metrics.api_requests;
|
||||
keySpend[key].metrics.successful_requests += metrics.metrics.successful_requests;
|
||||
keySpend[key].metrics.failed_requests += metrics.metrics.failed_requests;
|
||||
keySpend[key].metrics.cache_read_input_tokens += metrics.metrics.cache_read_input_tokens || 0;
|
||||
keySpend[key].metrics.cache_creation_input_tokens += metrics.metrics.cache_creation_input_tokens || 0;
|
||||
});
|
||||
});
|
||||
|
||||
return Object.entries(keySpend)
|
||||
.map(([api_key, metrics]) => ({
|
||||
api_key,
|
||||
key_alias: keyActivityLabel(metrics.metadata),
|
||||
user_email: metrics.metadata.user_email ?? null,
|
||||
tags: metrics.metadata.tags || [],
|
||||
spend: metrics.metrics.spend,
|
||||
}))
|
||||
.sort((a, b) => b.spend - a.spend)
|
||||
.slice(0, topKeysLimit);
|
||||
}, [userSpendData.results, topKeysLimit]);
|
||||
const topKeys = useMemo<TopKeyItem[]>(
|
||||
() => getGlobalTopKeys(userSpendData.results, topKeysLimit),
|
||||
[userSpendData.results, topKeysLimit],
|
||||
);
|
||||
|
||||
const sortedDailyResults = useMemo(
|
||||
() => [...userSpendData.results].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()),
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-123",
|
||||
key_alias: "Test Key",
|
||||
user_email: null,
|
||||
user: null,
|
||||
spend: 100,
|
||||
tags: [
|
||||
{ tag: "tag-1", usage: 50 },
|
||||
|
|
@ -127,7 +127,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-123",
|
||||
key_alias: "Key without user",
|
||||
user_email: null,
|
||||
user: null,
|
||||
spend: 100,
|
||||
},
|
||||
]}
|
||||
|
|
@ -143,7 +143,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-123",
|
||||
key_alias: "",
|
||||
user_email: "alice@example.com",
|
||||
user: "alice@example.com",
|
||||
spend: 100,
|
||||
},
|
||||
]}
|
||||
|
|
@ -154,6 +154,25 @@ describe("TopKeyView", () => {
|
|||
expect(screen.getByText("alice@example.com")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render a user ID in the User column", () => {
|
||||
render(
|
||||
<TopKeyView
|
||||
{...baseProps}
|
||||
topKeys={[
|
||||
{
|
||||
api_key: "key-123",
|
||||
key_alias: "Key with user ID",
|
||||
user: "user-123",
|
||||
spend: 100,
|
||||
},
|
||||
]}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("User")).toBeInTheDocument();
|
||||
expect(screen.getByText("user-123")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should switch to chart view when chart view button is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<TopKeyView {...baseProps} />);
|
||||
|
|
@ -178,7 +197,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-123",
|
||||
key_alias: "A Very Long Key Alias",
|
||||
user_email: null,
|
||||
user: null,
|
||||
spend: 100,
|
||||
},
|
||||
]}
|
||||
|
|
@ -234,7 +253,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "sk-1234567890abcdef",
|
||||
key_alias: "Test Key",
|
||||
user_email: null,
|
||||
user: null,
|
||||
spend: 100,
|
||||
},
|
||||
]}
|
||||
|
|
@ -253,7 +272,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-123",
|
||||
key_alias: "",
|
||||
user_email: null,
|
||||
user: null,
|
||||
spend: 100,
|
||||
},
|
||||
]}
|
||||
|
|
@ -270,7 +289,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-123",
|
||||
key_alias: "Test Key",
|
||||
user_email: null,
|
||||
user: null,
|
||||
spend: 123.456,
|
||||
},
|
||||
]}
|
||||
|
|
@ -287,7 +306,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-123",
|
||||
key_alias: "Test Key",
|
||||
user_email: null,
|
||||
user: null,
|
||||
spend: 0.004,
|
||||
},
|
||||
]}
|
||||
|
|
@ -304,7 +323,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-123",
|
||||
key_alias: "Test Key",
|
||||
user_email: null,
|
||||
user: null,
|
||||
spend: 0,
|
||||
},
|
||||
]}
|
||||
|
|
@ -322,7 +341,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-123",
|
||||
key_alias: "Test Key",
|
||||
user_email: null,
|
||||
user: null,
|
||||
spend: 100,
|
||||
tags: [],
|
||||
},
|
||||
|
|
@ -341,7 +360,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-123",
|
||||
key_alias: "Test Key",
|
||||
user_email: null,
|
||||
user: null,
|
||||
spend: 100,
|
||||
},
|
||||
]}
|
||||
|
|
@ -359,7 +378,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-123",
|
||||
key_alias: "Test Key",
|
||||
user_email: null,
|
||||
user: null,
|
||||
spend: 100,
|
||||
tags: [
|
||||
{ tag: "tag-1", usage: 50 },
|
||||
|
|
@ -385,7 +404,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-123",
|
||||
key_alias: "Test Key",
|
||||
user_email: null,
|
||||
user: null,
|
||||
spend: 100,
|
||||
tags: [
|
||||
{ tag: "tag-1", usage: 50 },
|
||||
|
|
@ -413,7 +432,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-123",
|
||||
key_alias: "Test Key",
|
||||
user_email: null,
|
||||
user: null,
|
||||
spend: 100,
|
||||
tags: [
|
||||
{ tag: "tag-1", usage: 50 },
|
||||
|
|
@ -451,7 +470,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-123",
|
||||
key_alias: "Test Key",
|
||||
user_email: null,
|
||||
user: null,
|
||||
spend: 100,
|
||||
},
|
||||
]}
|
||||
|
|
@ -486,7 +505,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-123",
|
||||
key_alias: "Test Key",
|
||||
user_email: null,
|
||||
user: null,
|
||||
spend: 100,
|
||||
},
|
||||
]}
|
||||
|
|
@ -524,7 +543,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-123",
|
||||
key_alias: "Test Key",
|
||||
user_email: null,
|
||||
user: null,
|
||||
spend: 100,
|
||||
},
|
||||
]}
|
||||
|
|
@ -561,7 +580,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-123",
|
||||
key_alias: "Test Key",
|
||||
user_email: null,
|
||||
user: null,
|
||||
spend: 100,
|
||||
},
|
||||
]}
|
||||
|
|
@ -601,7 +620,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-123",
|
||||
key_alias: "Test Key",
|
||||
user_email: null,
|
||||
user: null,
|
||||
spend: 100,
|
||||
},
|
||||
]}
|
||||
|
|
@ -632,7 +651,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-123",
|
||||
key_alias: "Test Key",
|
||||
user_email: null,
|
||||
user: null,
|
||||
spend: 100,
|
||||
},
|
||||
]}
|
||||
|
|
@ -663,7 +682,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-123",
|
||||
key_alias: "Test Key",
|
||||
user_email: null,
|
||||
user: null,
|
||||
spend: 100,
|
||||
tags: [
|
||||
{ tag: "tag-low", usage: 10 },
|
||||
|
|
@ -697,7 +716,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-123",
|
||||
key_alias: "This is a very long key alias",
|
||||
user_email: null,
|
||||
user: null,
|
||||
spend: 100,
|
||||
},
|
||||
]}
|
||||
|
|
@ -714,7 +733,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-123",
|
||||
key_alias: null,
|
||||
user_email: null,
|
||||
user: null,
|
||||
spend: 100,
|
||||
},
|
||||
]}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ const TOP_KEYS_LIMITS = [5, 10, 25, 50] as const;
|
|||
export interface TopKeyItem {
|
||||
api_key: string;
|
||||
key_alias: string | null;
|
||||
user_email?: string | null;
|
||||
user?: string | null;
|
||||
tags?: TagUsage[] | null;
|
||||
spend: number;
|
||||
}
|
||||
|
|
@ -103,11 +103,11 @@ const TopKeyView: React.FC<TopKeyViewProps> = ({ topKeys, teams, showTags = fals
|
|||
accessorKey: "key_alias",
|
||||
cell: (info: any) => info.getValue() || "-",
|
||||
},
|
||||
...(topKeys.some((k) => k.user_email)
|
||||
...(topKeys.some((k) => k.user)
|
||||
? [
|
||||
{
|
||||
header: "User",
|
||||
accessorKey: "user_email",
|
||||
accessorKey: "user",
|
||||
cell: (info: any) => info.getValue() || "-",
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-1",
|
||||
key_alias: "Production Key",
|
||||
user_email: null,
|
||||
user: null,
|
||||
tags: [
|
||||
{ tag: "production", usage: 0.005 } as TagUsage, // <$0.01
|
||||
{ tag: "high-volume", usage: 125.5 } as TagUsage, // High spend
|
||||
|
|
@ -47,7 +47,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-2",
|
||||
key_alias: "Staging Key",
|
||||
user_email: null,
|
||||
user: null,
|
||||
tags: [
|
||||
{ tag: "staging", usage: 45.75 } as TagUsage, // Medium spend
|
||||
{ tag: "testing", usage: 0.008 } as TagUsage, // <$0.01
|
||||
|
|
@ -58,7 +58,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-3",
|
||||
key_alias: "Development Key",
|
||||
user_email: null,
|
||||
user: null,
|
||||
tags: [
|
||||
{ tag: "dev", usage: 0.002 } as TagUsage, // <$0.01
|
||||
{ tag: "experimental", usage: 0.001 } as TagUsage, // <$0.01
|
||||
|
|
@ -190,7 +190,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-no-tags",
|
||||
key_alias: "No Tags Key",
|
||||
user_email: null,
|
||||
user: null,
|
||||
tags: [],
|
||||
spend: 10.0,
|
||||
},
|
||||
|
|
@ -205,7 +205,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-undefined-tags",
|
||||
key_alias: "Undefined Tags Key",
|
||||
user_email: null,
|
||||
user: null,
|
||||
tags: undefined,
|
||||
spend: 5.0,
|
||||
},
|
||||
|
|
@ -220,7 +220,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-null-tags",
|
||||
key_alias: "Null Tags Key",
|
||||
user_email: null,
|
||||
user: null,
|
||||
tags: null,
|
||||
spend: 3.0,
|
||||
},
|
||||
|
|
@ -237,7 +237,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-long-tags",
|
||||
key_alias: "Long Tags Key",
|
||||
user_email: null,
|
||||
user: null,
|
||||
tags: [{ tag: "very-long-tag-name", usage: 10.0 } as TagUsage, { tag: "short", usage: 5.0 } as TagUsage],
|
||||
spend: 15.0,
|
||||
},
|
||||
|
|
@ -258,14 +258,14 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "key-mixed-1",
|
||||
key_alias: "Mixed Key 1",
|
||||
user_email: null,
|
||||
user: null,
|
||||
tags: [{ tag: "expensive", usage: 999.99 } as TagUsage, { tag: "cheap", usage: 0.001 } as TagUsage],
|
||||
spend: 1000.0,
|
||||
},
|
||||
{
|
||||
api_key: "key-mixed-2",
|
||||
key_alias: "Mixed Key 2",
|
||||
user_email: null,
|
||||
user: null,
|
||||
tags: [{ tag: "moderate", usage: 50.0 } as TagUsage, { tag: "tiny", usage: 0.005 } as TagUsage],
|
||||
spend: 50.01,
|
||||
},
|
||||
|
|
@ -307,7 +307,7 @@ describe("TopKeyView", () => {
|
|||
{
|
||||
api_key: "test-key-123",
|
||||
key_alias: "Test Key",
|
||||
user_email: null,
|
||||
user: null,
|
||||
tags: [],
|
||||
spend: 25.5,
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue