diff --git a/ui/litellm-dashboard/src/components/common_components/MemberTable.tsx b/ui/litellm-dashboard/src/components/common_components/MemberTable.tsx index d58094642d2..259a44214d4 100644 --- a/ui/litellm-dashboard/src/components/common_components/MemberTable.tsx +++ b/ui/litellm-dashboard/src/components/common_components/MemberTable.tsx @@ -18,6 +18,7 @@ export interface MemberTableProps { extraColumns?: ColumnsType; showDeleteForMember?: (member: Member) => boolean; emptyText?: string; + rowSelection?: React.ComponentProps>["rowSelection"]; } export default function MemberTable({ @@ -31,6 +32,7 @@ export default function MemberTable({ extraColumns = [], showDeleteForMember, emptyText, + rowSelection, }: MemberTableProps) { const baseColumns: ColumnsType = [ { @@ -106,6 +108,7 @@ export default function MemberTable({ record.user_id ?? record.user_email ?? JSON.stringify(record)} pagination={false} size="small" diff --git a/ui/litellm-dashboard/src/components/team/TeamMemberTab.test.tsx b/ui/litellm-dashboard/src/components/team/TeamMemberTab.test.tsx index a07c57eaa30..016720cbe9e 100644 --- a/ui/litellm-dashboard/src/components/team/TeamMemberTab.test.tsx +++ b/ui/litellm-dashboard/src/components/team/TeamMemberTab.test.tsx @@ -182,6 +182,25 @@ describe("TeamMembersComponent", () => { expect(screen.getByText("Add Member")).toBeInTheDocument(); }); + it("should show checkboxes after Select Members is clicked", async () => { + const user = userEvent.setup(); + renderWithProviders( + , + ); + + await user.click(screen.getByRole("button", { name: "Select Members" })); + + expect(screen.getByRole("button", { name: "Bulk Edit (0 selected)" })).toBeDisabled(); + expect(screen.getAllByRole("checkbox")).toHaveLength(3); + }); + it("should display dash when user email is null", () => { renderWithProviders( ([]); const [bulkUpdateForm] = Form.useForm(); const formatNumber = (value: number | null): string => { if (value === null || value === undefined) return "0"; @@ -193,8 +195,6 @@ export default function TeamMemberTab({ ]; const handleBulkUpdate = async (values: { - all_members_in_team?: boolean; - user_ids?: string[]; apply_role?: boolean; role?: "admin" | "user"; apply_max_budget?: boolean; @@ -209,6 +209,11 @@ export default function TeamMemberTab({ allowed_models?: string[]; }) => { if (!accessToken) return; + const userIds = selectedMembers.flatMap((member) => (member.user_id ? [member.user_id] : [])); + if (userIds.length === 0) { + NotificationsManager.fromBackend("Select at least one team member"); + return; + } const updateFields: TeamMemberBulkUpdateFields = { ...(values.apply_role ? { role: values.role } : {}), ...(values.apply_max_budget ? { max_budget_in_team: values.max_budget_in_team ?? null } : {}), @@ -224,15 +229,11 @@ export default function TeamMemberTab({ setIsBulkUpdating(true); try { - const response = await teamMemberBulkUpdateCall( - accessToken, - teamData.team_id, - values.user_ids ?? [], - values.all_members_in_team === true, - updateFields, - ); + const response = await teamMemberBulkUpdateCall(accessToken, teamData.team_id, userIds, false, updateFields); await onMembersUpdated?.(); setIsBulkUpdateVisible(false); + setSelectedMembers([]); + setSelectionMode(false); bulkUpdateForm.resetFields(); NotificationsManager.success( `${response.successful_updates.length} team member${response.successful_updates.length === 1 ? "" : "s"} updated`, @@ -250,9 +251,22 @@ export default function TeamMemberTab({ return ( <> {canEditTeam && ( - + + + {selectionMode && ( + + )} + )} (member.user_id ? [member.user_id] : [])), + onChange: (_selectedRowKeys, selectedRows) => setSelectedMembers(selectedRows), + getCheckboxProps: (member) => ({ disabled: member.user_id === null }), + } + : undefined + } showDeleteForMember={() => isProxyAdmin || (canEditTeam && !isUserTeamAdmin) || (isUserTeamAdmin && !disableTeamAdminDeleteTeamUser) } /> setIsBulkUpdateVisible(false)} onOk={() => bulkUpdateForm.submit()} @@ -288,31 +311,6 @@ export default function TeamMemberTab({ confirmLoading={isBulkUpdating} >
- - Update all {teamData.team_info.members_with_roles.length} members - - - {({ getFieldValue }) => - !getFieldValue("all_members_in_team") && ( - -