From d090b4ad3e55ef69d44a90b07fab88e1c4816716 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Fri, 12 Dec 2025 13:15:25 -0800 Subject: [PATCH] Add All Proxy Models To Default User Settings --- .../components/DefaultUserSettings.test.tsx | 153 ++++++++++++++++++ ...SOSettings.tsx => DefaultUserSettings.tsx} | 14 +- .../src/components/view_users.tsx | 4 +- 3 files changed, 166 insertions(+), 5 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/DefaultUserSettings.test.tsx rename ui/litellm-dashboard/src/components/{SSOSettings.tsx => DefaultUserSettings.tsx} (98%) diff --git a/ui/litellm-dashboard/src/components/DefaultUserSettings.test.tsx b/ui/litellm-dashboard/src/components/DefaultUserSettings.test.tsx new file mode 100644 index 0000000000..78d50fa7f3 --- /dev/null +++ b/ui/litellm-dashboard/src/components/DefaultUserSettings.test.tsx @@ -0,0 +1,153 @@ +import { act, fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import DefaultUserSettings from "./DefaultUserSettings"; +import * as networking from "./networking"; + +vi.mock("./networking", () => ({ + getInternalUserSettings: vi.fn(), + updateInternalUserSettings: vi.fn(), + modelAvailableCall: vi.fn(), +})); + +vi.mock("./common_components/budget_duration_dropdown", () => ({ + default: ({ value, onChange }: { value: string | null; onChange: (value: string | null) => void }) => ( + + ), + getBudgetDurationLabel: (value: string) => value, +})); + +vi.mock("./key_team_helpers/fetch_available_models_team_key", () => ({ + getModelDisplayName: (model: string) => model, +})); + +describe("DefaultUserSettings", () => { + const mockGetInternalUserSettings = vi.mocked(networking.getInternalUserSettings); + const mockUpdateInternalUserSettings = vi.mocked(networking.updateInternalUserSettings); + const mockModelAvailableCall = vi.mocked(networking.modelAvailableCall); + + const defaultProps = { + accessToken: "test-token", + userID: "user-123", + userRole: "Admin", + possibleUIRoles: { + internal_user_admin: { + ui_label: "Admin", + description: "Full access", + }, + internal_user_viewer: { + ui_label: "Viewer", + description: "Read-only access", + }, + }, + }; + + const mockSettings = { + values: { + user_role: "internal_user_admin", + budget_duration: "monthly", + max_budget: 1000, + teams: [], + }, + field_schema: { + description: "Default user settings", + properties: { + user_role: { + type: "string", + description: "User role", + }, + budget_duration: { + type: "string", + description: "Budget duration", + }, + max_budget: { + type: "number", + description: "Maximum budget", + }, + teams: { + type: "array", + description: "Teams", + }, + }, + }, + }; + + beforeEach(() => { + mockGetInternalUserSettings.mockClear(); + mockUpdateInternalUserSettings.mockClear(); + mockModelAvailableCall.mockClear(); + mockModelAvailableCall.mockResolvedValue({ + data: [{ id: "gpt-4" }, { id: "gpt-3.5-turbo" }], + }); + }); + + it("should render", async () => { + mockGetInternalUserSettings.mockResolvedValue(mockSettings); + + render(); + + await waitFor(() => { + expect(mockGetInternalUserSettings).toHaveBeenCalled(); + }); + + expect(screen.getByText("Default User Settings")).toBeInTheDocument(); + }); + + it("should toggle edit mode when edit button is clicked", async () => { + mockGetInternalUserSettings.mockResolvedValue(mockSettings); + + render(); + + await waitFor(() => { + expect(screen.getByText("Edit Settings")).toBeInTheDocument(); + }); + + const editButton = screen.getByText("Edit Settings"); + act(() => { + fireEvent.click(editButton); + }); + + expect(screen.getByText("Cancel")).toBeInTheDocument(); + expect(screen.getByText("Save Changes")).toBeInTheDocument(); + expect(screen.queryByText("Edit Settings")).not.toBeInTheDocument(); + }); + + it("should save settings when save button is clicked", async () => { + mockGetInternalUserSettings.mockResolvedValue(mockSettings); + mockUpdateInternalUserSettings.mockResolvedValue({ + settings: { + ...mockSettings.values, + max_budget: 2000, + }, + }); + + render(); + + await waitFor(() => { + expect(screen.getByText("Edit Settings")).toBeInTheDocument(); + }); + + const editButton = screen.getByText("Edit Settings"); + act(() => { + fireEvent.click(editButton); + }); + + await waitFor(() => { + expect(screen.getByText("Save Changes")).toBeInTheDocument(); + }); + + const saveButton = screen.getByText("Save Changes"); + act(() => { + fireEvent.click(saveButton); + }); + + await waitFor(() => { + expect(mockUpdateInternalUserSettings).toHaveBeenCalled(); + }); + + expect(screen.getByText("Edit Settings")).toBeInTheDocument(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/SSOSettings.tsx b/ui/litellm-dashboard/src/components/DefaultUserSettings.tsx similarity index 98% rename from ui/litellm-dashboard/src/components/SSOSettings.tsx rename to ui/litellm-dashboard/src/components/DefaultUserSettings.tsx index 6402220f37..988a3bcec9 100644 --- a/ui/litellm-dashboard/src/components/SSOSettings.tsx +++ b/ui/litellm-dashboard/src/components/DefaultUserSettings.tsx @@ -8,7 +8,7 @@ import { getModelDisplayName } from "./key_team_helpers/fetch_available_models_t import { formatNumberWithCommas } from "@/utils/dataUtils"; import NotificationManager from "./molecules/notifications_manager"; -interface SSOSettingsProps { +interface DefaultUserSettingsProps { accessToken: string | null; possibleUIRoles?: Record> | null; userID: string; @@ -21,7 +21,12 @@ interface TeamEntry { user_role: "user" | "admin"; } -const SSOSettings: React.FC = ({ accessToken, possibleUIRoles, userID, userRole }) => { +const DefaultUserSettings: React.FC = ({ + accessToken, + possibleUIRoles, + userID, + userRole, +}) => { const [loading, setLoading] = useState(true); const [settings, setSettings] = useState(null); const [isEditing, setIsEditing] = useState(false); @@ -277,6 +282,9 @@ const SSOSettings: React.FC = ({ accessToken, possibleUIRoles, + {availableModels.map((model: string) => (