feat(ui): migrate budgets, workflows, and guardrails-monitor to path routes (#30236)

* feat(ui): cut budgets, workflows, and guardrails-monitor over to path routes

Continues the page-by-page App Router migration (#30185, #30226). All
three legacy switch arms passed only accessToken, so each route wrapper
is a thin useAuthorized() + render. MIGRATED_PAGES routes the sidebar
and redirects the legacy ?page= URLs; the e2e fixture picks all three
up in the migration smoke and sidebar specs automatically.

* refactor(ui): colocate budgets, workflows, and guardrails-monitor components

budgets and workflow_runs were imported only by the legacy switch, so
they move wholesale into their route folders; the budgetItem type
hoists into the shared useBudgets hook, which owns the API response
shape, so the hooks layer no longer imports from a page folder.
GuardrailsMonitor keeps LogViewer, mockData, and MetricCard at the
shared src/components home because ToolDetail and ToolPolicies import
them; the rest moves. eslint suppressions are re-keyed accordingly.

* fix(ui): restore MetricCard test-utils path and merge duplicate import

MetricCard.test.tsx got the moved-tree depth rewrite before being moved
back to src/components/GuardrailsMonitor, leaving a five-level path
that escapes the project root; the suite failed at import. Also merge
the two imports from useBudgets in budget_panel.tsx. Both flagged by
Greptile.
This commit is contained in:
ryan-crabbe-berri 2026-06-11 14:27:40 -07:00 committed by GitHub
parent 1828a7c6f0
commit 3ad385a8a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 75 additions and 44 deletions

View File

@ -10,16 +10,18 @@
*
* 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, prompts, search-tools, skills,
* tag-management, tool-policies, transform-request, ui-theme, vector-stores,
* workflows).
* (caching, cost-tracking, guardrails, logs, mcp-servers, memory,
* policies, prompts, search-tools, skills, tag-management, tool-policies,
* transform-request, ui-theme, vector-stores).
*/
export const MIGRATED_E2E_PAGES: Record<string, string> = {
api_ref: "api-reference",
"llm-playground": "playground",
projects: "projects",
"access-groups": "access-groups",
budgets: "budgets",
workflows: "workflows",
"guardrails-monitor": "guardrails-monitor",
};
export const MIGRATED_E2E_SEGMENTS: string[] = [...new Set(Object.values(MIGRATED_E2E_PAGES))];

View File

@ -419,22 +419,22 @@
"count": 1
}
},
"src/components/GuardrailsMonitor/EvaluationSettingsModal.tsx": {
"src/app/(dashboard)/guardrails-monitor/components/EvaluationSettingsModal.tsx": {
"react-hooks/set-state-in-effect": {
"count": 1
}
},
"src/components/GuardrailsMonitor/GuardrailsMonitorView.tsx": {
"src/app/(dashboard)/guardrails-monitor/components/GuardrailsMonitorView.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"src/components/GuardrailsMonitor/ScoreChart.test.tsx": {
"src/app/(dashboard)/guardrails-monitor/components/ScoreChart.test.tsx": {
"react/display-name": {
"count": 1
}
},
"src/components/GuardrailsMonitor/ScoreChart.tsx": {
"src/app/(dashboard)/guardrails-monitor/components/ScoreChart.tsx": {
"no-restricted-imports": {
"count": 1
}
@ -798,22 +798,22 @@
"count": 1
}
},
"src/components/budgets/budget_modal.tsx": {
"src/app/(dashboard)/budgets/components/budget_modal.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"src/components/budgets/budget_panel.test.tsx": {
"src/app/(dashboard)/budgets/components/budget_panel.test.tsx": {
"unused-imports/no-unused-imports": {
"count": 2
}
},
"src/components/budgets/budget_panel.tsx": {
"src/app/(dashboard)/budgets/components/budget_panel.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"src/components/budgets/edit_budget_modal.tsx": {
"src/app/(dashboard)/budgets/components/edit_budget_modal.tsx": {
"no-restricted-imports": {
"count": 1
}
@ -2163,7 +2163,7 @@
"count": 1
}
},
"src/components/workflow_runs/index.tsx": {
"src/app/(dashboard)/workflows/WorkflowRuns.tsx": {
"no-restricted-syntax": {
"count": 3
},

View File

@ -2,7 +2,7 @@ import React from "react";
import { TextInput, Accordion, AccordionHeader, AccordionBody } from "@tremor/react";
import { Button as Button2, Modal, Form, InputNumber, Select } from "antd";
import { useCreateBudget } from "@/app/(dashboard)/hooks/budgets/useBudgets";
import NotificationsManager from "../molecules/notifications_manager";
import NotificationsManager from "@/components/molecules/notifications_manager";
interface BudgetModalProps {
isModalVisible: boolean;

View File

@ -21,10 +21,10 @@ import {
} from "@tremor/react";
import React, { useState } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import DeleteResourceModal from "../common_components/DeleteResourceModal";
import TableIconActionButton from "../common_components/IconActionButton/TableIconActionButtons/TableIconActionButton";
import NotificationsManager from "../molecules/notifications_manager";
import { useBudgets, useDeleteBudget } from "@/app/(dashboard)/hooks/budgets/useBudgets";
import DeleteResourceModal from "@/components/common_components/DeleteResourceModal";
import TableIconActionButton from "@/components/common_components/IconActionButton/TableIconActionButtons/TableIconActionButton";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { useBudgets, useDeleteBudget, budgetItem } from "@/app/(dashboard)/hooks/budgets/useBudgets";
import BudgetModal from "./budget_modal";
import EditBudgetModal from "./edit_budget_modal";
import { CREATE_END_USER_CURL_COMMAND, CHAT_COMPLETIONS_CURL_COMMAND, OPENAI_SDK_PYTHON_CODE } from "./constants";
@ -35,14 +35,6 @@ interface BudgetSettingsPageProps {
accessToken: string | null;
}
export interface budgetItem {
budget_id: string;
max_budget: number | null;
rpm_limit: number | null;
tpm_limit: number | null;
updated_at: string;
}
const BudgetPanel: React.FC<BudgetSettingsPageProps> = ({ accessToken }) => {
const [isCreateModelVisible, setIsCreateModelVisible] = useState(false);
const [isEditModalVisible, setIsEditModalVisible] = useState(false);

View File

@ -2,8 +2,8 @@ import React, { useEffect } from "react";
import { TextInput, Accordion, AccordionHeader, AccordionBody } from "@tremor/react";
import { Button as Button2, Modal, Form, InputNumber, Select } from "antd";
import { useUpdateBudget } from "@/app/(dashboard)/hooks/budgets/useBudgets";
import { budgetItem } from "./budget_panel";
import NotificationsManager from "../molecules/notifications_manager";
import { budgetItem } from "@/app/(dashboard)/hooks/budgets/useBudgets";
import NotificationsManager from "@/components/molecules/notifications_manager";
interface EditBudgetModalProps {
isModalVisible: boolean;

View File

@ -0,0 +1,9 @@
"use client";
import BudgetPanel from "./components/budget_panel";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export default function Budgets() {
const { accessToken } = useAuthorized();
return <BudgetPanel accessToken={accessToken} />;
}

View File

@ -4,9 +4,9 @@ import { Button, Col, Row, Spin, Tabs } from "antd";
import React, { useMemo, useState } from "react";
import { getGuardrailsUsageDetail, getGuardrailsUsageLogs } from "@/components/networking";
import { EvaluationSettingsModal } from "./EvaluationSettingsModal";
import { LogViewer } from "./LogViewer";
import { MetricCard } from "./MetricCard";
import type { LogEntry } from "./mockData";
import { LogViewer } from "@/components/GuardrailsMonitor/LogViewer";
import { MetricCard } from "@/components/GuardrailsMonitor/MetricCard";
import type { LogEntry } from "@/components/GuardrailsMonitor/mockData";
interface GuardrailDetailProps {
guardrailId: string;

View File

@ -4,9 +4,9 @@ import { Button, Card, Col, Row, Spin, Table, Typography } from "antd";
import type { ColumnsType } from "antd/es/table";
import React, { useMemo, useState } from "react";
import { getGuardrailsUsageOverview } from "@/components/networking";
import { type PerformanceRow } from "./mockData";
import { type PerformanceRow } from "@/components/GuardrailsMonitor/mockData";
import { EvaluationSettingsModal } from "./EvaluationSettingsModal";
import { MetricCard } from "./MetricCard";
import { MetricCard } from "@/components/GuardrailsMonitor/MetricCard";
import { ScoreChart } from "./ScoreChart";
interface GuardrailsOverviewProps {

View File

@ -1,7 +1,7 @@
import React from "react";
import { describe, it, expect, vi } from "vitest";
import { screen } from "@testing-library/react";
import { renderWithProviders } from "../../../tests/test-utils";
import { renderWithProviders } from "../../../../../tests/test-utils";
import { ScoreChart } from "./ScoreChart";
vi.mock("@tremor/react", async (importOriginal) => {

View File

@ -0,0 +1,9 @@
"use client";
import GuardrailsMonitorView from "./components/GuardrailsMonitorView";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export default function GuardrailsMonitor() {
const { accessToken } = useAuthorized();
return <GuardrailsMonitorView accessToken={accessToken} />;
}

View File

@ -2,7 +2,14 @@ import { useQuery, useMutation, useQueryClient, UseQueryResult } from "@tanstack
import { createQueryKeys } from "../common/queryKeysFactory";
import { getBudgetList, budgetCreateCall, budgetUpdateCall, budgetDeleteCall } from "@/components/networking";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import { budgetItem } from "@/components/budgets/budget_panel";
export interface budgetItem {
budget_id: string;
max_budget: number | null;
rpm_limit: number | null;
tpm_limit: number | null;
updated_at: string;
}
export const budgetKeys = createQueryKeys("budgets");

View File

@ -3,7 +3,6 @@
import ModelsAndEndpointsView from "@/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView";
import AdminPanel from "@/components/AdminPanel";
import AgentsPanel from "@/components/agents";
import BudgetPanel from "@/components/budgets/budget_panel";
import CacheDashboard from "@/components/cache_dashboard";
import ClaudeCodePluginsPanel from "@/components/claude_code_plugins";
import { teamListCall as v2TeamListCall } from "@/app/(dashboard)/hooks/teams/useTeams";
@ -12,7 +11,6 @@ import useProxySettings from "@/app/(dashboard)/hooks/proxySettings/useProxySett
import LoadingScreen from "@/components/common_components/LoadingScreen";
import { CostTrackingSettings } from "@/components/CostTrackingSettings";
import GeneralSettings from "@/components/general_settings";
import GuardrailsMonitorView from "@/components/GuardrailsMonitor/GuardrailsMonitorView";
import GuardrailsPanel from "@/components/guardrails";
import PoliciesPanel from "@/components/policies";
import { Team } from "@/components/key_team_helpers/key_list";
@ -37,7 +35,6 @@ import UserDashboard from "@/components/user_dashboard";
import VectorStoreManagement from "@/components/vector_store_management";
import ToolPoliciesView from "@/components/ToolPoliciesView";
import { MemoryView } from "@/components/MemoryView";
import WorkflowRuns from "@/components/workflow_runs";
import SpendLogsTable from "@/components/view_logs";
import ViewUserDashboard from "@/components/view_users";
import { useAuth } from "@/contexts/AuthContext";
@ -385,8 +382,6 @@ function CreateKeyPageContent() {
<AdminPanel proxySettings={proxySettings} />
) : page == "logging-and-alerts" ? (
<Settings userID={userID} userRole={userRole} accessToken={accessToken} premiumUser={premiumUser} />
) : page == "budgets" ? (
<BudgetPanel accessToken={accessToken} />
) : page == "guardrails" ? (
<GuardrailsPanel accessToken={accessToken} userRole={userRole} />
) : page == "policies" ? (
@ -450,12 +445,8 @@ function CreateKeyPageContent() {
<VectorStoreManagement accessToken={accessToken} userRole={userRole} userID={userID} />
) : page == "tool-policies" ? (
<ToolPoliciesView accessToken={accessToken} userRole={userRole} />
) : page == "workflows" ? (
<WorkflowRuns accessToken={accessToken} />
) : page == "memory" ? (
<MemoryView accessToken={accessToken} userID={userID} userRole={userRole} />
) : page == "guardrails-monitor" ? (
<GuardrailsMonitorView accessToken={accessToken} />
) : page == "new_usage" ? (
<NewUsagePage teams={(teams as Team[]) ?? []} organizations={(organizations as Organization[]) ?? []} />
) : (

View File

@ -0,0 +1,9 @@
"use client";
import WorkflowRuns from "./WorkflowRuns";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export default function Workflows() {
const { accessToken } = useAuthorized();
return <WorkflowRuns accessToken={accessToken} />;
}

View File

@ -55,6 +55,15 @@ describe("migratedHref / legacyPageHref", () => {
expect(MIGRATED_PAGES.projects).toBe("projects");
expect(MIGRATED_PAGES["access-groups"]).toBe("access-groups");
});
it("maps the budgets, workflows, and guardrails-monitor sidebar ids to their routes", async () => {
vi.doMock("@/components/networking", () => ({ serverRootPath: "/" }));
const { MIGRATED_PAGES } = await import("./migratedPages");
expect(MIGRATED_PAGES.budgets).toBe("budgets");
expect(MIGRATED_PAGES.workflows).toBe("workflows");
expect(MIGRATED_PAGES["guardrails-monitor"]).toBe("guardrails-monitor");
});
});
describe("dev server (NODE_ENV=development)", () => {

View File

@ -15,6 +15,9 @@ export const MIGRATED_PAGES: Record<string, string> = {
"llm-playground": "playground",
projects: "projects",
"access-groups": "access-groups",
budgets: "budgets",
workflows: "workflows",
"guardrails-monitor": "guardrails-monitor",
};
function uiBase(): string {