Delete Team Member with friction (#16167)

This commit is contained in:
yuneng-jiang
2025-11-02 09:04:00 -08:00
committed by GitHub
parent 88240c4cba
commit feea17181c
2 changed files with 124 additions and 7 deletions
@@ -0,0 +1,81 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import TeamInfoView from "./team_info";
import { render, waitFor } from "@testing-library/react";
import * as networking from "@/components/networking";
// Mock the networking module
vi.mock("@/components/networking", () => ({
teamInfoCall: vi.fn(),
teamMemberDeleteCall: vi.fn(),
teamMemberAddCall: vi.fn(),
teamMemberUpdateCall: vi.fn(),
teamUpdateCall: vi.fn(),
getGuardrailsList: vi.fn(),
fetchMCPAccessGroups: vi.fn(),
getTeamPermissionsCall: vi.fn(),
}));
describe("TeamInfoView", () => {
afterEach(() => {
vi.clearAllMocks();
});
it("should render", async () => {
// Mock the team info response
vi.mocked(networking.teamInfoCall).mockResolvedValue({
team_id: "123",
team_info: {
team_alias: "Test Team",
team_id: "123",
organization_id: null,
admins: ["admin@test.com"],
members: ["user1@test.com", "user2@test.com"],
members_with_roles: [
{
user_id: "user1@test.com",
user_email: "user1@test.com",
role: "member",
spend: 0,
budget_id: "budget1",
},
],
metadata: {},
tpm_limit: null,
rpm_limit: null,
max_budget: null,
budget_duration: null,
models: [],
blocked: false,
spend: 0,
max_parallel_requests: null,
budget_reset_at: null,
model_id: null,
litellm_model_table: null,
created_at: "2024-01-01T00:00:00Z",
team_member_budget_table: null,
},
keys: [],
team_memberships: [],
});
vi.mocked(networking.getGuardrailsList).mockResolvedValue([]);
vi.mocked(networking.fetchMCPAccessGroups).mockResolvedValue([]);
const { getByText } = render(
<TeamInfoView
teamId="123"
onUpdate={() => {}}
onClose={() => {}}
accessToken="123"
is_team_admin={true}
is_proxy_admin={true}
userModels={[]}
editTeam={false}
premiumUser={false}
/>,
);
await waitFor(() => {
expect(getByText("User ID")).toBeInTheDocument();
});
});
});
@@ -25,7 +25,7 @@ import {
teamUpdateCall,
getGuardrailsList,
} from "@/components/networking";
import { Button, Form, Input, Select, message, Tooltip } from "antd";
import { Button, Form, Input, Select, message, Modal, Tooltip } from "antd";
import { InfoCircleOutlined } from "@ant-design/icons";
import { ArrowLeftIcon } from "@heroicons/react/outline";
import MemberModal from "./edit_membership";
@@ -138,6 +138,8 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
const [mcpAccessGroupsLoaded, setMcpAccessGroupsLoaded] = useState(false);
const [copiedStates, setCopiedStates] = useState<Record<string, boolean>>({});
const [guardrailsList, setGuardrailsList] = useState<string[]>([]);
const [memberToDelete, setMemberToDelete] = useState<Member | null>(null);
const [isDeleting, setIsDeleting] = useState(false);
console.log("userModels in team info", userModels);
@@ -268,13 +270,16 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
}
};
const handleMemberDelete = async (member: Member) => {
try {
if (accessToken == null) {
return;
}
const handleMemberDelete = (member: Member) => {
setMemberToDelete(member);
};
await teamMemberDeleteCall(accessToken, teamId, member);
const handleDeleteConfirm = async () => {
if (!memberToDelete || !accessToken) return;
setIsDeleting(true);
try {
await teamMemberDeleteCall(accessToken, teamId, memberToDelete);
NotificationsManager.success("Team member removed successfully");
@@ -287,9 +292,16 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
} catch (error) {
NotificationsManager.fromBackend("Failed to remove team member");
console.error("Error removing team member:", error);
} finally {
setIsDeleting(false);
setMemberToDelete(null);
}
};
const handleDeleteCancel = () => {
setMemberToDelete(null);
};
const handleTeamUpdate = async (values: any) => {
try {
if (!accessToken) return;
@@ -885,6 +897,30 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
onSubmit={handleMemberCreate}
accessToken={accessToken}
/>
{/* Delete Member Confirmation Modal */}
{memberToDelete && (
<Modal
title="Delete Team Member"
open={memberToDelete !== null}
onOk={handleDeleteConfirm}
onCancel={handleDeleteCancel}
confirmLoading={isDeleting}
okText={isDeleting ? "Deleting..." : "Delete"}
okButtonProps={{ danger: true }}
>
<p>Are you sure you want to remove this member from the team?</p>
<p className="mt-2">
<strong>User ID:</strong> {memberToDelete.user_id}
</p>
{memberToDelete.user_email && (
<p>
<strong>Email:</strong> {memberToDelete.user_email}
</p>
)}
<p className="mt-2 text-red-600">This action cannot be undone.</p>
</Modal>
)}
</div>
);
};