diff --git a/awx/ui/client/src/shared/Modal.js b/awx/ui/client/src/shared/Modal.js
index e37defa900..e6890ac367 100644
--- a/awx/ui/client/src/shared/Modal.js
+++ b/awx/ui/client/src/shared/Modal.js
@@ -46,7 +46,7 @@ angular.module('ModalDialog', ['Utilities', 'ParseHelper'])
return function(params) {
- var scope = params.scope,
+ let scope = params.scope,
buttonSet = params.buttons,
width = params.width || 500,
height = params.height || 600,
diff --git a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js
index 642dc26971..ca5957f134 100644
--- a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js
+++ b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js
@@ -4,8 +4,8 @@
* All Rights Reserved
*************************************************/
-export default [ '$state','moment',
- function($state, moment) {
+export default [ '$state','moment', '$timeout', '$window',
+ function($state, moment, $timeout, $window) {
return {
scope: {
@@ -21,38 +21,89 @@ export default [ '$state','moment',
link: function(scope, element) {
let margin = {top: 20, right: 20, bottom: 20, left: 20},
- width = 950,
- height = 550,
i = 0,
nodeW = 120,
nodeH = 60,
rootW = 60,
- rootH = 40;
+ rootH = 40,
+ startNodeOffsetY = scope.mode === 'details' ? 17 : 10,
+ verticalSpaceBetweenNodes = 20,
+ windowHeight,
+ windowWidth,
+ tree,
+ line,
+ zoomObj,
+ baseSvg,
+ svgGroup;
- let tree = d3.layout.tree()
- .size([height, width]);
+ scope.dimensionsSet = false;
- let line = d3.svg.line()
- .x(function(d){return d.x;})
- .y(function(d){return d.y;});
+ $timeout(function(){
+ let dimensions = calcAvailableScreenSpace();
- let zoomObj = d3.behavior.zoom().scaleExtent([0.5, 2]);
+ windowHeight = dimensions.height;
+ windowWidth = dimensions.width;
- let baseSvg = d3.select(element[0]).append("svg")
- .attr("width", width - margin.right - margin.left)
- .attr("height", height - margin.top - margin.bottom)
- .attr("class", "WorkflowChart-svg")
- .call(zoomObj
- .on("zoom", naturalZoom)
- );
+ $('.WorkflowMaker-chart').css("height", windowHeight);
+ $('.WorkflowMaker-chart').css("width", windowWidth);
- let svgGroup = baseSvg.append("g")
- .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
+ scope.dimensionsSet = true;
+
+ init();
+ });
+
+ function init() {
+ tree = d3.layout.tree()
+ .nodeSize([nodeH + verticalSpaceBetweenNodes,nodeW])
+ .separation(function(a, b) {
+ // This should tighten up some of the other nodes so there's not so much wasted space
+ return a.parent === b.parent ? 1 : 1.25;
+ });
+
+ line = d3.svg.line()
+ .x(function(d){return d.x;})
+ .y(function(d){return d.y;});
+
+ zoomObj = d3.behavior.zoom().scaleExtent([0.5, 2]);
+
+ baseSvg = d3.select(element[0]).append("svg")
+ .attr("class", "WorkflowChart-svg")
+ .call(zoomObj
+ .on("zoom", naturalZoom)
+ );
+
+ svgGroup = baseSvg.append("g")
+ .attr("transform", "translate(" + margin.left + "," + (windowHeight/2 - rootH/2 - startNodeOffsetY) + ")");
+ }
+
+ function calcAvailableScreenSpace() {
+ let dimensions = {};
+
+ if(scope.mode !== 'details') {
+ // This is the workflow editor
+ dimensions.height = $('.WorkflowMaker-contentLeft').outerHeight() - $('.WorkflowLegend-maker').outerHeight();
+ dimensions.width = $('#workflow-modal-dialog').width() - $('.WorkflowMaker-contentRight').outerWidth();
+ }
+ else {
+ // This is the workflow details view
+ let panel = $('.WorkflowResults-rightSide').children('.Panel')[0];
+ let panelWidth = $(panel).width();
+ let panelHeight = $(panel).height();
+ let headerHeight = $('.StandardOut-panelHeader').outerHeight();
+ let legendHeight = $('.WorkflowLegend-details').outerHeight();
+ let proposedHeight = panelHeight - headerHeight - legendHeight - 40;
+
+ dimensions.height = proposedHeight > 200 ? proposedHeight : 200;
+ dimensions.width = panelWidth;
+ }
+
+ return dimensions;
+ }
function lineData(d){
let sourceX = d.source.isStartNode ? d.source.y + rootW : d.source.y + nodeW;
- let sourceY = d.source.isStartNode ? d.source.x + 10 + rootH / 2 : d.source.x + nodeH / 2;
+ let sourceY = d.source.isStartNode ? d.source.x + startNodeOffsetY + rootH / 2 : d.source.x + nodeH / 2;
let targetX = d.target.y;
let targetY = d.target.x + nodeH / 2;
@@ -108,7 +159,7 @@ export default [ '$state','moment',
let scale = d3.event.scale,
translation = d3.event.translate;
- translation = [translation[0] + (margin.left*scale), translation[1] + (margin.top*scale)];
+ translation = [translation[0] + (margin.left*scale), translation[1] + ((windowHeight/2 - rootH/2 - startNodeOffsetY)*scale)];
svgGroup.attr("transform", "translate(" + translation + ")scale(" + scale + ")");
@@ -122,12 +173,12 @@ export default [ '$state','moment',
let scale = zoom / 100,
translation = zoomObj.translate(),
origZoom = zoomObj.scale(),
- unscaledOffsetX = (translation[0] + ((width*origZoom) - width)/2)/origZoom,
- unscaledOffsetY = (translation[1] + ((height*origZoom) - height)/2)/origZoom,
- translateX = unscaledOffsetX*scale - ((scale*width)-width)/2,
- translateY = unscaledOffsetY*scale - ((scale*height)-height)/2;
+ unscaledOffsetX = (translation[0] + ((windowWidth*origZoom) - windowWidth)/2)/origZoom,
+ unscaledOffsetY = (translation[1] + ((windowHeight*origZoom) - windowHeight)/2)/origZoom,
+ translateX = unscaledOffsetX*scale - ((scale*windowWidth)-windowWidth)/2,
+ translateY = unscaledOffsetY*scale - ((scale*windowHeight)-windowHeight)/2;
- svgGroup.attr("transform", "translate(" + [translateX + (margin.left*scale), translateY + (margin.top*scale)] + ")scale(" + scale + ")");
+ svgGroup.attr("transform", "translate(" + [translateX + (margin.left*scale), translateY + ((windowHeight/2 - rootH/2 - startNodeOffsetY)*scale)] + ")scale(" + scale + ")");
zoomObj.scale(scale);
zoomObj.translate([translateX, translateY]);
}
@@ -145,572 +196,584 @@ export default [ '$state','moment',
translateX = translateCoords[0];
translateY = direction === 'up' ? translateCoords[1] - distance : translateCoords[1] + distance;
}
- svgGroup.attr("transform", "translate(" + translateX + "," + translateY + ")scale(" + scale + ")");
+ svgGroup.attr("transform", "translate(" + translateX + "," + (translateY + ((windowHeight/2 - rootH/2 - startNodeOffsetY)*scale)) + ")scale(" + scale + ")");
zoomObj.translate([translateX, translateY]);
}
function resetZoomAndPan() {
- svgGroup.attr("transform", "translate(" + margin.left + "," + margin.top + ")scale(" + 1 + ")");
+ svgGroup.attr("transform", "translate(" + margin.left + "," + (windowHeight/2 - rootH/2 - startNodeOffsetY) + ")scale(" + 1 + ")");
// Update the zoomObj
zoomObj.scale(1);
zoomObj.translate([0,0]);
}
function update() {
- // Declare the nodes
- let nodes = tree.nodes(scope.treeData),
- links = tree.links(nodes);
- let node = svgGroup.selectAll("g.node")
- .data(nodes, function(d) {
- d.y = d.depth * 180;
- return d.id || (d.id = ++i);
- });
+ if(scope.dimensionsSet) {
+ // Declare the nodes
+ let nodes = tree.nodes(scope.treeData),
+ links = tree.links(nodes);
+ let node = svgGroup.selectAll("g.node")
+ .data(nodes, function(d) {
+ d.y = d.depth * 180;
+ return d.id || (d.id = ++i);
+ });
- let nodeEnter = node.enter().append("g")
- .attr("class", "node")
- .attr("id", function(d){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 + ")"; });
+ let nodeEnter = node.enter().append("g")
+ .attr("class", "node")
+ .attr("id", function(d){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 + ")"; });
- nodeEnter.each(function(d) {
- let thisNode = d3.select(this);
- if(d.isStartNode && scope.mode === 'details') {
- // Overwrite the default root height and width and replace it with a small blue square
- rootW = 25;
- rootH = 25;
- thisNode.append("rect")
- .attr("width", rootW)
- .attr("height", rootH)
- .attr("y", 10)
- .attr("rx", 5)
- .attr("ry", 5)
- .attr("fill", "#337ab7")
- .attr("class", "WorkflowChart-rootNode");
- }
- else if(d.isStartNode && scope.mode !== 'details') {
- thisNode.append("rect")
- .attr("width", rootW)
- .attr("height", rootH)
- .attr("y", 10)
- .attr("rx", 5)
- .attr("ry", 5)
- .attr("fill", "#5cb85c")
- .attr("class", "WorkflowChart-rootNode")
- .call(add_node);
- thisNode.append("text")
- .attr("x", 13)
- .attr("y", 30)
- .attr("dy", ".35em")
- .attr("class", "WorkflowChart-startText")
- .text(function () { return "START"; })
- .call(add_node);
- }
- else {
- thisNode.append("rect")
- .attr("width", nodeW)
- .attr("height", nodeH)
- .attr("rx", 5)
- .attr("ry", 5)
- .attr('stroke', function(d) {
- if(d.job && d.job.status) {
- if(d.job.status === "successful"){
- return "#5cb85c";
- }
- else if (d.job.status === "failed" || d.job.status === "error" || d.job.status === "cancelled") {
- return "#d9534f";
+ nodeEnter.each(function(d) {
+ let thisNode = d3.select(this);
+ if(d.isStartNode && scope.mode === 'details') {
+ // Overwrite the default root height and width and replace it with a small blue square
+ rootW = 25;
+ rootH = 25;
+ thisNode.append("rect")
+ .attr("width", rootW)
+ .attr("height", rootH)
+ .attr("y", startNodeOffsetY)
+ .attr("rx", 5)
+ .attr("ry", 5)
+ .attr("fill", "#337ab7")
+ .attr("class", "WorkflowChart-rootNode");
+ }
+ else if(d.isStartNode && scope.mode !== 'details') {
+ thisNode.append("rect")
+ .attr("width", rootW)
+ .attr("height", rootH)
+ //.attr("y", (windowHeight-margin.top-margin.bottom)/2 - rootH)
+ .attr("y", 10)
+ .attr("rx", 5)
+ .attr("ry", 5)
+ .attr("fill", "#5cb85c")
+ .attr("class", "WorkflowChart-rootNode")
+ .call(add_node);
+ thisNode.append("text")
+ .attr("x", 13)
+ //.attr("y", (windowHeight-margin.top-margin.bottom)/2 - rootH + rootH/2)
+ .attr("y", 30)
+ .attr("dy", ".35em")
+ .attr("class", "WorkflowChart-startText")
+ .text(function () { return "START"; })
+ .call(add_node);
+ }
+ else {
+ thisNode.append("rect")
+ .attr("width", nodeW)
+ .attr("height", nodeH)
+ .attr("rx", 5)
+ .attr("ry", 5)
+ .attr('stroke', function(d) {
+ if(d.job && d.job.status) {
+ if(d.job.status === "successful"){
+ return "#5cb85c";
+ }
+ else if (d.job.status === "failed" || d.job.status === "error" || d.job.status === "cancelled") {
+ return "#d9534f";
+ }
+ else {
+ return "#D7D7D7";
+ }
}
else {
return "#D7D7D7";
}
+ })
+ .attr('stroke-width', "2px")
+ .attr("class", function(d) {
+ return d.placeholder ? "rect placeholder" : "rect";
+ });
+
+ thisNode.append("path")
+ .attr("d", rounded_rect(1, 0, 5, nodeH, 5, 1, 0, 1, 0))
+ .attr("class", "WorkflowChart-activeNode")
+ .style("display", function(d) { return d.isActiveEdit ? null : "none"; });
+
+ thisNode.append("text")
+ .attr("x", function(d){ 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("dy", ".35em")
+ .attr("text-anchor", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? "inherit" : "middle"; })
+ .attr("class", "WorkflowChart-defaultText WorkflowChart-nameText")
+ .text(function (d) {
+ return (d.unifiedJobTemplate && d.unifiedJobTemplate.name) ? d.unifiedJobTemplate.name : "";
+ }).each(wrap);
+
+ thisNode.append("foreignObject")
+ .attr("x", 43)
+ .attr("y", 45)
+ .style("font-size","0.7em")
+ .attr("class", "WorkflowChart-conflictText")
+ .html(function () {
+ return "\uf06a EDGE CONFLICT";
+ })
+ .style("display", function(d) { return (d.edgeConflict && !d.placeholder) ? null : "none"; });
+
+ thisNode.append("foreignObject")
+ .attr("x", 17)
+ .attr("y", 22)
+ .attr("dy", ".35em")
+ .attr("text-anchor", "middle")
+ .attr("class", "WorkflowChart-defaultText WorkflowChart-incompleteText")
+ .html(function () {
+ return "\uf06a INCOMPLETE";
+ })
+ .style("display", function(d) { return d.unifiedJobTemplate || d.placeholder ? "none" : null; });
+
+ thisNode.append("circle")
+ .attr("cy", nodeH)
+ .attr("r", 10)
+ .attr("class", "WorkflowChart-nodeTypeCircle")
+ .style("display", function(d) { return d.unifiedJobTemplate && (d.unifiedJobTemplate.type === "project" || d.unifiedJobTemplate.unified_job_type === "project_update" || d.unifiedJobTemplate.type === "inventory_source" || d.unifiedJobTemplate.unified_job_type === "inventory_update") ? null : "none"; });
+
+ thisNode.append("text")
+ .attr("y", nodeH)
+ .attr("dy", ".35em")
+ .attr("text-anchor", "middle")
+ .attr("class", "WorkflowChart-nodeTypeLetter")
+ .text(function (d) {
+ return (d.unifiedJobTemplate && (d.unifiedJobTemplate.type === "project" || d.unifiedJobTemplate.unified_job_type === "project_update")) ? "P" : (d.unifiedJobTemplate && (d.unifiedJobTemplate.type === "inventory_source" || d.unifiedJobTemplate.unified_job_type === "inventory_update") ? "I" : "");
+ })
+ .style("display", function(d) { return d.unifiedJobTemplate && (d.unifiedJobTemplate.type === "project" || d.unifiedJobTemplate.unified_job_type === "project_update" || d.unifiedJobTemplate.type === "inventory_source" || d.unifiedJobTemplate.unified_job_type === "inventory_update") ? null : "none"; });
+
+ thisNode.append("rect")
+ .attr("width", nodeW)
+ .attr("height", nodeH)
+ .attr("class", "transparentRect")
+ .call(edit_node)
+ .on("mouseover", function(d) {
+ if(!d.isStartNode) {
+ d3.select("#node-" + d.id)
+ .classed("hovering", true);
+ }
+ })
+ .on("mouseout", function(d){
+ if(!d.isStartNode) {
+ d3.select("#node-" + d.id)
+ .classed("hovering", false);
+ }
+ });
+ thisNode.append("text")
+ .attr("x", nodeW - 50)
+ .attr("y", nodeH - 10)
+ .attr("dy", ".35em")
+ .attr("class", "WorkflowChart-detailsLink")
+ .style("display", function(d){ return d.job && d.job.status && d.job.id ? null : "none"; })
+ .text(function () {
+ return "DETAILS";
+ })
+ .call(details);
+ thisNode.append("circle")
+ .attr("id", function(d){return "node-" + d.id + "-add";})
+ .attr("cx", nodeW)
+ .attr("r", 10)
+ .attr("class", "addCircle nodeCircle")
+ .style("display", function(d) { return d.placeholder || scope.canAddWorkflowJobTemplate === false ? "none" : null; })
+ .call(add_node)
+ .on("mouseover", function(d) {
+ d3.select("#node-" + d.id)
+ .classed("hovering", true);
+ d3.select("#node-" + d.id + "-add")
+ .classed("addHovering", true);
+ })
+ .on("mouseout", function(d){
+ d3.select("#node-" + d.id)
+ .classed("hovering", false);
+ d3.select("#node-" + d.id + "-add")
+ .classed("addHovering", false);
+ });
+ thisNode.append("path")
+ .attr("class", "nodeAddCross WorkflowChart-hoverPath")
+ .style("fill", "white")
+ .attr("transform", function() { return "translate(" + nodeW + "," + 0 + ")"; })
+ .attr("d", d3.svg.symbol()
+ .size(60)
+ .type("cross")
+ )
+ .style("display", function(d) { return d.placeholder || scope.canAddWorkflowJobTemplate === false ? "none" : null; })
+ .call(add_node)
+ .on("mouseover", function(d) {
+ d3.select("#node-" + d.id)
+ .classed("hovering", true);
+ d3.select("#node-" + d.id + "-add")
+ .classed("addHovering", true);
+ })
+ .on("mouseout", function(d){
+ d3.select("#node-" + d.id)
+ .classed("hovering", false);
+ d3.select("#node-" + d.id + "-add")
+ .classed("addHovering", false);
+ });
+ thisNode.append("circle")
+ .attr("id", function(d){return "node-" + d.id + "-remove";})
+ .attr("cx", nodeW)
+ .attr("cy", nodeH)
+ .attr("r", 10)
+ .attr("class", "removeCircle")
+ .style("display", function(d) { return (d.canDelete === false || d.placeholder || scope.canAddWorkflowJobTemplate === false) ? "none" : null; })
+ .call(remove_node)
+ .on("mouseover", function(d) {
+ d3.select("#node-" + d.id)
+ .classed("hovering", true);
+ d3.select("#node-" + d.id + "-remove")
+ .classed("removeHovering", true);
+ })
+ .on("mouseout", function(d){
+ d3.select("#node-" + d.id)
+ .classed("hovering", false);
+ d3.select("#node-" + d.id + "-remove")
+ .classed("removeHovering", false);
+ });
+ thisNode.append("path")
+ .attr("class", "nodeRemoveCross WorkflowChart-hoverPath")
+ .style("fill", "white")
+ .attr("transform", function() { return "translate(" + nodeW + "," + nodeH + ") rotate(-45)"; })
+ .attr("d", d3.svg.symbol()
+ .size(60)
+ .type("cross")
+ )
+ .style("display", function(d) { return (d.canDelete === false || d.placeholder || scope.canAddWorkflowJobTemplate === false) ? "none" : null; })
+ .call(remove_node)
+ .on("mouseover", function(d) {
+ d3.select("#node-" + d.id)
+ .classed("hovering", true);
+ d3.select("#node-" + d.id + "-remove")
+ .classed("removeHovering", true);
+ })
+ .on("mouseout", function(d){
+ d3.select("#node-" + d.id)
+ .classed("hovering", false);
+ d3.select("#node-" + d.id + "-remove")
+ .classed("removeHovering", false);
+ });
+
+ thisNode.append("circle")
+ .attr("class", function(d) {
+
+ let statusClass = "WorkflowChart-nodeStatus ";
+
+ if(d.job){
+ switch(d.job.status) {
+ case "pending":
+ statusClass += "workflowChart-nodeStatus--running";
+ break;
+ case "waiting":
+ statusClass += "workflowChart-nodeStatus--running";
+ break;
+ case "running":
+ statusClass += "workflowChart-nodeStatus--running";
+ break;
+ case "successful":
+ statusClass += "workflowChart-nodeStatus--success";
+ break;
+ case "failed":
+ statusClass += "workflowChart-nodeStatus--failed";
+ break;
+ case "error":
+ statusClass += "workflowChart-nodeStatus--failed";
+ break;
+ }
+ }
+
+ return statusClass;
+ })
+ .style("display", function(d) { return d.job && d.job.status ? null : "none"; })
+ .attr("cy", 10)
+ .attr("cx", 10)
+ .attr("r", 6);
+
+ thisNode.append("foreignObject")
+ .attr("x", 5)
+ .attr("y", 43)
+ .style("font-size","0.7em")
+ .attr("class", "WorkflowChart-elapsed")
+ .html(function (d) {
+ if(d.job && d.job.elapsed) {
+ let elapsedMs = d.job.elapsed * 1000;
+ let elapsedMoment = moment.duration(elapsedMs);
+ 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");
+ return "
" + elapsedString + "
";
+ }
+ else {
+ return "";
+ }
+ })
+ .style("display", function(d) { return (d.job && d.job.elapsed) ? null : "none"; });
+ }
+ });
+
+ node.exit().remove();
+
+ let link = svgGroup.selectAll("g.link")
+ .data(links, function(d) {
+ return d.source.id + "-" + d.target.id;
+ });
+
+ let linkEnter = link.enter().append("g")
+ .attr("class", "link")
+ .attr("id", function(d){return "link-" + d.source.id + "-" + d.target.id;});
+
+ // Add entering links in the parent’s old position.
+ linkEnter.insert("path", "g")
+ .attr("class", function(d) {
+ return (d.source.placeholder || d.target.placeholder) ? "linkPath placeholder" : "linkPath";
+ })
+ .attr("d", lineData)
+ .attr('stroke', function(d) {
+ if(d.target.edgeType) {
+ if(d.target.edgeType === "failure") {
+ return "#d9534f";
+ }
+ else if(d.target.edgeType === "success") {
+ return "#5cb85c";
+ }
+ else if(d.target.edgeType === "always"){
+ return "#337ab7";
+ }
+ }
+ else {
+ return "#D7D7D7";
+ }
+ });
+
+ linkEnter.append("circle")
+ .attr("id", function(d){return "link-" + d.source.id + "-" + d.target.id + "-add";})
+ .attr("cx", function(d) {
+ return (d.source.isStartNode) ? (d.target.y + d.source.y + rootW) / 2 : (d.target.y + d.source.y + nodeW) / 2;
+ })
+ .attr("cy", function(d) {
+ 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("class", "addCircle linkCircle")
+ .style("display", function(d) { return (d.source.placeholder || d.target.placeholder || scope.canAddWorkflowJobTemplate === false) ? "none" : null; })
+ .call(add_node_between)
+ .on("mouseover", function(d) {
+ 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);
+ })
+ .on("mouseout", function(d){
+ 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);
+ });
+
+ linkEnter.append("path")
+ .attr("class", "linkCross")
+ .style("fill", "white")
+ .attr("transform", function(d) {
+ let translate;
+ 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 + ")";
+ }
+ else {
+ translate = "translate(" + (d.target.y + d.source.y + nodeW) / 2 + "," + (d.target.x + d.source.x + nodeH) / 2 + ")";
+ }
+ return translate;
+ })
+ .attr("d", d3.svg.symbol()
+ .size(60)
+ .type("cross")
+ )
+ .style("display", function(d) { return (d.source.placeholder || d.target.placeholder || scope.canAddWorkflowJobTemplate === false) ? "none" : null; })
+ .call(add_node_between)
+ .on("mouseover", function(d) {
+ 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);
+ })
+ .on("mouseout", function(d){
+ 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);
+ });
+
+ link.exit().remove();
+
+ // Transition nodes and links to their new positions.
+ let t = baseSvg.transition();
+
+ t.selectAll(".nodeCircle")
+ .style("display", function(d) { return d.placeholder || scope.canAddWorkflowJobTemplate === false ? "none" : null; });
+
+ t.selectAll(".nodeAddCross")
+ .style("display", function(d) { return d.placeholder || scope.canAddWorkflowJobTemplate === false ? "none" : null; });
+
+ t.selectAll(".removeCircle")
+ .style("display", function(d) { return (d.canDelete === false || d.placeholder || scope.canAddWorkflowJobTemplate === false) ? "none" : null; });
+
+ t.selectAll(".nodeRemoveCross")
+ .style("display", function(d) { return (d.canDelete === false || d.placeholder || scope.canAddWorkflowJobTemplate === false) ? "none" : null; });
+
+ t.selectAll(".linkPath")
+ .attr("class", function(d) {
+ return (d.source.placeholder || d.target.placeholder) ? "linkPath placeholder" : "linkPath";
+ })
+ .attr("d", lineData)
+ .attr('stroke', function(d) {
+ if(d.target.edgeType) {
+ if(d.target.edgeType === "failure") {
+ return "#d9534f";
+ }
+ else if(d.target.edgeType === "success") {
+ return "#5cb85c";
+ }
+ else if(d.target.edgeType === "always"){
+ return "#337ab7";
+ }
}
else {
return "#D7D7D7";
}
- })
- .attr('stroke-width', "2px")
- .attr("class", function(d) {
- return d.placeholder ? "rect placeholder" : "rect";
});
- thisNode.append("path")
- .attr("d", rounded_rect(1, 0, 5, nodeH, 5, 1, 0, 1, 0))
- .attr("class", "WorkflowChart-activeNode")
- .style("display", function(d) { return d.isActiveEdit ? null : "none"; });
-
- thisNode.append("text")
- .attr("x", function(d){ 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("dy", ".35em")
- .attr("text-anchor", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? "inherit" : "middle"; })
- .attr("class", "WorkflowChart-defaultText WorkflowChart-nameText")
- .text(function (d) {
- return (d.unifiedJobTemplate && d.unifiedJobTemplate.name) ? d.unifiedJobTemplate.name : "";
- }).each(wrap);
-
- thisNode.append("foreignObject")
- .attr("x", 43)
- .attr("y", 45)
- .style("font-size","0.7em")
- .attr("class", "WorkflowChart-conflictText")
- .html(function () {
- return "\uf06a EDGE CONFLICT";
- })
- .style("display", function(d) { return (d.edgeConflict && !d.placeholder) ? null : "none"; });
-
- thisNode.append("foreignObject")
- .attr("x", 17)
- .attr("y", 22)
- .attr("dy", ".35em")
- .attr("text-anchor", "middle")
- .attr("class", "WorkflowChart-defaultText WorkflowChart-incompleteText")
- .html(function () {
- return "\uf06a INCOMPLETE";
- })
- .style("display", function(d) { return d.unifiedJobTemplate || d.placeholder ? "none" : null; });
-
- thisNode.append("circle")
- .attr("cy", nodeH)
- .attr("r", 10)
- .attr("class", "WorkflowChart-nodeTypeCircle")
- .style("display", function(d) { return d.unifiedJobTemplate && (d.unifiedJobTemplate.type === "project" || d.unifiedJobTemplate.unified_job_type === "project_update" || d.unifiedJobTemplate.type === "inventory_source" || d.unifiedJobTemplate.unified_job_type === "inventory_update") ? null : "none"; });
-
- thisNode.append("text")
- .attr("y", nodeH)
- .attr("dy", ".35em")
- .attr("text-anchor", "middle")
- .attr("class", "WorkflowChart-nodeTypeLetter")
- .text(function (d) {
- return (d.unifiedJobTemplate && (d.unifiedJobTemplate.type === "project" || d.unifiedJobTemplate.unified_job_type === "project_update")) ? "P" : (d.unifiedJobTemplate && (d.unifiedJobTemplate.type === "inventory_source" || d.unifiedJobTemplate.unified_job_type === "inventory_update") ? "I" : "");
- })
- .style("display", function(d) { return d.unifiedJobTemplate && (d.unifiedJobTemplate.type === "project" || d.unifiedJobTemplate.unified_job_type === "project_update" || d.unifiedJobTemplate.type === "inventory_source" || d.unifiedJobTemplate.unified_job_type === "inventory_update") ? null : "none"; });
-
- thisNode.append("rect")
- .attr("width", nodeW)
- .attr("height", nodeH)
- .attr("class", "transparentRect")
- .call(edit_node)
- .on("mouseover", function(d) {
- if(!d.isStartNode) {
- d3.select("#node-" + d.id)
- .classed("hovering", true);
- }
- })
- .on("mouseout", function(d){
- if(!d.isStartNode) {
- d3.select("#node-" + d.id)
- .classed("hovering", false);
- }
- });
- thisNode.append("text")
- .attr("x", nodeW - 50)
- .attr("y", nodeH - 10)
- .attr("dy", ".35em")
- .attr("class", "WorkflowChart-detailsLink")
- .style("display", function(d){ return d.job && d.job.status && d.job.id ? null : "none"; })
- .text(function () {
- return "DETAILS";
- })
- .call(details);
- thisNode.append("circle")
- .attr("id", function(d){return "node-" + d.id + "-add";})
- .attr("cx", nodeW)
- .attr("r", 10)
- .attr("class", "addCircle nodeCircle")
- .style("display", function(d) { return d.placeholder || scope.canAddWorkflowJobTemplate === false ? "none" : null; })
- .call(add_node)
- .on("mouseover", function(d) {
- d3.select("#node-" + d.id)
- .classed("hovering", true);
- d3.select("#node-" + d.id + "-add")
- .classed("addHovering", true);
- })
- .on("mouseout", function(d){
- d3.select("#node-" + d.id)
- .classed("hovering", false);
- d3.select("#node-" + d.id + "-add")
- .classed("addHovering", false);
- });
- thisNode.append("path")
- .attr("class", "nodeAddCross WorkflowChart-hoverPath")
- .style("fill", "white")
- .attr("transform", function() { return "translate(" + nodeW + "," + 0 + ")"; })
- .attr("d", d3.svg.symbol()
- .size(60)
- .type("cross")
- )
- .style("display", function(d) { return d.placeholder || scope.canAddWorkflowJobTemplate === false ? "none" : null; })
- .call(add_node)
- .on("mouseover", function(d) {
- d3.select("#node-" + d.id)
- .classed("hovering", true);
- d3.select("#node-" + d.id + "-add")
- .classed("addHovering", true);
- })
- .on("mouseout", function(d){
- d3.select("#node-" + d.id)
- .classed("hovering", false);
- d3.select("#node-" + d.id + "-add")
- .classed("addHovering", false);
- });
- thisNode.append("circle")
- .attr("id", function(d){return "node-" + d.id + "-remove";})
- .attr("cx", nodeW)
- .attr("cy", nodeH)
- .attr("r", 10)
- .attr("class", "removeCircle")
- .style("display", function(d) { return (d.canDelete === false || d.placeholder || scope.canAddWorkflowJobTemplate === false) ? "none" : null; })
- .call(remove_node)
- .on("mouseover", function(d) {
- d3.select("#node-" + d.id)
- .classed("hovering", true);
- d3.select("#node-" + d.id + "-remove")
- .classed("removeHovering", true);
- })
- .on("mouseout", function(d){
- d3.select("#node-" + d.id)
- .classed("hovering", false);
- d3.select("#node-" + d.id + "-remove")
- .classed("removeHovering", false);
- });
- thisNode.append("path")
- .attr("class", "nodeRemoveCross WorkflowChart-hoverPath")
- .style("fill", "white")
- .attr("transform", function() { return "translate(" + nodeW + "," + nodeH + ") rotate(-45)"; })
- .attr("d", d3.svg.symbol()
- .size(60)
- .type("cross")
- )
- .style("display", function(d) { return (d.canDelete === false || d.placeholder || scope.canAddWorkflowJobTemplate === false) ? "none" : null; })
- .call(remove_node)
- .on("mouseover", function(d) {
- d3.select("#node-" + d.id)
- .classed("hovering", true);
- d3.select("#node-" + d.id + "-remove")
- .classed("removeHovering", true);
- })
- .on("mouseout", function(d){
- d3.select("#node-" + d.id)
- .classed("hovering", false);
- d3.select("#node-" + d.id + "-remove")
- .classed("removeHovering", false);
- });
-
- thisNode.append("circle")
- .attr("class", function(d) {
-
- let statusClass = "WorkflowChart-nodeStatus ";
-
- if(d.job){
- switch(d.job.status) {
- case "pending":
- statusClass += "workflowChart-nodeStatus--running";
- break;
- case "waiting":
- statusClass += "workflowChart-nodeStatus--running";
- break;
- case "running":
- statusClass += "workflowChart-nodeStatus--running";
- break;
- case "successful":
- statusClass += "workflowChart-nodeStatus--success";
- break;
- case "failed":
- statusClass += "workflowChart-nodeStatus--failed";
- break;
- case "error":
- statusClass += "workflowChart-nodeStatus--failed";
- break;
- }
- }
-
- return statusClass;
- })
- .style("display", function(d) { return d.job && d.job.status ? null : "none"; })
- .attr("cy", 10)
- .attr("cx", 10)
- .attr("r", 6);
-
- thisNode.append("foreignObject")
- .attr("x", 5)
- .attr("y", 43)
- .style("font-size","0.7em")
- .attr("class", "WorkflowChart-elapsed")
- .html(function (d) {
- if(d.job && d.job.elapsed) {
- let elapsedMs = d.job.elapsed * 1000;
- let elapsedMoment = moment.duration(elapsedMs);
- 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");
- return "" + elapsedString + "
";
- }
- else {
- return "";
- }
- })
- .style("display", function(d) { return (d.job && d.job.elapsed) ? null : "none"; });
- }
- });
-
- node.exit().remove();
-
- let link = svgGroup.selectAll("g.link")
- .data(links, function(d) {
- return d.source.id + "-" + d.target.id;
- });
-
- let linkEnter = link.enter().append("g")
- .attr("class", "link")
- .attr("id", function(d){return "link-" + d.source.id + "-" + d.target.id;});
-
- // Add entering links in the parent’s old position.
- linkEnter.insert("path", "g")
- .attr("class", function(d) {
- return (d.source.placeholder || d.target.placeholder) ? "linkPath placeholder" : "linkPath";
- })
- .attr("d", lineData)
- .attr('stroke', function(d) {
- if(d.target.edgeType) {
- if(d.target.edgeType === "failure") {
- return "#d9534f";
- }
- else if(d.target.edgeType === "success") {
- return "#5cb85c";
- }
- else if(d.target.edgeType === "always"){
- return "#337ab7";
- }
- }
- else {
- return "#D7D7D7";
- }
- });
-
- linkEnter.append("circle")
- .attr("id", function(d){return "link-" + d.source.id + "-" + d.target.id + "-add";})
- .attr("cx", function(d) {
- return (d.source.isStartNode) ? (d.target.y + d.source.y + rootW) / 2 : (d.target.y + d.source.y + nodeW) / 2;
- })
- .attr("cy", function(d) {
- return (d.source.isStartNode) ? ((d.target.x + 10 + rootH/2) + (d.source.x + nodeH/2)) / 2 : (d.target.x + d.source.x + nodeH) / 2;
- })
- .attr("r", 10)
- .attr("class", "addCircle linkCircle")
- .style("display", function(d) { return (d.source.placeholder || d.target.placeholder || scope.canAddWorkflowJobTemplate === false) ? "none" : null; })
- .call(add_node_between)
- .on("mouseover", function(d) {
- 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);
- })
- .on("mouseout", function(d){
- 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);
- });
-
- linkEnter.append("path")
- .attr("class", "linkCross")
- .style("fill", "white")
- .attr("transform", function(d) {
- let translate;
- if(d.source.isStartNode) {
- translate = "translate(" + (d.target.y + d.source.y + rootW) / 2 + "," + ((d.target.x + 10 + rootH/2) + (d.source.x + nodeH/2)) / 2 + ")";
- }
- else {
- translate = "translate(" + (d.target.y + d.source.y + nodeW) / 2 + "," + (d.target.x + d.source.x + nodeH) / 2 + ")";
- }
- return translate;
- })
- .attr("d", d3.svg.symbol()
- .size(60)
- .type("cross")
- )
- .style("display", function(d) { return (d.source.placeholder || d.target.placeholder || scope.canAddWorkflowJobTemplate === false) ? "none" : null; })
- .call(add_node_between)
- .on("mouseover", function(d) {
- 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);
- })
- .on("mouseout", function(d){
- 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);
- });
-
- link.exit().remove();
-
- // Transition nodes and links to their new positions.
- let t = baseSvg.transition();
-
- t.selectAll(".nodeCircle")
- .style("display", function(d) { return d.placeholder || scope.canAddWorkflowJobTemplate === false ? "none" : null; });
-
- t.selectAll(".nodeAddCross")
- .style("display", function(d) { return d.placeholder || scope.canAddWorkflowJobTemplate === false ? "none" : null; });
-
- t.selectAll(".removeCircle")
- .style("display", function(d) { return (d.canDelete === false || d.placeholder || scope.canAddWorkflowJobTemplate === false) ? "none" : null; });
-
- t.selectAll(".nodeRemoveCross")
- .style("display", function(d) { return (d.canDelete === false || d.placeholder || scope.canAddWorkflowJobTemplate === false) ? "none" : null; });
-
- t.selectAll(".linkPath")
- .attr("class", function(d) {
- return (d.source.placeholder || d.target.placeholder) ? "linkPath placeholder" : "linkPath";
+ t.selectAll(".linkCircle")
+ .style("display", function(d) { return (d.source.placeholder || d.target.placeholder || scope.canAddWorkflowJobTemplate === false) ? "none" : null; })
+ .attr("cx", function(d) {
+ return (d.source.isStartNode) ? (d.target.y + d.source.y + rootW) / 2 : (d.target.y + d.source.y + nodeW) / 2;
})
- .attr("d", lineData)
+ .attr("cy", function(d) {
+ 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")
+ .style("display", function(d) { return (d.source.placeholder || d.target.placeholder || scope.canAddWorkflowJobTemplate === false) ? "none" : null; })
+ .attr("transform", function(d) {
+ let translate;
+ 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 + ")";
+ }
+ else {
+ translate = "translate(" + (d.target.y + d.source.y + nodeW) / 2 + "," + (d.target.x + d.source.x + nodeH) / 2 + ")";
+ }
+ return translate;
+ });
+
+ t.selectAll(".rect")
.attr('stroke', function(d) {
- if(d.target.edgeType) {
- if(d.target.edgeType === "failure") {
- return "#d9534f";
- }
- else if(d.target.edgeType === "success") {
+ if(d.job && d.job.status) {
+ if(d.job.status === "successful"){
return "#5cb85c";
}
- else if(d.target.edgeType === "always"){
- return "#337ab7";
+ else if (d.job.status === "failed" || d.job.status === "error" || d.job.status === "cancelled") {
+ return "#d9534f";
+ }
+ else {
+ return "#D7D7D7";
}
}
else {
return "#D7D7D7";
}
+ })
+ .attr("class", function(d) {
+ return d.placeholder ? "rect placeholder" : "rect";
+ });
+
+ t.selectAll(".node")
+ .attr("parent", function(d){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 + ")"; });
+
+ t.selectAll(".WorkflowChart-nodeTypeCircle")
+ .style("display", function(d) { return d.unifiedJobTemplate && (d.unifiedJobTemplate.type === "project" || d.unifiedJobTemplate.unified_job_type === "project_update" || d.unifiedJobTemplate.type === "inventory_source" || d.unifiedJobTemplate.unified_job_type === "inventory_update" ) ? null : "none"; });
+
+ t.selectAll(".WorkflowChart-nodeTypeLetter")
+ .text(function (d) {
+ return (d.unifiedJobTemplate && (d.unifiedJobTemplate.type === "project" || d.unifiedJobTemplate.unified_job_type === "project_update")) ? "P" : (d.unifiedJobTemplate && (d.unifiedJobTemplate.type === "inventory_source" || d.unifiedJobTemplate.unified_job_type === "inventory_update") ? "I" : "");
+ })
+ .style("display", function(d) { return d.unifiedJobTemplate && (d.unifiedJobTemplate.type === "project" || d.unifiedJobTemplate.unified_job_type === "project_update" || d.unifiedJobTemplate.type === "inventory_source" || d.unifiedJobTemplate.unified_job_type === "inventory_update") ? null : "none"; });
+
+ t.selectAll(".WorkflowChart-nodeStatus")
+ .attr("class", function(d) {
+
+ let statusClass = "WorkflowChart-nodeStatus ";
+
+ if(d.job){
+ switch(d.job.status) {
+ case "pending":
+ statusClass += "workflowChart-nodeStatus--running";
+ break;
+ case "waiting":
+ statusClass += "workflowChart-nodeStatus--running";
+ break;
+ case "running":
+ statusClass += "workflowChart-nodeStatus--running";
+ break;
+ case "successful":
+ statusClass += "workflowChart-nodeStatus--success";
+ break;
+ case "failed":
+ statusClass += "workflowChart-nodeStatus--failed";
+ break;
+ case "error":
+ statusClass += "workflowChart-nodeStatus--failed";
+ break;
+ }
+ }
+
+ return statusClass;
+ })
+ .style("display", function(d) { return d.job && d.job.status ? null : "none"; })
+ .transition()
+ .duration(0)
+ .attr("r", 6)
+ .each(function(d) {
+ if(d.job && d.job.status && (d.job.status === "pending" || d.job.status === "waiting" || d.job.status === "running")) {
+ // Pulse the circle
+ var circle = d3.select(this);
+ (function repeat() {
+ circle = circle.transition()
+ .duration(2000)
+ .attr("r", 6)
+ .transition()
+ .duration(2000)
+ .attr("r", 0)
+ .ease('sine')
+ .each("end", repeat);
+ })();
+ }
});
- t.selectAll(".linkCircle")
- .style("display", function(d) { return (d.source.placeholder || d.target.placeholder || scope.canAddWorkflowJobTemplate === false) ? "none" : null; })
- .attr("cx", function(d) {
- return (d.source.isStartNode) ? (d.target.y + d.source.y + rootW) / 2 : (d.target.y + d.source.y + nodeW) / 2;
- })
- .attr("cy", function(d) {
- return (d.source.isStartNode) ? ((d.target.x + 10 + rootH/2) + (d.source.x + nodeH/2)) / 2 : (d.target.x + d.source.x + nodeH) / 2;
- });
+ t.selectAll(".WorkflowChart-nameText")
+ .attr("x", function(d){ 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"; })
+ .text(function (d) {
+ return (d.unifiedJobTemplate && d.unifiedJobTemplate.name) ? wrap(d.unifiedJobTemplate.name) : "";
+ });
- t.selectAll(".linkCross")
- .style("display", function(d) { return (d.source.placeholder || d.target.placeholder || scope.canAddWorkflowJobTemplate === false) ? "none" : null; })
- .attr("transform", function(d) {
- let translate;
- if(d.source.isStartNode) {
- translate = "translate(" + (d.target.y + d.source.y + rootW) / 2 + "," + ((d.target.x + 10 + rootH/2) + (d.source.x + nodeH/2)) / 2 + ")";
- }
- else {
- translate = "translate(" + (d.target.y + d.source.y + nodeW) / 2 + "," + (d.target.x + d.source.x + nodeH) / 2 + ")";
- }
- return translate;
- });
+ t.selectAll(".WorkflowChart-detailsLink")
+ .style("display", function(d){ return d.job && d.job.status && d.job.id ? null : "none"; });
- t.selectAll(".rect")
- .attr('stroke', function(d) {
- if(d.job && d.job.status) {
- if(d.job.status === "successful"){
- return "#5cb85c";
- }
- else if (d.job.status === "failed" || d.job.status === "error" || d.job.status === "cancelled") {
- return "#d9534f";
- }
- else {
- return "#D7D7D7";
- }
- }
- else {
- return "#D7D7D7";
- }
- })
- .attr("class", function(d) {
- return d.placeholder ? "rect placeholder" : "rect";
- });
+ t.selectAll(".WorkflowChart-incompleteText")
+ .style("display", function(d){ return d.unifiedJobTemplate || d.placeholder ? "none" : null; });
- t.selectAll(".node")
- .attr("parent", function(d){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 + ")"; });
+ t.selectAll(".WorkflowChart-conflictText")
+ .style("display", function(d) { return (d.edgeConflict && !d.placeholder) ? null : "none"; });
- t.selectAll(".WorkflowChart-nodeTypeCircle")
- .style("display", function(d) { return d.unifiedJobTemplate && (d.unifiedJobTemplate.type === "project" || d.unifiedJobTemplate.unified_job_type === "project_update" || d.unifiedJobTemplate.type === "inventory_source" || d.unifiedJobTemplate.unified_job_type === "inventory_update" ) ? null : "none"; });
+ t.selectAll(".WorkflowChart-activeNode")
+ .style("display", function(d) { return d.isActiveEdit ? null : "none"; });
- t.selectAll(".WorkflowChart-nodeTypeLetter")
- .text(function (d) {
- return (d.unifiedJobTemplate && (d.unifiedJobTemplate.type === "project" || d.unifiedJobTemplate.unified_job_type === "project_update")) ? "P" : (d.unifiedJobTemplate && (d.unifiedJobTemplate.type === "inventory_source" || d.unifiedJobTemplate.unified_job_type === "inventory_update") ? "I" : "");
- })
- .style("display", function(d) { return d.unifiedJobTemplate && (d.unifiedJobTemplate.type === "project" || d.unifiedJobTemplate.unified_job_type === "project_update" || d.unifiedJobTemplate.type === "inventory_source" || d.unifiedJobTemplate.unified_job_type === "inventory_update") ? null : "none"; });
-
- t.selectAll(".WorkflowChart-nodeStatus")
- .attr("class", function(d) {
-
- let statusClass = "WorkflowChart-nodeStatus ";
-
- if(d.job){
- switch(d.job.status) {
- case "pending":
- statusClass += "workflowChart-nodeStatus--running";
- break;
- case "waiting":
- statusClass += "workflowChart-nodeStatus--running";
- break;
- case "running":
- statusClass += "workflowChart-nodeStatus--running";
- break;
- case "successful":
- statusClass += "workflowChart-nodeStatus--success";
- break;
- case "failed":
- statusClass += "workflowChart-nodeStatus--failed";
- break;
- case "error":
- statusClass += "workflowChart-nodeStatus--failed";
- break;
- }
- }
-
- return statusClass;
- })
- .style("display", function(d) { return d.job && d.job.status ? null : "none"; })
- .transition()
- .duration(0)
- .attr("r", 6)
- .each(function(d) {
- if(d.job && d.job.status && (d.job.status === "pending" || d.job.status === "waiting" || d.job.status === "running")) {
- // Pulse the circle
- var circle = d3.select(this);
- (function repeat() {
- circle = circle.transition()
- .duration(2000)
- .attr("r", 6)
- .transition()
- .duration(2000)
- .attr("r", 0)
- .ease('sine')
- .each("end", repeat);
- })();
+ t.selectAll(".WorkflowChart-elapsed")
+ .style("display", function(d) { return (d.job && d.job.elapsed) ? null : "none"; });
+ }
+ else if(!scope.watchDimensionsSet){
+ scope.watchDimensionsSet = scope.$watch('dimensionsSet', function(){
+ if(scope.dimensionsSet) {
+ scope.watchDimensionsSet();
+ scope.watchDimensionsSet = null;
+ update();
}
});
-
- t.selectAll(".WorkflowChart-nameText")
- .attr("x", function(d){ 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"; })
- .text(function (d) {
- return (d.unifiedJobTemplate && d.unifiedJobTemplate.name) ? wrap(d.unifiedJobTemplate.name) : "";
- });
-
- t.selectAll(".WorkflowChart-detailsLink")
- .style("display", function(d){ return d.job && d.job.status && d.job.id ? null : "none"; });
-
- t.selectAll(".WorkflowChart-incompleteText")
- .style("display", function(d){ 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")
- .style("display", function(d) { return d.isActiveEdit ? null : "none"; });
-
- t.selectAll(".WorkflowChart-elapsed")
- .style("display", function(d) { return (d.job && d.job.elapsed) ? null : "none"; });
-
+ }
}
function add_node() {
@@ -809,6 +872,29 @@ export default [ '$state','moment',
}
});
+ function onResize(){
+ let dimensions = calcAvailableScreenSpace();
+
+ $('.WorkflowMaker-chart').css("width", dimensions.width);
+ $('.WorkflowMaker-chart').css("height", dimensions.height);
+ }
+
+ function cleanUpResize() {
+ angular.element($window).off('resize', onResize);
+ }
+
+ if(scope.mode === 'details') {
+ angular.element($window).on('resize', onResize);
+ scope.$on('$destroy', cleanUpResize);
+ }
+ else {
+ scope.$on('workflowMakerModalResized', function(){
+ let dimensions = calcAvailableScreenSpace();
+
+ $('.WorkflowMaker-chart').css("width", dimensions.width);
+ $('.WorkflowMaker-chart').css("height", dimensions.height);
+ });
+ }
}
};
}];
diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.controller.js b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.controller.js
index e4cbfcd134..7fcf27cc6c 100644
--- a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.controller.js
+++ b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.controller.js
@@ -888,6 +888,10 @@ export default ['$scope', 'WorkflowService', 'generateList', 'TemplateList', 'Pr
});
};
+ $scope.$on('WorkflowDialogReady', function(){
+ $scope.modalOpen = true;
+ });
+
init();
}
diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.directive.js b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.directive.js
index 54c1e526fd..bfac9c1469 100644
--- a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.directive.js
+++ b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.directive.js
@@ -6,8 +6,8 @@
import workflowMakerController from './workflow-maker.controller';
-export default ['templateUrl', 'CreateDialog', 'Wait', '$state',
- function(templateUrl, CreateDialog, Wait, $state) {
+export default ['templateUrl', 'CreateDialog', 'Wait', '$state', '$window',
+ function(templateUrl, CreateDialog, Wait, $state, $window) {
return {
scope: {
workflowJobTemplateObj: '=',
@@ -17,11 +17,17 @@ export default ['templateUrl', 'CreateDialog', 'Wait', '$state',
templateUrl: templateUrl('templates/workflows/workflow-maker/workflow-maker'),
controller: workflowMakerController,
link: function(scope) {
+
+ let availableHeight = $(window).height(),
+ availableWidth = $(window).width(),
+ minimumWidth = 1300,
+ minimumHeight = 550;
+
CreateDialog({
id: 'workflow-modal-dialog',
scope: scope,
- width: 1400,
- height: 720,
+ width: availableWidth > minimumWidth ? availableWidth : minimumWidth,
+ height: availableHeight > minimumHeight ? availableHeight : minimumHeight,
draggable: false,
dialogClass: 'SurveyMaker-dialog',
position: ['center', 20],
@@ -34,6 +40,10 @@ export default ['templateUrl', 'CreateDialog', 'Wait', '$state',
// Let the modal height be variable based on the content
// and set a uniform padding
$('#workflow-modal-dialog').css({ 'padding': '20px' });
+ $('#workflow-modal-dialog').parent('.ui-dialog').height(availableHeight > minimumHeight ? availableHeight : minimumHeight);
+ $('#workflow-modal-dialog').parent('.ui-dialog').width(availableWidth > minimumWidth ? availableWidth : minimumWidth);
+ $('#workflow-modal-dialog').outerHeight(availableHeight > minimumHeight ? availableHeight : minimumHeight);
+ $('#workflow-modal-dialog').outerWidth(availableWidth > minimumWidth ? availableWidth : minimumWidth);
},
_allowInteraction: function(e) {
@@ -55,6 +65,24 @@ export default ['templateUrl', 'CreateDialog', 'Wait', '$state',
$state.go('^');
};
+
+ function onResize(){
+ availableHeight = $(window).height();
+ availableWidth = $(window).width();
+ $('#workflow-modal-dialog').parent('.ui-dialog').height(availableHeight > minimumHeight ? availableHeight : minimumHeight);
+ $('#workflow-modal-dialog').parent('.ui-dialog').width(availableWidth > minimumWidth ? availableWidth : minimumWidth);
+ $('#workflow-modal-dialog').outerHeight(availableHeight > minimumHeight ? availableHeight : minimumHeight);
+ $('#workflow-modal-dialog').outerWidth(availableWidth > minimumWidth ? availableWidth : minimumWidth);
+
+ scope.$broadcast('workflowMakerModalResized');
+ }
+
+ function cleanUpResize() {
+ angular.element($window).off('resize', onResize);
+ }
+
+ angular.element($window).on('resize', onResize);
+ scope.$on('$destroy', cleanUpResize);
}
};
}
diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.partial.html b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.partial.html
index 099d03ae10..14f09234aa 100644
--- a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.partial.html
+++ b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.partial.html
@@ -64,7 +64,7 @@
-
+
{{(workflowMakerFormConfig.nodeMode === 'edit' && nodeBeingEdited) ? ((nodeBeingEdited.unifiedJobTemplate && nodeBeingEdited.unifiedJobTemplate.name) ? nodeBeingEdited.unifiedJobTemplate.name : "EDIT TEMPLATE") : "ADD A TEMPLATE"}}