Name
sn_flow_diagram.FlowDiagramBuilderApi
Description
No description available
Script
var FlowDiagramBuilderApi = Class.create();
FlowDiagramBuilderApi.prototype = {
initialize: function() {},
type: 'FlowDiagramBuilderApi'
};
// class variables
FlowDiagramBuilderApi.templateDiagramJson = null;
FlowDiagramBuilderApi.linkLabel = "";
FlowDiagramBuilderApi.edgeId = "";
FlowDiagramBuilderApi.startPort = "";
FlowDiagramBuilderApi.endPort = "";
FlowDiagramBuilderApi.startKey = "";
FlowDiagramBuilderApi.endKey = "";
FlowDiagramBuilderApi.addBtnVisibility = "onHover";
FlowDiagramBuilderApi.addBtnText = "";
FlowDiagramBuilderApi.flowLogicRecordsHash = {};
FlowDiagramBuilderApi.recordOrder = 0;
FlowDiagramBuilderApi.instanceRecordsHash = {};
FlowDiagramBuilderApi.instanceRecordsDepthHash = {};
FlowDiagramBuilderApi.instanceRecordsOrderedList = [];
FlowDiagramBuilderApi.sourceIfsByDepthHash = {};
FlowDiagramBuilderApi.maxInteger = 4503599627370495;
FlowDiagramBuilderApi.currentParallelBlockKey = "";
FlowDiagramBuilderApi.currentParallelBlockDepth = -1;
FlowDiagramBuilderApi.currentDecisionBlockKey = "";
FlowDiagramBuilderApi.currentDecisionBlockDepth = -1;
FlowDiagramBuilderApi.parallelBranchAddLookup = {};
FlowDiagramBuilderApi.stagesAssociatedNodeMap = {};
FlowDiagramBuilderApi.decisionAnswers = {};
// hash of BEGINNING and END records
FlowDiagramBuilderApi.hiddenRecordsHash = {};
FlowDiagramBuilderApi.previousIfOrElseIfKey = "";
// maps deleted else node to the node that replaces the else node
FlowDiagramBuilderApi.elseReplacementHash = {};
FlowDiagramBuilderApi.getTranslatedFlowFromDatabase = function(flowSysId, errors) {
if (!errors || !(errors instanceof Array))
errors = [];
var flow = sn_flow.FlowDesigner.getCompleteFlow(flowSysId);
return FlowDiagramBuilderApi.getTranslatedFlow(flow, errors);
};
FlowDiagramBuilderApi.populateSortedInstances = function(flowJSON, instances) {
// flow from UI only has componentInstances; flow from database only has actionInstances, flowLogicInstances, and/or subflowInstances
if (flowJSON.componentInstances)
instances = FlowDiagramBuilderApi.concatToMainArrayWithFlowDiagramType(instances, flowJSON.componentInstances, FlowDiagramConstants.INSTANCE_TYPES.COMPONENT_INSTANCE_TYPE);
else {
if (flowJSON.actionInstances)
instances = FlowDiagramBuilderApi.concatToMainArrayWithFlowDiagramType(instances, flowJSON.actionInstances, FlowDiagramConstants.INSTANCE_TYPES.ACTION_INSTANCE_TYPE);
if (flowJSON.flowLogicInstances)
instances = FlowDiagramBuilderApi.concatToMainArrayWithFlowDiagramType(instances, flowJSON.flowLogicInstances, FlowDiagramConstants.INSTANCE_TYPES.FLOW_LOGIC_INSTANCE_TYPE);
if (flowJSON.subFlowInstances)
instances = FlowDiagramBuilderApi.concatToMainArrayWithFlowDiagramType(instances, flowJSON.subFlowInstances, FlowDiagramConstants.INSTANCE_TYPES.SUBFLOW_INSTANCE_TYPE);
}
// this sort supports order value with dots (e.g. 1.2.33)
instances = FlowDiagramHelper.sortByArrayPosition(instances);
return instances;
};
// flow can be full fledged flow from database or UI or in the following structure:
// {
// name,
// id, // sys_if of the flow
// type, // flow,subflow,action
// componentInstances: [
// {
// id,
// uiUniqueIdentifier,
// name,
// parent,
// order,
// arrayPosition,
// connectedTo,
// depth,
// actionType: {
// parent_action,
// id,
// name
// },
// flowLogicDefinition: {
// id,
// type,
// name
// }
// }
// ]
// }
FlowDiagramBuilderApi.getTranslatedFlow = function(flowJSON, errors) {
if (!errors || !(errors instanceof Array))
errors = [];
var dataModel = FlowDiagramBuilderApi.getDiagramJSON(flowJSON, errors);
dataModel.configuration = FlowDiagramBuilderApi.getConfiguration();
return dataModel;
};
FlowDiagramBuilderApi.getDiagramJSON = function(flowJSON, errors) {
if (!errors || !(errors instanceof Array))
errors = [];
var i, j;
var dataModel = FlowDiagramTranslateModel.getDataModel();
dataModel.diagramJSON = FlowDiagramTranslateModel.getDiagramJSONModel();
if (flowJSON) {
if (flowJSON && errors.length === 0) {
dataModel.name = flowJSON.name;
dataModel.flowIsReadOnly = flowJSON.flowIsReadOnly;
try {
var templateJSON = FlowDiagramDatabaseApi.getTemplateJSON(FlowDiagramConstants.SYS_IDS.SN_FLOW_DIAGRAM_BUILDER_CONFIGURATION);
FlowDiagramBuilderApi.templateDiagramJson = JSON.parse(templateJSON);
} catch (e) {
gs.error("FlowDiagramBuilderApi: " + e);
errors.push(e);
}
if (errors.length === 0 && FlowDiagramBuilderApi.templateDiagramJson && FlowDiagramBuilderApi.templateDiagramJson.edges.length > 0) {
FlowDiagramBuilderApi.edgeId = FlowDiagramBuilderApi.templateDiagramJson.edges[0].edge_id;
FlowDiagramBuilderApi.startPort = FlowDiagramBuilderApi.templateDiagramJson.edges[0].fromPort;
FlowDiagramBuilderApi.endPort = FlowDiagramBuilderApi.templateDiagramJson.edges[0].toPort;
}
if (errors.length === 0) {
var existingTrigger = false;
var existingSubflowInputs = flowJSON.existingSubflowInputs;
var triggerInstanceName = FlowDiagramConstants.NODE_NAMES.TRIGGER;
if (flowJSON.triggerInstances && flowJSON.triggerInstances.length > 0) {
existingTrigger = true;
triggerInstanceName = flowJSON.triggerInstances[0].name;
}
var hierarchyStack = [];
var flowType = flowJSON.type;
var existingSubflowInputs = flowJSON.existingSubflowInputs;
var error = FlowDiagramBuilderApi.addStartEnd(hierarchyStack, dataModel, triggerInstanceName, existingTrigger, flowType, existingSubflowInputs);
if (error)
errors.push(error);
else {
var instances = [];
try {
instances = FlowDiagramBuilderApi.populateSortedInstances(flowJSON, instances);
var activeIfKeyByDepthHash = {};
for (i = 0; i < instances.length; i++) {
FlowDiagramBuilderApi.addInstance(errors, hierarchyStack, dataModel, instances[i], activeIfKeyByDepthHash);
}
FlowDiagramBuilderApi.processRemainingActiveIfKeys(activeIfKeyByDepthHash, dataModel.diagramJSON.nodes);
FlowDiagramBuilderApi.updateEndOfParallelAndParallelBlock(dataModel.diagramJSON.nodes);
FlowDiagramBuilderApi.updatePathBroken(errors, dataModel.diagramJSON.nodes, dataModel.diagramJSON.edges);
// setupLoopExitNodeHashAndloopBackNodes will calculate all IF nodes and FOR loop nodes that ends with a node
// then based on their depth, END IF or END LOOP nodes will be created and added before the original node.
// If a END node was created, all edges pointing to the original node prior to this will be updated to point to the corresponding
// END node. All loopback edges created prior to this will be deleted. A new loopback edge will be created if END LOOP node
// was created and the loopback edge will be sourced from the END LOOP node.
var loopExitNodeHash = FlowDiagramBuilderApi.setupLoopExitNodeHashAndloopBackNodes(dataModel.diagramJSON.nodes, dataModel.diagramJSON.edges);
dataModel.diagramJSON.nodes = FlowDiagramBuilderApi.processEndNodes(dataModel.diagramJSON.nodes, dataModel.diagramJSON.edges, loopExitNodeHash);
// has to execute after end of parallel node is generated
FlowDiagramBuilderApi.updatePathBrokenForParallel(dataModel.diagramJSON.nodes, dataModel.diagramJSON.edges);
FlowDiagramBuilderApi.removeImmediateEndNodesConnectedToPathBroken(dataModel.diagramJSON.nodes, dataModel.diagramJSON.edges);
FlowDiagramBuilderApi.processOrphanedEndOfNodeInPathBroken(dataModel.diagramJSON.nodes, dataModel.diagramJSON.edges);
FlowDiagramBuilderApi.connectPathBrokenToNonStartPathBrokenNodeWithoutIncomingEdges(dataModel.diagramJSON.nodes, dataModel.diagramJSON.edges);
FlowDiagramBuilderApi.updateGoBackToEdges(dataModel.diagramJSON.nodes, dataModel.diagramJSON.edges);
FlowDiagramBuilderApi.updateEdgeLabels(dataModel.diagramJSON.edges);
FlowDiagramBuilderApi.removeUnnecessaryEdges(dataModel.diagramJSON.edges);
FlowDiagramBuilderApi.removeAddButtonOnEndEdges(dataModel.diagramJSON.edges);
FlowDiagramBuilderApi.removeUnconnectedNodes(dataModel.diagramJSON.nodes, dataModel.diagramJSON.edges);
FlowDiagramBuilderApi.addStageNodes(flowJSON, dataModel.diagramJSON.nodes, dataModel.diagramJSON.edges);
FlowDiagramBuilderApi.removeUnnecessaryFieldsOnEdges(dataModel.diagramJSON.edges);
FlowDiagramBuilderApi.removeUnnecessaryFieldsOnNodes(dataModel.diagramJSON.nodes);
FlowDiagramBuilderApi.assignSortedNodesToDataModel(dataModel);
} catch (e) {
errors.push(e);
}
}
}
// Todo:
// The following two methods loop through all the nodes and edges
// We should combine them into the construction code of the nodes and edges to improve performance.
var nodeMap = {};
try {
nodeMap = FlowDiagramBuilderApi.addDataModelRelationship(flowJSON, dataModel);
} catch (e) {
errors.push(e);
}
try {
FlowDiagramBuilderApi.decorateLinks(dataModel, nodeMap);
} catch (e) {
errors.push(e);
}
}
}
return dataModel;
};
FlowDiagramBuilderApi.updateGoBackToEdges = function(nodes, edges) {
var goBackToLabelMap = {};
var updateEdgeLabels = [];
var deleteEdges = {};
var fromNodes = nodes.filter(function(n) {
return n.node_id === FlowDiagramConstants.SYS_IDS.FLOW_LOGIC_GO_BACK_TO_ACTION;
});
for (var i = 0; i < fromNodes.length; i++) {
var goBackToNode = fromNodes[i];
var goBackToTargetKey = goBackToNode.data.go_back_to_step;
var toNode = FlowDiagramBuilderApi.instanceRecordsHash[goBackToTargetKey];
if (toNode && toNode.key) {
var goBackToEdge = edges.filter(function(e) {
return e.from === goBackToNode.key;
})[0];
if (goBackToEdge) {
var toNodeHash = FlowDiagramBuilderApi.instanceRecordsHash[toNode.key];
var toPort = FlowDiagramBuilderApi.getToPort(toNodeHash.type);
goBackToEdge.to = toNode.key;
goBackToEdge.toPort = toPort;
goBackToEdge.linkLabel = "";
var currentLabel = "";
if (goBackToLabelMap[goBackToTargetKey]) {
currentLabel = goBackToLabelMap[goBackToTargetKey];
}
var label = FlowDiagramHelper.getGoBackToLabel(goBackToTargetKey, goBackToNode.order, currentLabel);
goBackToLabelMap[goBackToTargetKey] = label;
updateEdgeLabels.push({
key: goBackToTargetKey,
edge: goBackToEdge
});
}
} else if (!goBackToTargetKey) { // go_back_to_step is empty, no toNode, to delete the edge
deleteEdges[goBackToNode.key] = goBackToNode.key;
}
}
// update labels on GoBackTo edges
for (var j = 0; j < updateEdgeLabels.length; j++) {
var targetKey = updateEdgeLabels[j].key;
var edge = updateEdgeLabels[j].edge;
edge.linkLabel = goBackToLabelMap[targetKey];
}
// remove edges
for (var i = 0; i < edges.length; i++) {
// Do not delete GoBackTo edge as we will update later
if (deleteEdges[edges[i].from]) {
edges.splice(i, 1);
i--;
}
}
};
// Adds stage nodes. Updates existing network of nodes and edges accordingly.
FlowDiagramBuilderApi.addStageNodes = function(flowJSON, totalNodes, totalEdges) {
var i, j, k;
if (flowJSON && flowJSON.stages) {
var stages = [];
// Sort edges based on its destination (to)
var edgesByTo = {};
for (i = 0; i < totalEdges.length; i++) {
var edge = totalEdges[i];
if (edge.to) {
if (!edgesByTo[edge.to]) {
edgesByTo[edge.to] = [];
}
edgesByTo[edge.to].push(edge);
}
}
for (var key in flowJSON.stages) {
if (Object.prototype.hasOwnProperty.call(flowJSON.stages, key)) {
var stage = flowJSON.stages[key];
if (stage && stage.componentIndexes) {
for (i = 0; i < stage.componentIndexes.length; i++) {
var componentIndexValue = stage.componentIndexes[i];
// create the stage node for each stage.componentIndexes
var stageNode = {
key: FlowDiagramHelper.addUiUniqueIdentifierDashes(sn_flow.FlowDesigner.getGeneratedGuid()),
instanceType: FlowDiagramConstants.INSTANCE_TYPES.STAGE_TYPE,
type: stage.type,
label: stage.label,
instance_sys_id: stage.id,
stageId: stage.stageId,
node_id: FlowDiagramConstants.SYS_IDS.FLOW_STAGE_ACTION,
componentIndexes_index: i,
componentIndexes_value: componentIndexValue,
arrayPosition: componentIndexValue,
associated_node_key: "", // will be added later
order: stage.order,
duration: stage.duration,
value: stage.value,
definition_sys_id: "", // will be empty
depth: "", // will be added later
data: {
stageLabel: stage.label,
stageDurationLabel: "",
isLabelBold: stage.type !== "error",
isDurationBold: stage.type === "error",
isErrorStage: stage.type === "error",
}
};
stages.push(stageNode);
}
}
}
}
var orderedStages = FlowDiagramHelper.sortByArrayPosition(stages, "desc");
var orderedNodes = [];
for (i = 0; i < totalNodes.length; i++) {
if (totalNodes[i].arrayPosition || totalNodes[i].arrayPosition === 0) {
orderedNodes.push(totalNodes[i]);
}
}
orderedNodes = FlowDiagramHelper.sortByArrayPosition(orderedNodes, "desc");
for (i = 0; i < orderedStages.length; i++) {
var currentStage = orderedStages[i];
var validStagePositionCount = 0;
// stage arrayPosition can be iterated not in order
for (j = 0; j < orderedNodes.length; j++) {
var currentNode = orderedNodes[j];
var isCurrentNodeNotUnderForEach = currentNode && currentNode.sourceForEachKeys && currentNode.sourceForEachKeys instanceof Array && (currentNode.sourceForEachKeys.toString().includes(currentNode.key) || currentNode.sourceForEachKeys.length === 0);
var currentNodeHasCurrentStage = currentNode && (currentNode.arrayPosition || currentNode.arrayPosition === 0) && currentNode.arrayPosition === currentStage.arrayPosition;
if (currentNodeHasCurrentStage && isCurrentNodeNotUnderForEach && !currentNode.parallelBlockKey) {
// stage cannot be within a FOR loop nor PARALLEL FOR
// fill in the stage node's remaining fields after finding the stage's attached node
currentStage.depth = currentNode.depth;
currentStage.associated_node_key = currentNode.key;
currentStage.key = FlowDiagramHelper.addStringToUuid(FlowDiagramConstants.INSTANCE_TYPES.STAGE_TYPE, currentNode.key);
// store map of stage key to associated node key for lookup
FlowDiagramBuilderApi.stagesAssociatedNodeMap[currentStage.key] = currentNode.key;
if (currentNode.parent) {
currentStage.parent = currentNode.parent;
}
// find all edges pointing to the stage's attached node and update the edges to point to the stage node
var connectedEdges = edgesByTo[currentNode.key];
if (connectedEdges) {
for (k = 0; k < connectedEdges.length; k++) {
var connectedEdge = connectedEdges[k];
if (connectedEdge.toPort !== FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_INPUT_LOOP_BACK_SYS_ID) {
connectedEdge.to = currentStage.key;
connectedEdge.toPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_STAGE_INPUT_SYS_ID;
}
}
}
// add a new edge that will connect between the stage node and stage's attached node
var newEdge = FlowDiagramTranslateModel.getEdgeModel();
if (newEdge && FlowDiagramBuilderApi.instanceRecordsHash[currentNode.key]) {
newEdge = FlowDiagramBuilderApi.updateEdge(
newEdge, {
from: currentStage.key,
to: currentNode.key,
fromPort: FlowDiagramConstants.SYS_IDS.DIAGRAM_STAGE_OUTPUT_SYS_ID,
toPort: FlowDiagramBuilderApi.getToPort(FlowDiagramBuilderApi.instanceRecordsHash[currentNode.key].type),
edgeId: FlowDiagramBuilderApi.edgeId,
addBtnVisibility: FlowDiagramConstants.LINK_ADD_BTN_VISIBILITY.forLoopLoopback, // remove the add button
hideToArrow: true
}
);
totalEdges.push(newEdge);
}
validStagePositionCount++;
break;
}
}
delete currentStage.arrayPosition;
// only add the stage if there is one or more valid stage placement in flow
if (validStagePositionCount > 0) {
totalNodes.push(currentStage);
}
}
}
};
// there could be non-path broken node that will need to be connected to a path broken node
// this node will not have any incoming edge
// only path broken node can have no incoming edges
// path broken node created in this function will not have an associated END OF node
FlowDiagramBuilderApi.connectPathBrokenToNonStartPathBrokenNodeWithoutIncomingEdges = function(nodes, edges) {
var edgesTo = {};
var edgesFrom = {};
var orderedNodes = FlowDiagramBuilderApi.sortNodes(nodes);
var i;
for (i = 0; i < edges.length; i++) {
var edge = edges[i];
edgesTo[edge.to] = true;
}
for (i = 0; i < orderedNodes.length; i++) {
var node = orderedNodes[i];
// create path broken node if the node is not path broken nor start nor trigger node nor end of * node
// and there are no incoming edges
if (!((node.instanceType &&
(node.instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.PATHBROKEN ||
node.instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDIF ||
node.instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDLOOP ||
node.instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDDOLOOP ||
node.instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDPARALLEL ||
node.instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDDECISION
)) ||
node.key === FlowDiagramBuilderApi.startKey ||
node.key === FlowDiagramConstants.NODE_KEYS.FLOW_DIAGRAM_ADD_TRIGGER_NODE_KEY) &&
!edgesTo[node.key]) {
var nodeId = FlowDiagramBuilderApi.getFlowLogicNodeId(FlowDiagramConstants.FLOW_LOGIC_TYPES.PATHBROKEN);
// PATH BROKEN node is not defined anywhere in Flow Designer. We could have used END Node's key as PATH BROKEN node's
// key but decided to create its own since all node keys should be unique
var newKey = FlowDiagramHelper.addUiUniqueIdentifierDashes(sn_flow.FlowDesigner.getGeneratedGuid());
// PATH BROKEN node does not exist in Flow and therefore there is no instance.order for it
var newNode = FlowDiagramBuilderApi.createNode({
name: FlowDiagramConstants.NODE_NAMES.PATH_BROKEN,
key: newKey,
node_id: nodeId,
instanceType: FlowDiagramConstants.FLOW_LOGIC_TYPES.PATHBROKEN
});
nodes.push(newNode);
var newEdge = FlowDiagramTranslateModel.getEdgeModel();
if (newEdge) {
newEdge = FlowDiagramBuilderApi.updateEdge(
newEdge, {
from: newNode.key,
to: node.key,
fromPort: FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_PATH_BROKEN_OUTPUT_SYS_ID,
toPort: FlowDiagramBuilderApi.getToPort(node.type),
edgeId: FlowDiagramBuilderApi.edgeId,
}
);
edges.push(newEdge);
}
}
}
};
FlowDiagramBuilderApi.updateEdgesFromToPointToNewNode = function(edges, node, newNode, fromPort) {
for (var i = 0; i < edges.length; i++) {
var edge = edges[i];
if (edge.from === node.key) {
edge.from = newNode.key;
edge.fromPort = fromPort;
}
}
};
// first/immediate END OF node attached to Path Broken node is not necessary because you cannot
// add a node after END flow logic node. Therefore remove the immediate END OF node followed by a
// path broken node
// there could be Path Broken node added without assoicated END OF node by
// connectPathBrokenToNonStartPathBrokenNodeWithoutIncomingEdges()
FlowDiagramBuilderApi.removeImmediateEndNodesConnectedToPathBroken = function(nodes, edges) {
var i, j;
var orderedNodes = FlowDiagramBuilderApi.sortNodes(nodes, "desc");
var nodeTypeFrom;
// record all path broken nodes and edges from the path broken node in pathBrokenNodes
var pathBrokenNodes = {};
for (i = 0; i < edges.length; i++) {
var edge = edges[i];
nodeTypeFrom = FlowDiagramBuilderApi.getInstanceRecordHashNodeType(edge.from);
if (nodeTypeFrom && nodeTypeFrom === FlowDiagramConstants.FLOW_LOGIC_TYPES.PATHBROKEN) {
// nodeTypeTo is node type of first immediate node attached to this path broken node
var nodeTypeTo = FlowDiagramBuilderApi.getHiddenRecordHashNodeType(edge.to);
if (nodeTypeTo && (nodeTypeTo === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDLOOP ||
nodeTypeTo === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDDOLOOP ||
nodeTypeTo === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDIF ||
nodeTypeTo === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDPARALLEL || nodeTypeTo === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDDECISION)) {
pathBrokenNodes[edge.from] = {
endNode: FlowDiagramBuilderApi.hiddenRecordsHash[edge.to],
edgeFromPathBroken: edge,
edgesFromEnd: []
};
for (j = 0; j < edges.length; j++) {
// if nodeTypeTo exists then FlowDiagramBuilderApi.hiddenRecordsHash[edge.to] exists
if (edges[j].from === FlowDiagramBuilderApi.hiddenRecordsHash[edge.to].key) {
var edgeFromNodeTypeTo = edges[j];
pathBrokenNodes[edge.from].edgesFromEnd.push(edgeFromNodeTypeTo);
}
}
}
}
}
var edgesToDelete = {};
var nodesToDelete = {};
for (i = 0; i < orderedNodes.length; i++) {
var node = orderedNodes[i];
// skip parallel path broken nodes as they are handled within updatePathBrokenForParallel()
if (pathBrokenNodes[node.key] && !node.endParallelNodeKey && !node.endDecisionNodeKey) {
// update all edges from this particular path broken node in pbNode
var pbNode = pathBrokenNodes[node.key];
if (pbNode.endNode && pbNode.edgeFromPathBroken && pbNode.edgesFromEnd) {
var edgeFrom = pbNode.edgeFromPathBroken;
for (j = 0; j < pbNode.edgesFromEnd.length; j++) {
var edgeTo = pbNode.edgesFromEnd[j];
edgeTo.from = edgeFrom.from;
edgeTo.fromPort = edgeFrom.fromPort;
}
// copy END OF field from the deleted END OF node to the path broken
// this copy will enable the path broken node to function as the
// END OF node deleted in UI
if (pbNode.endNode.loopStartNodeKey) {
node.loopStartNodeKey = pbNode.endNode.loopStartNodeKey;
} else if (pbNode.endNode.sourceIfNodeKey) {
node.sourceIfNodeKey = pbNode.endNode.sourceIfNodeKey;
} else if (pbNode.endNode.endParallelNodeKey) {
node.endParallelNodeKey = pbNode.endNode.endParallelNodeKey;
} else if (pbNode.endNode.endDecisionNodeKey) {
node.endDecisionNodeKey = pbNode.endNode.endDecisionNodeKey;
}
edgesToDelete[edgeFrom.to] = edgeFrom;
nodesToDelete[pbNode.endNode.key] = pbNode.endNode;
}
}
}
FlowDiagramBuilderApi.deleteNodesIfKeyMatch(nodes, nodesToDelete);
FlowDiagramBuilderApi.deleteEdgesIfToMatch(edges, edgesToDelete);
};
FlowDiagramBuilderApi.updateRecordsToDelete = function(node, edgesFrom, edgesTo, edgesToDelete, nodesToDelete) {
if (node, edgesFrom, edgesTo, edgesToDelete, nodesToDelete) {
var edgeFrom = edgesFrom[node.key];
var edgeTo = edgesTo[node.key];
if (edgeFrom && edgeTo) {
// update edgeFrom, delete edgeTo, delete node
edgeFrom.from = edgeTo.from;
edgeFrom.fromPort = edgeTo.fromPort;
edgesTo[node.key] = null;
edgesToDelete[edgeTo.to] = edgeTo;
nodesToDelete[node.key] = node;
}
}
};
FlowDiagramBuilderApi.deleteNodesIfKeyMatch = function(nodes, nodesToDelete) {
for (var i = 0; i < nodes.length; i++) {
if (nodesToDelete[nodes[i].key]) {
nodes.splice(i, 1);
i--;
}
}
};
FlowDiagramBuilderApi.deleteEdgesIfToMatch = function(edges, edgesToDelete) {
for (var i = 0; i < edges.length; i++) {
// Do not delete GoBackTo edge as we will update later
if (edgesToDelete[edges[i].to] && FlowDiagramBuilderApi.instanceRecordsHash[edges[i].from].type !== FlowDiagramConstants.FLOW_LOGIC_TYPES.GOBACKTO) {
edges.splice(i, 1);
i--;
}
}
};
FlowDiagramBuilderApi.getInstanceRecordHashNodeType = function(key) {
if (key &&
FlowDiagramBuilderApi.instanceRecordsHash[key] &&
FlowDiagramBuilderApi.instanceRecordsHash[key].type) {
return FlowDiagramBuilderApi.instanceRecordsHash[key].type;
} else {
return null;
}
};
FlowDiagramBuilderApi.getHiddenRecordHashNodeType = function(key) {
if (key &&
FlowDiagramBuilderApi.hiddenRecordsHash[key] &&
FlowDiagramBuilderApi.hiddenRecordsHash[key].instanceType) {
return FlowDiagramBuilderApi.hiddenRecordsHash[key].instanceType;
} else {
return null;
}
};
FlowDiagramBuilderApi.getSourceIfKeysAsText = function(nodeKey) {
var sourceIfKeyText = "";
var sourceIfKeys = FlowDiagramBuilderApi.getSourceIfKeysFromNodeKey(nodeKey);
if (sourceIfKeys && sourceIfKeys instanceof Array && sourceIfKeys.length > 0) {
sourceIfKeyText = sourceIfKeys.join(',');
}
return sourceIfKeyText;
};
FlowDiagramBuilderApi.updateInstanceRecordHash = function(nodeKey, key, value) {
if (nodeKey && FlowDiagramBuilderApi.instanceRecordsHash[nodeKey]) {
FlowDiagramBuilderApi.instanceRecordsHash[nodeKey][key] = value;
}
};
FlowDiagramBuilderApi.isElseBlock = function(previousNode, nextNode, sourceNode, nodeType) {
// if previousNode and nextNode have match sourceIfKeys
if (sourceNode && previousNode && previousNode.sourceIfKeys && nextNode.sourceIfKeys) {
if (previousNode.sourceIfKeys.toString().includes(sourceNode.key) && nextNode.sourceIfKeys.toString().includes(sourceNode.key)) {
if ((previousNode.depth > nextNode.depth || nextNode.depth === (sourceNode.depth + 1)) && nextNode.arrayPosition > (previousNode.arrayPosition + 1)) {
return true;
} else if (nodeType && nodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSEIF) {
return true;
}
}
}
return false;
};
FlowDiagramBuilderApi.pushEndOfLogicKeys = function(node, keys, type) {
if (node) {
if (!node[type]) {
node[type] = [];
}
if (typeof keys === "string") {
node[type].push(keys);
}
if (keys instanceof Array) {
for (var i = 0; i < keys.length; i++) {
node[type].push(keys[i].key);
}
}
}
};
FlowDiagramBuilderApi.pushEndOfParallelKeys = function(node, keys) {
FlowDiagramBuilderApi.pushEndOfLogicKeys(node, keys, "endOfParallelKeys");
};
FlowDiagramBuilderApi.pushEndOfDecisionKeys = function(node, keys) {
FlowDiagramBuilderApi.pushEndOfLogicKeys(node, keys, "endOfDecisionKeys");
};
FlowDiagramBuilderApi.removeFromArrayByKey = function(array, key, value) {
if (!array || !(array instanceof Array) || !key || !value) {
return;
}
for (var i = 0; i < array.length; i++) {
if (array[i][key] === value) {
array.splice(i, 1);
break;
}
}
};
// Description: this method sets two attributes on nodes: parallelKey/decisioinoKey and endOfParallelKeys/endOfDecisionKeys
// parallelKey set when a node is identified as being within a PARALLEL block
// decisionKey set when a node is identified as being within a DECISION block
// endOfParallelKeys set when a node is identified as the node which ENDPARALLEL will connect to
// endOfDecisionKeys set when a node is identified as the node which ENDDECISION will connect to
FlowDiagramBuilderApi.updateEndOfParallelAndParallelBlock = function(nodes) {
// this value is [] where is there is no ancestor PARALLEL/DECISION. Can have multiple current parallel/decision keys
var foundParallelNodes = [];
var foundParallelNodesMap = {};
var currentParallelNode;
var parallelNodeActiveByDecision;
var foundDecisionNodes = [];
var foundDecisionNodesMap = {};
var currentDecisionNode;
var decisionNodeActiveByParallel;
var previousNodeType;
var orderedNodes = FlowDiagramBuilderApi.sortNodes(nodes);
var i, j, k;
for (i = 0; i < orderedNodes.length; i++) {
var currentNode = orderedNodes[i];
var previousNode;
if (i > 0) {
previousNode = orderedNodes[i - 1];
}
// only process nodes that are not Trigger or Add a node (ie have an arrayPosition)
if (currentNode && FlowDiagramBuilderApi.isValidArrayPosition(currentNode.arrayPosition)) {
var nodeType = FlowDiagramBuilderApi.getInstanceRecordHashNodeType(currentNode.key);
// Note: sourceKey can be - sourceForEachKeys, sourceIfKeys, parallelKey, decisionKey
var sourceIfNode = FlowDiagramBuilderApi.getImmediateSourceIfKeyNodeFromNode(currentNode);
var processedParallelResult = FlowDiagramBuilderApi.processParallel(nodeType, currentNode, previousNode, sourceIfNode, parallelNodeActiveByDecision, currentParallelNode, foundParallelNodes, foundParallelNodesMap);
parallelNodeActiveByDecision = processedParallelResult.parallelNodeActiveByDecision;
currentParallelNode = processedParallelResult.currentParallelNode;
var processedDecisionResult = FlowDiagramBuilderApi.processDecision(nodeType, currentNode, previousNode, sourceIfNode, decisionNodeActiveByParallel, currentDecisionNode, foundDecisionNodes, foundDecisionNodesMap);
decisionNodeActiveByParallel = processedDecisionResult.decisionNodeActiveByParallel;
currentDecisionNode = processedDecisionResult.currentDecisionNode;
} else if (currentNode && currentNode.key === FlowDiagramBuilderApi.endKey) {
if (foundParallelNodes.length > 0) {
// we have reached last node "Add a node" signifying end of PARALLEL
FlowDiagramBuilderApi.pushEndOfParallelKeys(currentNode, foundParallelNodes);
}
if (foundDecisionNodes.length > 0) {
// we have reached last node "Add a node" signifying end of DECISION
FlowDiagramBuilderApi.pushEndOfDecisionKeys(currentNode, foundDecisionNodes);
}
}
}
};
FlowDiagramBuilderApi.processParallel = function(nodeType, currentNode, previousNode, sourceIfNode, parallelNodeActiveByDecision, currentParallelNode, foundParallelNodes, foundParallelNodesMap) {
// 0. if parallelNodeActiveByDecision then there is an active parallel logic that has not ended because of
// make-a-decision flow logic. If the current node is not part a DECISION BLOCK logic, then claim the
// end of the active parallel logic. Another way for the active parallel logic to end with
// parallelNodeActiveByDecision (having a value) is reaching the end node, which will claim
// end of all active parallel logic.
var parallelNode;
var previousNodeType;
if (parallelNodeActiveByDecision && !(nodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISIONBLOCK || currentNode.decisionBlockKey)) {
parallelNode = parallelNodeActiveByDecision;
parallelNodeActiveByDecision = null;
FlowDiagramBuilderApi.pushEndOfParallelKeys(currentNode, parallelNode.key);
if (currentParallelNode && currentParallelNode.key === parallelNode.key) {
currentParallelNode = null;
}
// remove from foundParallelNodes
for (k = 0; k < foundParallelNodes.length; k++) {
FlowDiagramBuilderApi.removeFromArrayByKey(foundParallelNodes, "key", parallelNode.key);
}
}
// 1. set the parallelKey attribute
if (nodeType && (nodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLEL)) {
currentParallelNode = currentNode;
foundParallelNodesMap[currentNode.key] = currentNode;
foundParallelNodes.push({
key: currentNode.key
});
currentNode.parallelKey = currentNode.key;
FlowDiagramBuilderApi.updateInstanceRecordHash(currentNode.key, "parallelKey", currentNode.key);
} else if (currentParallelNode && (currentNode.depth > currentParallelNode.depth || currentNode.parallelBlockKey || (nodeType && nodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLELBLOCK))) {
// is a child of PARALLEL node
currentNode.parallelKey = currentParallelNode.key;
FlowDiagramBuilderApi.updateInstanceRecordHash(currentNode.key, "parallelKey", currentParallelNode.key);
}
// 2. set the endOfParallelKeys attribute on first node outside of PARALLEL block, or set flag endOfIfKey if we need to set on first node out of IF block
if (previousNode && previousNode.parallelKey && foundParallelNodesMap[previousNode.parallelKey] && ((!currentNode.parallelKey) || (nodeType && (nodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLEL)))) {
// first node outside of PARALLEL block
parallelNode = foundParallelNodesMap[previousNode.parallelKey];
previousNodeType = FlowDiagramBuilderApi.getInstanceRecordHashNodeType(previousNode.key);
// if previousNode was a part of a decision block and currentNode type is DECISIONBLOCK (which means previousNode
// was involved in a decisionBlock and currentNode is beginning of a new decisionBlock under a same make-a-decision
// flow logic) or PARALLELBLOCK, current parallel logic does not end with the currentNode. Rather, current parallel logic ends
// when current make-a-decision flow logic ends.
if ((previousNodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISIONBLOCK || previousNode.decisionBlockKey) &&
nodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISIONBLOCK || nodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLELBLOCK) {
parallelNodeActiveByDecision = parallelNode;
} else if (!FlowDiagramBuilderApi.isElseBlock(previousNode, currentNode, sourceIfNode, nodeType)) {
FlowDiagramBuilderApi.pushEndOfParallelKeys(currentNode, parallelNode.key);
if (currentParallelNode && currentParallelNode.key === parallelNode.key) {
currentParallelNode = null;
}
// remove from foundParallelNodes
for (k = 0; k < foundParallelNodes.length; k++) {
FlowDiagramBuilderApi.removeFromArrayByKey(foundParallelNodes, "key", parallelNode.key);
}
} else {
// add endOfIfKey to foundParallelNodes
for (k = 0; k < foundParallelNodes.length; k++) {
if (foundParallelNodes[k].key === parallelNode.key) {
foundParallelNodes[k].endOfIfKey = sourceIfNode.key;
if (currentParallelNode && currentParallelNode.key === parallelNode.key) {
currentParallelNode = null;
}
break;
}
}
}
}
if (foundParallelNodes.length > 0) {
// a PARALLEL node can also be end of PARALLEL from a previous PARALLEL node but its children cannot
if (!currentNode.parallelKey || (nodeType && nodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLEL)) {
// loop foundParallelNodes check if we should set endOfParallelKeys on currentNode
var copyOfFoundParallelNodes = JSON.parse(JSON.stringify(foundParallelNodes));
for (j = 0; j < copyOfFoundParallelNodes.length; j++) {
parallelNode = copyOfFoundParallelNodes[j];
if (parallelNode.endOfIfKey) {
if (currentNode.endOfIfKeys && parallelNode.endOfIfKey === currentNode.endOfIfKeys) {
FlowDiagramBuilderApi.pushEndOfParallelKeys(currentNode, parallelNode.key);
if (currentParallelNode && currentParallelNode.key === parallelNode.key) {
currentParallelNode = null;
}
FlowDiagramBuilderApi.removeFromArrayByKey(foundParallelNodes, "key", parallelNode.key);
}
}
}
}
}
return {
parallelNodeActiveByDecision: parallelNodeActiveByDecision,
currentParallelNode: currentParallelNode
};
};
FlowDiagramBuilderApi.processDecision = function(nodeType, currentNode, previousNode, sourceIfNode, decisionNodeActiveByParallel, currentDecisionNode, foundDecisionNodes, foundDecisionNodesMap) {
// 0. if decisionNodeActiveByParallel then there is an active decision logic that has not ended because of
// make-a-parallel flow logic. If the current node is not part a PARALLEL BLOCK logic, then claim the
// end of the active decision logic. Another way for the active decision logic to end with
// decisionNodeActiveByParallel (having a value) is reaching the end node, which will claim
// end of all active decision logic.
var decisionNode;
var previousNodeType;
if (decisionNodeActiveByParallel && !(nodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLELBLOCK || currentNode.parallelBlockKey)) {
decisionNode = decisionNodeActiveByParallel;
decisionNodeActiveByParallel = null;
FlowDiagramBuilderApi.pushEndOfDecisionKeys(currentNode, decisionNode.key);
if (currentDecisionNode && currentDecisionNode.key === decisionNode.key) {
currentDecisionNode = null;
}
// remove from foundParallelNodes
for (k = 0; k < foundDecisionNodes.length; k++) {
FlowDiagramBuilderApi.removeFromArrayByKey(foundDecisionNodes, "key", decisionNode.key);
}
}
// 1. set the decisionKey attribute
if (nodeType && (nodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISION)) {
currentDecisionNode = currentNode;
foundDecisionNodesMap[currentNode.key] = currentNode;
foundDecisionNodes.push({
key: currentNode.key
});
currentNode.decisionKey = currentNode.key;
FlowDiagramBuilderApi.updateInstanceRecordHash(currentNode.key, "decisionKey", currentNode.key);
} else if (currentDecisionNode && (currentNode.depth > currentDecisionNode.depth || currentNode.decisionBlockKey || (nodeType && nodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISIONBLOCK))) {
// is a child of DECISION node
currentNode.decisionKey = currentDecisionNode.key;
FlowDiagramBuilderApi.updateInstanceRecordHash(currentNode.key, "decisionKey", currentDecisionNode.key);
}
// 2. set the endOfDecisionKeys attribute on first node outside of DECISION block, or set flag endOfIfKey if we need to set on first node out of IF block
if (previousNode && previousNode.decisionKey && foundDecisionNodesMap[previousNode.decisionKey] && ((!currentNode.decisionKey) || (nodeType && (nodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISION)))) {
// first node outside of DECISION block
decisionNode = foundDecisionNodesMap[previousNode.decisionKey];
previousNodeType = FlowDiagramBuilderApi.getInstanceRecordHashNodeType(previousNode.key);
// if previousNode was a part of a decision block and currentNode type is DECISIONBLOCK (which means previousNode
// was involved in a decisionBlock and currentNode is beginning of a new decisionBlock under a same make-a-decision
// flow logic) or PARALLELBLOCK, current parallel logic does not end with the currentNode. Rather, current decision logic ends
// when current make-a-decision flow logic ends.
if ((previousNodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISIONBLOCK || previousNode.decisionBlockKey) &&
nodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISIONBLOCK || nodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLELBLOCK) {
//nodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISIONBLOCK ) {
decisionNodeActiveByParallel = decisionNode;
} else if (!FlowDiagramBuilderApi.isElseBlock(previousNode, currentNode, sourceIfNode, nodeType)) {
FlowDiagramBuilderApi.pushEndOfDecisionKeys(currentNode, decisionNode.key);
if (currentDecisionNode && currentDecisionNode.key === decisionNode.key) {
currentDecisionNode = null;
}
// remove from foundDecisionNodes
for (k = 0; k < foundDecisionNodes.length; k++) {
FlowDiagramBuilderApi.removeFromArrayByKey(foundDecisionNodes, "key", decisionNode.key);
}
} else {
// add endOfIfKey to foundParallelNodes
for (k = 0; k < foundDecisionNodes.length; k++) {
if (foundDecisionNodes[k].key === decisionNode.key) {
foundDecisionNodes[k].endOfIfKey = sourceIfNode.key;
if (currentDecisionNode && currentDecisionNode.key === decisionNode.key) {
currentDecisionNode = null;
}
break;
}
}
}
}
if (foundDecisionNodes.length > 0) {
// a DECISION node can also be end of DECISION from a previous DECISION node but its children cannot
if (!currentNode.decisionKey || (nodeType && nodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISION)) {
// loop foundDecisionNodes check if we should set endOfDecisionKeys on currentNode
var copyOfFoundDecisionNodes = JSON.parse(JSON.stringify(foundDecisionNodes));
for (j = 0; j < copyOfFoundDecisionNodes.length; j++) {
decisionNode = copyOfFoundDecisionNodes[j];
if (decisionNode.endOfIfKey) {
if (currentNode.endOfIfKeys && decisionNode.endOfIfKey === currentNode.endOfIfKeys) {
FlowDiagramBuilderApi.pushEndOfDecisionKeys(currentNode, decisionNode.key);
if (currentDecisionNode && currentDecisionNode.key === decisionNode.key) {
currentDecisionNode = null;
}
FlowDiagramBuilderApi.removeFromArrayByKey(foundDecisionNodes, "key", decisionNode.key);
}
}
}
}
}
return {
decisionNodeActiveByParallel: decisionNodeActiveByParallel,
currentDecisionNode: currentDecisionNode
};
};
FlowDiagramBuilderApi.getConfiguration = function() {
return FlowDiagramDatabaseApi.getConfiguration(FlowDiagramConstants.SYS_IDS.SN_FLOW_DIAGRAM_BUILDER_CONFIGURATION);
};
// put the nodes back to the ascending order because the matched nodes from the flow-diagramming search need to be in the ascending order.
FlowDiagramBuilderApi.assignSortedNodesToDataModel = function(dataModel) {
var nodes = dataModel.diagramJSON.nodes;
dataModel.diagramJSON.nodes = FlowDiagramBuilderApi.sortNodes(nodes);
};
FlowDiagramBuilderApi.isValidArrayPosition = function(arrayPosition) {
if ((arrayPosition === 0 || arrayPosition === "0") || arrayPosition) {
return true;
} else {
return false;
}
};
// order is optional and input can be "asc" and "desc". "asc" if order is not present
FlowDiagramBuilderApi.sortNodes = function(nodes, order) {
var node;
for (i = 0; i < nodes.length; i++) {
node = nodes[i];
if (node.key === FlowDiagramBuilderApi.endKey) {
node.arrayPosition = FlowDiagramBuilderApi.maxInteger;
} else if (!(FlowDiagramBuilderApi.isValidArrayPosition(node.arrayPosition))) {
node.arrayPosition = -1;
}
}
var orderedNodes;
if (order) {
orderedNodes = FlowDiagramHelper.sortByArrayPosition(nodes, order);
} else {
orderedNodes = FlowDiagramHelper.sortByArrayPosition(nodes);
}
for (i = 0; i < orderedNodes.length; i++) {
node = orderedNodes[i];
if (node.key === FlowDiagramBuilderApi.endKey || node.arrayPosition === -1) {
delete node.arrayPosition;
}
}
return orderedNodes;
};
// 1) for every node that has loopback edge, record the loopback edge's destination as loopBackNodes in FlowDiagramBuilderApi.instanceRecordsHash
// 2) find all edges and store loopback edges and non-loopback edges separately based on edge source node
// then for every loopback edge, get a list of non-loopback edges if source node of loopback edge equals source node of non-loopback edge
// create loopExitNodeHash and for every designation(to) node of non-loopback edge, identify designation (to) node of the loopback edge that
// share the same source node
FlowDiagramBuilderApi.setupLoopExitNodeHashAndloopBackNodes = function(nodes, edges) {
var i, j;
var loopExitNodeHash = {};
var loopbackEdgeHash = {};
var nonLoopbackEdgeHash = {};
for (i = 0; i < edges.length; i++) {
var currentEdgeFrom = edges[i].from;
var currentEdgeTo = edges[i].to;
if (edges[i].toPort === FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_INPUT_LOOP_BACK_SYS_ID) {
if (!loopbackEdgeHash[currentEdgeFrom])
loopbackEdgeHash[currentEdgeFrom] = [];
if (FlowDiagramBuilderApi.instanceRecordsHash[currentEdgeFrom] &&
FlowDiagramBuilderApi.instanceRecordsHash[currentEdgeTo]) {
if (!FlowDiagramBuilderApi.instanceRecordsHash[currentEdgeFrom].loopBackNodes)
FlowDiagramBuilderApi.instanceRecordsHash[currentEdgeFrom].loopBackNodes = [];
// Remember that loopback can point to itself and therefore there could be a record in
// FlowDiagramBuilderApi.instanceRecordsHash[currentEdgeFrom].loopBackNodes that points to
// FlowDiagramBuilderApi.instanceRecordsHash[currentEdgeFrom]
FlowDiagramBuilderApi.instanceRecordsHash[currentEdgeFrom].loopBackNodes.push(FlowDiagramBuilderApi.instanceRecordsHash[currentEdgeTo]);
}
loopbackEdgeHash[currentEdgeFrom].push(currentEdgeTo);
} else {
if (!nonLoopbackEdgeHash[currentEdgeFrom])
nonLoopbackEdgeHash[currentEdgeFrom] = [];
nonLoopbackEdgeHash[currentEdgeFrom].push(currentEdgeTo);
}
}
for (var key in loopbackEdgeHash) {
if (Object.prototype.hasOwnProperty.call(loopbackEdgeHash, key)) {
var loopbackEdges = loopbackEdgeHash[key];
for (i = 0; i < loopbackEdges.length; i++) {
var loopStartNodeKey = loopbackEdges[i];
var loopExitEdges = nonLoopbackEdgeHash[key];
if (loopExitEdges) {
for (j = 0; j < loopExitEdges.length; j++) {
var loopExitEdgeTo = loopExitEdges[j];
if (!loopExitNodeHash[loopExitEdgeTo])
loopExitNodeHash[loopExitEdgeTo] = {};
if (!loopExitNodeHash[loopExitEdgeTo][loopStartNodeKey])
loopExitNodeHash[loopExitEdgeTo][loopStartNodeKey] = loopStartNodeKey;
}
}
}
}
}
return loopExitNodeHash;
};
// for the target "node", returns all end of if nodes and starting for loop nodes
FlowDiagramBuilderApi.getSourceNodes = function(node, loopExitNodeHash) {
var i;
var sourceNodes = [];
if (node.endOfIfKeys) {
var endOfIfkeys = node.endOfIfKeys.split(',');
for (i = 0; i < endOfIfkeys.length; i++) {
var sourceIfKey = endOfIfkeys[i];
var sourceIfNode = FlowDiagramBuilderApi.instanceRecordsHash[sourceIfKey];
if (sourceIfNode)
sourceNodes.push(sourceIfNode);
}
}
var loopExitNodes = loopExitNodeHash[node.key];
if (loopExitNodes) {
var processedLoopStartKeyHash = {};
for (var key in loopExitNodes) {
if (Object.prototype.hasOwnProperty.call(loopExitNodes, key)) {
var loopStartKey = loopExitNodes[key];
if (!processedLoopStartKeyHash[loopStartKey]) {
processedLoopStartKeyHash[loopStartKey] = true;
if (FlowDiagramBuilderApi.instanceRecordsHash[loopStartKey])
sourceNodes.push(FlowDiagramBuilderApi.instanceRecordsHash[loopStartKey]);
}
}
}
}
if (node.endOfParallelKeys) {
for (i = 0; i < node.endOfParallelKeys.length; i++) {
sourceNodes.push(FlowDiagramBuilderApi.instanceRecordsHash[node.endOfParallelKeys[i]]);
}
}
if (node.endOfDecisionKeys) {
for (i = 0; i < node.endOfDecisionKeys.length; i++) {
sourceNodes.push(FlowDiagramBuilderApi.instanceRecordsHash[node.endOfDecisionKeys[i]]);
}
}
return sourceNodes;
};
// assuming that the newNodeList is sorted, the function will iterate through
// newNodeList in order and return node that has depth lower than newNode
// toNode is valid only if it is part of newNodesPerNodeHash; newNodesPerNodeHash contains all end nodes created for "node"
FlowDiagramBuilderApi.getToNode = function(newNodeList, newNode, newNodesPerNodeHash) {
var node = null;
var sourceIfKeys = null;
if (newNode.sourceIfNodeKey) {
sourceIfKeys = FlowDiagramBuilderApi.getSourceIfKeysFromNodeKey(newNode.sourceIfNodeKey);
if (sourceIfKeys && sourceIfKeys instanceof Array && sourceIfKeys.length > 0) {
sourceIfKeys = sourceIfKeys.join(',');
}
}
for (var i = 0; i < newNodeList.length; i++) {
var sourceIfNodeKeyMatch = true;
// check to see if newNode's sourceIfNodeKey's sourceIfKeys include tempNode's sourceIfNodeKey if newNode is endif and tempNode is endif
// which means tempNode's sourceIfNodeKey is an ancestor of newNode's sourceIfNodeKey
if (newNodeList[i].depth < newNode.depth) {
var tempNode = newNodeList[i];
if (sourceIfKeys && tempNode.sourceIfNodeKey) {
sourceIfNodeKeyMatch = false;
if (sourceIfKeys.includes(tempNode.sourceIfNodeKey))
sourceIfNodeKeyMatch = true;
}
if (newNodesPerNodeHash[tempNode.key] && sourceIfNodeKeyMatch) {
var result = FlowDiagramBuilderApi.processNodeIfTempNodeAndNewNodeIsUnderParallelOrDecision(tempNode, newNode);
if (result) {
node = result;
break;
}
}
}
}
return node;
};
FlowDiagramBuilderApi.processNodeIfTempNodeAndNewNodeIsUnderParallelOrDecision = function(tempNode, newNode) {
var node;
// if tempNode is ENDPARALLEL node and the newNode (END OF node) is under a parallel node (but not Decision type), then
// return the tempNode only if it is the parallelNode that the newNode is under (newNode.parallelKey)
// if tempNode is ENDDECISION node and the newNode (END OF node) is under a decision node (but not Parallel type), then
// return the tempNode only if it is the decisionNode that the newNode is under (newNode.decisionKey)
if (tempNode && newNode) {
var paralleKeyMatchIfEndParallelAndNewNodeUnderParallelOrDecisionKey = true;
if (tempNode.instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDPARALLEL && newNode.parallelKey && !newNode.instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDDECISION) {
paralleKeyMatchIfEndParallelAndNewNodeUnderParallelOrDecisionKey = false;
if (tempNode.parallelKey === newNode.parallelKey) {
paralleKeyMatchIfEndParallelAndNewNodeUnderParallelOrDecisionKey = true;
}
} else if (tempNode.instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDDECISION && newNode.decisionKey && !newNode.instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDPARALLEL) {
paralleKeyMatchIfEndParallelAndNewNodeUnderParallelOrDecisionKey = false;
if (tempNode.decisionKey === newNode.decisionKey) {
paralleKeyMatchIfEndParallelAndNewNodeUnderParallelOrDecisionKey = true;
}
}
if (paralleKeyMatchIfEndParallelAndNewNodeUnderParallelOrDecisionKey) {
node = tempNode;
}
}
return node;
};
FlowDiagramBuilderApi.getToEndNodeToPort = function(type) {
var toPort = null;
if (type === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDLOOP)
toPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_END_LOOP_INPUT_SYS_ID;
else if (type === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDDOLOOP)
toPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_END_DO_LOOP_INPUT_SYS_ID;
else if (type === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDIF)
toPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_END_IF_INPUT_SYS_ID;
else if (type === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDPARALLEL)
toPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_END_PARALLEL_INPUT_SYS_ID;
else if (type === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDDECISION)
toPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_END_DECISION_INPUT_SYS_ID;
return toPort;
};
FlowDiagramBuilderApi.removeAllLoopbackEdges = function(edges) {
// remove all loopback edges since these will be added back by ENDLOOP node implementation
// the loopback edge data has been stored by setupLoopExitNodeHashAndloopBackNodes()
for (var i = 0; i < edges.length; i++) {
if (edges[i].toPort === FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_INPUT_LOOP_BACK_SYS_ID) {
edges.splice(i, 1);
i--;
}
}
};
FlowDiagramBuilderApi.findNodesWithAtLeastOneBranch = function(nodes) {
// map of 1-to-many nodes with at least one parallel branch or one decision branch
var branchMap = {};
for (var k = 0; k < nodes.length; k++) {
node = nodes[k];
if (node.name === FlowDiagramConstants.NODE_NAMES.PARALLEL_BRANCH || node.node_id === FlowDiagramConstants.SYS_IDS.FLOW_LOGIC_DECISION_BLOCK_ACTION) {
branchMap[node.parallelKey] = true;
}
}
return branchMap;
};
FlowDiagramBuilderApi.isTypeEndNode = function(instanceType) {
if (
instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDIF ||
instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDLOOP ||
instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDDOLOOP ||
instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDPARALLEL ||
instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDDECISION
) {
return true;
}
return false;
};
FlowDiagramBuilderApi.addLoopbackEdge = function(edges, newNode, sourceNode) {
// if the newly created END node is an endloop node, then add a loopback edge to the orginal
// FOR LOOP node
if (newNode.instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDLOOP) {
var newEdgeLoopBack = FlowDiagramTranslateModel.getEdgeModel();
if (newEdgeLoopBack) {
newEdgeLoopBack = FlowDiagramBuilderApi.updateEdge(newEdgeLoopBack, {
from: newNode.key,
to: sourceNode.key,
fromPort: FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_END_LOOP_OUTPUT_SYS_ID,
toPort: FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_INPUT_LOOP_BACK_SYS_ID,
edgeId: FlowDiagramBuilderApi.edgeId,
linkLabel: FlowDiagramConstants.LINK_LABELS.loopBackConnector,
labelColor: FlowDiagramConstants.LINK_LABEL_COLORS.loop,
addBtnVisibility: FlowDiagramConstants.LINK_ADD_BTN_VISIBILITY.forLoopLoopback,
});
}
edges.push(newEdgeLoopBack);
} else if (newNode.instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDDOLOOP) {
var newEdgeLoopBack = FlowDiagramTranslateModel.getEdgeModel();
if (newEdgeLoopBack) {
newEdgeLoopBack = FlowDiagramBuilderApi.updateEdge(newEdgeLoopBack, {
from: newNode.key,
to: sourceNode.key,
fromPort: FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_END_DO_LOOP_OUTPUT_LOOPBACK_SYS_ID,
toPort: FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_INPUT_LOOP_BACK_SYS_ID,
edgeId: FlowDiagramBuilderApi.edgeId,
linkLabel: FlowDiagramConstants.LINK_LABELS.loopBackConnector,
labelColor: FlowDiagramConstants.LINK_LABEL_COLORS.loop,
addBtnVisibility: FlowDiagramConstants.LINK_ADD_BTN_VISIBILITY.forLoopLoopback,
});
}
edges.push(newEdgeLoopBack);
}
};
FlowDiagramBuilderApi.processEndNodes = function(nodes, edges, loopExitNodeHash) {
var i, j, k;
var node;
var newNodesBySourceKeyHash = {};
var newNodeList = [];
// remove all loopback edges since these will be added back by ENDLOOP node implementation
FlowDiagramBuilderApi.removeAllLoopbackEdges(edges);
// map of 1-to-many nodes with more than one parallel or decision branch
var hasBranchMap = FlowDiagramBuilderApi.findNodesWithAtLeastOneBranch(nodes);
nodes = FlowDiagramBuilderApi.sortNodes(nodes, "desc");
for (i = 0; i < nodes.length; i++) {
node = nodes[i];
var newNodesPerNodeHash = {};
var isEndNode = FlowDiagramBuilderApi.isTypeEndNode(node.instanceType);
if (!isEndNode) {
// for the target "node", returns all end of if nodes and starting for loop nodes
var sourceNodes = FlowDiagramBuilderApi.getSourceNodes(node, loopExitNodeHash);
// sourceNodes exists for the node.key which means that there is either a loopback edge from this node
// or if statement ends with this node
if (sourceNodes.length > 0) {
if (FlowDiagramHelper.hasProperty(sourceNodes[0], "customOrder")) {
sourceNodes = FlowDiagramHelper.sortByCustomOrder(sourceNodes);
} else {
sourceNodes = FlowDiagramHelper.sortByDepth(sourceNodes);
}
var lastCreatedNode = null;
var lastCreatedNodeFromPort;
var lastCreatedNodeType;
var newEdge;
var newEdgeLabel = "";
var anyEndNodeAdded = false;
var toNode;
var toNodeToPort;
// reviewing sourceNode that has lowest depth first, which should be connected to node
for (j = 0; j < sourceNodes.length; j++) {
var sourceNode = sourceNodes[j];
// if new END NODE has not been created for the target sourceNode.key
if (!newNodesBySourceKeyHash[sourceNode.key]) {
anyEndNodeAdded = true;
var newNodeType;
var newNodeFromPort;
if (sourceNode.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.IF) {
newNodeType = FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDIF;
newNodeFromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_END_IF_OUTPUT_SYS_ID;
} else if (sourceNode.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.FOREACH) {
newNodeType = FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDLOOP;
newNodeFromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_END_LOOP_OUTPUT_SYS_ID;
} else if (sourceNode.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.DOUNTIL) {
newNodeType = FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDDOLOOP;
newNodeFromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_END_DO_LOOP_OUTPUT_SYS_ID;
} else if (sourceNode.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLEL) {
newNodeType = FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDPARALLEL;
newNodeFromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_END_PARALLEL_OUTPUT_SYS_ID;
} else if (sourceNode.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISION) {
newNodeType = FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDDECISION;
newNodeFromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_END_DECISION_OUTPUT_SYS_ID;
}
// create END node for each unique sourceNode
// First END node should have highest depth among END nodes
var newNode = FlowDiagramBuilderApi.createHiddenNode(nodes, sourceNode.key, sourceNode.parallelKey, sourceNode.decisionKey, newNodeType, sourceNode.depth, false);
if (sourceNode.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.DOUNTIL) {
newNode.name = FlowDiagramHelper.generateNameWithCondition(FlowDiagramConstants.NODE_NAMES.UNTIL, sourceNode.data);
newNode.order = sourceNode.orderBottom;
newNode.arrayPosition = sourceNode.arrayPosition;
newNode.icon = sourceNode.icon;
}
if (FlowDiagramHelper.hasProperty(sourceNode, "customOrder")) {
newNode.customOrder = sourceNode.customOrder;
}
// if it is not the first END node created for the node, create an edge between last END node
// created and current END node created
if (j !== 0) {
// toNode cannot be node on same/lower depth and thus newNode should be connected to closest node that is not on the same/lower depth
// toNode is valid only if it is part of newNodesPerNodeHash; newNodesPerNodeHash contains all end nodes created for "node"
toNode = FlowDiagramBuilderApi.getToNode(newNodeList, newNode, newNodesPerNodeHash);
if (toNode) {
if (toNode.instanceType) {
toNodeToPort = FlowDiagramBuilderApi.getToEndNodeToPort(toNode.instanceType);
}
newEdge = FlowDiagramTranslateModel.getEdgeModel();
if (newEdge) {
newEdge = FlowDiagramBuilderApi.updateEdge(
newEdge, {
from: newNode.key,
to: toNode.key,
fromPort: newNodeFromPort,
toPort: toNodeToPort,
edgeId: FlowDiagramBuilderApi.edgeId,
}
);
if (newNodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDLOOP || newNodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDDOLOOP) {
newEdge.linkLabel = FlowDiagramConstants.LINK_LABELS.loopContinueConnector;
newEdge.labelColor = FlowDiagramConstants.LINK_LABEL_COLORS.loop;
}
edges.push(newEdge);
}
}
}
newNodesBySourceKeyHash[sourceNode.key] = newNode;
newNodesPerNodeHash[newNode.key] = newNode;
newNodeList.push(newNode);
if (FlowDiagramHelper.hasProperty(sourceNode, "customOrder")) {
newNodeList = FlowDiagramHelper.sortByCustomOrder(newNodeList, 'desc');
} else {
newNodeList = FlowDiagramHelper.sortByDepth(newNodeList, 'desc');
}
// if the newly created END node is an endloop node, then add a loopback edge to the orginal
// FOR LOOP node
FlowDiagramBuilderApi.addLoopbackEdge(edges, newNode, sourceNode);
if (lastCreatedNode === null) {
lastCreatedNode = newNode;
lastCreatedNodeType = newNodeType;
if (newNodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDLOOP)
lastCreatedNodeFromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_END_LOOP_OUTPUT_SYS_ID;
else if (newNodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDDOLOOP)
lastCreatedNodeFromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_END_DO_LOOP_OUTPUT_SYS_ID;
else if (newNodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDIF)
lastCreatedNodeFromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_END_IF_OUTPUT_SYS_ID;
else if (newNodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDPARALLEL)
lastCreatedNodeFromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_END_PARALLEL_OUTPUT_SYS_ID;
else if (newNodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDDECISION)
lastCreatedNodeFromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_END_DECISION_OUTPUT_SYS_ID;
}
}
}
// true if any end node have been added for the target "node"
if (anyEndNodeAdded) {
var toPort;
// all edges pointing to target "node" should point to newly created END nodes
// if there are multiples of END nodes then decide by highest depth value
for (k = 0; k < edges.length; k++) {
var currentEdgeFrom = edges[k].from;
var currentEdgeTo = edges[k].to;
var currentEdgeToPort = edges[k].toPort;
if (currentEdgeTo === node.key &&
currentEdgeToPort !== FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_INPUT_LOOP_BACK_SYS_ID) {
// highestDepthLoopBackNode points to FOR LOOP node that ends with target "node" that has the
// highest depth value, which means that the FOR LOOP node is closest to the target "node" among
// other FOR LOOP nodes that ends with target "node"
var highestDepthLoopBackNode = null;
if (FlowDiagramBuilderApi.instanceRecordsHash[currentEdgeFrom] &&
FlowDiagramBuilderApi.instanceRecordsHash[currentEdgeFrom].loopBackNodes) {
var loopBackNodes = FlowDiagramBuilderApi.instanceRecordsHash[currentEdgeFrom].loopBackNodes;
if (loopBackNodes && loopBackNodes instanceof Array && loopBackNodes.length > 0) {
loopBackNodes = FlowDiagramHelper.sortByDepth(loopBackNodes, 'desc');
highestDepthLoopBackNode = loopBackNodes[0];
}
}
// sourceKey is the source if node associated with currentEdgeFrom
var sourceKey = null;
var parallelKey = null;
var decisionKey = null;
var currentEdgeFromNode = FlowDiagramBuilderApi.instanceRecordsHash[currentEdgeFrom];
if (currentEdgeFromNode) {
sourceKey = FlowDiagramBuilderApi.getImmediateSourceIfKeyFromNodeKey(currentEdgeFrom);
if (currentEdgeFromNode.parallelKey) {
parallelKey = currentEdgeFromNode.parallelKey;
}
if (currentEdgeFromNode.decisionKey) {
decisionKey = currentEdgeFromNode.decisionKey;
}
}
toNode = FlowDiagramBuilderApi.getCurrentEdgeFromToNode(newNodesBySourceKeyHash, sourceKey, parallelKey, decisionKey, highestDepthLoopBackNode);
// if Do Parallel has branches then remove the edge from ENDPARALLEL to Do Parallel
if (toNode) {
if (toNode.instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDLOOP)
toPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_END_LOOP_INPUT_SYS_ID;
else if (toNode.instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDDOLOOP)
toPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_END_DO_LOOP_INPUT_SYS_ID;
else if (toNode.instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDIF)
toPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_END_IF_INPUT_SYS_ID;
else if (toNode.instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDPARALLEL)
toPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_END_PARALLEL_INPUT_SYS_ID;
else if (toNode.instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDDECISION)
toPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_END_DECISION_INPUT_SYS_ID;
edges[k].to = toNode.key;
edges[k].toPort = toPort;
var removeParallelLoopbackEdge = toNode.endParallelNodeKey && toNode.endParallelNodeKey === edges[k].from && hasBranchMap[toNode.endParallelNodeKey];
var removeDecisionLoopbackEdge = toNode.endDecisionNodeKey && toNode.endDecisionNodeKey === edges[k].from && hasBranchMap[toNode.endDecisionNodeKey];
if (removeParallelLoopbackEdge || removeDecisionLoopbackEdge) {
edges.splice(k, 1);
}
}
}
}
// If the current node type is not FLOW_LOGIC_PATH_BROKEN_ACTION then an edge must be created
// that connects between the node and the lastly created END IF or END LOOP node
// Please note that all END nodes added will be placed/ordered before target "node"
var nodeFromInstanceRecordHash = FlowDiagramBuilderApi.instanceRecordsHash[node.key];
if (node.key === FlowDiagramBuilderApi.endKey ||
(nodeFromInstanceRecordHash && nodeFromInstanceRecordHash.type !== FlowDiagramConstants.SYS_IDS.FLOW_LOGIC_PATH_BROKEN_ACTION)) {
// endKey check must be first
if (node.key === FlowDiagramBuilderApi.endKey) {
toPort = FlowDiagramBuilderApi.endPort;
} else {
toPort = FlowDiagramBuilderApi.getToPort(nodeFromInstanceRecordHash.type);
}
newEdge = FlowDiagramTranslateModel.getEdgeModel();
if (newEdge) {
newEdge = FlowDiagramBuilderApi.updateEdge(
newEdge, {
from: lastCreatedNode.key,
to: node.key,
fromPort: lastCreatedNodeFromPort,
toPort: toPort,
edgeId: FlowDiagramBuilderApi.edgeId,
label: newEdgeLabel,
}
);
if (lastCreatedNodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDLOOP ||
lastCreatedNodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDDOLOOP) {
newEdge.linkLabel = FlowDiagramConstants.LINK_LABELS.loopContinueConnector;
newEdge.labelColor = FlowDiagramConstants.LINK_LABEL_COLORS.loop;
}
edges.push(newEdge);
}
}
}
}
}
}
return nodes;
};
FlowDiagramBuilderApi.getToPort = function(type) {
var toPort;
if (type === FlowDiagramConstants.INSTANCE_TYPES.ACTION_INSTANCE_TYPE) {
toPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_ACTION_INPUT_SYS_ID;
} else if (type === FlowDiagramConstants.FLOW_LOGIC_TYPES.IF ||
type === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSE ||
type === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSEIF) {
toPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_IF_ELSE_INPUT_SYS_ID;
} else if (type === FlowDiagramConstants.FLOW_LOGIC_TYPES.FOREACH || type === FlowDiagramConstants.FLOW_LOGIC_TYPES.DOUNTIL) {
toPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_INPUT_NORMAL_SYS_ID;
} else if (type === FlowDiagramConstants.FLOW_LOGIC_TYPES.END) {
toPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_END_INPUT_SYS_ID;
} else if (Object.keys(FlowDiagramConstants.FLOW_LOGIC_TYPES.ONE_TO_ONE).toString().includes(type)) {
toPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_ONE_TO_ONE_INPUT;
} else if (Object.keys(FlowDiagramConstants.FLOW_LOGIC_TYPES.ONE_TO_MANY).toString().includes(type)) {
toPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_ONE_TO_MANY_INPUT;
} else if (type === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLELBLOCK) {
toPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_PARALLELBLOCK_INPUT_SYS_ID;
} else if (type === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISIONBLOCK) {
toPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_DECISIONBLOCK_INPUT_SYS_ID;
}
return toPort;
};
// return the node with highest depth by comparing the source IF, FOR, PARALLEL, DECISION nodes if the nodes are present
FlowDiagramBuilderApi.getCurrentEdgeFromToNode = function(newNodesBySourceKeyHash, sourceKey, parallelKey, decisionKey, highestDepthLoopBackNode) {
var sourceKeyNode = FlowDiagramBuilderApi.instanceRecordsHash[sourceKey];
var parallelKeyNode = FlowDiagramBuilderApi.instanceRecordsHash[parallelKey];
var decisionKeyNode = FlowDiagramBuilderApi.instanceRecordsHash[decisionKey];
var toNode = null;
var ifDepth = -1;
var parallelDepth = -1;
var decisionDepth = -1;
var forDepth = -1;
if (sourceKeyNode && sourceKeyNode.depth) {
ifDepth = sourceKeyNode.depth;
}
if (parallelKeyNode && parallelKeyNode.depth) {
parallelDepth = parallelKeyNode.depth;
}
if (decisionKeyNode && decisionKeyNode.depth) {
decisionDepth = decisionKeyNode.depth;
}
if (highestDepthLoopBackNode && highestDepthLoopBackNode.depth) {
forDepth = highestDepthLoopBackNode.depth;
}
if (sourceKeyNode && ifDepth > parallelDepth && ifDepth > decisionDepth && ifDepth > forDepth) {
toNode = newNodesBySourceKeyHash[sourceKey];
} else if (parallelKeyNode && parallelDepth > ifDepth && parallelDepth > forDepth && parallelDepth > decisionDepth) {
toNode = newNodesBySourceKeyHash[parallelKey];
} else if (decisionKeyNode && decisionDepth > ifDepth && decisionDepth > forDepth && decisionDepth > parallelDepth) {
toNode = newNodesBySourceKeyHash[decisionKey];
} else if (highestDepthLoopBackNode && forDepth > ifDepth && forDepth > parallelDepth && forDepth > decisionDepth) {
toNode = newNodesBySourceKeyHash[highestDepthLoopBackNode.key];
}
return toNode;
};
FlowDiagramBuilderApi.decorateLinks = function(dataModel, nodeMap) {
function isTrueConnector(edge, fromNode) {
if (fromNode) {
return fromNode.node_id === FlowDiagramConstants.SYS_IDS.FLOW_LOGIC_CONDITIONAL_DIAGRAM_ACTION && edge.fromPort === FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_IF_ELSE_OUTPUT_TRUE_SYS_ID;
}
return false;
}
function isFalseConnector(edge, fromNode) {
if (fromNode) {
return fromNode.node_id === FlowDiagramConstants.SYS_IDS.FLOW_LOGIC_CONDITIONAL_DIAGRAM_ACTION && edge.fromPort === FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_IF_ELSE_OUTPUT_FALSE_SYS_ID;
}
return false;
}
function isLoopContinueConnector(edge, fromNode) {
if (fromNode) {
if (fromNode.instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDLOOP) {
return fromNode.instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDLOOP && edge.toPort !== FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_INPUT_LOOP_BACK_SYS_ID;
} else if (fromNode.instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDDOLOOP) {
return fromNode.instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDDOLOOP && edge.toPort !== FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_INPUT_DO_LOOP_BACK_SYS_ID;
}
}
return false;
}
function isLoopBackConnector(edge, fromNode) {
if (fromNode) {
if (fromNode.instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDLOOP) {
return fromNode.instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDLOOP && edge.toPort === FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_INPUT_LOOP_BACK_SYS_ID;
} else if (fromNode.instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDDOLOOP) {
return fromNode.instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDDOLOOP && edge.toPort === FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_INPUT_DO_LOOP_BACK_SYS_ID;
}
}
return false
}
function isDecisionConnector(fromNode) {
if (fromNode && fromNode.data && FlowDiagramHelper.hasProperty(fromNode.data, "decision_label")) {
return true;
}
return false;
}
function isFromPathBrokenConnector(edge, fromNode) {
if (fromNode) {
return fromNode.node_id === FlowDiagramConstants.SYS_IDS.FLOW_LOGIC_PATH_BROKEN_ACTION && edge.toPort !== FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_INPUT_LOOP_BACK_SYS_ID;
}
return false;
}
function isGoBackToConnector(edge) {
return edge.fromPort === FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_GOBACKTO_OUTPUT_SYS_ID;
}
dataModel.diagramJSON.edges.forEach(function(edge) {
var fromNode = nodeMap[edge.from];
var toNode = nodeMap[edge.to];
if (isTrueConnector(edge, fromNode)) {
edge.labelSegmentIndex = FlowDiagramConstants.LINK_LABEL_SEGMENT.trueConnector.segmentIndex;
edge.labelSegmentFraction = FlowDiagramConstants.LINK_LABEL_SEGMENT.trueConnector.segmentFraction;
edge.labelAlignmentFocus = FlowDiagramConstants.LINK_LABEL_SEGMENT.trueConnector.alignmentFocus;
edge.addBtnSegmentIndex = FlowDiagramConstants.LINK_ADD_BTN_SEGMENT.trueConnector.segmentIndex;
edge.addBtnSegmentFraction = FlowDiagramConstants.LINK_ADD_BTN_SEGMENT.trueConnector.segmentFraction;
} else if (isFalseConnector(edge, fromNode)) {
edge.labelSegmentIndex = FlowDiagramConstants.LINK_LABEL_SEGMENT.falseConnector.segmentIndex;
edge.labelSegmentFraction = FlowDiagramConstants.LINK_LABEL_SEGMENT.falseConnector.segmentFraction;
edge.labelAlignmentFocus = FlowDiagramConstants.LINK_LABEL_SEGMENT.falseConnector.alignmentFocus;
edge.addBtnSegmentIndex = FlowDiagramConstants.LINK_ADD_BTN_SEGMENT.falseConnector.segmentIndex;
edge.addBtnSegmentFraction = FlowDiagramConstants.LINK_ADD_BTN_SEGMENT.falseConnector.segmentFraction;
} else if (isDecisionConnector(fromNode)) {
edge.addBtnSegmentIndex = FlowDiagramConstants.LINK_ADD_BTN_SEGMENT.decisionConnector.segmentIndex;
edge.addBtnSegmentFraction = FlowDiagramConstants.LINK_ADD_BTN_SEGMENT.decisionConnector.segmentFraction;
edge.hideToArrow = FlowDiagramConstants.LINK_ADD_BTN_SEGMENT.decisionConnector.hideToArrow;
edge.addToEndSegmentLength = FlowDiagramConstants.LINK_ADD_BTN_SEGMENT.decisionConnector.addToEndSegmentLength;
// show the edge toArrow when there are no branches
if (!fromNode.relation || !fromNode.relation.children || fromNode.relation.children.length === 0) {
edge.hideToArrow = false;
}
// if toNode is Stage then hide the + button on the edge
if (toNode && toNode.key && FlowDiagramBuilderApi.stagesAssociatedNodeMap[toNode.key]) {
edge.addToEndSegmentLength = 0;
edge.addBtnVisibility = FlowDiagramConstants.LINK_ADD_BTN_VISIBILITY.forLoopLoopback;
}
} else if (isLoopBackConnector(edge, fromNode)) {
edge.labelSegmentIndex = FlowDiagramConstants.LINK_LABEL_SEGMENT.loopBackConnector.segmentIndex;
edge.labelSegmentFraction = FlowDiagramConstants.LINK_LABEL_SEGMENT.loopBackConnector.segmentFraction;
edge.labelAlignmentFocus = FlowDiagramConstants.LINK_LABEL_SEGMENT.loopBackConnector.alignmentFocus;
} else if (isLoopContinueConnector(edge, fromNode) || isFromPathBrokenConnector(edge, fromNode)) {
edge.labelSegmentIndex = FlowDiagramConstants.LINK_LABEL_SEGMENT.loopContinueConnector.segmentIndex;
edge.labelSegmentFraction = FlowDiagramConstants.LINK_LABEL_SEGMENT.loopContinueConnector.segmentFraction;
edge.labelAlignmentFocus = FlowDiagramConstants.LINK_LABEL_SEGMENT.loopContinueConnector.alignmentFocus;
edge.addBtnSegmentIndex = FlowDiagramConstants.LINK_ADD_BTN_SEGMENT.loopContinueConnector.segmentIndex;
edge.addBtnSegmentFraction = FlowDiagramConstants.LINK_ADD_BTN_SEGMENT.loopContinueConnector.segmentFraction;
} else if (isGoBackToConnector(edge)) {
edge.labelSegmentIndex = FlowDiagramConstants.LINK_LABEL_SEGMENT.goBackToConnector.segmentIndex;
edge.labelSegmentFraction = FlowDiagramConstants.LINK_LABEL_SEGMENT.goBackToConnector.segmentFraction;
edge.labelAlignmentFocus = FlowDiagramConstants.LINK_LABEL_SEGMENT.goBackToConnector.alignmentFocus;
edge.addBtnSegmentIndex = FlowDiagramConstants.LINK_LABEL_SEGMENT.goBackToConnector.segmentIndex;
edge.addBtnSegmentFraction = FlowDiagramConstants.LINK_LABEL_SEGMENT.goBackToConnector.segmentFraction;
} else {
edge.addBtnSegmentIndex = FlowDiagramConstants.LINK_ADD_BTN_SEGMENT.normal.segmentIndex;
edge.addBtnSegmentFraction = FlowDiagramConstants.LINK_ADD_BTN_SEGMENT.normal.segmentFraction;
}
});
};
FlowDiagramBuilderApi.addDataModelRelationship = function(flowJSON, dataModel) {
if (!flowJSON || !flowJSON.componentInstances)
return;
var instances = flowJSON.componentInstances;
var instanceRelations = {};
var typeLookup = {};
if (instances.length > 0) {
var currentDepth1 = instances[0];
instances.forEach(function(instance) {
var key = instance.uiUniqueIdentifier;
var children = instance.children || [];
var selfRelation = instanceRelations[instance.uiUniqueIdentifier];
if (!selfRelation) selfRelation = {};
selfRelation.parent = instance.parent;
selfRelation.children = instance.children;
if (instance.depth === 1 && key !== currentDepth1.uiUniqueIdentifier) {
var currentDepth1Id = currentDepth1.uiUniqueIdentifier;
selfRelation.previousSibling = currentDepth1Id;
var previousSibling = instanceRelations[currentDepth1Id];
if (!previousSibling) previousSibling = {};
previousSibling.nextSibling = key;
instanceRelations[currentDepth1Id] = previousSibling;
currentDepth1 = instance;
}
instanceRelations[instance.uiUniqueIdentifier] = selfRelation;
for (var i = 0; i < children.length; i++) {
var childRelation = instanceRelations[children[i]];
if (!childRelation) childRelation = {};
if (i > 0) {
childRelation.previousSibling = children[i - 1];
}
if (i < children.length - 1) {
childRelation.nextSibling = children[i + 1];
}
instanceRelations[children[i]] = childRelation;
if (instance && instance.flowLogicDefinition && instance.flowLogicDefinition.type) {
typeLookup[key] = instance.flowLogicDefinition.type;
}
}
});
}
dataModel.diagramJSON.nodes.forEach(function(node) {
node.relation = instanceRelations[node.key];
});
function isElseIfElseNode(nodeId, instances) {
if (!nodeId) {
return false;
}
var matched;
for (var i = 0; i < instances.length; i++) {
if (instances[i].uiUniqueIdentifier === nodeId) {
matched = instances[i];
break;
}
}
if (matched) {
var flowLogicType;
if (matched.flowLogicDefinition) {
flowLogicType = matched.flowLogicDefinition.type;
}
if (
flowLogicType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSEIF ||
flowLogicType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSE
) {
return true;
}
}
return false;
}
var nodeMap = {};
dataModel.diagramJSON.nodes.forEach(function(node) {
nodeMap[node.key] = node;
});
dataModel.diagramJSON.nodes.forEach(function(node) {
var endId;
var beforeInsertion, beforeInsertionId, afterInsertion, parentInsertion;
if (node.instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDLOOP || node.instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDDOLOOP) {
endId = node.key;
if (node.relation === undefined || node.relation === null) {
node.relation = {};
}
// for each ENDLOOP, find the corresponding LOOP node
var loopNodeId = node.loopStartNodeKey;
var loopNode = nodeMap[loopNodeId];
var loopParentId, loopNextSiblingId;
if (loopNode.relation) {
loopParentId = loopNode.relation.parent;
loopNextSiblingId = loopNode.relation.nextSibling;
}
if (loopParentId) {
parentInsertion = nodeMap[loopParentId];
}
if (loopNextSiblingId) {
afterInsertion = nodeMap[loopNextSiblingId];
}
beforeInsertion = loopNode;
beforeInsertionId = loopNode.key;
} else if (node.instanceType === "ENDIF") {
// for each ENDIF,
// 1. find the last IF / ELSEIF / hidden ELSE in the corresponding IF node, and
// 2. find the next sibling of the last IF / ELSEIF / hidden ELSE
endId = node.key;
if (node.relation === undefined || node.relation === null) {
node.relation = {};
}
var ifNodeId = node.sourceIfNodeKey;
var ifNode = nodeMap[ifNodeId];
var current = ifNode;
var ifParentId;
if (ifNode.relation) {
ifParentId = ifNode.relation.parent;
}
if (ifParentId) {
parentInsertion = nodeMap[ifParentId];
}
var nextSiblingId = current.relation ?
current.relation.nextSibling :
undefined;
while (isElseIfElseNode(nextSiblingId, instances)) {
current = nodeMap[nextSiblingId];
if (!current) {
// hidden ELSE
beforeInsertionId = nextSiblingId;
nextSiblingId = instanceRelations[nextSiblingId].nextSibling;
break;
} else {
beforeInsertionId = current.key;
nextSiblingId = current.relation ?
current.relation.nextSibling :
undefined;
}
}
// current is the last IF / ELSEIF / hidden ELSE
// nextSibling is the next sibling of the last IF / ELSEIF / hidden ELSE
beforeInsertion = current;
if (beforeInsertion && !beforeInsertionId) {
beforeInsertionId = beforeInsertion.key;
}
if (nextSiblingId) {
afterInsertion = nodeMap[nextSiblingId];
}
}
if (endId) {
if (
parentInsertion &&
parentInsertion.relation &&
parentInsertion.relation.children
) {
var insertionIndex = parentInsertion.relation.children.indexOf(
beforeInsertionId
);
if (insertionIndex > -1) {
parentInsertion.relation.children.splice(
insertionIndex + 1,
0,
endId
);
}
node.relation.parent = parentInsertion.key;
}
if (afterInsertion && afterInsertion.relation) {
afterInsertion.relation.previousSibling = endId;
node.relation.nextSibling = afterInsertion.key;
}
if (beforeInsertion && beforeInsertion.relation) {
beforeInsertion.relation.nextSibling = endId;
node.relation.previousSibling = beforeInsertion.key;
} else {
node.relation.previousSibling = beforeInsertionId;
}
}
});
return nodeMap;
};
FlowDiagramBuilderApi.removeUnnecessaryFieldsOnNodes = function(nodes) {
for (var i = 0; i < nodes.length; i++) {
// array of ancestor FOR EACH flow logic keys
if (nodes[i].sourceForEachKeys) {
delete nodes[i].sourceForEachKeys;
}
// array of ancestor IF flow logic keys
if (nodes[i].sourceIfKeys) {
delete nodes[i].sourceIfKeys;
}
// array of IF flow logic keys that ends with the previous node
if (nodes[i].endOfIfKeys) {
delete nodes[i].endOfIfKeys;
}
// parallel block key that contains this node. parallel block node is later removed
if (nodes[i].parallelBlockKey) {
delete nodes[i].parallelBlockKey;
}
// parallel keys that ends with the previous node
if (nodes[i].endOfParallelKeys) {
delete nodes[i].endOfParallelKeys;
}
// parallel key that contains this node
if (nodes[i].parallelKey) {
delete nodes[i].parallelKey;
}
// decision key that contains this node
if (nodes[i].decisionKey) {
delete nodes[i].decisionKey;
}
// traversed keys/nodes for the path broken node
if (nodes[i].traversedKeys) {
delete nodes[i].traversedKeys;
}
// decision block key that contains this node.
if (nodes[i].decisionBlockKey) {
delete nodes[i].decisionBlockKey;
}
// decision keys that ends with the previous node
if (nodes[i].endOfDecisionKeys) {
delete nodes[i].endOfDecisionKeys;
}
}
};
FlowDiagramBuilderApi.addEndOfIfKeys = function(record, key) {
if (record.endOfIfKeys)
record.endOfIfKeys += ',' + key;
else
record.endOfIfKeys = key;
};
// processes the remaining keys for END node
FlowDiagramBuilderApi.processRemainingActiveIfKeys = function(activeIfKeyByDepthHash, totalNodes) {
var endNode;
var i;
for (i = 0; i < totalNodes.length; i++) {
if (totalNodes[i].key === FlowDiagramBuilderApi.endKey) {
endNode = totalNodes[i];
break;
}
}
if (activeIfKeyByDepthHash && totalNodes && endNode) {
for (var key in activeIfKeyByDepthHash) {
if (Object.prototype.hasOwnProperty.call(activeIfKeyByDepthHash, key)) {
var val = activeIfKeyByDepthHash[key];
if (val && val instanceof Array) {
for (i = 0; i < val.length; i++) {
FlowDiagramBuilderApi.addEndOfIfKeys(endNode, val[i].key);
}
}
}
}
}
};
// remove unconnected nodes (nodes that are not connected by any edge)
// most likely a by product of ELSE implementation
FlowDiagramBuilderApi.removeUnconnectedNodes = function (
totalNodes,
totalEdges
) {
var i, j;
var hiddenNodes = [];
for (i = 0; i < totalEdges.length; i++) {
if (FlowDiagramBuilderApi.instanceRecordsHash[totalEdges[i].to])
FlowDiagramBuilderApi.instanceRecordsHash[totalEdges[i].to]
.connectedCount++;
if (FlowDiagramBuilderApi.instanceRecordsHash[totalEdges[i].from])
FlowDiagramBuilderApi.instanceRecordsHash[totalEdges[i].from]
.connectedCount++;
// inner logic may have ENDIF nodes disconnected by END FLOW which do not cause a Path Broken
if (FlowDiagramBuilderApi.hiddenRecordsHash[totalEdges[i].to] && FlowDiagramBuilderApi.hiddenRecordsHash[totalEdges[i].to].instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDIF) {
if (!FlowDiagramBuilderApi.hiddenRecordsHash[totalEdges[i].to].connectedCount) {
FlowDiagramBuilderApi.hiddenRecordsHash[totalEdges[i].to].connectedCount = 1;
hiddenNodes.push(FlowDiagramBuilderApi.hiddenRecordsHash[totalEdges[i].to]);
} else {
FlowDiagramBuilderApi.hiddenRecordsHash[totalEdges[i].to].connectedCount += 1;
}
}
if (FlowDiagramBuilderApi.hiddenRecordsHash[totalEdges[i].from] && FlowDiagramBuilderApi.hiddenRecordsHash[totalEdges[i].from].instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDIF) {
if (!FlowDiagramBuilderApi.hiddenRecordsHash[totalEdges[i].from].connectedCount) {
FlowDiagramBuilderApi.hiddenRecordsHash[totalEdges[i].from].connectedCount = 1;
hiddenNodes.push(FlowDiagramBuilderApi.hiddenRecordsHash[totalEdges[i].from]);
} else {
FlowDiagramBuilderApi.hiddenRecordsHash[totalEdges[i].from].connectedCount += 1;
}
}
}
var orderedNodes = FlowDiagramBuilderApi.instanceRecordsOrderedList;
for (i = 0; i < orderedNodes.length; i++) {
if (orderedNodes[i].connectedCount === 0) {
for (j = 0; j < totalNodes.length; j++) {
if (totalNodes[j].key === orderedNodes[i].key) {
totalNodes.splice(j, 1);
j--;
break;
}
}
}
}
// hidden nodes (ie ENDIF, ENDLOOP) must have at least 2 edges, else remove them
for (var k = 0; k < hiddenNodes.length; k++) {
if (hiddenNodes[k].connectedCount <= 1) {
const nodeKey = hiddenNodes[k].key;
for (j = 0; j < totalNodes.length; j++) {
if (totalNodes[j].key === nodeKey) {
totalNodes.splice(j, 1);
j--;
break;
}
}
// we also need to remove the connected edge
for (var m = 0; m < totalEdges.length; m++) {
if (totalEdges[m].from === nodeKey || totalEdges[m].to === nodeKey) {
totalEdges.splice(m, 1);
m--;
break;
}
}
}
}
};
// updatePathBrokenForParallel() should only affect PARALLEL and not DECISION
// because of PARALLEL's unique behaivior on path broken; only one branch with all paths blocked by
// END flow logic will result in path broken for all PARALLEL branches.
// Must be called after end of parallel node has been generated
// 1) sort nodes
// 2) for every parallel/decision branch, check to see if there is blocked path
// 3) if there is a blocked path, add path broken after the end of parallel node
// and move all outgoing edges from end of parallel node to the new path broken node
FlowDiagramBuilderApi.updatePathBrokenForParallel = function(totalNodes, totalEdges) {
var i, j, k;
var orderedNodes = FlowDiagramBuilderApi.sortNodes(totalNodes);
var parallelKey;
var endOfParallelKey;
var pathBlockedForParallelData = {};
for (i = 0; i < orderedNodes.length; i++) {
if (orderedNodes[i] && orderedNodes[i].key) {
if (FlowDiagramBuilderApi.getInstanceRecordHashNodeType(orderedNodes[i].key) === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLEL) {
parallelKey = orderedNodes[i].key;
endOfParallelKey = "";
// find the node that contains matching endOfParallelKeys in regards to PARALLEL node's key
// which will decide where the end of parallel node will be inserted (and connected)
for (j = i + 1; j < orderedNodes.length; j++) {
if (orderedNodes[j] && orderedNodes[j].endOfParallelKeys) {
var currentEndOfParallelKeys = orderedNodes[j].endOfParallelKeys.join(",");
if (currentEndOfParallelKeys.includes(parallelKey)) {
endOfParallelKey = orderedNodes[j].key;
break;
}
}
}
} else if (FlowDiagramBuilderApi.getInstanceRecordHashNodeType(orderedNodes[i].key) === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLELBLOCK) {
if (parallelKey && endOfParallelKey) {
var previousKeys = "";
// represent highest order node used/checked in isAllEdgesEndWithoutReachingTheEnd() recursion
// highestOrder is used to get another return value from isAllEdgesEndWithoutReachingTheEnd()
// highestOrder = resultArray[0]
// traversedKeys = resultArray[1]
var resultArray = [];
resultArray.push(-1);
resultArray.push("");
var allPathsBlocked = FlowDiagramBuilderApi.isAllEdgesEndWithoutReachingTheEnd(orderedNodes[i].key, previousKeys, totalEdges, resultArray, endOfParallelKey);
if (allPathsBlocked && !pathBlockedForParallelData[parallelKey]) {
pathBlockedForParallelData[parallelKey] = {
key: parallelKey,
resultArray: resultArray,
startKey: orderedNodes[i].key
};
}
}
}
}
}
for (var key in pathBlockedForParallelData) {
if (Object.prototype.hasOwnProperty.call(pathBlockedForParallelData, key)) {
var pathBlockedParallel = pathBlockedForParallelData[key];
if (pathBlockedParallel) {
var pathBlockedParallelKey = pathBlockedParallel.key;
var pathBlockedParallelResultArray = pathBlockedParallel.resultArray;
var pathBlockedParallelStartKey = pathBlockedParallel.startKey;
for (i = 0; i < orderedNodes.length; i++) {
var node = orderedNodes[i];
if (node &&
node.instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDPARALLEL &&
node.endParallelNodeKey === pathBlockedParallelKey) {
// add path broken node
var nodeOrderVisited = pathBlockedParallelResultArray[0]; // customOrder based on instanceRecordsOrderedList
// find the correct node that matches to customOrder based on arrayPosition,
// since orderedNodes array will contain additional nodes such as Trigger, PATHBROKEN, ENDPARALLEL, etc which are sorted to the start
for (var j = 0; j < orderedNodes.length; j++) {
var arrayPosition = orderedNodes[j].arrayPosition;
if (arrayPosition === nodeOrderVisited) {
nodeOrderVisited = orderedNodes[i];
}
}
var newNode = FlowDiagramBuilderApi.createPathBrokenNode(orderedNodes, nodeOrderVisited, pathBlockedParallelResultArray[1], node);
// move all outgoing edges from end of parallel node to the new path broken node
for (j = 0; j < totalEdges.length; j++) {
var edge = totalEdges[j];
if (edge.from === node.key) {
edge.from = newNode.key;
edge.fromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_PATH_BROKEN_OUTPUT_SYS_ID;
break;
}
}
// when adjusting the layout of nodes, we need to keep track of which Parallel node the path broken belongs
newNode.endParallelNodeKey = pathBlockedParallelKey;
// iterate nodes from orderedNodes[i] to nodeOrderVisited and get all deletedLoopbackEdges
// for the all traversed nodes to create the path broken node
// orderedNodes[].deletedLoopbackEdges contains deleted loopback edges because of END FLOW node
var nextNodeOrder = pathBlockedParallelResultArray[0] + 1;
var deletedLoopbackEdges = [];
// Must use FlowDiagramBuilderApi.instanceRecordsOrderedList instead of orderedNodes for the below iteration because
// isAllEdgesEndWithoutReachingTheEnd's logic does not account for end of XX nodes and assign order values accordingly.
// Because orderedNodes includes end of parallel nodes, if orderedNodes is used for below iteration it will screw up the order iteration.
for (j = 0; j < FlowDiagramBuilderApi.instanceRecordsOrderedList.length; j++) {
if (FlowDiagramBuilderApi.instanceRecordsOrderedList[j].key === pathBlockedParallelStartKey) {
var currentOrderedNodeIndex = j;
while (currentOrderedNodeIndex < FlowDiagramBuilderApi.instanceRecordsOrderedList.length && currentOrderedNodeIndex < nextNodeOrder) {
var currentOrderedNode = FlowDiagramBuilderApi.instanceRecordsOrderedList[currentOrderedNodeIndex];
// deletedLoopbackEdges is only stored in FlowDiagramBuilderApi.instanceRecordsHash
var record = FlowDiagramBuilderApi.instanceRecordsHash[currentOrderedNode.key];
if (record && record.deletedLoopbackEdges)
deletedLoopbackEdges = deletedLoopbackEdges.concat(record.deletedLoopbackEdges);
currentOrderedNodeIndex++;
}
// re-add the deleted loopback edges
if (deletedLoopbackEdges) {
for (k = 0; k < deletedLoopbackEdges.length; k++) {
deletedLoopbackEdges[k].from = newNode.key;
deletedLoopbackEdges[k].fromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_PATH_BROKEN_OUTPUT_SYS_ID;
totalEdges.push(deletedLoopbackEdges[k]);
}
}
break;
}
}
// push the new node to totalNodes which is dataModel.diagramJSON.nodes
totalNodes.push(newNode);
break;
}
}
}
}
}
};
FlowDiagramBuilderApi.createPathBrokenNode = function(orderedNodes, nodeOrderVisited, traversedKeys, originNode) {
var nodeId = FlowDiagramBuilderApi.getFlowLogicNodeId(FlowDiagramConstants.FLOW_LOGIC_TYPES.PATHBROKEN);
// PATH BROKEN node is not defined anywhere in Flow Designer. We could have used END Node's key as PATH BROKEN node's
// key but decided to create its own since all node keys should be unique
// PATH BROKEN node does not exist in Flow and therefore there is no instance.order for it
var newNode = FlowDiagramBuilderApi.createNode({
name: FlowDiagramConstants.NODE_NAMES.PATH_BROKEN,
node_id: nodeId,
instanceType: FlowDiagramConstants.FLOW_LOGIC_TYPES.PATHBROKEN,
traversedKeys: traversedKeys //resultArray[1]
});
newNode.sourceIfKeys = [];
// orderedNodes[nodeOrderVisited] (which is the node before the new PATH BROKEN node) must be END flow logic node
// in other words, path broken node can only be created after a END flow logic node
var endNode = orderedNodes[nodeOrderVisited] || originNode;
if (endNode) {
newNode.key = FlowDiagramHelper.addStringToUuid(FlowDiagramConstants.FLOW_LOGIC_TYPES.PATHBROKEN, endNode.key);
var endNodeOrder = endNode.order || endNode.customOrder;
if (!isNaN(endNodeOrder)) {
newNode.order = endNodeOrder;
}
if (endNode.depth) {
newNode.depth = endNode.depth;
}
if (FlowDiagramBuilderApi.isValidArrayPosition(endNode.arrayPosition)) {
newNode.arrayPosition = endNode.arrayPosition;
}
}
// copy the last visited sourceIfKeys to newNode. These sourceIfKeys will be used to
// determine how to connect ENDOF nodes to this newly created path broken node.
if (orderedNodes.length > nodeOrderVisited &&
endNode &&
endNode.sourceIfKeys) {
newNode.sourceIfKeys = endNode.sourceIfKeys.slice();
}
if (orderedNodes.length > nodeOrderVisited &&
endNode &&
endNode.sourceForEachKeys) {
newNode.sourceForEachKeys = endNode.sourceForEachKeys.slice();
}
// add updatePath logic for PARALLEL
var parallelKey = null;
if (endNode) {
if (endNode.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLEL) {
parallelKey = endNode.key;
} else if (endNode.parallelKey) {
parallelKey = endNode.parallelKey;
}
}
// add updatePath logic for DECISION
var decisionKey = null;
if (endNode) {
if (endNode.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISION) {
decisionKey = endNode.key;
} else if (endNode.decisionKey) {
decisionKey = endNode.decisionKey;
}
}
FlowDiagramBuilderApi.instanceRecordsHash[newNode.key] = {
key: newNode.key,
type: FlowDiagramConstants.FLOW_LOGIC_TYPES.PATHBROKEN,
deletedLoopbackEdges: null,
connectedCount: 0,
data: newNode.data,
sourceIfKeys: newNode.sourceIfKeys,
sourceForEachKeys: newNode.sourceForEachKeys,
parallelKey: parallelKey,
decisionKey: decisionKey,
traversedKeys: traversedKeys //resultArray[1]
};
return newNode;
};
// Add Path broken if necessary
// note that path broken nodes can be added additionally in
// connectPathBrokenToNonStartPathBrokenNodeWithoutIncomingEdges() for edge cases
FlowDiagramBuilderApi.updatePathBroken = function(errors, totalNodes, totalEdges) {
var i, j;
var orderedNodes = FlowDiagramBuilderApi.instanceRecordsOrderedList;
// Contains all nodes keys traversed for a valid path broken find. A node should only
// be traversed once for a valid path broken find
var totalTraversedKeysForValidPathBroken = "";
var forcePathBrokenFind = false;
var forceNodeOrderVisited = -1;
for (i = 0; i < orderedNodes.length; i++) {
// for the first "orphaned node" is found, update so that the next interation of PathBroken loop
// starts with the first "orphaned node" found and if the first "orphaned node" has a PathBroken path
// then the remaining nodes will get added after node visited in the current PathBroken loop
// which is forced by forceNodeOrderVisited
// when the new PathBroken loop begins with the first "orphaned node" it might not start with
// DECISION, IF, or FOR. Therefore we set forcePathBrokenFind to bypass the check
// PARALLEL node is separately handled in updatePathBrokenForParallel()
if ((forcePathBrokenFind ||
orderedNodes[i].type === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISION ||
orderedNodes[i].type === FlowDiagramConstants.FLOW_LOGIC_TYPES.IF ||
orderedNodes[i].type === FlowDiagramConstants.FLOW_LOGIC_TYPES.FOREACH ||
orderedNodes[i].type === FlowDiagramConstants.FLOW_LOGIC_TYPES.DOUNTIL) &&
totalTraversedKeysForValidPathBroken.includes(orderedNodes[i].key) === false) {
forcePathBrokenFind = false;
var localForceNodeOrderVisited = forceNodeOrderVisited;
forceNodeOrderVisited = -1;
// used to guard against stack overflow on recursion and record previously visited nodes/keys
var previousKeys = "";
// represent highest order node used/checked in isAllEdgesEndWithoutReachingTheEnd() recursion
// highestOrder is used to get another return value from isAllEdgesEndWithoutReachingTheEnd()
// highestOrder = resultArray[0]
// traversedKeys = resultArray[1]
var resultArray = [];
resultArray.push(-1);
resultArray.push("");
var allPathsBlocked = FlowDiagramBuilderApi.isAllEdgesEndWithoutReachingTheEnd(orderedNodes[i].key, previousKeys, totalEdges, resultArray, null);
// "allPathsBlocked && resultArray[0] !== -1" means that the current node did not reach END and is not connected to any END flow logic
// which makes it not a path broken. Creation of Path broken node requires END flow logic.
if (allPathsBlocked && resultArray[0] !== -1) {
// We want the Path broken node to be connected [localForceNodeOrderVisited if exist or highestOrder plus 1] order node
var nextNodeOrder = localForceNodeOrderVisited === -1 ? resultArray[0] + 1 : localForceNodeOrderVisited + 1;
var nodeOrderVisited = localForceNodeOrderVisited === -1 ? resultArray[0] : localForceNodeOrderVisited + 1;
// Nodes in PARALLEL (not DECISION) will be separately handled in updatePathBrokenForParallel()
if (orderedNodes[nodeOrderVisited] && !orderedNodes[nodeOrderVisited].parallelKey) {
var newNode = FlowDiagramBuilderApi.createPathBrokenNode(orderedNodes, nodeOrderVisited, resultArray[1]);
// iterate nodes from orderedNodes[i] to nodeOrderVisited and get all deletedLoopbackEdges
// for the all traversed nodes to create the path broken node
// orderedNodes[].deletedLoopbackEdges contains deleted loopback edges because of END FLOW node
var deletedLoopbackEdges = [];
var currentOrderedNodeIndex = i;
var currentOrderedNode = orderedNodes[i];
while (currentOrderedNodeIndex < orderedNodes.length && currentOrderedNodeIndex < nextNodeOrder) {
if (orderedNodes[currentOrderedNodeIndex].deletedLoopbackEdges)
deletedLoopbackEdges = deletedLoopbackEdges.concat(orderedNodes[currentOrderedNodeIndex].deletedLoopbackEdges);
currentOrderedNodeIndex++;
}
var to;
var toPort;
if (nextNodeOrder >= orderedNodes.length) {
to = FlowDiagramBuilderApi.endKey;
toPort = FlowDiagramBuilderApi.endPort;
} else if (nextNodeOrder < orderedNodes.length && nextNodeOrder >= 0) {
var nextNode = orderedNodes[nextNodeOrder];
if ((nextNode.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.END || nextNode.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.GOBACKTO) &&
nextNode.edges &&
nextNode.edges.length === 0) {
// skip the node if the nextNode is an orphaned END node
nextNode = orderedNodes[nextNodeOrder + 1];
}
if (nextNode) {
to = nextNode.key;
toPort = FlowDiagramBuilderApi.getToPort(nextNode.type);
} else {
// signifies nextNodeOrder == orderedNodes.length and thus the next node is the END node
to = FlowDiagramBuilderApi.endKey;
toPort = FlowDiagramBuilderApi.endPort;
}
} else {
errors.push("Unexpected error happened during the processing of updatePathBroken()");
}
if (errors && errors.length === 0) {
var newEdge = FlowDiagramTranslateModel.getEdgeModel();
if (newEdge) {
newEdge = FlowDiagramBuilderApi.updateEdge(
newEdge, {
from: newNode.key,
to: to,
fromPort: FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_PATH_BROKEN_OUTPUT_SYS_ID,
toPort: toPort,
edgeId: FlowDiagramBuilderApi.edgeId,
}
);
totalEdges.push(newEdge);
}
// re-add the deleted loopback edges
if (deletedLoopbackEdges) {
for (j = 0; j < deletedLoopbackEdges.length; j++) {
deletedLoopbackEdges[j].from = newNode.key;
deletedLoopbackEdges[j].fromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_PATH_BROKEN_OUTPUT_SYS_ID;
totalEdges.push(deletedLoopbackEdges[j]);
}
}
// push the new node to totalNodes which is dataModel.diagramJSON.nodes
totalNodes.push(newNode);
// if there is an node without any edge pointing to it after END flow logic within the nodes traversed to identify
// a path broken, connect the node to the newly created pathbroken node (we will call this "orphaned node")
var toNodeEdgesExists = {};
for (j = 0; j < totalEdges.length; j++) {
var edge = totalEdges[j];
toNodeEdgesExists[edge.to] = true;
}
var newIndexForUpdatePathBroken = -1;
// j = i + 1 so that it can check for orderedNodes[j - 1].type
// j < nodeOrderVisited because the below for loop does not have to process j = nodeOrderVisited
// since there should not be any nodes within the path broken traverse that is orphaned at the
// path broken traverse
for (j = i + 1; j < nodeOrderVisited; j++) {
if (orderedNodes[j - 1] &&
(orderedNodes[j - 1].type === FlowDiagramConstants.FLOW_LOGIC_TYPES.END || orderedNodes[j - 1].type === FlowDiagramConstants.FLOW_LOGIC_TYPES.GOBACKTO) &&
newEdge &&
orderedNodes[j].type !== FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSE &&
!toNodeEdgesExists[orderedNodes[j].key]) {
newEdge.to = orderedNodes[j].key;
newEdge.toPort = FlowDiagramBuilderApi.getToPort(orderedNodes[j].type);
// for the first "orphaned node" is found, update so that the next interation of PathBroken loop
// starts with the first "orphaned node" found and if the first "orphaned node" has a PathBroken path
// then the remaining nodes will get added after node visited in the current PathBroken loop
// which is forced by forceNodeOrderVisited
// when the new PathBroken loop begins with the first "orphaned node" it might not start with
// IF, or FOR. Therefore we set forcePathBrokenFind to bypass the check
if (newIndexForUpdatePathBroken === -1) {
newIndexForUpdatePathBroken = j - 1; // j-1 because it will be incremented 1 by loop i++ operation
forcePathBrokenFind = true;
forceNodeOrderVisited = nodeOrderVisited;
}
}
}
// Skip checking on path Broken for nodes that have already been visited by isAllEdgesEndWithoutReachingTheEnd
// unless there is "orphaned node". Start searching from "orphaned node" if exists.
if (newIndexForUpdatePathBroken >= 0)
i = newIndexForUpdatePathBroken;
else
i = nodeOrderVisited;
// keeps all keys traversed for path broken node
totalTraversedKeysForValidPathBroken += ("," + resultArray[1]);
}
}
} else {
break;
}
} else {
forcePathBrokenFind = false;
forceNodeOrderVisited = -1;
}
}
};
FlowDiagramBuilderApi.insertToHashOfArray = function(hashArray, record, key) {
if (!hashArray[key]) {
hashArray[key] = [];
}
hashArray[key].push(record);
};
// Sometimes there could be a orphaned END OF node because of path broken processing
// if there is a such orphaned END OF node, find the source node and the associated path broken node
// move all edges (such as loopback) from the END OF node to the path broken node and then delete END OF node
FlowDiagramBuilderApi.processOrphanedEndOfNodeInPathBroken = function(nodes, edges) {
var edgesTo = {};
var edgesFrom = {};
var pathBrokenNodes = [];
var i, j, key;
var nodesToDelete = {};
for (i = 0; i < nodes.length; i++) {
if (nodes[i].instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.PATHBROKEN) {
pathBrokenNodes.push(nodes[i]);
}
}
for (i = 0; i < edges.length; i++) {
var edge = edges[i];
FlowDiagramBuilderApi.insertToHashOfArray(edgesTo, edge, edge.to);
FlowDiagramBuilderApi.insertToHashOfArray(edgesFrom, edge, edge.from);
}
for (key in FlowDiagramBuilderApi.hiddenRecordsHash) {
if (Object.prototype.hasOwnProperty.call(FlowDiagramBuilderApi.hiddenRecordsHash, key)) {
var hiddenRecord = FlowDiagramBuilderApi.hiddenRecordsHash[key];
if (hiddenRecord) {
if (!edgesTo[hiddenRecord.key] || edgesTo[hiddenRecord.key].length <= 0) {
// find the path broken record that traversed the source of this hiddenRecord
var sourceNodeKey;
if (hiddenRecord.loopStartNodeKey) {
sourceNodeKey = hiddenRecord.loopStartNodeKey;
} else if (hiddenRecord.sourceIfNodeKey) {
sourceNodeKey = hiddenRecord.sourceIfNodeKey;
} else if (hiddenRecord.endParallelNodeKey) {
sourceNodeKey = hiddenRecord.endParallelNodeKey;
} else if (hiddenRecord.endDecisionNodeKey) {
sourceNodeKey = hiddenRecord.endDecisionNodeKey;
}
for (i = 0; i < pathBrokenNodes.length; i++) {
var pathBrokenNode = pathBrokenNodes[i];
if (sourceNodeKey &&
pathBrokenNode.traversedKeys &&
pathBrokenNode.traversedKeys.includes(sourceNodeKey)) {
// we have found an orphaned END/hidden node. First reaasign all continue loop edges
// and then remove the node
if (edgesFrom[hiddenRecord.key]) {
for (j = 0; j < edgesFrom[hiddenRecord.key].length; j++) {
var edgeFromHiddenRecordKey = edgesFrom[hiddenRecord.key][j];
FlowDiagramBuilderApi.removeEdgeFromEdges(edgeFromHiddenRecordKey, edges);
}
}
// At this point, hiddenRecord should not be connected, and therefore mark to delete the node
nodesToDelete[hiddenRecord.key] = hiddenRecord;
delete FlowDiagramBuilderApi.hiddenRecordsHash[key];
}
}
}
}
}
}
FlowDiagramBuilderApi.deleteNodesIfKeyMatch(nodes, nodesToDelete);
};
FlowDiagramBuilderApi.updateEdgeLabels = function(edges) {
var i;
for (i = 0; i < edges.length; i++) {
if (edges[i].toPort !== FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_INPUT_LOOP_BACK_SYS_ID &&
edges[i].linkLabel === FlowDiagramConstants.LINK_LABELS.loopBackConnector) {
if (edges[i].fromPort === FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_IF_ELSE_OUTPUT_TRUE_SYS_ID)
edges[i].linkLabel = FlowDiagramConstants.LINK_LABELS.trueConnector;
else if (edges[i].fromPort === FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_IF_ELSE_OUTPUT_FALSE_SYS_ID)
edges[i].linkLabel = FlowDiagramConstants.LINK_LABELS.falseConnector;
else
edges[i].linkLabel = "";
}
}
};
FlowDiagramBuilderApi.removeUnnecessaryFieldsOnEdges = function(totalEdges) {
if (totalEdges) {
for (var i = 0; i < totalEdges.length; i++) {
var edge = totalEdges[i];
if (edge.minimumDepth) {
delete edge.minimumDepth;
}
}
}
};
FlowDiagramBuilderApi.removeUnnecessaryEdges = function(totalEdges) {
var edgesHash = {};
if (totalEdges) {
for (var i = 0; i < totalEdges.length; i++) {
var edge = totalEdges[i];
// nested for loops and if statements can sometimes create continue edge that points to itself
// delete it
if (edge.linkLabel === FlowDiagramConstants.LINK_LABELS.loopContinueConnector &&
edge.from === edge.to) {
totalEdges.splice(i, 1);
i--;
}
// remove all self pointing edges which is not a loop back edge
else if (edge.from === edge.to &&
edge.toPort !== FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_INPUT_LOOP_BACK_SYS_ID) {
totalEdges.splice(i, 1);
i--;
}
// remove edges with missing from or to
else if (!edge.to || !edge.from) {
totalEdges.splice(i, 1);
i--;
}
// remove duplicated edges
else {
var hashKey = edge.from + '_' + edge.to + '_' + edge.fromPort + '_' + edge.toPort;
if (!edgesHash[hashKey])
edgesHash[hashKey] = true;
else {
totalEdges.splice(i, 1);
i--;
}
}
}
}
};
FlowDiagramBuilderApi.removeAddButtonOnEndEdges = function(totalEdges) {
if (totalEdges) {
for (var i = 0; i < totalEdges.length; i++) {
var edge = totalEdges[i];
if (edge.to === FlowDiagramBuilderApi.endKey) {
edge.addBtnVisibility = FlowDiagramConstants.LINK_ADD_BTN_VISIBILITY.forLoopLoopback;
}
}
}
};
FlowDiagramBuilderApi.createHiddenNode = function(totalNodes, sourceKey, parallelKey, decisionKey, nodeType, depth, addOneToFirstLastHexadecimal) {
// add new node
var nodeId = FlowDiagramBuilderApi.getFlowLogicNodeId(nodeType);
// newKey will be the sourceKey reversed with dashes in tact
var newKey = FlowDiagramHelper.reverseUuid(sourceKey);
if (addOneToFirstLastHexadecimal) {
newKey = FlowDiagramHelper.addOneToFirstLastHexadecimal(newKey);
}
// END LOOP node does not exist in Flow and therefore there is no instance.order/arrayPosition for it
var newNode = FlowDiagramBuilderApi.createNode({
name: nodeType,
key: newKey,
node_id: nodeId,
instanceType: nodeType,
depth: depth,
});
if (nodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDLOOP ||
nodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDDOLOOP) {
newNode.loopStartNodeKey = sourceKey;
} else if (nodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDIF) {
newNode.sourceIfNodeKey = sourceKey;
} else if (nodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDPARALLEL) {
newNode.endParallelNodeKey = sourceKey;
} else if (nodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.BEGINNINGPARALLEL) {
newNode.beginningParallelNodeKey = sourceKey;
} else if (nodeType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDDECISION) {
newNode.endDecisionNodeKey = sourceKey;
}
if (parallelKey) {
newNode.parallelKey = parallelKey;
}
if (decisionKey) {
newNode.decisionKey = decisionKey;
}
// create a deep copy; works if you do not use Dates, functions, undefined, regExp or Infinity
FlowDiagramBuilderApi.hiddenRecordsHash[newKey] = JSON.parse(JSON.stringify(newNode));
totalNodes.push(newNode);
return newNode;
};
FlowDiagramBuilderApi.concatToMainArrayWithFlowDiagramType = function(instances, newInstances, type) {
if (newInstances && newInstances instanceof Array) {
FlowDiagramBuilderApi.assignFlowDiagramType(newInstances, type);
var result = instances.concat(newInstances);
return result;
}
};
FlowDiagramBuilderApi.getActionDefinitionIdFromDatabase = function(actionType) {
var definitionId = "";
if (actionType) {
if (actionType.fParentActionId)
definitionId = actionType.fParentActionId;
else
definitionId = actionType.fId;
}
return definitionId;
};
FlowDiagramBuilderApi.getActionDefinitionIdFromUI = function(actionType) {
var definitionId = "";
if (actionType) {
if (actionType.parent_action)
definitionId = actionType.parent_action;
else
definitionId = actionType.id;
}
return definitionId;
};
FlowDiagramBuilderApi.getFlowLogicNodeId = function(type) {
var nodeId = "";
if (type === FlowDiagramConstants.FLOW_LOGIC_TYPES.IF || type === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSE || type === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSEIF)
nodeId = FlowDiagramConstants.SYS_IDS.FLOW_LOGIC_CONDITIONAL_DIAGRAM_ACTION;
else if (type === FlowDiagramConstants.FLOW_LOGIC_TYPES.FOREACH || type === FlowDiagramConstants.FLOW_LOGIC_TYPES.DOUNTIL)
nodeId = FlowDiagramConstants.SYS_IDS.FLOW_LOGIC_LOOP_DIAGRAM_ACTION;
else if (type === FlowDiagramConstants.FLOW_LOGIC_TYPES.END)
nodeId = FlowDiagramConstants.SYS_IDS.FLOW_LOGIC_END_ACTION;
else if (type === FlowDiagramConstants.FLOW_LOGIC_TYPES.PATHBROKEN)
nodeId = FlowDiagramConstants.SYS_IDS.FLOW_LOGIC_PATH_BROKEN_ACTION;
else if (type === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDLOOP)
nodeId = FlowDiagramConstants.SYS_IDS.FLOW_LOGIC_END_LOOP_ACTION;
else if (type === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDDOLOOP)
nodeId = FlowDiagramConstants.SYS_IDS.FLOW_LOGIC_END_DO_LOOP_ACTION;
else if (type === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDIF)
nodeId = FlowDiagramConstants.SYS_IDS.FLOW_LOGIC_END_IF_ACTION;
else if (Object.keys(FlowDiagramConstants.FLOW_LOGIC_TYPES.ONE_TO_ONE).toString().includes(type))
nodeId = FlowDiagramConstants.SYS_IDS.FLOW_LOGIC_ONE_TO_ONE_ACTION;
else if (type === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDPARALLEL)
nodeId = FlowDiagramConstants.SYS_IDS.FLOW_LOGIC_END_PARALLEL_ACTION;
else if (type === FlowDiagramConstants.FLOW_LOGIC_TYPES.BEGINNINGPARALLEL)
nodeId = FlowDiagramConstants.SYS_IDS.FLOW_LOGIC_BEGINNING_PARALLEL_ACTION;
else if (type === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLEL)
nodeId = FlowDiagramConstants.SYS_IDS.FLOW_LOGIC_PARALLEL_ACTION;
else if (type === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLELBLOCK)
nodeId = FlowDiagramConstants.SYS_IDS.FLOW_LOGIC_PARALLEL_BLOCK_ACTION;
else if (type === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISION)
nodeId = FlowDiagramConstants.SYS_IDS.FLOW_LOGIC_DECISION_ACTION;
else if (type === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISIONBLOCK)
nodeId = FlowDiagramConstants.SYS_IDS.FLOW_LOGIC_DECISION_BLOCK_ACTION;
else if (type === FlowDiagramConstants.FLOW_LOGIC_TYPES.ENDDECISION)
nodeId = FlowDiagramConstants.SYS_IDS.FLOW_LOGIC_END_DECISION_ACTION;
else if (type === FlowDiagramConstants.FLOW_LOGIC_TYPES.GOBACKTO)
nodeId = FlowDiagramConstants.SYS_IDS.FLOW_LOGIC_GO_BACK_TO_ACTION;
return nodeId;
};
FlowDiagramBuilderApi.addInstance = function(errors, hierarchyStack, dataModel, instance, activeIfKeyByDepthHash) {
if (instance && errors && errors.length === 0) {
var rand = instance.uiUniqueIdentifier;
var newNode;
var definitionId;
var nodeId;
var name = instance.name;
var order = instance.order;
var arrayPosition = instance.arrayPosition;
if (instance.flow_diagram_type === FlowDiagramConstants.INSTANCE_TYPES.ACTION_INSTANCE_TYPE) {
if (instance.actionType) {
definitionId = FlowDiagramBuilderApi.getActionDefinitionIdFromDatabase(instance.actionType);
newNode = FlowDiagramBuilderApi.createNode({
name: FlowDiagramHelper.generateNodeName(instance),
key: rand,
node_id: FlowDiagramConstants.SYS_IDS.FLOW_ACTION_DIAGRAM_ACTION,
definition_sys_id: definitionId,
instance_sys_id: instance.id,
instanceType: FlowDiagramConstants.INSTANCE_TYPES.ACTION_INSTANCE_TYPE,
order: order,
depth: instance.depth,
data: instance.data,
arrayPosition: arrayPosition,
parent: instance.parent,
});
}
} else if (instance.flow_diagram_type === FlowDiagramConstants.INSTANCE_TYPES.SUBFLOW_INSTANCE_TYPE) {
if (instance.subFlow) {
definitionId = instance.subflowSysId;
newNode = FlowDiagramBuilderApi.createNode({
name: FlowDiagramHelper.generateNodeName(instance),
key: rand,
node_id: FlowDiagramConstants.SYS_IDS.FLOW_ACTION_DIAGRAM_ACTION,
definition_sys_id: definitionId,
instance_sys_id: instance.id,
instanceType: FlowDiagramConstants.INSTANCE_TYPES.SUBFLOW_INSTANCE_TYPE,
order: order,
depth: instance.depth,
data: instance.data,
arrayPosition: arrayPosition,
parent: instance.parent,
});
}
} else if (instance.flow_diagram_type === FlowDiagramConstants.INSTANCE_TYPES.FLOW_LOGIC_INSTANCE_TYPE) {
if (instance.flowLogicDefinition && instance.flowLogicDefinition.type) {
var flowLogicDefinition = instance.flowLogicDefinition;
nodeId = FlowDiagramBuilderApi.getFlowLogicNodeId(flowLogicDefinition.type);
newNode = FlowDiagramBuilderApi.createNode({
name: FlowDiagramHelper.generateNodeName(instance),
key: rand,
node_id: nodeId,
definition_sys_id: instance.flowLogicDefinition.id,
instance_sys_id: instance.id,
instanceType: FlowDiagramConstants.INSTANCE_TYPES.FLOW_LOGIC_INSTANCE_TYPE,
order: order,
depth: instance.depth,
data: instance.data,
arrayPosition: arrayPosition,
parent: instance.parent
});
}
} else if (instance.flow_diagram_type === FlowDiagramConstants.INSTANCE_TYPES.COMPONENT_INSTANCE_TYPE) {
var instanceType;
var isActionSystemLevel = false;
if (instance.actionType) {
nodeId = FlowDiagramConstants.SYS_IDS.FLOW_ACTION_DIAGRAM_ACTION;
definitionId = FlowDiagramBuilderApi.getActionDefinitionIdFromUI(instance.actionType);
instanceType = FlowDiagramConstants.INSTANCE_TYPES.ACTION_INSTANCE_TYPE;
name = FlowDiagramHelper.generateNodeName(instance);
if (instance.actionType.system) {
isActionSystemLevel = true;
}
} else if (instance.flowLogicDefinition) {
instanceType = FlowDiagramConstants.INSTANCE_TYPES.FLOW_LOGIC_INSTANCE_TYPE;
definitionId = instance.flowLogicDefinition.id;
if (instance.flowLogicDefinition.type)
nodeId = FlowDiagramBuilderApi.getFlowLogicNodeId(instance.flowLogicDefinition.type);
name = FlowDiagramHelper.generateNodeName(instance);
} else if (instance.subFlow) {
nodeId = FlowDiagramConstants.SYS_IDS.FLOW_ACTION_DIAGRAM_ACTION;
definitionId = instance.subflowSysId;
instanceType = FlowDiagramConstants.INSTANCE_TYPES.SUBFLOW_INSTANCE_TYPE;
name = FlowDiagramHelper.generateNodeName(instance);
}
if (definitionId && nodeId)
newNode = FlowDiagramBuilderApi.createNode({
name: name,
key: rand,
node_id: nodeId,
definition_sys_id: definitionId,
instance_sys_id: instance.id,
instanceType: instanceType,
order: order,
depth: instance.depth,
data: instance.data,
arrayPosition: arrayPosition,
parent: instance.parent,
orderBottom: instance.orderBottom,
isActionSystemLevel: isActionSystemLevel
});
}
FlowDiagramBuilderApi.setParallelBlockKey(newNode, instance, rand);
FlowDiagramBuilderApi.setDecisionBlockKey(newNode, instance, rand);
FlowDiagramBuilderApi.addNodeAndEdges(errors, hierarchyStack, instance, newNode, dataModel, activeIfKeyByDepthHash);
}
};
FlowDiagramBuilderApi.setParallelBlockKey = function(newNode, instance, rand) {
// Attach currentParallelBlockKey to all nodes under the current ParallelBlock node
// end and start a new currentParallelBlockKey if there is another ParallelBlock within same depth is previous ParallelBlock
// end currentParallelBlockKey if the node has lower depth than existing ParallelBlock's depth.
// Note that Parallel node cannot be nested
if (newNode && instance) {
if (instance.flowLogicDefinition &&
instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLELBLOCK) {
if (FlowDiagramBuilderApi.currentParallelBlockDepth === -1) {
FlowDiagramBuilderApi.currentParallelBlockKey = rand;
FlowDiagramBuilderApi.currentParallelBlockDepth = instance.depth;
} else if (FlowDiagramBuilderApi.currentParallelBlockDepth === instance.depth) {
FlowDiagramBuilderApi.currentParallelBlockKey = rand;
}
} else {
if (FlowDiagramBuilderApi.currentParallelBlockKey && FlowDiagramBuilderApi.currentParallelBlockDepth > instance.depth) {
// FlowDiagramBuilderApi.currentParallelBlockKey is present but current node's depth is lower than the PARALLEL BLOCK's depth
// which signifies the end of FlowDiagramBuilderApi.currentParallelBlockKey
FlowDiagramBuilderApi.currentParallelBlockKey = "";
FlowDiagramBuilderApi.currentParallelBlockDepth = -1;
}
if (FlowDiagramBuilderApi.currentParallelBlockKey) {
newNode.parallelBlockKey = FlowDiagramBuilderApi.currentParallelBlockKey;
}
}
}
};
FlowDiagramBuilderApi.setDecisionBlockKey = function(newNode, instance, rand) {
// Attach currentDecisionBlockKey to all nodes under the current DecisionBlock node
// end and start a new currentDecisionBlockKey if there is another DecisionBlock within same depth is previous DecisionBlock
// end currentDecisionBlockKey if the node has lower depth than existing DecisionBlock's depth.
// Note that Decision node cannot be nested
if (newNode && instance) {
if (instance.flowLogicDefinition &&
instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISIONBLOCK) {
if (FlowDiagramBuilderApi.currentDecisionBlockDepth === -1) {
FlowDiagramBuilderApi.currentDecisionBlockKey = rand;
FlowDiagramBuilderApi.currentDecisionBlockDepth = instance.depth;
} else if (FlowDiagramBuilderApi.currentDecisionBlockDepth === instance.depth) {
FlowDiagramBuilderApi.currentDecisionBlockKey = rand;
}
} else {
if (FlowDiagramBuilderApi.currentDecisionBlockKey && FlowDiagramBuilderApi.currentDecisionBlockDepth > instance.depth) {
// FlowDiagramBuilderApi.currentDecisionBlockKey is present but current node's depth is lower than the DECISION BLOCK's depth
// which signifies the end of FlowDiagramBuilderApi.currentDecisionBlockKey
FlowDiagramBuilderApi.currentDecisionBlockKey = "";
FlowDiagramBuilderApi.currentDecisionBlockDepth = -1;
}
if (FlowDiagramBuilderApi.currentDecisionBlockKey) {
newNode.decisionBlockKey = FlowDiagramBuilderApi.currentDecisionBlockKey;
}
}
}
};
FlowDiagramBuilderApi.addNodeAndEdges = function(errors, hierarchyStack, instance, newNode, dataModel, activeIfKeyByDepthHash) {
if (newNode && dataModel && dataModel.diagramJSON && dataModel.diagramJSON.edges && dataModel.diagramJSON.nodes) {
var newEdges = [];
var addNewNode = FlowDiagramBuilderApi.createAndUpdateEdgesAndNodes(errors, hierarchyStack, instance, newNode, dataModel.diagramJSON.edges, dataModel.diagramJSON.nodes, newEdges, activeIfKeyByDepthHash);
if (errors.length === 0) {
dataModel.diagramJSON.edges = dataModel.diagramJSON.edges.concat(newEdges);
if (addNewNode && dataModel && dataModel.diagramJSON && dataModel.diagramJSON.nodes) {
dataModel.diagramJSON.nodes.push(newNode);
}
}
}
};
FlowDiagramBuilderApi.addStartEnd = function(hierarchyStack, dataModel, triggerDisplayName, existingTrigger, flowType, existingSubflowInputs) {
var templateStart = FlowDiagramTranslateModel.getNodeModel();
var templateEnd = FlowDiagramTranslateModel.getNodeModel();
var startNodeLabel = '';
var instanceType = '';
var name = triggerDisplayName;
if (FlowDiagramBuilderApi.templateDiagramJson && hierarchyStack && dataModel) {
for (var i = 0; i < FlowDiagramBuilderApi.templateDiagramJson.nodes.length; i++) {
if (FlowDiagramBuilderApi.templateDiagramJson.nodes[i].node_id === FlowDiagramConstants.SYS_IDS.FLOW_DIAGRAM_TEMPLATE_ADD_TRIGGER_NODE_ID) {
if (flowType == 'subflow') {
templateStart = FlowDiagramBuilderApi.templateDiagramJson.nodes[i];
if (existingSubflowInputs) {
templateStart.node_id = FlowDiagramConstants.SYS_IDS.FLOW_DIAGRAM_TEMPLATE_SUBFLOW_INS_OUTS_NODE_ID;
name = FlowDiagramConstants.NODE_NAMES.INPUTS_OUTPUTS;
} else {
templateStart.node_id = FlowDiagramConstants.SYS_IDS.FLOW_DIAGRAM_TEMPLATE_ADD_A_SUBFLOW_INS_OUTS_NODE_ID;
name = FlowDiagramConstants.NODE_NAMES.ADD_INPUTS_OUTPUTS;
startNodeLabel = FlowDiagramConstants.NODE_NAMES.ADD_INPUTS_OUTPUTS;
templateStart.key = FlowDiagramConstants.NODE_KEYS.FLOW_DIAGRAM_ADD_SUBFLOW_INPUTS_OUTPUTS_KEY;
}
FlowDiagramBuilderApi.startKey = templateStart.key;
instanceType = FlowDiagramConstants.INSTANCE_TYPES.SUBFLOW_INPUTS_OUTPUTS_TYPE;
} else {
templateStart = FlowDiagramBuilderApi.templateDiagramJson.nodes[i];
if (existingTrigger)
templateStart.node_id = FlowDiagramConstants.SYS_IDS.FLOW_DIAGRAM_TEMPLATE_TRIGGER_NODE_ID;
FlowDiagramBuilderApi.startKey = templateStart.key;
startNodeLabel = existingTrigger ? FlowDiagramConstants.NODE_NAMES.TRIGGER : FlowDiagramConstants.NODE_NAMES.ADD_TRIGGER;
instanceType = FlowDiagramConstants.INSTANCE_TYPES.TRIGGER_TYPE;
}
} else if (FlowDiagramBuilderApi.templateDiagramJson.nodes[i].node_id === FlowDiagramConstants.SYS_IDS.FLOW_DIAGRAM_TEMPLATE_ADD_A_NODE_NODE_ID) {
templateEnd = FlowDiagramBuilderApi.templateDiagramJson.nodes[i];
FlowDiagramBuilderApi.endKey = templateEnd.key;
}
}
var start = FlowDiagramBuilderApi.createNode({
name: name,
key: existingTrigger || flowType == 'subflow' ? templateStart.key : FlowDiagramConstants.NODE_KEYS.FLOW_DIAGRAM_ADD_TRIGGER_NODE_KEY,
node_id: templateStart.node_id,
labelText: startNodeLabel,
instanceType: instanceType
});
var end = FlowDiagramBuilderApi.createNode({
name: FlowDiagramConstants.NODE_NAMES.ADD_NODE,
key: templateEnd.key,
node_id: templateEnd.node_id,
});
var edge = FlowDiagramBuilderApi.createFirstEdge(start, end);
dataModel.diagramJSON.edges.push(edge);
dataModel.diagramJSON.nodes.push(start);
dataModel.diagramJSON.nodes.push(end);
var edges = [];
edges.push(edge);
hierarchyStack.push({
key: templateStart.key, // this can be uiUniqueIdentifier of the start node, if it exists
parent: "",
edges: edges,
});
return null;
} else {
var errorMessage = "FlowDiagramBuilderApi templateDiagramJson has not been assigned";
return errorMessage;
}
};
/**
* create a base node object using a model object.
* Fill base and add any properties from the passed in object
*
* @param nodeProperties {object}
* @return {object} - see FlowDiagramTranslateModel.getNodeModel() for base properties
*/
FlowDiagramBuilderApi.createNode = function(nodeProperties) {
var node = FlowDiagramTranslateModel.getNodeModel();
var keys = _combineKeys(nodeProperties, node);
// if there is content, lets fill the node
if (nodeProperties) {
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
node[key] = nodeProperties[key];
}
}
return node;
function _combineKeys(obj1, obj2) {
var keys = typeof obj1 === 'object' ? Object.keys(obj1) : [];
if (typeof obj2 === 'object') {
keys.concat(Object.keys(obj2));
}
return keys;
}
};
FlowDiagramBuilderApi.createFirstEdge = function(from, to) {
var edge = FlowDiagramTranslateModel.getEdgeModel();
if (from && to) {
edge.from = from.key;
edge.to = to.key;
edge.edge_id = FlowDiagramBuilderApi.edgeId;
edge.fromPort = FlowDiagramBuilderApi.startPort;
edge.toPort = FlowDiagramBuilderApi.endPort;
edge.linkLabel = FlowDiagramBuilderApi.linkLabel;
edge.addBtnVisibility = FlowDiagramBuilderApi.addBtnVisibility;
edge.addBtnText = FlowDiagramBuilderApi.addBtnText;
}
return edge;
};
FlowDiagramBuilderApi.processPreviousEdgesForLoopBack = function(instance, node, previousEdges, currentHierarchyStackRecord, newEdges, copyEdges, totalEdges, deletedLoopbackEdges, bypassParentCheck) {
if (instance && currentHierarchyStackRecord && (instance.parent === currentHierarchyStackRecord.parent || instance.parent === currentHierarchyStackRecord.key || bypassParentCheck)) {
var edge;
var edges = [];
var i;
for (i = 0; i < previousEdges.length; i++) {
var previousEdge;
if (previousEdges && previousEdges instanceof Array)
previousEdge = previousEdges[i];
// for loopback edge should always be from the latest instance/node until they share same parent, which means end of loop or if/else if/else statement
if (previousEdge && previousEdge.toPort === FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_INPUT_LOOP_BACK_SYS_ID && FlowDiagramBuilderApi.instanceRecordsHash[previousEdge.to] && FlowDiagramBuilderApi.instanceRecordsHash[previousEdge.to].parent !== instance.parent) {
var updateEdge = false;
// if the previous instance was a If/ELSE IF/ELSE node, edge from FALSE port should migrate to the node connected to FALSE node and
// edge from TRUE port should migrate to the node connected to TRUE edge
// Don't update the edge if instance is ELSE node since it will be deleted soon
if (instance.flowLogicDefinition && instance.flowLogicDefinition.type && instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSE) {
updateEdge = false;
previousEdges.splice(i, 1);
i--;
copyEdges.push(previousEdge);
} else if (FlowDiagramBuilderApi.instanceRecordsHash[previousEdge.to].depth <= node.depth) {
// if the current node's depth is lower than previousEdge.to node's depth, node is out of the for loop that previousEdge belongs to
// and therefore do not update previousEdge
if (previousEdge.fromPort === FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_IF_ELSE_OUTPUT_FALSE_SYS_ID) {
if (instance.parent === currentHierarchyStackRecord.parent)
updateEdge = true;
else if (instance.parent === currentHierarchyStackRecord.key && currentHierarchyStackRecord.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSE)
updateEdge = true;
} else if (previousEdge.fromPort === FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_IF_ELSE_OUTPUT_TRUE_SYS_ID) {
if (instance.parent === currentHierarchyStackRecord.key)
updateEdge = true;
} else
updateEdge = true;
}
// only update the continue edge if the new node is contained within the FOR EACH node that the edge points to
// FlowDiagramBuilderApi.instanceRecordsHash[node.key] has not been initialized so use FlowDiagramBuilderApi.instanceRecordsHash[instance.parent] instead
// FlowDiagramBuilderApi.processSourceForEachKeys() should have processed all nodes regarding sourceForEachKeys except for the current node and
// that is the reason why to use FlowDiagramBuilderApi.instanceRecordsHash[instance.parent]
if (updateEdge === true && instance && instance.parent && FlowDiagramBuilderApi.instanceRecordsHash[instance.parent]) {
var sourceForEachKeys = FlowDiagramBuilderApi.instanceRecordsHash[instance.parent].sourceForEachKeys;
if (sourceForEachKeys && sourceForEachKeys instanceof Array && sourceForEachKeys.length > 0) {
sourceForEachKeys = sourceForEachKeys.join(',');
} else {
sourceForEachKeys = "";
}
if (!sourceForEachKeys.includes(previousEdge.to)) {
updateEdge = false;
}
}
if (updateEdge) {
var fromPort;
if (instance.actionType || instance.subFlow)
fromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_ACTION_OUTPUT_SYS_ID;
else if (instance.flowLogicDefinition && instance.flowLogicDefinition.type) {
if (instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.IF || instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSE || instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSEIF) {
fromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_IF_ELSE_OUTPUT_TRUE_SYS_ID;
if (instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.IF || instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSEIF) {
// Do not create a new edge for ELSE node for loopback since it will be removed anyways when ELSE node is removed
// need another edge reaching to loop back
edge = FlowDiagramTranslateModel.getEdgeModel();
if (edge) {
edge = FlowDiagramBuilderApi.updateEdge(
edge, {
from: node.key,
fromPort: FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_IF_ELSE_OUTPUT_FALSE_SYS_ID,
edgeId: previousEdge.edge_id,
to: previousEdge.to,
toPort: previousEdge.toPort,
linkLabel: previousEdge.linkLabel,
labelColor: previousEdge.labelColor,
addBtnVisibility: previousEdge.addBtnVisibility,
}
);
edges.push(edge);
}
}
} else if (instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.FOREACH || instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.DOUNTIL) {
fromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_OUTPUT_SYS_ID;
} else if (instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.END) {
if (node.node_id === FlowDiagramConstants.SYS_IDS.FLOW_LOGIC_END_ACTION) {
var instanceParent = FlowDiagramBuilderApi.instanceRecordsHash[instance.parent];
if (instanceParent) {
if (instanceParent.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.FOREACH || instanceParent.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.DOUNTIL) {
fromPort = ""; // fromPort should never by ""; this must be overwritten by incoming PATH BROKEN node
}
// if edge that points to FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_INPUT_LOOP_BACK_SYS_ID is about to be attached
// to END node, and parent exists, then delete the edge
previousEdges.splice(i, 1);
FlowDiagramBuilderApi.removeEdgeFromEdges(previousEdge, newEdges);
FlowDiagramBuilderApi.removeEdgeFromEdges(previousEdge, totalEdges);
deletedLoopbackEdges.push(previousEdge);
continue;
}
} else if (node.node_id === FlowDiagramConstants.SYS_IDS.FLOW_LOGIC_PATH_BROKEN_ACTION)
fromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_PATH_BROKEN_OUTPUT_SYS_ID;
} else if (Object.keys(FlowDiagramConstants.FLOW_LOGIC_TYPES.ONE_TO_ONE).toString().includes(instance.flowLogicDefinition.type)) {
fromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_ONE_TO_ONE_OUTPUT;
} else if (instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLEL) {
fromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_ONE_TO_MANY_OUTPUT;
} else if (instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLELBLOCK) {
fromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_PARALLELBLOCK_OUTPUT_SYS_ID;
} else if (instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISION) {
fromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_ONE_TO_MANY_OUTPUT;
} else if (instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISIONBLOCK) {
fromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_DECISIONBLOCK_OUTPUT_SYS_ID;
} else if (instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.GOBACKTO) {
fromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_GOBACKTO_OUTPUT_SYS_ID;
}
}
previousEdge.fromPort = fromPort;
previousEdge.from = node.key;
// remove the edge from the previousEdges and add it to edges on new node
previousEdges.splice(i, 1);
i--;
copyEdges.push(previousEdge);
}
}
}
if (edges && copyEdges && newEdges && copyEdges instanceof Array && newEdges instanceof Array) {
for (i = 0; i < edges.length; i++) {
copyEdges.push(edges[i]);
newEdges.push(edges[i]);
}
}
}
};
FlowDiagramBuilderApi.removeEdgeFromEdges = function(edgeToRemove, edges) {
if (edges && edgeToRemove) {
for (var i = 0; i < edges.length; i++) {
if (edges[i].from === edgeToRemove.from && edges[i].to === edgeToRemove.to && edges[i].edge_id && edgeToRemove.edge_id && edges[i].fromPort === edgeToRemove.fromPort && edges[i].toPort === edgeToRemove.toPort) {
edges.splice(i, 1);
i--;
}
}
}
};
FlowDiagramBuilderApi.removeEdgeCompletely = function(edgeToRemove, hierarchyStack, edgesList) {
var i;
if (edgesList) {
for (i = 0; i < edgesList.length; i++)
FlowDiagramBuilderApi.removeEdgeFromEdges(edgeToRemove, edgesList[i]);
}
if (hierarchyStack) {
for (i = 0; i < hierarchyStack.length; i++)
FlowDiagramBuilderApi.removeEdgeFromEdges(edgeToRemove, hierarchyStack[i].edges);
}
};
// update if the previousEdge is on FALSE edge and previousEdge is from the node that replaces the ELSE node which is parent of instance
// (if previousEdge connects from FALSE port and instance.parent's immediate IF is same as
// previousEdge.from's immediage IF then the two must be connected to same IF or
// instance.parent is a ELSE connected to previousEdge.from which is a IF and therefore do not update previousEdge)
// or else if do not update edge if minimumDepth is specified and instance.depth is bigger than minimumDepth
// because previousEdge should not be updated by the instance with higher depth
FlowDiagramBuilderApi.processUpdateEdgeForElseChildrenAndMinimumDepth = function(updateEdge, instance, previousEdge, newTargetKey) {
if (FlowDiagramBuilderApi.elseReplacementHash[instance.parent] &&
previousEdge.fromPort === FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_IF_ELSE_OUTPUT_FALSE_SYS_ID &&
FlowDiagramBuilderApi.elseReplacementHash[instance.parent].key === previousEdge.from) {
updateEdge = FlowDiagramBuilderApi.processUpdateEdgeBasedOnImmediateSourceKey(instance, previousEdge);
FlowDiagramBuilderApi.updateNewTargetNodeKeyIfUpdateEdge(updateEdge, instance, newTargetKey);
} else if (previousEdge.minimumDepth && instance.depth > previousEdge.minimumDepth) {
updateEdge = false;
}
return updateEdge;
};
FlowDiagramBuilderApi.updateNewTargetNodeKeyIfUpdateEdge = function(updateEdge, instance, newTargetKey) {
// if the edge needs to be updated, store newTargetKey within the ELSE node
// in this case newTargetKey is node's key next to ELSE node and all edges pointing to the ELSE node should point to newTargetKey
if (updateEdge && instance && FlowDiagramBuilderApi.elseReplacementHash && FlowDiagramBuilderApi.elseReplacementHash[instance.parent]) {
FlowDiagramBuilderApi.elseReplacementHash[instance.parent].newTargetNodeKey = newTargetKey;
}
};
FlowDiagramBuilderApi.processUpdateEdgeBasedOnImmediateSourceKey = function(instance, previousEdge) {
var updateEdge = true;
// if previousEdge connects from FALSE port and instance.parent's immediate IF is same as
// previousEdge.from's immediage IF then the two must be connected to same IF or
// instance.parent is a ELSE connected to previousEdge.from which is a IF and therefore do not update previousEdge
var previousEdgeFromImmediateSourceKey = FlowDiagramBuilderApi.getImmediateSourceIfKeyFromNodeKey(previousEdge.from);
var instanceParentImmediateSourceKey = FlowDiagramBuilderApi.getImmediateSourceIfKeyFromNodeKey(instance.parent);
if (previousEdgeFromImmediateSourceKey &&
instanceParentImmediateSourceKey &&
previousEdgeFromImmediateSourceKey !== instanceParentImmediateSourceKey) {
updateEdge = false;
}
return updateEdge;
};
FlowDiagramBuilderApi.createAndUpdateEdgesAndNodes = function(errors, hierarchyStack, instance, node, totalEdges, totalNodes, newEdges, activeIfKeyByDepthHash) {
var i, j, key;
var copyEdges = [];
var previousEdges = [];
var previousEdge;
var toPort = "";
var deletedLoopbackEdges = [];
var addNewNode = true;
var instanceType;
var updateEdge;
if (instance && hierarchyStack && node && errors && activeIfKeyByDepthHash) {
if (instance.actionType || instance.subFlow) {
toPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_ACTION_INPUT_SYS_ID;
instanceType = FlowDiagramConstants.INSTANCE_TYPES.ACTION_INSTANCE_TYPE;
} else if (instance.flowLogicDefinition && instance.flowLogicDefinition.type) {
if (instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.IF || instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSE || instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSEIF) {
toPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_IF_ELSE_INPUT_SYS_ID;
instanceType = instance.flowLogicDefinition.type;
} else if (instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.FOREACH || instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.DOUNTIL) {
toPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_INPUT_NORMAL_SYS_ID;
instanceType = instance.flowLogicDefinition.type;
} else if (instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.END) {
if (node.node_id === FlowDiagramConstants.SYS_IDS.FLOW_LOGIC_END_ACTION) {
toPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_END_INPUT_SYS_ID;
instanceType = FlowDiagramConstants.FLOW_LOGIC_TYPES.END;
} else if (node.node_id === FlowDiagramConstants.SYS_IDS.FLOW_LOGIC_PATH_BROKEN_ACTION) {
toPort = "";
instanceType = FlowDiagramConstants.FLOW_LOGIC_TYPES.PATHBROKEN;
}
} else if (Object.keys(FlowDiagramConstants.FLOW_LOGIC_TYPES.ONE_TO_ONE).toString().includes(instance.flowLogicDefinition.type)) {
toPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_ONE_TO_ONE_INPUT;
instanceType = instance.flowLogicDefinition.type;
} else if (Object.keys(FlowDiagramConstants.FLOW_LOGIC_TYPES.ONE_TO_MANY).toString().includes(instance.flowLogicDefinition.type)) {
toPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_ONE_TO_MANY_INPUT;
instanceType = instance.flowLogicDefinition.type;
} else if (instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLELBLOCK) {
toPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_PARALLELBLOCK_INPUT_SYS_ID;
instanceType = instance.flowLogicDefinition.type;
} else if (instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISIONBLOCK) {
toPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_DECISIONBLOCK_INPUT_SYS_ID;
instanceType = instance.flowLogicDefinition.type;
} else if (instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.GOBACKTO) {
toPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_GOBACKTO_INPUT_SYS_ID;
instanceType = instance.flowLogicDefinition.type;
}
}
for (i = hierarchyStack.length - 1; i >= 0; i--) {
var edgesProcessedForLoopback = false;
// when poping on hierarchyStack if the last node in the hierarchyStack is same as instance's parent, then last node is IF/ELSE IF/ELSE
// set "toPort, to" accordingly and do not pop the node because the last node's FALSE edge is not ready to be updated; the FALSE
// edge shall be updated when the node to be popped is not the instance.parent
// there is no need to pop left over nodes in hierarchyStack because all nodes point to END node in default
if (instance.parent === hierarchyStack[i].key) {
// if "instance.parent === hierarchyStack[i].key" is true, it means that the current instance's
// parent equals to the hierarchyStack[i], which means that hierarchyStack[i] is either FOR or IF/ELSE IF/ELSE node that directly
// contains the instance
updateEdge = true;
if (i === hierarchyStack.length - 1) {
previousEdges = hierarchyStack[i].edges;
FlowDiagramBuilderApi.processPreviousEdgesForLoopBack(instance, node, previousEdges, hierarchyStack[i], newEdges, copyEdges, totalEdges, deletedLoopbackEdges, false);
edgesProcessedForLoopback = true;
for (j = 0; j < previousEdges.length; j++) {
previousEdge = previousEdges[j];
if ((previousEdge.from === hierarchyStack[i].key &&
(previousEdge.fromPort === FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_IF_ELSE_OUTPUT_TRUE_SYS_ID ||
previousEdge.fromPort === FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_OUTPUT_SYS_ID)) ||
(previousEdge.fromPort === FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_IF_ELSE_OUTPUT_FALSE_SYS_ID &&
hierarchyStack[i].type === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSE)) {
updateEdge = FlowDiagramBuilderApi.processUpdateEdgeForElseChildrenAndMinimumDepth(updateEdge, instance, previousEdge, node.key);
if (updateEdge) {
previousEdge.toPort = toPort;
previousEdge.to = node.key;
previousEdges.splice(j, 1);
j--;
}
} else if (previousEdge.from === hierarchyStack[i].key && hierarchyStack[i].type === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLELBLOCK) {
previousEdge.toPort = toPort;
previousEdge.to = node.key;
previousEdges.splice(j, 1);
j--;
} else if (previousEdge.from === hierarchyStack[i].key &&
previousEdge.toPort === FlowDiagramBuilderApi.endPort &&
hierarchyStack[i].type === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLEL &&
instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLELBLOCK) {
// if the previous node is FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLEL
// and currentnode is FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLELBLOCK
// and previousEdge's toPort is end of the flow, delete the edge since it will be replaced by an edge created between
// PARALLEL and PARALLELBLOCK when PARALLELBLOCK is created
FlowDiagramBuilderApi.removeEdgeCompletely(previousEdge, null, [newEdges, totalEdges]);
previousEdges.splice(j, 1);
j--;
} else if (previousEdge.from === hierarchyStack[i].key && hierarchyStack[i].type === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISIONBLOCK) {
previousEdge.toPort = toPort;
previousEdge.to = node.key;
previousEdges.splice(j, 1);
j--;
} else if (previousEdge.from === hierarchyStack[i].key &&
previousEdge.toPort === FlowDiagramBuilderApi.endPort &&
hierarchyStack[i].type === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISION &&
instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISIONBLOCK) {
// if the previous node is FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISION
// and currentnode is FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISIONBLOCK
// and previousEdge's toPort is end of the flow, delete the edge since it will be replaced by an edge created between
// DECISION and DECISIONBLOCK when DECISIONBLOCK is created
FlowDiagramBuilderApi.removeEdgeCompletely(previousEdge, null, [newEdges, totalEdges]);
previousEdges.splice(j, 1);
j--;
} else if (previousEdge.from === hierarchyStack[i].key && hierarchyStack[i].type === FlowDiagramConstants.FLOW_LOGIC_TYPES.GOBACKTO) {
previousEdge.toPort = toPort;
previousEdge.to = node.key;
previousEdges.splice(j, 1);
j--;
}
}
} else {
previousEdges = hierarchyStack[i].edges;
for (j = 0; j < previousEdges.length; j++) {
previousEdge = previousEdges[j];
updateEdge = FlowDiagramBuilderApi.processUpdateEdgeForElseChildrenAndMinimumDepth(updateEdge, instance, previousEdge, node.key);
if (updateEdge) {
previousEdge.toPort = toPort;
previousEdge.to = node.key;
}
}
}
// iterate through hierarchyStack until instance's parent match is found
break;
} else if (instance.parent === hierarchyStack[i].parent) {
// if "instance.parent === hierarchyStack[i].parent" is true, it means that the current instance's is at the
// same hierarchical depth with hierarchyStack[i]
previousEdges = hierarchyStack[i].edges;
FlowDiagramBuilderApi.processPreviousEdgesForLoopBack(instance, node, previousEdges, hierarchyStack[i], newEdges, copyEdges, totalEdges, deletedLoopbackEdges, false);
edgesProcessedForLoopback = true;
}
previousEdges = hierarchyStack[i].edges;
for (j = 0; j < previousEdges.length; j++) {
previousEdge = previousEdges[j];
if (previousEdge.toPort !== FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_INPUT_LOOP_BACK_SYS_ID) {
updateEdge = true;
updateEdge = FlowDiagramBuilderApi.processUpdateEdgeForElseChildrenAndMinimumDepth(updateEdge, instance, previousEdge);
if (updateEdge) {
if (hierarchyStack[i] &&
instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSEIF) {
// below if and else if statements are used for when current node is ELSE IF and previous node's IF node TRUE edge
// or ELSE IF node TRUE edge don't get connected to current node
// if hierarchyStack[i] is previous node and IF or ELSE IF node and current node is ELSE IF and (
// if the previousEdge's depth is same as instance's depth and the edge is FALSE edge and
// the previousEdge's type is either ELSE or IF, then previousEdge must be the edge that connects main IF or ELSE IF
// flow logic to the current node (which is a ELSE IF) and therefore update. Otherwise, don't update)
if ((hierarchyStack[i].type === FlowDiagramConstants.FLOW_LOGIC_TYPES.IF ||
hierarchyStack[i].type === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSEIF)) {
if (!(FlowDiagramBuilderApi.instanceRecordsHash[previousEdge.from].depth === instance.depth &&
previousEdge.fromPort === FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_IF_ELSE_OUTPUT_FALSE_SYS_ID &&
(FlowDiagramBuilderApi.instanceRecordsHash[previousEdge.from].type === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSEIF ||
FlowDiagramBuilderApi.instanceRecordsHash[previousEdge.from].type === FlowDiagramConstants.FLOW_LOGIC_TYPES.IF))) {
updateEdge = false;
}
}
// if hierarchyStack[i] is previous node and hierarchyStack[i]'s parent type is IF or ELSE IF node and
// current node is ELSE IF and the edge is coming from TRUE, don't update.
else if (FlowDiagramBuilderApi.instanceRecordsHash[hierarchyStack[i].parent]) {
var previousNodeParentType = FlowDiagramBuilderApi.instanceRecordsHash[hierarchyStack[i].parent].type;
if (previousNodeParentType === FlowDiagramConstants.FLOW_LOGIC_TYPES.IF ||
previousNodeParentType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSEIF)
updateEdge = false;
}
}
// if instance is ELSE IF and if there is any parent of hierarchyStack[i] that is IF or ELSE IF that shares the same parent of the instance
// and previousEdge is not a FALSE edge, then don't update.
// As long as the instance is ELSE IF then the previousEdge won't be updated. However, if instance of anything other than
// ELSE IF then the previousEdge will be updated. Remember that not all nodes will be used as instance to process upon a particular
// previousEdge, which will be based on how hierarchyStack was implemented
// previousEdge as FALSE edge needs to be updated and connected to upcoming ELSE IF or ELSE node if the previousEdge and currentHierachyStack are in same depth
// if currentHierachyStack.depth < FlowDiagramBuilderApi.instanceRecordsHash[previousEdge.from],
// then previousEdge.from must be on immediate "true" edge path of currentHierachyStack
// in other words, if FlowDiagramBuilderApi.instanceRecordsHash[previousEdge.from],
// and currentHierachyStack is IF or ELSE IF, only way for previousEdge.from to have bigger depth number than currentHierachyStack
// is to be part of its true edge heritage and its false edge heritage cannot have bigger depth number
// therefore we treat previousEdge as it is attached to true edge because it stemmed from immediate true edge of currentHierachyStack
// therefore bypassing the previousEdge.fromPort !== FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_IF_ELSE_OUTPUT_FALSE_SYS_ID
// if currentHierachyStack is an ancestor of previousEdge
var currentHierachyStack = hierarchyStack[i];
var previousEdgeFrom = "";
if (previousEdge && FlowDiagramBuilderApi.instanceRecordsHash[previousEdge.from]) {
previousEdgeFrom = FlowDiagramBuilderApi.instanceRecordsHash[previousEdge.from];
}
if (instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSEIF) {
while (currentHierachyStack && instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSEIF) {
if ((currentHierachyStack.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.IF ||
previousNodeParentType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSEIF) &&
currentHierachyStack.parent === instance.parent &&
previousEdgeFrom &&
(previousEdge.fromPort !== FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_IF_ELSE_OUTPUT_FALSE_SYS_ID ||
currentHierachyStack.depth < previousEdgeFrom.depth)) {
updateEdge = false;
break;
}
currentHierachyStack = FlowDiagramBuilderApi.instanceRecordsHash[currentHierachyStack.parent];
}
// DEF0346001: edge from node PARALLELBLOCK is being connected to ELSEIF
// do not update previousEdge if previousEdge.from is within a PARALLELBLOCK or DECISIONBLOCK
if (previousEdgeFrom && (previousEdgeFrom.parallelKey || previousEdgeFrom.parallelBlockKey || previousEdgeFrom.decisionKey || previousEdgeFrom.decisionBlockKey || previousEdgeFrom.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISION || previousEdgeFrom.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISIONBLOCK)) {
updateEdge = false;
}
}
// do not update previousEdge if previousEdge.from is PARALLEL node or previousEdge.to is PARALLELBLOCK node because
// these edges should not be altered (PARALLEL node always point to PARALLELBLOCK, PARALLELBLOCK always comes from PARALLEL)
if (FlowDiagramBuilderApi.instanceRecordsHash[previousEdge.from] &&
FlowDiagramBuilderApi.instanceRecordsHash[previousEdge.from].type === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLEL &&
FlowDiagramBuilderApi.instanceRecordsHash[previousEdge.to] &&
FlowDiagramBuilderApi.instanceRecordsHash[previousEdge.to].type === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLELBLOCK)
updateEdge = false;
else if (instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLELBLOCK)
updateEdge = false; // do not connect any currently existing edge to PARALLELBLOCK.
// do not update previousEdge if previousEdge.from is DECISION node or previousEdge.to is DECISIONBLOCK node because
// these edges should not be altered (DECISION node always point to DECISIONBLOCK, DECISIONBLOCK always comes from DECISION)
else if (FlowDiagramBuilderApi.instanceRecordsHash[previousEdge.from] &&
FlowDiagramBuilderApi.instanceRecordsHash[previousEdge.from].type === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISION &&
FlowDiagramBuilderApi.instanceRecordsHash[previousEdge.to] &&
FlowDiagramBuilderApi.instanceRecordsHash[previousEdge.to].type === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISIONBLOCK)
updateEdge = false;
else if (instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISIONBLOCK)
updateEdge = false; // do not connect any currently existing edge to DECISIONBLOCK.
}
// if the previousEdge will not be update or instance is ELSE (then instance will be removed by processElseNode())
// if instance.depth is smaller than previousEdge, previousEdge's fromPort is IF_ELSE_OUTPUT_FALSE,
// then the future instance that updates the previousEdge will need depth equal or smaller to
// instance.depth and the value will be stored as minimumDepth.
var previousEdgeFromRecord = FlowDiagramBuilderApi.instanceRecordsHash[previousEdge.from];
if (updateEdge) {
previousEdge.toPort = toPort;
previousEdge.to = node.key;
// Process loopback edges if it has not been process for hierarchyStack[i]
if (edgesProcessedForLoopback === false) {
FlowDiagramBuilderApi.processPreviousEdgesForLoopBack(instance, node, previousEdges, hierarchyStack[i], newEdges, copyEdges, totalEdges, deletedLoopbackEdges, true);
edgesProcessedForLoopback = true;
}
if (previousEdgeFromRecord &&
previousEdgeFromRecord.depth > instance.depth &&
previousEdge.fromPort === FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_IF_ELSE_OUTPUT_FALSE_SYS_ID &&
instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSE)
previousEdge.minimumDepth = instance.depth;
} else {
if (previousEdgeFromRecord &&
previousEdgeFromRecord.depth > instance.depth &&
previousEdge.fromPort === FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_IF_ELSE_OUTPUT_FALSE_SYS_ID)
previousEdge.minimumDepth = instance.depth;
// move the edge to the current node so that it can be properly updated later
copyEdges.push(previousEdge);
previousEdges.splice(j, 1);
j--;
}
}
}
hierarchyStack.splice(i, 1);
}
FlowDiagramBuilderApi.addNewEdges(instance, node, newEdges, copyEdges, hierarchyStack, totalNodes);
// set the deletedLoopbackEdges so that it could be added back later during path broken implementation
if ((instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.END || instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.GOBACKTO) && FlowDiagramBuilderApi.instanceRecordsHash[node.key])
FlowDiagramBuilderApi.instanceRecordsHash[node.key].deletedLoopbackEdges = deletedLoopbackEdges;
else if (instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSE)
addNewNode = FlowDiagramBuilderApi.processElseNode(errors, hierarchyStack, instance, node, totalEdges, totalNodes, newEdges);
if (FlowDiagramBuilderApi.instanceRecordsHash[instance.parent] &&
(FlowDiagramBuilderApi.instanceRecordsHash[instance.parent].type === FlowDiagramConstants.FLOW_LOGIC_TYPES.FOREACH ||
FlowDiagramBuilderApi.instanceRecordsHash[instance.parent].type === FlowDiagramConstants.FLOW_LOGIC_TYPES.DOUNTIL) &&
instanceType !== FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSE &&
instanceType !== FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSEIF) {
FlowDiagramBuilderApi.processLoopback(instanceType, instance, node, copyEdges, newEdges, totalEdges, hierarchyStack);
}
FlowDiagramBuilderApi.processEndOfIfKeys(activeIfKeyByDepthHash, node, instance, instanceType);
FlowDiagramBuilderApi.processSourceIfKeys(node, instance, instanceType);
FlowDiagramBuilderApi.processSourceForEachKeys(node, instance, instanceType);
FlowDiagramBuilderApi.processSourceParallelKeys(node, instance, instanceType);
if (addNewNode &&
(instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.IF || instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSEIF)) {
FlowDiagramBuilderApi.previousIfOrElseIfKey = node.key;
}
} else
errors.push("Error! instance or hierarchyStack or node in FlowDiagramBuilderApi.createAndUpdateEdgesAndNodes is null");
return addNewNode;
};
FlowDiagramBuilderApi.processSourceParallelKeys = function(node, instance, instanceType) {
if (node && node.key) {
if (!FlowDiagramBuilderApi.instanceRecordsHash[node.key]) {
FlowDiagramBuilderApi.instanceRecordsHash[node.key] = {};
}
if (node.parallelBlockKey) {
FlowDiagramBuilderApi.instanceRecordsHash[node.key].parallelBlockKey = node.parallelBlockKey;
}
if (node && node.parallelKey) {
FlowDiagramBuilderApi.instanceRecordsHash[node.key].parallelKey = node.parallelKey;
}
}
};
FlowDiagramBuilderApi.getSourceIfKeysFromNodeKey = function(nodeKey) {
if (FlowDiagramBuilderApi.instanceRecordsHash[nodeKey] &&
FlowDiagramBuilderApi.instanceRecordsHash[nodeKey].sourceIfKeys)
return FlowDiagramBuilderApi.instanceRecordsHash[nodeKey].sourceIfKeys;
else
return null;
};
FlowDiagramBuilderApi.getImmediateSourceIfKeyFromNodeKey = function(nodeKey) {
var result = null;
var sourceIfKeys = FlowDiagramBuilderApi.getSourceIfKeysFromNodeKey(nodeKey);
if (sourceIfKeys && sourceIfKeys instanceof Array && sourceIfKeys.length > 0) {
return sourceIfKeys[sourceIfKeys.length - 1];
}
return null;
};
FlowDiagramBuilderApi.getImmediateSourceIfKeyFromNode = function(node) {
if (node && node.sourceIfKeys) {
var sourceIfKeys = node.sourceIfKeys;
if (sourceIfKeys && sourceIfKeys instanceof Array && sourceIfKeys.length > 0) {
return sourceIfKeys[sourceIfKeys.length - 1];
}
}
return null;
};
FlowDiagramBuilderApi.getImmediateSourceIfKeyNodeFromNodeKey = function(nodeKey) {
var resultKey = FlowDiagramBuilderApi.getImmediateSourceIfKeyFromNodeKey(nodeKey);
if (resultKey && FlowDiagramBuilderApi.instanceRecordsHash[resultKey]) {
return FlowDiagramBuilderApi.instanceRecordsHash[resultKey];
}
return null;
};
FlowDiagramBuilderApi.getImmediateSourceIfKeyNodeFromNode = function(node) {
var resultKey = FlowDiagramBuilderApi.getImmediateSourceIfKeyFromNode(node);
if (resultKey && FlowDiagramBuilderApi.instanceRecordsHash[resultKey]) {
return FlowDiagramBuilderApi.instanceRecordsHash[resultKey];
}
return null;
};
FlowDiagramBuilderApi.copyKeys = function(keys) {
var newKeys = [];
if (keys instanceof Array) {
for (var i = 0; i < keys.length; i++) {
newKeys.push(keys[i]);
}
}
return newKeys;
};
FlowDiagramBuilderApi.processSourceForEachKeys = function(node, instance, instanceType) {
if (!node.sourceForEachKeys)
node.sourceForEachKeys = [];
if (instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.FOREACH || instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.DOUNTIL) {
if (instance.parent && FlowDiagramBuilderApi.instanceRecordsHash[instance.parent] && FlowDiagramBuilderApi.instanceRecordsHash[instance.parent].sourceForEachKeys) {
node.sourceForEachKeys = FlowDiagramBuilderApi.copyKeys(FlowDiagramBuilderApi.instanceRecordsHash[instance.parent].sourceForEachKeys);
}
node.sourceForEachKeys.push(node.key);
FlowDiagramBuilderApi.instanceRecordsHash[node.key].sourceForEachKeys = node.sourceForEachKeys;
} else if (instance.parent && FlowDiagramBuilderApi.instanceRecordsHash[instance.parent] && FlowDiagramBuilderApi.instanceRecordsHash[instance.parent].sourceForEachKeys) {
// if the node is not FOR EACH node then get sourceForEach value from the parent if exists
node.sourceForEachKeys = FlowDiagramBuilderApi.copyKeys(FlowDiagramBuilderApi.instanceRecordsHash[instance.parent].sourceForEachKeys);
FlowDiagramBuilderApi.instanceRecordsHash[node.key].sourceForEachKeys = node.sourceForEachKeys;
}
};
// saves the nearest IF node key value to the node if the node is part of a IF statement into the array sourceIfKeys
// sourceIfKeys will contain all IF ancestors containing the node in order
// sourceIfsByDepthHash will always contain the most immediate IF node on the depth level
FlowDiagramBuilderApi.processSourceIfKeys = function(node, instance, instanceType) {
if (!node.sourceIfKeys)
node.sourceIfKeys = [];
if (instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.IF) {
if (instance.parent && FlowDiagramBuilderApi.instanceRecordsHash[instance.parent] && FlowDiagramBuilderApi.instanceRecordsHash[instance.parent].sourceIfKeys) {
node.sourceIfKeys = FlowDiagramBuilderApi.copyKeys(FlowDiagramBuilderApi.instanceRecordsHash[instance.parent].sourceIfKeys);
}
node.sourceIfKeys.push(node.key);
FlowDiagramBuilderApi.instanceRecordsHash[node.key].sourceIfKeys = node.sourceIfKeys;
FlowDiagramBuilderApi.sourceIfsByDepthHash[instance.depth] = node;
} else if (instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSEIF || instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSE) {
if (FlowDiagramBuilderApi.sourceIfsByDepthHash[instance.depth]) {
if (instance.parent && FlowDiagramBuilderApi.instanceRecordsHash[instance.parent] && FlowDiagramBuilderApi.instanceRecordsHash[instance.parent].sourceIfKeys) {
node.sourceIfKeys = FlowDiagramBuilderApi.copyKeys(FlowDiagramBuilderApi.instanceRecordsHash[instance.parent].sourceIfKeys);
}
node.sourceIfKeys = node.sourceIfKeys.concat(FlowDiagramBuilderApi.copyKeys(FlowDiagramBuilderApi.sourceIfsByDepthHash[instance.depth].sourceIfKeys));
FlowDiagramBuilderApi.instanceRecordsHash[node.key].sourceIfKeys = node.sourceIfKeys;
}
} else if (instance.parent && FlowDiagramBuilderApi.instanceRecordsHash[instance.parent] && FlowDiagramBuilderApi.instanceRecordsHash[instance.parent].sourceIfKeys) {
// if the node is not IF/ELSE IF/ELSE node then get sourceIf value from the parent if exists
node.sourceIfKeys = FlowDiagramBuilderApi.copyKeys(FlowDiagramBuilderApi.instanceRecordsHash[instance.parent].sourceIfKeys);
FlowDiagramBuilderApi.instanceRecordsHash[node.key].sourceIfKeys = node.sourceIfKeys;
}
};
// do not update endOfIfKeys if the new node is PARALLELBLOCK or the new node is in different PARALLELBLOCK
// compared to source if's PARALLELBLOCK
FlowDiagramBuilderApi.requireUpdateOnEndOfIfKeys = function(activeIfKeyData, node, instanceType) {
var update = true;
if ((activeIfKeyData.parallelBlockKey &&
node.parallelBlockKey &&
activeIfKeyData.parallelBlockKey != node.parallelBlockKey) ||
instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLELBLOCK) {
update = false;
} else if ((activeIfKeyData.decisionBlockKey &&
node.decisionBlockKey &&
activeIfKeyData.decisionBlockKey != node.decisionBlockKey) ||
instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISIONBLOCK) {
update = false;
}
return update;
};
// collects all IF node key values that ends on a node and marks the node with all IF node key values that they end with
// used for creating END IF nodes
FlowDiagramBuilderApi.processEndOfIfKeys = function(activeIfKeyByDepthHash, node, instance, instanceType) {
var i;
var minimumDepth;
var activeIfKeyArray;
var activeIfKeyData;
// Check for activeIfKeyByDepthHash records with same value as instance.value. If there is such record, then check the current
// instance and signify END OF IF statement if the current instanceType is not ELSE IF nor ELSE
if (activeIfKeyByDepthHash[instance.depth] && activeIfKeyByDepthHash[instance.depth] instanceof Array) {
activeIfKeyArray = activeIfKeyByDepthHash[instance.depth];
for (i = 0; i < activeIfKeyArray.length; i++) {
activeIfKeyData = activeIfKeyArray[i];
minimumDepth = activeIfKeyData.mininumDepth;
if (instance.depth <= minimumDepth &&
!(instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSEIF ||
instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSE)) {
if (FlowDiagramBuilderApi.requireUpdateOnEndOfIfKeys(activeIfKeyData, node, instanceType)) {
if (node.endOfIfKeys)
node.endOfIfKeys += ',' + activeIfKeyData.key;
else
node.endOfIfKeys = activeIfKeyData.key;
activeIfKeyArray.splice(i, 1);
i--;
}
}
}
}
// iterate through activeIfKeyByDepthHash for any records that is bigger depth
// if instance is ELSE or ELSE IF than attach the records with bigger depth to activeIfKeyByDepthHash[instance.depth]
// otherwise signify END OF IF statement
for (key in activeIfKeyByDepthHash) {
if (Object.prototype.hasOwnProperty.call(activeIfKeyByDepthHash, key)) {
activeIfKeyArray = activeIfKeyByDepthHash[key];
if (activeIfKeyArray && key > instance.depth) {
for (i = 0; i < activeIfKeyArray.length; i++) {
activeIfKeyData = activeIfKeyArray[i];
if (FlowDiagramBuilderApi.requireUpdateOnEndOfIfKeys(activeIfKeyData, node, instanceType)) {
if (instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSEIF ||
instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSE) {
// there must be an active starting IF node processed in the past and
// therefore activeIfKeyByDepthHash[instance.depth] cannot be empty. In other words, there has been IF node that inserted
// activeIfKeyByDepthHash[instance.depth] that has not been removed
activeIfKeyData.mininumDepth = instance.depth;
activeIfKeyByDepthHash[instance.depth].push(activeIfKeyData);
} else {
if (node.endOfIfKeys)
node.endOfIfKeys += ',' + activeIfKeyData.key;
else
node.endOfIfKeys = activeIfKeyData.key;
}
activeIfKeyArray.splice(i, 1);
i--;
}
}
}
}
}
if (instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.IF) {
if (activeIfKeyByDepthHash[instance.depth])
activeIfKeyByDepthHash[instance.depth].push({
key: node.key,
mininumDepth: instance.depth,
parallelBlockKey: node.parallelBlockKey ? node.parallelBlockKey : "",
decisionBlockKey: node.decisionBlockKey ? node.decisionBlockKey : "",
});
else
activeIfKeyByDepthHash[instance.depth] = [{
key: node.key,
mininumDepth: instance.depth,
parallelBlockKey: node.parallelBlockKey ? node.parallelBlockKey : "",
decisionBlockKey: node.decisionBlockKey ? node.decisionBlockKey : "",
}];
}
};
FlowDiagramBuilderApi.deleteLoopbackEdgesToInstanceParent = function(edges, instance, hierarchyStack, edgesList) {
for (var i = 0; i < edges.length; i++) {
if (edges[i].toPort === FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_INPUT_LOOP_BACK_SYS_ID && edges[i].to === instance.parent) {
// copyEdges is part of hierarchyStack so don't need to explicitly delete here
FlowDiagramBuilderApi.removeEdgeCompletely(edges[i], hierarchyStack, edgesList);
}
}
};
FlowDiagramBuilderApi.processLoopback = function(instanceType, instance, node, copyEdges, newEdges, totalEdges, hierarchyStack) {
// all of the loopback edges on higher depth that points to instance.parent should be removed and new loopback edges should be added according to type here
for (key in FlowDiagramBuilderApi.instanceRecordsHash) {
if (Object.prototype.hasOwnProperty.call(FlowDiagramBuilderApi.instanceRecordsHash, key)) {
var val = FlowDiagramBuilderApi.instanceRecordsHash[key];
if (val) {
var edges = val.edges;
FlowDiagramBuilderApi.deleteLoopbackEdgesToInstanceParent(edges, instance, hierarchyStack, [edges, copyEdges, newEdges, totalEdges]);
}
}
}
FlowDiagramBuilderApi.deleteLoopbackEdgesToInstanceParent(copyEdges, instance, hierarchyStack, [copyEdges, newEdges, totalEdges]);
FlowDiagramBuilderApi.deleteLoopbackEdgesToInstanceParent(newEdges, instance, hierarchyStack, [copyEdges, newEdges, totalEdges]);
var edgeData = {
from: node.key,
to: instance.parent,
edgeId: FlowDiagramBuilderApi.edgeId,
toPort: FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_INPUT_LOOP_BACK_SYS_ID,
linkLabel: FlowDiagramConstants.LINK_LABELS.loopBackConnector,
labelColor: FlowDiagramConstants.LINK_LABEL_COLORS.loop,
addBtnVisibility: FlowDiagramConstants.LINK_ADD_BTN_VISIBILITY.forLoopLoopback,
};
if (instanceType === FlowDiagramConstants.INSTANCE_TYPES.ACTION_INSTANCE_TYPE ||
instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.FOREACH ||
instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.DOUNTIL ||
instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.PATHBROKEN ||
instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLEL ||
instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLELBLOCK ||
instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISION ||
instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISIONBLOCK ||
instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.GOBACKTO ||
Object.keys(FlowDiagramConstants.FLOW_LOGIC_TYPES.ONE_TO_ONE).toString().includes(instanceType)) {
// add one
var fromPort;
if (instanceType === FlowDiagramConstants.INSTANCE_TYPES.ACTION_INSTANCE_TYPE)
fromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_ACTION_OUTPUT_SYS_ID;
else if (instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.FOREACH || instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.DOUNTIL)
fromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_OUTPUT_SYS_ID;
else if (instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.PATHBROKEN)
fromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_PATH_BROKEN_OUTPUT_SYS_ID;
else if (Object.keys(FlowDiagramConstants.FLOW_LOGIC_TYPES.ONE_TO_ONE).toString().includes(instanceType))
fromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_ONE_TO_ONE_OUTPUT;
else if (instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLEL)
fromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_ONE_TO_MANY_OUTPUT;
else if (instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLELBLOCK)
fromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_PARALLELBLOCK_OUTPUT_SYS_ID;
else if (instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISION)
fromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_ONE_TO_MANY_OUTPUT;
else if (instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISIONBLOCK)
fromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_DECISIONBLOCK_OUTPUT_SYS_ID;
else if (instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.GOBACKTO)
fromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_GOBACKTO_OUTPUT_SYS_ID;
var edge = FlowDiagramTranslateModel.getEdgeModel();
if (edge) {
edgeData.fromPort = fromPort;
edge = FlowDiagramBuilderApi.updateEdge(edge, edgeData);
copyEdges.push(edge);
newEdges.push(edge);
}
} else if (instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.IF) {
// add two
var trueEdge = FlowDiagramTranslateModel.getEdgeModel();
if (trueEdge) {
edgeData.fromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_IF_ELSE_OUTPUT_TRUE_SYS_ID;
trueEdge = FlowDiagramBuilderApi.updateEdge(trueEdge, edgeData);
copyEdges.push(trueEdge);
newEdges.push(trueEdge);
}
var falseEdge = FlowDiagramTranslateModel.getEdgeModel();
if (falseEdge) {
edgeData.fromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_IF_ELSE_OUTPUT_FALSE_SYS_ID;
falseEdge = FlowDiagramBuilderApi.updateEdge(falseEdge, edgeData);
copyEdges.push(falseEdge);
newEdges.push(falseEdge);
}
} else if (instanceType === FlowDiagramConstants.FLOW_LOGIC_TYPES.END) {
// don't add
}
};
FlowDiagramBuilderApi.processElseNode = function(errors, hierarchyStack, instance, node, totalEdges, totalNodes, newEdges) {
var i, j;
var addNewNode = true;
var targetKey = node.key;
var allPathsBlocked = false;
if (FlowDiagramBuilderApi.instanceRecordsHash[targetKey]) {
var targetType = FlowDiagramBuilderApi.instanceRecordsHash[targetKey].type;
if (targetType === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSE) {
var edges = FlowDiagramBuilderApi.instanceRecordsHash[targetKey].edges;
var trueEdge;
var falseEdge;
for (i = 0; i < edges.length; i++) {
if (edges[i].fromPort === FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_IF_ELSE_OUTPUT_FALSE_SYS_ID && edges[i].toPort !== FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_INPUT_LOOP_BACK_SYS_ID)
falseEdge = edges[i];
else if (edges[i].fromPort === FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_IF_ELSE_OUTPUT_TRUE_SYS_ID && edges[i].toPort !== FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_INPUT_LOOP_BACK_SYS_ID)
trueEdge = edges[i];
}
// newTargetKey is node's key next to ELSE node and all edges pointing to the ELSE node should point to newTargetKey
var newTargetNodeKey = "";
for (key in FlowDiagramBuilderApi.instanceRecordsHash) {
if (Object.prototype.hasOwnProperty.call(FlowDiagramBuilderApi.instanceRecordsHash, key)) {
var val = FlowDiagramBuilderApi.instanceRecordsHash[key];
if (val && val.edges) {
for (i = 0; i < val.edges.length; i++) {
// Find the TRUE edge that is pointing (edge.to === node.key) to ELSE node. Make the found edge point to where ELSE node is pointing to.
if (val.edges[i].to === targetKey && trueEdge) {
newTargetNodeKey = trueEdge.to; // trueEdge.to usually points to END node at this point
val.edges[i].to = trueEdge.to;
val.edges[i].toPort = trueEdge.toPort;
if (hierarchyStack[hierarchyStack.length - 1] && hierarchyStack[hierarchyStack.length - 1].type === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSE) {
hierarchyStack[hierarchyStack.length - 1].edges.push(val.edges[i]);
}
}
}
}
}
}
if (falseEdge)
FlowDiagramBuilderApi.removeEdgeCompletely(falseEdge, hierarchyStack, [edges, newEdges, totalEdges]);
if (trueEdge)
FlowDiagramBuilderApi.removeEdgeCompletely(trueEdge, hierarchyStack, [edges, newEdges, totalEdges]);
// don't add the ELSE node to totalNodes/diagramJSON
addNewNode = false;
// add entry in the hash to identify which node replaces the else node deleted
FlowDiagramBuilderApi.addElseReplacementHash(node, newTargetNodeKey);
}
}
return addNewNode;
};
FlowDiagramBuilderApi.addElseReplacementHash = function(node, newTargetNodeKey) {
if (node && FlowDiagramBuilderApi.elseReplacementHash) {
FlowDiagramBuilderApi.elseReplacementHash[node.key] = {
key: FlowDiagramBuilderApi.previousIfOrElseIfKey,
newTargetNodeKey: newTargetNodeKey,
node: node
};
}
};
// previousKeys are keys previously visited by this recursion. used to prevent stack overflow. Stored in resultArray[1]
// highestOrder (resultArray[0]) is used to find out the highest order of node visited.
// if end node is visited then highest order = FlowDiagramBuilderApi.instanceRecordsOrderedList.length
// if endNodeKey is reached then that means there is no path blockage up to endNodeKey and therefore return false
FlowDiagramBuilderApi.isAllEdgesEndWithoutReachingTheEnd = function(key, previousKeys, totalEdges, resultArray, endNodeKey) {
// there could be an edge that has "from key" = "to key" (example: loopback edge to itself) so just return true in below
// below checks for if it is processing the same key that has been processed before
if (key && previousKeys.includes(key))
return true;
else
previousKeys += (key + ",");
if (key === FlowDiagramBuilderApi.endKey) {
resultArray[0] = FlowDiagramBuilderApi.instanceRecordsOrderedList.length;
resultArray[1] = previousKeys;
return false;
} else if (key === endNodeKey && FlowDiagramBuilderApi.instanceRecordsHash[key]) {
resultArray[0] = FlowDiagramBuilderApi.instanceRecordsHash[key].customOrder;
resultArray[1] = previousKeys;
return false;
} else if (FlowDiagramBuilderApi.instanceRecordsHash[key] && (FlowDiagramBuilderApi.instanceRecordsHash[key].type === FlowDiagramConstants.FLOW_LOGIC_TYPES.END || FlowDiagramBuilderApi.instanceRecordsHash[key].type === FlowDiagramConstants.FLOW_LOGIC_TYPES.GOBACKTO)) {
if (FlowDiagramBuilderApi.instanceRecordsHash[key].customOrder > resultArray[0]) {
resultArray[0] = FlowDiagramBuilderApi.instanceRecordsHash[key].customOrder;
resultArray[1] = previousKeys;
}
return true;
}
for (var i = 0; i < totalEdges.length; i++) {
// loopbacks don't count since it will never be a leaf node
if (totalEdges[i] && totalEdges[i].from && totalEdges[i].to && totalEdges[i].from === key && totalEdges[i].toPort !== FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_INPUT_LOOP_BACK_SYS_ID) {
if (FlowDiagramBuilderApi.isAllEdgesEndWithoutReachingTheEnd(totalEdges[i].to, previousKeys, totalEdges, resultArray, endNodeKey) === false)
return false;
}
}
return true;
};
FlowDiagramBuilderApi.insertInstanceRecordInDepthArray = function(record) {
var depthArray;
if (!record.depth) {
if (record.parent) {
var recordParent = FlowDiagramBuilderApi.instanceRecordsHash[record.parent];
if (recordParent)
record.depth = 1 + recordParent.depth;
else
record.depth = 0;
} else {
record.depth = 1;
}
}
depthArray = FlowDiagramBuilderApi.getInstanceRecordInDepthArray(record.depth);
depthArray.push(record);
};
FlowDiagramBuilderApi.getInstanceRecordInDepthArray = function(depth) {
if (!FlowDiagramBuilderApi.instanceRecordsDepthHash[depth])
FlowDiagramBuilderApi.instanceRecordsDepthHash[depth] = [];
return FlowDiagramBuilderApi.instanceRecordsDepthHash[depth];
};
FlowDiagramBuilderApi.addNewEdges = function(instance, node, newEdges, copyEdges, hierarchyStack, totalNodes) {
var newEdge;
var instanceRecordsHashEdges = [];
var i, j;
var type = "";
for (i = 0; i < copyEdges.length; i++)
instanceRecordsHashEdges.push(copyEdges[i]);
if (instance.actionType || instance.subFlow) {
type = FlowDiagramConstants.INSTANCE_TYPES.ACTION_INSTANCE_TYPE;
FlowDiagramBuilderApi.instanceRecordsHash[instance.uiUniqueIdentifier] = {
key: instance.uiUniqueIdentifier,
parent: instance.parent,
type: type,
edges: instanceRecordsHashEdges,
deletedLoopbackEdges: null,
connectedCount: 0,
depth: instance.depth,
};
FlowDiagramBuilderApi.insertInstanceRecordInDepthArray(FlowDiagramBuilderApi.instanceRecordsHash[instance.uiUniqueIdentifier]);
FlowDiagramBuilderApi.insertInstanceRecordInOrderedListWithOrder(FlowDiagramBuilderApi.instanceRecordsHash[instance.uiUniqueIdentifier]);
newEdge = FlowDiagramTranslateModel.getEdgeModel();
if (newEdge) {
newEdge = FlowDiagramBuilderApi.updateEdge(
newEdge, {
from: node.key,
to: FlowDiagramBuilderApi.endKey,
fromPort: FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_ACTION_OUTPUT_SYS_ID,
toPort: FlowDiagramBuilderApi.endPort,
edgeId: FlowDiagramBuilderApi.edgeId,
}
);
newEdges.push(newEdge);
copyEdges.push(newEdge);
instanceRecordsHashEdges.push(newEdge);
}
} else if (instance.flowLogicDefinition && instance.flowLogicDefinition.type) {
type = instance.flowLogicDefinition.type;
FlowDiagramBuilderApi.instanceRecordsHash[instance.uiUniqueIdentifier] = {
key: instance.uiUniqueIdentifier,
parent: instance.parent,
orderBottom: instance.orderBottom,
data: instance.data,
type: type,
edges: instanceRecordsHashEdges,
deletedLoopbackEdges: null,
connectedCount: 0,
depth: instance.depth,
};
FlowDiagramBuilderApi.insertInstanceRecordInDepthArray(FlowDiagramBuilderApi.instanceRecordsHash[instance.uiUniqueIdentifier]);
FlowDiagramBuilderApi.insertInstanceRecordInOrderedListWithOrder(FlowDiagramBuilderApi.instanceRecordsHash[instance.uiUniqueIdentifier]);
if (instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.IF || instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSE || instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.ELSEIF) {
var updatedEdgeData = {
from: node.key,
to: FlowDiagramBuilderApi.endKey,
toPort: FlowDiagramBuilderApi.endPort,
edgeId: FlowDiagramBuilderApi.edgeId,
labelColor: FlowDiagramConstants.LINK_LABEL_COLORS.decision,
};
var newEdgeTrue = FlowDiagramTranslateModel.getEdgeModel();
if (newEdgeTrue) {
updatedEdgeData.fromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_IF_ELSE_OUTPUT_TRUE_SYS_ID;
updatedEdgeData.linkLabel = FlowDiagramConstants.LINK_LABELS.trueConnector;
newEdgeTrue = FlowDiagramBuilderApi.updateEdge(newEdgeTrue, updatedEdgeData);
newEdges.push(newEdgeTrue);
copyEdges.push(newEdgeTrue);
instanceRecordsHashEdges.push(newEdgeTrue);
}
var newEdgeFalse = FlowDiagramTranslateModel.getEdgeModel();
if (newEdgeFalse) {
updatedEdgeData.fromPort = FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_IF_ELSE_OUTPUT_FALSE_SYS_ID;
updatedEdgeData.linkLabel = FlowDiagramConstants.LINK_LABELS.falseConnector;
newEdgeFalse = FlowDiagramBuilderApi.updateEdge(newEdgeFalse, updatedEdgeData);
newEdges.push(newEdgeFalse);
copyEdges.push(newEdgeFalse);
instanceRecordsHashEdges.push(newEdgeFalse);
}
} else if (instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.FOREACH || instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.DOUNTIL) {
var newEdgeLoopContinue = FlowDiagramTranslateModel.getEdgeModel();
if (newEdgeLoopContinue) {
newEdgeLoopContinue = FlowDiagramBuilderApi.updateEdge(
newEdgeLoopContinue, {
from: node.key,
to: FlowDiagramBuilderApi.endKey,
fromPort: FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_OUTPUT_SYS_ID,
toPort: FlowDiagramBuilderApi.endPort,
edgeId: FlowDiagramBuilderApi.edgeId,
linkLabel: "", // intentionally left blank
labelColor: FlowDiagramConstants.LINK_LABEL_COLORS.loop,
addBtnVisibility: FlowDiagramConstants.LINK_ADD_BTN_VISIBILITY.forLoopWithChildEnd,
}
);
newEdges.push(newEdgeLoopContinue);
copyEdges.push(newEdgeLoopContinue);
instanceRecordsHashEdges.push(newEdgeLoopContinue);
}
var newEdgeLoopBack = FlowDiagramTranslateModel.getEdgeModel();
if (newEdgeLoopBack) {
newEdgeLoopBack = FlowDiagramBuilderApi.updateEdge(
newEdgeLoopBack, {
from: node.key,
to: node.key,
fromPort: FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_OUTPUT_SYS_ID,
toPort: FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_FOR_INPUT_LOOP_BACK_SYS_ID,
edgeId: FlowDiagramBuilderApi.edgeId,
linkLabel: "", // intentionally left blank
labelColor: FlowDiagramConstants.LINK_LABEL_COLORS.loop,
addBtnVisibility: FlowDiagramConstants.LINK_ADD_BTN_VISIBILITY.forLoopLoopback,
}
);
newEdges.push(newEdgeLoopBack);
copyEdges.push(newEdgeLoopBack);
instanceRecordsHashEdges.push(newEdgeLoopBack);
}
} else if (Object.keys(FlowDiagramConstants.FLOW_LOGIC_TYPES.ONE_TO_ONE).toString().includes(instance.flowLogicDefinition.type)) {
var newLogicEdge = FlowDiagramTranslateModel.getEdgeModel();
if (newLogicEdge) {
var updatedLogicEdgeData = {
from: node.key,
to: FlowDiagramBuilderApi.endKey,
toPort: FlowDiagramBuilderApi.endPort,
fromPort: FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_ONE_TO_ONE_OUTPUT,
edgeId: FlowDiagramBuilderApi.edgeId
};
newLogicEdge = FlowDiagramBuilderApi.updateEdge(newLogicEdge, updatedLogicEdgeData);
newEdges.push(newLogicEdge);
copyEdges.push(newLogicEdge);
instanceRecordsHashEdges.push(newLogicEdge);
}
} else if (instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.END) {
// Intentionally empty for node.node_id === FlowDiagramConstants.SYS_IDS.FLOW_LOGIC_END_ACTION; END node does not have an output edge
} else if (instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLEL) {
var newEdgeParallel = FlowDiagramTranslateModel.getEdgeModel();
if (newEdgeParallel) {
if (!instance.children || instance.children.length === 0) {
newEdgeParallel = FlowDiagramBuilderApi.updateEdge(
newEdgeParallel, {
from: node.key,
to: FlowDiagramBuilderApi.endKey,
fromPort: FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_ONE_TO_MANY_OUTPUT,
toPort: FlowDiagramConstants.DIAGRAM_PORT_END_PARALLEL_OUTPUT_SYS_ID,
edgeId: FlowDiagramBuilderApi.edgeId,
linkLabel: "", // intentionally left blank
labelColor: FlowDiagramConstants.LINK_LABEL_COLORS.decision,
addBtnVisibility: FlowDiagramConstants.LINK_ADD_BTN_VISIBILITY.forLoopEmptyEnd,
addBtnText: "+ Add branch",
}
);
} else {
newEdgeParallel = FlowDiagramBuilderApi.updateEdge(
newEdgeParallel, {
from: node.key,
to: node.key,
fromPort: FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_ONE_TO_ONE_OUTPUT,
toPort: FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_ONE_TO_MANY_INPUT,
edgeId: FlowDiagramBuilderApi.edgeId,
linkLabel: "", // intentionally left blank
labelColor: FlowDiagramConstants.LINK_LABEL_COLORS.decision,
addBtnVisibility: FlowDiagramConstants.LINK_ADD_BTN_VISIBILITY.forLoopLoopback,
}
);
FlowDiagramBuilderApi.parallelBranchAddLookup[node.key] = instance.children[instance.children.length - 1]; // capture key of last child for turning on add branch on
}
newEdges.push(newEdgeParallel);
copyEdges.push(newEdgeParallel);
instanceRecordsHashEdges.push(newEdgeParallel);
}
} else if (instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.PARALLELBLOCK) {
// add extra edge to connect between PARALLEL and PARALLELBLOCK (current node)
// PARALLEL will always be the instance.parent of PARALLELBLOCK
var newEdgeFromParallel = FlowDiagramTranslateModel.getEdgeModel();
if (newEdgeFromParallel) {
if (FlowDiagramBuilderApi.parallelBranchAddLookup[instance.parent] && FlowDiagramBuilderApi.parallelBranchAddLookup[instance.parent] === node.key) {
newEdgeFromParallel = FlowDiagramBuilderApi.updateEdge(
newEdgeFromParallel, {
from: instance.parent,
to: node.key,
fromPort: FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_ONE_TO_MANY_OUTPUT,
toPort: FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_PARALLELBLOCK_INPUT_SYS_ID,
edgeId: FlowDiagramBuilderApi.edgeId,
linkLabel: "", // intentionally left blank
labelColor: FlowDiagramConstants.LINK_LABEL_COLORS.decision,
addBtnVisibility: FlowDiagramConstants.LINK_ADD_BTN_VISIBILITY.forLoopEmptyEnd,
addBtnText: "+ Add branch"
}
);
} else {
newEdgeFromParallel = FlowDiagramBuilderApi.updateEdge(
newEdgeFromParallel, {
from: instance.parent,
to: node.key,
fromPort: FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_ONE_TO_MANY_OUTPUT,
toPort: FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_PARALLELBLOCK_INPUT_SYS_ID,
edgeId: FlowDiagramBuilderApi.edgeId,
linkLabel: "", // intentionally left blank
labelColor: FlowDiagramConstants.LINK_LABEL_COLORS.decision,
addBtnVisibility: FlowDiagramConstants.LINK_ADD_BTN_VISIBILITY.forLoopLoopback
}
);
}
newEdges.push(newEdgeFromParallel);
copyEdges.push(newEdgeFromParallel);
instanceRecordsHashEdges.push(newEdgeFromParallel);
}
var newEdgeParallelBlock = FlowDiagramTranslateModel.getEdgeModel();
if (newEdgeParallelBlock) {
newEdgeParallelBlock = FlowDiagramBuilderApi.updateEdge(
newEdgeParallelBlock, {
from: node.key,
to: FlowDiagramBuilderApi.endKey,
fromPort: FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_PARALLELBLOCK_OUTPUT_SYS_ID,
toPort: FlowDiagramBuilderApi.endPort,
edgeId: FlowDiagramBuilderApi.edgeId,
linkLabel: "", // intentionally left blank
labelColor: FlowDiagramConstants.LINK_LABEL_COLORS.decision,
addBtnVisibility: FlowDiagramConstants.LINK_ADD_BTN_VISIBILITY.forLoopEmptyEnd,
}
);
newEdges.push(newEdgeParallelBlock);
copyEdges.push(newEdgeParallelBlock);
instanceRecordsHashEdges.push(newEdgeParallelBlock);
}
} else if (instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISION) {
var newEdgeDecision = FlowDiagramTranslateModel.getEdgeModel();
if (newEdgeDecision) {
if (!instance.children || instance.children.length === 0) {
// If there is no children, then there is no associated DECISIONBLOCK. Currently we add the edge between DECISION
// and DECISIONBLOCK when DECISIONBLOCK is added and therefore we need to add an edge to END node if there is no
// associated DECISIONBLOCK
newEdgeDecision = FlowDiagramBuilderApi.updateEdge(
newEdgeDecision, {
from: node.key,
to: FlowDiagramBuilderApi.endKey,
fromPort: FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_ONE_TO_MANY_OUTPUT,
toPort: FlowDiagramBuilderApi.endPort,
edgeId: FlowDiagramBuilderApi.edgeId,
linkLabel: "", // intentionally left blank
labelColor: FlowDiagramConstants.LINK_LABEL_COLORS.decision,
addBtnVisibility: FlowDiagramConstants.LINK_ADD_BTN_VISIBILITY.forLoopLoopback,
}
);
newEdges.push(newEdgeDecision);
copyEdges.push(newEdgeDecision);
instanceRecordsHashEdges.push(newEdgeDecision);
}
if (instance.decisionTableInformation && instance.decisionTableInformation.decision_answers instanceof Array) {
var decisionAnswers = instance.decisionTableInformation.decision_answers;
for (j = 0; j < decisionAnswers.length; j++) {
if (decisionAnswers[j].valid && decisionAnswers[j].value) {
// FlowDiagramBuilderApi.decisionAnswers[decisionAnswers[j].value] can be overwritten with same value of decisionAnswers[j].label
// and that is fine
FlowDiagramBuilderApi.decisionAnswers[decisionAnswers[j].value] = decisionAnswers[j].label;
}
}
}
}
// Update node display name based on label
if (node && node.data && node.data.decision_label) {
node.name = node.data.decision_label;
} else {
node.name = FlowDiagramConstants.NODE_NAMES.DECISION;
}
} else if (instance.flowLogicDefinition.type === FlowDiagramConstants.FLOW_LOGIC_TYPES.DECISIONBLOCK) {
// add extra edge to connect between DECISION and DECISIONBLOCK (current node)
// DECISION will always be the instance.parent of DECISIONBLOCK
var newEdgeFromDecision = FlowDiagramTranslateModel.getEdgeModel();
if (newEdgeFromDecision) {
newEdgeFromDecision = FlowDiagramBuilderApi.updateEdge(
newEdgeFromDecision, {
from: instance.parent,
to: node.key,
fromPort: FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_ONE_TO_MANY_OUTPUT,
toPort: FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_DECISIONBLOCK_INPUT_SYS_ID,
edgeId: FlowDiagramBuilderApi.edgeId,
linkLabel: "", // intentionally left blank
labelColor: FlowDiagramConstants.LINK_LABEL_COLORS.decision,
addBtnVisibility: FlowDiagramConstants.LINK_ADD_BTN_VISIBILITY.forLoopEmptyEnd
}
);
newEdges.push(newEdgeFromDecision);
copyEdges.push(newEdgeFromDecision);
instanceRecordsHashEdges.push(newEdgeFromDecision);
}
var newEdgeDecisionBlock = FlowDiagramTranslateModel.getEdgeModel();
if (newEdgeDecisionBlock) {
newEdgeDecisionBlock = FlowDiagramBuilderApi.updateEdge(
newEdgeDecisionBlock, {
from: node.key,
to: FlowDiagramBuilderApi.endKey,
fromPort: FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_DECISIONBLOCK_OUTPUT_SYS_ID,
toPort: FlowDiagramBuilderApi.endPort,
edgeId: FlowDiagramBuilderApi.edgeId,
linkLabel: "", // intentionally left blank
labelColor: FlowDiagramConstants.LINK_LABEL_COLORS.decision,
addBtnVisibility: FlowDiagramConstants.LINK_ADD_BTN_VISIBILITY.forLoopEmptyEnd,
}
);
newEdges.push(newEdgeDecisionBlock);
copyEdges.push(newEdgeDecisionBlock);
instanceRecordsHashEdges.push(newEdgeDecisionBlock);
}
if (instance.inputs instanceof Array) {
// Finding appropriate name/label for this decision block
var newName;
for (j = 0; j < instance.inputs.length; j++) {
var instanceInput = instance.inputs[j];
// If this is the label input and it is populated, use that
if (instanceInput.field_name === 'label' && instanceInput.value) {
newName = instanceInput.value;
}
// Otherwise, if we haven't found the name already, use the default decision answer label
else if (!newName && instanceInput.field_name === 'answer') {
if (instanceInput.value === '')
newName = 'Otherwise';
else if (instanceInput.value) {
var label = FlowDiagramBuilderApi.decisionAnswers[instanceInput.value];
if (label)
newName = label;
}
}
}
if (newName) {
node.name = newName;
}
}
} else if (type === FlowDiagramConstants.FLOW_LOGIC_TYPES.GOBACKTO) {
var newEdge = FlowDiagramTranslateModel.getEdgeModel();
if (newEdge) {
// to, toPort, linkLabel will be updated later
var to = FlowDiagramBuilderApi.endKey;
var toPort = FlowDiagramBuilderApi.endPort;
newEdge = FlowDiagramBuilderApi.updateEdge(
newEdge, {
from: node.key,
to: to,
fromPort: FlowDiagramConstants.SYS_IDS.DIAGRAM_PORT_GOBACKTO_OUTPUT_SYS_ID,
toPort: toPort,
edgeId: FlowDiagramBuilderApi.edgeId,
addBtnVisibility: "never",
linkLabel: "",
labelColor: FlowDiagramConstants.LINK_LABEL_COLORS.loop,
}
);
newEdges.push(newEdge);
copyEdges.push(newEdge);
}
}
}
// need to separate copyEdges vs newEdges because an edge can be taken out from copyEdges array whereas
// no edge will be taken out from newEdges
hierarchyStack.push({
key: instance.uiUniqueIdentifier,
parent: instance.parent,
depth: instance.depth,
edges: copyEdges,
type: type,
deletedLoopbackEdges: null,
});
FlowDiagramBuilderApi.recordOrder++;
};
FlowDiagramBuilderApi.insertInstanceRecordInOrderedListWithOrder = function(record) {
record.customOrder = FlowDiagramBuilderApi.recordOrder;
FlowDiagramBuilderApi.instanceRecordsOrderedList.push(record);
};
// assigns "type" to flow_diagram_type field for all records in an array
FlowDiagramBuilderApi.assignFlowDiagramType = function(targetArray, type) {
if (targetArray instanceof Array) {
for (var i = 0; i < targetArray.length; i++)
targetArray[i].flow_diagram_type = type;
}
};
FlowDiagramBuilderApi.updateEdge = function(edge, updateData) {
if (edge) {
edge.from = updateData.from;
edge.to = updateData.to;
edge.fromPort = updateData.fromPort || updateData.fromPort;
edge.toPort = updateData.toPort || updateData.toPort;
edge.edge_id = updateData.edgeId;
edge.linkLabel = updateData.linkLabel || '';
edge.labelColor = updateData.labelColor || '';
edge.addBtnVisibility = updateData.addBtnVisibility || 'onHover';
edge.addBtnText = updateData.addBtnText || '';
edge.hideToArrow = updateData.hideToArrow || false;
}
return edge;
};
FlowDiagramBuilderApi.getDiagramDifference = function(newDiagram, oldDiagram, errors) {
if (!errors || !(errors instanceof Array))
errors = [];
function arrayToMap(arr, keyFunc) {
// Map is not defined in rhino engine
var result = {};
arr.forEach(function(item) {
var key = keyFunc(item);
result[key] = item;
});
return result;
}
function getNodeKey(node) {
return node.key;
}
function getEdgeKey(edge) {
return edge.from + "|" + edge.to + "|" + edge.fromPort + "|" + edge.toPort;
}
function getAddedItems(newArr, oldMap, keyFunc) {
return newArr.filter(function(item) {
return !oldMap.hasOwnProperty(keyFunc(item));
});
}
function getDeletedItems(oldArr, newMap, keyFunc) {
return oldArr.filter(function(item) {
return !newMap.hasOwnProperty(keyFunc(item));
});
}
function isEquivalentValue(v1, v2) {
if (Array.isArray(v1) && Array.isArray(v2)) {
return isEquivalentArray(v1, v2);
} else if (
v1 !== null &&
typeof v1 === "object" &&
v2 !== null &&
typeof v2 === "object"
) {
// the typeof an Array value is also "object"
return isEquivalentObject(v1, v2);
} else {
return v1 == v2;
}
}
function isEquivalentArray(arr1, arr2) {
if (arr1.length !== arr2.length) {
return false;
}
for (var i = 0; i < arr1.length; i++) {
if (!isEquivalentValue(arr1[i], arr2[i])) {
return false;
}
}
return true;
}
function isEquivalentObject(obj1, obj2, propNames) {
var props1 = Object.getOwnPropertyNames(obj1);
var props2 = Object.getOwnPropertyNames(obj2);
if (propNames !== undefined) {
props1 = props1.filter(function(name) {
return propNames.indexOf(name) !== -1;
});
props2 = props2.filter(function(name) {
return propNames.indexOf(name) !== -1;
});
}
if (props1.length !== props2.length) {
return false;
}
for (var i = 0; i < props1.length; i++) {
var propName = props1[i];
if (!isEquivalentValue(obj1[propName], obj2[propName])) {
return false;
}
}
return true;
}
function getUpdatedItems(newArray, oldMap, keyFunc, propNames) {
return newArray.filter(function(item) {
var oldItem = oldMap[keyFunc(item)];
if (!oldItem) {
return false;
}
return !isEquivalentObject(item, oldItem, propNames);
});
}
var newNodes = newDiagram.diagramJSON.nodes;
var oldNodes = oldDiagram.diagramJSON.nodes;
var newEdges = newDiagram.diagramJSON.edges;
var oldEdges = oldDiagram.diagramJSON.edges;
// key -> node object
var newNodeMap = arrayToMap(newNodes, getNodeKey);
var oldNodeMap = arrayToMap(oldNodes, getNodeKey);
// from|to|fromPort|toPort -> edge object
var newEdgeMap = arrayToMap(newEdges, getEdgeKey);
var oldEdgeMap = arrayToMap(oldEdges, getEdgeKey);
var addedNodes = getAddedItems(newNodes, oldNodeMap, getNodeKey);
// make sure that we do not compare the template related attributes
var updatedNodes = getUpdatedItems(newNodes, oldNodeMap, getNodeKey, ['node_id', 'name', 'instance_sys_id', 'definition_sys_id', 'instanceType', 'order', 'relation', 'data']);
var deletedNodes = getDeletedItems(oldNodes, newNodeMap, getNodeKey);
var addedEdges = getAddedItems(newEdges, oldEdgeMap, getEdgeKey);
var deletedEdges = getDeletedItems(oldEdges, newEdgeMap, getEdgeKey);
var updatedEdges = getUpdatedItems(newEdges, oldEdgeMap, getEdgeKey, ['linkLabel']);
var diff = {
nodes: {
added: addedNodes,
updated: updatedNodes,
deleted: deletedNodes
},
edges: {
added: addedEdges,
deleted: deletedEdges,
updated: updatedEdges
},
};
return diff;
};
Sys ID
60e7cec1ff3030104ef14ee9453bf1eb