feat(ui): migrate projects and access-groups to path routes (#30226)
* feat(ui): cut projects and access-groups over to path routes Same recipe as playground (#30185): MIGRATED_PAGES entries route the sidebar and redirect the legacy ?page= URLs, the switch arms are deleted, and the e2e fixture grows two entries. Both components were already zero-prop and self-fetching via React Query hooks, so the route wrappers are trivial. * refactor(ui): move Projects and AccessGroups components into their route folders Both folders were imported only by the legacy switch, so they colocate wholesale under (dashboard)/{projects,access-groups}/components. Their React Query hooks stay in the shared (dashboard)/hooks layer. eslint suppressions are re-keyed to the new paths. * test(ui): enable enable_projects_ui in e2e global setup The projects migration smoke clicks the Projects sidebar link, which only renders when the enterprise-gated enable_projects_ui setting is on; the seeded e2e database starts with it off, so the locator timed out in both e2e_ui_testing jobs. CI already launches the proxy with LITELLM_LICENSE for premium UI coverage, so flip the setting in globalSetup via the same /update/ui_settings call the admin UI toggle makes, failing loudly if the PATCH is rejected. * test(ui): use Playwright request context instead of raw fetch in global setup The frontend lint bans raw fetch() outside src/lib/http/; the e2e convention for proxy API calls is Playwright's APIRequestContext, as in routerSettings.spec.ts.
This commit is contained in:
parent
530c0b2326
commit
a2c916fb45
@ -11,13 +11,15 @@
|
||||
* Keep this in lockstep with MIGRATED_PAGES in src/utils/migratedPages.ts.
|
||||
* Pending (add as each PR lands): the leaf-pages batch
|
||||
* (budgets, caching, cost-tracking, guardrails, guardrails-monitor, logs,
|
||||
* mcp-servers, memory, policies, projects, prompts, search-tools, skills,
|
||||
* mcp-servers, memory, policies, prompts, search-tools, skills,
|
||||
* tag-management, tool-policies, transform-request, ui-theme, vector-stores,
|
||||
* workflows, access-groups).
|
||||
* workflows).
|
||||
*/
|
||||
export const MIGRATED_E2E_PAGES: Record<string, string> = {
|
||||
api_ref: "api-reference",
|
||||
"llm-playground": "playground",
|
||||
projects: "projects",
|
||||
"access-groups": "access-groups",
|
||||
};
|
||||
|
||||
export const MIGRATED_E2E_SEGMENTS: string[] = [...new Set(Object.values(MIGRATED_E2E_PAGES))];
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { chromium, expect } from "@playwright/test";
|
||||
import { chromium, expect, request } from "@playwright/test";
|
||||
import { users, Role, STORAGE_PATHS } from "./fixtures/users";
|
||||
import * as fs from "fs";
|
||||
|
||||
@ -6,6 +6,21 @@ async function globalSetup() {
|
||||
const browser = await chromium.launch();
|
||||
const rootPath = process.env.SERVER_ROOT_PATH ?? "";
|
||||
|
||||
// The Projects sidebar item is hidden unless the enterprise-gated
|
||||
// enable_projects_ui setting is on, and the seeded DB starts with it off.
|
||||
// The proxy runs with LITELLM_LICENSE in CI, so enable it the same way
|
||||
// the admin UI toggle does; the projects migration smoke needs the link.
|
||||
const masterKey = process.env.LITELLM_MASTER_KEY || "sk-1234";
|
||||
const api = await request.newContext();
|
||||
const settingsRes = await api.patch(`http://localhost:4000${rootPath}/update/ui_settings`, {
|
||||
headers: { Authorization: `Bearer ${masterKey}` },
|
||||
data: { enable_projects_ui: true },
|
||||
});
|
||||
if (!settingsRes.ok()) {
|
||||
throw new Error(`Enabling enable_projects_ui failed (${settingsRes.status()}): ${await settingsRes.text()}`);
|
||||
}
|
||||
await api.dispose();
|
||||
|
||||
for (const role of Object.values(Role)) {
|
||||
const { email, password } = users[role];
|
||||
const storagePath = STORAGE_PATHS[role];
|
||||
|
||||
@ -472,22 +472,22 @@
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"src/components/Projects/ProjectDetailsPage.tsx": {
|
||||
"src/app/(dashboard)/projects/components/ProjectDetailsPage.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/components/Projects/ProjectKeysSection.tsx": {
|
||||
"src/app/(dashboard)/projects/components/ProjectKeysSection.tsx": {
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/components/Projects/ProjectModals/ProjectBaseForm.tsx": {
|
||||
"src/app/(dashboard)/projects/components/ProjectModals/ProjectBaseForm.tsx": {
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"src/components/Projects/ProjectsPage.tsx": {
|
||||
"src/app/(dashboard)/projects/components/ProjectsPage.tsx": {
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import { AccessGroupResponse } from "@/app/(dashboard)/hooks/accessGroups/useAcc
|
||||
import { screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { renderWithProviders } from "../../../tests/test-utils";
|
||||
import { renderWithProviders } from "../../../../../tests/test-utils";
|
||||
import { AccessGroupDetail } from "./AccessGroupsDetailsPage";
|
||||
|
||||
vi.mock("@/app/(dashboard)/hooks/accessGroups/useAccessGroupDetails");
|
||||
@ -17,7 +17,7 @@ import {
|
||||
} from "antd";
|
||||
import { ArrowLeftIcon, BotIcon, EditIcon, KeyIcon, LayersIcon, ServerIcon, UsersIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import DefaultProxyAdminTag from "../common_components/DefaultProxyAdminTag";
|
||||
import DefaultProxyAdminTag from "@/components/common_components/DefaultProxyAdminTag";
|
||||
import { AccessGroupEditModal } from "./AccessGroupsModal/AccessGroupEditModal";
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
@ -65,7 +65,7 @@ vi.mock("./AccessGroupsModal/AccessGroupCreateModal", () => ({
|
||||
) : null,
|
||||
}));
|
||||
|
||||
vi.mock("../common_components/IconActionButton/TableIconActionButtons/TableIconActionButton", () => ({
|
||||
vi.mock("@/components/common_components/IconActionButton/TableIconActionButtons/TableIconActionButton", () => ({
|
||||
default: ({ variant, tooltipText, onClick }: { variant: string; tooltipText: string; onClick: () => void }) => (
|
||||
<button data-testid={`action-button-${variant.toLowerCase()}`} aria-label={tooltipText} onClick={onClick}>
|
||||
{variant}
|
||||
@ -13,12 +13,12 @@ import {
|
||||
import { Button, Card, Flex, Input, Layout, Pagination, Space, Table, Tag, theme, Tooltip, Typography } from "antd";
|
||||
import { BotIcon, LayersIcon, SearchIcon, ServerIcon } from "lucide-react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import DeleteResourceModal from "../common_components/DeleteResourceModal";
|
||||
import TableIconActionButton from "../common_components/IconActionButton/TableIconActionButtons/TableIconActionButton";
|
||||
import DeleteResourceModal from "@/components/common_components/DeleteResourceModal";
|
||||
import TableIconActionButton from "@/components/common_components/IconActionButton/TableIconActionButtons/TableIconActionButton";
|
||||
import {
|
||||
SortState,
|
||||
TableHeaderSortDropdown,
|
||||
} from "../common_components/TableHeaderSortDropdown/TableHeaderSortDropdown";
|
||||
} from "@/components/common_components/TableHeaderSortDropdown/TableHeaderSortDropdown";
|
||||
import { AccessGroupDetail } from "./AccessGroupsDetailsPage";
|
||||
import { AccessGroupCreateModal } from "./AccessGroupsModal/AccessGroupCreateModal";
|
||||
import { AccessGroup } from "./types";
|
||||
@ -0,0 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { AccessGroupsPage } from "./components/AccessGroupsPage";
|
||||
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
|
||||
|
||||
export default function AccessGroups() {
|
||||
useAuthorized();
|
||||
return <AccessGroupsPage />;
|
||||
}
|
||||
@ -34,8 +34,6 @@ import TransformRequestPanel from "@/components/transform_request";
|
||||
import UIThemeSettings from "@/components/ui_theme_settings";
|
||||
import Usage from "@/components/usage";
|
||||
import UserDashboard from "@/components/user_dashboard";
|
||||
import { AccessGroupsPage } from "@/components/AccessGroups/AccessGroupsPage";
|
||||
import { ProjectsPage } from "@/components/Projects/ProjectsPage";
|
||||
import VectorStoreManagement from "@/components/vector_store_management";
|
||||
import ToolPoliciesView from "@/components/ToolPoliciesView";
|
||||
import { MemoryView } from "@/components/MemoryView";
|
||||
@ -448,10 +446,6 @@ function CreateKeyPageContent() {
|
||||
<TagManagement accessToken={accessToken} userRole={userRole} userID={userID} />
|
||||
) : page == "skills" || page == "claude-code-plugins" ? (
|
||||
<ClaudeCodePluginsPanel accessToken={accessToken} userRole={userRole} />
|
||||
) : page == "access-groups" ? (
|
||||
<AccessGroupsPage />
|
||||
) : page == "projects" ? (
|
||||
<ProjectsPage />
|
||||
) : page == "vector-stores" ? (
|
||||
<VectorStoreManagement accessToken={accessToken} userRole={userRole} userID={userID} />
|
||||
) : page == "tool-policies" ? (
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { renderWithProviders, screen } from "../../../tests/test-utils";
|
||||
import { renderWithProviders, screen } from "../../../../../tests/test-utils";
|
||||
import { ProjectDetail } from "./ProjectDetailsPage";
|
||||
import { ProjectResponse } from "@/app/(dashboard)/hooks/projects/useProjects";
|
||||
|
||||
@ -19,7 +19,7 @@ import { LoadingOutlined } from "@ant-design/icons";
|
||||
import { BarChart } from "@tremor/react";
|
||||
import { ArrowLeftIcon, DollarSignIcon, EditIcon, KeyIcon, UsersIcon } from "lucide-react";
|
||||
import { useMemo, useState } from "react";
|
||||
import DefaultProxyAdminTag from "../common_components/DefaultProxyAdminTag";
|
||||
import DefaultProxyAdminTag from "@/components/common_components/DefaultProxyAdminTag";
|
||||
import { EditProjectModal } from "./ProjectModals/EditProjectModal";
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
@ -1,5 +1,5 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { renderWithProviders, screen } from "../../../tests/test-utils";
|
||||
import { renderWithProviders, screen } from "../../../../../tests/test-utils";
|
||||
import { ProjectKeysSection } from "./ProjectKeysSection";
|
||||
|
||||
const mockUseKeys = vi.fn();
|
||||
@ -1,5 +1,5 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { renderWithProviders, screen } from "../../../tests/test-utils";
|
||||
import { renderWithProviders, screen } from "../../../../../tests/test-utils";
|
||||
import { ProjectKeysTable } from "./ProjectKeysTable";
|
||||
import { KeyResponse } from "@/components/key_team_helpers/key_list";
|
||||
|
||||
@ -2,7 +2,7 @@ import { KeyResponse } from "@/components/key_team_helpers/key_list";
|
||||
import { Empty, Table, Tooltip } from "antd";
|
||||
import type { ColumnsType } from "antd/es/table";
|
||||
import type { SpinProps } from "antd";
|
||||
import DefaultProxyAdminTag from "../common_components/DefaultProxyAdminTag";
|
||||
import DefaultProxyAdminTag from "@/components/common_components/DefaultProxyAdminTag";
|
||||
|
||||
interface ProjectKeysTableProps {
|
||||
keys: KeyResponse[];
|
||||
@ -1,6 +1,6 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { renderWithProviders, screen } from "../../../../tests/test-utils";
|
||||
import { renderWithProviders, screen } from "../../../../../../tests/test-utils";
|
||||
import { CreateProjectModal } from "./CreateProjectModal";
|
||||
|
||||
const mockMutate = vi.fn();
|
||||
@ -1,6 +1,6 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { renderWithProviders, screen } from "../../../../tests/test-utils";
|
||||
import { renderWithProviders, screen } from "../../../../../../tests/test-utils";
|
||||
import { EditProjectModal } from "./EditProjectModal";
|
||||
import { ProjectResponse } from "@/app/(dashboard)/hooks/projects/useProjects";
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { renderWithProviders, screen, waitFor } from "../../../../tests/test-utils";
|
||||
import { renderWithProviders, screen, waitFor } from "../../../../../../tests/test-utils";
|
||||
import { Form } from "antd";
|
||||
import { ProjectBaseForm, ProjectFormValues } from "./ProjectBaseForm";
|
||||
|
||||
@ -19,9 +19,9 @@ import type { FormInstance } from "antd";
|
||||
import { PlusOutlined, MinusCircleOutlined } from "@ant-design/icons";
|
||||
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
|
||||
import { useTeams } from "@/app/(dashboard)/hooks/teams/useTeams";
|
||||
import { Team } from "../../key_team_helpers/key_list";
|
||||
import { fetchTeamModels } from "../../organisms/create_key_button";
|
||||
import { getModelDisplayName } from "../../key_team_helpers/fetch_available_models_team_key";
|
||||
import { Team } from "@/components/key_team_helpers/key_list";
|
||||
import { fetchTeamModels } from "@/components/organisms/create_key_button";
|
||||
import { getModelDisplayName } from "@/components/key_team_helpers/fetch_available_models_team_key";
|
||||
import { getGuardrailsList } from "@/components/networking";
|
||||
|
||||
export interface ProjectFormValues {
|
||||
@ -1,6 +1,6 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { renderWithProviders, screen, waitFor } from "../../../tests/test-utils";
|
||||
import { renderWithProviders, screen, waitFor } from "../../../../../tests/test-utils";
|
||||
import { ProjectsPage } from "./ProjectsPage";
|
||||
import { ProjectResponse } from "@/app/(dashboard)/hooks/projects/useProjects";
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { ProjectsPage } from "./components/ProjectsPage";
|
||||
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
|
||||
|
||||
export default function Projects() {
|
||||
useAuthorized();
|
||||
return <ProjectsPage />;
|
||||
}
|
||||
@ -47,6 +47,14 @@ describe("migratedHref / legacyPageHref", () => {
|
||||
|
||||
expect(MIGRATED_PAGES["llm-playground"]).toBe("playground");
|
||||
});
|
||||
|
||||
it("maps the projects and access-groups sidebar ids to their routes", async () => {
|
||||
vi.doMock("@/components/networking", () => ({ serverRootPath: "/" }));
|
||||
const { MIGRATED_PAGES } = await import("./migratedPages");
|
||||
|
||||
expect(MIGRATED_PAGES.projects).toBe("projects");
|
||||
expect(MIGRATED_PAGES["access-groups"]).toBe("access-groups");
|
||||
});
|
||||
});
|
||||
|
||||
describe("dev server (NODE_ENV=development)", () => {
|
||||
|
||||
@ -13,6 +13,8 @@ export const MIGRATED_PAGES: Record<string, string> = {
|
||||
// Legacy alias: older bookmarks used the hyphenated ?page=api-reference form.
|
||||
"api-reference": "api-reference",
|
||||
"llm-playground": "playground",
|
||||
projects: "projects",
|
||||
"access-groups": "access-groups",
|
||||
};
|
||||
|
||||
function uiBase(): string {
|
||||
|
||||
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user