From 8b4c2da9ea712f38c6ae55af2c80eaacda654836 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 23 Dec 2025 18:16:30 -0800 Subject: [PATCH] Resolve org alias on teams table --- .../src/components/OldTeams.test.tsx | 172 +++++++++++++++++- .../src/components/OldTeams.tsx | 18 +- 2 files changed, 179 insertions(+), 11 deletions(-) diff --git a/ui/litellm-dashboard/src/components/OldTeams.test.tsx b/ui/litellm-dashboard/src/components/OldTeams.test.tsx index f3b4ec82d5..76fc26a884 100644 --- a/ui/litellm-dashboard/src/components/OldTeams.test.tsx +++ b/ui/litellm-dashboard/src/components/OldTeams.test.tsx @@ -1,10 +1,13 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { act, fireEvent, render, screen, waitFor } from "@testing-library/react"; +import React from "react"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { fetchAvailableModelsForTeamOrKey } from "./key_team_helpers/fetch_available_models_team_key"; import { fetchMCPAccessGroups, getGuardrailsList, teamCreateCall } from "./networking"; import OldTeams from "./OldTeams"; const mockTeamInfoView = vi.fn(); +const mockUseOrganizations = vi.fn(); vi.mock("./networking", () => ({ teamCreateCall: vi.fn(), @@ -57,6 +60,25 @@ vi.mock("@/components/team/team_info", () => ({ }, })); +vi.mock("@/app/(dashboard)/hooks/organizations/useOrganizations", () => ({ + useOrganizations: () => mockUseOrganizations(), +})); + +const createQueryClient = () => { + return new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }); +}; + +const renderWithQueryClient = (component: React.ReactElement) => { + const queryClient = createQueryClient(); + return render({component}); +}; + describe("OldTeams - handleCreate organization handling", () => { beforeEach(() => { vi.clearAllMocks(); @@ -64,6 +86,7 @@ describe("OldTeams - handleCreate organization handling", () => { vi.mocked(fetchAvailableModelsForTeamOrKey).mockResolvedValue([]); vi.mocked(fetchMCPAccessGroups).mockResolvedValue([]); vi.mocked(getGuardrailsList).mockResolvedValue({ guardrails: [] }); + mockUseOrganizations.mockReturnValue({ data: null }); }); it("should not include organization_id when it's an empty string", async () => { @@ -274,7 +297,8 @@ describe("OldTeams - handleCreate organization handling", () => { }); it("should clear the delete modal when the cancel button is clicked", async () => { - render( + mockUseOrganizations.mockReturnValue({ data: [] }); + renderWithQueryClient( { describe("OldTeams - empty state", () => { beforeEach(() => { vi.clearAllMocks(); + mockUseOrganizations.mockReturnValue({ data: [] }); }); it("should display empty state message when teams array is empty", () => { - render( + renderWithQueryClient( { }); it("should display empty state message when teams is null", () => { - render( + renderWithQueryClient( { }); it("should not display empty state when teams array has items", () => { - render( + renderWithQueryClient( { vi.mocked(fetchAvailableModelsForTeamOrKey).mockResolvedValue([]); vi.mocked(fetchMCPAccessGroups).mockResolvedValue([]); vi.mocked(getGuardrailsList).mockResolvedValue({ guardrails: [] }); + mockUseOrganizations.mockReturnValue({ data: [] }); }); it("passes premiumUser flag to TeamInfoView", async () => { - render( + renderWithQueryClient( { describe("OldTeams - Default Team Settings tab visibility", () => { beforeEach(() => { vi.clearAllMocks(); + mockUseOrganizations.mockReturnValue({ data: [] }); }); it("should show Default Team Settings tab for Admin role", () => { - render( + renderWithQueryClient( { }); it("should show Default Team Settings tab for proxy_admin role", () => { - render( + renderWithQueryClient( { }); it("should not show Default Team Settings tab for proxy_admin_viewer role", () => { - render( + renderWithQueryClient( { }); it("should not show Default Team Settings tab for Admin Viewer role", () => { - render( + renderWithQueryClient( { beforeEach(() => { vi.clearAllMocks(); vi.mocked(fetchAvailableModelsForTeamOrKey).mockResolvedValue(["gpt-4", "gpt-3.5-turbo"]); + mockUseOrganizations.mockReturnValue({ data: [] }); }); it("should not render all-proxy-models option in models select", async () => { vi.mocked(fetchAvailableModelsForTeamOrKey).mockResolvedValue(["gpt-4", "gpt-3.5-turbo"]); - render( + renderWithQueryClient( { expect(allProxyModelsOption).not.toBeInTheDocument(); }); }); + +describe("OldTeams - organization alias display", () => { + beforeEach(() => { + vi.clearAllMocks(); + mockUseOrganizations.mockReturnValue({ data: [] }); + }); + + it("should display organization alias instead of organization id", () => { + const mockOrganizations = [ + { + organization_id: "org-123", + organization_alias: "Test Organization", + budget_id: "budget-1", + metadata: {}, + models: [], + spend: 0, + model_spend: {}, + created_at: new Date().toISOString(), + created_by: "user-1", + updated_at: new Date().toISOString(), + updated_by: "user-1", + litellm_budget_table: null, + teams: null, + users: null, + members: null, + }, + ]; + + mockUseOrganizations.mockReturnValue({ data: mockOrganizations }); + + renderWithQueryClient( + , + ); + + expect(screen.getByText("Test Organization")).toBeInTheDocument(); + expect(screen.queryByText("org-123")).not.toBeInTheDocument(); + }); + + it("should display organization id when alias is not found", () => { + mockUseOrganizations.mockReturnValue({ data: [] }); + + renderWithQueryClient( + , + ); + + expect(screen.getByText("org-unknown")).toBeInTheDocument(); + }); + + it("should display N/A when organization_id is null", () => { + mockUseOrganizations.mockReturnValue({ data: [] }); + + renderWithQueryClient( + , + ); + + expect(screen.getByText("N/A")).toBeInTheDocument(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/OldTeams.tsx b/ui/litellm-dashboard/src/components/OldTeams.tsx index 562d75c327..77bbdb483d 100644 --- a/ui/litellm-dashboard/src/components/OldTeams.tsx +++ b/ui/litellm-dashboard/src/components/OldTeams.tsx @@ -1,3 +1,4 @@ +import { useOrganizations } from "@/app/(dashboard)/hooks/organizations/useOrganizations"; import AvailableTeamsPanel from "@/components/team/available_teams"; import TeamInfoView from "@/components/team/team_info"; import TeamSSOSettings from "@/components/TeamSSOSettings"; @@ -149,6 +150,18 @@ const getAdminOrganizations = ( return []; }; +const getOrganizationAlias = ( + organizationId: string | null | undefined, + organizations: Organization[] | null | undefined, +): string => { + if (!organizationId || !organizations) { + return organizationId || "N/A"; + } + + const organization = organizations.find((org) => org.organization_id === organizationId); + return organization?.organization_alias || organizationId; +}; + // @deprecated const Teams: React.FC = ({ teams, @@ -161,6 +174,7 @@ const Teams: React.FC = ({ premiumUser = false, }) => { console.log(`organizations: ${JSON.stringify(organizations)}`); + const { data: organizationsData } = useOrganizations(accessToken); const [lastRefreshed, setLastRefreshed] = useState(""); const [currentOrg, setCurrentOrg] = useState(null); const [currentOrgForCreateTeam, setCurrentOrgForCreateTeam] = useState(null); @@ -940,7 +954,9 @@ const Teams: React.FC = ({ - {team.organization_id} + + {getOrganizationAlias(team.organization_id, organizationsData || organizations)} + {perTeamInfo &&