fix(ui): include cache token columns in usage export (#32015)

This commit is contained in:
Mateo Wang 2026-07-02 20:04:07 -07:00 • committed by GitHub
parent cf6fdac304
commit 2633e8f8a8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 210 additions and 0 deletions

View file

@ -358,6 +358,23 @@ describe("EntityUsageExport utils", () => {
expect(result[0]).toHaveProperty("Total Tokens");
expect(result[0]).toHaveProperty("Prompt Tokens");
expect(result[0]).toHaveProperty("Completion Tokens");
expect(result[0]).toHaveProperty("Cache Read Input Tokens");
expect(result[0]).toHaveProperty("Cache Creation Input Tokens");
});
it("should export exact cache token values per entity per day", () => {
const result = generateDailyData(mockSpendData, "Team", mockTeamAliasMap);
const day1Team1 = result.find((r) => r.Date === "2025-01-01" && r["Team ID"] === "team-1");
const day1Team2 = result.find((r) => r.Date === "2025-01-01" && r["Team ID"] === "team-2");
const day2Team1 = result.find((r) => r.Date === "2025-01-02" && r["Team ID"] === "team-1");
expect(day1Team1?.["Cache Read Input Tokens"]).toBe(50);
expect(day1Team1?.["Cache Creation Input Tokens"]).toBe(30);
expect(day1Team2?.["Cache Read Input Tokens"]).toBe(100);
expect(day1Team2?.["Cache Creation Input Tokens"]).toBe(60);
expect(day2Team1?.["Cache Read Input Tokens"]).toBe(75);
expect(day2Team1?.["Cache Creation Input Tokens"]).toBe(45);
});
it("should sort data by date ascending", () => {
@ -469,6 +486,8 @@ describe("EntityUsageExport utils", () => {
expect(result[0]["Prompt Tokens"]).toBe(0);
expect(result[0]["Completion Tokens"]).toBe(0);
expect(result[0]["Cache Read Input Tokens"]).toBe(0);
expect(result[0]["Cache Creation Input Tokens"]).toBe(0);
});
});
@ -614,6 +633,61 @@ describe("EntityUsageExport utils", () => {
expect(result[0]).toHaveProperty("Total Tokens");
expect(result[0]).toHaveProperty("Prompt Tokens");
expect(result[0]).toHaveProperty("Completion Tokens");
expect(result[0]).toHaveProperty("Cache Read Input Tokens");
expect(result[0]).toHaveProperty("Cache Creation Input Tokens");
});
it("should export and aggregate cache token values per key", () => {
const makeDay = (cacheRead: number, cacheCreation: number) => ({
date: "2025-01-01",
breakdown: {
entities: {
"team-1": {
metrics: {
spend: 5.0,
api_requests: 50,
successful_requests: 50,
failed_requests: 0,
total_tokens: 500,
prompt_tokens: 300,
completion_tokens: 200,
cache_read_input_tokens: cacheRead,
cache_creation_input_tokens: cacheCreation,
},
api_key_breakdown: {
key1: {
metrics: {
spend: 5.0,
api_requests: 50,
successful_requests: 50,
failed_requests: 0,
total_tokens: 500,
prompt_tokens: 300,
completion_tokens: 200,
cache_read_input_tokens: cacheRead,
cache_creation_input_tokens: cacheCreation,
},
metadata: {
team_id: "team-1",
key_alias: "alias-1",
},
},
},
},
},
},
});
const spendDataWithCache: EntitySpendData = {
results: [makeDay(40, 25), makeDay(10, 5)],
metadata: mockSpendDataWithKeys.metadata,
};
const result = generateDailyWithKeysData(spendDataWithCache, "Team");
expect(result).toHaveLength(1);
expect(result[0]["Cache Read Input Tokens"]).toBe(50);
expect(result[0]["Cache Creation Input Tokens"]).toBe(30);
});
it("should sort data by date ascending", () => {
@ -935,6 +1009,8 @@ describe("EntityUsageExport utils", () => {
expect(key1Entry?.["Prompt Tokens"]).toBe(0);
expect(key1Entry?.["Completion Tokens"]).toBe(0);
expect(key1Entry?.["Cache Read Input Tokens"]).toBe(0);
expect(key1Entry?.["Cache Creation Input Tokens"]).toBe(0);
});
it("should handle empty api_key_breakdown", () => {
@ -1096,6 +1172,117 @@ describe("EntityUsageExport utils", () => {
expect(result[0]).toHaveProperty("Successful");
expect(result[0]).toHaveProperty("Failed");
expect(result[0]).toHaveProperty("Total Tokens");
expect(result[0]).toHaveProperty("Prompt Tokens");
expect(result[0]).toHaveProperty("Completion Tokens");
expect(result[0]).toHaveProperty("Cache Read Input Tokens");
expect(result[0]).toHaveProperty("Cache Creation Input Tokens");
});
it("should export prompt, completion, and cache token values summed across keys for the same model", () => {
const data: EntitySpendData = {
results: [
{
date: "2025-03-01",
breakdown: {
entities: {
"team-1": {
metrics: {
spend: 5.0,
api_requests: 25,
successful_requests: 25,
failed_requests: 0,
total_tokens: 1050,
prompt_tokens: 750,
completion_tokens: 300,
cache_read_input_tokens: 450,
cache_creation_input_tokens: 200,
},
api_key_breakdown: {
key1: {
metrics: {
spend: 2.0,
api_requests: 10,
successful_requests: 10,
failed_requests: 0,
total_tokens: 700,
},
metadata: { team_id: "team-1" },
},
key2: {
metrics: {
spend: 3.0,
api_requests: 15,
successful_requests: 15,
failed_requests: 0,
total_tokens: 350,
},
metadata: { team_id: "team-1" },
},
},
},
},
models: {
"claude-sonnet-4-5": {
metrics: {
spend: 5.0,
api_requests: 25,
successful_requests: 25,
failed_requests: 0,
total_tokens: 1050,
},
api_key_breakdown: {
key1: {
metrics: {
spend: 2.0,
api_requests: 10,
successful_requests: 10,
failed_requests: 0,
total_tokens: 700,
prompt_tokens: 500,
completion_tokens: 200,
cache_read_input_tokens: 300,
cache_creation_input_tokens: 120,
},
metadata: { team_id: "team-1" },
},
key2: {
metrics: {
spend: 3.0,
api_requests: 15,
successful_requests: 15,
failed_requests: 0,
total_tokens: 350,
prompt_tokens: 250,
completion_tokens: 100,
cache_read_input_tokens: 150,
cache_creation_input_tokens: 80,
},
metadata: { team_id: "team-1" },
},
},
},
},
},
},
],
metadata: {
total_spend: 5.0,
total_api_requests: 25,
total_successful_requests: 25,
total_failed_requests: 0,
total_tokens: 1050,
},
};
const result = generateDailyWithModelsData(data, "Team");
expect(result).toHaveLength(1);
expect(result[0].Model).toBe("claude-sonnet-4-5");
expect(result[0]["Total Tokens"]).toBe(1050);
expect(result[0]["Prompt Tokens"]).toBe(750);
expect(result[0]["Completion Tokens"]).toBe(300);
expect(result[0]["Cache Read Input Tokens"]).toBe(450);
expect(result[0]["Cache Creation Input Tokens"]).toBe(200);
});
it("should sort data by date ascending", () => {

View file

@ -126,6 +126,8 @@ export const generateDailyData = (
"Total Tokens": data.metrics.total_tokens,
"Prompt Tokens": data.metrics.prompt_tokens || 0,
"Completion Tokens": data.metrics.completion_tokens || 0,
"Cache Read Input Tokens": data.metrics.cache_read_input_tokens || 0,
"Cache Creation Input Tokens": data.metrics.cache_creation_input_tokens || 0,
});
});
});
@ -154,6 +156,8 @@ export const generateDailyWithKeysData = (
total_tokens: number;
prompt_tokens: number;
completion_tokens: number;
cache_read_input_tokens: number;
cache_creation_input_tokens: number;
};
};
} = {};
@ -186,6 +190,8 @@ export const generateDailyWithKeysData = (
total_tokens: keyData.metrics?.total_tokens || 0,
prompt_tokens: keyData.metrics?.prompt_tokens || 0,
completion_tokens: keyData.metrics?.completion_tokens || 0,
cache_read_input_tokens: keyData.metrics?.cache_read_input_tokens || 0,
cache_creation_input_tokens: keyData.metrics?.cache_creation_input_tokens || 0,
},
};
} else {
@ -197,6 +203,9 @@ export const generateDailyWithKeysData = (
aggregatedData[uniqueKey].metrics.total_tokens += keyData.metrics?.total_tokens || 0;
aggregatedData[uniqueKey].metrics.prompt_tokens += keyData.metrics?.prompt_tokens || 0;
aggregatedData[uniqueKey].metrics.completion_tokens += keyData.metrics?.completion_tokens || 0;
aggregatedData[uniqueKey].metrics.cache_read_input_tokens += keyData.metrics?.cache_read_input_tokens || 0;
aggregatedData[uniqueKey].metrics.cache_creation_input_tokens +=
keyData.metrics?.cache_creation_input_tokens || 0;
}
});
});
@ -216,6 +225,8 @@ export const generateDailyWithKeysData = (
"Total Tokens": item.metrics.total_tokens,
"Prompt Tokens": item.metrics.prompt_tokens,
"Completion Tokens": item.metrics.completion_tokens,
"Cache Read Input Tokens": item.metrics.cache_read_input_tokens,
"Cache Creation Input Tokens": item.metrics.cache_creation_input_tokens,
}));
return dailyKeyBreakdown.sort((a, b) => new Date(a.Date).getTime() - new Date(b.Date).getTime());
@ -251,6 +262,10 @@ export const generateDailyWithModelsData = (
successful: 0,
failed: 0,
tokens: 0,
promptTokens: 0,
completionTokens: 0,
cacheReadInputTokens: 0,
cacheCreationInputTokens: 0,
};
}
dailyEntityModels[entity][model].spend += keyMetrics.spend || 0;
@ -258,6 +273,10 @@ export const generateDailyWithModelsData = (
dailyEntityModels[entity][model].successful += keyMetrics.successful_requests || 0;
dailyEntityModels[entity][model].failed += keyMetrics.failed_requests || 0;
dailyEntityModels[entity][model].tokens += keyMetrics.total_tokens || 0;
dailyEntityModels[entity][model].promptTokens += keyMetrics.prompt_tokens || 0;
dailyEntityModels[entity][model].completionTokens += keyMetrics.completion_tokens || 0;
dailyEntityModels[entity][model].cacheReadInputTokens += keyMetrics.cache_read_input_tokens || 0;
dailyEntityModels[entity][model].cacheCreationInputTokens += keyMetrics.cache_creation_input_tokens || 0;
});
});
});
@ -276,6 +295,10 @@ export const generateDailyWithModelsData = (
Successful: metrics.successful,
Failed: metrics.failed,
"Total Tokens": metrics.tokens,
"Prompt Tokens": metrics.promptTokens,
"Completion Tokens": metrics.completionTokens,
"Cache Read Input Tokens": metrics.cacheReadInputTokens,
"Cache Creation Input Tokens": metrics.cacheCreationInputTokens,
});
});
});