fix(usage): label key activity tables with email when alias is missing

Key Activity charts already fell back to user_email. The top-keys tables
and usage export still printed '-' or a truncated hash. They now use the
same alias-then-email label.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
This commit is contained in:
Cursor Agent 2026-09-03 14:58:05 +00:00
parent e76a18ca32
commit 6f4ea2d296
No known key found for this signature in database
6 changed files with 34 additions and 5 deletions

View file

@ -1,3 +1,4 @@
import { keyActivityLabel } from "@/components/UsagePage/keyActivityLabel";
import { BreakdownMetrics, DailyData, KeyMetricWithMetadata, TagUsage } from "@/components/UsagePage/types";
export type ExtendedDailyData = DailyData & {
@ -118,6 +119,7 @@ export const getTopAPIKeys = (results: ExtendedDailyData[], topKeysLimit: number
metadata: {
key_alias: metrics.metadata.key_alias,
team_id: metrics.metadata.team_id || null,
user_email: metrics.metadata.user_email,
tags: tagDictionary[key] || [],
},
};
@ -137,7 +139,7 @@ export const getTopAPIKeys = (results: ExtendedDailyData[], topKeysLimit: number
return Object.entries(keySpend)
.map(([api_key, metrics]) => ({
api_key,
key_alias: metrics.metadata.key_alias || "-", // Using truncated key as alias
key_alias: keyActivityLabel(metrics.metadata),
tags: metrics.metadata.tags || "-",
spend: metrics.metrics.spend,
}))

View file

@ -44,6 +44,7 @@ import { Tag } from "@/components/tag_management/types";
import UserAgentActivity from "@/components/user_agent_activity";
import ViewUserSpend from "@/components/view_user_spend";
import { usePaginatedDailyActivity } from "../hooks/usePaginatedDailyActivity";
import { keyActivityLabel } from "@/components/UsagePage/keyActivityLabel";
import { DailyData, KeyMetricWithMetadata, MetricWithMetadata } from "@/components/UsagePage/types";
import { valueFormatterSpend } from "@/components/UsagePage/utils/value_formatters";
import {
@ -426,6 +427,7 @@ const UsagePage: React.FC<UsagePageProps> = ({ teams, organizations }) => {
metadata: {
key_alias: metrics.metadata.key_alias,
team_id: null,
user_email: metrics.metadata.user_email,
tags: metrics.metadata.tags || [],
},
};
@ -445,7 +447,7 @@ const UsagePage: React.FC<UsagePageProps> = ({ teams, organizations }) => {
return Object.entries(keySpend)
.map(([api_key, metrics]) => ({
api_key,
key_alias: metrics.metadata.key_alias || "-",
key_alias: keyActivityLabel(metrics.metadata),
tags: metrics.metadata.tags || [],
spend: metrics.metrics.spend,
}))

View file

@ -1,6 +1,7 @@
import { formatNumberWithCommas } from "@/utils/dataUtils";
import type { DateRangePickerValue } from "@/components/shared/date_picker_types";
import Papa from "papaparse";
import { keyActivityLabel } from "@/components/UsagePage/keyActivityLabel";
import type { EntityBreakdown, EntitySpendData, EntityType, ExportMetadata, ExportScope } from "./types";
const resolveEntityDisplay = (
@ -186,7 +187,7 @@ export const generateDailyWithKeysData = (
// Iterate through each API key in the breakdown
Object.entries(apiKeyBreakdown).forEach(([keyId, keyData]: [string, any]) => {
const keyAlias = keyData?.metadata?.key_alias || null;
const keyAlias = keyActivityLabel(keyData?.metadata, "") || null;
// Create unique key for aggregation: Date_EntityID_KeyID
const uniqueKey = `${day.date}_${entityId}_${keyId}`;

View file

@ -0,0 +1,15 @@
import { keyActivityLabel } from "./keyActivityLabel";
describe("keyActivityLabel", () => {
it("prefers key_alias", () => {
expect(keyActivityLabel({ key_alias: "batch-worker", user_email: "alice@example.com" })).toBe("batch-worker");
});
it("falls back to user_email when alias is missing", () => {
expect(keyActivityLabel({ key_alias: null, user_email: "alice@example.com" })).toBe("alice@example.com");
});
it("uses the fallback when both alias and email are missing", () => {
expect(keyActivityLabel({ key_alias: null, user_email: null }, "key-hash-abc")).toBe("key-hash-abc");
});
});

View file

@ -0,0 +1,8 @@
import type { KeyMetadata } from "./types";
export function keyActivityLabel(
metadata: Pick<KeyMetadata, "key_alias" | "user_email"> | null | undefined,
fallback = "-",
): string {
return metadata?.key_alias || metadata?.user_email || fallback;
}

View file

@ -7,6 +7,7 @@ import { ChevronDown } from "lucide-react";
import React, { useState } from "react";
import { Team } from "./key_team_helpers/key_list";
import KeyModelUsageView from "./UsagePage/components/KeyModelUsageView";
import { keyActivityLabel } from "./UsagePage/keyActivityLabel";
import { DailyData, KeyMetricWithMetadata, ModelActivityData, TopApiKeyData, TopModelData } from "./UsagePage/types";
import { valueFormatter } from "./UsagePage/utils/value_formatters";
@ -433,7 +434,7 @@ export const ActivityMetrics: React.FC<ActivityMetricsProps> = ({ modelMetrics,
// Helper function to format key label
export const formatKeyLabel = (modelData: KeyMetricWithMetadata, model: string, teams: Team[]): string => {
const keyAlias = modelData.metadata.key_alias || modelData.metadata.user_email || `key-hash-${model}`;
const keyAlias = keyActivityLabel(modelData.metadata, `key-hash-${model}`);
const teamId = modelData.metadata.team_id;
if (teamId) {
const teamAlias = resolveTeamAliasFromTeamID(teamId, teams);
@ -516,7 +517,7 @@ export const processActivityData = (
if (!apiKeyBreakdown[apiKey]) {
apiKeyBreakdown[apiKey] = {
api_key: apiKey,
key_alias: keyData.metadata.key_alias,
key_alias: keyActivityLabel(keyData.metadata, "") || null,
team_id: keyData.metadata.team_id,
spend: 0,
requests: 0,