Background
A workflow is composed of nodes and edges, where nodes are linked through edges. Each node has its own configuration settings. The workflow includes Exclusive, Parallel, and Inclusive gateway nodes. Once the workflow is designed and node configurations are set, it can be deployed using the deploy button, making it ready for execution.
Overview
This module offers utility functions for managing and deploying workflows, focusing on handling node and edge types, generating unique IDs, and configuring parallel gateway branches.
Solution
// To return the type of node and edge for deployment
export const getDeployNodeType = (type) => {
let deployType = '';
switch(type) {
// other components
case 'Parallel Gateway': deployType = 'Parallel';
break;
default: deployType = type
}
return deployType;
}
export const getNodeUniqueID = (type, componentID, isParallel) => {
let id = '';
switch(type) {
// other component case
case 'Parallel Gateway':
id = `PLG_${componentID}`
break;
}
return id
}
let parallelFlow = [];
// function used for parallel gateway branch configuration
export const getBranches = (source, target, branch, instance, workflowData, branchGateway, workflowErrors) => {
let outgoer = [], nextOutgoer = [];
switch(source.component_type) {
case 'Exclusive Gateway':
const gatewayEdges = workflowData.connections.filter((item) => item.source === source.id);
gatewayEdges.forEach((item, index=0) => {
if(!item.data.input_conditions || item?.data?.input_conditions?.length === 0) {
workflowErrors.push(item.id);
return;
}
const itemTarget = instance.getNode(item.target);
if(itemTarget) {
parallelFlow.push(item.source);
parallelFlow.push(item.id);
branchGateway.push({
id: getNodeUniqueID(source.component_type, source.id),
component_id: source.id,
workflow_id: workflowData._id,
facility_id: workflowData.facility_id,
enterprise_id: source.enterprise_id,
next: getNodeUniqueID(itemTarget.component_type, itemTarget.id),
data_source: item.data.data_source,
input_conditions: item.data.input_conditions,
global_operator: item.data.operator,
name: source.data.name,
type: getDeployNodeType(source.component_type),
edge_name: item.label,
index: source.data.execution_orders.indexOf(item.id)
})
if(itemTarget.component_type === 'End') {
const currentTask = getEndTask(item, itemTarget, workflowData)
parallelFlow.push(item.target.id);
branch.tasks.push(currentTask);
}
}
});
outgoer = getOutgoers(source, workflowData.components, workflowData.connections);
outgoer.forEach((item, index) => {
nextOutgoer = getOutgoers(item, workflowData.components, workflowData.connections);
if(nextOutgoer && nextOutgoer.length > 0) {
if(outgoer && outgoer.length > 0 && nextOutgoer[0].component_type === 'Parallel Gateway' && nextOutgoer[0].data.selects?.parallel_data?.parallel_type === 'Converging') {
let endData = getEndTask(outgoer[index], nextOutgoer[0], workflowData)
branch.tasks.push(endData);
let edge = workflowData.connections.find((item) => item.source === nextOutgoer[0].id);
if(edge) {
let connectingNode = instance.getNode(edge.target);
branch['next'] = getNodeUniqueID(connectingNode.component_type, connectingNode.id)
}
}
getBranches(item, nextOutgoer.length > 0 ? nextOutgoer[0] : source.id, branch, instance, workflowData, branchGateway, workflowErrors);
} else {
if(target?.component_type === 'End') {
const currentTask = getEndTask(source, target, workflowData)
parallelFlow.push(item.target.id);
branch.tasks.push(currentTask);
}
}
})
break;
case 'insurance-lookup-function':
case 'manage-patient-function':
case 'patient-lookup-function':
case 'document-queuing-function':
case 'print-function':
case 'email-message-function':
case 'record-on-file-function':
case 'Sub-process':
case 'data-lookup-function':
parallelFlow.push(source.id);
let isConverging = (target.component_type === 'Parallel Gateway' && target.data.selects?.parallel_data?.parallel_type === 'Converging')
let functionObj = {
id: getNodeUniqueID(source.component_type, source.id),
component_id: source.id,
workflow_id: workflowData._id,
facility_id: workflowData.facility_id,
enterprise_id: source.enterprise_id,
name: source.data.name,
next: (target.component_type === 'End' || (target.component_type === 'Parallel Gateway' && target.data.selects?.parallel_data?.parallel_type === 'Converging')) ? 'End' : getNodeUniqueID(target.component_type, target.id),
type: getDeployNodeType(source.component_type),
meta_data: {
next_type: (target.component_type === 'Parallel Gateway' && target.data.selects?.parallel_data?.parallel_type === 'Converging') ? 'End' : getDeployNodeType(target.component_type),
end_id: (target.component_type === 'End' || isConverging) ? source.id+'_'+target.id : '',
is_end: (target.component_type === 'End' || isConverging) ? true : false
},
functionName: source.data.name,
parameters: {
input: {
data: ""
},
}
}
switch(source.component_type) {
case 'patient-lookup-function':
if(!source.data.patient_lookup_attributes || source.data.patient_lookup_attributes.length === 0) {
workflowErrors.push(source.id);
}
break;
case 'insurance-lookup-function':
if(!source.data.insurance_lookup_attributes || source.data.insurance_lookup_attributes.length === 0) {
workflowErrors.push(source.id);
}
break;
case 'document-queuing-function':
if((source.data.document_packets.length === 0 && source.data.forms?.length === 0)) {
workflowErrors.push(source.id);
}
break;
case 'print-function':
if((source.data.labels.length === 0 && source.data.forms?.length === 0)) {
workflowErrors.push(source.id);
}
break;
case 'email-message-function':
if(!source.data.email_template_id) {
workflowErrors.push(source.id);
}
break;
case 'record-on-file-function':
if(!source.data.document_id) {
workflowErrors.push(source.id);
}
break;
case 'data-lookup-function':
if(!source.data.data_table_id) {
workflowErrors.push(source.id);
}
break;
case 'archive-documents-function':
if(!source.data.document_types || source.data.document_types.length === 0) {
workflowErrors.push(source.id);
}
break;
case 'Sub-process':
if(!source.data.sub_process.workflow_id) {
workflowErrors.push(source.id);
}
break;
}
functionObj.parameters = getNodeData(source, functionObj.parameters)
if(source.component_type === 'Sub-process') {
branch.subprocess.push(functionObj);
} else {
branch.functions.push(functionObj);
}
outgoer = getOutgoers(source, workflowData.components, workflowData.connections);
nextOutgoer = getOutgoers(outgoer[0], workflowData.components, workflowData.connections);
if(nextOutgoer && nextOutgoer.length > 0) {
if(outgoer && outgoer.length > 0 && nextOutgoer[0].component_type === 'Parallel Gateway' && nextOutgoer[0].data.selects?.parallel_data?.parallel_type === 'Converging') {
let endData = getEndTask(outgoer[0], nextOutgoer[0], workflowData)
branch.tasks.push(endData);
let edge = workflowData.connections.find((item) => item.source === nextOutgoer[0].id);
if(edge) {
let connectingNode = instance.getNode(edge.target);
branch['next'] = getNodeUniqueID(connectingNode.component_type, connectingNode.id)
}
}
getBranches(target, nextOutgoer.length > 0 ? nextOutgoer[0] : source.id, branch, instance, workflowData, branchGateway, workflowErrors);
} else {
if(target.component_type === 'End') {
const currentTask = getEndTask(source, target, workflowData)
parallelFlow.push(target.id);
branch.tasks.push(currentTask);
}
}
break;
}
return branch;
}
export async function handleDeployWorkflow(setDeployLoader, instance, workflowData, setWorkflowComponentError) {
let workflowDetails // consists the meta data of the workflow
try {
let deployFlag = true;
parallelFlow = [];
let payload = {
workflow_id: workflowDetails._id,
facility_id: workflowDetails.facility_id,
name: workflowDetails.title,
description: workflowDetails.description,
start: '', // id of the start component in case of main process and subprocess start component in case of sub process
tasks:[], // array of objects consisting of component details of Start, End components as well as Edge components of Inclusive and Exclusive gateways
gateways: [], // array of objects consisting of component details of all the gateways
functions: [], // array of objects consisting of component details of all the functions
wait_events: [], // array of objects consisting of component details of Wait Components
parallel_gateway: [], // temporary array consisting of parallel gateway information
subprocess:[] // array of objects consisting of component details of Sub process Components
};
const parallelGatewayObj = []
const edgeList = workflowDetails.connecions; // consist of all the edges in the workflow
if(edgeList.length === 0) {
// show error: 'Deployment failed. One or more connections in your workflow are incomplete'
return
}
edgeList.forEach((item) => {
if(!parallelFlow.includes(item.id) && !parallelFlow.includes(item.source)) {
const source = instance.getNode(item.source);
const target = instance.getNode(item.target);
if(source && target) {
switch(source.component_type) {
case 'Parallel Gateway':
if(source.component_type === 'Parallel Gateway' && source.data.selects?.parallel_data?.parallel_type === 'Converging') {
return;
}
let branch = {
workflow_id: workflowDetails._id,
facility_id: workflowDetails.facility_id,
name: source.data.name,
description: workflowDetails.description,
id: getNodeUniqueID(source.component_type, source.id, ''),
component_id: source.id,
component_name: source.data.name,
start: source.component_type === 'Inclusive Gateway' ? getNodeUniqueID('edge', item.id) : getNodeUniqueID(target.component_type, target.id),
type: getDeployNodeType(source.component_type),
tasks:[],
gateways: [],
functions: [],
wait_events: [],
parallel_gateway: [],
subprocess: []
};
parallelFlow.push(target.id);
parallelFlow.push(item.id);
if(target.component_type === 'End') {
const currentTask = getEndTask(source, target, workflowDetails)
branch.tasks.push(currentTask);
}
const outgoer = getOutgoers(target, workflowDetails.components, workflowDetails.connections);
let branchGateway = [];
const oneBranch = getBranches(target, outgoer[0], branch, instance, workflowDetails, branchGateway, workflowErrors);
const obj = {};
const result = branchGateway.reduce(function(r, el) {
var e = el.id;
if (!obj[e]) {
obj[e] = {
id: el.id,
name: el.component_name,
type: el.type,
next: 'End',
component_id: el.component_id,
workflow_id: workflowDetails._id,
facility_id: workflowDetails.facility_id,
enterprise_id: source.enterprise_id,
conditions: []
}
r.push(obj[e]);
}
if(el.input_conditions.length === 0) {
deployFlag = false;
}
obj[e].conditions.push({expression: '', booleanEquals: true, next: el.next, name: el.edge_name, input_conditions: el.input_conditions, global_operator: el.global_operator, is_rule_used: el.is_rule_used,
rule_id: el.rule_id, data_source: el.data_source});
return r;
}, []);
if(oneBranch) {
oneBranch['gateways'] = result;
oneBranch['gateways'].push(...oneBranch.parallel_gateway);
delete oneBranch.parallel_gateway; // delete the temporary object
parallelGatewayObj.push(oneBranch);
}
break;
}
}
}
})
// for parallel gateway construction
const obj1 = {}
const result1 = parallelGatewayObj.reduce(function(r, el) {
let outflows = workflowDetails.connections.filter((item) => item.source === el.component_id).map((item) => item.id)
var e = el.id;
if (!obj1[e]) {
obj1[e] = {
id: el.id,
name: el.name,
type: el.type,
next: el.next || 'End',
component_id: el.component_id,
workflow_id: workflowDetails._id,
facility_id: workflowDetails.facility_id,
outflows: outflows,
branches: []
}
r.push(obj1[e]);
}
obj1[e].branches.push(el);
return r;
}, []);
if(result1.length > 0) {
payload.gateways.push(...result1);
}
// for parallel gateway construction
if(workflowErrors.length > 0) {
// error message stating: "Please add the configuration of required components and edges correctly."
} else {
// api call to deploy the workflow
}
} catch(error) {
// handle the error
return;
}
}
Functions
1. getDeployNodeType(type)
Description: Returns the deployment type of a given node.
Parameters:
type(string): The type of the node.
Returns:
- (string): The corresponding deployment type.
Example:
getDeployNodeType('Parallel Gateway'); // Returns 'Parallel'
2. getNodeUniqueID(type, componentID, isParallel)
Description: Generates a unique ID for a given node type and component.
Parameters:
type(string): The type of the node.componentID(string): The component identifier.isParallel(boolean): Indicates if the node is part of a parallel gateway.
Returns:
- (string): The unique identifier for the node.
Example:
getNodeUniqueID('Parallel Gateway', '12345', false); // Returns 'PLG_12345'
3. getBranches(source, target, branch, instance, workflowData, branchGateway, workflowErrors)
Description: Handles the configuration of parallel gateway branches within a workflow.
Parameters:
source(object): The source node.target(object): The target node.branch(object): The branch configuration object.instance(object): Workflow instance handler.workflowData(object): The entire workflow data structure.branchGateway(array): Stores information about parallel gateway branches.workflowErrors(array): Collects workflow configuration errors.
Returns:
- (object): The updated branch configuration.
Example:
getBranches(sourceNode, targetNode, branchConfig, instance, workflowData, false, branchGateway, []);
4. handleDeployWorkflow(setDeployLoader, instance, workflowData, handleToast, setWorkflowComponentError)
Description: Handles the deployment of a workflow by processing nodes, edges, and parallel gateways.
Parameters:
setDeployLoader(function): Function to set the loading state.instance(object): Workflow instance handler.workflowData(object): The entire workflow data structure.handleToast(function): Function to display toast notifications.setWorkflowComponentError(function): Function to update workflow errors.
Returns:
void
Example:
handleDeployWorkflow(false, setLoader, instance, workflowData, showToast, setErrors);
Data Structures
workflowData: Contains components, connections, and metadata of the workflow.parallelFlow: Stores IDs of nodes involved in parallel execution.
Error Handling
- If workflow connections are incomplete, an error message is shown.
- Configuration issues with required components or edges result in workflow errors.
- Errors are stored in
workflowErrorsand displayed viasetWorkflowComponentError.
Deployment Process
- Identify and process nodes and edges.
- Configure parallel gateway branches.
- Validate workflow configurations.
- Deploy the workflow if no errors are detected.
Contributors
- Pooja A Shetty
- Ashwath Prabhu