From aa8c64555a10ce851edb49c5cab44330e4915234 Mon Sep 17 00:00:00 2001 From: ryan Date: Wed, 22 Jul 2026 23:28:15 +0000 Subject: [PATCH] feat(ui): truncate team names with copy tooltip on org info page Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../organization/organization_view.tsx | 5 +- .../components/shared/copyable_badge.test.tsx | 41 +++++++++++++ .../src/components/shared/copyable_badge.tsx | 57 +++++++++++++++++++ 3 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/shared/copyable_badge.test.tsx create mode 100644 ui/litellm-dashboard/src/components/shared/copyable_badge.tsx diff --git a/ui/litellm-dashboard/src/components/organization/organization_view.tsx b/ui/litellm-dashboard/src/components/organization/organization_view.tsx index 402cc33902f..b7421966e61 100644 --- a/ui/litellm-dashboard/src/components/organization/organization_view.tsx +++ b/ui/litellm-dashboard/src/components/organization/organization_view.tsx @@ -24,6 +24,7 @@ import { organizationUpdateCall, } from "../networking"; import ObjectPermissionsView from "../object_permissions_view"; +import { CopyableBadge } from "../shared/copyable_badge"; import NumericalInput from "../shared/numerical_input"; import MemberModal from "../team/EditMembership"; import VectorStoreSelector from "../vector_store_management/VectorStoreSelector"; @@ -303,9 +304,7 @@ const OrganizationInfoView: React.FC = ({ Teams
{orgData.teams?.map((team, index) => ( - - {teamAliasMap[team.team_id] || team.team_id} - + ))}
diff --git a/ui/litellm-dashboard/src/components/shared/copyable_badge.test.tsx b/ui/litellm-dashboard/src/components/shared/copyable_badge.test.tsx new file mode 100644 index 00000000000..ed5ff486d16 --- /dev/null +++ b/ui/litellm-dashboard/src/components/shared/copyable_badge.test.tsx @@ -0,0 +1,41 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { describe, expect, it, vi } from "vitest"; + +import { CopyableBadge } from "./copyable_badge"; + +const { copyToClipboardMock } = vi.hoisted(() => ({ copyToClipboardMock: vi.fn() })); + +vi.mock("@/utils/dataUtils", async (importOriginal) => ({ + ...(await importOriginal()), + copyToClipboard: copyToClipboardMock, +})); + +describe("CopyableBadge", () => { + it("renders the value and truncates it", () => { + render(); + const label = screen.getByText("app-aigateway-inference-producttech-product-default"); + expect(label.className).toContain("truncate"); + expect(label.className).toContain("max-w-[220px]"); + }); + + it("copies the full value when clicked", async () => { + copyToClipboardMock.mockResolvedValue(true); + const user = userEvent.setup(); + render(); + + await user.click(screen.getByTestId("team-badge")); + + expect(copyToClipboardMock).toHaveBeenCalledWith("team-alias-long-name"); + }); + + it("exposes a copy affordance via accessible label", () => { + render(); + expect(screen.getByRole("button", { name: "Copy my-team" })).toBeInTheDocument(); + }); + + it("honors a custom max width", () => { + render(); + expect(screen.getByText("short").className).toContain("max-w-[120px]"); + }); +}); diff --git a/ui/litellm-dashboard/src/components/shared/copyable_badge.tsx b/ui/litellm-dashboard/src/components/shared/copyable_badge.tsx new file mode 100644 index 00000000000..463b23462d3 --- /dev/null +++ b/ui/litellm-dashboard/src/components/shared/copyable_badge.tsx @@ -0,0 +1,57 @@ +"use client"; + +import { Tag } from "antd"; +import { Check, Copy } from "lucide-react"; +import * as React from "react"; + +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; +import { cn } from "@/lib/cva.config"; +import { copyToClipboard } from "@/utils/dataUtils"; + +interface CopyableBadgeProps { + value: string; + color?: string; + maxWidthClassName?: string; + dataTestId?: string; +} + +export function CopyableBadge({ + value, + color = "red", + maxWidthClassName = "max-w-[220px]", + dataTestId, +}: CopyableBadgeProps) { + const [copied, setCopied] = React.useState(false); + + const handleCopy = async () => { + const success = await copyToClipboard(value); + if (success) { + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + }; + + return ( + + + + + {value} + + + + + {copied ? : } + {value} + {copied ? "Copied" : "Click to copy"} + + + + + ); +}