diff --git a/ui/litellm-dashboard/src/components/EntityUsageExport/utils.test.ts b/ui/litellm-dashboard/src/components/EntityUsageExport/utils.test.ts index 26b2b3c7895..b8f36d57c85 100644 --- a/ui/litellm-dashboard/src/components/EntityUsageExport/utils.test.ts +++ b/ui/litellm-dashboard/src/components/EntityUsageExport/utils.test.ts @@ -2883,6 +2883,51 @@ describe("EntityUsageExport utils", () => { ); }); + it("should keep owners separate when entity and user ids contain underscores", () => { + const collisionFixture: EntitySpendData = { + results: [ + { + date: "2025-03-01", + breakdown: { + entities: { + team_1: { + metrics: { spend: 1, api_requests: 1, total_tokens: 10 }, + api_key_breakdown: { + kX: { + metrics: { spend: 1, api_requests: 1, total_tokens: 10 }, + metadata: { team_id: "team_1", user_id: "u1" }, + }, + }, + }, + team: { + metrics: { spend: 2, api_requests: 2, total_tokens: 20 }, + api_key_breakdown: { + kY: { + metrics: { spend: 2, api_requests: 2, total_tokens: 20 }, + metadata: { team_id: "team", user_id: "1_u1" }, + }, + }, + }, + }, + }, + }, + ], + metadata: usersFixture.metadata, + }; + + const rows = generateDailyWithUsersData(collisionFixture, "Team"); + + expect(rows).toHaveLength(2); + const team1Row = rows.find((r) => r["Team ID"] === "team_1"); + expect(team1Row?.["User ID"]).toBe("u1"); + expect(team1Row?.Keys).toBe(1); + expect(team1Row?.["Spend ($)"]).toBe("1.0000"); + const teamRow = rows.find((r) => r["Team ID"] === "team"); + expect(teamRow?.["User ID"]).toBe("1_u1"); + expect(teamRow?.Keys).toBe(1); + expect(teamRow?.["Spend ($)"]).toBe("2.0000"); + }); + it("should leave daily and daily_with_models output without user columns", () => { const daily = generateDailyData(usersFixture, "Team"); expect(daily[0]).not.toHaveProperty("User ID"); diff --git a/ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts b/ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts index 5cb00f69158..95ce584cc89 100644 --- a/ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts +++ b/ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts @@ -277,7 +277,7 @@ export const generateDailyWithUsersData = ( const { id: entityId, alias: entityAlias } = resolveEntityDisplay(entity, teamAliasMap, data.metadata); Object.entries(data.api_key_breakdown || {}).forEach(([keyId, keyData]: [string, any]) => { const userId = keyData?.metadata?.user_id || "Unassigned"; - const uniqueKey = `${day.date}_${entityId}_${userId}`; + const uniqueKey = JSON.stringify([day.date, entityId, userId]); if (!aggregatedData[uniqueKey]) { aggregatedData[uniqueKey] = { Date: day.date,