diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/CurlPreview.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/CurlPreview.test.tsx new file mode 100644 index 00000000000..14a4e5dcbcf --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/CurlPreview.test.tsx @@ -0,0 +1,26 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; +import CurlPreview from "./CurlPreview"; + +const input = { + baseUrl: "http://localhost:4000", + endpoint: "/v1/systemone" as const, + payload: JSON.stringify({ model: "jev-latest", state: "ok" }), + authHeaderName: "Authorization", +}; + +describe("CurlPreview", () => { + it("shows the command and a copy button for a valid payload", () => { + render(); + expect(screen.getByLabelText("cURL request")).toHaveTextContent("http://localhost:4000/v1/systemone"); + expect(screen.getByRole("button", { name: /copy curl/i })).toBeInTheDocument(); + expect(screen.queryByRole("alert")).not.toBeInTheDocument(); + }); + + it("hides the copy button and points back to the form while the payload has errors", () => { + render(); + expect(screen.getByRole("alert")).toHaveTextContent("Fix 2 issues on the Form tab"); + expect(screen.getByLabelText("cURL request")).toHaveTextContent("jev-latest"); + expect(screen.queryByRole("button", { name: /copy curl/i })).not.toBeInTheDocument(); + }); +}); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/CurlPreview.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/CurlPreview.tsx index 2e01ebb0ea8..ecefd87933b 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/CurlPreview.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/CurlPreview.tsx @@ -7,13 +7,12 @@ interface CurlPreviewProps extends CurlCommandInput { export default function CurlPreview({ errorCount = 0, ...input }: CurlPreviewProps) { const command = buildCurlCommand(input); - const invalid = errorCount > 0; return (
cURL request
- {invalid ? ( + {errorCount > 0 ? ( Fix {errorCount} {errorCount === 1 ? "issue" : "issues"} on the Form tab before copying diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneUI.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneUI.tsx index f16b1e53661..ae5def7dc48 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneUI.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneUI.tsx @@ -20,7 +20,7 @@ import CurlPreview from "./CurlPreview"; import ResponseView from "./ResponseView"; import SystemOneForm from "./SystemOneForm"; import OpenAIDecisionsForm from "./OpenAIDecisionsForm"; -import { validateSystemOnePayload, validateOpenAIDecisionsPayload } from "./lib/validatePayload"; +import { validateSystemOnePayload, validateOpenAIDecisionsPayload, errorCount } from "./lib/validatePayload"; import { useDecisionModels, type ApiKeySource } from "./useDecisionModels"; interface SystemOneUIProps { @@ -281,7 +281,7 @@ export default function SystemOneUI({ accessToken, disabledPersonalKeyCreation = endpoint={endpoint} payload={validation.payload === undefined ? rawPayload : JSON.stringify(validation.payload)} authHeaderName={getGlobalLitellmHeaderName()} - errorCount={validation.issues.filter((issue) => issue.severity === "error").length} + errorCount={errorCount(validation)} /> diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/ValidationFeedback.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/ValidationFeedback.tsx index 9b7f13bcfdf..369c4243857 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/ValidationFeedback.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/ValidationFeedback.tsx @@ -1,9 +1,9 @@ import { Badge } from "@/components/ui/badge"; import { CircleAlert, CircleCheck, TriangleAlert } from "lucide-react"; -import type { PayloadValidation } from "./lib/validatePayload"; +import { errorCount as countErrors, type PayloadValidation } from "./lib/validatePayload"; export function ValidationStatus({ validation }: { validation: PayloadValidation }) { - const errorCount = validation.issues.filter((issue) => issue.severity === "error").length; + const errorCount = countErrors(validation); if (errorCount > 0) { return ( diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/lib/validatePayload.ts b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/lib/validatePayload.ts index 316c98e6c92..d74923bb3e6 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/lib/validatePayload.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/lib/validatePayload.ts @@ -23,6 +23,9 @@ export interface PayloadValidation { export type SystemOnePayloadValidation = PayloadValidation; +export const errorCount = (validation: PayloadValidation): number => + validation.issues.filter((issue) => issue.severity === "error").length; + const invalid = (path: string, message: string) => ({ isValid: false, issues: [{ path, message, severity: "error" as const }],