From 4a2edf17022d2dee9e0c377ddafecfa83322d623 Mon Sep 17 00:00:00 2001 From: ryan-crabbe-berri Date: Fri, 11 Sep 2026 17:47:35 -0700 Subject: [PATCH] feat(ui): link the Organization cell on the Teams page (#40749) The Teams table showed a team's organization as plain text, so getting from a team to the org that owns it meant copying the alias and searching the Organizations page by hand. It now uses the same link helper the key tables use, so the cell points at the org detail page. Claude-Session: https://claude.ai/code/session_01NfwfQhamRNnSqgXMUjf3h4 --- .../components/TeamsPage/TeamsTable.test.tsx | 24 +++++++++++++++++++ .../components/TeamsPage/teamTableColumns.tsx | 5 ++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/ui/litellm-dashboard/src/components/TeamsPage/TeamsTable.test.tsx b/ui/litellm-dashboard/src/components/TeamsPage/TeamsTable.test.tsx index d3b9a55a7d5..95958994775 100644 --- a/ui/litellm-dashboard/src/components/TeamsPage/TeamsTable.test.tsx +++ b/ui/litellm-dashboard/src/components/TeamsPage/TeamsTable.test.tsx @@ -34,6 +34,10 @@ vi.mock("@/app/(dashboard)/hooks/teams/useTeams", () => ({ teamsTableKeys: { all: ["teamsTable"] }, })); +vi.mock("next/navigation", () => ({ + useRouter: () => ({ push: vi.fn() }), +})); + vi.mock("@/app/(dashboard)/hooks/organizations/useOrganizations", () => ({ useOrganizations: vi.fn().mockReturnValue({ data: [{ organization_id: "org-1", organization_alias: "Test Organization" }], @@ -306,10 +310,30 @@ describe("column rendering details", () => { }); }); + it("points the organization cell at the org's detail page, aliased or not", async () => { + mockUseTeamsTable.mockReturnValue( + teamsResult([ + { ...mockTeam, team_id: "a", organization_id: "org-1" }, + { ...mockTeam, team_id: "b", team_alias: "Orphan Team", organization_id: "org-unknown" }, + ]), + ); + renderTable(); + + expect(await screen.findByRole("link", { name: "Test Organization" })).toHaveAttribute( + "href", + "/ui/organizations?org=org-1", + ); + expect(screen.getByRole("link", { name: "org-unknown" })).toHaveAttribute( + "href", + "/ui/organizations?org=org-unknown", + ); + }); + it("renders an em dash for a team with no organization", () => { mockUseTeamsTable.mockReturnValue(teamsResult([{ ...mockTeam, organization_id: null as unknown as string }])); renderTable(); expect(screen.getByText("—")).toBeInTheDocument(); + expect(screen.queryByRole("link", { name: /organization/i })).not.toBeInTheDocument(); }); it("falls back to keys.length when keys_count is absent", () => { diff --git a/ui/litellm-dashboard/src/components/TeamsPage/teamTableColumns.tsx b/ui/litellm-dashboard/src/components/TeamsPage/teamTableColumns.tsx index da4948dd892..84369a58307 100644 --- a/ui/litellm-dashboard/src/components/TeamsPage/teamTableColumns.tsx +++ b/ui/litellm-dashboard/src/components/TeamsPage/teamTableColumns.tsx @@ -15,6 +15,7 @@ import { } from "@/components/ui/dropdown-menu"; import { Skeleton } from "@/components/ui/skeleton"; import { cn } from "@/lib/cva.config"; +import { orgDetailHref } from "@/utils/entityLinks"; import { copyToClipboard, formatNumberWithCommas } from "@/utils/dataUtils"; import { Team } from "../key_team_helpers/key_list"; @@ -183,8 +184,8 @@ export const getTeamTableColumns = ({ const displayValue = org?.organization_alias || orgId; const width = info.cell.column.getSize(); return ( - - {displayValue} + + ); },