diff --git a/ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts b/ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts index 45bf21a6e7..6013e27648 100644 --- a/ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts +++ b/ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts @@ -3,19 +3,16 @@ import type { DateRangePickerValue } from "@tremor/react"; import Papa from "papaparse"; import type { EntityBreakdown, EntitySpendData, EntityType, ExportMetadata, ExportScope } from "./types"; -// Helper function to extract team_id from api_key_breakdown -const extractTeamIdFromApiKeyBreakdown = (apiKeyBreakdown: Record | undefined): string | null => { - if (!apiKeyBreakdown) return null; - - // Look through all API keys to find the first non-null team_id - for (const apiKeyData of Object.values(apiKeyBreakdown)) { - const teamId = (apiKeyData as any)?.metadata?.team_id; - if (teamId) { - return teamId; - } - } - return null; -}; +// Resolve display name for an entity. For teams the teamAliasMap provides +// a human-readable alias; for every other entity type the entity key itself +// (tag name, org id, customer id, …) is already the correct label. +const resolveEntityDisplay = ( + entity: string, + teamAliasMap: Record, +): { id: string; alias: string } => ({ + id: entity, + alias: teamAliasMap[entity] || entity, +}); // Mirrors backend SpendMetrics fields (litellm/types/activity_tracking.py). // If the backend adds a field, add it here too. @@ -68,18 +65,7 @@ export const getEntityBreakdown = ( spendData.results.forEach((day) => { Object.entries(resolveEntities(day.breakdown)).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) || entity; - // Extract key_alias from the first API key that has one - const apiKeyBreakdown = data.api_key_breakdown || {}; - let keyAlias: string | null = null; - for (const apiKeyData of Object.values(apiKeyBreakdown)) { - const alias = (apiKeyData as any)?.metadata?.key_alias; - if (alias) { - keyAlias = alias; - break; - } - } + const { id, alias } = resolveEntityDisplay(entity, teamAliasMap); if (!entitySpend[entity]) { entitySpend[entity] = { @@ -95,8 +81,8 @@ export const getEntityBreakdown = ( cache_creation_input_tokens: 0, }, metadata: { - alias: keyAlias || teamAliasMap[teamId] || entity, - id: teamId, + alias, + id, }, }; } @@ -124,14 +110,12 @@ export const generateDailyData = ( spendData.results.forEach((day) => { Object.entries(resolveEntities(day.breakdown)).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; + const { id, alias } = resolveEntityDisplay(entity, teamAliasMap); dailyBreakdown.push({ Date: day.date, - [entityLabel]: teamAlias || "-", - [`${entityLabel} ID`]: teamId || "-", + [entityLabel]: alias, + [`${entityLabel} ID`]: id, "Spend ($)": formatNumberWithCommas(data.metrics.spend, 4), Requests: data.metrics.api_requests, "Successful Requests": data.metrics.successful_requests, @@ -151,12 +135,12 @@ export const generateDailyWithKeysData = ( entityLabel: string, teamAliasMap: Record = {}, ): any[] => { - // Aggregate by unique (Date, Team ID, Key ID) combination to prevent duplicates + // Aggregate by unique (Date, Entity ID, Key ID) combination to prevent duplicates const aggregatedData: { [key: string]: { Date: string; - teamId: string; - teamAlias: string | null; + entityId: string; + entityAlias: string; keyId: string; keyAlias: string | null; metrics: { @@ -173,23 +157,22 @@ export const generateDailyWithKeysData = ( spendData.results.forEach((day) => { Object.entries(resolveEntities(day.breakdown)).forEach(([entity, data]: [string, any]) => { + const { id: entityId, alias: entityAlias } = resolveEntityDisplay(entity, teamAliasMap); const apiKeyBreakdown = data.api_key_breakdown || {}; // Iterate through each API key in the breakdown Object.entries(apiKeyBreakdown).forEach(([keyId, keyData]: [string, any]) => { const keyAlias = keyData?.metadata?.key_alias || null; - const teamId = keyData?.metadata?.team_id || entity; - const teamAlias = teamId ? teamAliasMap[teamId] || null : null; - // Create unique key for aggregation: Date_TeamID_KeyID - const uniqueKey = `${day.date}_${teamId}_${keyId}`; + // Create unique key for aggregation: Date_EntityID_KeyID + const uniqueKey = `${day.date}_${entityId}_${keyId}`; if (!aggregatedData[uniqueKey]) { - // First time seeing this (Date, Team ID, Key ID) combination + // First time seeing this (Date, Entity ID, Key ID) combination aggregatedData[uniqueKey] = { Date: day.date, - teamId, - teamAlias, + entityId, + entityAlias, keyId, keyAlias, metrics: { @@ -219,8 +202,8 @@ export const generateDailyWithKeysData = ( // Convert aggregated data to array format const dailyKeyBreakdown = Object.values(aggregatedData).map((item) => ({ Date: item.Date, - [entityLabel]: item.teamAlias || "-", - [`${entityLabel} ID`]: item.teamId || "-", + [entityLabel]: item.entityAlias, + [`${entityLabel} ID`]: item.entityId, "Key Alias": item.keyAlias || "-", "Key ID": item.keyId, "Spend ($)": formatNumberWithCommas(item.metrics.spend, 4), @@ -273,16 +256,13 @@ export const generateDailyWithModelsData = ( }); Object.entries(dailyEntityModels).forEach(([entity, models]) => { - const entityData = resolveEntities(day.breakdown)[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; + const { id, alias } = resolveEntityDisplay(entity, teamAliasMap); Object.entries(models).forEach(([model, metrics]: [string, any]) => { dailyModelBreakdown.push({ Date: day.date, - [entityLabel]: teamAlias || "-", - [`${entityLabel} ID`]: teamId || "-", + [entityLabel]: alias, + [`${entityLabel} ID`]: id, Model: model, "Spend ($)": formatNumberWithCommas(metrics.spend, 4), Requests: metrics.requests,