Merge pull request #18071 from BerriAI/litellm_ui_hide_add_models

[Feature] UI - Add Models Conditional Rendering
This commit is contained in:
yuneng-jiang
2025-12-16 11:38:50 -08:00
committed by GitHub
5 changed files with 243 additions and 48 deletions
@@ -1,7 +1,7 @@
/* @vitest-environment jsdom */
import { render } from "@testing-library/react";
import { describe, it, expect, vi } from "vitest";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { render } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import ModelsAndEndpointsView from "./ModelsAndEndpointsView";
// Minimal stubs to avoid Next.js router and network usage during render
@@ -57,21 +57,40 @@ vi.mock("@/app/(dashboard)/hooks/useTeams", () => ({
}),
}));
const mockUseModelsInfo = vi.fn();
vi.mock("@/app/(dashboard)/hooks/models/useModels", () => ({
useModelsInfo: () => mockUseModelsInfo(),
}));
const mockUseUISettings = vi.fn();
vi.mock("@/app/(dashboard)/hooks/uiSettings/useUISettings", () => ({
useUISettings: () => mockUseUISettings(),
}));
const createQueryClient = () =>
new QueryClient({
defaultOptions: { queries: { retry: false, gcTime: 0 } },
});
describe("ModelsAndEndpointsView", () => {
it("should render the models and endpoints view", async () => {
// JSDOM polyfill for libraries expecting ResizeObserver (e.g., recharts)
// Note: ResizeObserver is now globally mocked in setupTests.ts, but keeping this for backwards compatibility
beforeEach(() => {
mockUseModelsInfo.mockReturnValue({
data: { data: [] },
isLoading: false,
refetch: vi.fn(),
});
mockUseUISettings.mockReturnValue({
data: { values: {} },
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(global as any).ResizeObserver = class {
observe() {}
unobserve() {}
disconnect() {}
};
});
it("should render the models and endpoints view", async () => {
const queryClient = createQueryClient();
const { findByText } = render(
<QueryClientProvider client={queryClient}>
@@ -36,7 +36,7 @@ import ModelAnalyticsTab from "@/app/(dashboard)/models-and-endpoints/components
import ModelRetrySettingsTab from "@/app/(dashboard)/models-and-endpoints/components/ModelRetrySettingsTab";
import PriceDataManagementTab from "@/app/(dashboard)/models-and-endpoints/components/PriceDataManagementTab";
import { useUISettings } from "@/app/(dashboard)/hooks/uiSettings/useUISettings";
import { all_admin_roles, internalUserRoles } from "@/utils/roles";
import { all_admin_roles, internalUserRoles, isProxyAdminRole, isUserTeamAdminForAnyTeam } from "@/utils/roles";
import HealthCheckComponent from "../../../components/model_dashboard/HealthCheckComponent";
import ModelGroupAliasSettings from "../../../components/model_group_alias_settings";
import NotificationsManager from "../../../components/molecules/notifications_manager";
@@ -161,8 +161,13 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({
const credentialsList = credentialsResponse?.credentials || [];
const { data: uiSettings } = useUISettings(accessToken || "");
const isProxyAdmin = userRole && isProxyAdminRole(userRole);
const isInternalUser = userRole && internalUserRoles.includes(userRole);
const shouldHideAddModelTab = isInternalUser && uiSettings?.values?.disable_model_add_for_internal_users === true;
const isUserTeamAdmin = userID && isUserTeamAdminForAnyTeam(teams, userID);
const addModelDisabledForInternalUsers =
isInternalUser && uiSettings?.values?.disable_model_add_for_internal_users === true;
// Hide tab if user is NOT a proxy admin AND (internal user with setting enabled OR not a team admin)
const shouldHideAddModelTab = !isProxyAdmin && (addModelDisabledForInternalUsers || !isUserTeamAdmin);
const setProviderModelsFn = (provider: Providers) => {
const _providerModels = getProviderModels(provider, modelMap);
@@ -0,0 +1,196 @@
import { describe, it, expect } from "vitest";
import { isAdminRole, isProxyAdminRole, isUserTeamAdminForAnyTeam, isUserTeamAdminForSingleTeam } from "./roles";
import { Team } from "@/components/networking";
describe("roles", () => {
describe("isAdminRole", () => {
it("should return true for all admin roles", () => {
expect(isAdminRole("Admin")).toBe(true);
expect(isAdminRole("Admin Viewer")).toBe(true);
expect(isAdminRole("proxy_admin")).toBe(true);
expect(isAdminRole("proxy_admin_viewer")).toBe(true);
expect(isAdminRole("org_admin")).toBe(true);
});
it("should return false for non-admin roles", () => {
expect(isAdminRole("Internal User")).toBe(false);
expect(isAdminRole("Internal Viewer")).toBe(false);
expect(isAdminRole("regular_user")).toBe(false);
expect(isAdminRole("")).toBe(false);
});
});
describe("isProxyAdminRole", () => {
it("should return true for proxy_admin and Admin roles", () => {
expect(isProxyAdminRole("proxy_admin")).toBe(true);
expect(isProxyAdminRole("Admin")).toBe(true);
});
it("should return false for other admin roles", () => {
expect(isProxyAdminRole("Admin Viewer")).toBe(false);
expect(isProxyAdminRole("proxy_admin_viewer")).toBe(false);
expect(isProxyAdminRole("org_admin")).toBe(false);
});
it("should return false for non-admin roles", () => {
expect(isProxyAdminRole("Internal User")).toBe(false);
expect(isProxyAdminRole("Internal Viewer")).toBe(false);
expect(isProxyAdminRole("regular_user")).toBe(false);
expect(isProxyAdminRole("")).toBe(false);
});
});
describe("isUserTeamAdminForSingleTeam", () => {
it("should return true when user is team admin", () => {
const team: Team = {
team_id: "team-1",
team_alias: "Test Team",
models: [],
max_budget: null,
budget_duration: null,
tpm_limit: null,
rpm_limit: null,
organization_id: "org-1",
created_at: "2024-01-01",
keys: [],
members_with_roles: [
{ user_id: "user-1", user_email: "user1@test.com", role: "admin" },
{ user_id: "user-2", user_email: "user2@test.com", role: "user" },
],
};
expect(isUserTeamAdminForSingleTeam(team, "user-1")).toBe(true);
});
it("should return false when user is not team admin", () => {
const team: Team = {
team_id: "team-1",
team_alias: "Test Team",
models: [],
max_budget: null,
budget_duration: null,
tpm_limit: null,
rpm_limit: null,
organization_id: "org-1",
created_at: "2024-01-01",
keys: [],
members_with_roles: [
{ user_id: "user-1", user_email: "user1@test.com", role: "user" },
{ user_id: "user-2", user_email: "user2@test.com", role: "user" },
],
};
expect(isUserTeamAdminForSingleTeam(team, "user-1")).toBe(false);
});
it("should return false when user is not in team", () => {
const team: Team = {
team_id: "team-1",
team_alias: "Test Team",
models: [],
max_budget: null,
budget_duration: null,
tpm_limit: null,
rpm_limit: null,
organization_id: "org-1",
created_at: "2024-01-01",
keys: [],
members_with_roles: [{ user_id: "user-2", user_email: "user2@test.com", role: "admin" }],
};
expect(isUserTeamAdminForSingleTeam(team, "user-1")).toBe(false);
});
it("should return false when team is null", () => {
expect(isUserTeamAdminForSingleTeam(null, "user-1")).toBe(false);
});
it("should return false when members_with_roles is null", () => {
const team = {
team_id: "team-1",
team_alias: "Test Team",
models: [],
max_budget: null,
budget_duration: null,
tpm_limit: null,
rpm_limit: null,
organization_id: "org-1",
created_at: "2024-01-01",
keys: [],
members_with_roles: [],
} as Team;
expect(isUserTeamAdminForSingleTeam(team, "user-1")).toBe(false);
});
});
describe("isUserTeamAdminForAnyTeam", () => {
it("should return true when user is admin of at least one team", () => {
const teams: Team[] = [
{
team_id: "team-1",
team_alias: "Test Team 1",
models: [],
max_budget: null,
budget_duration: null,
tpm_limit: null,
rpm_limit: null,
organization_id: "org-1",
created_at: "2024-01-01",
keys: [],
members_with_roles: [{ user_id: "user-1", user_email: "user1@test.com", role: "user" }],
},
{
team_id: "team-2",
team_alias: "Test Team 2",
models: [],
max_budget: null,
budget_duration: null,
tpm_limit: null,
rpm_limit: null,
organization_id: "org-1",
created_at: "2024-01-01",
keys: [],
members_with_roles: [{ user_id: "user-1", user_email: "user1@test.com", role: "admin" }],
},
];
expect(isUserTeamAdminForAnyTeam(teams, "user-1")).toBe(true);
});
it("should return false when user is not admin of any team", () => {
const teams: Team[] = [
{
team_id: "team-1",
team_alias: "Test Team 1",
models: [],
max_budget: null,
budget_duration: null,
tpm_limit: null,
rpm_limit: null,
organization_id: "org-1",
created_at: "2024-01-01",
keys: [],
members_with_roles: [{ user_id: "user-1", user_email: "user1@test.com", role: "user" }],
},
{
team_id: "team-2",
team_alias: "Test Team 2",
models: [],
max_budget: null,
budget_duration: null,
tpm_limit: null,
rpm_limit: null,
organization_id: "org-1",
created_at: "2024-01-01",
keys: [],
members_with_roles: [{ user_id: "user-2", user_email: "user2@test.com", role: "admin" }],
},
];
expect(isUserTeamAdminForAnyTeam(teams, "user-1")).toBe(false);
});
it("should return false when teams is null", () => {
expect(isUserTeamAdminForAnyTeam(null, "user-1")).toBe(false);
});
it("should return false when teams is empty array", () => {
expect(isUserTeamAdminForAnyTeam([], "user-1")).toBe(false);
});
});
});
@@ -1,41 +0,0 @@
import { describe, it, expect } from "vitest";
import { isAdminRole, isProxyAdminRole } from "./roles";
describe("roles", () => {
describe("isAdminRole", () => {
it("should return true for all admin roles", () => {
expect(isAdminRole("Admin")).toBe(true);
expect(isAdminRole("Admin Viewer")).toBe(true);
expect(isAdminRole("proxy_admin")).toBe(true);
expect(isAdminRole("proxy_admin_viewer")).toBe(true);
expect(isAdminRole("org_admin")).toBe(true);
});
it("should return false for non-admin roles", () => {
expect(isAdminRole("Internal User")).toBe(false);
expect(isAdminRole("Internal Viewer")).toBe(false);
expect(isAdminRole("regular_user")).toBe(false);
expect(isAdminRole("")).toBe(false);
});
});
describe("isProxyAdminRole", () => {
it("should return true for proxy_admin and Admin roles", () => {
expect(isProxyAdminRole("proxy_admin")).toBe(true);
expect(isProxyAdminRole("Admin")).toBe(true);
});
it("should return false for other admin roles", () => {
expect(isProxyAdminRole("Admin Viewer")).toBe(false);
expect(isProxyAdminRole("proxy_admin_viewer")).toBe(false);
expect(isProxyAdminRole("org_admin")).toBe(false);
});
it("should return false for non-admin roles", () => {
expect(isProxyAdminRole("Internal User")).toBe(false);
expect(isProxyAdminRole("Internal Viewer")).toBe(false);
expect(isProxyAdminRole("regular_user")).toBe(false);
expect(isProxyAdminRole("")).toBe(false);
});
});
});
+16
View File
@@ -1,3 +1,5 @@
import { Team } from "@/components/networking";
// Define admin roles and permissions
export const old_admin_roles = ["Admin", "Admin Viewer"];
export const v2_admin_role_names = ["proxy_admin", "proxy_admin_viewer", "org_admin"];
@@ -15,3 +17,17 @@ export const isAdminRole = (role: string): boolean => {
export const isProxyAdminRole = (role: string): boolean => {
return role === "proxy_admin" || role === "Admin";
};
export const isUserTeamAdminForAnyTeam = (teams: Team[] | null, userID: string): boolean => {
if (teams == null) {
return false;
}
return teams.some((team) => isUserTeamAdminForSingleTeam(team, userID));
};
export const isUserTeamAdminForSingleTeam = (team: Team | null, userID: string): boolean => {
if (team == null || team.members_with_roles == null) {
return false;
}
return team.members_with_roles.some((member) => member.user_id === userID && member.role === "admin");
};