# Process Builder Technical Appendix This document provides technical implementation details for developers working with the Process Builder system. > For user documentation and usage guidelines, please refer to [Process Builder Documentation](USER_GUIDE.md) ## Recent Updates (December 2024) ### Critical Bug Fixes and Enhancements - **Process Definition Loading**: Fixed issue where processes with empty nodes array but nodes embedded in edges wouldn't display on canvas - **URL Parameter Support**: Added direct linking to processes via `/process-builder?id=uuid` pattern - **Save Functionality**: Enhanced with success/error messages and proper state management - **Navigation State**: Fixed unsaved changes modal appearing after successful saves - **Connection Dragging**: Resolved Vue Flow interference with connector dragging functionality - **Database Integration**: Full API integration with comprehensive error handling and validation - **Toast Notifications**: Implemented user feedback system for all operations - **Form Builder Consistency**: Updated form builder manage page to match process builder design ### Breaking Changes - Process store now requires API integration for all operations - Local state has been eliminated in favor of database-driven architecture - URL parameters are now required for process editing workflows ## Architecture Overview ### Technology Stack - **Frontend Framework**: Nuxt 3 / Vue 3 - **State Management**: Pinia - **Flow Visualization**: Vue Flow - **UI Framework**: Tailwind CSS - **Icons**: Material Design Icons - **Validation**: Zod - **Form Components**: FormKit ### Key Dependencies ```json { "@vue-flow/core": "^1.42.5", "@vue-flow/background": "^1.3.2", "@vue-flow/controls": "^1.1.2", "@vue-flow/minimap": "^1.5.3", "@pinia/nuxt": "^0.4.11", "@formkit/nuxt": "^1.5.5", "uuid": "^10.0.0", "zod": "^3.22.2" } ``` ## Project Structure ``` pages/ ├── process-builder/ │ ├── index.vue # Main builder interface │ └── manage.vue # Process management components/ ├── process-flow/ │ ├── ProcessFlowCanvas.vue # Flow canvas │ ├── ProcessFlowNodes.js # Custom node types │ ├── FormSelector.vue # Form selection component │ ├── GatewayConditionManager.vue # Gateway conditions UI │ ├── ApiNodeConfiguration.vue # API node configuration │ ├── FormNodeConfiguration.vue # Form node configuration │ ├── BusinessRuleConfiguration.vue # Business rule configuration │ └── VariableManager.vue # Process variables manager stores/ ├── processBuilder.js # State management └── variableStore.js # Variable state management composables/ └── useProcessValidation.js # Process validation types/ └── process-builder.d.ts # TypeScript definitions ``` ## URL Parameter System The Process Builder now supports direct linking to specific processes via URL parameters, enabling seamless navigation and bookmarking. ### Implementation #### Route Handling ```javascript // pages/process-builder/index.vue const route = useRoute(); const router = useRouter(); // Watch for URL parameter changes watch(() => route.query.id, async (newId, oldId) => { if (newId && newId !== oldId) { try { await loadProcessFromUrl(newId); } catch (error) { console.error('Error loading process from URL:', error); // Redirect to clean state on error router.push('/process-builder'); } } }, { immediate: true }); // Load process from URL parameter const loadProcessFromUrl = async (processId) => { if (!processId || processId === 'new') return; try { setLoading(true); await processStore.loadProcess(processId); if (!processStore.currentProcess) { throw new Error('Process not found'); } // Update URL without triggering navigation await router.replace({ path: '/process-builder', query: { id: processId } }); } catch (error) { console.error('Failed to load process:', error); toast.error('Failed to load process: ' + (error.message || 'Unknown error')); // Clear invalid URL parameter await router.replace('/process-builder'); } finally { setLoading(false); } }; ``` #### Navigation Updates ```javascript // Create new process with URL update const createNewProcess = async () => { try { processStore.clearProcess(); // Navigate to clean URL for new process await router.push('/process-builder'); } catch (error) { console.error('Error creating new process:', error); } }; // Save process with URL synchronization const saveProcess = async () => { try { const result = await processStore.saveProcess(); if (result && result.id) { // Update URL with saved process ID await router.replace({ path: '/process-builder', query: { id: result.id } }); toast.success('Process saved successfully'); } } catch (error) { toast.error('Failed to save process'); } }; ``` ### URL Patterns - **New Process**: `/process-builder` (no parameters) - **Edit Process**: `/process-builder?id={uuid}` - **Navigation**: Automatic URL updates when saving new processes - **Validation**: Invalid IDs redirect to clean builder state ### Error Handling - **Invalid Process ID**: Graceful fallback to new process state - **Network Errors**: User-friendly error messages with toast notifications - **Missing Processes**: Automatic cleanup of invalid URL parameters - **Loading States**: Visual feedback during process loading ### Integration Points - **Process Management**: Direct links from manage page to builder - **Form Builder**: Consistent URL pattern across builders - **Navigation Guards**: Unsaved changes detection with URL awareness - **Bookmarking**: Users can bookmark specific processes for quick access ## Database Integration & API System The Process Builder now features comprehensive database integration with a RESTful API system, replacing local state management with persistent storage. ### API Endpoints #### Core Process Operations ```javascript // GET /api/process - List all processes with pagination GET /api/process?page=1&limit=10&search=workflow&status=draft // GET /api/process/[id] - Get specific process GET /api/process/550e8400-e29b-41d4-a716-446655440000 // POST /api/process - Create new process POST /api/process { "name": "New Workflow", "description": "Process description", "processDefinition": { nodes: [], edges: [] }, "processVariables": [], "isTemplate": false } // PUT /api/process/[id] - Update existing process PUT /api/process/550e8400-e29b-41d4-a716-446655440000 { "name": "Updated Workflow", "processDefinition": { /* updated definition */ } } // DELETE /api/process/[id] - Delete process DELETE /api/process/550e8400-e29b-41d4-a716-446655440000 ``` #### Advanced Operations ```javascript // POST /api/process/[id]/duplicate - Duplicate process POST /api/process/550e8400-e29b-41d4-a716-446655440000/duplicate { "name": "Workflow Copy", "regenerateIds": true } // POST /api/process/[id]/publish - Publish process POST /api/process/550e8400-e29b-41d4-a716-446655440000/publish { "version": "1.0.0", "notes": "Initial release" } // GET /api/process/templates - Get process templates GET /api/process/templates ``` ### Process Store Integration #### Enhanced Store Methods ```javascript // stores/processBuilder.js export const useProcessBuilderStore = defineStore('processBuilder', () => { // Load process from API with error handling const loadProcess = async (processId) => { try { loading.value = true; const { data } = await $fetch(`/api/process/${processId}`); if (!data) { throw new Error('Process not found'); } currentProcess.value = data; // Handle backward compatibility for process definitions if (data.processDefinition) { if (data.processDefinition.nodes?.length === 0 && data.processDefinition.edges?.length > 0) { // Extract nodes from edges for backward compatibility const extractedNodes = extractNodesFromEdges(data.processDefinition.edges); nodes.value = extractedNodes; edges.value = data.processDefinition.edges; } else { nodes.value = data.processDefinition.nodes || []; edges.value = data.processDefinition.edges || []; } } return data; } catch (error) { console.error('Error loading process:', error); throw error; } finally { loading.value = false; } }; // Save process with validation and success feedback const saveProcess = async () => { try { loading.value = true; const processData = { name: localProcess.value.name, description: localProcess.value.description, processDefinition: { nodes: nodes.value, edges: edges.value }, processVariables: processVariables.value, processSettings: { // Process configuration settings processType: localProcess.value.processType, priority: localProcess.value.priority, category: localProcess.value.category, timeoutDuration: localProcess.value.timeoutDuration, allowParallel: localProcess.value.allowParallel, enableErrorRecovery: localProcess.value.enableErrorRecovery, sendNotifications: localProcess.value.sendNotifications } }; let result; if (currentProcess.value?.id) { // Update existing process result = await $fetch(`/api/process/${currentProcess.value.id}`, { method: 'PUT', body: processData }); } else { // Create new process result = await $fetch('/api/process', { method: 'POST', body: processData }); } if (result?.data) { currentProcess.value = result.data; hasUnsavedChanges.value = false; lastSavedState.value = JSON.stringify(processData); return result.data; } throw new Error('Save operation failed'); } catch (error) { console.error('Error saving process:', error); throw error; } finally { loading.value = false; } }; // Fetch all processes with filtering const fetchProcesses = async (options = {}) => { try { const params = new URLSearchParams({ page: options.page || 1, limit: options.limit || 20, ...(options.search && { search: options.search }), ...(options.status && { status: options.status }) }); const response = await $fetch(`/api/process?${params}`); processes.value = response.data || []; return response; } catch (error) { console.error('Error fetching processes:', error); throw error; } }; return { // State currentProcess: readonly(currentProcess), processes: readonly(processes), loading: readonly(loading), hasUnsavedChanges: readonly(hasUnsavedChanges), // Actions loadProcess, saveProcess, fetchProcesses, clearProcess, duplicateProcess, deleteProcess }; }); ``` ### Backward Compatibility #### Process Definition Loading ```javascript // Handle legacy process definitions with embedded nodes in edges const extractNodesFromEdges = (edges) => { const nodeMap = new Map(); edges.forEach(edge => { // Extract source node if (edge.sourceNode && !nodeMap.has(edge.source)) { nodeMap.set(edge.source, { id: edge.source, type: edge.sourceNode.type, position: edge.sourceNode.position || { x: 0, y: 0 }, data: edge.sourceNode.data || {} }); } // Extract target node if (edge.targetNode && !nodeMap.has(edge.target)) { nodeMap.set(edge.target, { id: edge.target, type: edge.targetNode.type, position: edge.targetNode.position || { x: 0, y: 0 }, data: edge.targetNode.data || {} }); } }); return Array.from(nodeMap.values()); }; ``` ### Error Handling & Validation #### API Error Responses ```javascript // Standardized error response format { "success": false, "error": { "code": "VALIDATION_ERROR", "message": "Process name is required", "details": { "field": "name", "value": "", "constraint": "required" } } } // Network error handling try { await processStore.saveProcess(); } catch (error) { if (error.statusCode === 404) { toast.error('Process not found'); } else if (error.statusCode === 422) { toast.error('Validation error: ' + error.data?.error?.message); } else { toast.error('An unexpected error occurred'); } } ``` #### Data Validation ```javascript // Process validation using Zod schemas import { z } from 'zod'; const ProcessSchema = z.object({ name: z.string().min(1, 'Process name is required'), description: z.string().optional(), processDefinition: z.object({ nodes: z.array(z.any()), edges: z.array(z.any()) }), processVariables: z.array(z.any()).default([]), isTemplate: z.boolean().default(false) }); // Validate before save const validateProcess = (processData) => { try { return ProcessSchema.parse(processData); } catch (error) { throw new Error(`Validation failed: ${error.message}`); } }; ``` ## Vue Flow Integration & Performance Fixes Critical fixes were implemented to resolve interference between state management and Vue Flow's internal operations, particularly affecting connection dragging functionality. ### Connection Dragging Bug Fix #### Problem The aggressive syncing of node positions and edge updates was interfering with Vue Flow's native drag-and-drop functionality, causing connections to fail when dragging from node handles. #### Solution ```javascript // stores/processBuilder.js - Optimized node sync handling const syncNodePositions = (vueFlowNodes) => { if (!vueFlowNodes || dragging.value) return; // Skip sync during dragging const positionsChanged = vueFlowNodes.some(vfNode => { const storeNode = nodes.value.find(n => n.id === vfNode.id); if (!storeNode) return false; return Math.abs(storeNode.position.x - vfNode.position.x) > 1 || Math.abs(storeNode.position.y - vfNode.position.y) > 1; }); if (positionsChanged) { vueFlowNodes.forEach(vfNode => { const nodeIndex = nodes.value.findIndex(n => n.id === vfNode.id); if (nodeIndex !== -1) { nodes.value[nodeIndex].position = { ...vfNode.position }; } }); } }; // Enhanced edge handling with change detection const handleEdgeChanges = (changes, currentEdges) => { if (!changes || changes.length === 0) return; let hasChanges = false; changes.forEach(change => { if (change.type === 'add' && change.item) { // Only add if it doesn't already exist const exists = edges.value.some(e => e.id === change.item.id); if (!exists) { addEdge(change.item); hasChanges = true; } } else if (change.type === 'remove') { const index = edges.value.findIndex(e => e.id === change.id); if (index !== -1) { edges.value.splice(index, 1); hasChanges = true; } } }); if (hasChanges) { markUnsavedChanges(); } }; ``` #### Canvas Component Updates ```vue ``` ### Performance Optimizations #### Reduced Re-renders ```javascript // Computed properties for reactive data binding const flowNodes = computed({ get: () => processStore.nodes, set: (newNodes) => { if (!isDragging.value) { processStore.updateNodes(newNodes); } } }); const flowEdges = computed({ get: () => processStore.edges, set: (newEdges) => { processStore.updateEdges(newEdges); } }); // Debounced position sync for smooth dragging import { debounce } from 'lodash-es'; const debouncedSync = debounce((nodes) => { processStore.syncNodePositions(nodes); }, 100); ``` #### Memory Management ```javascript // Cleanup watchers and event listeners onBeforeUnmount(() => { // Clear any pending debounced calls debouncedSync.cancel(); // Reset dragging state processStore.setDragging(false); // Clear selections if (vueFlowInstance.value) { vueFlowInstance.value.setSelectedNodes([]); vueFlowInstance.value.setSelectedEdges([]); } }); ``` ### State Synchronization #### Bidirectional Data Flow ```javascript // Process Store - Enhanced state management export const useProcessBuilderStore = defineStore('processBuilder', () => { const dragging = ref(false); const setDragging = (value) => { dragging.value = value; }; const updateNodes = (newNodes) => { if (!dragging.value) { nodes.value = newNodes.map(node => ({ ...node, position: { ...node.position } })); markUnsavedChanges(); } }; const updateEdges = (newEdges) => { edges.value = newEdges.map(edge => ({ ...edge })); markUnsavedChanges(); }; // Smart change detection const markUnsavedChanges = () => { const currentState = JSON.stringify({ nodes: nodes.value, edges: edges.value, variables: processVariables.value }); if (currentState !== lastSavedState.value) { hasUnsavedChanges.value = true; } }; return { // State dragging: readonly(dragging), // Actions setDragging, updateNodes, updateEdges, syncNodePositions, handleNodeChanges, handleEdgeChanges }; }); ``` ## Component Architecture ### Core Components 1. **ProcessFlowCanvas.vue** ```vue ``` 2. **ProcessFlowNodes.js** ```javascript import { h, markRaw } from 'vue'; import { Handle, Position } from '@vue-flow/core'; // Enhanced custom node renderer with 4-point connection system const CustomNode = markRaw({ template: `
{{ label }}
`, props: ['id', 'type', 'label', 'data'], components: { Handle }, mounted() { // Add event listeners for handle visibility this.$el.addEventListener('mouseenter', this.showHandles); this.$el.addEventListener('mouseleave', this.hideHandles); }, methods: { showHandles() { const handles = this.$el.querySelectorAll('.vue-flow__handle'); handles.forEach(handle => { handle.style.opacity = '1'; }); }, hideHandles() { const handles = this.$el.querySelectorAll('.vue-flow__handle'); handles.forEach(handle => { handle.style.opacity = '0'; }); } } }); // Enhanced CSS for handles const handleStyles = ` .custom-node:hover .vue-flow__handle { opacity: 1 !important; z-index: 100; } .vue-flow__handle { opacity: 0; transition: all 0.2s ease; cursor: crosshair; border: 2px solid; border-radius: 50%; } .handle-input { background-color: #3b82f6; border-color: #1d4ed8; } .handle-output { background-color: #10b981; border-color: #059669; } .vue-flow__handle:hover { transform: scale(1.2); box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3); } .custom-node.connecting .vue-flow__handle { opacity: 1; } /* Special handling for gateway nodes (rotated) */ .node-gateway .vue-flow__handle { position: absolute; } .node-gateway .vue-flow__handle[data-handlepos="top"] { top: -6px; left: 50%; transform: translate(-50%, -50%) rotate(-45deg); } .node-gateway .vue-flow__handle[data-handlepos="right"] { right: -6px; top: 50%; transform: translate(50%, -50%) rotate(-45deg); } .node-gateway .vue-flow__handle[data-handlepos="bottom"] { bottom: -6px; left: 50%; transform: translate(-50%, 50%) rotate(-45deg); } .node-gateway .vue-flow__handle[data-handlepos="left"] { left: -6px; top: 50%; transform: translate(-50%, -50%) rotate(-45deg); } `; // Node type definitions with enhanced handle system export const nodeTypes = markRaw({ task: TaskNode, start: StartNode, end: EndNode, gateway: GatewayNode, form: FormNode, api: ApiNode, script: ScriptNode, 'business-rule': BusinessRuleNode, notification: NotificationNode }); // Connection validation export const isValidConnection = (connection) => { // Prevent self-connections if (connection.source === connection.target) { return false; } // Validate handle types const sourceHandle = connection.sourceHandle; const targetHandle = connection.targetHandle; // Ensure proper source/target handle types if (sourceHandle && !sourceHandle.includes('right') && !sourceHandle.includes('bottom')) { return false; } if (targetHandle && !targetHandle.includes('top') && !targetHandle.includes('left')) { return false; } return true; }; // Enhanced connection handler with handle-specific routing export const onConnect = (params) => { const { source, target, sourceHandle, targetHandle } = params; return { id: `${source}-${target}-${Date.now()}`, source, target, sourceHandle, targetHandle, type: 'smoothstep', animated: true, style: { strokeWidth: 2, stroke: '#64748b' }, data: { sourcePosition: sourceHandle?.split('-')[1] || 'bottom', targetPosition: targetHandle?.split('-')[1] || 'top' } }; }; #### Handle System Features 1. **4-Point Connection System**: - **Top Handle**: Primary input connection point (blue, target) - **Right Handle**: Secondary output connection point (green, source) - **Bottom Handle**: Primary output connection point (green, source) - **Left Handle**: Secondary input connection point (blue, target) 2. **Enhanced Visibility**: - Handles are invisible by default for clean UI - Become visible on node hover with smooth transitions - Unique IDs for precise connection targeting (`nodeId-position`) 3. **Visual Feedback**: - Color-coded handles (blue for inputs, green for outputs) - Hover effects with scaling and shadow - Connection state awareness 4. **Special Node Handling**: - **Start Nodes**: Only output handles (right + bottom) - **End Nodes**: Only input handles (top + left) - **Gateway Nodes**: Rotated handle positioning for diamond shape 5. **Connection Validation**: - Prevents self-connections - Validates proper source/target handle types - Ensures logical connection flow ## Enhanced Node Configuration Components The following components implement the improved UI/UX for node configuration: ### 1. VariableManager.vue The Variable Manager allows users to create, edit, and manage global process variables. ```vue ``` ### 2. BusinessRuleConfiguration.vue The Business Rule Configuration component provides a stepped workflow for configuring rule nodes. ```vue ### 3. GatewayConditionManager.vue The Gateway Condition Manager provides an enhanced UI for decision point configuration. ```vue ``` ### 4. ApiNodeConfiguration.vue The API Node Configuration component provides a stepped interface for configuring API calls. ```vue ``` ### 5. FormNodeConfiguration.vue The Form Node Configuration component provides a comprehensive 3-step interface for configuring form interactions with enhanced data mapping and conditional field behavior. #### Architecture Overview ```vue ``` #### Script Implementation ```javascript ``` #### Key Features 1. **3-Step Configuration Workflow**: - **Step 1**: Form selection with integrated FormSelector component - **Step 2**: Bidirectional data mapping between process variables and form fields - **Step 3**: Dynamic field conditions for runtime form behavior 2. **Input/Output Mappings**: - **Input Mappings**: Map process variables to form fields for pre-filling - **Output Mappings**: Capture form submission data in process variables - **Auto-Variable Creation**: Automatically create process variables from form fields - **FormKit Integration**: Seamless dropdown selection with proper v-model binding 3. **Field Conditions**: - **Conditional Logic**: Support for 9 different operators (equals, contains, greater than, etc.) - **Multiple Actions**: readonly, hide, show, required, optional, enable - **Process Variable Integration**: Conditions based on current process state - **Real-time Updates**: Dynamic form behavior during process execution 4. **Data Persistence**: - **Deep Copying**: Proper reactivity management to prevent data corruption - **Explicit Save**: Manual save mechanism with `saveAllChanges()` function exposed via `defineExpose` - **Change Tracking**: Reliable change detection and persistence 5. **API Integration**: - **Form Field Loading**: Dynamic loading of form fields via `/api/forms/{formId}/fields` - **Error Handling**: Comprehensive error handling for API failures - **Loading States**: Proper loading state management #### Component Integration The FormNodeConfiguration component integrates with: - **FormSelector.vue**: For form selection and browsing - **VariableStore**: For process variable management and creation - **FormNodeConfigurationModal.vue**: Modal wrapper with save/cancel functionality - **ProcessBuilder**: Main process builder integration #### Data Flow ``` 1. User selects form → handleFormSelection() → loadFormFields() → Update localNodeData 2. User adds mappings → addInputMapping()/addOutputMapping() → Update arrays → saveChanges() 3. User configures conditions → addFieldCondition() → Update conditions array → saveChanges() 4. User clicks Save → FormNodeConfigurationModal calls saveAllChanges() → emit('update', data) 5. Parent receives update → Process node data is persisted ``` This architecture ensures reliable data persistence, proper reactivity management, and seamless integration with the broader process builder system. ## State Management The project uses Pinia for state management. Key stores include: ### processBuilder.js Enhanced with Settings Management The Process Builder store has been enhanced to handle comprehensive process settings: ```javascript export const useProcessBuilderStore = defineStore('processBuilder', { state: () => ({ processes: [], currentProcess: null, selectedNodeId: null, selectedEdgeId: null, history: [], historyIndex: -1, unsavedChanges: false }), actions: { /** * Update the current process with new data including settings */ updateCurrentProcess(processUpdates) { if (!this.currentProcess) return; this.currentProcess = { ...this.currentProcess, ...processUpdates, updatedAt: new Date().toISOString() }; this.unsavedChanges = true; this.saveToHistory('Update process settings'); }, /** * Enhanced save process with settings persistence */ async saveProcess() { if (!this.currentProcess) return; try { // Save process data including all settings const processData = { ...this.currentProcess, variables: useVariableStore().getAllVariables.process, // Settings are now part of the process object structure settings: this.currentProcess.settings || {} }; // TODO: Implement API call to save process with settings const index = this.processes.findIndex(p => p.id === this.currentProcess.id); if (index !== -1) { this.processes[index] = processData; } else { this.processes.push(processData); } this.unsavedChanges = false; return true; } catch (error) { console.error('Error saving process:', error); return false; } } } }); ``` ### Process Settings Data Structure The enhanced process object now includes comprehensive settings: ```typescript interface ProcessSettings { // Process Info priority: 'low' | 'normal' | 'high' | 'critical'; category: string; owner: string; // Execution Settings processType: 'standard' | 'approval' | 'data_collection' | 'automation' | 'review'; maxExecutionTime: number; // minutes autoTimeout: number; // hours allowParallel: boolean; enableErrorRecovery: boolean; sendNotifications: boolean; // Data & Variables dataPersistence: 'session' | 'temporary' | 'short_term' | 'long_term' | 'permanent'; logVariableChanges: boolean; encryptSensitiveData: boolean; dataRetentionPolicy: string; // Permissions executionPermission: 'public' | 'authenticated' | 'roles' | 'managers' | 'admin'; allowedRoles: string; modificationPermission: 'owner' | 'managers' | 'admin' | 'editors'; requireApproval: boolean; enableAuditTrail: boolean; } interface EnhancedProcess { id: string; name: string; description: string; nodes: ProcessNode[]; edges: ProcessEdge[]; variables: Record; settings: ProcessSettings; createdAt: string; updatedAt: string; } ``` ## Process Settings Implementation ### ProcessSettingsModal.vue Component Architecture The Process Settings modal is implemented as a comprehensive tabbed interface: ```vue ``` ### JSON Export Functionality The JSON export feature provides comprehensive process configuration export: ```javascript // Complete export data structure const formattedJson = computed(() => { const exportData = { processInfo: { id: localProcess.value.id, name: localProcess.value.name, description: localProcess.value.description, priority: localProcess.value.priority, category: localProcess.value.category, owner: localProcess.value.owner }, settings: { processType: localProcess.value.processType, maxExecutionTime: localProcess.value.maxExecutionTime, autoTimeout: localProcess.value.autoTimeout, allowParallel: localProcess.value.allowParallel, enableErrorRecovery: localProcess.value.enableErrorRecovery, sendNotifications: localProcess.value.sendNotifications }, dataSettings: { dataPersistence: localProcess.value.dataPersistence, logVariableChanges: localProcess.value.logVariableChanges, encryptSensitiveData: localProcess.value.encryptSensitiveData, dataRetentionPolicy: localProcess.value.dataRetentionPolicy }, permissions: { executionPermission: localProcess.value.executionPermission, allowedRoles: localProcess.value.allowedRoles, modificationPermission: localProcess.value.modificationPermission, requireApproval: localProcess.value.requireApproval, enableAuditTrail: localProcess.value.enableAuditTrail }, workflow: { nodes: processStore.currentProcess?.nodes || [], edges: processStore.currentProcess?.edges || [] }, variables: variableStore.getAllVariables, metadata: { nodeCount: nodeCount.value, edgeCount: edgeCount.value, variableCount: variableCount.value, exportedAt: new Date().toISOString() } } return JSON.stringify(exportData, null, 2) }) // Export functions const copyToClipboard = async () => { try { await navigator.clipboard.writeText(formattedJson.value) console.log('JSON copied to clipboard') } catch (err) { console.error('Failed to copy JSON:', err) } } const downloadJson = () => { const blob = new Blob([formattedJson.value], { type: 'application/json' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = `${localProcess.value.name || 'process'}_settings.json` document.body.appendChild(a) a.click() document.body.removeChild(a) URL.revokeObjectURL(url) } ``` ### Integration with Process Builder The Process Settings modal is integrated into the main Process Builder interface: ```vue ``` ### API Integration Considerations For future API integration, the settings should be handled as follows: ```javascript // API endpoint structure for process settings POST /api/processes/{processId}/settings PUT /api/processes/{processId}/settings GET /api/processes/{processId}/settings // Request/Response format { "processInfo": { "name": "Customer Onboarding", "description": "Complete customer onboarding workflow", "priority": "high", "category": "Sales", "owner": "Sales Manager" }, "settings": { "processType": "approval", "maxExecutionTime": 480, "autoTimeout": 48, "allowParallel": true, "enableErrorRecovery": true, "sendNotifications": true }, "dataSettings": { "dataPersistence": "long_term", "logVariableChanges": true, "encryptSensitiveData": false, "dataRetentionPolicy": "Delete after 30 days" }, "permissions": { "executionPermission": "roles", "allowedRoles": "hr_manager,department_head", "modificationPermission": "managers", "requireApproval": true, "enableAuditTrail": true } } ``` ### Performance Considerations 1. **Lazy Loading**: Settings are only loaded when the modal is opened 2. **Local State Management**: Changes are made to local copies to avoid unnecessary reactivity 3. **Debounced Updates**: Consider debouncing settings updates for better performance 4. **Validation**: Client-side validation before API calls ### Security Considerations 1. **Permission Validation**: Server-side validation of permission changes 2. **Audit Trail**: All settings changes should be logged 3. **Role Verification**: Verify user permissions before allowing settings modifications 4. **Data Encryption**: Implement proper encryption for sensitive settings ### variableStore.js ```javascript import { defineStore } from 'pinia'; export const useVariableStore = defineStore('variables', { state: () => ({ variables: { global: [], local: {} } }), getters: { getAllVariables: (state) => state.variables, getVariableByName: (state) => (name, scope = 'global') => { if (scope === 'global') { return state.variables.global.find(v => v.name === name); } else { return state.variables.local[scope]?.find(v => v.name === name); } } }, actions: { addVariable(variable) { const { scope = 'global' } = variable; if (scope === 'global') { this.variables.global.push(variable); } else { if (!this.variables.local[scope]) { this.variables.local[scope] = []; } this.variables.local[scope].push(variable); } }, updateVariable(name, updatedVariable, scope = 'global') { if (scope === 'global') { const index = this.variables.global.findIndex(v => v.name === name); if (index !== -1) { this.variables.global[index] = { ...updatedVariable }; } } else { if (this.variables.local[scope]) { const index = this.variables.local[scope].findIndex(v => v.name === name); if (index !== -1) { this.variables.local[scope][index] = { ...updatedVariable }; } } } }, deleteVariable(name, scope = 'global') { if (scope === 'global') { this.variables.global = this.variables.global.filter(v => v.name !== name); } else if (this.variables.local[scope]) { this.variables.local[scope] = this.variables.local[scope].filter(v => v.name !== name); } } } }); ``` ## UI Component Styling The project uses Tailwind CSS for styling with consistent patterns: ### Color Theming by Component Type Each node type has a consistent color theme: - **Business Rules**: Purple - **API Tasks**: Indigo - **Form Tasks**: Emerald - **Decision Points**: Orange - **Variables**: Blue ### Common Visual Elements 1. **Modal Headers**: ```html

{Title}

{Description}

``` 2. **Step Indicators**: ```html
``` 3. **Empty States**: ```html

{Empty State Title}

{Empty State Description}

{Action Text}
``` ## Best Practices for Development When developing new components or enhancing existing ones: 1. **Consistent Design Pattern**: - Follow the established design patterns for node configurations - Use the same structure for headers, step indicators, and action buttons - Maintain the color coding system for different node types 2. **Responsive Components**: - Ensure all components work on various screen sizes - Use responsive utilities from Tailwind - Test on mobile and desktop views 3. **State Management**: - Store node configuration in the appropriate Pinia store - Use reactive Vue 3 patterns with `ref`, `computed`, etc. - Implement proper validation before saving 4. **Accessibility**: - Ensure all UI elements are keyboard accessible - Use semantic HTML elements - Maintain proper focus management in modals 5. **Data Flow Visualization**: - Use visual indicators to show data flow direction - Provide clear feedback on how variables are used - Highlight connections between nodes --- For user documentation and usage guidelines, please refer to [Process Builder Documentation](USER_GUIDE.md). Last updated: December 2024