mirror of
https://github.com/tiennm99/litellm.git
synced 2026-07-15 06:19:29 +00:00
fix(ui): use entity key for export instead of extracting team_id from api_key_breakdown
The export utility always extracted team_id from api_key_breakdown metadata to populate the entity label/ID columns. This worked for team exports (where entity key = team_id) but was wrong for every other entity type — tags, orgs, customers, agents, users all showed the API key's team name (e.g. "admins") instead of the actual entity value. Replace extractTeamIdFromApiKeyBreakdown with resolveEntityDisplay which uses the entity key directly. For teams the teamAliasMap still resolves a human-readable alias; for all other types the entity key itself is the correct label.
This commit is contained in:
@@ -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<string, any> | 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<string, string>,
|
||||
): { 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<string, string> = {},
|
||||
): 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,
|
||||
|
||||
Reference in New Issue
Block a user