From c5c98cf706638c2311409e533d0956620b625f3c Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Thu, 13 Aug 2026 14:56:58 -0700 Subject: [PATCH 1/4] test(ui): characterise GuardrailsOverview before the shadcn migration Covers the header and export action, all five summary metric cards, the table toolbar heading, the evaluation settings modal wiring, the busy state and the request failure message. Every assertion is role, title or text based so it holds against both the antd markup and its shadcn replacement, letting the migration commit land without editing this file --- .../_components/GuardrailsOverview.test.tsx | 70 ++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.test.tsx index 9bfa71f77be..b616982d69b 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.test.tsx @@ -1,4 +1,4 @@ -import { render, screen } from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import userEvent from "@testing-library/user-event"; import { beforeEach, describe, expect, it, vi } from "vitest"; @@ -14,7 +14,7 @@ vi.mock("./ScoreChart", () => ({ })); vi.mock("./EvaluationSettingsModal", () => ({ - EvaluationSettingsModal: () => null, + EvaluationSettingsModal: ({ open }: { open: boolean }) => (open ?
Evaluation settings modal
: null), })); const mockGetGuardrailsUsageOverview = vi.mocked(networking.getGuardrailsUsageOverview); @@ -28,6 +28,18 @@ function wrapper({ children }: { children: React.ReactNode }) { return {children}; } +function renderOverview(onSelectGuardrail = vi.fn()) { + return render( + , + { wrapper }, + ); +} + describe("GuardrailsOverview", () => { beforeEach(() => { vi.clearAllMocks(); @@ -92,4 +104,58 @@ describe("GuardrailsOverview", () => { expect(onSelectGuardrail).toHaveBeenCalledWith("guardrail-low"); }); + + it("renders the page header and the export action", async () => { + renderOverview(); + + expect(await screen.findByRole("heading", { name: "Guardrails Monitor", level: 1 })).toBeInTheDocument(); + expect(screen.getByText("Monitor guardrail performance across all requests")).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /Export Data/i })).toBeInTheDocument(); + }); + + it("renders every summary metric card", async () => { + renderOverview(); + + expect(await screen.findByText("1,500")).toBeInTheDocument(); + expect(screen.getByText("Total Evaluations")).toBeInTheDocument(); + expect(screen.getByText("Blocked Requests")).toBeInTheDocument(); + expect(screen.getByText("84")).toBeInTheDocument(); + expect(screen.getByText("Pass Rate")).toBeInTheDocument(); + expect(screen.getByText("94.4%")).toBeInTheDocument(); + expect(screen.getByText("23ms")).toBeInTheDocument(); + expect(screen.getByText("Active Guardrails")).toBeInTheDocument(); + expect(screen.getByText("2")).toBeInTheDocument(); + }); + + it("renders the table toolbar heading and its description", async () => { + renderOverview(); + + expect(await screen.findByRole("heading", { name: "Guardrail Performance", level: 5 })).toBeInTheDocument(); + expect(screen.getByText("Click a guardrail to view details, logs, and configuration")).toBeInTheDocument(); + }); + + it("opens the evaluation settings modal from the toolbar action", async () => { + const user = userEvent.setup(); + renderOverview(); + + expect(screen.queryByText("Evaluation settings modal")).not.toBeInTheDocument(); + + await user.click(await screen.findByTitle("Evaluation settings")); + + expect(await screen.findByText("Evaluation settings modal")).toBeInTheDocument(); + }); + + it("marks the overview busy while the usage request is in flight", async () => { + mockGetGuardrailsUsageOverview.mockReturnValue(new Promise(() => {})); + renderOverview(); + + await waitFor(() => expect(document.querySelector('[aria-busy="true"]')).toBeInTheDocument()); + }); + + it("shows a failure message when the usage request rejects", async () => { + mockGetGuardrailsUsageOverview.mockRejectedValue(new Error("network down")); + renderOverview(); + + expect(await screen.findByText("Failed to load data. Try again.")).toBeInTheDocument(); + }); }); From 7c9cd2b3dfdaacc0fb9e4e7daed75bdd327a61c1 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Thu, 13 Aug 2026 15:21:54 -0700 Subject: [PATCH 2/4] refactor(ui): migrate guardrails-monitor to shadcn Replaces the antd Button, Row, Col, Spin and Typography usage in GuardrailsOverview with the shadcn button, a CSS grid and the house loading wrapper, and swaps the ant-design icons for lucide equivalents that the already-migrated GuardrailDetail sibling uses. The busy state keeps the aria-busy attribute the antd Spin exposed, so the loading contract is unchanged for assistive tech. The route's other analyzer-listed file, GuardrailsMonitorView, keeps its DateRangePickerValue type import because the shared AdvancedDatePicker prop contract requires it, so there is nothing to migrate there. Retires the file's now unused no-restricted-imports suppression --- ui/litellm-dashboard/eslint-suppressions.json | 3 - .../_components/GuardrailsOverview.tsx | 84 +++++++++---------- 2 files changed, 40 insertions(+), 47 deletions(-) diff --git a/ui/litellm-dashboard/eslint-suppressions.json b/ui/litellm-dashboard/eslint-suppressions.json index d7f71a5840d..5df4a5af0ae 100644 --- a/ui/litellm-dashboard/eslint-suppressions.json +++ b/ui/litellm-dashboard/eslint-suppressions.json @@ -321,9 +321,6 @@ "src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.tsx": { "no-nested-ternary": { "count": 5 - }, - "no-restricted-imports": { - "count": 1 } }, "src/app/(dashboard)/guardrails/_components/GuardrailTestPanel.tsx": { diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.tsx b/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.tsx index 8d45b4a4ee8..3d91fc1b9b6 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.tsx @@ -1,11 +1,12 @@ -import { DownloadOutlined, RiseOutlined, SafetyOutlined, SettingOutlined, WarningOutlined } from "@ant-design/icons"; import { useQuery } from "@tanstack/react-query"; import type { ColumnDef, OnChangeFn, SortingState } from "@tanstack/react-table"; -import { Button, Col, Row, Spin, Typography } from "antd"; +import { Download, Settings, Shield, TrendingUp, TriangleAlert } from "lucide-react"; import React, { useMemo, useState } from "react"; import { DataTable, DataTableSortHeader } from "@/components/shared/DataTable"; import { getGuardrailsUsageOverview } from "@/components/networking"; import { type PerformanceRow } from "@/components/GuardrailsMonitor/mockData"; +import { Button } from "@/components/ui/button"; +import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner"; import { EvaluationSettingsModal } from "./EvaluationSettingsModal"; import { MetricCard } from "@/components/GuardrailsMonitor/MetricCard"; import { ScoreChart } from "./ScoreChart"; @@ -197,51 +198,42 @@ export function GuardrailsOverview({
- +

Guardrails Monitor

Monitor guardrail performance across all requests

-
- - - - - - } - /> - - - } - /> - - - 150 ? "text-red-600" : metrics.avgLatency > 50 ? "text-amber-600" : "text-green-600" - } - /> - - - - - +
+ + } + /> + } + /> + 150 ? "text-red-600" : metrics.avgLatency > 50 ? "text-amber-600" : "text-green-600" + } + /> + +
@@ -250,7 +242,11 @@ export function GuardrailsOverview({
{(isLoading || error) && (
- {isLoading && } + {isLoading && ( + + + + )} {error && Failed to load data. Try again.}
)} @@ -270,20 +266,20 @@ export function GuardrailsOverview({ toolbar={() => (
- - Guardrail Performance - +
Guardrail Performance

Click a guardrail to view details, logs, and configuration

)} From b341a223399874314f151c0667b50b4b4255ee02 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Thu, 13 Aug 2026 15:57:03 -0700 Subject: [PATCH 3/4] fix(ui): keep the guardrails metric cards wrapping at narrow widths The antd Row and Col the migration replaced never squeezed the summary cards below their content width; they wrapped onto a second line instead. Measured on a live dashboard, antd laid out five per row at 1280 and 1024, then four plus one at 900 and three plus two at 820, never narrower than about 126px. A fixed grid-cols-5 kept all five on one line and compressed them to 86px at 820, so the metric values overflowed their cards. An auto-fit track with a 7rem minimum reproduces antd's wrap points and card widths exactly at all four measured viewports, and is identical at 1280, so the route's visual baseline is unchanged. The added test fails against grid-cols-5 and passes against the track --- .../_components/GuardrailsOverview.test.tsx | 9 +++++++++ .../_components/GuardrailsOverview.tsx | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.test.tsx index b616982d69b..2aff2083870 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.test.tsx @@ -152,6 +152,15 @@ describe("GuardrailsOverview", () => { await waitFor(() => expect(document.querySelector('[aria-busy="true"]')).toBeInTheDocument()); }); + it("wraps the metric cards on a flexible track instead of a fixed column count", async () => { + renderOverview(); + + const grid = (await screen.findByText("Total Evaluations")).closest("div.grid"); + + expect(grid).not.toBeNull(); + expect(grid!.className).toMatch(/grid-cols-\[repeat\(auto-fit,minmax\([^)]+,1fr\)\)\]/); + }); + it("shows a failure message when the usage request rejects", async () => { mockGetGuardrailsUsageOverview.mockRejectedValue(new Error("network down")); renderOverview(); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.tsx b/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.tsx index 3d91fc1b9b6..e1048c5322f 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.tsx @@ -211,7 +211,7 @@ export function GuardrailsOverview({
-
+
Date: Thu, 13 Aug 2026 16:21:01 -0700 Subject: [PATCH 4/4] test(ui): drop the metric grid class assertion The assertion pinned the Tailwind track string, which jsdom can never evaluate: it does no layout, so the test could not fail for the reason that matters, and its pattern accepted any minmax minimum, staying green if that minimum changed enough to break the layout outright. Only a viewport-resize browser test can observe this, which belongs in tests/e2e/ui rather than a route's unit tests. The responsive behaviour itself is unchanged; the widths it reproduces are recorded in b341a22339 --- .../_components/GuardrailsOverview.test.tsx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.test.tsx index 2aff2083870..b616982d69b 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.test.tsx @@ -152,15 +152,6 @@ describe("GuardrailsOverview", () => { await waitFor(() => expect(document.querySelector('[aria-busy="true"]')).toBeInTheDocument()); }); - it("wraps the metric cards on a flexible track instead of a fixed column count", async () => { - renderOverview(); - - const grid = (await screen.findByText("Total Evaluations")).closest("div.grid"); - - expect(grid).not.toBeNull(); - expect(grid!.className).toMatch(/grid-cols-\[repeat\(auto-fit,minmax\([^)]+,1fr\)\)\]/); - }); - it("shows a failure message when the usage request rejects", async () => { mockGetGuardrailsUsageOverview.mockRejectedValue(new Error("network down")); renderOverview();