fix(ui): keep team create org and models selections during org refetch

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Devin AI 2026-08-31 15:45:06 +00:00
parent 4ba8517134
commit b907bab816
2 changed files with 78 additions and 12 deletions

View file

@ -1488,3 +1488,71 @@ describe("Teams - the exact bytes the create call sends", () => {
expect(teamCreateCall).not.toHaveBeenCalled();
});
});
describe("Teams - create team modal keeps user selections", () => {
const orgs = () => [
{ organization_id: "org-1", organization_alias: "Org One", models: [], members: [] },
{ organization_id: "org-2", organization_alias: "Org Two", models: [], members: [] },
];
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(fetchAvailableModelsForTeamOrKey).mockResolvedValue(["gpt-4"]);
vi.mocked(fetchMCPAccessGroups).mockResolvedValue([]);
vi.mocked(getGuardrailsList).mockResolvedValue({ guardrails: [] });
mockUseOrganizations.mockReturnValue({ data: orgs() });
});
const openCreateModal = async () => {
const queryClient = createQueryClient();
const wrap = (children: React.ReactElement) => (
<NuqsTestingAdapter hasMemory>
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
</NuqsTestingAdapter>
);
const view = render(wrap(<Teams accessToken="test-token" userID="user-123" userRole="Admin" />));
act(() => {
fireEvent.click(screen.getAllByRole("button", { name: /create team/i })[0]);
});
await waitFor(() => {
expect(screen.getByLabelText(/team name/i)).toBeInTheDocument();
});
return {
rerenderTeams: () => view.rerender(wrap(<Teams accessToken="test-token" userID="user-123" userRole="Admin" />)),
};
};
const selectOrganization = async (label: string) => {
const user = userEvent.setup();
const orgCombobox = screen.getByRole("combobox", { name: /organization/i });
await user.click(orgCombobox);
await user.click(await screen.findByText(label));
return orgCombobox;
};
it("keeps the selected organization and models when the organizations list refetches", async () => {
const { rerenderTeams } = await openCreateModal();
const orgCombobox = await selectOrganization("Org One");
fireEvent.change(screen.getByTestId("create-team-models-select"), { target: { value: "gpt-4" } });
mockUseOrganizations.mockReturnValue({ data: orgs() });
act(() => {
rerenderTeams();
});
expect(orgCombobox).toHaveValue("Org One");
expect(screen.getByTestId("create-team-models-select")).toHaveValue("gpt-4");
});
it("clears the selected models when the user picks a different organization", async () => {
await openCreateModal();
await selectOrganization("Org One");
fireEvent.change(screen.getByTestId("create-team-models-select"), { target: { value: "gpt-4" } });
expect(screen.getByTestId("create-team-models-select")).toHaveValue("gpt-4");
await selectOrganization("Org Two");
expect(screen.getByTestId("create-team-models-select")).toHaveValue("");
});
});

View file

@ -17,7 +17,7 @@ import { useZodForm } from "@/lib/forms/useZodForm";
import { TagsInput } from "@/app/(dashboard)/guardrails/_components/content_filter/TagsInput";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { ChevronDown, Plus, Users } from "lucide-react";
import React, { useEffect, useMemo, useState } from "react";
import React, { useEffect, useMemo, useRef, useState } from "react";
import { z } from "zod/v4";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { PageHeader } from "@/components/shared/PageHeader";
@ -264,13 +264,10 @@ const Teams: React.FC<TeamProps> = ({ accessToken, userID, userRole, premiumUser
? `Default: ${getBudgetDurationLabel(defaultBudgetDuration)} (${defaultBudgetDuration})`
: "n/a";
useEffect(() => {
form.setValue("models", []);
}, [currentOrgForCreateTeam, userModels]);
// Handle organization preselection when modal opens
const wasTeamModalVisible = useRef(false);
useEffect(() => {
if (isTeamModalVisible) {
if (isTeamModalVisible && !wasTeamModalVisible.current) {
const adminOrgs = getAdminOrganizations(userRole, userID, organizations);
// Org admins must scope a team to an org, so with exactly one we preselect it.
@ -284,6 +281,7 @@ const Teams: React.FC<TeamProps> = ({ accessToken, userID, userRole, premiumUser
setCurrentOrgForCreateTeam(currentOrg);
}
}
wasTeamModalVisible.current = isTeamModalVisible;
}, [isTeamModalVisible, isOrgAdmin, userRole, userID, organizations, currentOrg]);
// Add this useEffect to fetch guardrails
@ -686,6 +684,11 @@ const Teams: React.FC<TeamProps> = ({ accessToken, userID, userRole, premiumUser
const adminOrgs = getAdminOrganizations(userRole, userID, organizations);
const isSingleOrg = adminOrgs.length === 1;
const hasNoOrgs = adminOrgs.length === 0;
const handleCreateTeamOrgChange = (next: string, onChange: (value: string | null) => void) => {
onChange(next === "" ? null : next);
setCurrentOrgForCreateTeam(adminOrgs.find((org) => org.organization_id === next) ?? null);
form.setValue("models", []);
};
return (
<>
@ -721,12 +724,7 @@ const Teams: React.FC<TeamProps> = ({ accessToken, userID, userRole, premiumUser
hasNoOrgs ? "No organizations available" : "Search or select an Organization"
}
emptyText="No organizations available"
onValueChange={(next) => {
onChange(next === "" ? null : next);
setCurrentOrgForCreateTeam(
adminOrgs.find((org) => org.organization_id === next) ?? null,
);
}}
onValueChange={(next) => handleCreateTeamOrgChange(next, onChange)}
/>
)}
</FormField>