import React, { useMemo } from 'react'; import '@shared/styles/theme.css'; import { Badge, Expandable } from '@shared/components'; import { useToolData } from '@shared/hooks/useToolData'; import type { ValidationSummaryData, ValidationError, ValidationWarning } from '@shared/types'; interface NodeGroup { node: string; errors: ValidationError[]; warnings: ValidationWarning[]; } function SeverityBar({ errorCount, warningCount }: { errorCount: number; warningCount: number }) { const total = errorCount + warningCount; if (total === 0) { return (
All checks passed
); } const errorPct = (errorCount / total) * 100; const warningPct = (warningCount / total) * 100; return (
{errorCount > 0 && (
)} {warningCount > 0 && (
)}
{errorCount} error{errorCount !== 1 ? 's' : ''} {' · '} {warningCount} warning{warningCount !== 1 ? 's' : ''}
); } function IssueItem({ issue, variant }: { issue: ValidationError | ValidationWarning; variant: 'error' | 'warning' }) { const color = variant === 'error' ? 'var(--n8n-error)' : 'var(--n8n-warning)'; const fix = 'fix' in issue ? issue.fix : undefined; return (
{issue.message}
{issue.property && (
{issue.property}
)} {fix && (
→ {fix}
)}
); } function NodeGroupSection({ group }: { group: NodeGroup }) { const errCount = group.errors.length; const warnCount = group.warnings.length; return ( 0} >
{errCount > 0 && {errCount} error{errCount !== 1 ? 's' : ''}} {warnCount > 0 && {warnCount} warning{warnCount !== 1 ? 's' : ''}}
{group.errors.map((err, i) => ( ))} {group.warnings.map((warn, i) => ( ))}
); } export default function App() { const { data: raw, error, isConnected } = useToolData(); const inner = raw?.data || raw; const errors: ValidationError[] = inner?.errors || raw?.errors || []; const warnings: ValidationWarning[] = inner?.warnings || raw?.warnings || []; const nodeGroups = useMemo(() => { if (errors.length === 0 && warnings.length === 0) return null; const hasNodes = errors.some((e) => e.node) || warnings.some((w) => w.node); const uniqueNodes = new Set([ ...errors.filter((e) => e.node).map((e) => e.node!), ...warnings.filter((w) => w.node).map((w) => w.node!), ]); if (!hasNodes || uniqueNodes.size <= 1) return null; const groups: NodeGroup[] = []; for (const node of uniqueNodes) { groups.push({ node, errors: errors.filter((e) => e.node === node), warnings: warnings.filter((w) => w.node === node), }); } // Ungrouped items const ungroupedErrors = errors.filter((e) => !e.node); const ungroupedWarnings = warnings.filter((w) => !w.node); if (ungroupedErrors.length > 0 || ungroupedWarnings.length > 0) { groups.push({ node: 'General', errors: ungroupedErrors, warnings: ungroupedWarnings }); } // Sort: most issues first groups.sort((a, b) => (b.errors.length + b.warnings.length) - (a.errors.length + a.warnings.length)); return groups; }, [errors, warnings]); if (error) { return
Error: {error}
; } if (!isConnected) { return
Connecting...
; } if (!raw) { return
Waiting for data...
; } const valid = inner.valid ?? raw.valid ?? false; const displayName = raw.displayName || raw.data?.workflowName; const suggestions: string[] = inner?.suggestions || raw?.suggestions || []; const errorCount = raw.summary?.errorCount ?? inner?.summary?.errorCount ?? errors.length; const warningCount = raw.summary?.warningCount ?? inner?.summary?.warningCount ?? warnings.length; return (
{valid ? 'Valid' : 'Invalid'} {displayName && ( {displayName} )}
{nodeGroups ? ( nodeGroups.map((group) => ( )) ) : ( <> {errors.length > 0 && ( {errors.map((err, i) => ( ))} )} {warnings.length > 0 && ( {warnings.map((warn, i) => ( ))} )} )} {suggestions.length > 0 && (
    {suggestions.map((suggestion, i) => (
  • → {suggestion}
  • ))}
)}
); }