From ed00664882b3766ae9a5e80ad8e65c5313cbe977 Mon Sep 17 00:00:00 2001 From: Md Afiq Iskandar Date: Mon, 28 Jul 2025 11:35:38 +0800 Subject: [PATCH] Implement Node Validation System for Process Flow - Introduced a comprehensive node validation system in the process flow builder, enhancing the user experience by providing real-time feedback on node configurations and process integrity. - Added `useNodeValidation` composable to manage validation logic, including checks for required nodes, configuration completeness, and flow logic. - Integrated validation indicators in node components (ApiNode, FormNode, GatewayNode, ScriptNode) to visually represent validation issues. - Created `ValidationIndicator` and `ValidationTooltip` components for displaying validation statuses and detailed messages. - Updated `ProcessFlowCanvas.vue` to trigger validation on node and edge changes, ensuring immediate feedback during process design. - Enhanced `processBuilder` store to manage validation results and summary statistics, allowing for a centralized validation state. - Documented the validation system implementation plan to guide future enhancements and user training. --- .claude/settings.local.json | 7 +- components/process-flow/ProcessFlowCanvas.vue | 181 ++++++ .../process-flow/ValidationIndicator.vue | 175 ++++++ components/process-flow/ValidationTooltip.vue | 160 +++++ components/process-flow/custom/ApiNode.vue | 16 + components/process-flow/custom/FormNode.vue | 16 + .../process-flow/custom/GatewayNode.vue | 16 + components/process-flow/custom/ScriptNode.vue | 16 + composables/useNodeValidation.js | 589 ++++++++++++++++++ docs/node-validation-system-plan.md | 463 ++++++++++++++ stores/processBuilder.js | 101 ++- 11 files changed, 1737 insertions(+), 3 deletions(-) create mode 100644 components/process-flow/ValidationIndicator.vue create mode 100644 components/process-flow/ValidationTooltip.vue create mode 100644 composables/useNodeValidation.js create mode 100644 docs/node-validation-system-plan.md diff --git a/.claude/settings.local.json b/.claude/settings.local.json index a4af320..c8b6d3d 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,8 +1,11 @@ { "permissions": { "allow": [ - "Bash(mkdir:*)" + "Bash(mkdir:*)", + "Bash(yarn dev:*)", + "Bash(yarn lint:*)", + "Bash(yarn build:*)" ], "deny": [] } -} \ No newline at end of file +} diff --git a/components/process-flow/ProcessFlowCanvas.vue b/components/process-flow/ProcessFlowCanvas.vue index 3aba1b0..cf6cc47 100644 --- a/components/process-flow/ProcessFlowCanvas.vue +++ b/components/process-flow/ProcessFlowCanvas.vue @@ -14,6 +14,7 @@ import { Background } from "@vue-flow/background"; import { Controls } from "@vue-flow/controls"; import { MiniMap } from "@vue-flow/minimap"; import InteractiveArrowEdge from "./InteractiveArrowEdge.vue"; +import { useNodeValidation } from '~/composables/useNodeValidation'; // Import all file-based custom node components import StartNode from "~/components/process-flow/custom/StartNode.vue"; import EndNode from "~/components/process-flow/custom/EndNode.vue"; @@ -96,6 +97,16 @@ const emit = defineEmits([ // Get the flow instance const { flowInstance } = useVueFlow(); +// Initialize validation system +const { + validateProcess, + validationResults, + isValidating, + overallValidationStatus, + validationSummary, + clearValidation +} = useNodeValidation(); + // Initialize Vue Flow const { nodes, @@ -734,6 +745,27 @@ onEdgesChange((changes) => { emit("edgesChange", changes, edges.value); }); +// Watch for changes to nodes and edges to trigger validation +watch( + () => [nodes.value, edges.value], + ([currentNodes, currentEdges]) => { + if (currentNodes && currentNodes.length > 0) { + // Debounce validation to avoid excessive re-computation + clearTimeout(validationTimeout.value); + validationTimeout.value = setTimeout(() => { + validateProcess(currentNodes, currentEdges || []); + }, 300); + } else { + // Clear validation if no nodes + clearValidation(); + } + }, + { deep: true } +); + +// Timeout for debouncing validation +const validationTimeout = ref(null); + // Handle new connections const handleConnect = (connection) => { if (!connection.source || !connection.target) return; @@ -1928,6 +1960,55 @@ function fromObject(flowObject) { + + + +
+
+

Process Validation

+
+ + + {{ overallValidationStatus === 'error' ? 'Errors' : + overallValidationStatus === 'warning' ? 'Warnings' : 'Valid' }} + +
+
+ +
+
+ + {{ validationSummary.errors }} error{{ validationSummary.errors > 1 ? 's' : '' }} + + + {{ validationSummary.warnings }} warning{{ validationSummary.warnings > 1 ? 's' : '' }} + + + {{ validationSummary.infos }} info{{ validationSummary.infos > 1 ? 's' : '' }} + +
+
+ +
+ + Process validation passed +
+ +
+ Add nodes to validate process +
+ +
+ + Validating... +
+
+
@@ -2313,4 +2394,104 @@ function fromObject(flowObject) { .help-list li:last-child { margin-bottom: 0; } + +/* Validation Panel Styles */ +.validation-panel { + pointer-events: all; +} + +.validation-panel-content { + background: white; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + padding: 12px; + min-width: 220px; + max-width: 280px; +} + +.validation-title { + font-size: 12px; + font-weight: 600; + color: #374151; + margin: 0; +} + +.validation-status { + display: flex; + align-items: center; + gap: 4px; + font-size: 11px; + font-weight: 500; + padding: 2px 6px; + border-radius: 4px; +} + +.validation-status.error { + color: #dc2626; + background: #fef2f2; +} + +.validation-status.warning { + color: #d97706; + background: #fffbeb; +} + +.validation-status.valid { + color: #059669; + background: #f0fdf4; +} + +.validation-summary { + margin-top: 8px; +} + +.summary-stats { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.stat-item { + font-size: 10px; + padding: 2px 6px; + border-radius: 3px; + font-weight: 500; +} + +.stat-item.error { + color: #dc2626; + background: #fef2f2; +} + +.stat-item.warning { + color: #d97706; + background: #fffbeb; +} + +.stat-item.info { + color: #2563eb; + background: #eff6ff; +} + +.validation-success { + display: flex; + align-items: center; + gap: 6px; + margin-top: 8px; +} + +.validation-empty { + margin-top: 8px; +} + +.validation-loading { + display: flex; + align-items: center; + gap: 6px; + margin-top: 8px; +} + +.status-text { + font-size: 10px; +} diff --git a/components/process-flow/ValidationIndicator.vue b/components/process-flow/ValidationIndicator.vue new file mode 100644 index 0000000..daabd14 --- /dev/null +++ b/components/process-flow/ValidationIndicator.vue @@ -0,0 +1,175 @@ + + + + + \ No newline at end of file diff --git a/components/process-flow/ValidationTooltip.vue b/components/process-flow/ValidationTooltip.vue new file mode 100644 index 0000000..a6734bc --- /dev/null +++ b/components/process-flow/ValidationTooltip.vue @@ -0,0 +1,160 @@ + + + + + \ No newline at end of file diff --git a/components/process-flow/custom/ApiNode.vue b/components/process-flow/custom/ApiNode.vue index ce66b3e..8c52544 100644 --- a/components/process-flow/custom/ApiNode.vue +++ b/components/process-flow/custom/ApiNode.vue @@ -1,5 +1,7 @@