diff --git a/awx/ui/client/features/templates/templates.strings.js b/awx/ui/client/features/templates/templates.strings.js
index ffc36b32f0..8b0713ae4c 100644
--- a/awx/ui/client/features/templates/templates.strings.js
+++ b/awx/ui/client/features/templates/templates.strings.js
@@ -110,9 +110,12 @@ function TemplatesStrings (BaseString) {
ON_SUCCESS: t.s('On Success'),
ON_FAILURE: t.s('On Failure'),
ALWAYS: t.s('Always'),
+ PAUSE: t.s('Wait For Approval'),
+ JOB_TEMPLATE: t.s('Job Template'),
PROJECT_SYNC: t.s('Project Sync'),
INVENTORY_SYNC: t.s('Inventory Sync'),
WORKFLOW: t.s('Workflow'),
+ TEMPLATE: t.s('Template'),
WARNING: t.s('Warning'),
TOTAL_NODES: t.s('TOTAL NODES'),
ADD_A_NODE: t.s('ADD A NODE'),
@@ -145,7 +148,7 @@ function TemplatesStrings (BaseString) {
EXIT: t.s('EXIT'),
CANCEL: t.s('CANCEL'),
SAVE_AND_EXIT: t.s('SAVE & EXIT'),
- PAUSE_NODE: t.s('Pause Node')
+ APPROVAL: t.s('Approval')
};
}
diff --git a/awx/ui/client/src/templates/main.js b/awx/ui/client/src/templates/main.js
index 70d8b24b00..4f301cdb91 100644
--- a/awx/ui/client/src/templates/main.js
+++ b/awx/ui/client/src/templates/main.js
@@ -12,6 +12,7 @@ import workflowEdit from './workflows/edit-workflow/main';
import labels from './labels/main';
import prompt from './prompt/main';
import workflowChart from './workflows/workflow-chart/main';
+import workflowKey from './workflows/workflow-key/main';
import workflowMaker from './workflows/workflow-maker/main';
import workflowControls from './workflows/workflow-controls/main';
import WorkflowForm from './workflows.form';
@@ -31,7 +32,7 @@ import {
export default
angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, prompt.name, workflowAdd.name, workflowEdit.name,
- workflowChart.name, workflowMaker.name, workflowControls.name
+ workflowChart.name, workflowKey.name, workflowMaker.name, workflowControls.name
])
.service('TemplatesService', templatesService)
.factory('WorkflowForm', WorkflowForm)
@@ -499,320 +500,7 @@ angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, p
views: {
'modal': {
template: ``
- },
- 'jobTemplateList@templates.editWorkflowJobTemplate.workflowMaker': {
- templateProvider: function(WorkflowMakerJobTemplateList, generateList) {
-
- let html = generateList.build({
- list: WorkflowMakerJobTemplateList,
- input_type: 'radio',
- mode: 'lookup'
- });
- return html;
- },
- // $scope encapsulated in this controller will be a initialized as child of 'modal' $scope, because of element hierarchy
- controller: ['$scope', 'WorkflowMakerJobTemplateList', 'JobTemplateDataset',
- function($scope, list, Dataset) {
-
- init();
-
- function init() {
- $scope.list = list;
- $scope[`${list.iterator}_dataset`] = Dataset.data;
- $scope[list.name] = $scope[`${list.iterator}_dataset`].results;
-
- $scope.$watch('wf_maker_templates', function(){
- if($scope.selectedTemplate){
- $scope.wf_maker_templates.forEach(function(row, i) {
- if(row.id === $scope.selectedTemplate.id) {
- $scope.wf_maker_templates[i].checked = 1;
- }
- else {
- $scope.wf_maker_templates[i].checked = 0;
- }
- });
- }
- });
- }
-
- $scope.toggle_row = function(selectedRow) {
- if ($scope.workflowJobTemplateObj.summary_fields.user_capabilities.edit) {
- $scope.wf_maker_templates.forEach(function(row, i) {
- if (row.id === selectedRow.id) {
- $scope.wf_maker_templates[i].checked = 1;
- $scope.selection[list.iterator] = {
- id: row.id,
- name: row.name
- };
-
- $scope.templateManuallySelected(row);
- }
- });
- }
- };
-
- $scope.$watch('selectedTemplate', () => {
- $scope.wf_maker_templates.forEach(function(row, i) {
- if(_.has($scope, 'selectedTemplate.id') && row.id === $scope.selectedTemplate.id) {
- $scope.wf_maker_templates[i].checked = 1;
- }
- else {
- $scope.wf_maker_templates[i].checked = 0;
- }
- });
- });
-
- $scope.$watch('activeTab', () => {
- if(!$scope.activeTab || $scope.activeTab !== "jobs") {
- $scope.wf_maker_templates.forEach(function(row, i) {
- $scope.wf_maker_templates[i].checked = 0;
- });
- }
- });
-
- $scope.$on('clearWorkflowLists', function() {
- $scope.wf_maker_templates.forEach(function(row, i) {
- $scope.wf_maker_templates[i].checked = 0;
- });
- });
- }
- ]
- },
- 'inventorySyncList@templates.editWorkflowJobTemplate.workflowMaker': {
- templateProvider: function(WorkflowInventorySourcesList, generateList) {
- let html = generateList.build({
- list: WorkflowInventorySourcesList,
- input_type: 'radio',
- mode: 'lookup'
- });
- return html;
- },
- // encapsulated $scope in this controller will be a initialized as child of 'modal' $scope, because of element hierarchy
- controller: ['$scope', 'WorkflowInventorySourcesList', 'InventorySourcesDataset',
- function($scope, list, Dataset) {
-
- init();
-
- function init() {
- $scope.list = list;
- $scope[`${list.iterator}_dataset`] = Dataset.data;
- $scope[list.name] = $scope[`${list.iterator}_dataset`].results;
-
- $scope.$watch('wf_maker_inventory_sources', function(){
- if($scope.selectedTemplate){
- $scope.wf_maker_inventory_sources.forEach(function(row, i) {
- if(row.id === $scope.selectedTemplate.id) {
- $scope.wf_maker_inventory_sources[i].checked = 1;
- }
- else {
- $scope.wf_maker_inventory_sources[i].checked = 0;
- }
- });
- }
- });
- }
-
- $scope.toggle_row = function(selectedRow) {
- if ($scope.workflowJobTemplateObj.summary_fields.user_capabilities.edit) {
- $scope.wf_maker_inventory_sources.forEach(function(row, i) {
- if (row.id === selectedRow.id) {
- $scope.wf_maker_inventory_sources[i].checked = 1;
- $scope.selection[list.iterator] = {
- id: row.id,
- name: row.name
- };
-
- $scope.templateManuallySelected(row);
- }
- });
- }
- };
-
- $scope.$watch('selectedTemplate', () => {
- $scope.wf_maker_inventory_sources.forEach(function(row, i) {
- if(_.hasIn($scope, 'selectedTemplate.id') && row.id === $scope.selectedTemplate.id) {
- $scope.wf_maker_inventory_sources[i].checked = 1;
- }
- else {
- $scope.wf_maker_inventory_sources[i].checked = 0;
- }
- });
- });
-
- $scope.$watch('activeTab', () => {
- if(!$scope.activeTab || $scope.activeTab !== "inventory_sync") {
- $scope.wf_maker_inventory_sources.forEach(function(row, i) {
- $scope.wf_maker_inventory_sources[i].checked = 0;
- });
- }
- });
-
- $scope.$on('clearWorkflowLists', function() {
- $scope.wf_maker_inventory_sources.forEach(function(row, i) {
- $scope.wf_maker_inventory_sources[i].checked = 0;
- });
- });
- }
- ]
- },
- 'projectSyncList@templates.editWorkflowJobTemplate.workflowMaker': {
- templateProvider: function(WorkflowProjectList, generateList) {
- let html = generateList.build({
- list: WorkflowProjectList,
- input_type: 'radio',
- mode: 'lookup'
- });
- return html;
- },
- // encapsulated $scope in this controller will be a initialized as child of 'modal' $scope, because of element hierarchy
- controller: ['$scope', 'WorkflowProjectList', 'ProjectDataset',
- function($scope, list, Dataset) {
-
- init();
-
- function init() {
- $scope.list = list;
- $scope[`${list.iterator}_dataset`] = Dataset.data;
- $scope[list.name] = $scope[`${list.iterator}_dataset`].results;
-
- $scope.$watch('wf_maker_projects', function(){
- if($scope.selectedTemplate){
- $scope.wf_maker_projects.forEach(function(row, i) {
- if(row.id === $scope.selectedTemplate.id) {
- $scope.wf_maker_projects[i].checked = 1;
- }
- else {
- $scope.wf_maker_projects[i].checked = 0;
- }
- });
- }
- });
- }
-
- $scope.toggle_row = function(selectedRow) {
- if ($scope.workflowJobTemplateObj.summary_fields.user_capabilities.edit) {
- $scope.wf_maker_projects.forEach(function(row, i) {
- if (row.id === selectedRow.id) {
- $scope.wf_maker_projects[i].checked = 1;
- $scope.selection[list.iterator] = {
- id: row.id,
- name: row.name
- };
-
- $scope.templateManuallySelected(row);
- }
- });
- }
- };
-
- $scope.$watch('selectedTemplate', () => {
- $scope.wf_maker_projects.forEach(function(row, i) {
- if(_.hasIn($scope, 'selectedTemplate.id') && row.id === $scope.selectedTemplate.id) {
- $scope.wf_maker_projects[i].checked = 1;
- }
- else {
- $scope.wf_maker_projects[i].checked = 0;
- }
- });
- });
-
- $scope.$watch('activeTab', () => {
- if(!$scope.activeTab || $scope.activeTab !== "project_sync") {
- $scope.wf_maker_projects.forEach(function(row, i) {
- $scope.wf_maker_projects[i].checked = 0;
- });
- }
- });
-
- $scope.$on('clearWorkflowLists', function() {
- $scope.wf_maker_projects.forEach(function(row, i) {
- $scope.wf_maker_projects[i].checked = 0;
- });
- });
- }
- ]
}
- },
- resolve: {
- JobTemplateDataset: ['WorkflowMakerJobTemplateList', 'QuerySet', '$stateParams', 'GetBasePath',
- (list, qs, $stateParams, GetBasePath) => {
- let path = GetBasePath(list.basePath);
- return qs.search(path, $stateParams[`${list.iterator}_search`]);
- }
- ],
- ProjectDataset: ['WorkflowProjectList', 'QuerySet', '$stateParams', 'GetBasePath',
- (list, qs, $stateParams, GetBasePath) => {
- let path = GetBasePath(list.basePath);
- return qs.search(path, $stateParams[`${list.iterator}_search`]);
- }
- ],
- InventorySourcesDataset: ['InventorySourcesList', 'QuerySet', '$stateParams', 'GetBasePath',
- (list, qs, $stateParams, GetBasePath) => {
- let path = GetBasePath(list.basePath);
- return qs.search(path, $stateParams[`${list.iterator}_search`]);
- }
- ],
- WorkflowMakerJobTemplateList: ['TemplateList', 'i18n',
- (TemplateList, i18n) => {
- let list = _.cloneDeep(TemplateList);
- delete list.actions;
- delete list.fields.type;
- delete list.fields.description;
- delete list.fields.smart_status;
- delete list.fields.labels;
- delete list.fieldActions;
- list.name = 'wf_maker_templates';
- list.iterator = 'wf_maker_template';
- list.fields.name.columnClass = "col-md-8";
- list.fields.name.tag = i18n._('WORKFLOW');
- list.fields.name.showTag = "{{wf_maker_template.type === 'workflow_job_template'}}";
- list.disableRow = "{{ !workflowJobTemplateObj.summary_fields.user_capabilities.edit }}";
- list.disableRowValue = '!workflowJobTemplateObj.summary_fields.user_capabilities.edit';
- list.basePath = 'unified_job_templates';
- list.fields.info = {
- ngInclude: "'/static/partials/job-template-details.html'",
- type: 'template',
- columnClass: 'col-md-3',
- infoHeaderClass: 'col-md-3',
- label: '',
- nosort: true
- };
- list.maxVisiblePages = 5;
- list.searchBarFullWidth = true;
-
- return list;
- }
- ],
- WorkflowProjectList: ['ProjectList',
- (ProjectList) => {
- let list = _.cloneDeep(ProjectList);
- delete list.fields.status;
- delete list.fields.scm_type;
- delete list.fields.last_updated;
- list.name = 'wf_maker_projects';
- list.iterator = 'wf_maker_project';
- list.fields.name.columnClass = "col-md-11";
- list.maxVisiblePages = 5;
- list.searchBarFullWidth = true;
- list.disableRow = "{{ !workflowJobTemplateObj.summary_fields.user_capabilities.edit }}";
- list.disableRowValue = '!workflowJobTemplateObj.summary_fields.user_capabilities.edit';
-
- return list;
- }
- ],
- WorkflowInventorySourcesList: ['InventorySourcesList',
- (InventorySourcesList) => {
- let list = _.cloneDeep(InventorySourcesList);
- list.name = 'wf_maker_inventory_sources';
- list.iterator = 'wf_maker_inventory_source';
- list.maxVisiblePages = 5;
- list.searchBarFullWidth = true;
- list.disableRow = "{{ !workflowJobTemplateObj.summary_fields.user_capabilities.edit }}";
- list.disableRowValue = '!workflowJobTemplateObj.summary_fields.user_capabilities.edit';
-
- return list;
- }
- ]
}
};
diff --git a/awx/ui/client/src/templates/templates.service.js b/awx/ui/client/src/templates/templates.service.js
index ef718064fd..f97899ddd8 100644
--- a/awx/ui/client/src/templates/templates.service.js
+++ b/awx/ui/client/src/templates/templates.service.js
@@ -291,13 +291,13 @@ export default ['Rest', 'GetBasePath', '$q', 'NextPage', function(Rest, GetBaseP
Rest.setUrl(url);
return Rest.post(params.data);
},
- createApprovalTemplate: (params) => {
- params = params || {};
- Rest.setUrl(GetBasePath('workflow_approval_templates'));
- return Rest.post(params);
+ createApprovalTemplate: ({url, data}) => {
+ data = data || {};
+ Rest.setUrl(url);
+ return Rest.post(data);
},
patchApprovalTemplate: ({id, data}) => {
- Rest.setUrl(`${GetBasePath('workflow_approval_templates')}/${id}`);
+ Rest.setUrl(`/api/v2/workflow_approval_templates/${id}`);
return Rest.patch(data);
}
};
diff --git a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.block.less b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.block.less
index c1719b07ac..a9c1550d60 100644
--- a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.block.less
+++ b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.block.less
@@ -117,6 +117,7 @@
.WorkflowChart-nodeTypeLetter {
fill: @default-bg;
+ font-size: 10px;
}
.WorkflowChart-nodeStatus--running {
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 8156407b5b..e4c64231d5 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
@@ -109,11 +109,11 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
// TODO: this function is hacky and we need to come up with a better solution
// see: http://stackoverflow.com/questions/15975440/add-ellipses-to-overflowing-text-in-svg#answer-27723752
const wrap = (text) => {
- if(text && text.length > maxNodeTextLength) {
- return text.substring(0,maxNodeTextLength) + '...';
+ if(text) {
+ return text.length > maxNodeTextLength ? text.substring(0,maxNodeTextLength) + '...' : text;
}
else {
- return text;
+ return '';
}
};
@@ -143,7 +143,7 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
translation = [translation[0], translation[1] + ((windowHeight/2 - rootH/2 - startNodeOffsetY)*scale)];
- svgGroup.attr("transform", "translate(" + translation + ")scale(" + scale + ")");
+ svgGroup.attr("transform", `translate(${translation})scale(${scale})`);
scope.workflowZoomed({
zoom: scale
@@ -160,7 +160,7 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
translateX = unscaledOffsetX*scale - ((scale*windowWidth)-windowWidth)/2,
translateY = unscaledOffsetY*scale - ((scale*windowHeight)-windowHeight)/2;
- svgGroup.attr("transform", "translate(" + [translateX, translateY + ((windowHeight/2 - rootH/2 - startNodeOffsetY)*scale)] + ")scale(" + scale + ")");
+ svgGroup.attr("transform", `translate(${[translateX, translateY + ((windowHeight/2 - rootH/2 - startNodeOffsetY)*scale)]})scale(${scale})`);
zoomObj.scale(scale);
zoomObj.translate([translateX, translateY]);
};
@@ -178,27 +178,32 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
translateX = translateCoords[0];
translateY = direction === 'up' ? translateCoords[1] - distance : translateCoords[1] + distance;
}
- svgGroup.attr("transform", "translate(" + translateX + "," + (translateY + ((windowHeight/2 - rootH/2 - startNodeOffsetY)*scale)) + ")scale(" + scale + ")");
+ svgGroup.attr("transform", `translate(${translateX},${(translateY + ((windowHeight/2 - rootH/2 - startNodeOffsetY)*scale))})scale(${scale})`);
zoomObj.translate([translateX, translateY]);
};
const resetZoomAndPan = () => {
- svgGroup.attr("transform", "translate(0," + (windowHeight/2 - rootH/2 - startNodeOffsetY) + ")scale(" + 1 + ")");
+ svgGroup.attr("transform", `translate(0,${(windowHeight/2 - rootH/2 - startNodeOffsetY)})scale(1)`);
// Update the zoomObj
zoomObj.scale(1);
zoomObj.translate([0,0]);
};
const zoomToFitChart = () => {
- let graphDimensions = d3.select('#aw-workflow-chart-g')[0][0].getBoundingClientRect(),
+ const startNodeWidth = scope.mode === 'details' ? 25 : 60,
+ graphDimensions = d3.select('#aw-workflow-chart-g')[0][0].getBoundingClientRect(),
availableScreenSpace = calcAvailableScreenSpace(),
- currentZoomValue = zoomObj.scale(),
- unscaledH = graphDimensions.height/currentZoomValue,
+ currentZoomValue = zoomObj.scale();
+
+ // For some reason the start node isn't accounted for in the width... add it
+ graphDimensions.width = graphDimensions.width + (startNodeWidth*currentZoomValue);
+
+ const unscaledH = graphDimensions.height/currentZoomValue,
unscaledW = graphDimensions.width/currentZoomValue,
scaleNeededForMaxHeight = (availableScreenSpace.height)/unscaledH,
scaleNeededForMaxWidth = (availableScreenSpace.width)/unscaledW,
lowerScale = Math.min(scaleNeededForMaxHeight, scaleNeededForMaxWidth),
- scaleToFit = lowerScale < 0.5 ? 0.5 : (lowerScale > 2 ? 2 : Math.floor(lowerScale * 10)/10);
+ scaleToFit = lowerScale < 0.5 ? 0.5 : (lowerScale > 2 ? 2 : Math.floor(lowerScale * 1000)/1000);
manualZoom(scaleToFit*100);
@@ -206,100 +211,99 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
zoom: scaleToFit
});
- svgGroup.attr("transform", "translate(0," + (windowHeight/2 - (nodeH*scaleToFit/2)) + ")scale(" + scaleToFit + ")");
+ svgGroup.attr("transform", `translate(0, ${(windowHeight/2 - (nodeH*scaleToFit/2))})scale(${scaleToFit})`);
zoomObj.translate([0, windowHeight/2 - (nodeH*scaleToFit/2) - ((windowHeight/2 - rootH/2 - startNodeOffsetY)*scaleToFit)]);
};
+ const buildLinkTooltip = (d) => {
+ let edgeTypeLabel;
+ switch(d.edgeType) {
+ case "always":
+ edgeTypeLabel = TemplatesStrings.get('workflow_maker.ALWAYS');
+ break;
+ case "success":
+ edgeTypeLabel = TemplatesStrings.get('workflow_maker.ON_SUCCESS');
+ break;
+ case "failure":
+ edgeTypeLabel = TemplatesStrings.get('workflow_maker.ON_FAILURE');
+ break;
+ }
+ let linkInstructionText = !scope.readOnly ? TemplatesStrings.get('workflow_maker.EDIT_LINK_TOOLTIP') : TemplatesStrings.get('workflow_maker.VIEW_LINK_TOOLTIP');
+ let linkTooltip = svgGroup.append("g")
+ .attr("class", "WorkflowChart-tooltip");
+ const tipRef = linkTooltip.append("foreignObject")
+ // In order for this to work in FF a height of at least 1 must be present
+ .attr("width", 100)
+ .attr("height", 1)
+ .style("overflow", "visible")
+ .html(`
+
+
${TemplatesStrings.get('workflow_maker.RUN')}: ${edgeTypeLabel}
+
${linkInstructionText}
+
+ `);
+ const tipDimensions = tipRef.select('.WorkflowChart-tooltipContents').node().getBoundingClientRect();
+ let sourceNode = d3.select(`#node-${d.source.id}`);
+ const sourceNodeX = d3.transform(sourceNode.attr("transform")).translate[0];
+ const sourceNodeY = d3.transform(sourceNode.attr("transform")).translate[1];
+ let targetNode = d3.select(`#node-${d.target.id}`);
+ const targetNodeX = d3.transform(targetNode.attr("transform")).translate[0];
+ const targetNodeY = d3.transform(targetNode.attr("transform")).translate[1];
+ let xPos, yPos, arrowPoints;
+ const scaledHeight = tipDimensions.height/zoomObj.scale();
+
+ if (nodePositionMap[d.source.id].y === nodePositionMap[d.target.id].y) {
+ xPos = (sourceNodeX + nodeW + targetNodeX)/2 - 50;
+ yPos = sourceNodeY + nodeH/2 - scaledHeight - 20;
+ arrowPoints = {
+ pt1: {
+ x: xPos + 40,
+ y: yPos + scaledHeight
+ },
+ pt2: {
+ x: xPos + 60,
+ y: yPos + scaledHeight
+ },
+ pt3: {
+ x: xPos + 50,
+ y: yPos + scaledHeight + 10
+ }
+ };
+ } else {
+ xPos = (sourceNodeX + nodeW + targetNodeX)/2 - 120;
+ yPos = (sourceNodeY + (nodeH/2) + targetNodeY + (nodeH/2))/2 - (scaledHeight/2);
+ arrowPoints = {
+ pt1: {
+ x: xPos + 100,
+ y: yPos + (scaledHeight/2) - 10
+ },
+ pt2: {
+ x: xPos + 100,
+ y: yPos + (scaledHeight/2) + 10
+ },
+ pt3: {
+ x: xPos + 110,
+ y: yPos + (scaledHeight/2)
+ }
+ };
+ }
+
+ linkTooltip.append("polygon")
+ .attr("class", "WorkflowChart-tooltipArrow")
+ .attr("points", `${arrowPoints.pt1.x},${arrowPoints.pt1.y} ${arrowPoints.pt2.x},${arrowPoints.pt2.y} ${arrowPoints.pt3.x},${arrowPoints.pt3.y}`);
+
+ tipRef.attr('height', scaledHeight);
+ tipRef.attr("transform", `translate(${xPos},${yPos})`);
+ };
+
const updateGraph = () => {
if(scope.dimensionsSet) {
- const buildLinkTooltip = (d) => {
- let edgeTypeLabel;
- switch(d.edgeType) {
- case "always":
- edgeTypeLabel = TemplatesStrings.get('workflow_maker.ALWAYS');
- break;
- case "success":
- edgeTypeLabel = TemplatesStrings.get('workflow_maker.ON_SUCCESS');
- break;
- case "failure":
- edgeTypeLabel = TemplatesStrings.get('workflow_maker.ON_FAILURE');
- break;
- }
- let linkInstructionText = !scope.readOnly ? TemplatesStrings.get('workflow_maker.EDIT_LINK_TOOLTIP') : TemplatesStrings.get('workflow_maker.VIEW_LINK_TOOLTIP');
- let linkTooltip = svgGroup.append("g")
- .attr("class", "WorkflowChart-tooltip");
- const tipRef = linkTooltip.append("foreignObject")
- // In order for this to work in FF a height of at least 1 must be present
- .attr("width", 100)
- .attr("height", 1)
- .style("overflow", "visible")
- .html(function(){
- return `
-
${TemplatesStrings.get('workflow_maker.RUN')}: ${edgeTypeLabel}
-
${linkInstructionText}
-
`;
- });
- const tipDimensions = tipRef.select('.WorkflowChart-tooltipContents').node().getBoundingClientRect();
- let sourceNode = d3.select(`#node-${d.source.id}`);
- const sourceNodeX = d3.transform(sourceNode.attr("transform")).translate[0];
- const sourceNodeY = d3.transform(sourceNode.attr("transform")).translate[1];
- let targetNode = d3.select(`#node-${d.target.id}`);
- const targetNodeX = d3.transform(targetNode.attr("transform")).translate[0];
- const targetNodeY = d3.transform(targetNode.attr("transform")).translate[1];
- let xPos, yPos, arrowPoints;
- const scaledHeight = tipDimensions.height/zoomObj.scale();
-
- if (nodePositionMap[d.source.id].y === nodePositionMap[d.target.id].y) {
- xPos = (sourceNodeX + nodeW + targetNodeX)/2 - 50;
- yPos = sourceNodeY + nodeH/2 - scaledHeight - 20;
- arrowPoints = {
- pt1: {
- x: xPos + 40,
- y: yPos + scaledHeight
- },
- pt2: {
- x: xPos + 60,
- y: yPos + scaledHeight
- },
- pt3: {
- x: xPos + 50,
- y: yPos + scaledHeight + 10
- }
- };
- } else {
- xPos = (sourceNodeX + nodeW + targetNodeX)/2 - 120;
- yPos = (sourceNodeY + (nodeH/2) + targetNodeY + (nodeH/2))/2 - (scaledHeight/2);
- arrowPoints = {
- pt1: {
- x: xPos + 100,
- y: yPos + (scaledHeight/2) - 10
- },
- pt2: {
- x: xPos + 100,
- y: yPos + (scaledHeight/2) + 10
- },
- pt3: {
- x: xPos + 110,
- y: yPos + (scaledHeight/2)
- }
- };
- }
-
- linkTooltip.append("polygon")
- .attr("class", "WorkflowChart-tooltipArrow")
- .attr("points", function() {
- return `${arrowPoints.pt1.x},${arrowPoints.pt1.y} ${arrowPoints.pt2.x},${arrowPoints.pt2.y} ${arrowPoints.pt3.x},${arrowPoints.pt3.y}`;
- });
-
- tipRef.attr('height', scaledHeight);
- tipRef.attr("transform", `translate(${xPos},${yPos})`);
- };
-
let g = new dagre.graphlib.Graph();
g.setGraph({rankdir: 'LR', nodesep: 30, ranksep: 120});
- g.setDefaultEdgeLabel(function() { return {}; });
+ // This is needed for Dagre
+ g.setDefaultEdgeLabel(() => { return {}; });
scope.graphState.arrayOfNodesForChart.forEach((node) => {
if (node.id === 1) {
@@ -326,19 +330,19 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
});
let links = svgGroup.selectAll(".WorkflowChart-link")
- .data(scope.graphState.arrayOfLinksForChart, function(d) { return `${d.source.id}-${d.target.id}`; });
+ .data(scope.graphState.arrayOfLinksForChart, (d) => { return `${d.source.id}-${d.target.id}`; });
// Remove any stale links
links.exit().remove();
// Update existing links
baseSvg.selectAll(".WorkflowChart-link")
- .attr("id", function(d){return "link-" + d.source.id + "-" + d.target.id;});
+ .attr("id", (d) => `link-${d.source.id}-${d.target.id}`);
baseSvg.selectAll(".WorkflowChart-linkPath")
.transition()
.attr("d", lineData)
- .attr('stroke', function(d) {
+ .attr('stroke', (d) => {
let edgeType = d.edgeType;
if(edgeType) {
if(edgeType === "failure") {
@@ -357,8 +361,8 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
});
baseSvg.selectAll(".WorkflowChart-linkOverlay")
- .attr("id", function(d){return "link-" + d.source.id + "-" + d.target.id + "-overlay";})
- .attr("class", function(d) {
+ .attr("id", (d) => `link-${d.source.id}-${d.target.id}-overlay`)
+ .attr("class", (d) => {
let linkClasses = ["WorkflowChart-linkOverlay"];
if (
scope.graphState.linkBeingEdited &&
@@ -369,7 +373,7 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
}
return linkClasses.join(' ');
})
- .attr("points",function(d) {
+ .attr("points",(d) => {
let x1 = nodePositionMap[d.target.id].x;
let y1 = normalizeY(nodePositionMap[d.target.id].y) + (nodePositionMap[d.target.id].height/2);
let x2 = nodePositionMap[d.source.id].x + nodePositionMap[d.target.id].width;
@@ -387,12 +391,12 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
});
baseSvg.selectAll(".WorkflowChart-circleBetweenNodes")
- .attr("id", function(d){return "link-" + d.source.id + "-" + d.target.id + "-add";})
- .style("display", function(d) { return (d.edgeType === 'placeholder' || scope.graphState.isLinkMode || d.source.id === scope.graphState.nodeBeingAdded || d.target.id === scope.graphState.nodeBeingAdded || scope.readOnly) ? "none" : null; })
- .attr("cx", function(d) {
+ .attr("id", (d) => `link-${d.source.id}-${d.target.id}-add`)
+ .style("display", (d) => { return (d.edgeType === 'placeholder' || scope.graphState.isLinkMode || d.source.id === scope.graphState.nodeBeingAdded || d.target.id === scope.graphState.nodeBeingAdded || scope.readOnly) ? "none" : null; })
+ .attr("cx", (d) => {
return (nodePositionMap[d.source.id].x + nodePositionMap[d.source.id].width + nodePositionMap[d.target.id].x)/2;
})
- .attr("cy", function(d) {
+ .attr("cy", (d) => {
const normalizedSourceY = normalizeY(nodePositionMap[d.source.id].y);
const halfSourceHeight = nodePositionMap[d.source.id].height/2;
const normalizedTargetY = normalizeY(nodePositionMap[d.target.id].y);
@@ -408,8 +412,8 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
});
baseSvg.selectAll(".WorkflowChart-betweenNodesIcon")
- .style("display", function(d) { return (d.edgeType === 'placeholder' || scope.graphState.isLinkMode || d.source.id === scope.graphState.nodeBeingAdded || d.target.id === scope.graphState.nodeBeingAdded || scope.readOnly) ? "none" : null; })
- .attr("transform", function(d) {
+ .style("display", (d) => { return (d.edgeType === 'placeholder' || scope.graphState.isLinkMode || d.source.id === scope.graphState.nodeBeingAdded || d.target.id === scope.graphState.nodeBeingAdded || scope.readOnly) ? "none" : null; })
+ .attr("transform", (d) => {
let translate;
const normalizedSourceY = normalizeY(nodePositionMap[d.source.id].y);
@@ -423,17 +427,17 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
yPos = yPos + 4;
}
- translate = "translate(" + (nodePositionMap[d.source.id].x + nodePositionMap[d.source.id].width + nodePositionMap[d.target.id].x)/2 + "," + yPos + ")";
+ translate = `translate(${(nodePositionMap[d.source.id].x + nodePositionMap[d.source.id].width + nodePositionMap[d.target.id].x)/2}, ${yPos})`;
return translate;
});
// Add any new links
let linkEnter = links.enter().append("g")
.attr("class", "WorkflowChart-link")
- .attr("id", function(d){return "link-" + d.source.id + "-" + d.target.id;});
+ .attr("id", (d) => `link-${d.source.id}-${d.target.id}`);
linkEnter.append("polygon", "g")
- .attr("class", function(d) {
+ .attr("class", (d) => {
let linkClasses = ["WorkflowChart-linkOverlay"];
if (
scope.graphState.linkBeingEdited &&
@@ -444,9 +448,9 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
}
return linkClasses.join(' ');
})
- .attr("id", function(d){return "link-" + d.source.id + "-" + d.target.id + "-overlay";})
+ .attr("id", (d) => `link-${d.source.id}-${d.target.id}-overlay`)
.call(edit_link)
- .attr("points",function(d) {
+ .attr("points",(d) => {
let x1 = nodePositionMap[d.target.id].x;
let y1 = normalizeY(nodePositionMap[d.target.id].y) + (nodePositionMap[d.target.id].height/2);
let x2 = nodePositionMap[d.source.id].x + nodePositionMap[d.target.id].width;
@@ -462,7 +466,7 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
return [pt1, pt2, pt3, pt4].join(" ");
})
- .on("mouseover", function(d) {
+ .on("mouseover", (d) => {
if(
d.edgeType !== 'placeholder' &&
!scope.graphState.isLinkMode &&
@@ -478,10 +482,10 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
buildLinkTooltip(d);
}
})
- .on("mouseout", function(d){
+ .on("mouseout", (d) => {
if(d.source.id !== 1 && d.target.id !== scope.graphState.nodeBeingAdded && scope.mode !== 'details') {
$(`#aw-workflow-chart-g`).prepend($(`#link-${d.source.id}-${d.target.id}`));
- d3.select("#link-" + d.source.id + "-" + d.target.id)
+ d3.select(`#link-${d.source.id}-${d.target.id}`)
.classed("WorkflowChart-linkHovering", false);
}
$('.WorkflowChart-tooltip').remove();
@@ -492,7 +496,7 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
.attr("class", "WorkflowChart-linkPath")
.attr("d", lineData)
.call(edit_link)
- .on("mouseenter", function(d) {
+ .on("mouseenter", (d) => {
if(
d.edgeType !== 'placeholder' &&
!scope.graphState.isLinkMode &&
@@ -508,15 +512,15 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
buildLinkTooltip(d);
}
})
- .on("mouseleave", function(d){
+ .on("mouseleave", (d) => {
if(d.source.id !== 1 && d.target.id !== scope.graphState.nodeBeingAdded && scope.mode !== 'details') {
$(`#aw-workflow-chart-g`).prepend($(`#link-${d.source.id}-${d.target.id}`));
- d3.select("#link-" + d.source.id + "-" + d.target.id)
+ d3.select(`#link-${d.source.id}-${d.target.id}`)
.classed("WorkflowChart-linkHovering", false);
}
$('.WorkflowChart-tooltip').remove();
})
- .attr('stroke', function(d) {
+ .attr('stroke', (d) => {
let edgeType = d.edgeType;
if(d.edgeType) {
if(edgeType === "failure") {
@@ -535,14 +539,14 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
});
linkEnter.append("circle")
- .attr("id", function(d){return "link-" + d.source.id + "-" + d.target.id + "-add";})
+ .attr("id", (d) => `link-${d.source.id}-${d.target.id}-add`)
.attr("r", 10)
.attr("class", "WorkflowChart-addCircle WorkflowChart-circleBetweenNodes")
- .style("display", function(d) { return (d.edgeType === 'placeholder' || scope.graphState.isLinkMode || d.source.id === scope.graphState.nodeBeingAdded || d.target.id === scope.graphState.nodeBeingAdded || scope.readOnly) ? "none" : null; })
- .attr("cx", function(d) {
+ .style("display", (d) => { return (d.edgeType === 'placeholder' || scope.graphState.isLinkMode || d.source.id === scope.graphState.nodeBeingAdded || d.target.id === scope.graphState.nodeBeingAdded || scope.readOnly) ? "none" : null; })
+ .attr("cx", (d) => {
return (nodePositionMap[d.source.id].x + nodePositionMap[d.source.id].width + nodePositionMap[d.target.id].x)/2;
})
- .attr("cy", function(d) {
+ .attr("cy", (d) => {
const normalizedSourceY = normalizeY(nodePositionMap[d.source.id].y);
const halfSourceHeight = nodePositionMap[d.source.id].height/2;
const normalizedTargetY = normalizeY(nodePositionMap[d.target.id].y);
@@ -557,14 +561,14 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
return yPos;
})
.call(add_node_with_child)
- .on("mouseover", function(d) {
+ .on("mouseover", (d) => {
$(`#link-${d.source.id}-${d.target.id}`).appendTo(`#aw-workflow-chart-g`);
- d3.select("#link-" + d.source.id + "-" + d.target.id)
+ d3.select(`#link-${d.source.id}-${d.target.id}`)
.classed("WorkflowChart-addHovering", true);
})
- .on("mouseout", function(d){
+ .on("mouseout", (d) => {
$(`#aw-workflow-chart-g`).prepend($(`#link-${d.source.id}-${d.target.id}`));
- d3.select("#link-" + d.source.id + "-" + d.target.id)
+ d3.select(`#link-${d.source.id}-${d.target.id}`)
.classed("WorkflowChart-addHovering", false);
});
@@ -575,10 +579,8 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
.size(60)
.type("cross")
)
- .style("display", function(d) { return (d.edgeType === 'placeholder' || scope.graphState.isLinkMode || d.source.id === scope.graphState.nodeBeingAdded || d.target.id === scope.graphState.nodeBeingAdded || scope.readOnly) ? "none" : null; })
- .attr("transform", function(d) {
- let translate;
-
+ .style("display", (d) => { return (d.edgeType === 'placeholder' || scope.graphState.isLinkMode || d.source.id === scope.graphState.nodeBeingAdded || d.target.id === scope.graphState.nodeBeingAdded || scope.readOnly) ? "none" : null; })
+ .attr("transform", (d) => {
const normalizedSourceY = normalizeY(nodePositionMap[d.source.id].y);
const halfSourceHeight = nodePositionMap[d.source.id].height/2;
const normalizedTargetY = normalizeY(nodePositionMap[d.target.id].y);
@@ -590,23 +592,22 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
yPos = yPos + 4;
}
- translate = "translate(" + (nodePositionMap[d.source.id].x + nodePositionMap[d.source.id].width + nodePositionMap[d.target.id].x)/2 + "," + yPos + ")";
- return translate;
+ return `translate(${(nodePositionMap[d.source.id].x + nodePositionMap[d.source.id].width + nodePositionMap[d.target.id].x)/2}, ${yPos})`;
})
.call(add_node_with_child)
- .on("mouseover", function(d) {
+ .on("mouseover", (d) => {
$(`#link-${d.source.id}-${d.target.id}`).appendTo(`#aw-workflow-chart-g`);
- d3.select("#link-" + d.source.id + "-" + d.target.id)
+ d3.select(`#link-${d.source.id}-${d.target.id}`)
.classed("WorkflowChart-addHovering", true);
})
- .on("mouseout", function(d){
+ .on("mouseout", (d) => {
$(`#aw-workflow-chart-g`).prepend($(`#link-${d.source.id}-${d.target.id}`));
- d3.select("#link-" + d.source.id + "-" + d.target.id)
+ d3.select(`#link-${d.source.id}-${d.target.id}`)
.classed("WorkflowChart-addHovering", false);
});
let nodes = svgGroup.selectAll('.WorkflowChart-node')
- .data(scope.graphState.arrayOfNodesForChart, function(d) { return d.id; });
+ .data(scope.graphState.arrayOfNodesForChart, (d) => { return d.id; });
// Remove any stale nodes
nodes.exit().remove();
@@ -614,33 +615,33 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
// Update existing nodes
baseSvg.selectAll(".WorkflowChart-node")
.transition()
- .attr("transform", function (d) {
+ .attr("transform", (d) => {
// Update prior x and prior y
d.px = d.x;
d.py = d.y;
- return "translate(" + nodePositionMap[d.id].x + "," + normalizeY(nodePositionMap[d.id].y) + ")";
+ return `translate(${nodePositionMap[d.id].x}, ${normalizeY(nodePositionMap[d.id].y)})`;
});
baseSvg.selectAll(".WorkflowChart-nodeAddCircle")
- .style("display", function(d) { return scope.graphState.isLinkMode || d.id === scope.graphState.nodeBeingAdded || scope.readOnly ? "none" : null; });
+ .style("display", (d) => { return scope.graphState.isLinkMode || d.id === scope.graphState.nodeBeingAdded || scope.readOnly ? "none" : null; });
baseSvg.selectAll(".WorkflowChart-nodeAddIcon")
- .style("display", function(d) { return scope.graphState.isLinkMode || d.id === scope.graphState.nodeBeingAdded || scope.readOnly ? "none" : null; });
+ .style("display", (d) => { return scope.graphState.isLinkMode || d.id === scope.graphState.nodeBeingAdded || scope.readOnly ? "none" : null; });
baseSvg.selectAll(".WorkflowChart-linkCircle")
- .style("display", function(d) { return scope.graphState.isLinkMode || d.id === scope.graphState.nodeBeingAdded || scope.readOnly ? "none" : null; });
+ .style("display", (d) => { return scope.graphState.isLinkMode || d.id === scope.graphState.nodeBeingAdded || scope.readOnly ? "none" : null; });
baseSvg.selectAll(".WorkflowChart-nodeLinkIcon")
- .style("display", function(d) { return scope.graphState.isLinkMode || d.id === scope.graphState.nodeBeingAdded || scope.readOnly ? "none" : null; });
+ .style("display", (d) => { return scope.graphState.isLinkMode || d.id === scope.graphState.nodeBeingAdded || scope.readOnly ? "none" : null; });
baseSvg.selectAll(".WorkflowChart-nodeRemoveCircle")
- .style("display", function(d) { return scope.graphState.isLinkMode || d.id === scope.graphState.nodeBeingAdded || scope.readOnly ? "none" : null; });
+ .style("display", (d) => { return scope.graphState.isLinkMode || d.id === scope.graphState.nodeBeingAdded || scope.readOnly ? "none" : null; });
baseSvg.selectAll(".WorkflowChart-nodeRemoveIcon")
- .style("display", function(d) { return scope.graphState.isLinkMode || d.id === scope.graphState.nodeBeingAdded || scope.readOnly ? "none" : null; });
+ .style("display", (d) => { return scope.graphState.isLinkMode || d.id === scope.graphState.nodeBeingAdded || scope.readOnly ? "none" : null; });
baseSvg.selectAll(".WorkflowChart-rect")
- .attr('stroke', function(d) {
+ .attr('stroke', (d) => {
if(d.job && d.job.status) {
if(d.job.status === "successful"){
return "#5cb85c";
@@ -656,30 +657,26 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
return "#D7D7D7";
}
})
- .attr("class", function(d) {
+ .attr("class", (d) => {
let classString = d.id === scope.graphState.nodeBeingAdded ? "WorkflowChart-rect WorkflowChart-isNodeBeingAdded" : "WorkflowChart-rect";
classString += !d.unifiedJobTemplate ? " WorkflowChart-dashedNode" : "";
return classString;
});
baseSvg.selectAll(".WorkflowChart-nodeOverlay")
- .attr("class", function(d) { return d.isInvalidLinkTarget ? "WorkflowChart-nodeOverlay WorkflowChart-nodeOverlay--disabled" : "WorkflowChart-nodeOverlay WorkflowChart-nodeOverlay--transparent"; });
+ .attr("class", (d) => { return d.isInvalidLinkTarget ? "WorkflowChart-nodeOverlay WorkflowChart-nodeOverlay--disabled" : "WorkflowChart-nodeOverlay WorkflowChart-nodeOverlay--transparent"; });
baseSvg.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" ||
- d.unifiedJobTemplate.type === "workflow_job_template" ||
- d.unifiedJobTemplate.unified_job_type === "workflow_job") ? null : "none";
- });
+ .style("display", (d) => d.unifiedJobTemplate ? null : "none");
baseSvg.selectAll(".WorkflowChart-nodeTypeLetter")
- .text(function (d) {
+ .text((d) => {
let nodeTypeLetter = "";
if (d.unifiedJobTemplate && d.unifiedJobTemplate.type) {
switch (d.unifiedJobTemplate.type) {
+ case "job_template":
+ nodeTypeLetter = "JT";
+ break;
case "project":
nodeTypeLetter = "P";
break;
@@ -692,6 +689,9 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
}
} else if (d.unifiedJobTemplate && d.unifiedJobTemplate.unified_job_type) {
switch (d.unifiedJobTemplate.unified_job_type) {
+ case "job":
+ nodeTypeLetter = "JT";
+ break;
case "project_update":
nodeTypeLetter = "P";
break;
@@ -705,50 +705,88 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
}
return nodeTypeLetter;
})
- .style("display", function (d) {
+ .style("display", (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" ||
- d.unifiedJobTemplate.type === "workflow_job_template" ||
- d.unifiedJobTemplate.unified_job_type === "workflow_job") ? null : "none";
+ d.unifiedJobTemplate.type !== "workflow_approval_template" &&
+ d.unifiedJobTemplate.unified_job_type !== "workflow_approval" ? null : "none";
});
- baseSvg.selectAll(".WorkflowChart-nodeStatus")
- .attr("class", function(d) {
+ baseSvg.selectAll(".WorkflowChart-nodeTypeLetter")
+ .text((d) => {
+ let nodeTypeLetter = "";
+ if (d.unifiedJobTemplate && d.unifiedJobTemplate.type) {
+ switch (d.unifiedJobTemplate.type) {
+ case "job_template":
+ nodeTypeLetter = "JT";
+ break;
+ case "project":
+ nodeTypeLetter = "P";
+ break;
+ case "inventory_source":
+ nodeTypeLetter = "I";
+ break;
+ case "workflow_job_template":
+ nodeTypeLetter = "W";
+ break;
+ }
+ } else if (d.unifiedJobTemplate && d.unifiedJobTemplate.unified_job_type) {
+ switch (d.unifiedJobTemplate.unified_job_type) {
+ case "job":
+ nodeTypeLetter = "JT";
+ break;
+ case "project_update":
+ nodeTypeLetter = "P";
+ break;
+ case "inventory_update":
+ nodeTypeLetter = "I";
+ break;
+ case "workflow_job":
+ nodeTypeLetter = "W";
+ break;
+ }
+ }
+ return nodeTypeLetter;
+ })
+ .style("display", (d) => {
+ return d.unifiedJobTemplate &&
+ d.unifiedJobTemplate.type !== "workflow_approval_template" &&
+ d.unifiedJobTemplate.unified_job_type !== "workflow_approval" ? null : "none";
+ });
- let statusClass = "WorkflowChart-nodeStatus ";
+ baseSvg.selectAll(".WorkflowChart-pauseIcon")
+ .style("display", (d) => {
+ return d.unifiedJobTemplate &&
+ (d.unifiedJobTemplate.type === "workflow_approval_template" ||
+ d.unifiedJobTemplate.unified_job_type === "workflow_approval") ? null : "none";
+ });
+
+ baseSvg.selectAll(".WorkflowChart-nodeStatus")
+ .attr("class", (d) => {
+ let statusClasses = ["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";
+ statusClasses.push("WorkflowChart-nodeStatus--running");
break;
case "successful":
- statusClass += "WorkflowChart-nodeStatus--success";
+ statusClasses.push("WorkflowChart-nodeStatus--success");
break;
case "failed":
- statusClass += "WorkflowChart-nodeStatus--failed";
- break;
case "error":
- statusClass += "WorkflowChart-nodeStatus--failed";
+ statusClasses.push("WorkflowChart-nodeStatus--failed");
break;
case "canceled":
- statusClass += "WorkflowChart-nodeStatus--canceled";
+ statusClasses.push("WorkflowChart-nodeStatus--canceled");
break;
}
}
- return statusClass;
+ return statusClasses.join(' ');
})
- .style("display", function(d) { return d.job && d.job.status ? null : "none"; })
+ .style("display", (d) => { return d.job && d.job.status ? null : "none"; })
.transition()
.duration(0)
.attr("r", 6)
@@ -770,28 +808,20 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
});
baseSvg.selectAll(".WorkflowChart-nameText")
- .attr("x", 0)
- .attr("y", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? 10 : (nodeH / 2) - 10; })
+ .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"; })
- .html(function (d) {
- const name = _.get(d, 'unifiedJobTemplate.name');
- const wrappedName = name ? wrap(name) : "";
- // TODO: clean this up
- if (d.unifiedJobTemplate && d.unifiedJobTemplate.unified_job_type === 'workflow_approval') {
- return `
-
-
-
- ${wrappedName}
- `;
- } else {
- return `${wrappedName}`;
- }
- });
+ .text((d) => wrap(_.get(d, 'unifiedJobTemplate.name')));
baseSvg.selectAll(".WorkflowChart-detailsLink")
- .style("display", function(d){ return d.job && d.job.status && d.job.id ? null : "none"; })
- .html(function (d) {
+ .style("display", (d) => {
+ const isApprovalStep = d.unifiedJobTemplate && d.unifiedJobTemplate.unified_job_type === 'workflow_approval';
+ return d.job &&
+ !isApprovalStep &&
+ d.job.status &&
+ d.job.id ? null : "none";
+ })
+ .html((d) => {
let href = "";
if (d.job) {
href = `/#/workflow_node_results/${d.job.id}`;
@@ -800,27 +830,25 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
});
baseSvg.selectAll(".WorkflowChart-deletedText")
- .style("display", function(d){ return d.unifiedJobTemplate || d.id === scope.graphState.nodeBeingAdded ? "none" : null; });
+ .style("display", (d) => { return d.unifiedJobTemplate || d.id === scope.graphState.nodeBeingAdded ? "none" : null; });
baseSvg.selectAll(".WorkflowChart-activeNode")
- .style("display", function(d) { return d.id === scope.graphState.nodeBeingEdited ? null : "none"; });
+ .style("display", (d) => { return d.id === scope.graphState.nodeBeingEdited ? null : "none"; });
baseSvg.selectAll(".WorkflowChart-elapsed")
- .style("display", function(d) { return (d.job && d.job.elapsed) ? null : "none"; });
+ .style("display", (d) => { return (d.job && d.job.elapsed) ? null : "none"; });
baseSvg.selectAll(".WorkflowChart-addLinkCircle")
- .attr("fill", function(d) { return scope.graphState.addLinkSource === d.id ? "#337AB7" : "#D7D7D7"; })
- .style("display", function(d) { return scope.graphState.isLinkMode && !d.isInvalidLinkTarget ? null : "none"; });
+ .attr("fill", (d) => { return scope.graphState.addLinkSource === d.id ? "#337AB7" : "#D7D7D7"; })
+ .style("display", (d) => { return scope.graphState.isLinkMode && !d.isInvalidLinkTarget ? null : "none"; });
// Add new nodes
const nodeEnter = nodes
.enter()
.append('g')
.attr("class", "WorkflowChart-node")
- .attr("id", function(d){return "node-" + d.id;})
- .attr("transform", function (d) {
- return "translate(" + nodePositionMap[d.id].x + "," + normalizeY(nodePositionMap[d.id].y) + ")";
- });
+ .attr("id", (d) => `node-${d.id}`)
+ .attr("transform", (d) => `translate(${nodePositionMap[d.id].x},${normalizeY(nodePositionMap[d.id].y)})`);
nodeEnter.each(function(d) {
let thisNode = d3.select(this);
@@ -852,7 +880,7 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
.attr("y", 30)
.attr("dy", ".35em")
.attr("class", "WorkflowChart-startText")
- .text(function () { return TemplatesStrings.get('workflow_maker.START'); })
+ .text(TemplatesStrings.get('workflow_maker.START'))
.call(add_node_without_child);
}
else {
@@ -861,13 +889,13 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
.attr("cx", nodeW)
.attr("r", 8)
.attr("class", "WorkflowChart-addLinkCircle")
- .style("display", function() { return scope.graphState.isLinkMode ? null : "none"; });
+ .style("display", scope.graphState.isLinkMode ? null : "none");
thisNode.append("rect")
.attr("width", nodeW)
.attr("height", nodeH)
.attr("rx", 5)
.attr("ry", 5)
- .attr('stroke', function(d) {
+ .attr('stroke', (d) => {
if(d.job && d.job.status) {
if(d.job.status === "successful"){
return "#5cb85c";
@@ -884,7 +912,7 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
}
})
.attr('stroke-width', "2px")
- .attr("class", function(d) {
+ .attr("class", (d) => {
let classString = d.id === scope.graphState.nodeBeingAdded ? "WorkflowChart-rect WorkflowChart-isNodeBeingAdded" : "WorkflowChart-rect";
classString += !_.get(d, 'unifiedJobTemplate.name') ? " WorkflowChart-dashedNode" : "";
return classString;
@@ -893,30 +921,15 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
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.id === scope.graphState.nodeBeingEdited ? null : "none"; });
+ .style("display", (d) => { return d.id === scope.graphState.nodeBeingEdited ? null : "none"; });
- thisNode.append("foreignObject")
- // .attr("x", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? 20 : nodeW / 2; })
- .attr("x", 0)
- .attr("y", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? 10 : (nodeH / 2) - 10; })
+ 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")
- .html(function (d) {
- const name = _.get(d, 'unifiedJobTemplate.name');
- const wrappedName = name ? wrap(name) : "";
- // TODO: clean this up
- if (d.unifiedJobTemplate && d.unifiedJobTemplate.unified_job_type === 'workflow_approval') {
- return `
-
-
-
- ${wrappedName}
- `;
- } else {
- return `${wrappedName}`;
- }
- });
+ .text((d) => wrap(_.get(d, 'unifiedJobTemplate.name')));
thisNode.append("foreignObject")
.attr("x", 0)
@@ -924,94 +937,107 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.attr("class", "WorkflowChart-defaultText WorkflowChart-deletedText")
- .html(function () {
- return `${TemplatesStrings.get('workflow_maker.DELETED')}`;
- })
- .style("display", function(d) { return d.unifiedJobTemplate || d.id === scope.graphState.nodeBeingAdded ? "none" : null; });
+ .html(`${TemplatesStrings.get('workflow_maker.DELETED')}`)
+ .style("display", (d) => { return d.unifiedJobTemplate || d.id === scope.graphState.nodeBeingAdded ? "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" ||
- d.unifiedJobTemplate.type === "workflow_job_template" ||
- d.unifiedJobTemplate.unified_job_type === "workflow_job") ? null : "none";
- });
+ .style("display", (d) => d.unifiedJobTemplate ? null : "none");
+
thisNode.append("text")
.attr("y", nodeH)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.attr("class", "WorkflowChart-nodeTypeLetter")
- .text(function (d) {
- let nodeTypeLetter = "";
- if (d.unifiedJobTemplate && d.unifiedJobTemplate.type) {
- switch (d.unifiedJobTemplate.type) {
- case "project":
- nodeTypeLetter = "P";
- break;
- case "inventory_source":
- nodeTypeLetter = "I";
- break;
- case "workflow_job_template":
- nodeTypeLetter = "W";
- break;
- }
- } else if (d.unifiedJobTemplate && d.unifiedJobTemplate.unified_job_type) {
- switch (d.unifiedJobTemplate.unified_job_type) {
- case "project_update":
- nodeTypeLetter = "P";
- break;
- case "inventory_update":
- nodeTypeLetter = "I";
- break;
- case "workflow_job":
- nodeTypeLetter = "W";
- break;
- }
- }
- return nodeTypeLetter;
+ .text((d) => {
+ let nodeTypeLetter = "";
+ if (d.unifiedJobTemplate && d.unifiedJobTemplate.type) {
+ switch (d.unifiedJobTemplate.type) {
+ case "job_template":
+ nodeTypeLetter = "JT";
+ break;
+ case "project":
+ nodeTypeLetter = "P";
+ break;
+ case "inventory_source":
+ nodeTypeLetter = "I";
+ break;
+ case "workflow_job_template":
+ nodeTypeLetter = "W";
+ break;
+ }
+ } else if (d.unifiedJobTemplate && d.unifiedJobTemplate.unified_job_type) {
+ switch (d.unifiedJobTemplate.unified_job_type) {
+ case "job":
+ nodeTypeLetter = "JT";
+ break;
+ case "project_update":
+ nodeTypeLetter = "P";
+ break;
+ case "inventory_update":
+ nodeTypeLetter = "I";
+ break;
+ case "workflow_job":
+ nodeTypeLetter = "W";
+ break;
+ }
+ }
+ return nodeTypeLetter;
})
- .style("display", function (d) {
+ .style("display", (d) => {
return d.unifiedJobTemplate &&
(d.unifiedJobTemplate.type === "project" ||
- d.unifiedJobTemplate.unified_job_type === "project_update" ||
+ d.unifiedJobTemplate.unified_job_type === "project_update" ||
+ d.unifiedJobTemplate.type === "job_template" ||
+ d.unifiedJobTemplate.unified_job_type === "job" ||
d.unifiedJobTemplate.type === "inventory_source" ||
d.unifiedJobTemplate.unified_job_type === "inventory_update" ||
d.unifiedJobTemplate.type === "workflow_job_template" ||
d.unifiedJobTemplate.unified_job_type === "workflow_job") ? null : "none";
- });
+ });
+
+ thisNode.append("foreignObject")
+ .attr("x", -5)
+ .attr("y", nodeH - 9)
+ .attr("dy", ".35em")
+ .attr("height", "15px")
+ .attr("width", "11px")
+ .attr("class", "WorkflowChart-pauseIcon")
+ .html(``)
+ .style("display", (d) => {
+ return d.unifiedJobTemplate &&
+ (d.unifiedJobTemplate.type === "workflow_approval_template" ||
+ d.unifiedJobTemplate.unified_job_type === "workflow_approval") ? null : "none";
+ });
thisNode.append("rect")
.attr("width", nodeW)
.attr("height", nodeH)
- .attr("class", function(d) { return d.isInvalidLinkTarget ? "WorkflowChart-nodeOverlay WorkflowChart-nodeOverlay--disabled" : "WorkflowChart-nodeOverlay WorkflowChart-nodeOverlay--transparent"; })
+ .attr("class", (d) => { return d.isInvalidLinkTarget ? "WorkflowChart-nodeOverlay WorkflowChart-nodeOverlay--disabled" : "WorkflowChart-nodeOverlay WorkflowChart-nodeOverlay--transparent"; })
.call(node_click)
- .on("mouseover", function(d) {
+ .on("mouseover", (d) => {
if(d.id !== 1) {
$(`#node-${d.id}`).appendTo(`#aw-workflow-chart-g`);
let resourceName = (d.unifiedJobTemplate && d.unifiedJobTemplate.name) ? d.unifiedJobTemplate.name : "";
if(resourceName && resourceName.length > maxNodeTextLength) {
+ const sanitizedResourceName = $filter('sanitize')(resourceName);
// When the graph is initially rendered all the links come after the nodes (when you look at the dom).
// SVG components are painted in order of appearance. There is no concept of z-index, only the order.
// As such, we need to move the nodes after the links so that when the tooltip renders it shows up on top
// of the links and not underneath them. I tried rendering the links before the nodes but that lead to
// some weird link animation that I didn't care to try to fix.
- svgGroup.selectAll("g.WorkflowChart-node").each(function() {
- this.parentNode.appendChild(this);
- });
+ svgGroup.selectAll("g.WorkflowChart-node").each(() => this.parentNode.appendChild(this));
// After the nodes have been properly placed after the links, we need to make sure that the node that
// the user is hovering over is at the very end of the list. This way the tooltip will appear on top
// of all other nodes.
- svgGroup.selectAll("g.WorkflowChart-node").sort(function (a) {
+ svgGroup.selectAll("g.WorkflowChart-node").sort((a) => {
return (a.index !== d.index) ? -1 : 1;
});
// Render the tooltip quickly in the dom and then remove. This lets us know how big the tooltip is so that we can place
// it properly on the workflow
- let tooltipDimensionChecker = $("" + $filter('sanitize')(resourceName) + "
");
+ let tooltipDimensionChecker = $(`${sanitizedResourceName}
`);
$('body').append(tooltipDimensionChecker);
let tipWidth = $(tooltipDimensionChecker).outerWidth();
let tipHeight = $(tooltipDimensionChecker).outerHeight();
@@ -1023,9 +1049,7 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
.attr("width", tipWidth)
.attr("height", tipHeight+20)
.attr("class", "WorkflowChart-tooltip")
- .html(function(){
- return "" + $filter('sanitize')(resourceName) + "
";
- });
+ .html(`${sanitizedResourceName}
`);
}
if (scope.graphState.isLinkMode && !d.isInvalidLinkTarget && scope.graphState.addLinkSource !== d.id) {
@@ -1076,15 +1100,15 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
.attr("stroke", "#D7D7D7")
.attr('marker-mid', "url(#aw-workflow-chart-arrow)");
}
- d3.select("#node-" + d.id)
+ d3.select(`#node-${d.id}`)
.classed("WorkflowChart-nodeHovering", true);
}
})
- .on("mouseout", function(d){
+ .on("mouseout", (d) => {
$('.WorkflowChart-tooltip').remove();
$('.WorkflowChart-potentialLink').remove();
if(d.id !== 1) {
- d3.select("#node-" + d.id)
+ d3.select(`#node-${d.id}`)
.classed("WorkflowChart-nodeHovering", false);
}
});
@@ -1095,11 +1119,15 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
.attr("width", "40px")
.attr("dy", ".35em")
.attr("class", "WorkflowChart-detailsLink")
- .style("display", function(d){ return d.job && d.job.status && d.job.id ? null : "none"; })
- .on("mousedown", function(){
- d3.event.stopPropagation();
+ .style("display", (d) => {
+ const isApprovalStep = d.unifiedJobTemplate && d.unifiedJobTemplate.unified_job_type === 'workflow_approval';
+ return d.job &&
+ !isApprovalStep &&
+ d.job.status &&
+ d.job.id ? null : "none";
})
- .html(function (d) {
+ .on("mousedown", () => d3.event.stopPropagation())
+ .html((d) => {
let href = "";
if (d.job) {
href = `/#/workflow_node_results/${d.job.id}`;
@@ -1107,64 +1135,64 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
return `${TemplatesStrings.get('workflow_maker.DETAILS')}`;
});
thisNode.append("circle")
- .attr("id", function(d){return "node-" + d.id + "-add";})
+ .attr("id", (d) => `node-${d.id}-add`)
.attr("cx", nodeW)
.attr("r", 10)
.attr("class", "WorkflowChart-addCircle WorkflowChart-nodeAddCircle")
- .style("display", function(d) { return d.id === scope.graphState.nodeBeingAdded || scope.readOnly ? "none" : null; })
+ .style("display", (d) => { return d.id === scope.graphState.nodeBeingAdded || scope.readOnly ? "none" : null; })
.call(add_node_without_child)
- .on("mouseover", function(d) {
- d3.select("#node-" + d.id)
+ .on("mouseover", (d) => {
+ d3.select(`#node-${d.id}`)
.classed("WorkflowChart-nodeHovering", true);
- d3.select("#node-" + d.id + "-add")
+ d3.select(`#node-${d.id}-add`)
.classed("WorkflowChart-addHovering", true);
})
- .on("mouseout", function(d){
- d3.select("#node-" + d.id)
+ .on("mouseout", (d) => {
+ d3.select(`#node-${d.id}`)
.classed("WorkflowChart-nodeHovering", false);
- d3.select("#node-" + d.id + "-add")
+ d3.select(`#node-${d.id}-add`)
.classed("WorkflowChart-addHovering", false);
});
thisNode.append("path")
.attr("class", "WorkflowChart-nodeAddIcon")
.style("fill", "white")
- .attr("transform", function() { return "translate(" + nodeW + "," + 0 + ")"; })
+ .attr("transform", `translate(${nodeW}, 0)`)
.attr("d", d3.svg.symbol()
.size(60)
.type("cross")
)
- .style("display", function(d) { return d.id === scope.graphState.nodeBeingAdded || scope.readOnly ? "none" : null; })
+ .style("display", (d) => { return d.id === scope.graphState.nodeBeingAdded || scope.readOnly ? "none" : null; })
.call(add_node_without_child)
- .on("mouseover", function(d) {
- d3.select("#node-" + d.id)
+ .on("mouseover", (d) => {
+ d3.select(`#node-${d.id}`)
.classed("WorkflowChart-nodeHovering", true);
- d3.select("#node-" + d.id + "-add")
+ d3.select(`#node-${d.id}-add`)
.classed("WorkflowChart-addHovering", true);
})
- .on("mouseout", function(d){
- d3.select("#node-" + d.id)
+ .on("mouseout", (d) => {
+ d3.select(`#node-${d.id}`)
.classed("WorkflowChart-nodeHovering", false);
- d3.select("#node-" + d.id + "-add")
+ d3.select(`#node-${d.id}-add`)
.classed("WorkflowChart-addHovering", false);
});
thisNode.append("circle")
- .attr("id", function(d){return "node-" + d.id + "-link";})
+ .attr("id", (d) => `node-${d.id}-link`)
.attr("cx", nodeW)
.attr("cy", nodeH/2)
.attr("r", 10)
.attr("class", "WorkflowChart-linkCircle")
- .style("display", function(d) { return d.id === scope.graphState.nodeBeingAdded || scope.readOnly ? "none" : null; })
+ .style("display", (d) => { return d.id === scope.graphState.nodeBeingAdded || scope.readOnly ? "none" : null; })
.call(add_link)
- .on("mouseover", function(d) {
- d3.select("#node-" + d.id)
+ .on("mouseover", (d) => {
+ d3.select(`#node-${d.id}`)
.classed("WorkflowChart-nodeHovering", true);
- d3.select("#node-" + d.id + "-link")
+ d3.select(`#node-${d.id}-link`)
.classed("WorkflowChart-linkButtonHovering", true);
})
- .on("mouseout", function(d){
- d3.select("#node-" + d.id)
+ .on("mouseout", (d) => {
+ d3.select(`#node-${d.id}`)
.classed("WorkflowChart-nodeHovering", false);
- d3.select("#node-" + d.id + "-link")
+ d3.select(`#node-${d.id}-link`)
.classed("WorkflowChart-linkButtonHovering", false);
});
thisNode.append("foreignObject")
@@ -1173,123 +1201,130 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
.attr("height", "17px")
.attr("width", "13px")
.style("font-size","14px")
- .html(function () {
- return ``;
- })
+ .html(``)
.attr("class", "WorkflowChart-nodeLinkIcon")
- .style("display", function(d) { return d.id === scope.graphState.nodeBeingAdded || scope.readOnly ? "none" : null; })
+ .style("display", (d) => { return d.id === scope.graphState.nodeBeingAdded || scope.readOnly ? "none" : null; })
.call(add_link)
- .on("mouseover", function(d) {
- d3.select("#node-" + d.id)
+ .on("mouseover", (d) => {
+ d3.select(`#node-${d.id}`)
.classed("WorkflowChart-nodeHovering", true);
- d3.select("#node-" + d.id + "-link")
+ d3.select(`#node-${d.id}-link`)
.classed("WorkflowChart-linkButtonHovering", true);
})
- .on("mouseout", function(d){
- d3.select("#node-" + d.id)
+ .on("mouseout", (d) => {
+ d3.select(`#node-${d.id}`)
.classed("WorkflowChart-nodeHovering", false);
- d3.select("#node-" + d.id + "-link")
+ d3.select(`#node-${d.id}-link`)
.classed("WorkflowChart-linkButtonHovering", false);
});
thisNode.append("circle")
- .attr("id", function(d){return "node-" + d.id + "-remove";})
+ .attr("id", (d) => `node-${d.id}-remove`)
.attr("cx", nodeW)
.attr("cy", nodeH)
.attr("r", 10)
.attr("class", "WorkflowChart-nodeRemoveCircle")
- .style("display", function(d) { return (d.id === 1 || d.id === scope.graphState.nodeBeingAdded || scope.readOnly) ? "none" : null; })
+ .style("display", (d) => { return (d.id === 1 || d.id === scope.graphState.nodeBeingAdded || scope.readOnly) ? "none" : null; })
.call(remove_node)
- .on("mouseover", function(d) {
- d3.select("#node-" + d.id)
+ .on("mouseover", (d) => {
+ d3.select(`#node-${d.id}`)
.classed("WorkflowChart-nodeHovering", true);
- d3.select("#node-" + d.id + "-remove")
+ d3.select(`#node-${d.id}-remove`)
.classed("removeHovering", true);
})
- .on("mouseout", function(d){
- d3.select("#node-" + d.id)
+ .on("mouseout", (d) => {
+ d3.select(`#node-${d.id}`)
.classed("WorkflowChart-nodeHovering", false);
- d3.select("#node-" + d.id + "-remove")
+ d3.select(`#node-${d.id}-remove`)
.classed("removeHovering", false);
});
thisNode.append("path")
.attr("class", "WorkflowChart-nodeRemoveIcon")
.style("fill", "white")
- .attr("transform", function() { return "translate(" + nodeW + "," + nodeH + ") rotate(-45)"; })
+ .attr("transform", `translate(${nodeW}, ${nodeH}) rotate(-45)`)
.attr("d", d3.svg.symbol()
.size(60)
.type("cross")
)
- .style("display", function(d) { return (d.id === 1 || d.id === scope.graphState.nodeBeingAdded || scope.readOnly) ? "none" : null; })
+ .style("display", (d) => { return (d.id === 1 || d.id === scope.graphState.nodeBeingAdded || scope.readOnly) ? "none" : null; })
.call(remove_node)
- .on("mouseover", function(d) {
- d3.select("#node-" + d.id)
+ .on("mouseover", (d) => {
+ d3.select(`#node-${d.id}`)
.classed("WorkflowChart-nodeHovering", true);
- d3.select("#node-" + d.id + "-remove")
+ d3.select(`#node-${d.id}-remove`)
.classed("removeHovering", true);
})
- .on("mouseout", function(d){
- d3.select("#node-" + d.id)
+ .on("mouseout", (d) => {
+ d3.select(`#node-${d.id}`)
.classed("WorkflowChart-nodeHovering", false);
- d3.select("#node-" + d.id + "-remove")
+ d3.select(`#node-${d.id}-remove`)
.classed("removeHovering", false);
});
thisNode.append("circle")
- .attr("class", function(d) {
-
- let statusClass = "WorkflowChart-nodeStatus ";
-
+ .attr("class", (d) => {
+ let statusClasses = ["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";
+ statusClasses.push("WorkflowChart-nodeStatus--running");
break;
case "successful":
- statusClass += "WorkflowChart-nodeStatus--success";
+ statusClasses.push("WorkflowChart-nodeStatus--success");
break;
case "failed":
- statusClass += "WorkflowChart-nodeStatus--failed";
- break;
case "error":
- statusClass += "WorkflowChart-nodeStatus--failed";
+ statusClasses.push("WorkflowChart-nodeStatus--failed");
break;
case "canceled":
- statusClass += "WorkflowChart-nodeStatus--canceled";
+ statusClasses.push("WorkflowChart-nodeStatus--canceled");
break;
}
}
-
- return statusClass;
+
+ return statusClasses.join(' ');
})
- .style("display", function(d) { return d.job && d.job.status ? null : "none"; })
+ .style("display", (d) => { return d.job && d.job.status ? null : "none"; })
.attr("cy", 10)
.attr("cx", 10)
- .attr("r", 6);
+ .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
+ let 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);
+ })();
+ }
+ });
thisNode.append("foreignObject")
.attr("x", 5)
.attr("y", 43)
.style("font-size","0.7em")
.attr("class", "WorkflowChart-elapsed")
- .html(function (d) {
+ .html((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 + "
";
+ return `${elapsedString}
`;
}
else {
return "";
}
})
- .style("display", function(d) { return (d.job && d.job.elapsed) ? null : "none"; });
+ .style("display", (d) => { return (d.job && d.job.elapsed) ? null : "none"; });
}
});
@@ -1303,7 +1338,7 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
svgGroup.selectAll(".WorkflowChart-node").order();
}
else if(!scope.watchDimensionsSet){
- scope.watchDimensionsSet = scope.$watch('dimensionsSet', function(){
+ scope.watchDimensionsSet = scope.$watch('dimensionsSet', () => {
if(scope.dimensionsSet) {
scope.watchDimensionsSet();
scope.watchDimensionsSet = null;
@@ -1314,7 +1349,7 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
};
function add_node_without_child() {
- this.on("click", function(d) {
+ this.on("click", (d) => {
if(!scope.readOnly && !scope.graphState.isLinkMode) {
scope.addNodeWithoutChild({
parent: d
@@ -1324,7 +1359,7 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
}
function add_node_with_child() {
- this.on("click", function(d) {
+ this.on("click", (d) => {
if(!scope.readOnly && !scope.graphState.isLinkMode && d.edgeType !== 'placeholder') {
scope.addNodeWithChild({
link: d
@@ -1334,7 +1369,7 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
}
function remove_node() {
- this.on("click", function(d) {
+ this.on("click", (d) => {
if(d.id !== 1 && !scope.readOnly && !scope.graphState.isLinkMode) {
scope.deleteNode({
nodeToDelete: d
@@ -1344,7 +1379,7 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
}
function node_click() {
- this.on("click", function(d) {
+ this.on("click", (d) => {
if(d.id !== scope.graphState.nodeBeingAdded){
if(scope.graphState.isLinkMode && !d.isInvalidLinkTarget && scope.graphState.addLinkSource !== d.id) {
$('.WorkflowChart-potentialLink').remove();
@@ -1362,7 +1397,7 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
}
function edit_link() {
- this.on("click", function(d) {
+ this.on("click", (d) => {
if(!scope.graphState.isLinkMode && d.source.id !== 1 && d.source.id !== scope.graphState.nodeBeingAdded && d.target.id !== scope.graphState.nodeBeingAdded && scope.mode !== 'details'){
scope.editLink({
linkToEdit: d
@@ -1372,7 +1407,7 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
}
function add_link() {
- this.on("click", function(d) {
+ this.on("click", (d) => {
if (!scope.readOnly && !scope.graphState.isLinkMode) {
scope.selectNodeForLinking({
nodeToStartLink: d
@@ -1381,29 +1416,29 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
});
}
- scope.$on('refreshWorkflowChart', function(){
+ scope.$on('refreshWorkflowChart', () => {
if(scope.graphState) {
updateGraph();
}
});
- scope.$on('panWorkflowChart', function(evt, params) {
+ scope.$on('panWorkflowChart', (evt, params) => {
manualPan(params.direction);
});
- scope.$on('resetWorkflowChart', function(){
+ scope.$on('resetWorkflowChart', () => {
resetZoomAndPan();
});
- scope.$on('zoomWorkflowChart', function(evt, params) {
+ scope.$on('zoomWorkflowChart', (evt, params) => {
manualZoom(params.zoom);
});
- scope.$on('zoomToFitChart', function() {
+ scope.$on('zoomToFitChart', () => {
zoomToFitChart();
});
- let clearWatchGraphState = scope.$watch('graphState.arrayOfNodesForChart', function(newVal) {
+ let clearWatchGraphState = scope.$watch('graphState.arrayOfNodesForChart', (newVal) => {
if(newVal) {
updateGraph();
clearWatchGraphState();
@@ -1432,10 +1467,10 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
scope.dimensionsSet = true;
line = d3.svg.line()
- .x(function (d) {
+ .x((d) => {
return d.x;
})
- .y(function (d) {
+ .y((d) => {
return d.y;
});
@@ -1449,7 +1484,7 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
svgGroup = baseSvg.append("g")
.attr("id", "aw-workflow-chart-g")
- .attr("transform", "translate(0," + (windowHeight/2 - rootH/2 - startNodeOffsetY) + ")");
+ .attr("transform", `translate(0, ${(windowHeight/2 - rootH/2 - startNodeOffsetY)})`);
const defs = baseSvg.append("defs");
@@ -1469,16 +1504,16 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
angular.element($window).on('resize', onResize);
scope.$on('$destroy', cleanUpResize);
- scope.$on('workflowDetailsResized', function(){
+ scope.$on('workflowDetailsResized', () => {
$('.WorkflowMaker-chart').hide();
- $timeout(function(){
+ $timeout(() => {
onResize();
$('.WorkflowMaker-chart').show();
});
});
}
else {
- scope.$on('workflowMakerModalResized', function(){
+ scope.$on('workflowMakerModalResized', () => {
let dimensions = calcAvailableScreenSpace();
$('.WorkflowMaker-chart').css("width", dimensions.width);
diff --git a/awx/ui/client/src/templates/workflows/workflow-key/main.js b/awx/ui/client/src/templates/workflows/workflow-key/main.js
new file mode 100644
index 0000000000..5721249659
--- /dev/null
+++ b/awx/ui/client/src/templates/workflows/workflow-key/main.js
@@ -0,0 +1,11 @@
+/*************************************************
+ * Copyright (c) 2019 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import workflowKey from './workflow-key.directive';
+
+export default
+ angular.module('workflowKey', [])
+ .directive('workflowKey', workflowKey);
diff --git a/awx/ui/client/src/templates/workflows/workflow-key/workflow-key.directive.js b/awx/ui/client/src/templates/workflows/workflow-key/workflow-key.directive.js
new file mode 100644
index 0000000000..b8f92c0fa9
--- /dev/null
+++ b/awx/ui/client/src/templates/workflows/workflow-key/workflow-key.directive.js
@@ -0,0 +1,18 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+export default ['templateUrl', 'TemplatesStrings',
+ function(templateUrl, TemplatesStrings) {
+ return {
+ scope: {},
+ templateUrl: templateUrl('templates/workflows/workflow-key/workflow-key'),
+ restrict: 'E',
+ link: function(scope) {
+ scope.strings = TemplatesStrings;
+ }
+ };
+ }
+];
diff --git a/awx/ui/client/src/templates/workflows/workflow-key/workflow-key.partial.html b/awx/ui/client/src/templates/workflows/workflow-key/workflow-key.partial.html
new file mode 100644
index 0000000000..6c3aac61a6
--- /dev/null
+++ b/awx/ui/client/src/templates/workflows/workflow-key/workflow-key.partial.html
@@ -0,0 +1,43 @@
+
+ -
+
{{:: strings.get('workflow_maker.KEY')}}
+
+ -
+
+
{{:: strings.get('workflow_maker.ON_SUCCESS')}}
+
+ -
+
+
{{:: strings.get('workflow_maker.ON_FAILURE')}}
+
+ -
+
+
{{:: strings.get('workflow_maker.ALWAYS')}}
+
+ -
+
JT
+ {{:: strings.get('workflow_maker.JOB_TEMPLATE')}}
+
+ -
+
P
+ {{:: strings.get('workflow_maker.PROJECT_SYNC')}}
+
+ -
+
I
+ {{:: strings.get('workflow_maker.INVENTORY_SYNC')}}
+
+ -
+
W
+ {{:: strings.get('workflow_maker.WORKFLOW')}}
+
+ -
+
+
+
+ {{:: strings.get('workflow_maker.PAUSE')}}
+
+ -
+
!
+ {{:: strings.get('workflow_maker.WARNING')}}
+
+
\ No newline at end of file
diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/forms/main.js b/awx/ui/client/src/templates/workflows/workflow-maker/forms/main.js
index 426eaa131c..39153756eb 100644
--- a/awx/ui/client/src/templates/workflows/workflow-maker/forms/main.js
+++ b/awx/ui/client/src/templates/workflows/workflow-maker/forms/main.js
@@ -1,7 +1,9 @@
import workflowLinkForm from './workflow-link-form.directive';
import workflowNodeForm from './workflow-node-form.directive';
+import workflowNodeFormService from './workflow-node-form.service';
export default
angular.module('templates.workflowMaker.forms', [])
.directive('workflowLinkForm', workflowLinkForm)
- .directive('workflowNodeForm', workflowNodeForm);
+ .directive('workflowNodeForm', workflowNodeForm)
+ .service('WorkflowNodeFormService', workflowNodeFormService);
diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.service.js b/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.service.js
new file mode 100644
index 0000000000..460f6ccfa0
--- /dev/null
+++ b/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.service.js
@@ -0,0 +1,67 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+export default ['TemplateList', 'ProjectList', 'InventorySourcesList', 'i18n',
+ function(TemplateList, ProjectList, InventorySourcesList, i18n){
+ return {
+ inventorySourceListDefinition: function() {
+ const inventorySourceList = _.cloneDeep(InventorySourcesList);
+ inventorySourceList.name = 'wf_maker_inventory_sources';
+ inventorySourceList.iterator = 'wf_maker_inventory_source';
+ inventorySourceList.maxVisiblePages = 5;
+ inventorySourceList.searchBarFullWidth = true;
+ inventorySourceList.disableRow = "{{ readOnly }}";
+ inventorySourceList.disableRowValue = 'readOnly';
+
+ return inventorySourceList;
+ },
+ projectListDefinition: function(){
+ const projectList = _.cloneDeep(ProjectList);
+ delete projectList.fields.status;
+ delete projectList.fields.scm_type;
+ delete projectList.fields.last_updated;
+ projectList.name = 'wf_maker_projects';
+ projectList.iterator = 'wf_maker_project';
+ projectList.fields.name.columnClass = "col-md-11";
+ projectList.maxVisiblePages = 5;
+ projectList.searchBarFullWidth = true;
+ projectList.disableRow = "{{ readOnly }}";
+ projectList.disableRowValue = 'readOnly';
+
+ return projectList;
+ },
+ templateListDefinition: function(){
+ const templateList = _.cloneDeep(TemplateList);
+ delete templateList.actions;
+ delete templateList.fields.type;
+ delete templateList.fields.description;
+ delete templateList.fields.smart_status;
+ delete templateList.fields.labels;
+ delete templateList.fieldActions;
+ templateList.name = 'wf_maker_templates';
+ templateList.iterator = 'wf_maker_template';
+ templateList.fields.name.columnClass = "col-md-8";
+ templateList.fields.name.tag = i18n._('WORKFLOW');
+ templateList.fields.name.showTag = "{{wf_maker_template.type === 'workflow_job_template'}}";
+ templateList.disableRow = "{{ readOnly }}";
+ templateList.disableRowValue = 'readOnly';
+ templateList.basePath = 'unified_job_templates';
+ templateList.fields.info = {
+ ngInclude: "'/static/partials/job-template-details.html'",
+ type: 'template',
+ columnClass: 'col-md-3',
+ infoHeaderClass: 'col-md-3',
+ label: '',
+ nosort: true
+ };
+ templateList.maxVisiblePages = 5;
+ templateList.searchBarFullWidth = true;
+
+ return templateList;
+ }
+ };
+ }
+];
diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less
index 87f20ce5db..f163c845e3 100644
--- a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less
+++ b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less
@@ -7,6 +7,15 @@
.ui-dialog-buttonpane, .ui-dialog-titlebar {
display:none;
}
+
+ input[type="number"]::-webkit-outer-spin-button, input[type="number"]::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+ }
+
+ input[type="number"] {
+ -moz-appearance: textfield;
+ }
}
.WorkflowMaker-header {
@@ -380,11 +389,11 @@
.Key-icon--circle {
border-radius: 50%;
- width: 20px;
- height: 20px;
+ width: 24px;
+ height: 24px;
color: @default-bg;
text-align: center;
- line-height: 20px;
+ line-height: 24px;
margin: 4px 5px 5px 0px;
}
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 925c9a03fd..fb81dd1618 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
@@ -145,21 +145,24 @@ export default ['$scope', 'TemplatesService',
let editPromises = [];
let credentialRequests = [];
+ // TODO: clean up data generation of approval template requests
Object.keys(nodeRef).map((workflowMakerNodeId) => {
const node = nodeRef[workflowMakerNodeId];
if (node.isNew) {
if (node.unifiedJobTemplate && node.unifiedJobTemplate.unified_job_type === "workflow_approval") {
- approvalTemplatePromises.push(TemplatesService.createApprovalTemplate({
- name: node.unifiedJobTemplate.name
- }).then(({data: approvalTemplateData}) => {
- addPromises.push(TemplatesService.addWorkflowNode({
- url: $scope.workflowJobTemplateObj.related.workflow_nodes,
+ addPromises.push(TemplatesService.addWorkflowNode({
+ url: $scope.workflowJobTemplateObj.related.workflow_nodes,
+ data: {}
+ }).then(({data}) => {
+ approvalTemplatePromises.push(TemplatesService.createApprovalTemplate({
+ url: data.related.create_approval_template,
data: {
- unified_job_template: approvalTemplateData.id
+ name: node.unifiedJobTemplate.name,
+ description: node.unifiedJobTemplate.description
}
- }).then(({data: nodeData}) => {
- node.originalNodeObject = nodeData;
- nodeIdToChartNodeIdMapping[nodeData.id] = parseInt(workflowMakerNodeId);
+ }).then(() => {
+ node.originalNodeObject = data;
+ nodeIdToChartNodeIdMapping[data.id] = parseInt(workflowMakerNodeId);
}).catch(({ data, status }) => {
Wait('stop');
ProcessErrors($scope, data, status, null, {
@@ -222,20 +225,11 @@ export default ['$scope', 'TemplatesService',
}));
} else {
approvalTemplatePromises.push(TemplatesService.createApprovalTemplate({
- name: node.unifiedJobTemplate.name
- }).then(({data: approvalTemplateData}) => {
- // Make sure that this isn't overwriting everything on the node...
- editPromises.push(TemplatesService.editWorkflowNode({
- id: node.originalNodeObject.id,
- data: {
- unified_job_template: approvalTemplateData.id
- }
- }).catch(({ data, status }) => {
- Wait('stop');
- ProcessErrors($scope, data, status, null, {
- hdr: $scope.strings.get('error.HEADER')
- });
- }));
+ url: node.originalNodeObject.related.create_approval_template,
+ data: {
+ name: node.unifiedJobTemplate.name,
+ description: node.unifiedJobTemplate.description
+ }
}).catch(({ data, status }) => {
Wait('stop');
ProcessErrors($scope, data, status, null, {
@@ -302,176 +296,173 @@ export default ['$scope', 'TemplatesService',
return TemplatesService.deleteWorkflowJobTemplateNode(nodeId);
});
- $q.all(approvalTemplatePromises)
+ $q.all(addPromises.concat(editPromises, deletePromises))
.then(() => {
- $q.all(addPromises.concat(editPromises, deletePromises))
- .then(() => {
- let disassociatePromises = [];
- let associatePromises = [];
- let linkMap = {};
+ $q.all(approvalTemplatePromises)
+ .then(() => {
+ let disassociatePromises = [];
+ let associatePromises = [];
+ let linkMap = {};
- // Build a link map for easy access
- $scope.graphState.arrayOfLinksForChart.forEach(link => {
- // link.source.id of 1 is our artificial start node
- if (link.source.id !== 1) {
- const sourceNodeId = nodeRef[link.source.id].originalNodeObject.id;
- const targetNodeId = nodeRef[link.target.id].originalNodeObject.id;
- if (!linkMap[sourceNodeId]) {
- linkMap[sourceNodeId] = {};
+ // Build a link map for easy access
+ $scope.graphState.arrayOfLinksForChart.forEach(link => {
+ // link.source.id of 1 is our artificial start node
+ if (link.source.id !== 1) {
+ const sourceNodeId = nodeRef[link.source.id].originalNodeObject.id;
+ const targetNodeId = nodeRef[link.target.id].originalNodeObject.id;
+ if (!linkMap[sourceNodeId]) {
+ linkMap[sourceNodeId] = {};
+ }
+
+ linkMap[sourceNodeId][targetNodeId] = link.edgeType;
}
+ });
- linkMap[sourceNodeId][targetNodeId] = link.edgeType;
- }
- });
-
- Object.keys(nodeRef).map((workflowNodeId) => {
- let nodeId = nodeRef[workflowNodeId].originalNodeObject.id;
- if (nodeRef[workflowNodeId].originalNodeObject.success_nodes) {
- nodeRef[workflowNodeId].originalNodeObject.success_nodes.forEach((successNodeId) => {
- if (
- !deletedNodeIds.includes(successNodeId) &&
- (!linkMap[nodeId] ||
- !linkMap[nodeId][successNodeId] ||
- linkMap[nodeId][successNodeId] !== "success")
- ) {
- disassociatePromises.push(
- TemplatesService.disassociateWorkflowNode({
- parentId: nodeId,
- nodeId: successNodeId,
- edge: "success"
- })
- );
- }
- });
- }
- if (nodeRef[workflowNodeId].originalNodeObject.failure_nodes) {
- nodeRef[workflowNodeId].originalNodeObject.failure_nodes.forEach((failureNodeId) => {
- if (
- !deletedNodeIds.includes(failureNodeId) &&
- (!linkMap[nodeId] ||
- !linkMap[nodeId][failureNodeId] ||
- linkMap[nodeId][failureNodeId] !== "failure")
- ) {
- disassociatePromises.push(
- TemplatesService.disassociateWorkflowNode({
- parentId: nodeId,
- nodeId: failureNodeId,
- edge: "failure"
- })
- );
- }
- });
- }
- if (nodeRef[workflowNodeId].originalNodeObject.always_nodes) {
- nodeRef[workflowNodeId].originalNodeObject.always_nodes.forEach((alwaysNodeId) => {
- if (
- !deletedNodeIds.includes(alwaysNodeId) &&
- (!linkMap[nodeId] ||
- !linkMap[nodeId][alwaysNodeId] ||
- linkMap[nodeId][alwaysNodeId] !== "always")
- ) {
- disassociatePromises.push(
- TemplatesService.disassociateWorkflowNode({
- parentId: nodeId,
- nodeId: alwaysNodeId,
- edge: "always"
- })
- );
- }
- });
- }
- });
-
- Object.keys(linkMap).map((sourceNodeId) => {
- Object.keys(linkMap[sourceNodeId]).map((targetNodeId) => {
- const sourceChartNodeId = nodeIdToChartNodeIdMapping[sourceNodeId];
- const targetChartNodeId = nodeIdToChartNodeIdMapping[targetNodeId];
- switch(linkMap[sourceNodeId][targetNodeId]) {
- case "success":
+ Object.keys(nodeRef).map((workflowNodeId) => {
+ let nodeId = nodeRef[workflowNodeId].originalNodeObject.id;
+ if (nodeRef[workflowNodeId].originalNodeObject.success_nodes) {
+ nodeRef[workflowNodeId].originalNodeObject.success_nodes.forEach((successNodeId) => {
if (
- !nodeRef[sourceChartNodeId].originalNodeObject.success_nodes ||
- !nodeRef[sourceChartNodeId].originalNodeObject.success_nodes.includes(nodeRef[targetChartNodeId].originalNodeObject.id)
+ !deletedNodeIds.includes(successNodeId) &&
+ (!linkMap[nodeId] ||
+ !linkMap[nodeId][successNodeId] ||
+ linkMap[nodeId][successNodeId] !== "success")
) {
- associatePromises.push(
- TemplatesService.associateWorkflowNode({
- parentId: parseInt(sourceNodeId),
- nodeId: parseInt(targetNodeId),
+ disassociatePromises.push(
+ TemplatesService.disassociateWorkflowNode({
+ parentId: nodeId,
+ nodeId: successNodeId,
edge: "success"
})
);
}
- break;
- case "failure":
+ });
+ }
+ if (nodeRef[workflowNodeId].originalNodeObject.failure_nodes) {
+ nodeRef[workflowNodeId].originalNodeObject.failure_nodes.forEach((failureNodeId) => {
if (
- !nodeRef[sourceChartNodeId].originalNodeObject.failure_nodes ||
- !nodeRef[sourceChartNodeId].originalNodeObject.failure_nodes.includes(nodeRef[targetChartNodeId].originalNodeObject.id)
+ !deletedNodeIds.includes(failureNodeId) &&
+ (!linkMap[nodeId] ||
+ !linkMap[nodeId][failureNodeId] ||
+ linkMap[nodeId][failureNodeId] !== "failure")
) {
- associatePromises.push(
- TemplatesService.associateWorkflowNode({
- parentId: parseInt(sourceNodeId),
- nodeId: parseInt(targetNodeId),
+ disassociatePromises.push(
+ TemplatesService.disassociateWorkflowNode({
+ parentId: nodeId,
+ nodeId: failureNodeId,
edge: "failure"
})
);
}
- break;
- case "always":
+ });
+ }
+ if (nodeRef[workflowNodeId].originalNodeObject.always_nodes) {
+ nodeRef[workflowNodeId].originalNodeObject.always_nodes.forEach((alwaysNodeId) => {
if (
- !nodeRef[sourceChartNodeId].originalNodeObject.always_nodes ||
- !nodeRef[sourceChartNodeId].originalNodeObject.always_nodes.includes(nodeRef[targetChartNodeId].originalNodeObject.id)
+ !deletedNodeIds.includes(alwaysNodeId) &&
+ (!linkMap[nodeId] ||
+ !linkMap[nodeId][alwaysNodeId] ||
+ linkMap[nodeId][alwaysNodeId] !== "always")
) {
- associatePromises.push(
- TemplatesService.associateWorkflowNode({
- parentId: parseInt(sourceNodeId),
- nodeId: parseInt(targetNodeId),
+ disassociatePromises.push(
+ TemplatesService.disassociateWorkflowNode({
+ parentId: nodeId,
+ nodeId: alwaysNodeId,
edge: "always"
})
);
}
- break;
+ });
}
});
- });
- $q.all(disassociatePromises)
- .then(() => {
- let credentialPromises = credentialRequests.map((request) => {
- return TemplatesService.postWorkflowNodeCredential({
- id: request.id,
- data: request.data
- });
- });
-
- return $q.all(associatePromises.concat(credentialPromises))
- .then(() => {
- Wait('stop');
- $scope.workflowChangesUnsaved = false;
- $scope.workflowChangesStarted = false;
- $scope.closeDialog();
- }).catch(({ data, status }) => {
- Wait('stop');
- ProcessErrors($scope, data, status, null, {
- hdr: $scope.strings.get('error.HEADER')
- });
- });
- }).catch(({
- data,
- status
- }) => {
- Wait('stop');
- ProcessErrors($scope, data, status, null, {
- hdr: $scope.strings.get('error.HEADER')
+ Object.keys(linkMap).map((sourceNodeId) => {
+ Object.keys(linkMap[sourceNodeId]).map((targetNodeId) => {
+ const sourceChartNodeId = nodeIdToChartNodeIdMapping[sourceNodeId];
+ const targetChartNodeId = nodeIdToChartNodeIdMapping[targetNodeId];
+ switch(linkMap[sourceNodeId][targetNodeId]) {
+ case "success":
+ if (
+ !nodeRef[sourceChartNodeId].originalNodeObject.success_nodes ||
+ !nodeRef[sourceChartNodeId].originalNodeObject.success_nodes.includes(nodeRef[targetChartNodeId].originalNodeObject.id)
+ ) {
+ associatePromises.push(
+ TemplatesService.associateWorkflowNode({
+ parentId: parseInt(sourceNodeId),
+ nodeId: parseInt(targetNodeId),
+ edge: "success"
+ })
+ );
+ }
+ break;
+ case "failure":
+ if (
+ !nodeRef[sourceChartNodeId].originalNodeObject.failure_nodes ||
+ !nodeRef[sourceChartNodeId].originalNodeObject.failure_nodes.includes(nodeRef[targetChartNodeId].originalNodeObject.id)
+ ) {
+ associatePromises.push(
+ TemplatesService.associateWorkflowNode({
+ parentId: parseInt(sourceNodeId),
+ nodeId: parseInt(targetNodeId),
+ edge: "failure"
+ })
+ );
+ }
+ break;
+ case "always":
+ if (
+ !nodeRef[sourceChartNodeId].originalNodeObject.always_nodes ||
+ !nodeRef[sourceChartNodeId].originalNodeObject.always_nodes.includes(nodeRef[targetChartNodeId].originalNodeObject.id)
+ ) {
+ associatePromises.push(
+ TemplatesService.associateWorkflowNode({
+ parentId: parseInt(sourceNodeId),
+ nodeId: parseInt(targetNodeId),
+ edge: "always"
+ })
+ );
+ }
+ break;
+ }
});
});
- }).catch(({ data, status }) => {
- Wait('stop');
- ProcessErrors($scope, data, status, null, {
- hdr: $scope.strings.get('error.HEADER')
+
+ $q.all(disassociatePromises)
+ .then(() => {
+ let credentialPromises = credentialRequests.map((request) => {
+ return TemplatesService.postWorkflowNodeCredential({
+ id: request.id,
+ data: request.data
+ });
+ });
+
+ return $q.all(associatePromises.concat(credentialPromises))
+ .then(() => {
+ Wait('stop');
+ $scope.workflowChangesUnsaved = false;
+ $scope.workflowChangesStarted = false;
+ $scope.closeDialog();
+ }).catch(({ data, status }) => {
+ Wait('stop');
+ ProcessErrors($scope, data, status, null, {
+ hdr: $scope.strings.get('error.HEADER')
+ });
+ });
+ }).catch(({
+ data,
+ status
+ }) => {
+ Wait('stop');
+ ProcessErrors($scope, data, status, null, {
+ hdr: $scope.strings.get('error.HEADER')
+ });
+ });
});
+ }).catch(({ data, status }) => {
+ Wait('stop');
+ ProcessErrors($scope, data, status, null, {
+ hdr: $scope.strings.get('error.HEADER')
});
- })
- .catch(() => {
- // TODO: handle
});
} else {
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 fabaa4c04c..92e904aa3d 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
@@ -68,39 +68,7 @@
-
- -
-
{{strings.get('workflow_maker.KEY')}}
-
- -
-
-
{{strings.get('workflow_maker.ON_SUCCESS')}}
-
- -
-
-
{{strings.get('workflow_maker.ON_FAILURE')}}
-
- -
-
-
{{strings.get('workflow_maker.ALWAYS')}}
-
- -
-
P
- {{strings.get('workflow_maker.PROJECT_SYNC')}}
-
- -
-
I
- {{strings.get('workflow_maker.INVENTORY_SYNC')}}
-
- -
-
W
- {{strings.get('workflow_maker.WORKFLOW')}}
-
- -
-
!
- {{strings.get('workflow_maker.WARNING')}}
-
-
+
{{strings.get('workflow_maker.TOTAL_NODES')}}
diff --git a/awx/ui/client/src/workflow-results/workflow-results.partial.html b/awx/ui/client/src/workflow-results/workflow-results.partial.html
index 4fad491288..61ca637641 100644
--- a/awx/ui/client/src/workflow-results/workflow-results.partial.html
+++ b/awx/ui/client/src/workflow-results/workflow-results.partial.html
@@ -327,35 +327,7 @@
-
- -
-
{{strings.legend.KEY}}
-
- -
-
-
{{strings.legend.ON_SUCCESS}}
-
- -
-
-
{{strings.legend.ON_FAILURE}}
-
- -
-
-
{{strings.legend.ALWAYS}}
-
- -
-
P
- {{strings.legend.PROJECT_SYNC}}
-
- -
-
I
- {{strings.legend.INVENTORY_SYNC}}
-
- -
-
W
- {{strings.legend.WORKFLOW}}
-
-
+