mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-14 23:21:35 +00:00
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:
parent
92407ec0d4
commit
0834c44457
2 changed files with 111 additions and 6 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue