diff --git a/ui/litellm-dashboard/src/components/templates/key_edit_view.integration.test.tsx b/ui/litellm-dashboard/src/components/templates/key_edit_view.integration.test.tsx index 9e3e19f1e1b..e48d3a591ef 100644 --- a/ui/litellm-dashboard/src/components/templates/key_edit_view.integration.test.tsx +++ b/ui/litellm-dashboard/src/components/templates/key_edit_view.integration.test.tsx @@ -1530,7 +1530,9 @@ describe("KeyEditView", () => { await waitFor(() => { expect(screen.getByLabelText("Organization")).toHaveValue("Engineering"); }); - await userEvent.click(screen.getByRole("button", { name: "Clear" })); + const clearButtons = screen.getAllByRole("button", { name: "Clear" }); + expect(clearButtons).toHaveLength(2); + await userEvent.click(clearButtons[0]); await userEvent.click(screen.getByRole("button", { name: /save changes/i })); await waitFor(() => { @@ -1571,7 +1573,7 @@ describe("KeyEditView", () => { const view = renderWithProviders(renderEditor()); await userEvent.click(await screen.findByRole("button", { name: "Detach from project" })); expect(screen.getByRole("combobox", { name: "Organization" })).toBeDisabled(); - expect(screen.getByRole("combobox", { name: "Team ID" })).toBeDisabled(); + expect(screen.getByRole("combobox", { name: "Team" })).toBeDisabled(); await userEvent.click(screen.getByRole("button", { name: "Cancel" })); expect(onCancel).toHaveBeenCalledOnce(); expect(onSubmit).not.toHaveBeenCalled(); @@ -1582,9 +1584,9 @@ describe("KeyEditView", () => { await waitFor(() => expect(onSubmit).toHaveBeenCalledWith(expect.objectContaining(expectedDetach), expect.any(Array)), ); - expect(screen.getByRole("combobox", { name: "Team ID" })).toBeDisabled(); + expect(screen.getByRole("combobox", { name: "Team" })).toBeDisabled(); view.rerender(renderEditor({ ...key, project_id: null })); - expect(screen.getByRole("combobox", { name: "Team ID" })).toBeEnabled(); + expect(screen.getByRole("combobox", { name: "Team" })).toBeEnabled(); expect(screen.queryByRole("button", { name: "Detach from project" })).not.toBeInTheDocument(); view.rerender(renderEditor(key, "Internal User")); expect(screen.queryByRole("button", { name: "Detach from project" })).not.toBeInTheDocument(); @@ -1615,7 +1617,7 @@ describe("KeyEditView", () => { />, ); expect(await screen.findByRole("combobox", { name: "Organization" })).toBeDisabled(); - expect(screen.getByRole("combobox", { name: "Team ID" })).toBeDisabled(); + expect(screen.getByRole("combobox", { name: "Team" })).toBeDisabled(); await userEvent.click(screen.getByRole("button", { name: /save changes/i })); await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(1)); expect(onSubmit.mock.calls[0][0]).toMatchObject({ organization_id: "org-1", team_id: "group-maple" }); @@ -2435,7 +2437,7 @@ describe("KeyEditView", () => { ); await screen.findByRole("button", { name: /save changes/i }); - await userEvent.click(screen.getByLabelText("Team ID")); + await userEvent.click(screen.getByRole("combobox", { name: "Team" })); await userEvent.click(await screen.findByRole("option", { name: /Team Nine/ })); await userEvent.click(screen.getByRole("button", { name: /save changes/i })); @@ -2446,6 +2448,98 @@ describe("KeyEditView", () => { expect(onSubmitMock.mock.calls[0][0].team_id).toBe("team-9"); }); + it("shows team aliases and IDs in the Team combobox", async () => { + renderWithProviders( + {}} + onSubmit={async () => {}} + accessToken="" + userID="" + userRole="Admin" + premiumUser={false} + />, + ); + + const teamCombobox = screen.getByRole("combobox", { name: "Team" }); + expect(teamCombobox).toHaveValue("Alpha"); + await userEvent.click(teamCombobox); + + const alphaOption = await screen.findByRole("option", { name: /Alpha/ }); + const betaOption = screen.getByRole("option", { name: /Beta/ }); + expect(alphaOption).toHaveTextContent("team-1"); + expect(betaOption).toHaveTextContent("team-2"); + }); + + it("shows a dash with the ID underneath for a team without an alias", async () => { + renderWithProviders( + {}} + onSubmit={async () => {}} + accessToken="" + userID="" + userRole="Admin" + premiumUser={false} + />, + ); + + await userEvent.click(screen.getByRole("combobox", { name: "Team" })); + + expect(await screen.findByRole("option")).toHaveTextContent(/^-team-3$/); + }); + + it("clears the team in the update payload and keeps the organization", async () => { + const onSubmitMock = vi.fn().mockResolvedValue(undefined); + renderWithProviders( + {}} + onSubmit={onSubmitMock} + accessToken="" + userID="" + userRole="Admin" + premiumUser={false} + />, + ); + + const teamCombobox = screen.getByRole("combobox", { name: "Team" }); + expect(teamCombobox).toHaveValue("Alpha"); + const [, teamClearButton] = screen.getAllByRole("button", { name: "Clear" }); + await userEvent.click(teamClearButton); + await userEvent.click(screen.getByRole("button", { name: /save changes/i })); + + await waitFor(() => { + expect(onSubmitMock).toHaveBeenCalledWith( + expect.objectContaining({ team_id: null, organization_id: "org-1" }), + expect.any(Array), + ); + }); + }); + + it("shows the selected team alias when it is outside the visible teams", async () => { + renderWithProviders( + {}} + onSubmit={async () => {}} + accessToken="" + userID="" + userRole="Admin" + premiumUser={false} + />, + ); + + expect(screen.getByRole("combobox", { name: "Team" })).toHaveValue("Gamma"); + }); + it("carries a picked MCP server into the payload", async () => { const onSubmitMock = vi.fn().mockResolvedValue(undefined); renderForPayload(onSubmitMock); diff --git a/ui/litellm-dashboard/src/components/templates/key_edit_view.tsx b/ui/litellm-dashboard/src/components/templates/key_edit_view.tsx index 1342b97d1a0..2ec5bfc1f2b 100644 --- a/ui/litellm-dashboard/src/components/templates/key_edit_view.tsx +++ b/ui/litellm-dashboard/src/components/templates/key_edit_view.tsx @@ -5,13 +5,13 @@ import { useUISettings } from "@/app/(dashboard)/hooks/uiSettings/useUISettings" import PolicySelector from "@/components/policies/PolicySelector"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { TooltipProvider } from "@/components/ui/tooltip"; import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner"; import { Field, FieldGroup, FieldLabel } from "@/components/ui/field"; import { FormField } from "@/components/shared/form/FormField"; +import { SearchSelect, SearchSelectOption } from "@/components/shared/SearchSelect"; import React, { useEffect, useRef, useState } from "react"; import { hasCapability } from "../../utils/capabilities"; import { isProxyAdminRole, rolesWithWriteAccess } from "../../utils/roles"; @@ -336,9 +336,6 @@ export function KeyEditView({ if (selectedTeam?.organization_id) { setSelectedOrganizationId(selectedTeam.organization_id); form.setValue("organization_id", selectedTeam.organization_id); - } else if (!teamId) { - setSelectedOrganizationId(null); - form.setValue("organization_id", null); } }; @@ -360,6 +357,17 @@ export function KeyEditView({ ? teams?.filter((t) => t.organization_id === selectedOrganizationId) : teams; + const teamOptionsFor = (selectedTeamId: string | null): SearchSelectOption[] => { + const selectedTeamOutsideFilter = teams?.find((t) => t.team_id === selectedTeamId && !visibleTeams?.includes(t)); + return [...(selectedTeamOutsideFilter ? [selectedTeamOutsideFilter] : []), ...(visibleTeams ?? [])].map( + ({ team_id, team_alias }: { team_id: string; team_alias?: string | null }) => ({ + label: team_alias || "-", + value: team_id, + sublabel: team_id, + }), + ); + }; + return (
{({ value, onChange, id }) => ( - + inputId={id} + placeholder="Select team" + emptyText="No teams found" + /> )}