Addresses a number of workflow related bugs

This commit is contained in:
mabashian
2018-11-16 12:12:39 -05:00
parent 395800fff1
commit 7e8ab97152
6 changed files with 229 additions and 208 deletions

View File

@@ -186,16 +186,8 @@
margin: auto; margin: auto;
} }
.WorkflowChart-tooltipArrow--right { .WorkflowChart-tooltipArrow {
width: 0; fill: @default-interface-txt;
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-dashedNode { .WorkflowChart-dashedNode {

View File

@@ -240,6 +240,81 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge
function update() { function update() {
if(scope.dimensionsSet) { 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(); var g = new dagre.graphlib.Graph();
g.setGraph({rankdir: 'LR', nodesep: 30, ranksep: 120}); 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") baseSvg.selectAll(".WorkflowChart-circleBetweenNodes")
.attr("id", function(d){return "link-" + d.source.id + "-" + d.target.id + "-add";}) .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) { .attr("cx", function(d) {
return (nodePositionMap[d.source.id].x + nodePositionMap[d.source.id].width + nodePositionMap[d.target.id].x)/2; 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") 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) { .attr("transform", function(d) {
let translate; let translate;
@@ -411,48 +486,20 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge
return [pt1, pt2, pt3, pt4].join(" "); return [pt1, pt2, pt3, pt4].join(" ");
}) })
.on("mouseover", function(d) { .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`); $(`#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); .classed("WorkflowChart-linkHovering", true);
let xPos, yPos, arrowClass; buildLinkTooltip(d);
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>`;
});
} }
}) })
.on("mouseout", function(d){ .on("mouseout", function(d){
if(!d.source.isStartNode && d.target.id !== scope.graphState.nodeBeingAdded && scope.mode !== 'details') { 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) .attr("d", lineData)
.call(edit_link) .call(edit_link)
.on("mouseenter", function(d) { .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`); $(`#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); .classed("WorkflowChart-linkHovering", true);
let xPos, yPos, arrowClass; buildLinkTooltip(d);
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>`;
});
} }
}) })
.on("mouseleave", function(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("id", function(d){return "link-" + d.source.id + "-" + d.target.id + "-add";})
.attr("r", 10) .attr("r", 10)
.attr("class", "WorkflowChart-addCircle WorkflowChart-circleBetweenNodes") .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) { .attr("cx", function(d) {
return (nodePositionMap[d.source.id].x + nodePositionMap[d.source.id].width + nodePositionMap[d.target.id].x)/2; 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) .size(60)
.type("cross") .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) { .attr("transform", function(d) {
let translate; let translate;
@@ -1263,7 +1283,7 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge
function add_node_with_child() { function add_node_with_child() {
this.on("click", function(d) { this.on("click", function(d) {
if(!scope.readOnly && !scope.graphState.isLinkMode) { if(!scope.readOnly && !scope.graphState.isLinkMode && d.edgeType !== 'placeholder') {
scope.addNodeWithChild({ scope.addNodeWithChild({
link: d 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) { if(d.unifiedJobTemplate) {
goToJobResults(d.unifiedJobTemplate.unified_job_type); goToJobResults(d.unifiedJobTemplate.unified_job_type);
} }
else { 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 // 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 // so that we can route the user to the correct stdout view
Rest.setUrl(GetBasePath("workflow_jobs") + `${d.originalNodeObj.workflow_job}/workflow_nodes/?order_by=id`);
Rest.setUrl(GetBasePath("unified_jobs") + "?id=" + d.job.id);
Rest.get() Rest.get()
.then(function (res) { .then(function (res) {
if(res.data.results && res.data.results.length > 0) { if (res.data.results && res.data.results.length > 0) {
goToJobResults(res.data.results[0].type); 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 }); })
}); .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 { return {
arrayOfNodesForChart, arrayOfNodesForChart,
arrayOfLinksForChart, arrayOfLinksForChart,
chartNodeIdToIndexMapping,
nodeIdToChartNodeIdMapping, nodeIdToChartNodeIdMapping,
nodeRef, nodeRef,
workflowMakerNodeIdCounter: nodeIdCounter 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="WorkflowMaker-form">
<div class="form-group Form-formGroup Form-formGroup--singleColumn"> <div class="form-group Form-formGroup Form-formGroup--singleColumn">
<div ng-show="linkConfig.mode === 'add' && !linkConfig.child">{{:: strings.get('workflow_maker.NEW_LINK')}}</div> <div ng-show="linkConfig.mode === 'add' && !linkConfig.target">{{:: strings.get('workflow_maker.NEW_LINK')}}</div>
<span ng-show="linkConfig.child"> <span ng-show="linkConfig.target">
<label for="edgeType" class="Form-inputLabelContainer"> <label for="edgeType" class="Form-inputLabelContainer">
<span class="Form-requiredAsterisk">*</span> <span class="Form-requiredAsterisk">*</span>
<span class="Form-inputLabel">{{:: strings.get('workflow_maker.RUN') }}</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-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('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-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>
</div> </div>

View File

@@ -1,11 +1,11 @@
<div ng-show="nodeFormDataLoaded"> <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="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-if="!readOnly"> <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 === '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 === '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 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>
<div class="WorkflowMaker-formLists" ng-if="!readOnly"> <div class="WorkflowMaker-formLists" ng-show="!readOnly">
<div id="workflow-jobs-list" ng-show="activeTab === 'jobs'"> <div id="workflow-jobs-list" ng-show="activeTab === 'jobs'">
<div ng-hide="wf_maker_templates.length === 0 && (searchTags | isEmpty)"> <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"> <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> </select>
</div> </div>
</div> </div>
<div ng-if="readOnly"> <div ng-show="readOnly">
<div <div
class="WorkflowMaker-readOnlyPromptText" class="WorkflowMaker-readOnlyPromptText"
ng-show="nodeConfig.node.originalNodeObject.job_type !== null || ng-show="nodeConfig.node.originalNodeObject.job_type !== null ||

View File

@@ -20,7 +20,6 @@ export default ['$scope', 'TemplatesService',
let deletedNodeIds = []; let deletedNodeIds = [];
let workflowMakerNodeIdCounter = 1; let workflowMakerNodeIdCounter = 1;
let nodeIdToChartNodeIdMapping = {}; let nodeIdToChartNodeIdMapping = {};
let chartNodeIdToIndexMapping = {};
let nodeRef = {}; let nodeRef = {};
$scope.showKey = false; $scope.showKey = false;
@@ -364,25 +363,22 @@ export default ['$scope', 'TemplatesService',
} }
$scope.graphState.arrayOfNodesForChart.push({ $scope.graphState.arrayOfNodesForChart.push({
index: $scope.graphState.arrayOfNodesForChart.length,
id: workflowMakerNodeIdCounter, id: workflowMakerNodeIdCounter,
unifiedJobTemplate: null unifiedJobTemplate: null
}); });
$scope.graphState.nodeBeingAdded = workflowMakerNodeIdCounter; $scope.graphState.nodeBeingAdded = workflowMakerNodeIdCounter;
chartNodeIdToIndexMapping[workflowMakerNodeIdCounter] = $scope.graphState.arrayOfNodesForChart.length - 1;
$scope.graphState.arrayOfLinksForChart.push({ $scope.graphState.arrayOfLinksForChart.push({
source: $scope.graphState.arrayOfNodesForChart[parent.index], source: {id: parent.id},
target: $scope.graphState.arrayOfNodesForChart[chartNodeIdToIndexMapping[workflowMakerNodeIdCounter]], target: {id: workflowMakerNodeIdCounter},
edgeType: "placeholder" edgeType: "placeholder"
}); });
$scope.nodeConfig = { $scope.nodeConfig = {
mode: "add", mode: "add",
nodeId: workflowMakerNodeIdCounter, nodeId: workflowMakerNodeIdCounter,
newNodeIsRoot: parent.index === 0 newNodeIsRoot: parent.id === 1
}; };
workflowMakerNodeIdCounter++; workflowMakerNodeIdCounter++;
@@ -402,18 +398,15 @@ export default ['$scope', 'TemplatesService',
} }
$scope.graphState.arrayOfNodesForChart.push({ $scope.graphState.arrayOfNodesForChart.push({
index: $scope.graphState.arrayOfNodesForChart.length,
id: workflowMakerNodeIdCounter, id: workflowMakerNodeIdCounter,
unifiedJobTemplate: null unifiedJobTemplate: null
}); });
$scope.graphState.nodeBeingAdded = workflowMakerNodeIdCounter; $scope.graphState.nodeBeingAdded = workflowMakerNodeIdCounter;
chartNodeIdToIndexMapping[workflowMakerNodeIdCounter] = $scope.graphState.arrayOfNodesForChart.length - 1;
$scope.graphState.arrayOfLinksForChart.push({ $scope.graphState.arrayOfLinksForChart.push({
source: $scope.graphState.arrayOfNodesForChart[link.source.index], source: {id: link.source.id},
target: $scope.graphState.arrayOfNodesForChart[chartNodeIdToIndexMapping[workflowMakerNodeIdCounter]], target: {id: workflowMakerNodeIdCounter},
edgeType: "placeholder" edgeType: "placeholder"
}); });
@@ -425,9 +418,9 @@ export default ['$scope', 'TemplatesService',
// Search for the link that used to exist between source and target and shift it to // Search for the link that used to exist between source and target and shift it to
// go from our new node to the target // go from our new node to the target
$scope.graphState.arrayOfLinksForChart.forEach((foo) => { $scope.graphState.arrayOfLinksForChart.forEach((linkToCompare) => {
if (foo.source.id === link.source.id && foo.target.id === link.target.id) { if (linkToCompare.source.id === link.source.id && linkToCompare.target.id === link.target.id) {
foo.source = $scope.graphState.arrayOfNodesForChart[chartNodeIdToIndexMapping[workflowMakerNodeIdCounter]]; linkToCompare.source = {id: workflowMakerNodeIdCounter};
} }
}); });
@@ -439,7 +432,7 @@ export default ['$scope', 'TemplatesService',
}; };
$scope.confirmNodeForm = function(selectedTemplate, promptData, edgeType) { $scope.confirmNodeForm = function(selectedTemplate, promptData, edgeType) {
const nodeIndex = chartNodeIdToIndexMapping[$scope.nodeConfig.nodeId]; const nodeId = $scope.nodeConfig.nodeId;
if ($scope.nodeConfig.mode === "add") { if ($scope.nodeConfig.mode === "add") {
if (selectedTemplate && edgeType && edgeType.value) { if (selectedTemplate && edgeType && edgeType.value) {
nodeRef[$scope.nodeConfig.nodeId] = { nodeRef[$scope.nodeConfig.nodeId] = {
@@ -448,11 +441,10 @@ export default ['$scope', 'TemplatesService',
isNew: true isNew: true
}; };
$scope.graphState.arrayOfNodesForChart[nodeIndex].unifiedJobTemplate = selectedTemplate;
$scope.graphState.nodeBeingAdded = null; $scope.graphState.nodeBeingAdded = null;
$scope.graphState.arrayOfLinksForChart.map( (link) => { $scope.graphState.arrayOfLinksForChart.map( (link) => {
if (link.target.index === nodeIndex) { if (link.target.id === nodeId) {
link.edgeType = edgeType.value; link.edgeType = edgeType.value;
} }
}); });
@@ -462,11 +454,16 @@ export default ['$scope', 'TemplatesService',
nodeRef[$scope.nodeConfig.nodeId].fullUnifiedJobTemplateObject = selectedTemplate; nodeRef[$scope.nodeConfig.nodeId].fullUnifiedJobTemplateObject = selectedTemplate;
nodeRef[$scope.nodeConfig.nodeId].promptData = _.cloneDeep(promptData); nodeRef[$scope.nodeConfig.nodeId].promptData = _.cloneDeep(promptData);
nodeRef[$scope.nodeConfig.nodeId].isEdited = true; nodeRef[$scope.nodeConfig.nodeId].isEdited = true;
$scope.graphState.arrayOfNodesForChart[nodeIndex].unifiedJobTemplate = selectedTemplate;
$scope.graphState.nodeBeingEdited = null; $scope.graphState.nodeBeingEdited = null;
} }
} }
$scope.graphState.arrayOfNodesForChart.map( (node) => {
if (node.id === nodeId) {
node.unifiedJobTemplate = selectedTemplate;
}
});
$scope.formState.showNodeForm = false; $scope.formState.showNodeForm = false;
$scope.nodeConfig = null; $scope.nodeConfig = null;
@@ -474,10 +471,15 @@ export default ['$scope', 'TemplatesService',
}; };
$scope.cancelNodeForm = function() { $scope.cancelNodeForm = function() {
const nodeIndex = chartNodeIdToIndexMapping[$scope.nodeConfig.nodeId]; const nodeId = $scope.nodeConfig.nodeId;
if ($scope.nodeConfig.mode === "add") { if ($scope.nodeConfig.mode === "add") {
// Remove the placeholder node from the array // 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 // Update the links
let parents = []; let parents = [];
@@ -487,47 +489,30 @@ export default ['$scope', 'TemplatesService',
for( let i = $scope.graphState.arrayOfLinksForChart.length; i--; ){ for( let i = $scope.graphState.arrayOfLinksForChart.length; i--; ){
const link = $scope.graphState.arrayOfLinksForChart[i]; const link = $scope.graphState.arrayOfLinksForChart[i];
if (link.source.index === nodeIndex || link.target.index === nodeIndex) { if (link.source.id === nodeId || link.target.id === nodeId) {
if (link.source.index === nodeIndex) { if (link.source.id === nodeId) {
const targetIndex = link.target.index < nodeIndex ? link.target.index : link.target.index - 1; children.push({id: link.target.id, edgeType: link.edgeType});
children.push({index: targetIndex, edgeType: link.edgeType}); } else if (link.target.id === nodeId) {
} else if (link.target.index === nodeIndex) { parents.push(link.source.id);
const sourceIndex = link.source.index < nodeIndex ? link.source.index : link.source.index - 1;
parents.push(sourceIndex);
} }
$scope.graphState.arrayOfLinksForChart.splice(i, 1); $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 // Add the new links
parents.forEach((parentIndex) => { parents.forEach((parentId) => {
children.forEach((child) => { children.forEach((child) => {
if (parentIndex === 0) { if (parentId === 1) {
child.edgeType = "always"; child.edgeType = "always";
} }
$scope.graphState.arrayOfLinksForChart.push({ $scope.graphState.arrayOfLinksForChart.push({
source: $scope.graphState.arrayOfNodesForChart[parentIndex], source: {id: parentId},
target: $scope.graphState.arrayOfNodesForChart[child.index], target: {id: child.id},
edgeType: child.edgeType 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") { } else if ($scope.nodeConfig.mode === "edit") {
$scope.graphState.nodeBeingEdited = null; $scope.graphState.nodeBeingEdited = null;
} }
@@ -543,7 +528,7 @@ export default ['$scope', 'TemplatesService',
$scope.cancelLinkForm(); $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) { if ($scope.nodeConfig) {
$scope.cancelNodeForm(); $scope.cancelNodeForm();
} }
@@ -582,11 +567,11 @@ export default ['$scope', 'TemplatesService',
$scope.linkConfig = { $scope.linkConfig = {
mode: "edit", mode: "edit",
parent: { source: {
id: linkToEdit.source.id, id: linkToEdit.source.id,
name: _.get(linkToEdit, 'source.unifiedJobTemplate.name') || "" name: _.get(linkToEdit, 'source.unifiedJobTemplate.name') || ""
}, },
child: { target: {
id: linkToEdit.target.id, id: linkToEdit.target.id,
name: _.get(linkToEdit, 'target.unifiedJobTemplate.name') || "" name: _.get(linkToEdit, 'target.unifiedJobTemplate.name') || ""
}, },
@@ -603,7 +588,7 @@ export default ['$scope', 'TemplatesService',
} }
if ($scope.linkConfig) { 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 // User is going from editing one link to editing another
if ($scope.linkConfig.mode === "add") { if ($scope.linkConfig.mode === "add") {
$scope.graphState.arrayOfLinksForChart.splice($scope.graphState.arrayOfLinksForChart.length-1, 1); $scope.graphState.arrayOfLinksForChart.splice($scope.graphState.arrayOfLinksForChart.length-1, 1);
@@ -620,27 +605,31 @@ export default ['$scope', 'TemplatesService',
if ($scope.nodeConfig) { if ($scope.nodeConfig) {
$scope.cancelNodeForm(); $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) { if ($scope.linkConfig) {
// This is the second node selected // This is the second node selected
$scope.linkConfig.child = { $scope.linkConfig.target = {
id: node.id, id: node.id,
name: node.unifiedJobTemplate.name name: node.unifiedJobTemplate.name
}; };
$scope.linkConfig.edgeType = "success"; $scope.linkConfig.edgeType = "success";
$scope.graphState.arrayOfNodesForChart.forEach((node) => { $scope.graphState.arrayOfNodesForChart.forEach((nodeToUpdate) => {
node.isInvalidLinkTarget = false; nodeToUpdate.isInvalidLinkTarget = false;
}); });
$scope.graphState.arrayOfLinksForChart.push({ $scope.graphState.arrayOfLinksForChart.push({
target: $scope.graphState.arrayOfNodesForChart[node.index], source: {id: $scope.linkConfig.source.id},
source: $scope.graphState.arrayOfNodesForChart[chartNodeIdToIndexMapping[$scope.linkConfig.parent.id]], target: {id: node.id},
edgeType: "placeholder" edgeType: "placeholder"
}); });
$scope.graphState.linkBeingEdited = { $scope.graphState.linkBeingEdited = {
source: $scope.graphState.arrayOfNodesForChart[node.index].id, source: {id: $scope.linkConfig.source.id},
target: $scope.graphState.arrayOfNodesForChart[chartNodeIdToIndexMapping[$scope.linkConfig.parent.id]].id target: {id: node.id}
}; };
$scope.graphState.arrayOfLinksForChart.forEach((link, index) => { $scope.graphState.arrayOfLinksForChart.forEach((link, index) => {
@@ -655,7 +644,7 @@ export default ['$scope', 'TemplatesService',
$scope.graphState.addLinkSource = node.id; $scope.graphState.addLinkSource = node.id;
$scope.linkConfig = { $scope.linkConfig = {
mode: "add", mode: "add",
parent: { source: {
id: node.id, id: node.id,
name: node.unifiedJobTemplate.name name: node.unifiedJobTemplate.name
} }
@@ -692,7 +681,11 @@ export default ['$scope', 'TemplatesService',
// Filter out the duplicates // Filter out the duplicates
invalidLinkTargetIds.filter((element, index, array) => index === array.indexOf(element)).forEach((ancestorId) => { 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; $scope.graphState.isLinkMode = true;
@@ -705,7 +698,7 @@ export default ['$scope', 'TemplatesService',
$scope.confirmLinkForm = (newEdgeType) => { $scope.confirmLinkForm = (newEdgeType) => {
$scope.graphState.arrayOfLinksForChart.forEach((link) => { $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; link.edgeType = newEdgeType;
} }
}); });
@@ -728,7 +721,7 @@ export default ['$scope', 'TemplatesService',
for( let i = $scope.graphState.arrayOfLinksForChart.length; i--; ){ for( let i = $scope.graphState.arrayOfLinksForChart.length; i--; ){
const link = $scope.graphState.arrayOfLinksForChart[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); $scope.graphState.arrayOfLinksForChart.splice(i, 1);
} }
} }
@@ -739,19 +732,19 @@ export default ['$scope', 'TemplatesService',
}; };
$scope.cancelLinkForm = () => { $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); $scope.graphState.arrayOfLinksForChart.splice($scope.graphState.arrayOfLinksForChart.length-1, 1);
let targetIsOrphaned = true; let targetIsOrphaned = true;
$scope.graphState.arrayOfLinksForChart.forEach((link) => { $scope.graphState.arrayOfLinksForChart.forEach((link) => {
if (link.target.id === $scope.linkConfig.child.id) { if (link.target.id === $scope.linkConfig.target.id) {
targetIsOrphaned = false; targetIsOrphaned = false;
} }
}); });
if (targetIsOrphaned) { if (targetIsOrphaned) {
// Link it to the start node // Link it to the start node
$scope.graphState.arrayOfLinksForChart.push({ $scope.graphState.arrayOfLinksForChart.push({
source: $scope.graphState.arrayOfNodesForChart[0], source: {id: 1},
target: $scope.graphState.arrayOfNodesForChart[chartNodeIdToIndexMapping[$scope.linkConfig.child.id]], target: {id: $scope.linkConfig.target.id},
edgeType: "always" edgeType: "always"
}); });
} }
@@ -781,53 +774,69 @@ export default ['$scope', 'TemplatesService',
$scope.confirmDeleteNode = function () { $scope.confirmDeleteNode = function () {
if ($scope.nodeToBeDeleted) { if ($scope.nodeToBeDeleted) {
const nodeIndex = $scope.nodeToBeDeleted.index; const nodeId = $scope.nodeToBeDeleted.id;
if ($scope.linkBeingWorkedOn) { if ($scope.linkConfig) {
$scope.cancelLinkForm(); $scope.cancelLinkForm();
} }
// Remove the node from the array // 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 // Update the links
let parents = []; let parents = [];
let children = []; let children = [];
let linkParentMapping = {};
// Remove any links that reference this node // Remove any links that reference this node
for( let i = $scope.graphState.arrayOfLinksForChart.length; i--; ){ for( let i = $scope.graphState.arrayOfLinksForChart.length; i--; ){
const link = $scope.graphState.arrayOfLinksForChart[i]; const link = $scope.graphState.arrayOfLinksForChart[i];
if (link.source.index === nodeIndex || link.target.index === nodeIndex) { if (!linkParentMapping[link.target.id]) {
if (link.source.index === nodeIndex) { linkParentMapping[link.target.id] = [];
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) { linkParentMapping[link.target.id].push(link.source.id);
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); $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 // Add the new links
parents.forEach((parentIndex) => { parents.forEach((parentId) => {
children.forEach((child) => { children.forEach((child) => {
if (parentIndex === 0) { if (parentId === 1) {
child.edgeType = "always"; // 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
});
}); });
}); });
@@ -837,12 +846,6 @@ export default ['$scope', 'TemplatesService',
delete nodeRef[$scope.nodeToBeDeleted.id]; delete nodeRef[$scope.nodeToBeDeleted.id];
for (const key in chartNodeIdToIndexMapping) {
if (chartNodeIdToIndexMapping[key] > $scope.nodeToBeDeleted.index) {
chartNodeIdToIndexMapping[key]--;
}
}
$scope.deleteOverlayVisible = false; $scope.deleteOverlayVisible = false;
$scope.nodeToBeDeleted = null; $scope.nodeToBeDeleted = null;
@@ -901,7 +904,7 @@ export default ['$scope', 'TemplatesService',
let arrayOfLinksForChart = []; let arrayOfLinksForChart = [];
let arrayOfNodesForChart = []; let arrayOfNodesForChart = [];
({arrayOfNodesForChart, arrayOfLinksForChart, chartNodeIdToIndexMapping, nodeIdToChartNodeIdMapping, nodeRef, workflowMakerNodeIdCounter} = WorkflowChartService.generateArraysOfNodesAndLinks(allNodes)); ({arrayOfNodesForChart, arrayOfLinksForChart, nodeIdToChartNodeIdMapping, nodeRef, workflowMakerNodeIdCounter} = WorkflowChartService.generateArraysOfNodesAndLinks(allNodes));
$scope.graphState = { arrayOfNodesForChart, arrayOfLinksForChart }; $scope.graphState = { arrayOfNodesForChart, arrayOfLinksForChart };