mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-11 22:51:28 +00:00
refactor(ui): decouple bulk invite from the invite user button
The bulk invite button was rendered from inside CreateUserButton, so the two actions were locked together and only the bulk one had been migrated, leaving the users page with an antd primary button sitting next to a shadcn one. Move BulkCreateUsersButton up to the users page toolbar so each action stands on its own, and migrate CreateUserButton's buttons to the shared shadcn Button so both triggers render identically. The teams prop only ever fed the bulk button, so it goes away from CreateUserButton and its other call site.
This commit is contained in:
parent
4eadf92ade
commit
ff3da21aab
5 changed files with 37 additions and 37 deletions
|
|
@ -148,12 +148,25 @@ describe("ViewUserDashboard", () => {
|
|||
expect(screen.getByRole("textbox", { name: "Default setting" })).toHaveValue("unsaved change");
|
||||
});
|
||||
|
||||
it("renders invite and bulk invite as toolbar actions alongside the other admin controls", async () => {
|
||||
renderDashboard();
|
||||
|
||||
const inviteButton = await screen.findByRole("button", { name: /\+ invite user/i });
|
||||
const bulkInviteButton = screen.getByRole("button", { name: /\+ bulk invite users/i });
|
||||
const toolbar = screen.getByTestId("toggle-user-selection").parentElement;
|
||||
|
||||
expect(inviteButton.parentElement).toBe(toolbar);
|
||||
expect(bulkInviteButton.parentElement).toBe(toolbar);
|
||||
});
|
||||
|
||||
it("shows the users table without admin controls for non-proxy admins", async () => {
|
||||
renderDashboard({ userRole: "Internal User" });
|
||||
|
||||
expect(await screen.findByText("test@example.com")).toBeInTheDocument();
|
||||
expect(screen.queryByRole("tab")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("toggle-user-selection")).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: /\+ invite user/i })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: /\+ bulk invite users/i })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps actions unavailable while the user list is loading", () => {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { parseAsString, useQueryState } from "nuqs";
|
|||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import BulkEditUserModal from "./BulkEditUsers";
|
||||
import BulkCreateUsersButton from "@/components/bulk_create_users_button";
|
||||
import { CreateUserButton } from "@/components/CreateUserButton";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
|
@ -365,12 +366,11 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
|
|||
{!userListQuery.isLoading && userID && accessToken && (
|
||||
<>
|
||||
{isProxyAdmin && (
|
||||
<CreateUserButton
|
||||
userID={userID}
|
||||
accessToken={accessToken}
|
||||
teams={teams}
|
||||
possibleUIRoles={possibleUIRoles}
|
||||
/>
|
||||
<CreateUserButton userID={userID} accessToken={accessToken} possibleUIRoles={possibleUIRoles} />
|
||||
)}
|
||||
|
||||
{isProxyAdmin && (
|
||||
<BulkCreateUsersButton accessToken={accessToken} teams={teams} possibleUIRoles={possibleUIRoles} />
|
||||
)}
|
||||
|
||||
{isProxyAdmin && (
|
||||
|
|
|
|||
|
|
@ -20,10 +20,6 @@ vi.mock("./networking", () => ({
|
|||
getProxyBaseUrl: vi.fn().mockReturnValue("http://localhost"),
|
||||
}));
|
||||
|
||||
vi.mock("./bulk_create_users_button", () => ({
|
||||
default: () => <div data-testid="bulk-create-users">Bulk Create Users</div>,
|
||||
}));
|
||||
|
||||
vi.mock("@/app/(dashboard)/hooks/organizations/useOrganizations", () => ({
|
||||
useOrganizations: vi.fn().mockReturnValue({ data: [], isLoading: false }),
|
||||
}));
|
||||
|
|
@ -42,7 +38,6 @@ const createQueryClient = () =>
|
|||
const defaultProps = {
|
||||
userID: "123",
|
||||
accessToken: "token",
|
||||
teams: [],
|
||||
possibleUIRoles: null as Record<string, Record<string, string>> | null,
|
||||
};
|
||||
|
||||
|
|
@ -75,6 +70,14 @@ describe("CreateUserButton", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("should not render the bulk invite button", async () => {
|
||||
renderWithProviders(<CreateUserButton {...defaultProps} />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("button", { name: /\+ invite user/i })).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.queryByRole("button", { name: /bulk invite users/i })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should open the invite modal when invite user button is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<CreateUserButton {...defaultProps} />);
|
||||
|
|
|
|||
|
|
@ -1,22 +1,11 @@
|
|||
import { InfoCircleOutlined, UserAddOutlined } from "@ant-design/icons";
|
||||
import { InfoCircleOutlined } from "@ant-design/icons";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useOrganizations } from "@/app/(dashboard)/hooks/organizations/useOrganizations";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Accordion, AccordionBody, AccordionHeader, SelectItem, TextInput } from "@tremor/react";
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Checkbox,
|
||||
Form,
|
||||
Input,
|
||||
Modal,
|
||||
Select,
|
||||
Select as Select2,
|
||||
Space,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from "antd";
|
||||
import { Alert, Checkbox, Form, Input, Modal, Select, Select as Select2, Space, Tooltip, Typography } from "antd";
|
||||
import { UserPlus } from "lucide-react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import BulkCreateUsers from "./bulk_create_users_button";
|
||||
import TeamDropdown from "./common_components/team_dropdown";
|
||||
import { getModelDisplayName } from "./key_team_helpers/fetch_available_models_team_key";
|
||||
import NotificationsManager from "./molecules/notifications_manager";
|
||||
|
|
@ -46,7 +35,6 @@ const generateUUID = (): string => {
|
|||
interface CreateuserProps {
|
||||
userID: string;
|
||||
accessToken: string;
|
||||
teams: any[] | null;
|
||||
possibleUIRoles: null | Record<string, Record<string, string>>;
|
||||
onUserCreated?: (userId: string) => void;
|
||||
isEmbedded?: boolean;
|
||||
|
|
@ -63,7 +51,6 @@ interface UISettings {
|
|||
export const CreateUserButton: React.FC<CreateuserProps> = ({
|
||||
userID,
|
||||
accessToken,
|
||||
teams,
|
||||
possibleUIRoles,
|
||||
onUserCreated,
|
||||
isEmbedded = false,
|
||||
|
|
@ -79,8 +66,6 @@ export const CreateUserButton: React.FC<CreateuserProps> = ({
|
|||
const [baseUrl, setBaseUrl] = useState<string | null>(null);
|
||||
const { data: organizations = [] } = useOrganizations();
|
||||
|
||||
// Derive teams from the user's organizations, falling back to the teams prop
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
|
|
@ -237,7 +222,7 @@ export const CreateUserButton: React.FC<CreateuserProps> = ({
|
|||
</Form.Item>
|
||||
|
||||
<div style={{ textAlign: "right", marginTop: "10px" }}>
|
||||
<Button htmlType="submit">Create User</Button>
|
||||
<Button type="submit">Create User</Button>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
|
|
@ -245,11 +230,10 @@ export const CreateUserButton: React.FC<CreateuserProps> = ({
|
|||
|
||||
// Original return for standalone mode
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<Button type="primary" className="mb-0" onClick={() => setIsModalVisible(true)}>
|
||||
<>
|
||||
<Button type="button" onClick={() => setIsModalVisible(true)}>
|
||||
+ Invite User
|
||||
</Button>
|
||||
<BulkCreateUsers accessToken={accessToken} teams={teams} possibleUIRoles={possibleUIRoles} />
|
||||
<Modal
|
||||
title="Invite User"
|
||||
open={isModalVisible}
|
||||
|
|
@ -377,7 +361,8 @@ export const CreateUserButton: React.FC<CreateuserProps> = ({
|
|||
</Accordion>
|
||||
|
||||
<div style={{ textAlign: "right", marginTop: "10px" }}>
|
||||
<Button type="primary" icon={<UserAddOutlined />} htmlType="submit">
|
||||
<Button type="submit">
|
||||
<UserPlus />
|
||||
Invite User
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -391,6 +376,6 @@ export const CreateUserButton: React.FC<CreateuserProps> = ({
|
|||
invitationLinkData={invitationLinkData}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1718,7 +1718,6 @@ const CreateKey: React.FC<CreateKeyProps> = ({ team, teams, data, addKey, autoOp
|
|||
<CreateUserButton
|
||||
userID={userID}
|
||||
accessToken={accessToken}
|
||||
teams={teams}
|
||||
possibleUIRoles={possibleUIRoles}
|
||||
onUserCreated={handleUserCreated}
|
||||
isEmbedded={true}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue