diff --git a/apps/web/app/routes/ws/deployments/_components/plans/PlanDiffDialog.tsx b/apps/web/app/routes/ws/deployments/_components/plans/PlanDiffDialog.tsx
index dfc615e99..6edd3bc5d 100644
--- a/apps/web/app/routes/ws/deployments/_components/plans/PlanDiffDialog.tsx
+++ b/apps/web/app/routes/ws/deployments/_components/plans/PlanDiffDialog.tsx
@@ -1,14 +1,18 @@
-import { useState } from "react";
+import type { RouterOutputs } from "@ctrlplane/trpc";
+import { useEffect, useState } from "react";
import { DiffEditor } from "@monaco-editor/react";
import { trpc } from "~/api/trpc";
import { useTheme } from "~/components/ThemeProvider";
+import { Badge } from "~/components/ui/badge";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "~/components/ui/dialog";
+import { Label } from "~/components/ui/label";
+import { Switch } from "~/components/ui/switch";
import { Tabs, TabsList, TabsTrigger } from "~/components/ui/tabs";
type PlanDiffDialogProps = {
@@ -16,67 +20,198 @@ type PlanDiffDialogProps = {
resultId: string | undefined;
title: string;
open: boolean;
+ initialTab?: TopTab;
onOpenChange: (open: boolean) => void;
};
+type TopTab = "diff" | "validations";
type DiffView = "split" | "unified";
+type Validation =
+ RouterOutputs["deployment"]["plans"]["resultValidations"][number];
+
+function ValidationsTab({
+ deploymentId,
+ resultId,
+ open,
+}: {
+ deploymentId: string;
+ resultId: string;
+ open: boolean;
+}) {
+ const query = trpc.deployment.plans.resultValidations.useQuery(
+ { deploymentId, resultId },
+ { enabled: open },
+ );
+
+ if (query.isLoading)
+ return (
+
+ Loading validations...
+
+ );
+
+ const validations = query.data ?? [];
+ if (validations.length === 0)
+ return (
+
+ No validations evaluated for this result
+
+ );
+
+ return (
+
+
+ {validations.map((v) => (
+
+ ))}
+
+
+ );
+}
+
+function ValidationItem({ validation }: { validation: Validation }) {
+ return (
+
+
+
+ {validation.passed ? "Passed" : "Failed"}
+
+ {validation.ruleName}
+
+ {validation.ruleDescription && (
+
+ {validation.ruleDescription}
+
+ )}
+ {validation.violations.length > 0 && (
+
+ {validation.violations.map((violation, i) => (
+ -
+ {violation.message}
+
+ ))}
+
+ )}
+
+ );
+}
+
+function DiffTab({
+ deploymentId,
+ resultId,
+ open,
+ view,
+}: {
+ deploymentId: string;
+ resultId: string;
+ open: boolean;
+ view: DiffView;
+}) {
+ const { theme } = useTheme();
+
+ const diffQuery = trpc.deployment.plans.resultDiff.useQuery(
+ { deploymentId, resultId },
+ { enabled: open },
+ );
+
+ if (diffQuery.isLoading)
+ return (
+
+ Loading diff...
+
+ );
+
+ if (diffQuery.data == null)
+ return (
+
+ No diff available
+
+ );
+
+ return (
+
+ );
+}
+
export function PlanDiffDialog({
deploymentId,
resultId,
title,
open,
+ initialTab = "diff",
onOpenChange,
}: PlanDiffDialogProps) {
- const [view, setView] = useState("split");
- const { theme } = useTheme();
+ const [tab, setTab] = useState(initialTab);
+ const [view, setView] = useState("unified");
- const diffQuery = trpc.deployment.plans.resultDiff.useQuery(
- { deploymentId, resultId: resultId ?? "" },
- { enabled: open && resultId != null },
- );
+ useEffect(() => {
+ if (open) setTab(initialTab);
+ }, [open, initialTab]);
return (