fix(ui): populate Customer/Customer ID in CSV export from end_user field

When exporting Customer Usage as CSV or JSON, the Customer and Customer ID
columns were blank because generateDailyData and generateDailyWithModelsData
looked for a team_id in API key metadata but found none for customer entity
types, with no fallback. The end_user value is the entity key itself, so
falling back to it (matching the existing behaviour in getEntityBreakdown)
fixes the export.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Julio Quinteros 2026-03-04 11:15:48 -03:00
parent 92407ec0d4
commit 0834c44457
2 changed files with 111 additions and 6 deletions

View file

@ -378,7 +378,7 @@ describe("EntityUsageExport utils", () => {
}
});
it("should use dash when team id is not available", () => {
it("should fall back to entity key when team id is not available in metadata", () => {
const spendDataWithoutTeamId: EntitySpendData = {
...mockSpendData,
results: [
@ -408,10 +408,55 @@ describe("EntityUsageExport utils", () => {
const result = generateDailyData(spendDataWithoutTeamId, "Team");
const entry = result[0];
expect(entry["Team ID"]).toBe("-");
expect(entry["Team ID"]).toBe("entity1");
expect(entry["Team"]).toBe("-");
});
it("should populate Customer ID from end_user when no team_id in metadata", () => {
const customerSpendData: EntitySpendData = {
...mockSpendData,
results: [
{
date: "2025-01-01",
breakdown: {
entities: {
"37": {
metrics: {
spend: 0.2735,
api_requests: 252,
successful_requests: 252,
failed_requests: 0,
total_tokens: 414370,
prompt_tokens: 300000,
completion_tokens: 114370,
},
api_key_breakdown: {
"sk-key123": {
metrics: {
spend: 0.2735,
api_requests: 252,
successful_requests: 252,
failed_requests: 0,
total_tokens: 414370,
},
metadata: {},
},
},
},
},
},
},
],
metadata: mockSpendData.metadata,
};
const result = generateDailyData(customerSpendData, "Customer");
const entry = result[0];
expect(entry["Customer ID"]).toBe("37");
expect(entry["Customer"]).toBe("-");
});
it("should format spend values correctly", () => {
const result = generateDailyData(mockSpendData, "Team");
@ -1149,6 +1194,64 @@ describe("EntityUsageExport utils", () => {
}
});
it("should populate Customer ID from end_user when no team_id in metadata", () => {
const customerSpendDataWithModels: EntitySpendData = {
...mockSpendDataWithModels,
results: [
{
date: "2025-01-01",
breakdown: {
entities: {
"37": {
metrics: {
spend: 0.2735,
api_requests: 252,
successful_requests: 252,
failed_requests: 0,
total_tokens: 414370,
prompt_tokens: 300000,
completion_tokens: 114370,
cache_read_input_tokens: 0,
cache_creation_input_tokens: 0,
},
api_key_breakdown: {
"sk-key123": {
metrics: {
spend: 0.2735,
api_requests: 252,
successful_requests: 252,
failed_requests: 0,
total_tokens: 414370,
},
metadata: {},
},
},
},
},
models: {
"vertex_ai/gemini-3-flash-preview": {
metrics: {
spend: 0.2735,
api_requests: 252,
successful_requests: 252,
failed_requests: 0,
total_tokens: 414370,
},
},
},
},
},
],
metadata: mockSpendDataWithModels.metadata,
};
const result = generateDailyWithModelsData(customerSpendDataWithModels, "Customer");
const entry = result[0];
expect(entry["Customer ID"]).toBe("37");
expect(entry["Customer"]).toBe("-");
});
it("should handle empty models breakdown", () => {
const spendDataWithoutModels: EntitySpendData = {
...mockSpendDataWithModels,

View file

@ -82,8 +82,9 @@ export const generateDailyData = (
spendData.results.forEach((day) => {
Object.entries(day.breakdown.entities || {}).forEach(([entity, data]: [string, any]) => {
// Extract team_id from api_key_breakdown metadata (not data.metadata which is empty)
const teamId = extractTeamIdFromApiKeyBreakdown(data.api_key_breakdown);
const teamAlias = teamId ? teamAliasMap[teamId] || null : null;
// Fall back to entity key itself, which for customer entity type is the end_user value
const teamId = extractTeamIdFromApiKeyBreakdown(data.api_key_breakdown) || entity;
const teamAlias = teamAliasMap[teamId] || null;
dailyBreakdown.push({
Date: day.date,
@ -232,8 +233,9 @@ export const generateDailyWithModelsData = (
Object.entries(dailyEntityModels).forEach(([entity, models]) => {
const entityData = day.breakdown.entities?.[entity];
// Extract team_id from api_key_breakdown metadata (not entityData.metadata which is empty)
const teamId = extractTeamIdFromApiKeyBreakdown(entityData?.api_key_breakdown);
const teamAlias = teamId ? teamAliasMap[teamId] || null : null;
// Fall back to entity key itself, which for customer entity type is the end_user value
const teamId = extractTeamIdFromApiKeyBreakdown(entityData?.api_key_breakdown) || entity;
const teamAlias = teamAliasMap[teamId] || null;
Object.entries(models).forEach(([model, metrics]: [string, any]) => {
dailyModelBreakdown.push({