Addresses a number of workflow related bugs

This commit is contained in:
mabashian 2018-11-16 12:12:39 -05:00
parent 281345dd67
commit 72263c5c7b
6 changed files with 229 additions and 208 deletions

View File

@ -186,16 +186,8 @@
margin: auto;
}
.WorkflowChart-tooltipArrow--right {
width: 0;
height: 0;
border-top: 10px solid transparent;
border-bottom: 10px solid transparent;
border-left: 10px solid @default-interface-txt;
margin: auto;
position: relative;
right: -55px;
top: -34px;
.WorkflowChart-tooltipArrow {
fill: @default-interface-txt;
}
.WorkflowChart-dashedNode {

View File

@ -240,6 +240,81 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge
function update() {
if(scope.dimensionsSet) {
const buildLinkTooltip = (d) => {
let sourceNode = d3.select(`#node-${d.source.id}`);
const sourceNodeX = d3.transform(sourceNode.attr("transform")).translate[0];
const sourceNodeY = d3.transform(sourceNode.attr("transform")).translate[1];
let targetNode = d3.select(`#node-${d.target.id}`);
const targetNodeX = d3.transform(targetNode.attr("transform")).translate[0];
const targetNodeY = d3.transform(targetNode.attr("transform")).translate[1];
let xPos, yPos, arrowPoints;
if (nodePositionMap[d.source.id].y === nodePositionMap[d.target.id].y) {
xPos = (sourceNodeX + nodeW + targetNodeX)/2 - 50;
yPos = (sourceNodeY + nodeH + targetNodeY)/2 - 70;
arrowPoints = {
pt1: {
x: xPos + 40,
y: yPos + 47
},
pt2: {
x: xPos + 60,
y: yPos + 47
},
pt3: {
x: xPos + 50,
y: yPos + 57
}
};
} else {
xPos = (sourceNodeX + nodeW + targetNodeX)/2 - 120;
yPos = (sourceNodeY + nodeH + targetNodeY)/2 - 30;
arrowPoints = {
pt1: {
x: xPos + 100,
y: yPos + 17
},
pt2: {
x: xPos + 100,
y: yPos + 33
},
pt3: {
x: xPos + 110,
y: yPos + 25
}
};
}
let edgeTypeLabel;
switch(d.edgeType) {
case "always":
edgeTypeLabel = TemplatesStrings.get('workflow_maker.ALWAYS');
break;
case "success":
edgeTypeLabel = TemplatesStrings.get('workflow_maker.ON_SUCCESS');
break;
case "failure":
edgeTypeLabel = TemplatesStrings.get('workflow_maker.ON_FAILURE');
break;
}
let linkInstructionText = !scope.readOnly ? TemplatesStrings.get('workflow_maker.EDIT_LINK_TOOLTIP') : TemplatesStrings.get('workflow_maker.VIEW_LINK_TOOLTIP');
let linkTooltip = svgGroup.append("g")
.attr("class", "WorkflowChart-tooltip");
linkTooltip.append("foreignObject")
.attr("transform", `translate(${xPos},${yPos})`)
.attr("width", 100)
.attr("height", 50)
.html(function(){
return `<div class='WorkflowChart-tooltipContents'>
<div>${TemplatesStrings.get('workflow_maker.RUN')}: ${edgeTypeLabel}</div>
<div>${linkInstructionText}</div>
</div>`;
});
linkTooltip.append("polygon")
.attr("class", "WorkflowChart-tooltipArrow")
.attr("points", function() {
return `${arrowPoints.pt1.x},${arrowPoints.pt1.y} ${arrowPoints.pt2.x},${arrowPoints.pt2.y} ${arrowPoints.pt3.x},${arrowPoints.pt3.y}`;
});
};
var g = new dagre.graphlib.Graph();
g.setGraph({rankdir: 'LR', nodesep: 30, ranksep: 120});
@ -336,7 +411,7 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge
baseSvg.selectAll(".WorkflowChart-circleBetweenNodes")
.attr("id", function(d){return "link-" + d.source.id + "-" + d.target.id + "-add";})
.style("display", function(d) { return (scope.graphState.isLinkMode || d.source.id === scope.graphState.nodeBeingAdded || d.target.id === scope.graphState.nodeBeingAdded || scope.readOnly) ? "none" : null; })
.style("display", function(d) { return (d.edgeType === 'placeholder' || scope.graphState.isLinkMode || d.source.id === scope.graphState.nodeBeingAdded || d.target.id === scope.graphState.nodeBeingAdded || scope.readOnly) ? "none" : null; })
.attr("cx", function(d) {
return (nodePositionMap[d.source.id].x + nodePositionMap[d.source.id].width + nodePositionMap[d.target.id].x)/2;
})
@ -356,7 +431,7 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge
});
baseSvg.selectAll(".WorkflowChart-betweenNodesIcon")
.style("display", function(d) { return (scope.graphState.isLinkMode || d.source.id === scope.graphState.nodeBeingAdded || d.target.id === scope.graphState.nodeBeingAdded || scope.readOnly) ? "none" : null; })
.style("display", function(d) { return (d.edgeType === 'placeholder' || scope.graphState.isLinkMode || d.source.id === scope.graphState.nodeBeingAdded || d.target.id === scope.graphState.nodeBeingAdded || scope.readOnly) ? "none" : null; })
.attr("transform", function(d) {
let translate;
@ -411,48 +486,20 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge
return [pt1, pt2, pt3, pt4].join(" ");
})
.on("mouseover", function(d) {
if(!scope.graphState.isLinkMode && !d.source.isStartNode && d.source.id !== scope.graphState.nodeBeingAdded && d.target.id !== scope.graphState.nodeBeingAdded && scope.mode !== 'details') {
if(
d.edgeType !== 'placeholder' &&
!scope.graphState.isLinkMode &&
!d.source.isStartNode &&
d.source.id !== scope.graphState.nodeBeingAdded &&
d.target.id !== scope.graphState.nodeBeingAdded &&
scope.mode !== 'details'
) {
$(`#link-${d.source.id}-${d.target.id}`).appendTo(`#aw-workflow-chart-g`);
d3.select(`#link-${d.source.id}-${d.target.id}`)
.classed("WorkflowChart-linkHovering", true);
let xPos, yPos, arrowClass;
if (nodePositionMap[d.source.id].y === nodePositionMap[d.target.id].y) {
xPos = (nodePositionMap[d.source.id].x + nodePositionMap[d.target.id].x)/2 + 45;
yPos = (nodePositionMap[d.source.id].y + nodePositionMap[d.target.id].y)/2 - 107;
arrowClass = 'WorkflowChart-tooltipArrow--down';
} else {
xPos = (nodePositionMap[d.source.id].x + nodePositionMap[d.target.id].x)/2 - 30;
yPos = (nodePositionMap[d.source.id].y + nodePositionMap[d.target.id].y)/2 - 70;
arrowClass = 'WorkflowChart-tooltipArrow--right';
}
let edgeTypeLabel;
switch(d.edgeType) {
case "always":
edgeTypeLabel = TemplatesStrings.get('workflow_maker.ALWAYS');
break;
case "success":
edgeTypeLabel = TemplatesStrings.get('workflow_maker.ON_SUCCESS');
break;
case "failure":
edgeTypeLabel = TemplatesStrings.get('workflow_maker.ON_FAILURE');
break;
}
let linkInstructionText = !scope.readOnly ? TemplatesStrings.get('workflow_maker.EDIT_LINK_TOOLTIP') : TemplatesStrings.get('workflow_maker.VIEW_LINK_TOOLTIP');
svgGroup.append("foreignObject")
.attr("transform", `translate(${xPos},${yPos})`)
.attr("width", 100)
.attr("height", 60)
.attr("class", "WorkflowChart-tooltip")
.html(function(){
return `<div class='WorkflowChart-tooltipContents'><div>${TemplatesStrings.get('workflow_maker.RUN')}: ${edgeTypeLabel}</div><div>${linkInstructionText}</div></div><div class='${arrowClass}'></div>`;
});
buildLinkTooltip(d);
}
})
.on("mouseout", function(d){
if(!d.source.isStartNode && d.target.id !== scope.graphState.nodeBeingAdded && scope.mode !== 'details') {
@ -471,46 +518,19 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge
.attr("d", lineData)
.call(edit_link)
.on("mouseenter", function(d) {
if(!scope.graphState.isLinkMode && !d.source.isStartNode && d.source.id !== scope.graphState.nodeBeingAdded && d.target.id !== scope.graphState.nodeBeingAdded && scope.mode !== 'details') {
if(
d.edgeType !== 'placeholder' &&
!scope.graphState.isLinkMode &&
!d.source.isStartNode &&
d.source.id !== scope.graphState.nodeBeingAdded &&
d.target.id !== scope.graphState.nodeBeingAdded &&
scope.mode !== 'details'
) {
$(`#link-${d.source.id}-${d.target.id}`).appendTo(`#aw-workflow-chart-g`);
d3.select("#link-" + d.source.id + "-" + d.target.id)
d3.select(`#link-${d.source.id}-${d.target.id}`)
.classed("WorkflowChart-linkHovering", true);
let xPos, yPos, arrowClass;
if (nodePositionMap[d.source.id].y === nodePositionMap[d.target.id].y) {
xPos = (nodePositionMap[d.source.id].x + nodePositionMap[d.target.id].x)/2 + 45;
yPos = (nodePositionMap[d.source.id].y + nodePositionMap[d.target.id].y)/2 - 107;
arrowClass = 'WorkflowChart-tooltipArrow--down';
} else {
xPos = (nodePositionMap[d.source.id].x + nodePositionMap[d.target.id].x)/2 - 30;
yPos = (nodePositionMap[d.source.id].y + nodePositionMap[d.target.id].y)/2 - 70;
arrowClass = 'WorkflowChart-tooltipArrow--right';
}
let edgeTypeLabel;
switch(d.edgeType) {
case "always":
edgeTypeLabel = TemplatesStrings.get('workflow_maker.ALWAYS');
break;
case "success":
edgeTypeLabel = TemplatesStrings.get('workflow_maker.ON_SUCCESS');
break;
case "failure":
edgeTypeLabel = TemplatesStrings.get('workflow_maker.ON_FAILURE');
break;
}
let linkInstructionText = !scope.readOnly ? TemplatesStrings.get('workflow_maker.EDIT_LINK_TOOLTIP') : TemplatesStrings.get('workflow_maker.VIEW_LINK_TOOLTIP');
svgGroup.append("foreignObject")
.attr("transform", `translate(${xPos},${yPos})`)
.attr("width", 100)
.attr("height", 60)
.attr("class", "WorkflowChart-tooltip")
.html(function(){
return `<div class='WorkflowChart-tooltipContents'><div>${TemplatesStrings.get('workflow_maker.RUN')}: ${edgeTypeLabel}</div><div>${linkInstructionText}</div></div><div class='${arrowClass}'></div>`;
});
buildLinkTooltip(d);
}
})
.on("mouseleave", function(d){
@ -543,7 +563,7 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge
.attr("id", function(d){return "link-" + d.source.id + "-" + d.target.id + "-add";})
.attr("r", 10)
.attr("class", "WorkflowChart-addCircle WorkflowChart-circleBetweenNodes")
.style("display", function(d) { return (scope.graphState.isLinkMode || d.source.id === scope.graphState.nodeBeingAdded || d.target.id === scope.graphState.nodeBeingAdded || scope.readOnly) ? "none" : null; })
.style("display", function(d) { return (d.edgeType === 'placeholder' || scope.graphState.isLinkMode || d.source.id === scope.graphState.nodeBeingAdded || d.target.id === scope.graphState.nodeBeingAdded || scope.readOnly) ? "none" : null; })
.attr("cx", function(d) {
return (nodePositionMap[d.source.id].x + nodePositionMap[d.source.id].width + nodePositionMap[d.target.id].x)/2;
})
@ -580,7 +600,7 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge
.size(60)
.type("cross")
)
.style("display", function(d) { return (scope.graphState.isLinkMode || d.source.id === scope.graphState.nodeBeingAdded || d.target.id === scope.graphState.nodeBeingAdded || scope.readOnly) ? "none" : null; })
.style("display", function(d) { return (d.edgeType === 'placeholder' || scope.graphState.isLinkMode || d.source.id === scope.graphState.nodeBeingAdded || d.target.id === scope.graphState.nodeBeingAdded || scope.readOnly) ? "none" : null; })
.attr("transform", function(d) {
let translate;
@ -1263,7 +1283,7 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge
function add_node_with_child() {
this.on("click", function(d) {
if(!scope.readOnly && !scope.graphState.isLinkMode) {
if(!scope.readOnly && !scope.graphState.isLinkMode && d.edgeType !== 'placeholder') {
scope.addNodeWithChild({
link: d
});
@ -1340,25 +1360,32 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge
}
};
if(d.job.id) {
if(d.job.type) {
if(d.unifiedJobTemplate) {
goToJobResults(d.unifiedJobTemplate.unified_job_type);
}
else {
// We don't have access to the unified resource and have to make
// We don't have access to the job type and have to make
// a GET request in order to find out what type job this was
// so that we can route the user to the correct stdout view
Rest.setUrl(GetBasePath("unified_jobs") + "?id=" + d.job.id);
Rest.setUrl(GetBasePath("workflow_jobs") + `${d.originalNodeObj.workflow_job}/workflow_nodes/?order_by=id`);
Rest.get()
.then(function (res) {
if(res.data.results && res.data.results.length > 0) {
goToJobResults(res.data.results[0].type);
}
})
.catch(({data, status}) => {
ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Unable to get job: ' + status });
});
.then(function (res) {
if (res.data.results && res.data.results.length > 0) {
const { results } = res.data;
const job = results.filter(result => result.summary_fields.job.id === d.job.id);
goToJobResults(job[0].summary_fields.job.type);
}
})
.catch(({
data,
status
}) => {
ProcessErrors(scope, data, status, null, {
hdr: 'Error!',
msg: 'Unable to get job: ' + status
});
});
}
}
});

View File

@ -97,7 +97,6 @@ export default [function(){
return {
arrayOfNodesForChart,
arrayOfLinksForChart,
chartNodeIdToIndexMapping,
nodeIdToChartNodeIdMapping,
nodeRef,
workflowMakerNodeIdCounter: nodeIdCounter

View File

@ -1,8 +1,8 @@
<div class="WorkflowMaker-formTitle">{{readOnly ? strings.get('workflow_maker.VIEW_LINK') : (linkConfig.mode === 'add' ? strings.get('workflow_maker.ADD_LINK') : strings.get('workflow_maker.EDIT_LINK')) }} | {{linkConfig.parent.name}} {{linkConfig.child ? 'to ' + linkConfig.child.name : ''}}</div>
<div class="WorkflowMaker-formTitle">{{readOnly ? strings.get('workflow_maker.VIEW_LINK') : (linkConfig.mode === 'add' ? strings.get('workflow_maker.ADD_LINK') : strings.get('workflow_maker.EDIT_LINK')) }} | {{linkConfig.source.name}} {{linkConfig.target ? 'to ' + linkConfig.target.name : ''}}</div>
<div class="WorkflowMaker-form">
<div class="form-group Form-formGroup Form-formGroup--singleColumn">
<div ng-show="linkConfig.mode === 'add' && !linkConfig.child">{{:: strings.get('workflow_maker.NEW_LINK')}}</div>
<span ng-show="linkConfig.child">
<div ng-show="linkConfig.mode === 'add' && !linkConfig.target">{{:: strings.get('workflow_maker.NEW_LINK')}}</div>
<span ng-show="linkConfig.target">
<label for="edgeType" class="Form-inputLabelContainer">
<span class="Form-requiredAsterisk">*</span>
<span class="Form-inputLabel">{{:: strings.get('workflow_maker.RUN') }}</span>
@ -25,6 +25,6 @@
<button type="button" class="btn btn-sm Form-primaryButton Form-primaryButton--noMargin" id="workflow_maker_unlink_btn" ng-show="!readOnly && linkConfig.canUnlink" ng-click="unlink()"> {{:: strings.get('workflow_maker.UNLINK') }}</button>
<button type="button" class="btn btn-sm Form-cancelButton" id="workflow_maker_cancel_link_btn" ng-show="!readOnly" ng-click="cancel()"> {{:: strings.get('CANCEL') }}</button>
<button type="button" class="btn btn-sm Form-cancelButton" id="workflow_maker_cancel_link_btn" ng-show="readOnly" ng-click="cancel()"> {{:: strings.get('CLOSE') }}</button>
<button type="button" class="btn btn-sm Form-saveButton" id="workflow_maker_select_link_btn" ng-show="!readOnly && linkConfig.child" ng-click="select({edgeType: edgeType.value})" ng-disabled="!edgeType"> {{:: strings.get('SAVE') }}</button>
<button type="button" class="btn btn-sm Form-saveButton" id="workflow_maker_select_link_btn" ng-show="!readOnly && linkConfig.target" ng-click="select({edgeType: edgeType.value})" ng-disabled="!edgeType"> {{:: strings.get('SAVE') }}</button>
</div>
</div>

View File

@ -1,11 +1,11 @@
<div ng-show="nodeFormDataLoaded">
<div class="WorkflowMaker-formTitle">{{nodeConfig.mode === 'edit' ? nodeConfig.node.unifiedJobTemplate.name : strings.get('workflow_maker.ADD_A_TEMPLATE')}}</div>
<div class="Form-tabHolder" ng-if="!readOnly">
<div class="WorkflowMaker-formTitle">{{nodeConfig.mode === 'edit' ? nodeConfig.node.fullUnifiedJobTemplateObject.name || nodeConfig.node.unifiedJobTemplate.name : strings.get('workflow_maker.ADD_A_TEMPLATE')}}</div>
<div class="Form-tabHolder" ng-show="!readOnly">
<div class="Form-tab WorkflowMaker-formTab" ng-class="{'is-selected': activeTab === 'jobs'}" ng-click="activeTab = 'jobs'">{{strings.get('workflow_maker.JOBS')}}</div>
<div class="Form-tab WorkflowMaker-formTab" ng-class="{'is-selected': activeTab === 'project_syncs'}" ng-click="activeTab = 'project_syncs'">{{strings.get('workflow_maker.PROJECT_SYNC')}}</div>
<div class="Form-tab WorkflowMaker-formTab" ng-class="{'is-selected': activeTab === 'inventory_syncs'}" ng-click="activeTab = 'inventory_syncs'">{{strings.get('workflow_maker.INVENTORY_SYNC')}}</div>
</div>
<div class="WorkflowMaker-formLists" ng-if="!readOnly">
<div class="WorkflowMaker-formLists" ng-show="!readOnly">
<div id="workflow-jobs-list" ng-show="activeTab === 'jobs'">
<div ng-hide="wf_maker_templates.length === 0 && (searchTags | isEmpty)">
<smart-search django-model="unified_job_templates" base-path="unified_job_templates" iterator="wf_maker_template" dataset="wf_maker_template_dataset" list="templateList" collection="wf_maker_templates" default-params="wf_maker_template_default_params" query-set="wf_maker_template_queryset" search-bar-full-width="true" search-tags="searchTags">
@ -141,7 +141,7 @@
</select>
</div>
</div>
<div ng-if="readOnly">
<div ng-show="readOnly">
<div
class="WorkflowMaker-readOnlyPromptText"
ng-show="nodeConfig.node.originalNodeObject.job_type !== null ||

View File

@ -20,7 +20,6 @@ export default ['$scope', 'TemplatesService',
let deletedNodeIds = [];
let workflowMakerNodeIdCounter = 1;
let nodeIdToChartNodeIdMapping = {};
let chartNodeIdToIndexMapping = {};
let nodeRef = {};
$scope.showKey = false;
@ -365,25 +364,22 @@ export default ['$scope', 'TemplatesService',
}
$scope.graphState.arrayOfNodesForChart.push({
index: $scope.graphState.arrayOfNodesForChart.length,
id: workflowMakerNodeIdCounter,
unifiedJobTemplate: null
});
$scope.graphState.nodeBeingAdded = workflowMakerNodeIdCounter;
chartNodeIdToIndexMapping[workflowMakerNodeIdCounter] = $scope.graphState.arrayOfNodesForChart.length - 1;
$scope.graphState.arrayOfLinksForChart.push({
source: $scope.graphState.arrayOfNodesForChart[parent.index],
target: $scope.graphState.arrayOfNodesForChart[chartNodeIdToIndexMapping[workflowMakerNodeIdCounter]],
source: {id: parent.id},
target: {id: workflowMakerNodeIdCounter},
edgeType: "placeholder"
});
$scope.nodeConfig = {
mode: "add",
nodeId: workflowMakerNodeIdCounter,
newNodeIsRoot: parent.index === 0
newNodeIsRoot: parent.id === 1
};
workflowMakerNodeIdCounter++;
@ -403,18 +399,15 @@ export default ['$scope', 'TemplatesService',
}
$scope.graphState.arrayOfNodesForChart.push({
index: $scope.graphState.arrayOfNodesForChart.length,
id: workflowMakerNodeIdCounter,
unifiedJobTemplate: null
});
$scope.graphState.nodeBeingAdded = workflowMakerNodeIdCounter;
chartNodeIdToIndexMapping[workflowMakerNodeIdCounter] = $scope.graphState.arrayOfNodesForChart.length - 1;
$scope.graphState.arrayOfLinksForChart.push({
source: $scope.graphState.arrayOfNodesForChart[link.source.index],
target: $scope.graphState.arrayOfNodesForChart[chartNodeIdToIndexMapping[workflowMakerNodeIdCounter]],
source: {id: link.source.id},
target: {id: workflowMakerNodeIdCounter},
edgeType: "placeholder"
});
@ -426,9 +419,9 @@ export default ['$scope', 'TemplatesService',
// Search for the link that used to exist between source and target and shift it to
// go from our new node to the target
$scope.graphState.arrayOfLinksForChart.forEach((foo) => {
if (foo.source.id === link.source.id && foo.target.id === link.target.id) {
foo.source = $scope.graphState.arrayOfNodesForChart[chartNodeIdToIndexMapping[workflowMakerNodeIdCounter]];
$scope.graphState.arrayOfLinksForChart.forEach((linkToCompare) => {
if (linkToCompare.source.id === link.source.id && linkToCompare.target.id === link.target.id) {
linkToCompare.source = {id: workflowMakerNodeIdCounter};
}
});
@ -440,7 +433,7 @@ export default ['$scope', 'TemplatesService',
};
$scope.confirmNodeForm = function(selectedTemplate, promptData, edgeType) {
const nodeIndex = chartNodeIdToIndexMapping[$scope.nodeConfig.nodeId];
const nodeId = $scope.nodeConfig.nodeId;
if ($scope.nodeConfig.mode === "add") {
if (selectedTemplate && edgeType && edgeType.value) {
nodeRef[$scope.nodeConfig.nodeId] = {
@ -449,11 +442,10 @@ export default ['$scope', 'TemplatesService',
isNew: true
};
$scope.graphState.arrayOfNodesForChart[nodeIndex].unifiedJobTemplate = selectedTemplate;
$scope.graphState.nodeBeingAdded = null;
$scope.graphState.arrayOfLinksForChart.map( (link) => {
if (link.target.index === nodeIndex) {
if (link.target.id === nodeId) {
link.edgeType = edgeType.value;
}
});
@ -463,11 +455,16 @@ export default ['$scope', 'TemplatesService',
nodeRef[$scope.nodeConfig.nodeId].fullUnifiedJobTemplateObject = selectedTemplate;
nodeRef[$scope.nodeConfig.nodeId].promptData = _.cloneDeep(promptData);
nodeRef[$scope.nodeConfig.nodeId].isEdited = true;
$scope.graphState.arrayOfNodesForChart[nodeIndex].unifiedJobTemplate = selectedTemplate;
$scope.graphState.nodeBeingEdited = null;
}
}
$scope.graphState.arrayOfNodesForChart.map( (node) => {
if (node.id === nodeId) {
node.unifiedJobTemplate = selectedTemplate;
}
});
$scope.formState.showNodeForm = false;
$scope.nodeConfig = null;
@ -475,10 +472,15 @@ export default ['$scope', 'TemplatesService',
};
$scope.cancelNodeForm = function() {
const nodeIndex = chartNodeIdToIndexMapping[$scope.nodeConfig.nodeId];
const nodeId = $scope.nodeConfig.nodeId;
if ($scope.nodeConfig.mode === "add") {
// Remove the placeholder node from the array
$scope.graphState.arrayOfNodesForChart.splice(nodeIndex, 1);
for( let i = $scope.graphState.arrayOfNodesForChart.length; i--; ){
if ($scope.graphState.arrayOfNodesForChart[i].id === nodeId) {
$scope.graphState.arrayOfNodesForChart.splice(i, 1);
i = 0;
}
}
// Update the links
let parents = [];
@ -488,47 +490,30 @@ export default ['$scope', 'TemplatesService',
for( let i = $scope.graphState.arrayOfLinksForChart.length; i--; ){
const link = $scope.graphState.arrayOfLinksForChart[i];
if (link.source.index === nodeIndex || link.target.index === nodeIndex) {
if (link.source.index === nodeIndex) {
const targetIndex = link.target.index < nodeIndex ? link.target.index : link.target.index - 1;
children.push({index: targetIndex, edgeType: link.edgeType});
} else if (link.target.index === nodeIndex) {
const sourceIndex = link.source.index < nodeIndex ? link.source.index : link.source.index - 1;
parents.push(sourceIndex);
if (link.source.id === nodeId || link.target.id === nodeId) {
if (link.source.id === nodeId) {
children.push({id: link.target.id, edgeType: link.edgeType});
} else if (link.target.id === nodeId) {
parents.push(link.source.id);
}
$scope.graphState.arrayOfLinksForChart.splice(i, 1);
} else {
if (link.source.index > nodeIndex) {
link.source.index--;
}
if (link.target.index > nodeIndex) {
link.target.index--;
}
}
}
// Add the new links
parents.forEach((parentIndex) => {
parents.forEach((parentId) => {
children.forEach((child) => {
if (parentIndex === 0) {
if (parentId === 1) {
child.edgeType = "always";
}
$scope.graphState.arrayOfLinksForChart.push({
source: $scope.graphState.arrayOfNodesForChart[parentIndex],
target: $scope.graphState.arrayOfNodesForChart[child.index],
source: {id: parentId},
target: {id: child.id},
edgeType: child.edgeType
});
});
});
delete chartNodeIdToIndexMapping[$scope.nodeConfig.nodeId];
for (const key in chartNodeIdToIndexMapping) {
if (chartNodeIdToIndexMapping[key] > nodeIndex) {
chartNodeIdToIndexMapping[key]--;
}
}
} else if ($scope.nodeConfig.mode === "edit") {
$scope.graphState.nodeBeingEdited = null;
}
@ -544,7 +529,7 @@ export default ['$scope', 'TemplatesService',
$scope.cancelLinkForm();
}
if (!$scope.nodeConfig || ($scope.nodeConfig && $scope.nodeConfig.nodeId !== nodeToEdit.index)) {
if (!$scope.nodeConfig || ($scope.nodeConfig && $scope.nodeConfig.nodeId !== nodeToEdit.id)) {
if ($scope.nodeConfig) {
$scope.cancelNodeForm();
}
@ -583,11 +568,11 @@ export default ['$scope', 'TemplatesService',
$scope.linkConfig = {
mode: "edit",
parent: {
source: {
id: linkToEdit.source.id,
name: _.get(linkToEdit, 'source.unifiedJobTemplate.name') || ""
},
child: {
target: {
id: linkToEdit.target.id,
name: _.get(linkToEdit, 'target.unifiedJobTemplate.name') || ""
},
@ -604,7 +589,7 @@ export default ['$scope', 'TemplatesService',
}
if ($scope.linkConfig) {
if ($scope.linkConfig.parent.id !== linkToEdit.source.id || $scope.linkConfig.child.id !== linkToEdit.target.id) {
if ($scope.linkConfig.source.id !== linkToEdit.source.id || $scope.linkConfig.target.id !== linkToEdit.target.id) {
// User is going from editing one link to editing another
if ($scope.linkConfig.mode === "add") {
$scope.graphState.arrayOfLinksForChart.splice($scope.graphState.arrayOfLinksForChart.length-1, 1);
@ -621,27 +606,31 @@ export default ['$scope', 'TemplatesService',
if ($scope.nodeConfig) {
$scope.cancelNodeForm();
}
// User was add/editing a link and then hit the link icon
if ($scope.linkConfig && $scope.linkConfig.target) {
$scope.cancelLinkForm();
}
if ($scope.linkConfig) {
// This is the second node selected
$scope.linkConfig.child = {
$scope.linkConfig.target = {
id: node.id,
name: node.unifiedJobTemplate.name
};
$scope.linkConfig.edgeType = "success";
$scope.graphState.arrayOfNodesForChart.forEach((node) => {
node.isInvalidLinkTarget = false;
$scope.graphState.arrayOfNodesForChart.forEach((nodeToUpdate) => {
nodeToUpdate.isInvalidLinkTarget = false;
});
$scope.graphState.arrayOfLinksForChart.push({
target: $scope.graphState.arrayOfNodesForChart[node.index],
source: $scope.graphState.arrayOfNodesForChart[chartNodeIdToIndexMapping[$scope.linkConfig.parent.id]],
source: {id: $scope.linkConfig.source.id},
target: {id: node.id},
edgeType: "placeholder"
});
$scope.graphState.linkBeingEdited = {
source: $scope.graphState.arrayOfNodesForChart[node.index].id,
target: $scope.graphState.arrayOfNodesForChart[chartNodeIdToIndexMapping[$scope.linkConfig.parent.id]].id
source: {id: $scope.linkConfig.source.id},
target: {id: node.id}
};
$scope.graphState.arrayOfLinksForChart.forEach((link, index) => {
@ -656,7 +645,7 @@ export default ['$scope', 'TemplatesService',
$scope.graphState.addLinkSource = node.id;
$scope.linkConfig = {
mode: "add",
parent: {
source: {
id: node.id,
name: node.unifiedJobTemplate.name
}
@ -693,7 +682,11 @@ export default ['$scope', 'TemplatesService',
// Filter out the duplicates
invalidLinkTargetIds.filter((element, index, array) => index === array.indexOf(element)).forEach((ancestorId) => {
$scope.graphState.arrayOfNodesForChart[chartNodeIdToIndexMapping[ancestorId]].isInvalidLinkTarget = true;
$scope.graphState.arrayOfNodesForChart.forEach((node) => {
if (node.id === ancestorId) {
node.isInvalidLinkTarget = true;
}
});
});
$scope.graphState.isLinkMode = true;
@ -706,7 +699,7 @@ export default ['$scope', 'TemplatesService',
$scope.confirmLinkForm = (newEdgeType) => {
$scope.graphState.arrayOfLinksForChart.forEach((link) => {
if (link.source.id === $scope.linkConfig.parent.id && link.target.id === $scope.linkConfig.child.id) {
if (link.source.id === $scope.linkConfig.source.id && link.target.id === $scope.linkConfig.target.id) {
link.edgeType = newEdgeType;
}
});
@ -729,7 +722,7 @@ export default ['$scope', 'TemplatesService',
for( let i = $scope.graphState.arrayOfLinksForChart.length; i--; ){
const link = $scope.graphState.arrayOfLinksForChart[i];
if (link.source.id === $scope.linkConfig.parent.id && link.target.id === $scope.linkConfig.child.id) {
if (link.source.id === $scope.linkConfig.source.id && link.target.id === $scope.linkConfig.target.id) {
$scope.graphState.arrayOfLinksForChart.splice(i, 1);
}
}
@ -740,19 +733,19 @@ export default ['$scope', 'TemplatesService',
};
$scope.cancelLinkForm = () => {
if ($scope.linkConfig.mode === "add" && $scope.linkConfig.child) {
if ($scope.linkConfig.mode === "add" && $scope.linkConfig.target) {
$scope.graphState.arrayOfLinksForChart.splice($scope.graphState.arrayOfLinksForChart.length-1, 1);
let targetIsOrphaned = true;
$scope.graphState.arrayOfLinksForChart.forEach((link) => {
if (link.target.id === $scope.linkConfig.child.id) {
if (link.target.id === $scope.linkConfig.target.id) {
targetIsOrphaned = false;
}
});
if (targetIsOrphaned) {
// Link it to the start node
$scope.graphState.arrayOfLinksForChart.push({
source: $scope.graphState.arrayOfNodesForChart[0],
target: $scope.graphState.arrayOfNodesForChart[chartNodeIdToIndexMapping[$scope.linkConfig.child.id]],
source: {id: 1},
target: {id: $scope.linkConfig.target.id},
edgeType: "always"
});
}
@ -782,53 +775,69 @@ export default ['$scope', 'TemplatesService',
$scope.confirmDeleteNode = function () {
if ($scope.nodeToBeDeleted) {
const nodeIndex = $scope.nodeToBeDeleted.index;
const nodeId = $scope.nodeToBeDeleted.id;
if ($scope.linkBeingWorkedOn) {
if ($scope.linkConfig) {
$scope.cancelLinkForm();
}
// Remove the node from the array
$scope.graphState.arrayOfNodesForChart.splice(nodeIndex, 1);
for( let i = $scope.graphState.arrayOfNodesForChart.length; i--; ){
if ($scope.graphState.arrayOfNodesForChart[i].id === nodeId) {
$scope.graphState.arrayOfNodesForChart.splice(i, 1);
i = 0;
}
}
// Update the links
let parents = [];
let children = [];
let linkParentMapping = {};
// Remove any links that reference this node
for( let i = $scope.graphState.arrayOfLinksForChart.length; i--; ){
const link = $scope.graphState.arrayOfLinksForChart[i];
if (link.source.index === nodeIndex || link.target.index === nodeIndex) {
if (link.source.index === nodeIndex) {
const targetIndex = link.target.index < nodeIndex ? link.target.index : link.target.index - 1;
children.push({index: targetIndex, edgeType: link.edgeType});
} else if (link.target.index === nodeIndex) {
const sourceIndex = link.source.index < nodeIndex ? link.source.index : link.source.index - 1;
parents.push(sourceIndex);
if (!linkParentMapping[link.target.id]) {
linkParentMapping[link.target.id] = [];
}
linkParentMapping[link.target.id].push(link.source.id);
if (link.source.id === nodeId || link.target.id === nodeId) {
if (link.source.id === nodeId) {
children.push({id: link.target.id, edgeType: link.edgeType});
} else if (link.target.id === nodeId) {
parents.push(link.source.id);
}
$scope.graphState.arrayOfLinksForChart.splice(i, 1);
} else {
// if (link.source.index > nodeIndex) {
// link.source.index = link.source.index - 1;
// }
// if (link.target.index > nodeIndex) {
// link.target.index = link.target.index - 1;
// }
}
}
// Add the new links
parents.forEach((parentIndex) => {
parents.forEach((parentId) => {
children.forEach((child) => {
if (parentIndex === 0) {
child.edgeType = "always";
if (parentId === 1) {
// We only want to create a link from the start node to this node if it
// doesn't have any other parents
if(linkParentMapping[child.id].length === 1) {
$scope.graphState.arrayOfLinksForChart.push({
source: {id: parentId},
target: {id: child.id},
edgeType: "always"
});
}
} else {
// We don't want to add a link that already exists
if (!linkParentMapping[child.id].includes(parentId)) {
$scope.graphState.arrayOfLinksForChart.push({
source: {id: parentId},
target: {id: child.id},
edgeType: child.edgeType
});
}
}
$scope.graphState.arrayOfLinksForChart.push({
source: $scope.graphState.arrayOfNodesForChart[parentIndex],
target: $scope.graphState.arrayOfNodesForChart[child.index],
edgeType: child.edgeType
});
});
});
@ -838,12 +847,6 @@ export default ['$scope', 'TemplatesService',
delete nodeRef[$scope.nodeToBeDeleted.id];
for (const key in chartNodeIdToIndexMapping) {
if (chartNodeIdToIndexMapping[key] > $scope.nodeToBeDeleted.index) {
chartNodeIdToIndexMapping[key]--;
}
}
$scope.deleteOverlayVisible = false;
$scope.nodeToBeDeleted = null;
@ -902,7 +905,7 @@ export default ['$scope', 'TemplatesService',
let arrayOfLinksForChart = [];
let arrayOfNodesForChart = [];
({arrayOfNodesForChart, arrayOfLinksForChart, chartNodeIdToIndexMapping, nodeIdToChartNodeIdMapping, nodeRef, workflowMakerNodeIdCounter} = WorkflowChartService.generateArraysOfNodesAndLinks(allNodes));
({arrayOfNodesForChart, arrayOfLinksForChart, nodeIdToChartNodeIdMapping, nodeRef, workflowMakerNodeIdCounter} = WorkflowChartService.generateArraysOfNodesAndLinks(allNodes));
$scope.graphState = { arrayOfNodesForChart, arrayOfLinksForChart };