Decouple editing a wf node with editing a node link

This commit is contained in:
mabashian
2018-10-04 20:29:10 -06:00
parent 1e10d4323f
commit 87d6253176
14 changed files with 1504 additions and 1194 deletions

View File

@@ -122,6 +122,7 @@ function TemplatesStrings (BaseString) {
INVENTORY_WILL_NOT_OVERRIDE: t.s('The inventory of this node will not be overridden by the parent workflow inventory.'), INVENTORY_WILL_NOT_OVERRIDE: t.s('The inventory of this node will not be overridden by the parent workflow inventory.'),
INVENTORY_PROMPT_WILL_OVERRIDE: t.s('The inventory of this node will be overridden if a parent workflow inventory is provided at launch.'), INVENTORY_PROMPT_WILL_OVERRIDE: t.s('The inventory of this node will be overridden if a parent workflow inventory is provided at launch.'),
INVENTORY_PROMPT_WILL_NOT_OVERRIDE: t.s('The inventory of this node will not be overridden if a parent workflow inventory is provided at launch.'), INVENTORY_PROMPT_WILL_NOT_OVERRIDE: t.s('The inventory of this node will not be overridden if a parent workflow inventory is provided at launch.'),
EDIT_LINK: ({ parentName, childName }) => t.s('EDIT LINK | {{parentName}} to {{childName}}', { parentName, childName })
} }
} }

View File

@@ -1,4 +1,9 @@
.link circle, .link .linkCross, .node .addCircle, .node .removeCircle, .node .WorkflowChart-hoverPath { .link circle,
.link polygon,
.link .linkCross,
.node circle,
.node .linkIcon,
.node .WorkflowChart-hoverPath {
opacity: 0; opacity: 0;
} }
@@ -18,6 +23,18 @@
fill: @default-err-hov; fill: @default-err-hov;
} }
.node .linkCircle {
fill: @default-link;
}
.node .linkIcon {
color: @default-bg;
}
.linkCircle.removeHovering {
fill: @default-link-hov;
}
.node { .node {
font-size: 12px; font-size: 12px;
font-family: 'Open Sans', sans-serif, 'FontAwesome'; font-family: 'Open Sans', sans-serif, 'FontAwesome';
@@ -50,8 +67,12 @@
.WorkflowChart-alwaysShowAdd .linkCross, .WorkflowChart-alwaysShowAdd .linkCross,
.hovering .addCircle, .hovering .addCircle,
.hovering .removeCircle, .hovering .removeCircle,
.addHovering .betweenNodesCircle,
.hovering .linkCircle,
.hovering .linkIcon,
.hovering .WorkflowChart-hoverPath, .hovering .WorkflowChart-hoverPath,
.hovering .linkCross { .addHovering .linkCross {
cursor: pointer;
opacity: 1; opacity: 1;
} }
@@ -136,3 +157,17 @@
.WorkflowChart-dashedNode { .WorkflowChart-dashedNode {
stroke-dasharray: 5,5; stroke-dasharray: 5,5;
} }
.linkOverlay {
fill: @default-interface-txt;
}
.linkActiveEdit.linkOverlay,
.overlayHovering .linkOverlay {
cursor: pointer;
opacity: 0.4;
}
.overlayHovering .linkPath {
cursor: pointer;
}

View File

@@ -15,6 +15,7 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
addNode: '&', addNode: '&',
editNode: '&', editNode: '&',
deleteNode: '&', deleteNode: '&',
editLink: '&',
workflowZoomed: '&', workflowZoomed: '&',
mode: '@' mode: '@'
}, },
@@ -63,12 +64,8 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
}); });
line = d3.svg.line() line = d3.svg.line()
.x(function (d) { .x(function(d){return d.x;})
return d.x; .y(function(d){return d.y;});
})
.y(function (d) {
return d.y;
});
zoomObj = d3.behavior.zoom().scaleExtent([0.5, 2]); zoomObj = d3.behavior.zoom().scaleExtent([0.5, 2]);
@@ -90,7 +87,8 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
// This is the workflow editor // This is the workflow editor
dimensions.height = $('.WorkflowMaker-contentLeft').outerHeight() - $('.WorkflowLegend-maker').outerHeight(); dimensions.height = $('.WorkflowMaker-contentLeft').outerHeight() - $('.WorkflowLegend-maker').outerHeight();
dimensions.width = $('#workflow-modal-dialog').width() - $('.WorkflowMaker-contentRight').outerWidth(); dimensions.width = $('#workflow-modal-dialog').width() - $('.WorkflowMaker-contentRight').outerWidth();
} else { }
else {
// This is the workflow details view // This is the workflow details view
let panel = $('.WorkflowResults-rightSide').children('.Panel')[0]; let panel = $('.WorkflowResults-rightSide').children('.Panel')[0];
let panelWidth = $(panel).width(); let panelWidth = $(panel).width();
@@ -113,7 +111,8 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
let targetX = d.target.y; let targetX = d.target.y;
let targetY = d.target.x + nodeH / 2; let targetY = d.target.x + nodeH / 2;
let points = [{ let points = [
{
x: sourceX, x: sourceX,
y: sourceY y: sourceY
}, },
@@ -131,7 +130,8 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
function wrap(text) { function wrap(text) {
if(text && text.length > maxNodeTextLength) { if(text && text.length > maxNodeTextLength) {
return text.substring(0,maxNodeTextLength) + '...'; return text.substring(0,maxNodeTextLength) + '...';
} else { }
else {
return text; return text;
} }
} }
@@ -140,33 +140,17 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
var retval; var retval;
retval = "M" + (x + r) + "," + y; retval = "M" + (x + r) + "," + y;
retval += "h" + (w - 2*r); retval += "h" + (w - 2*r);
if (tr) { if (tr) { retval += "a" + r + "," + r + " 0 0 1 " + r + "," + r; }
retval += "a" + r + "," + r + " 0 0 1 " + r + "," + r; else { retval += "h" + r; retval += "v" + r; }
} else {
retval += "h" + r;
retval += "v" + r;
}
retval += "v" + (h - 2*r); retval += "v" + (h - 2*r);
if (br) { if (br) { retval += "a" + r + "," + r + " 0 0 1 " + -r + "," + r; }
retval += "a" + r + "," + r + " 0 0 1 " + -r + "," + r; else { retval += "v" + r; retval += "h" + -r; }
} else {
retval += "v" + r;
retval += "h" + -r;
}
retval += "h" + (2*r - w); retval += "h" + (2*r - w);
if (bl) { if (bl) { retval += "a" + r + "," + r + " 0 0 1 " + -r + "," + -r; }
retval += "a" + r + "," + r + " 0 0 1 " + -r + "," + -r; else { retval += "h" + -r; retval += "v" + -r; }
} else {
retval += "h" + -r;
retval += "v" + -r;
}
retval += "v" + (2*r - h); retval += "v" + (2*r - h);
if (tl) { if (tl) { retval += "a" + r + "," + r + " 0 0 1 " + r + "," + -r; }
retval += "a" + r + "," + r + " 0 0 1 " + r + "," + -r; else { retval += "v" + -r; retval += "h" + r; }
} else {
retval += "v" + -r;
retval += "h" + r;
}
retval += "z"; retval += "z";
return retval; return retval;
} }
@@ -254,6 +238,7 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
// Declare the nodes // Declare the nodes
let nodes = tree.nodes(scope.treeData), let nodes = tree.nodes(scope.treeData),
links = tree.links(nodes); links = tree.links(nodes);
let node = svgGroup.selectAll("g.node") let node = svgGroup.selectAll("g.node")
.data(nodes, function(d) { .data(nodes, function(d) {
d.y = d.depth * 240; d.y = d.depth * 240;
@@ -262,15 +247,9 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
let nodeEnter = node.enter().append("g") let nodeEnter = node.enter().append("g")
.attr("class", "node") .attr("class", "node")
.attr("id", function (d) { .attr("id", function(d){return "node-" + d.id;})
return "node-" + d.id; .attr("parent", function(d){return d.parent ? d.parent.id : null;})
}) .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
.attr("parent", function (d) {
return d.parent ? d.parent.id : null;
})
.attr("transform", function (d) {
return "translate(" + d.y + "," + d.x + ")";
});
nodeEnter.each(function(d) { nodeEnter.each(function(d) {
let thisNode = d3.select(this); let thisNode = d3.select(this);
@@ -286,7 +265,8 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
.attr("ry", 5) .attr("ry", 5)
.attr("fill", "#337ab7") .attr("fill", "#337ab7")
.attr("class", "WorkflowChart-rootNode"); .attr("class", "WorkflowChart-rootNode");
} else if (d.isStartNode && scope.mode !== 'details') { }
else if(d.isStartNode && scope.mode !== 'details') {
thisNode.append("rect") thisNode.append("rect")
.attr("width", rootW) .attr("width", rootW)
.attr("height", rootH) .attr("height", rootH)
@@ -301,11 +281,10 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
.attr("y", 30) .attr("y", 30)
.attr("dy", ".35em") .attr("dy", ".35em")
.attr("class", "WorkflowChart-startText") .attr("class", "WorkflowChart-startText")
.text(function () { .text(function () { return TemplatesStrings.get('workflow_maker.START'); })
return TemplatesStrings.get('workflow_maker.START');
})
.call(add_node); .call(add_node);
} else { }
else {
thisNode.append("rect") thisNode.append("rect")
.attr("width", nodeW) .attr("width", nodeW)
.attr("height", nodeH) .attr("height", nodeH)
@@ -315,12 +294,15 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
if(d.job && d.job.status) { if(d.job && d.job.status) {
if(d.job.status === "successful"){ if(d.job.status === "successful"){
return "#5cb85c"; return "#5cb85c";
} else if (d.job.status === "failed" || d.job.status === "error" || d.job.status === "cancelled") { }
else if (d.job.status === "failed" || d.job.status === "error" || d.job.status === "cancelled") {
return "#d9534f"; return "#d9534f";
} else { }
else {
return "#D7D7D7"; return "#D7D7D7";
} }
} else { }
else {
return "#D7D7D7"; return "#D7D7D7";
} }
}) })
@@ -334,38 +316,18 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
thisNode.append("path") thisNode.append("path")
.attr("d", rounded_rect(1, 0, 5, nodeH, 5, 1, 0, 1, 0)) .attr("d", rounded_rect(1, 0, 5, nodeH, 5, 1, 0, 1, 0))
.attr("class", "WorkflowChart-activeNode") .attr("class", "WorkflowChart-activeNode")
.style("display", function (d) { .style("display", function(d) { return d.isActiveEdit ? null : "none"; });
return d.isActiveEdit ? null : "none";
});
thisNode.append("text") thisNode.append("text")
.attr("x", function (d) { .attr("x", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? 20 : nodeW / 2; })
return (scope.mode === 'details' && d.job && d.job.status) ? 20 : nodeW / 2; .attr("y", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? 10 : nodeH / 2; })
})
.attr("y", function (d) {
return (scope.mode === 'details' && d.job && d.job.status) ? 10 : nodeH / 2;
})
.attr("dy", ".35em") .attr("dy", ".35em")
.attr("text-anchor", function (d) { .attr("text-anchor", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? "inherit" : "middle"; })
return (scope.mode === 'details' && d.job && d.job.status) ? "inherit" : "middle";
})
.attr("class", "WorkflowChart-defaultText WorkflowChart-nameText") .attr("class", "WorkflowChart-defaultText WorkflowChart-nameText")
.text(function (d) { .text(function (d) {
return (d.unifiedJobTemplate && d.unifiedJobTemplate.name) ? d.unifiedJobTemplate.name : ""; return (d.unifiedJobTemplate && d.unifiedJobTemplate.name) ? d.unifiedJobTemplate.name : "";
}).each(wrap); }).each(wrap);
thisNode.append("foreignObject")
.attr("x", 54)
.attr("y", 45)
.style("font-size", "0.7em")
.attr("class", "WorkflowChart-conflictText")
.html(function () {
return `<span class=\"WorkflowChart-conflictIcon\">\uf06a</span><span> ${TemplatesStrings.get('workflow_maker.EDGE_CONFLICT')}</span>`;
})
.style("display", function (d) {
return (d.edgeConflict && !d.placeholder) ? null : "none";
});
thisNode.append("foreignObject") thisNode.append("foreignObject")
.attr("x", 62) .attr("x", 62)
.attr("y", 22) .attr("y", 22)
@@ -375,9 +337,7 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
.html(function () { .html(function () {
return `<span>${TemplatesStrings.get('workflow_maker.DELETED')}</span>`; return `<span>${TemplatesStrings.get('workflow_maker.DELETED')}</span>`;
}) })
.style("display", function (d) { .style("display", function(d) { return d.unifiedJobTemplate || d.placeholder ? "none" : null; });
return d.unifiedJobTemplate || d.placeholder ? "none" : null;
});
thisNode.append("circle") thisNode.append("circle")
.attr("cy", nodeH) .attr("cy", nodeH)
@@ -391,7 +351,6 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
d.unifiedJobTemplate.type === "workflow_job_template" || d.unifiedJobTemplate.type === "workflow_job_template" ||
d.unifiedJobTemplate.unified_job_type === "workflow_job") ? null : "none"; d.unifiedJobTemplate.unified_job_type === "workflow_job") ? null : "none";
}); });
thisNode.append("text") thisNode.append("text")
.attr("y", nodeH) .attr("y", nodeH)
.attr("dy", ".35em") .attr("dy", ".35em")
@@ -493,23 +452,17 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
.attr("y", nodeH - 10) .attr("y", nodeH - 10)
.attr("dy", ".35em") .attr("dy", ".35em")
.attr("class", "WorkflowChart-detailsLink") .attr("class", "WorkflowChart-detailsLink")
.style("display", function (d) { .style("display", function(d){ return d.job && d.job.status && d.job.id ? null : "none"; })
return d.job && d.job.status && d.job.id ? null : "none";
})
.text(function () { .text(function () {
return TemplatesStrings.get('workflow_maker.DETAILS'); return TemplatesStrings.get('workflow_maker.DETAILS');
}) })
.call(details); .call(details);
thisNode.append("circle") thisNode.append("circle")
.attr("id", function (d) { .attr("id", function(d){return "node-" + d.id + "-add";})
return "node-" + d.id + "-add";
})
.attr("cx", nodeW) .attr("cx", nodeW)
.attr("r", 10) .attr("r", 10)
.attr("class", "addCircle nodeCircle") .attr("class", "addCircle nodeCircle")
.style("display", function (d) { .style("display", function(d) { return d.placeholder || !(userCanAddEdit) ? "none" : null; })
return d.placeholder || !(userCanAddEdit) ? "none" : null;
})
.call(add_node) .call(add_node)
.on("mouseover", function(d) { .on("mouseover", function(d) {
d3.select("#node-" + d.id) d3.select("#node-" + d.id)
@@ -526,16 +479,12 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
thisNode.append("path") thisNode.append("path")
.attr("class", "nodeAddCross WorkflowChart-hoverPath") .attr("class", "nodeAddCross WorkflowChart-hoverPath")
.style("fill", "white") .style("fill", "white")
.attr("transform", function () { .attr("transform", function() { return "translate(" + nodeW + "," + 0 + ")"; })
return "translate(" + nodeW + "," + 0 + ")";
})
.attr("d", d3.svg.symbol() .attr("d", d3.svg.symbol()
.size(60) .size(60)
.type("cross") .type("cross")
) )
.style("display", function (d) { .style("display", function(d) { return d.placeholder || !(userCanAddEdit) ? "none" : null; })
return d.placeholder || !(userCanAddEdit) ? "none" : null;
})
.call(add_node) .call(add_node)
.on("mouseover", function(d) { .on("mouseover", function(d) {
d3.select("#node-" + d.id) d3.select("#node-" + d.id)
@@ -550,16 +499,12 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
.classed("addHovering", false); .classed("addHovering", false);
}); });
thisNode.append("circle") thisNode.append("circle")
.attr("id", function (d) { .attr("id", function(d){return "node-" + d.id + "-remove";})
return "node-" + d.id + "-remove";
})
.attr("cx", nodeW) .attr("cx", nodeW)
.attr("cy", nodeH) .attr("cy", nodeH)
.attr("r", 10) .attr("r", 10)
.attr("class", "removeCircle") .attr("class", "removeCircle")
.style("display", function (d) { .style("display", function(d) { return (d.canDelete === false || d.placeholder || !(userCanAddEdit)) ? "none" : null; })
return (d.canDelete === false || d.placeholder || !(userCanAddEdit)) ? "none" : null;
})
.call(remove_node) .call(remove_node)
.on("mouseover", function(d) { .on("mouseover", function(d) {
d3.select("#node-" + d.id) d3.select("#node-" + d.id)
@@ -576,16 +521,12 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
thisNode.append("path") thisNode.append("path")
.attr("class", "nodeRemoveCross WorkflowChart-hoverPath") .attr("class", "nodeRemoveCross WorkflowChart-hoverPath")
.style("fill", "white") .style("fill", "white")
.attr("transform", function () { .attr("transform", function() { return "translate(" + nodeW + "," + nodeH + ") rotate(-45)"; })
return "translate(" + nodeW + "," + nodeH + ") rotate(-45)";
})
.attr("d", d3.svg.symbol() .attr("d", d3.svg.symbol()
.size(60) .size(60)
.type("cross") .type("cross")
) )
.style("display", function (d) { .style("display", function(d) { return (d.canDelete === false || d.placeholder || !(userCanAddEdit)) ? "none" : null; })
return (d.canDelete === false || d.placeholder || !(userCanAddEdit)) ? "none" : null;
})
.call(remove_node) .call(remove_node)
.on("mouseover", function(d) { .on("mouseover", function(d) {
d3.select("#node-" + d.id) d3.select("#node-" + d.id)
@@ -599,6 +540,50 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
d3.select("#node-" + d.id + "-remove") d3.select("#node-" + d.id + "-remove")
.classed("removeHovering", false); .classed("removeHovering", false);
}); });
// thisNode.append("circle")
// .attr("id", function(d){return "node-" + d.id + "-link";})
// .attr("cx", nodeW)
// .attr("cy", nodeH/2)
// .attr("r", 10)
// .attr("class", "linkCircle nodeCircle")
// .style("display", function(d) { return d.placeholder || !(userCanAddEdit) ? "none" : null; })
// .call(link_node)
// .on("mouseover", function(d) {
// d3.select("#node-" + d.id)
// .classed("hovering", true);
// d3.select("#node-" + d.id + "-link")
// .classed("addHovering", true);
// })
// .on("mouseout", function(d){
// d3.select("#node-" + d.id)
// .classed("hovering", false);
// d3.select("#node-" + d.id + "-link")
// .classed("addHovering", false);
// });
// // TODO: clean up the placement of this icon... this works but it's not
// // clean
// thisNode.append("foreignObject")
// .attr("x", nodeW - 6)
// .attr("y", nodeH/2 - 9)
// .style("font-size","14px")
// .html(function () {
// return `<span class="fa fa-link" />`;
// })
// .attr("class", "linkIcon")
// .style("display", function(d) { return d.placeholder || !(userCanAddEdit) ? "none" : null; })
// .call(link_node)
// .on("mouseover", function(d) {
// d3.select("#node-" + d.id)
// .classed("hovering", true);
// d3.select("#node-" + d.id + "-link")
// .classed("addHovering", true);
// })
// .on("mouseout", function(d){
// d3.select("#node-" + d.id)
// .classed("hovering", false);
// d3.select("#node-" + d.id + "-link")
// .classed("addHovering", false);
// });
thisNode.append("circle") thisNode.append("circle")
.attr("class", function(d) { .attr("class", function(d) {
@@ -633,9 +618,7 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
return statusClass; return statusClass;
}) })
.style("display", function (d) { .style("display", function(d) { return d.job && d.job.status ? null : "none"; })
return d.job && d.job.status ? null : "none";
})
.attr("cy", 10) .attr("cy", 10)
.attr("cx", 10) .attr("cx", 10)
.attr("r", 6); .attr("r", 6);
@@ -652,13 +635,12 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
let paddedElapsedMoment = Math.floor(elapsedMoment.asHours()) < 10 ? "0" + Math.floor(elapsedMoment.asHours()) : Math.floor(elapsedMoment.asHours()); let paddedElapsedMoment = Math.floor(elapsedMoment.asHours()) < 10 ? "0" + Math.floor(elapsedMoment.asHours()) : Math.floor(elapsedMoment.asHours());
let elapsedString = paddedElapsedMoment + moment.utc(elapsedMs).format(":mm:ss"); let elapsedString = paddedElapsedMoment + moment.utc(elapsedMs).format(":mm:ss");
return "<div class=\"WorkflowChart-elapsedHolder\"><span>" + elapsedString + "</span></div>"; return "<div class=\"WorkflowChart-elapsedHolder\"><span>" + elapsedString + "</span></div>";
} else { }
else {
return ""; return "";
} }
}) })
.style("display", function (d) { .style("display", function(d) { return (d.job && d.job.elapsed) ? null : "none"; });
return (d.job && d.job.elapsed) ? null : "none";
});
} }
}); });
@@ -677,34 +659,76 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
let linkEnter = link.enter().append("g") let linkEnter = link.enter().append("g")
.attr("class", "link") .attr("class", "link")
.attr("id", function (d) { .attr("id", function(d){return "link-" + d.source.id + "-" + d.target.id;});
return "link-" + d.source.id + "-" + d.target.id;
linkEnter.append("polygon", "g")
.attr("class", function(d) {
let linkClasses = ["linkOverlay"];
if (d.source.isLinkEditParent && d.target.isLinkEditChild) {
linkClasses.push("linkActiveEdit");
}
return linkClasses.join(' ');
})
.attr("id", function(d){return "link-" + d.source.id + "-" + d.target.id + "-overlay";})
.attr("points",function(d) {
const pt1 = [d.source.y + nodeW, d.source.x + 10 + nodeH/2].join(",");
const pt2 = [d.target.y,d.target.x + 10 + nodeH/2].join(",");
const pt3 = [d.target.y,d.target.x - 10 + nodeH/2].join(",");
const pt4 = [d.source.y + nodeW,d.source.x - 10 + nodeH/2].join(",");
return [pt1, pt2, pt3, pt4].join(" ");
})
.call(edit_link)
.on("mouseover", function(d) {
if(!d.source.isStartNode && !d.target.placeholder && scope.mode !== 'details') {
d3.select("#link-" + d.source.id + "-" + d.target.id)
.classed("overlayHovering", true);
}
})
.on("mouseout", function(d){
if(!d.source.isStartNode && !d.target.placeholder && scope.mode !== 'details') {
d3.select("#link-" + d.source.id + "-" + d.target.id)
.classed("overlayHovering", false);
}
}); });
// Add entering links in the parents old position. // Add entering links in the parents old position.
linkEnter.insert("path", "g") linkEnter.append("path", "g")
.attr("class", function(d) { .attr("class", function(d) {
return (d.source.placeholder || d.target.placeholder) ? "linkPath placeholder" : "linkPath"; return (d.source.placeholder || d.target.placeholder) ? "linkPath placeholder" : "linkPath";
}) })
.attr("d", lineData) .attr("d", lineData)
.call(edit_link)
.on("mouseover", function(d) {
if(!d.source.isStartNode && !d.target.placeholder && scope.mode !== 'details') {
d3.select("#link-" + d.source.id + "-" + d.target.id)
.classed("overlayHovering", true);
}
})
.on("mouseout", function(d){
if(!d.source.isStartNode && !d.target.placeholder && scope.mode !== 'details') {
d3.select("#link-" + d.source.id + "-" + d.target.id)
.classed("overlayHovering", false);
}
})
.attr('stroke', function(d) { .attr('stroke', function(d) {
if(d.target.edgeType) { if(d.target.edgeType) {
if(d.target.edgeType === "failure") { if(d.target.edgeType === "failure") {
return "#d9534f"; return "#d9534f";
} else if (d.target.edgeType === "success") { }
else if(d.target.edgeType === "success") {
return "#5cb85c"; return "#5cb85c";
} else if (d.target.edgeType === "always") { }
else if(d.target.edgeType === "always"){
return "#337ab7"; return "#337ab7";
} }
} else { }
else {
return "#D7D7D7"; return "#D7D7D7";
} }
}); });
linkEnter.append("circle") linkEnter.append("circle")
.attr("id", function (d) { .attr("id", function(d){return "link-" + d.source.id + "-" + d.target.id + "-add";})
return "link-" + d.source.id + "-" + d.target.id + "-add";
})
.attr("cx", function(d) { .attr("cx", function(d) {
return (d.source.isStartNode) ? (d.target.y + d.source.y + rootW) / 2 : (d.target.y + d.source.y + nodeW) / 2; return (d.source.isStartNode) ? (d.target.y + d.source.y + rootW) / 2 : (d.target.y + d.source.y + nodeW) / 2;
}) })
@@ -712,21 +736,15 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
return (d.source.isStartNode) ? ((d.target.x + startNodeOffsetY + rootH/2) + (d.source.x + nodeH/2)) / 2 : (d.target.x + d.source.x + nodeH) / 2; return (d.source.isStartNode) ? ((d.target.x + startNodeOffsetY + rootH/2) + (d.source.x + nodeH/2)) / 2 : (d.target.x + d.source.x + nodeH) / 2;
}) })
.attr("r", 10) .attr("r", 10)
.attr("class", "addCircle linkCircle") .attr("class", "addCircle betweenNodesCircle")
.style("display", function (d) { .style("display", function(d) { return (d.source.placeholder || d.target.placeholder || !(userCanAddEdit)) ? "none" : null; })
return (d.source.placeholder || d.target.placeholder || !(userCanAddEdit)) ? "none" : null;
})
.call(add_node_between) .call(add_node_between)
.on("mouseover", function(d) { .on("mouseover", function(d) {
d3.select("#link-" + d.source.id + "-" + d.target.id) d3.select("#link-" + d.source.id + "-" + d.target.id)
.classed("hovering", true);
d3.select("#link-" + d.source.id + "-" + d.target.id + "-add")
.classed("addHovering", true); .classed("addHovering", true);
}) })
.on("mouseout", function(d){ .on("mouseout", function(d){
d3.select("#link-" + d.source.id + "-" + d.target.id) d3.select("#link-" + d.source.id + "-" + d.target.id)
.classed("hovering", false);
d3.select("#link-" + d.source.id + "-" + d.target.id + "-add")
.classed("addHovering", false); .classed("addHovering", false);
}); });
@@ -737,7 +755,8 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
let translate; let translate;
if(d.source.isStartNode) { if(d.source.isStartNode) {
translate = "translate(" + (d.target.y + d.source.y + rootW) / 2 + "," + ((d.target.x + startNodeOffsetY + rootH/2) + (d.source.x + nodeH/2)) / 2 + ")"; translate = "translate(" + (d.target.y + d.source.y + rootW) / 2 + "," + ((d.target.x + startNodeOffsetY + rootH/2) + (d.source.x + nodeH/2)) / 2 + ")";
} else { }
else {
translate = "translate(" + (d.target.y + d.source.y + nodeW) / 2 + "," + (d.target.x + d.source.x + nodeH) / 2 + ")"; translate = "translate(" + (d.target.y + d.source.y + nodeW) / 2 + "," + (d.target.x + d.source.x + nodeH) / 2 + ")";
} }
return translate; return translate;
@@ -746,20 +765,14 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
.size(60) .size(60)
.type("cross") .type("cross")
) )
.style("display", function (d) { .style("display", function(d) { return (d.source.placeholder || d.target.placeholder || !(userCanAddEdit)) ? "none" : null; })
return (d.source.placeholder || d.target.placeholder || !(userCanAddEdit)) ? "none" : null;
})
.call(add_node_between) .call(add_node_between)
.on("mouseover", function(d) { .on("mouseover", function(d) {
d3.select("#link-" + d.source.id + "-" + d.target.id) d3.select("#link-" + d.source.id + "-" + d.target.id)
.classed("hovering", true);
d3.select("#link-" + d.source.id + "-" + d.target.id + "-add")
.classed("addHovering", true); .classed("addHovering", true);
}) })
.on("mouseout", function(d){ .on("mouseout", function(d){
d3.select("#link-" + d.source.id + "-" + d.target.id) d3.select("#link-" + d.source.id + "-" + d.target.id)
.classed("hovering", false);
d3.select("#link-" + d.source.id + "-" + d.target.id + "-add")
.classed("addHovering", false); .classed("addHovering", false);
}); });
@@ -769,24 +782,16 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
let t = baseSvg.transition(); let t = baseSvg.transition();
t.selectAll(".nodeCircle") t.selectAll(".nodeCircle")
.style("display", function (d) { .style("display", function(d) { return d.placeholder || !(userCanAddEdit) ? "none" : null; });
return d.placeholder || !(userCanAddEdit) ? "none" : null;
});
t.selectAll(".nodeAddCross") t.selectAll(".nodeAddCross")
.style("display", function (d) { .style("display", function(d) { return d.placeholder || !(userCanAddEdit) ? "none" : null; });
return d.placeholder || !(userCanAddEdit) ? "none" : null;
});
t.selectAll(".removeCircle") t.selectAll(".removeCircle")
.style("display", function (d) { .style("display", function(d) { return (d.canDelete === false || d.placeholder || !(userCanAddEdit)) ? "none" : null; });
return (d.canDelete === false || d.placeholder || !(userCanAddEdit)) ? "none" : null;
});
t.selectAll(".nodeRemoveCross") t.selectAll(".nodeRemoveCross")
.style("display", function (d) { .style("display", function(d) { return (d.canDelete === false || d.placeholder || !(userCanAddEdit)) ? "none" : null; });
return (d.canDelete === false || d.placeholder || !(userCanAddEdit)) ? "none" : null;
});
t.selectAll(".linkPath") t.selectAll(".linkPath")
.attr("class", function(d) { .attr("class", function(d) {
@@ -797,20 +802,20 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
if(d.target.edgeType) { if(d.target.edgeType) {
if(d.target.edgeType === "failure") { if(d.target.edgeType === "failure") {
return "#d9534f"; return "#d9534f";
} else if (d.target.edgeType === "success") { }
else if(d.target.edgeType === "success") {
return "#5cb85c"; return "#5cb85c";
} else if (d.target.edgeType === "always") { }
else if(d.target.edgeType === "always"){
return "#337ab7"; return "#337ab7";
} }
} else { }
else {
return "#D7D7D7"; return "#D7D7D7";
} }
}); });
t.selectAll(".linkCircle") t.selectAll(".betweenNodesCircle")
.style("display", function (d) {
return (d.source.placeholder || d.target.placeholder || !(userCanAddEdit)) ? "none" : null;
})
.attr("cx", function(d) { .attr("cx", function(d) {
return (d.source.isStartNode) ? (d.target.y + d.source.y + rootW) / 2 : (d.target.y + d.source.y + nodeW) / 2; return (d.source.isStartNode) ? (d.target.y + d.source.y + rootW) / 2 : (d.target.y + d.source.y + nodeW) / 2;
}) })
@@ -818,15 +823,30 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
return (d.source.isStartNode) ? ((d.target.x + startNodeOffsetY + rootH/2) + (d.source.x + nodeH/2)) / 2 : (d.target.x + d.source.x + nodeH) / 2; return (d.source.isStartNode) ? ((d.target.x + startNodeOffsetY + rootH/2) + (d.source.x + nodeH/2)) / 2 : (d.target.x + d.source.x + nodeH) / 2;
}); });
t.selectAll(".linkCross") t.selectAll(".linkOverlay")
.style("display", function (d) { .attr("class", function(d) {
return (d.source.placeholder || d.target.placeholder || !(userCanAddEdit)) ? "none" : null; let linkClasses = ["linkOverlay"];
if (d.source.isLinkEditParent && d.target.isLinkEditChild) {
linkClasses.push("linkActiveEdit");
}
return linkClasses.join(' ');
}) })
.attr("points",function(d) {
const pt1 = [d.source.y + nodeW, d.source.x + 10 + nodeH/2].join(",");
const pt2 = [d.target.y,d.target.x + 10 + nodeH/2].join(",");
const pt3 = [d.target.y,d.target.x - 10 + nodeH/2].join(",");
const pt4 = [d.source.y + nodeW,d.source.x - 10 + nodeH/2].join(",");
return [pt1, pt2, pt3, pt4].join(" ");
});
t.selectAll(".linkCross")
.style("display", function(d) { return (d.source.placeholder || d.target.placeholder || !(userCanAddEdit)) ? "none" : null; })
.attr("transform", function(d) { .attr("transform", function(d) {
let translate; let translate;
if(d.source.isStartNode) { if(d.source.isStartNode) {
translate = "translate(" + (d.target.y + d.source.y + rootW) / 2 + "," + ((d.target.x + startNodeOffsetY + rootH/2) + (d.source.x + nodeH/2)) / 2 + ")"; translate = "translate(" + (d.target.y + d.source.y + rootW) / 2 + "," + ((d.target.x + startNodeOffsetY + rootH/2) + (d.source.x + nodeH/2)) / 2 + ")";
} else { }
else {
translate = "translate(" + (d.target.y + d.source.y + nodeW) / 2 + "," + (d.target.x + d.source.x + nodeH) / 2 + ")"; translate = "translate(" + (d.target.y + d.source.y + nodeW) / 2 + "," + (d.target.x + d.source.x + nodeH) / 2 + ")";
} }
return translate; return translate;
@@ -837,12 +857,15 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
if(d.job && d.job.status) { if(d.job && d.job.status) {
if(d.job.status === "successful"){ if(d.job.status === "successful"){
return "#5cb85c"; return "#5cb85c";
} else if (d.job.status === "failed" || d.job.status === "error" || d.job.status === "cancelled") { }
else if (d.job.status === "failed" || d.job.status === "error" || d.job.status === "cancelled") {
return "#d9534f"; return "#d9534f";
} else { }
else {
return "#D7D7D7"; return "#D7D7D7";
} }
} else { }
else {
return "#D7D7D7"; return "#D7D7D7";
} }
}) })
@@ -853,14 +876,8 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
}); });
t.selectAll(".node") t.selectAll(".node")
.attr("parent", function (d) { .attr("parent", function(d){return d.parent ? d.parent.id : null;})
return d.parent ? d.parent.id : null; .attr("transform", function(d) {d.px = d.x; d.py = d.y; return "translate(" + d.y + "," + d.x + ")"; });
})
.attr("transform", function (d) {
d.px = d.x;
d.py = d.y;
return "translate(" + d.y + "," + d.x + ")";
});
t.selectAll(".WorkflowChart-nodeTypeCircle") t.selectAll(".WorkflowChart-nodeTypeCircle")
.style("display", function (d) { .style("display", function (d) {
@@ -945,9 +962,7 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
return statusClass; return statusClass;
}) })
.style("display", function (d) { .style("display", function(d) { return d.job && d.job.status ? null : "none"; })
return d.job && d.job.status ? null : "none";
})
.transition() .transition()
.duration(0) .duration(0)
.attr("r", 6) .attr("r", 6)
@@ -969,44 +984,26 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
}); });
t.selectAll(".WorkflowChart-nameText") t.selectAll(".WorkflowChart-nameText")
.attr("x", function (d) { .attr("x", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? 20 : nodeW / 2; })
return (scope.mode === 'details' && d.job && d.job.status) ? 20 : nodeW / 2; .attr("y", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? 10 : nodeH / 2; })
}) .attr("text-anchor", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? "inherit" : "middle"; })
.attr("y", function (d) {
return (scope.mode === 'details' && d.job && d.job.status) ? 10 : nodeH / 2;
})
.attr("text-anchor", function (d) {
return (scope.mode === 'details' && d.job && d.job.status) ? "inherit" : "middle";
})
.text(function (d) { .text(function (d) {
return (d.unifiedJobTemplate && d.unifiedJobTemplate.name) ? wrap(d.unifiedJobTemplate.name) : ""; return (d.unifiedJobTemplate && d.unifiedJobTemplate.name) ? wrap(d.unifiedJobTemplate.name) : "";
}); });
t.selectAll(".WorkflowChart-detailsLink") t.selectAll(".WorkflowChart-detailsLink")
.style("display", function (d) { .style("display", function(d){ return d.job && d.job.status && d.job.id ? null : "none"; });
return d.job && d.job.status && d.job.id ? null : "none";
});
t.selectAll(".WorkflowChart-deletedText") t.selectAll(".WorkflowChart-deletedText")
.style("display", function (d) { .style("display", function(d){ return d.unifiedJobTemplate || d.placeholder ? "none" : null; });
return d.unifiedJobTemplate || d.placeholder ? "none" : null;
});
t.selectAll(".WorkflowChart-conflictText")
.style("display", function (d) {
return (d.edgeConflict && !d.placeholder) ? null : "none";
});
t.selectAll(".WorkflowChart-activeNode") t.selectAll(".WorkflowChart-activeNode")
.style("display", function (d) { .style("display", function(d) { return d.isActiveEdit ? null : "none"; });
return d.isActiveEdit ? null : "none";
});
t.selectAll(".WorkflowChart-elapsed") t.selectAll(".WorkflowChart-elapsed")
.style("display", function (d) { .style("display", function(d) { return (d.job && d.job.elapsed) ? null : "none"; });
return (d.job && d.job.elapsed) ? null : "none"; }
}); else if(!scope.watchDimensionsSet){
} else if (!scope.watchDimensionsSet) {
scope.watchDimensionsSet = scope.$watch('dimensionsSet', function(){ scope.watchDimensionsSet = scope.$watch('dimensionsSet', function(){
if(scope.dimensionsSet) { if(scope.dimensionsSet) {
scope.watchDimensionsSet(); scope.watchDimensionsSet();
@@ -1059,6 +1056,24 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
}); });
} }
function edit_link() {
this.on("click", function(d) {
if(!d.source.isStartNode && !d.target.placeholder && scope.mode !== 'details'){
// What if the node is new? it won't have a nodeId right?
scope.editLink({
parentId: d.source.nodeId,
childId: d.target.nodeId
});
}
});
}
function link_node() {
this.on("click", function(d) {
alert('this does not work, don\'t click it');
});
}
function details() { function details() {
this.on("mouseover", function() { this.on("mouseover", function() {
d3.select(this).style("text-decoration", "underline"); d3.select(this).style("text-decoration", "underline");
@@ -1070,53 +1085,37 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
let goToJobResults = function(job_type) { let goToJobResults = function(job_type) {
if(job_type === 'job') { if(job_type === 'job') {
$state.go('output', { $state.go('output', {id: d.job.id, type: 'playbook'});
id: d.job.id, }
type: 'playbook' else if(job_type === 'inventory_update') {
}); $state.go('output', {id: d.job.id, type: 'inventory'});
} else if (job_type === 'inventory_update') { }
$state.go('output', { else if(job_type === 'project_update') {
id: d.job.id, $state.go('output', {id: d.job.id, type: 'project'});
type: 'inventory'
});
} else if (job_type === 'project_update') {
$state.go('output', {
id: d.job.id,
type: 'project'
});
} else if (job_type === 'workflow_job') {
$state.go('workflowResults', {
id: d.job.id,
});
} }
}; };
if (d.job.type) { if(d.job.id) {
goToJobResults(d.job.type); if(d.unifiedJobTemplate) {
goToJobResults(d.unifiedJobTemplate.unified_job_type);
} }
else { else {
// We don't have access to the job type and have to make // We don't have access to the unified resource 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) {
const { results } = res.data; goToJobResults(res.data.results[0].type);
const job = results.filter(result => result.summary_fields.job.id === d.job.id);
goToJobResults(job[0].summary_fields.job.type);
} }
}) })
.catch(({ .catch(({data, status}) => {
data, ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Unable to get job: ' + status });
status
}) => {
ProcessErrors(scope, data, status, null, {
hdr: 'Error!',
msg: 'Unable to get job: ' + status
});
}); });
} }
}
}); });
} }
@@ -1159,6 +1158,7 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
function onResize(){ function onResize(){
let dimensions = calcAvailableScreenSpace(); let dimensions = calcAvailableScreenSpace();
$('.WorkflowMaker-chart').css("width", dimensions.width);
$('.WorkflowMaker-chart').css("height", dimensions.height); $('.WorkflowMaker-chart').css("height", dimensions.height);
} }
@@ -1177,14 +1177,15 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
$('.WorkflowMaker-chart').show(); $('.WorkflowMaker-chart').show();
}); });
}); });
} else { }
else {
scope.$on('workflowMakerModalResized', function(){ scope.$on('workflowMakerModalResized', function(){
let dimensions = calcAvailableScreenSpace(); let dimensions = calcAvailableScreenSpace();
$('.WorkflowMaker-chart').css("width", dimensions.width);
$('.WorkflowMaker-chart').css("height", dimensions.height); $('.WorkflowMaker-chart').css("height", dimensions.height);
}); });
} }
} }
}; };
} }];
];

View File

@@ -0,0 +1,7 @@
import workflowLinkForm from './workflow-link-form.directive';
import workflowNodeForm from './workflow-node-form.directive';
export default
angular.module('templates.workflowMaker.forms', [])
.directive('workflowLinkForm', workflowLinkForm)
.directive('workflowNodeForm', workflowNodeForm);

View File

@@ -0,0 +1,38 @@
/*************************************************
* Copyright (c) 2018 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default ['$scope', 'TemplatesStrings', 'CreateSelect2', '$timeout',
function($scope, TemplatesStrings, CreateSelect2, $timeout) {
$scope.strings = TemplatesStrings;
$scope.edgeTypeOptions = [
{
label: $scope.strings.get('workflow_maker.ALWAYS'),
value: 'always'
},
{
label: $scope.strings.get('workflow_maker.ON_SUCCESS'),
value: 'success'
},
{
label: $scope.strings.get('workflow_maker.ON_FAILURE'),
value: 'failure'
}
];
$scope.$watch('linkConfig.edgeType', () => {
if (_.has($scope, 'linkConfig.edgeType')) {
$scope.edgeType = {
value: $scope.linkConfig.edgeType
};
CreateSelect2({
element: '#workflow_node_edge_2',
multiple: false
});
}
});
}
];

View File

@@ -0,0 +1,22 @@
/*************************************************
* Copyright (c) 2018 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import workflowLinkFormController from './workflow-link-form.controller';
export default ['templateUrl',
function(templateUrl) {
return {
scope: {
linkConfig: '<',
cancel: '&',
select: '&'
},
restrict: 'E',
templateUrl: templateUrl('templates/workflows/workflow-maker/forms/workflow-link-form'),
controller: workflowLinkFormController
};
}
];

View File

@@ -0,0 +1,25 @@
<div class="WorkflowMaker-formTitle">{{:: strings.get('workflow_maker.EDIT_LINK', {parentName: linkConfig.parent.name, childName: linkConfig.child.name}) }}</div>
<div class="WorkflowMaker-form">
<div class="form-group Form-formGroup Form-formGroup--singleColumn">
<label for="edgeType" class="Form-inputLabelContainer">
<span class="Form-requiredAsterisk">*</span>
<span class="Form-inputLabel">{{:: strings.get('workflow_maker.RUN') }}</span>
</label>
<div>
<select
id="workflow_node_edge_2"
ng-options="v as v.label for v in edgeTypeOptions track by v.value"
ng-model="edgeType"
class="form-control Form-dropDown"
name="edgeType"
tabindex="-1"
aria-hidden="true">
</select>
</div>
</div>
<div class="buttons Form-buttons" id="workflow_maker_controls">
<button type="button" class="btn btn-sm Form-cancelButton" id="workflow_maker_cancel_link_btn" ng-click="cancel()"> {{:: strings.get('CANCEL') }}</button>
<button type="button" class="btn btn-sm Form-saveButton" id="workflow_maker_select_link_btn" ng-click="select({parentId: linkConfig.parent.id, childId: linkConfig.child.id, edgeType: edgeType.value})" ng-disabled="!edgeType"> {{:: strings.get('workflow_maker.SELECT') }}</button>
</div>
</div>
</div>

View File

@@ -0,0 +1,11 @@
/*************************************************
* Copyright (c) 2018 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default ['$scope',
function($scope) {
console.log('inside wnf controller');
}
];

View File

@@ -0,0 +1,21 @@
/*************************************************
* Copyright (c) 2018 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import workflowNodeFormController from './workflow-node-form.controller';
export default ['templateUrl',
function(templateUrl) {
return {
scope: {},
restrict: 'E',
templateUrl: templateUrl('templates/workflows/workflow-maker/forms/workflow-node-form'),
controller: workflowNodeFormController,
link: function(scope) {
console.log('inside link function for workflow node form');
}
};
}
];

View File

@@ -0,0 +1,51 @@
<div class="Form-tabHolder">
<div class="Form-tab WorkflowMaker-formTab" ng-class="{'is-selected': workflowMakerFormConfig.activeTab === 'jobs'}" ng-click="toggleFormTab('jobs')">{{strings.get('workflow_maker.JOBS')}}</div>
<div class="Form-tab WorkflowMaker-formTab" ng-class="{'is-selected': workflowMakerFormConfig.activeTab === 'project_sync'}" ng-click="toggleFormTab('project_sync')">{{strings.get('workflow_maker.PROJECT_SYNC')}}</div>
<div class="Form-tab WorkflowMaker-formTab" ng-class="{'is-selected': workflowMakerFormConfig.activeTab === 'inventory_sync'}" ng-click="toggleFormTab('inventory_sync')">{{strings.get('workflow_maker.INVENTORY_SYNC')}}</div>
</div>
<div class="WorkflowMaker-formLists">
<div id="workflow-jobs-list" ui-view="jobTemplateList" ng-show="workflowMakerFormConfig.activeTab === 'jobs'"></div>
<div id="workflow-project-sync-list" ui-view="projectSyncList" ng-show="workflowMakerFormConfig.activeTab === 'project_sync'"></div>
<div id="workflow-inventory-sync-list" ui-view="inventorySyncList" ng-show="workflowMakerFormConfig.activeTab === 'inventory_sync'"></div>
</div>
<span ng-show="selectedTemplate &&
((selectedTemplate.type === 'job_template' && workflowMakerFormConfig.activeTab === 'jobs') ||
(selectedTemplate.type === 'project' && workflowMakerFormConfig.activeTab === 'project_sync') ||
(selectedTemplate.type === 'inventory_source' && workflowMakerFormConfig.activeTab === 'inventory_sync'))">
<div ng-if="selectedTemplate && selectedTemplateInvalid">
<div class="WorkflowMaker-invalidJobTemplateWarning">
<span class="fa fa-warning"></span>
<span>{{:: strings.get('workflows.INVALID_JOB_TEMPLATE') }}</span>
</div>
</div>
<div ng-if="selectedTemplate && credentialRequiresPassword">
<div class="WorkflowMaker-invalidJobTemplateWarning">
<span class="fa fa-warning"></span>
<span>{{:: strings.get('workflows.CREDENTIAL_WITH_PASS') }}</span>
</div>
</div>
<div class="form-group Form-formGroup Form-formGroup--singleColumn" ng-show="selectedTemplate && !selectedTemplateInvalid && !(credentialRequiresPassword && !promptData.launchConf.ask_credential_on_launch)">
<label for="verbosity" class="Form-inputLabelContainer">
<span class="Form-requiredAsterisk">*</span>
<span class="Form-inputLabel">{{:: strings.get('workflow_maker.RUN') }}</span>
</label>
<div>
<select
id="workflow_node_edge"
ng-options="v as v.label for v in edgeTypeOptions track by v.value"
ng-model="edgeType"
class="form-control Form-dropDown"
name="edgeType"
tabindex="-1"
ng-disabled="!workflowJobTemplateObj.summary_fields.user_capabilities.edit"
aria-hidden="true">
</select>
</div>
</div>
<div class="buttons Form-buttons" id="workflow_maker_controls">
<button type="button" class="btn btn-sm Form-primaryButton Form-primaryButton--noMargin" id="workflow_maker_prompt_btn" ng-show="showPromptButton" ng-click="openPromptModal()"> {{:: strings.get('prompt.PROMPT') }}</button>
<button type="button" class="btn btn-sm Form-cancelButton" id="workflow_maker_cancel_btn" ng-show="(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)" ng-click="cancelNodeForm()"> {{:: strings.get('CANCEL') }}</button>
<button type="button" class="btn btn-sm Form-cancelButton" id="workflow_maker_close_btn" ng-show="!(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)" ng-click="cancelNodeForm()"> {{:: strings.get('CLOSE') }}</button>
<button type="button" class="btn btn-sm Form-saveButton" id="workflow_maker_select_btn" ng-show="(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate) && !selectedTemplateInvalid && !(credentialRequiresPassword && !promptData.launchConf.ask_credential_on_launch)" ng-click="confirmNodeForm()" ng-disabled="!selectedTemplate || promptModalMissingReqFields || credentialRequiresPassword"> {{:: strings.get('workflow_maker.SELECT') }}</button>
</div>
</span>

View File

@@ -1,8 +1,9 @@
import workflowMaker from './workflow-maker.directive'; import workflowMaker from './workflow-maker.directive';
import WorkflowMakerController from './workflow-maker.controller'; import WorkflowMakerController from './workflow-maker.controller';
import workflowMakerForms from './forms/main';
export default export default
angular.module('templates.workflowMaker', []) angular.module('templates.workflowMaker', [workflowMakerForms.name])
// In order to test this controller I had to expose it at the module level // In order to test this controller I had to expose it at the module level
// like so. Is this correct? Is there a better pattern for doing this? // like so. Is this correct? Is there a better pattern for doing this?
.controller('WorkflowMakerController', WorkflowMakerController) .controller('WorkflowMakerController', WorkflowMakerController)

View File

@@ -127,7 +127,6 @@
color: @list-title-txt; color: @list-title-txt;
font-size: 14px; font-size: 14px;
font-weight: bold; font-weight: bold;
text-transform: uppercase;
margin-bottom: 20px; margin-bottom: 20px;
} }
.WorkflowMaker-formHelp { .WorkflowMaker-formHelp {

View File

@@ -203,7 +203,7 @@ export default ['$scope', 'WorkflowService', 'TemplatesService',
}); });
}); });
} else { } else {
if (params.node.edited || !params.node.originalParentId || (params.node.originalParentId && params.parentId !== params.node.originalParentId)) { if (params.node.edited || !params.node.originalParentId || (params.node.originalParentId && (params.parentId !== params.node.originalParentId || params.node.originalEdge !== params.node.edgeType))) {
if (params.node.edited) { if (params.node.edited) {
@@ -446,6 +446,10 @@ export default ['$scope', 'WorkflowService', 'TemplatesService',
$scope.cancelNodeForm(); $scope.cancelNodeForm();
} }
if ($scope.linkBeingEdited) {
$scope.cancelLinkForm();
}
$scope.workflowMakerFormConfig.nodeMode = "add"; $scope.workflowMakerFormConfig.nodeMode = "add";
$scope.addParent = parent; $scope.addParent = parent;
$scope.betweenTwoNodes = betweenTwoNodes; $scope.betweenTwoNodes = betweenTwoNodes;
@@ -572,6 +576,10 @@ export default ['$scope', 'WorkflowService', 'TemplatesService',
$scope.startEditNode = function (nodeToEdit) { $scope.startEditNode = function (nodeToEdit) {
$scope.editNodeHelpMessage = null; $scope.editNodeHelpMessage = null;
if ($scope.linkBeingEdited) {
$scope.cancelLinkForm();
}
if (!$scope.nodeBeingEdited || ($scope.nodeBeingEdited && $scope.nodeBeingEdited.id !== nodeToEdit.id)) { if (!$scope.nodeBeingEdited || ($scope.nodeBeingEdited && $scope.nodeBeingEdited.id !== nodeToEdit.id)) {
if ($scope.placeholderNode || $scope.nodeBeingEdited) { if ($scope.placeholderNode || $scope.nodeBeingEdited) {
$scope.cancelNodeForm(); $scope.cancelNodeForm();
@@ -893,6 +901,91 @@ export default ['$scope', 'WorkflowService', 'TemplatesService',
}; };
/* EDIT LINK FUNCTIONS */
$scope.startEditLink = (parentId, childId) => {
const setupLinkEdit = () => {
const parentNode = WorkflowService.searchTree({
element: $scope.treeData.data,
matchingId: parentId,
byNodeId: true
});
parentNode.isLinkEditParent = true;
// Loop across children looking for childId
const childNode = _.find(parentNode.children, {'nodeId': childId});
childNode.isLinkEditChild = true;
$scope.linkBeingEdited = {
parent: parentNode,
child: childNode
}
$scope.linkConfig = {
parent: {
id: parentId,
name: parentNode.unifiedJobTemplate.name
},
child: {
id: childId,
name: childNode.unifiedJobTemplate.name
},
edgeType: childNode.edgeType
}
$scope.editLink = true;
$scope.$broadcast("refreshWorkflowChart");
}
if ($scope.nodeBeingEdited || $scope.placeholderNode) {
$scope.cancelNodeForm();
}
if ($scope.linkBeingEdited) {
if ($scope.linkBeingEdited.parent.nodeId !== parentId || $scope.linkBeingEdited.child.nodeId !== childId) {
$scope.linkBeingEdited.parent.isLinkEditParent = false;
$scope.linkBeingEdited.child.isLinkEditChild = false;
setupLinkEdit()
}
} else {
setupLinkEdit();
}
};
$scope.confirmLinkForm = (parentId, childId, edgeType) => {
$scope.linkBeingEdited.parent.isLinkEditParent = false;
$scope.linkBeingEdited.child.isLinkEditChild = false;
const parentNode = WorkflowService.searchTree({
element: $scope.treeData.data,
matchingId: parentId,
byNodeId: true
});
// Loop across children looking for childId
const childNode = _.find(parentNode.children, {'nodeId': childId});
childNode.edgeType = edgeType;
$scope.linkBeingEdited = null;
$scope.editLink = false;
$scope.$broadcast("refreshWorkflowChart");
}
$scope.cancelLinkForm = () => {
$scope.linkBeingEdited.parent.isLinkEditParent = false;
$scope.linkBeingEdited.child.isLinkEditChild = false;
$scope.linkBeingEdited = null;
$scope.editLink = false;
$scope.$broadcast("refreshWorkflowChart");
};
/* DELETE NODE FUNCTIONS */ /* DELETE NODE FUNCTIONS */
function resetDeleteNode() { function resetDeleteNode() {
@@ -912,6 +1005,10 @@ export default ['$scope', 'WorkflowService', 'TemplatesService',
$scope.confirmDeleteNode = function () { $scope.confirmDeleteNode = function () {
if ($scope.nodeToBeDeleted) { if ($scope.nodeToBeDeleted) {
if ($scope.linkBeingEdited) {
$scope.cancelLinkForm();
}
// TODO: turn this into a promise so that we can handle errors // TODO: turn this into a promise so that we can handle errors
WorkflowService.removeNodeFromTree({ WorkflowService.removeNodeFromTree({

View File

@@ -81,9 +81,10 @@
</div> </div>
</div> </div>
</div> </div>
<workflow-chart ng-if="modalOpen" tree-data="treeData.data" add-node="startAddNode(parent, betweenTwoNodes)" edit-node="startEditNode(nodeToEdit)" delete-node="startDeleteNode(nodeToDelete)" workflow-zoomed="workflowZoomed(zoom)" can-add-workflow-job-template="canAddWorkflowJobTemplate" workflow-job-template-obj="workflowJobTemplateObj" mode="edit" class="WorkflowMaker-chart"></workflow-chart> <workflow-chart ng-if="modalOpen" tree-data="treeData.data" add-node="startAddNode(parent, betweenTwoNodes)" edit-node="startEditNode(nodeToEdit)" edit-link="startEditLink(parentId, childId)" delete-node="startDeleteNode(nodeToDelete)" workflow-zoomed="workflowZoomed(zoom)" can-add-workflow-job-template="canAddWorkflowJobTemplate" workflow-job-template-obj="workflowJobTemplateObj" mode="edit" class="WorkflowMaker-chart"></workflow-chart>
</div> </div>
<div class="WorkflowMaker-contentRight"> <div class="WorkflowMaker-contentRight">
<span ng-show="!editLink">
<div class="WorkflowMaker-formTitle">{{(workflowMakerFormConfig.nodeMode === 'edit' && nodeBeingEdited) ? ((nodeBeingEdited.unifiedJobTemplate && nodeBeingEdited.unifiedJobTemplate.name) ? nodeBeingEdited.unifiedJobTemplate.name : strings.get('workflow_maker.EDIT_TEMPLATE')) : strings.get('workflow_maker.ADD_A_TEMPLATE')}}</div> <div class="WorkflowMaker-formTitle">{{(workflowMakerFormConfig.nodeMode === 'edit' && nodeBeingEdited) ? ((nodeBeingEdited.unifiedJobTemplate && nodeBeingEdited.unifiedJobTemplate.name) ? nodeBeingEdited.unifiedJobTemplate.name : strings.get('workflow_maker.EDIT_TEMPLATE')) : strings.get('workflow_maker.ADD_A_TEMPLATE')}}</div>
<div class="WorkflowMaker-formHelp" ng-show="workflowMakerFormConfig.nodeMode === 'idle'" ng-bind="treeData.data.totalNodes === 0 ? strings.get('workflow_maker.PLEASE_CLICK_THE_START_BUTTON') : strings.get('workflow_maker.PLEASE_HOVER_OVER_A_TEMPLATE')"></div> <div class="WorkflowMaker-formHelp" ng-show="workflowMakerFormConfig.nodeMode === 'idle'" ng-bind="treeData.data.totalNodes === 0 ? strings.get('workflow_maker.PLEASE_CLICK_THE_START_BUTTON') : strings.get('workflow_maker.PLEASE_HOVER_OVER_A_TEMPLATE')"></div>
<div class="WorkflowMaker-form" ng-show="workflowMakerFormConfig.nodeMode === 'add' || workflowMakerFormConfig.nodeMode === 'edit'"> <div class="WorkflowMaker-form" ng-show="workflowMakerFormConfig.nodeMode === 'add' || workflowMakerFormConfig.nodeMode === 'edit'">
@@ -98,10 +99,8 @@
<div id="workflow-inventory-sync-list" ui-view="inventorySyncList" ng-show="workflowMakerFormConfig.activeTab === 'inventory_sync'"></div> <div id="workflow-inventory-sync-list" ui-view="inventorySyncList" ng-show="workflowMakerFormConfig.activeTab === 'inventory_sync'"></div>
</div> </div>
<span ng-show="selectedTemplate && <span ng-show="selectedTemplate &&
((selectedTemplate.type === 'job_template' || selectedTemplate.type === 'workflow_job_template' && workflowMakerFormConfig.activeTab === 'jobs') || ((selectedTemplate.type === 'job_template' && workflowMakerFormConfig.activeTab === 'jobs') ||
(selectedTemplate.unified_job_type === 'job' || selectedTemplate.unified_job_type === 'workflow_job' && workflowMakerFormConfig.activeTab === 'jobs') ||
(selectedTemplate.type === 'project' && workflowMakerFormConfig.activeTab === 'project_sync') || (selectedTemplate.type === 'project' && workflowMakerFormConfig.activeTab === 'project_sync') ||
(selectedTemplate.unified_job_type === 'inventory_update' && workflowMakerFormConfig.activeTab === 'inventory_sync') ||
(selectedTemplate.type === 'inventory_source' && workflowMakerFormConfig.activeTab === 'inventory_sync'))"> (selectedTemplate.type === 'inventory_source' && workflowMakerFormConfig.activeTab === 'inventory_sync'))">
<div ng-if="selectedTemplate && selectedTemplateInvalid"> <div ng-if="selectedTemplate && selectedTemplateInvalid">
<div class="WorkflowMaker-invalidJobTemplateWarning"> <div class="WorkflowMaker-invalidJobTemplateWarning">
@@ -115,7 +114,7 @@
<span>{{:: strings.get('workflows.CREDENTIAL_WITH_PASS') }}</span> <span>{{:: strings.get('workflows.CREDENTIAL_WITH_PASS') }}</span>
</div> </div>
</div> </div>
<div class="form-group Form-formGroup Form-formGroup--singleColumn" ng-show="selectedTemplate && !selectedTemplateInvalid && !(credentialRequiresPassword && !promptData.launchConf.ask_credential_on_launch)"> <div class="form-group Form-formGroup Form-formGroup--singleColumn" ng-show="selectedTemplate && !selectedTemplateInvalid && !(credentialRequiresPassword && !promptData.launchConf.ask_credential_on_launch) && workflowMakerFormConfig.nodeMode === 'add'">
<label for="verbosity" class="Form-inputLabelContainer"> <label for="verbosity" 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>
@@ -133,8 +132,6 @@
</select> </select>
</div> </div>
</div> </div>
<div ng-show="editNodeHelpMessage" class="WorkflowMaker-formHelp" ng-bind="editNodeHelpMessage"></div>
<br />
<div class="buttons Form-buttons" id="workflow_maker_controls"> <div class="buttons Form-buttons" id="workflow_maker_controls">
<button type="button" class="btn btn-sm Form-primaryButton Form-primaryButton--noMargin" id="workflow_maker_prompt_btn" ng-show="showPromptButton" ng-click="openPromptModal()"> {{:: strings.get('prompt.PROMPT') }}</button> <button type="button" class="btn btn-sm Form-primaryButton Form-primaryButton--noMargin" id="workflow_maker_prompt_btn" ng-show="showPromptButton" ng-click="openPromptModal()"> {{:: strings.get('prompt.PROMPT') }}</button>
<button type="button" class="btn btn-sm Form-cancelButton" id="workflow_maker_cancel_btn" ng-show="(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)" ng-click="cancelNodeForm()"> {{:: strings.get('CANCEL') }}</button> <button type="button" class="btn btn-sm Form-cancelButton" id="workflow_maker_cancel_btn" ng-show="(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)" ng-click="cancelNodeForm()"> {{:: strings.get('CANCEL') }}</button>
@@ -143,6 +140,10 @@
</div> </div>
</span> </span>
</div> </div>
</span>
<span ng-if="editLink">
<workflow-link-form link-config="linkConfig" select="confirmLinkForm(parentId, childId, edgeType)" cancel="cancelLinkForm()"/>
</span>
</div> </div>
</div> </div>
<div class="WorkflowMaker-buttonHolder"> <div class="WorkflowMaker-buttonHolder">