From 6d32d4081dc5696d6be9307195afac3612d80de2 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 18 Aug 2026 12:16:01 -0700 Subject: [PATCH] refactor(ui): drop the unreachable user edit modal (#37327) EditUserModal was rendered by the users dashboard but nothing could ever open it. Its two pieces of state, editModalVisible and selectedUser, were only ever set to false and null, so the modal short-circuited to null on every render. The edit path users actually reach goes through the row actions menu, which routes to the user detail view and its edit form, so removing this leaves no capability behind. The submit handler that fed the dead modal goes with it, along with the imports it was the last consumer of. --- ui/litellm-dashboard/eslint-suppressions.json | 8 - .../users/_components/edit_user.test.tsx | 216 ------------------ .../users/_components/edit_user.tsx | 199 ---------------- .../users/_components/view_users.tsx | 46 ---- 4 files changed, 469 deletions(-) delete mode 100644 ui/litellm-dashboard/src/app/(dashboard)/users/_components/edit_user.test.tsx delete mode 100644 ui/litellm-dashboard/src/app/(dashboard)/users/_components/edit_user.tsx diff --git a/ui/litellm-dashboard/eslint-suppressions.json b/ui/litellm-dashboard/eslint-suppressions.json index c879eea3687..2436cb0e3b7 100644 --- a/ui/litellm-dashboard/eslint-suppressions.json +++ b/ui/litellm-dashboard/eslint-suppressions.json @@ -1471,14 +1471,6 @@ "count": 1 } }, - "src/app/(dashboard)/users/_components/edit_user.tsx": { - "local/filename-pascal-case": { - "count": 1 - }, - "no-restricted-imports": { - "count": 1 - } - }, "src/app/(dashboard)/users/_components/index.tsx": { "local/filename-pascal-case": { "count": 1 diff --git a/ui/litellm-dashboard/src/app/(dashboard)/users/_components/edit_user.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/edit_user.test.tsx deleted file mode 100644 index fd709bbdbe9..00000000000 --- a/ui/litellm-dashboard/src/app/(dashboard)/users/_components/edit_user.test.tsx +++ /dev/null @@ -1,216 +0,0 @@ -import React from "react"; -import { describe, expect, it, vi } from "vitest"; -import { screen, waitFor } from "@testing-library/react"; -import userEvent, { PointerEventsCheckLevel } from "@testing-library/user-event"; -import { renderWithProviders } from "@/../tests/test-utils"; -import EditUserModal from "./edit_user"; - -const POSSIBLE_UI_ROLES = { - proxy_admin: { ui_label: "Admin", description: "Can create keys, teams, users" }, - internal_user: { ui_label: "Internal User", description: "Can create keys for themselves" }, -}; - -const USER = { - user_id: "user-123", - user_email: "seed@example.com", - user_role: "internal_user", - spend: 3.5, - max_budget: 10, - budget_duration: "24h", - created_at: "2026-01-01T00:00:00Z", - updated_at: "2026-01-02T00:00:00Z", - teams: ["team-a"], - models: ["gpt-4"], - key_count: 7, -}; - -const renderModal = (overrides: Partial> = {}) => { - const onSubmit = vi.fn(); - const onCancel = vi.fn(); - renderWithProviders( - , - ); - return { onSubmit, onCancel }; -}; - -const save = async (user: ReturnType) => { - const buttons = screen.getAllByRole("button", { name: "Save" }); - await user.click(buttons[0]); -}; - -describe("EditUserModal", () => { - it("renders nothing when there is no user", () => { - renderWithProviders( - , - ); - expect(screen.queryByText(/Edit User/)).not.toBeInTheDocument(); - }); - - it("titles the modal with the user id", async () => { - renderModal(); - expect(await screen.findByText("Edit User user-123")).toBeInTheDocument(); - }); - - it("submits exactly the six bound fields, seeded from the user, and drops every other user key", async () => { - const user = userEvent.setup(); - const { onSubmit, onCancel } = renderModal(); - await screen.findByText("Edit User user-123"); - - await save(user); - - await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(1)); - const payload = onSubmit.mock.calls[0][0]; - expect(Object.keys(payload).sort()).toEqual([ - "budget_duration", - "max_budget", - "spend", - "user_email", - "user_id", - "user_role", - ]); - const seededPayload = { - user_id: "user-123", - user_email: "seed@example.com", - user_role: "internal_user", - spend: 3.5, - max_budget: 10, - budget_duration: "24h", - }; - expect(payload).toEqual(seededPayload); - expect(onCancel).toHaveBeenCalledTimes(1); - }); - - it("submits the edited email as a string", async () => { - const user = userEvent.setup(); - const { onSubmit } = renderModal(); - const email = await screen.findByLabelText("User Email"); - await user.clear(email); - await user.type(email, "edited@example.com"); - - await save(user); - - await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(1)); - expect(onSubmit.mock.calls[0][0].user_email).toBe("edited@example.com"); - }); - - it("submits spend as a number and max_budget as a string once both are retyped", async () => { - const user = userEvent.setup(); - const { onSubmit } = renderModal(); - const spend = await screen.findByLabelText("Spend (USD)"); - await user.clear(spend); - await user.type(spend, "42.567"); - const maxBudget = screen.getByLabelText("User Budget (USD)"); - await user.clear(maxBudget); - await user.type(maxBudget, "77.25"); - - await save(user); - - await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(1)); - const payload = onSubmit.mock.calls[0][0]; - expect(payload.spend).toBe(42.567); - expect(payload.max_budget).toBe("77.25"); - }); - - it("keeps a cleared spend and a cleared max_budget distinguishable from zero", async () => { - const user = userEvent.setup(); - const { onSubmit } = renderModal(); - const spend = await screen.findByLabelText("Spend (USD)"); - await user.clear(spend); - const maxBudget = screen.getByLabelText("User Budget (USD)"); - await user.clear(maxBudget); - - await save(user); - - await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(1)); - const payload = onSubmit.mock.calls[0][0]; - expect(payload.spend).toBeNull(); - expect(payload.max_budget).toBe(""); - }); - - it("clamps a negative spend up to the minimum on blur", async () => { - const user = userEvent.setup(); - const { onSubmit } = renderModal(); - const spend = await screen.findByLabelText("Spend (USD)"); - await user.clear(spend); - await user.type(spend, "-5"); - - await save(user); - - await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(1)); - expect(onSubmit.mock.calls[0][0].spend).toBe(0); - }); - - it("blocks the whole submit while max_budget is below its minimum", async () => { - const user = userEvent.setup(); - const { onSubmit, onCancel } = renderModal(); - const maxBudget = await screen.findByLabelText("User Budget (USD)"); - await user.clear(maxBudget); - await user.type(maxBudget, "-5"); - - await save(user); - - expect(onSubmit).not.toHaveBeenCalled(); - expect(onCancel).not.toHaveBeenCalled(); - }); - - it("submits the selected role value, not its label", async () => { - const user = userEvent.setup({ pointerEventsCheck: PointerEventsCheckLevel.Never }); - const { onSubmit } = renderModal(); - await screen.findByText("Edit User user-123"); - await user.click(screen.getByLabelText("User Role")); - await user.click(await screen.findByTitle("Admin")); - - await save(user); - - await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(1)); - expect(onSubmit.mock.calls[0][0].user_role).toBe("proxy_admin"); - }); - - it("submits the selected budget duration code", async () => { - const user = userEvent.setup({ pointerEventsCheck: PointerEventsCheckLevel.Never }); - const { onSubmit } = renderModal(); - await screen.findByText("Edit User user-123"); - await user.click(screen.getByLabelText("Reset Budget")); - await user.click(await screen.findByRole("option", { name: "weekly" })); - - await save(user); - - await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(1)); - expect(onSubmit.mock.calls[0][0].budget_duration).toBe("7d"); - }); - - it("does not submit when the user cancels", async () => { - const user = userEvent.setup(); - const { onSubmit, onCancel } = renderModal(); - await screen.findByText("Edit User user-123"); - await user.click(screen.getByRole("button", { name: /close/i })); - expect(onSubmit).not.toHaveBeenCalled(); - await waitFor(() => expect(onCancel).toHaveBeenCalledTimes(1)); - }); - - it("forwards null fields from the loaded user unchanged", async () => { - const actor = userEvent.setup({ pointerEventsCheck: PointerEventsCheckLevel.Never }); - const { onSubmit } = renderModal({ user: { ...USER, spend: null, max_budget: null, budget_duration: null } }); - - await save(actor); - - const nulledPayload = { - user_email: "seed@example.com", - user_id: "user-123", - user_role: "internal_user", - spend: null, - max_budget: null, - budget_duration: null, - }; - - await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(1)); - expect(onSubmit.mock.calls[0]?.[0]).toEqual(nulledPayload); - }); -}); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/users/_components/edit_user.tsx b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/edit_user.tsx deleted file mode 100644 index b11cb10d07a..00000000000 --- a/ui/litellm-dashboard/src/app/(dashboard)/users/_components/edit_user.tsx +++ /dev/null @@ -1,199 +0,0 @@ -import React from "react"; -import { useForm } from "react-hook-form"; -import { Modal } from "antd"; -import { CircleHelp } from "lucide-react"; - -import NumericalInput from "@/components/shared/numerical_input"; -import BudgetDurationDropdown from "@/components/common_components/budget_duration_dropdown"; -import { FieldGroup } from "@/components/shared/form/field"; -import { FormField } from "@/components/shared/form/FormField"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; - -interface EditableUser { - user_id: string; - user_email: string; - user_role: string; - spend: number | null; - max_budget: number | null; - budget_duration: string | null; -} - -interface EditUserFormValues { - user_email: string | undefined; - user_id: string | undefined; - user_role: string | undefined; - spend: number | null | undefined; - max_budget: number | string | null | undefined; - budget_duration: string | null | undefined; -} - -interface EditUserModalProps { - visible: boolean; - possibleUIRoles: null | Record>; - onCancel: () => void; - user: EditableUser | null; - onSubmit: (data: EditUserFormValues) => void; -} - -interface EditUserFormProps extends Omit { - user: EditableUser; -} - -const SPEND_MIN = 0; - -const toFormValues = (user: EditableUser): EditUserFormValues => ({ - user_email: user.user_email, - user_id: user.user_id, - user_role: user.user_role, - spend: user.spend, - max_budget: user.max_budget, - budget_duration: user.budget_duration, -}); - -const labelWithHint = (label: string, hint: string): React.ReactNode => ( - <> - {label} - - } /> - {hint} - - -); - -const roleOption = (uiLabel: string, description: string): React.ReactNode => ( -
- {uiLabel}

{description}

-
-); - -const EditUserForm: React.FC = ({ visible, possibleUIRoles, onCancel, user, onSubmit }) => { - const form = useForm({ defaultValues: toFormValues(user) }); - - const handleCancel = async () => { - form.reset(toFormValues(user)); - onCancel(); - }; - - const handleEditSubmit = async (formValues: EditUserFormValues) => { - onSubmit(formValues); - form.reset(toFormValues(user)); - onCancel(); - }; - - const clampSpendToMinimum = () => { - const spend = form.getValues("spend"); - if (typeof spend === "number" && spend < SPEND_MIN) { - form.setValue("spend", SPEND_MIN); - } - }; - - const roleItems: Record = Object.fromEntries( - Object.entries(possibleUIRoles ?? {}).map(([role, { ui_label, description }]) => [ - role, - roleOption(ui_label, description), - ]), - ); - - return ( - - -
- - - {({ ref, value, ...field }) => } - - - - {({ id, value, onChange, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy }) => ( - - )} - - - - {({ ref, value, onChange, onBlur, ...field }) => ( - onChange(event.target.value === "" ? null : event.target.valueAsNumber)} - onBlur={() => { - onBlur(); - clampSpendToMinimum(); - }} - /> - )} - - - - {({ ref: _ref, value, ...field }) => ( - - )} - - - - {({ id, value, onChange }) => } - - - -
- -
- -
- -
-
-
-
- ); -}; - -const EditUserModal: React.FC = ({ user, ...props }) => { - if (!user) { - return null; - } - - return ; -}; - -export default EditUserModal; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/users/_components/view_users.tsx b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/view_users.tsx index 05e02dc12c4..7a3133840be 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/users/_components/view_users.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/view_users.tsx @@ -7,18 +7,15 @@ import { CreateUserButton } from "@/components/CreateUserButton"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import EditUserModal from "./edit_user"; import { getPossibleUserRoles, getProxyBaseUrl, invitationCreateCall, userListCall, UserListResponse, - userUpdateUserCall, } from "@/components/networking"; import OnboardingModal, { InvitationLink } from "@/components/onboarding_link"; -import { updateExistingKeys } from "@/utils/dataUtils"; import { DEBOUNCE_WAIT_MS } from "@/utils/debounceConstants"; import { isAdminRole, isProxyAdminRole } from "@/utils/roles"; import { useDebouncedValue } from "@tanstack/react-pacer/debouncer"; @@ -77,8 +74,6 @@ const ViewUserDashboard: React.FC = ({ const [selectedUserId, setSelectedUserId] = useQueryState("user", parseAsString.withOptions({ history: "push" })); const [openInEditMode, setOpenInEditMode] = useState(false); - const [editModalVisible, setEditModalVisible] = useState(false); - const [selectedUser, setSelectedUser] = useState(null); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [isDeletingUser, setIsDeletingUser] = useState(false); const [userToDelete, setUserToDelete] = useState(null); @@ -207,39 +202,6 @@ const ViewUserDashboard: React.FC = ({ setUserToDelete(null); }; - const handleEditCancel = async () => { - setSelectedUser(null); - setEditModalVisible(false); - }; - - const handleEditSubmit = async (editedUser: any) => { - if (!accessToken || !token || !userRole || !userID) { - return; - } - - try { - const response = await userUpdateUserCall(accessToken, editedUser, null); - queryClient.setQueriesData({ queryKey: ["userList"] }, (previousData) => { - if (previousData === undefined) return previousData; - const updatedUsers = previousData.users.map((user) => { - if (user.user_id === response.data.user_id) { - return updateExistingKeys(user, response.data); - } - return user; - }); - - return { ...previousData, users: updatedUsers }; - }); - - toast.success(`User ${editedUser.user_id} updated successfully`); - } catch (error) { - console.error("There was an error updating the user", error); - } - setSelectedUser(null); - setEditModalVisible(false); - // Close the modal - }; - const handleToggleSelectionMode = () => { setSelectionMode(!selectionMode); setRowSelection({}); @@ -438,14 +400,6 @@ const ViewUserDashboard: React.FC = ({ )} {/* Existing Modals */} - -