fix(ui): key per-user export buckets on a collision-free tuple

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
yuneng 2026-09-22 00:25:13 +00:00
parent a2163a2831
commit 86a019128e
2 changed files with 46 additions and 1 deletions

View file

@ -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");

View file

@ -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,