Merge pull request #17902 from BerriAI/litellm_ui_default_user

[Fix] Add All Proxy Models To Default User Settings
This commit is contained in:
yuneng-jiang
2025-12-12 14:33:42 -08:00
committed by GitHub
3 changed files with 166 additions and 5 deletions
@@ -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 }) => (
<select data-testid="budget-duration" value={value || ""} onChange={(e) => onChange(e.target.value || null)}>
<option value="">Select duration</option>
<option value="daily">Daily</option>
<option value="monthly">Monthly</option>
</select>
),
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(<DefaultUserSettings {...defaultProps} />);
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(<DefaultUserSettings {...defaultProps} />);
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(<DefaultUserSettings {...defaultProps} />);
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();
});
});
@@ -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<string, Record<string, string>> | null;
userID: string;
@@ -21,7 +21,12 @@ interface TeamEntry {
user_role: "user" | "admin";
}
const SSOSettings: React.FC<SSOSettingsProps> = ({ accessToken, possibleUIRoles, userID, userRole }) => {
const DefaultUserSettings: React.FC<DefaultUserSettingsProps> = ({
accessToken,
possibleUIRoles,
userID,
userRole,
}) => {
const [loading, setLoading] = useState<boolean>(true);
const [settings, setSettings] = useState<any>(null);
const [isEditing, setIsEditing] = useState<boolean>(false);
@@ -277,6 +282,9 @@ const SSOSettings: React.FC<SSOSettingsProps> = ({ accessToken, possibleUIRoles,
<Option key="no-default-models" value="no-default-models">
No Default Models
</Option>
<Option key="all-proxy-models" value="all-proxy-models">
All Proxy Models
</Option>
{availableModels.map((model: string) => (
<Option key={model} value={model}>
{getModelDisplayName(model)}
@@ -482,4 +490,4 @@ const SSOSettings: React.FC<SSOSettingsProps> = ({ accessToken, possibleUIRoles,
);
};
export default SSOSettings;
export default DefaultUserSettings;
@@ -23,7 +23,7 @@ import { Typography } from "antd";
import DeleteResourceModal from "./common_components/DeleteResourceModal";
import NotificationsManager from "./molecules/notifications_manager";
import { modelAvailableCall, userDeleteCall } from "./networking";
import SSOSettings from "./SSOSettings";
import DefaultUserSettings from "./DefaultUserSettings";
import { columns } from "./view_users/columns";
import { UserDataTable } from "./view_users/table";
import { UserInfo } from "./view_users/types";
@@ -366,7 +366,7 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({ accessToken, toke
<Skeleton active paragraph={{ rows: 4 }} />
</div>
) : (
<SSOSettings
<DefaultUserSettings
accessToken={accessToken}
possibleUIRoles={possibleUIRoles}
userID={userID}