fix(ui): show team alias in key edit Team dropdown and allow clearing it (#45629)

* fix(ui): show team alias in key edit Team dropdown and allow clearing it

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(ui): show alias-less teams by ID once in the key edit Team dropdown

* fix(ui): keep the key organization when its team is cleared

* fix(ui): show a dash for teams without an alias, keeping the ID underneath

---------

Co-authored-by: yuneng <yuneng@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
devin-ai-integration[bot] 2026-10-09 19:47:03 +00:00 • committed by GitHub
parent dafa9cbd4c
commit 07e315ebfb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 120 additions and 28 deletions

View file

@ -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(
<KeyEditView
keyData={{ ...MOCK_KEY_DATA, team_id: "team-1" }}
teams={[
{ team_id: "team-1", team_alias: "Alpha" },
{ team_id: "team-2", team_alias: "Beta" },
]}
onCancel={() => {}}
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(
<KeyEditView
keyData={{ ...MOCK_KEY_DATA, team_id: null }}
teams={[{ team_id: "team-3", team_alias: null }]}
onCancel={() => {}}
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(
<KeyEditView
keyData={{ ...MOCK_KEY_DATA, organization_id: "org-1", team_id: "team-1" }}
teams={[{ team_id: "team-1", team_alias: "Alpha", organization_id: "org-1" }]}
onCancel={() => {}}
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(
<KeyEditView
keyData={{ ...MOCK_KEY_DATA, organization_id: "org-1", team_id: "team-9" }}
teams={[{ team_id: "team-9", team_alias: "Gamma", organization_id: "org-2" }]}
onCancel={() => {}}
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);

View file

@ -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 (
<TooltipProvider>
<form
@ -796,29 +804,19 @@ export function KeyEditView({
<FormField
control={form.control}
name="team_id"
label="Team ID"
label="Team"
description={hasProject ? "Team is locked because this key belongs to a project" : undefined}
>
{({ value, onChange, id }) => (
<Select
<SearchSelect
options={teamOptionsFor((value as string | null) ?? null)}
value={(value as string | null) ?? null}
onValueChange={(teamId: string | null) => handleTeamChange(onChange, teamId)}
onValueChange={(teamId) => handleTeamChange(onChange, teamId)}
disabled={hasProject}
items={Object.fromEntries(
(visibleTeams ?? []).map((t) => [t.team_id, `${t.team_alias} (${t.team_id})`]),
)}
>
<SelectTrigger id={id} className="w-full">
<SelectValue placeholder="Select team" />
</SelectTrigger>
<SelectContent>
{visibleTeams?.map((t) => (
<SelectItem key={t.team_id} value={t.team_id}>
{`${t.team_alias} (${t.team_id})`}
</SelectItem>
))}
</SelectContent>
</Select>
inputId={id}
placeholder="Select team"
emptyText="No teams found"
/>
)}
</FormField>