Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
* The team selector and filtering have been removed so that all keys are shown.
*/

export function VirtualKeysTable({ teams, organizations, onSortChange, currentSort }: VirtualKeysTableProps) {

Check warning on line 55 in ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Function 'VirtualKeysTable' has a complexity of 23. Maximum allowed is 20
const { data: fetchedOrganizations } = useOrganizations();
const resolvedOrganizations = fetchedOrganizations ?? organizations ?? [];
const [selectedKey, setSelectedKey] = useState<KeyResponse | null>(null);
Expand Down Expand Up @@ -467,10 +467,15 @@
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";
},
},
{
Expand Down Expand Up @@ -586,7 +591,7 @@
},
},
],
[teams, resolvedOrganizations],

Check warning on line 594 in ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

React Hook useMemo has a missing dependency: 'expandedAccordions'. Either include it or remove the dependency array
);

const filterOptions: FilterOption[] = [
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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() });
Expand Down Expand Up @@ -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={() => {}}
Expand Down
16 changes: 10 additions & 6 deletions ui/litellm-dashboard/src/components/templates/key_info_view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
keyData: KeyResponse | undefined;
onKeyDataUpdate?: (data: Partial<KeyResponse>) => void;
onDelete?: () => void;
teams: any[] | null;

Check warning on line 33 in ui/litellm-dashboard/src/components/templates/key_info_view.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Unexpected any. Specify a different type
backButtonText?: string;
}

Expand All @@ -55,7 +55,7 @@
* Please contribute to the new refactor.
* ─────────────────────────────────────────────────────────────────────────
*/
export default function KeyInfoView({

Check warning on line 58 in ui/litellm-dashboard/src/components/templates/key_info_view.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Function 'KeyInfoView' has a complexity of 114. Maximum allowed is 20
onClose,
keyData,
teams,
Expand Down Expand Up @@ -147,7 +147,7 @@
);
}

const handleKeyUpdate = async (formValues: Record<string, any>) => {

Check warning on line 150 in ui/litellm-dashboard/src/components/templates/key_info_view.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Async arrow function has a complexity of 49. Maximum allowed is 20

Check warning on line 150 in ui/litellm-dashboard/src/components/templates/key_info_view.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Unexpected any. Specify a different type
try {
if (!accessToken) return;

Expand Down Expand Up @@ -411,6 +411,15 @@
});
};

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
Expand Down Expand Up @@ -520,12 +529,7 @@
<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>

Expand Down
Loading