feat(ui): surface team budget on key overview when key has no own budget (#30801)

* feat(ui): surface team budget on key overview when key has no own budget

* fix(ui): replace IIFE with derived variable and use find() for team budget display
This commit is contained in:
Carsten Boloz 2026-06-24 06:53:34 -04:00 • committed by GitHub
parent d7205918b5
commit 6bffdebd1d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 95 additions and 11 deletions

View file

@ -467,10 +467,15 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
enableSorting: true,
cell: (info) => {
const maxBudget = info.getValue() as number | null;
if (maxBudget === null) {
return "Unlimited";
if (maxBudget !== null) {
return `$${formatNumberWithCommas(maxBudget)}`;
}
return `$${formatNumberWithCommas(maxBudget)}`;
const teamId = info.row.original.team_id;
const team = teams?.find((t) => t.team_id === teamId);
if (team?.max_budget != null) {
return `$${formatNumberWithCommas(team.max_budget)} (Team)`;
}
return "Unlimited";
},
},
{

View file

@ -1,7 +1,7 @@
import { renderWithProviders } from "../../../tests/test-utils";
import { screen, waitFor } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { KeyResponse } from "../key_team_helpers/key_list";
import { KeyResponse, Team } from "../key_team_helpers/key_list";
import KeyInfoView from "./key_info_view";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import useTeams from "@/app/(dashboard)/hooks/useTeams";
@ -103,8 +103,26 @@ const baseAuthorized = {
userEmail: null,
disabledPersonalKeyCreation: null,
showSSOBanner: false,
isLoading: false,
isAuthorized: true,
};
const makeTeam = (overrides: Partial<Team>): Team => ({
team_id: "team-default",
team_alias: "Default Team",
models: [],
max_budget: null,
budget_duration: null,
tpm_limit: null,
rpm_limit: null,
organization_id: "",
created_at: "2026-01-01T00:00:00Z",
keys: [],
members_with_roles: [],
spend: 0,
...overrides,
});
describe("KeyInfoView overview budget display (LIT-2845)", () => {
beforeEach(() => {
vi.mocked(useTeams).mockReturnValue({ teams: [], setTeams: vi.fn() });
@ -151,7 +169,64 @@ describe("KeyInfoView overview budget display (LIT-2845)", () => {
it("renders 'Unlimited' when max_budget is null", async () => {
renderWithProviders(
<KeyInfoView
keyData={{ ...MOCK_KEY_DATA, max_budget: null }}
keyData={{ ...MOCK_KEY_DATA, max_budget: null } as unknown as KeyResponse}
onClose={() => {}}
keyId={"test-key-id"}
onKeyDataUpdate={() => {}}
teams={[]}
/>,
);
await waitFor(() => {
expect(screen.getByText(/of Unlimited/)).toBeInTheDocument();
});
});
it("renders team budget with alias and duration when key has no own budget but team has one", async () => {
vi.mocked(useTeams).mockReturnValue({
teams: [makeTeam({ team_id: "team-123", team_alias: "Test Budget", max_budget: 1200, budget_duration: "30d" })],
setTeams: vi.fn(),
});
renderWithProviders(
<KeyInfoView
keyData={{ ...MOCK_KEY_DATA, max_budget: null, team_id: "team-123" } as unknown as KeyResponse}
onClose={() => {}}
keyId={"test-key-id"}
onKeyDataUpdate={() => {}}
teams={[]}
/>,
);
await waitFor(() => {
expect(screen.getByText(/of \$1,200\.00 \(Team: Test Budget \/ 30d\)/)).toBeInTheDocument();
});
});
it("renders team budget without duration when team has no budget_duration", async () => {
vi.mocked(useTeams).mockReturnValue({
teams: [makeTeam({ team_id: "team-456", team_alias: "No Duration Team", max_budget: 500 })],
setTeams: vi.fn(),
});
renderWithProviders(
<KeyInfoView
keyData={{ ...MOCK_KEY_DATA, max_budget: null, team_id: "team-456" } as unknown as KeyResponse}
onClose={() => {}}
keyId={"test-key-id"}
onKeyDataUpdate={() => {}}
teams={[]}
/>,
);
await waitFor(() => {
expect(screen.getByText(/of \$500\.00 \(Team: No Duration Team\)/)).toBeInTheDocument();
});
});
it("renders 'Unlimited' when key has no budget and team also has no budget", async () => {
vi.mocked(useTeams).mockReturnValue({
teams: [makeTeam({ team_id: "team-789", team_alias: "Free Team" })],
setTeams: vi.fn(),
});
renderWithProviders(
<KeyInfoView
keyData={{ ...MOCK_KEY_DATA, max_budget: null, team_id: "team-789" } as unknown as KeyResponse}
onClose={() => {}}
keyId={"test-key-id"}
onKeyDataUpdate={() => {}}

View file

@ -411,6 +411,15 @@ export default function KeyInfoView({
});
};
const parentTeam = currentKeyData.team_id ? teamsData?.find((team) => team.team_id === currentKeyData.team_id) : null;
const budgetDisplay =
currentKeyData.max_budget !== null
? `$${formatNumberWithCommas(currentKeyData.max_budget, 2)}`
: parentTeam?.max_budget != null
? `$${formatNumberWithCommas(parentTeam.max_budget, 2)} (Team: ${parentTeam.team_alias || parentTeam.team_id}${parentTeam.budget_duration ? ` / ${parentTeam.budget_duration}` : ""})`
: "Unlimited";
return (
<div className="w-full h-full overflow-y-auto p-4">
<KeyInfoHeader
@ -520,12 +529,7 @@ export default function KeyInfoView({
<Text>Spend</Text>
<div className="mt-2">
<Title>${formatNumberWithCommas(currentKeyData.spend, 4)}</Title>
<Text>
of{" "}
{currentKeyData.max_budget !== null
? `$${formatNumberWithCommas(currentKeyData.max_budget, 2)}`
: "Unlimited"}
</Text>
<Text>of {budgetDisplay}</Text>
</div>
</Card>