mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-06 20:25:29 +00:00
Merge pull request #17902 from BerriAI/litellm_ui_default_user
[Fix] Add All Proxy Models To Default User Settings
This commit is contained in:
@@ -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();
|
||||
});
|
||||
});
|
||||
+11
-3
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user