From 7369015c15718a358369931528a5b297b8c24ce7 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 12:22:09 +0000 Subject: [PATCH] fix(ui): render access group MCP and agent selections as wrapping chips Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../AccessGroupsModal/AccessGroupBaseForm.tsx | 61 ++----------------- .../AccessGroupEditModal.integration.test.tsx | 19 +++++- .../AccessGroupCreateDialog.tsx | 61 ++----------------- 3 files changed, 28 insertions(+), 113 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/access-groups/_components/AccessGroupsModal/AccessGroupBaseForm.tsx b/ui/litellm-dashboard/src/app/(dashboard)/access-groups/_components/AccessGroupsModal/AccessGroupBaseForm.tsx index f8ec3b5e1e7..33094565d6c 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/access-groups/_components/AccessGroupsModal/AccessGroupBaseForm.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/access-groups/_components/AccessGroupsModal/AccessGroupBaseForm.tsx @@ -9,8 +9,8 @@ import { useMCPServers } from "@/app/(dashboard)/hooks/mcpServers/useMCPServers" import { ModelSelect } from "@/components/ModelSelect/ModelSelect"; import { FieldGroup } from "@/components/ui/field"; import { FormField } from "@/components/shared/form/FormField"; +import { MultiSelect } from "@/components/shared/MultiSelect"; import { Input } from "@/components/ui/input"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Textarea } from "@/components/ui/textarea"; @@ -29,53 +29,6 @@ export const MODELS_TAB = "models"; export const MCP_SERVERS_TAB = "mcp-servers"; export const AGENTS_TAB = "agents"; -interface MultiSelectOption { - value: string; - label: string; -} - -interface MultiSelectProps { - id: string; - value: string[]; - onChange: (value: string[]) => void; - options: MultiSelectOption[]; - placeholder: string; - "aria-invalid": true | undefined; - "aria-describedby": string | undefined; -} - -const MultiSelect = ({ - id, - value, - onChange, - options, - placeholder, - "aria-invalid": ariaInvalid, - "aria-describedby": ariaDescribedBy, -}: MultiSelectProps) => ( - -); - interface AccessGroupBaseFormProps { form: UseFormReturn; isNameDisabled?: boolean; @@ -145,15 +98,13 @@ export function AccessGroupBaseForm({ - {({ id, value, onChange, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy }) => ( + {({ id, value, onChange }) => ( )} @@ -161,15 +112,13 @@ export function AccessGroupBaseForm({ - {({ id, value, onChange, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy }) => ( + {({ id, value, onChange }) => ( )} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/access-groups/_components/AccessGroupsModal/AccessGroupEditModal.integration.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/access-groups/_components/AccessGroupsModal/AccessGroupEditModal.integration.test.tsx index bd77ad8e897..472d4b8322e 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/access-groups/_components/AccessGroupsModal/AccessGroupEditModal.integration.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/access-groups/_components/AccessGroupsModal/AccessGroupEditModal.integration.test.tsx @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import userEvent, { PointerEventsCheckLevel } from "@testing-library/user-event"; -import { fireEvent, renderWithProviders, screen, waitFor } from "../../../../../../tests/test-utils"; +import { fireEvent, renderWithProviders, screen, waitFor, within } from "../../../../../../tests/test-utils"; import { AccessGroupEditModal } from "./AccessGroupEditModal"; import { AccessGroupResponse } from "@/app/(dashboard)/hooks/accessGroups/useAccessGroups"; @@ -164,6 +164,23 @@ describe("AccessGroupEditModal submit payload", () => { expect(mutate).not.toHaveBeenCalled(); }); + it("renders each selected MCP server as its own removable chip and drops one on remove", async () => { + const user = setup(); + renderModal(); + await screen.findByDisplayValue("Engineering"); + + await user.click(screen.getByRole("tab", { name: /MCP Servers/ })); + const chip = await screen.findByLabelText("Files"); + expect(chip).toHaveAttribute("data-slot", "combobox-chip"); + expect(screen.queryByText("srv-1")).not.toBeInTheDocument(); + + await user.click(within(chip).getByRole("button")); + await save(user); + + await waitFor(() => expect(mutate).toHaveBeenCalled()); + expect(variables().params.access_mcp_server_ids).toStrictEqual([]); + }); + it("sends models chosen on the Models tab", async () => { const user = setup(); renderModal({ ...accessGroup, access_model_names: [] }); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/access-groups/_components/access-group-create/AccessGroupCreateDialog.tsx b/ui/litellm-dashboard/src/app/(dashboard)/access-groups/_components/access-group-create/AccessGroupCreateDialog.tsx index a7f2ee18521..9965884728a 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/access-groups/_components/access-group-create/AccessGroupCreateDialog.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/access-groups/_components/access-group-create/AccessGroupCreateDialog.tsx @@ -11,10 +11,10 @@ import { ModelSelect } from "@/components/ModelSelect/ModelSelect"; import { toast } from "@/lib/toast"; import { FieldGroup } from "@/components/ui/field"; import { FormField } from "@/components/shared/form/FormField"; +import { MultiSelect } from "@/components/shared/MultiSelect"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Textarea } from "@/components/ui/textarea"; import { useZodForm } from "@/lib/forms/useZodForm"; @@ -25,53 +25,6 @@ import { accessGroupCreateSchema } from "./schema"; const GENERAL_TAB = "general"; -interface MultiSelectOption { - value: string; - label: string; -} - -interface MultiSelectProps { - id: string; - value: string[]; - onChange: (value: string[]) => void; - options: MultiSelectOption[]; - placeholder: string; - "aria-invalid": true | undefined; - "aria-describedby": string | undefined; -} - -const MultiSelect = ({ - id, - value, - onChange, - options, - placeholder, - "aria-invalid": ariaInvalid, - "aria-describedby": ariaDescribedBy, -}: MultiSelectProps) => ( - -); - const defaultCreateAccessGroup = async (body: AccessGroupCreateBody): Promise => { const { data } = await fetchClient.POST("/v1/access_group", { body }); return data; @@ -193,15 +146,13 @@ export const AccessGroupCreateDialog = ({ - {({ id, value, onChange, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy }) => ( + {({ id, value, onChange }) => ( )} @@ -209,15 +160,13 @@ export const AccessGroupCreateDialog = ({ - {({ id, value, onChange, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy }) => ( + {({ id, value, onChange }) => ( )}