refactor(ui): share the Decisions error count helper and cover the cURL preview

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
mateo 2026-10-11 05:48:57 +00:00
parent 1e8a6ade2a
commit 5de7e70a6d
5 changed files with 34 additions and 6 deletions

View file

@ -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(<CurlPreview {...input} />);
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(<CurlPreview {...input} errorCount={2} />);
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();
});
});

View file

@ -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 (
<div className="flex min-h-0 flex-1 flex-col rounded-md border">
<div className="flex items-center justify-between gap-2 border-b px-3 py-2 text-sm">
<span className="font-medium">cURL request</span>
<div className="flex items-center gap-2 text-xs text-muted-foreground">
{invalid ? (
{errorCount > 0 ? (
<span role="alert" className="text-destructive">
Fix {errorCount} {errorCount === 1 ? "issue" : "issues"} on the Form tab before copying
</span>

View file

@ -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)}
/>
</TabsContent>
</Tabs>

View file

@ -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<unknown> }) {
const errorCount = validation.issues.filter((issue) => issue.severity === "error").length;
const errorCount = countErrors(validation);
if (errorCount > 0) {
return (
<Badge variant="destructive">

View file

@ -23,6 +23,9 @@ export interface PayloadValidation<T> {
export type SystemOnePayloadValidation = PayloadValidation<PlaygroundRequest>;
export const errorCount = (validation: PayloadValidation<unknown>): number =>
validation.issues.filter((issue) => issue.severity === "error").length;
const invalid = (path: string, message: string) => ({
isValid: false,
issues: [{ path, message, severity: "error" as const }],