Skip to content

chore: opa plan validation ui#1097

Open
adityachoudhari26 wants to merge 1 commit intomainfrom
validations-ui
Open

chore: opa plan validation ui#1097
adityachoudhari26 wants to merge 1 commit intomainfrom
validations-ui

Conversation

@adityachoudhari26
Copy link
Copy Markdown
Member

@adityachoudhari26 adityachoudhari26 commented May 1, 2026

Summary by CodeRabbit

  • New Features
    • Added a validations tab to the plan comparison dialog, displaying validation results with pass/fail status and violation messages.
    • Plan results table now shows passed and failed validation counts.
    • Plan result rows are now clickable to view both diff and validation details with tab-based navigation.

Copilot AI review requested due to automatic review settings May 1, 2026 18:24
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 1, 2026

📝 Walkthrough

Walkthrough

This PR introduces a validation-focused UI layer to plan results. The TRPC backend now aggregates validation statistics per plan result and provides a dedicated endpoint for detailed validations. The frontend adds a second tab to the plan diff dialog for viewing validation results with pass/fail badges, and a validations cell in the results table that displays counts and enables navigation to the new validations tab.

Changes

Cohort / File(s) Summary
Plan Diff Dialog
apps/web/app/routes/ws/deployments/_components/plans/PlanDiffDialog.tsx
Transforms dialog into a two-tab interface with TopTab state and initialTab prop. Adds DiffTab (Monaco YAML diff) and ValidationsTab (plan validations with pass/fail badges). Includes Split/Unified toggle visible only on diff tab. Tab state resets on dialog reopen.
Plan Results Page
apps/web/app/routes/ws/deployments/page.$deploymentId.plans.$planId.tsx
Adds ValidationsCell displaying failed/passed validation counts. Extends row clickability to include cases with validations.total > 0. Replaces onViewDiff callback with generalized onOpenResult(resultId, tab) to control which tab opens in the dialog.
Deployment Plans Router
packages/trpc/src/routes/deployment-plans.ts
Extends results endpoint to include precomputed validation statistics (validations: { total, passed, failed }). Adds new resultValidations endpoint for fetching detailed validation entries per result, including rule metadata with fallbacks for missing descriptions.

Sequence Diagram

sequenceDiagram
    participant User
    participant Table as Plan Results Table
    participant Dialog as PlanDiffDialog
    participant TRPCClient as TRPC Client
    participant Backend as Backend API

    User->>Table: Click validations cell
    Table->>TRPCClient: onOpenResult(resultId, "validations")
    TRPCClient->>Backend: resultValidations({ deploymentId, resultId })
    Backend->>Backend: Fetch validation entries & rule metadata
    Backend-->>TRPCClient: Validation details + descriptions
    TRPCClient->>Dialog: Open with initialTab="validations"
    Dialog->>Dialog: Render ValidationsTab with badges & messages
    Dialog-->>User: Display validation results

    User->>Table: Click row (hasChanges or validations.total > 0)
    Table->>TRPCClient: onOpenResult(resultId, "diff")
    TRPCClient->>Backend: results({ deploymentId })
    Backend-->>TRPCClient: Plan diff data
    TRPCClient->>Dialog: Open with initialTab="diff"
    Dialog->>Dialog: Render DiffTab with Monaco YAML diff
    Dialog-->>User: Display diff with Split/Unified toggle
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 Tabs multiply like clover in spring,
Validations bloom with badges that sing,
Pass and fail counts now shine so bright,
Diffs and checks side-by-side, what a sight!
The dialog dances, data flows deep,
Two roads to truth for you to keep! 🌿✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'chore: opa plan validation ui' is partially related to the changeset. It references 'validation ui' which is a real component added, but it understates the scope by categorizing as a 'chore' when the changes involve significant new functionality (two-tab dialog, validations display, new API endpoint). Consider a more descriptive title like 'feat: add plan validation results UI with diff and validations tabs' to better reflect the scope and impact of these changes.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch validations-ui

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds plan validation visibility (OPA plan validation) to the deployment plan results UI by returning validation counts with plan results and exposing a new resultValidations endpoint to fetch per-result validation details for display in the diff dialog.

Changes:

  • Add aggregated validation counts to deployment.plans.results responses.
  • Add a new deployment.plans.resultValidations TRPC query for per-result validation details (rule name/description, pass/fail, violations).
  • Update the plan results UI to show a “Validations” column and add a “Validations” tab to the plan diff dialog.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
packages/trpc/src/routes/deployment-plans.ts Adds validation counts to plan results and a new resultValidations query.
apps/web/app/routes/ws/deployments/page.$deploymentId.plans.$planId.tsx Displays validation counts in the results table and wires opening the dialog to specific tabs.
apps/web/app/routes/ws/deployments/_components/plans/PlanDiffDialog.tsx Adds a “Validations” tab that fetches and renders rule evaluation results alongside the diff.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +135 to +142
const hasValidations = result.validations.total > 0;
const isClickable = result.hasChanges === true || hasValidations;
return (
<TableRow
className={cn("hover:bg-muted/50", isClickable && "cursor-pointer")}
onClick={isClickable ? () => onViewDiff(result.resultId) : undefined}
onClick={
isClickable ? () => onOpenResult(result.resultId, "diff") : undefined
}
Comment on lines 138 to +142
<TableRow
className={cn("hover:bg-muted/50", isClickable && "cursor-pointer")}
onClick={isClickable ? () => onViewDiff(result.resultId) : undefined}
onClick={
isClickable ? () => onOpenResult(result.resultId, "diff") : undefined
}
Comment on lines +89 to +90
{validation.violations.map((violation, i) => (
<li key={i} className="font-mono text-red-600 dark:text-red-400">
Comment on lines +293 to +308
const owner = await ctx.db
.select({ deploymentId: schema.deploymentPlan.deploymentId })
.from(schema.deploymentPlanTargetResult)
.innerJoin(
schema.deploymentPlanTarget,
eq(
schema.deploymentPlanTargetResult.targetId,
schema.deploymentPlanTarget.id,
),
)
.innerJoin(
schema.deploymentPlan,
eq(schema.deploymentPlanTarget.planId, schema.deploymentPlan.id),
)
.where(eq(schema.deploymentPlanTargetResult.id, input.resultId))
.then(takeFirstOrNull);
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
apps/web/app/routes/ws/deployments/page.$deploymentId.plans.$planId.tsx (1)

81-87: Convert function components to typed React.FC declarations.

The file contains 6 function declarations that are React components. Per codebase conventions, these should use React.FC with explicit typing: const ComponentName: React.FC<Props> = ({ prop }) => {}.

Affected components:

  • DiffStats (line 45)
  • ChangesCell (line 68)
  • ValidationsCell (line 81)
  • ResultsTableHeader (line 113)
  • ResultsTableRow (line 128)
  • NoResults (line 163)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/app/routes/ws/deployments/page`.$deploymentId.plans.$planId.tsx
around lines 81 - 87, Convert the listed function components to typed React.FC
declarations: replace each function declaration (DiffStats, ChangesCell,
ValidationsCell, ResultsTableHeader, ResultsTableRow, NoResults) with a const
arrow component using explicit props typing, e.g. const ComponentName:
React.FC<Props> = ({ ... }) => { ... }; ensure you reuse the same prop types
(e.g. the existing Result["validations"] type for ValidationsCell) and preserve
prop names and implementation body, export behavior and any default props or
handlers (like onClick) unchanged.
apps/web/app/routes/ws/deployments/_components/plans/PlanDiffDialog.tsx (1)

18-25: Consider adopting React.FC typing and interface for props to align with documented conventions.

While the code works correctly, the repository style guide documents preferences for React.FC-typed exported components and interface for public props. However, note that the codebase currently uses export function declarations as the dominant pattern across similar components, suggesting these guidelines are aspirational rather than uniformly enforced. Adopt these patterns if the team intends to standardize on them project-wide, or deprioritize if functional component declarations remain the preferred convention.

Affected segments: PlanDiffDialogProps (line 18), ValidationsTab (line 33), ValidationItem (line 73), DiffTab (line 100), PlanDiffDialog (line 156).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/app/routes/ws/deployments/_components/plans/PlanDiffDialog.tsx`
around lines 18 - 25, Change the exported components to use React.FC and convert
the public props type aliases into interfaces: replace the type alias
PlanDiffDialogProps with interface PlanDiffDialogProps and update
ValidationsTab, ValidationItem, DiffTab and PlanDiffDialog function declarations
to use React.FC<...> (e.g., const PlanDiffDialog: React.FC<PlanDiffDialogProps>
or export const PlanDiffDialog: React.FC<PlanDiffDialogProps>) so props are
typed via the new interfaces; keep the same prop names and signatures (resultId,
deploymentId, title, open, initialTab, onOpenChange) and ensure exported names
(ValidationsTab, ValidationItem, DiffTab, PlanDiffDialog) still match their
existing exports.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/app/routes/ws/deployments/page`.$deploymentId.plans.$planId.tsx:
- Around line 138-142: The TableRow currently uses onClick for navigation which
is pointer-only; make rows keyboard-accessible by, when isClickable is true,
adding focusability (e.g., tabIndex={0}), a proper semantic role (e.g.,
role="button" or adjust per semantics) and an onKeyDown handler that listens for
Enter and Space and calls onOpenResult(result.resultId, "diff") (for Space
ensure preventDefault to avoid scrolling). Update the TableRow props where
isClickable is used so it conditionally adds tabIndex, role, onKeyDown, and
keeps the existing onClick and cursor/focus styles so keyboard users can both
focus and activate the row.

---

Nitpick comments:
In `@apps/web/app/routes/ws/deployments/_components/plans/PlanDiffDialog.tsx`:
- Around line 18-25: Change the exported components to use React.FC and convert
the public props type aliases into interfaces: replace the type alias
PlanDiffDialogProps with interface PlanDiffDialogProps and update
ValidationsTab, ValidationItem, DiffTab and PlanDiffDialog function declarations
to use React.FC<...> (e.g., const PlanDiffDialog: React.FC<PlanDiffDialogProps>
or export const PlanDiffDialog: React.FC<PlanDiffDialogProps>) so props are
typed via the new interfaces; keep the same prop names and signatures (resultId,
deploymentId, title, open, initialTab, onOpenChange) and ensure exported names
(ValidationsTab, ValidationItem, DiffTab, PlanDiffDialog) still match their
existing exports.

In `@apps/web/app/routes/ws/deployments/page`.$deploymentId.plans.$planId.tsx:
- Around line 81-87: Convert the listed function components to typed React.FC
declarations: replace each function declaration (DiffStats, ChangesCell,
ValidationsCell, ResultsTableHeader, ResultsTableRow, NoResults) with a const
arrow component using explicit props typing, e.g. const ComponentName:
React.FC<Props> = ({ ... }) => { ... }; ensure you reuse the same prop types
(e.g. the existing Result["validations"] type for ValidationsCell) and preserve
prop names and implementation body, export behavior and any default props or
handlers (like onClick) unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d1d7acca-3acb-46cc-b26a-7669a6f271f8

📥 Commits

Reviewing files that changed from the base of the PR and between 6742277 and c3d18d5.

📒 Files selected for processing (3)
  • apps/web/app/routes/ws/deployments/_components/plans/PlanDiffDialog.tsx
  • apps/web/app/routes/ws/deployments/page.$deploymentId.plans.$planId.tsx
  • packages/trpc/src/routes/deployment-plans.ts

Comment on lines 138 to +142
<TableRow
className={cn("hover:bg-muted/50", isClickable && "cursor-pointer")}
onClick={isClickable ? () => onViewDiff(result.resultId) : undefined}
onClick={
isClickable ? () => onOpenResult(result.resultId, "diff") : undefined
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make clickable rows keyboard-accessible.

onClick on <TableRow> makes this interaction pointer-only. Add keyboard activation (Enter/Space) and focusability for isClickable rows.

♿ Suggested fix
     <TableRow
       className={cn("hover:bg-muted/50", isClickable && "cursor-pointer")}
+      role={isClickable ? "button" : undefined}
+      tabIndex={isClickable ? 0 : undefined}
       onClick={
         isClickable ? () => onOpenResult(result.resultId, "diff") : undefined
       }
+      onKeyDown={
+        isClickable
+          ? (e) => {
+              if (e.key === "Enter" || e.key === " ") {
+                e.preventDefault();
+                onOpenResult(result.resultId, "diff");
+              }
+            }
+          : undefined
+      }
     >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<TableRow
className={cn("hover:bg-muted/50", isClickable && "cursor-pointer")}
onClick={isClickable ? () => onViewDiff(result.resultId) : undefined}
onClick={
isClickable ? () => onOpenResult(result.resultId, "diff") : undefined
}
<TableRow
className={cn("hover:bg-muted/50", isClickable && "cursor-pointer")}
role={isClickable ? "button" : undefined}
tabIndex={isClickable ? 0 : undefined}
onClick={
isClickable ? () => onOpenResult(result.resultId, "diff") : undefined
}
onKeyDown={
isClickable
? (e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
onOpenResult(result.resultId, "diff");
}
}
: undefined
}
>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/app/routes/ws/deployments/page`.$deploymentId.plans.$planId.tsx
around lines 138 - 142, The TableRow currently uses onClick for navigation which
is pointer-only; make rows keyboard-accessible by, when isClickable is true,
adding focusability (e.g., tabIndex={0}), a proper semantic role (e.g.,
role="button" or adjust per semantics) and an onKeyDown handler that listens for
Enter and Space and calls onOpenResult(result.resultId, "diff") (for Space
ensure preventDefault to avoid scrolling). Update the TableRow props where
isClickable is used so it conditionally adds tabIndex, role, onKeyDown, and
keeps the existing onClick and cursor/focus styles so keyboard users can both
focus and activate the row.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants