Merge pull request #4389 from mabashian/4165-add-edit-node-audit

Add/Edit workflow from audit items
This commit is contained in:
Michael Abashian 2016-12-12 15:14:21 -05:00 committed by GitHub
commit e00e4f6a10
11 changed files with 417 additions and 425 deletions

View File

@ -170,7 +170,7 @@ export default
ngClick: 'cancelNodeForm()',
ngShow: '!canAddWorkflowJobTemplate'
},
save: {
select: {
ngClick: 'saveNodeForm()',
ngDisabled: "workflow_maker_form.$invalid || !selectedTemplate",
ngShow: 'canAddWorkflowJobTemplate'

View File

@ -16,9 +16,10 @@ export default
.factory('WorkflowFormObject', ['i18n', function(i18n) {
return {
addTitle: i18n._('New Workflow'),
addTitle: i18n._('New Workflow Job Template'),
editTitle: '{{ name }}',
name: 'workflow_job_template',
breadcrumbName: i18n._('WORKFLOW'),
base: 'workflow',
basePath: 'workflow_job_templates',
// the top-most node of generated state tree

View File

@ -1687,6 +1687,10 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
button.label = i18n._('Save');
button['class'] = 'Form-saveButton';
}
if (btn === 'select') {
button.label = i18n._('Select');
button['class'] = 'Form-saveButton';
}
if (btn === 'cancel') {
button.label = i18n._('Cancel');
button['class'] = 'Form-cancelButton';

View File

@ -17,7 +17,7 @@
</div>
<div class="modal-footer">
<button ng-click="cancelForm()" class="Lookup-cancel btn btn-default">Cancel</button>
<button ng-click="saveForm()" class="Lookup-save btn btn-primary">Save</button>
<button ng-click="saveForm()" class="Lookup-save btn btn-primary" ng-bind="list.lookupConfirmText ? list.lookupConfirmText : 'Save'"></button>
</div>
</div>
</div>

View File

@ -401,8 +401,10 @@ angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplatesA
}
},
resolve: {
ListDefinition: ['InventoryList', function(list) {
ListDefinition: ['InventoryList', function(InventoryList) {
// mutate the provided list definition here
let list = _.cloneDeep(InventoryList);
list.lookupConfirmText = 'SELECT';
return list;
}],
Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath',
@ -451,8 +453,9 @@ angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplatesA
}
},
resolve: {
ListDefinition: ['CredentialList', function(list) {
// mutate the provided list definition here
ListDefinition: ['CredentialList', function(CredentialList) {
let list = _.cloneDeep(CredentialList);
list.lookupConfirmText = 'SELECT';
return list;
}],
Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath',

View File

@ -33,10 +33,6 @@
$scope.parseType = 'yaml';
$scope.includeWorkflowMaker = false;
$scope.editRequests = [];
$scope.associateRequests = [];
$scope.disassociateRequests = [];
function init() {
// Select2-ify the lables input
@ -111,33 +107,6 @@
});
});
// Get the workflow nodes
TemplatesService.getWorkflowJobTemplateNodes(id)
.then(function(data){
$scope.workflowTree = WorkflowService.buildTree({
workflowNodes: data.data.results
});
// TODO: I think that the workflow chart directive (and eventually d3) is meddling with
// this workflowTree object and removing the children object for some reason (?)
// This happens on occasion and I think is a race condition (?)
if(!$scope.workflowTree.data.children) {
$scope.workflowTree.data.children = [];
}
// In the partial, the workflow maker directive has an ng-if attribute which is pointed at this scope variable.
// It won't get included until this the tree has been built - I'm open to better ways of doing this.
$scope.includeWorkflowMaker = true;
}, function(error){
ProcessErrors($scope, error.data, error.status, form, {
hdr: 'Error!',
msg: 'Failed to get workflow job template nodes. GET returned ' +
'status: ' + error.status
});
});
// Go out and GET the workflow job temlate data needed to populate the form
TemplatesService.getWorkflowJobTemplate(id)
.then(function(data){
@ -180,6 +149,35 @@
$scope.url = workflowJobTemplateData.url;
$scope.survey_enabled = workflowJobTemplateData.survey_enabled;
// Get the workflow nodes
TemplatesService.getWorkflowJobTemplateNodes(id)
.then(function(data){
$scope.workflowTree = WorkflowService.buildTree({
workflowNodes: data.data.results
});
// TODO: I think that the workflow chart directive (and eventually d3) is meddling with
// this workflowTree object and removing the children object for some reason (?)
// This happens on occasion and I think is a race condition (?)
if(!$scope.workflowTree.data.children) {
$scope.workflowTree.data.children = [];
}
$scope.workflowTree.workflow_job_template_obj = $scope.workflow_job_template_obj;
// In the partial, the workflow maker directive has an ng-if attribute which is pointed at this scope variable.
// It won't get included until this the tree has been built - I'm open to better ways of doing this.
$scope.includeWorkflowMaker = true;
}, function(error){
ProcessErrors($scope, error.data, error.status, form, {
hdr: 'Error!',
msg: 'Failed to get workflow job template nodes. GET returned ' +
'status: ' + error.status
});
});
}, function(error){
ProcessErrors($scope, error.data, error.status, form, {
hdr: 'Error!',
@ -189,160 +187,6 @@
});
}
function recursiveNodeUpdates(params, completionCallback) {
// params.parentId
// params.node
let generatePostUrl = function(){
let base = (params.parentId) ? GetBasePath('workflow_job_template_nodes') + params.parentId : $scope.workflow_job_template_obj.related.workflow_nodes;
if(params.parentId) {
if(params.node.edgeType === 'success') {
base += "/success_nodes";
}
else if(params.node.edgeType === 'failure') {
base += "/failure_nodes";
}
else if(params.node.edgeType === 'always') {
base += "/always_nodes";
}
}
return base;
};
let buildSendableNodeData = function() {
// Create the node
let sendableNodeData = {
unified_job_template: params.node.unifiedJobTemplate.id
};
// Check to see if the user has provided any prompt values that are different
// from the defaults in the job template
if(params.node.unifiedJobTemplate.type === "job_template" && params.node.promptValues) {
if(params.node.unifiedJobTemplate.ask_credential_on_launch) {
sendableNodeData.credential = !params.node.promptValues.credential || params.node.unifiedJobTemplate.summary_fields.credential.id !== params.node.promptValues.credential.id ? params.node.promptValues.credential.id : null;
}
if(params.node.unifiedJobTemplate.ask_inventory_on_launch) {
sendableNodeData.inventory = !params.node.promptValues.inventory || params.node.unifiedJobTemplate.summary_fields.inventory.id !== params.node.promptValues.inventory.id ? params.node.promptValues.inventory.id : null;
}
if(params.node.unifiedJobTemplate.ask_limit_on_launch) {
sendableNodeData.limit = !params.node.promptValues.limit || params.node.unifiedJobTemplate.limit !== params.node.promptValues.limit ? params.node.promptValues.limit : null;
}
if(params.node.unifiedJobTemplate.ask_job_type_on_launch) {
sendableNodeData.job_type = !params.node.promptValues.job_type || params.node.unifiedJobTemplate.job_type !== params.node.promptValues.job_type ? params.node.promptValues.job_type : null;
}
if(params.node.unifiedJobTemplate.ask_tags_on_launch) {
sendableNodeData.job_tags = !params.node.promptValues.job_tags || params.node.unifiedJobTemplate.job_tags !== params.node.promptValues.job_tags ? params.node.promptValues.job_tags : null;
}
if(params.node.unifiedJobTemplate.ask_skip_tags_on_launch) {
sendableNodeData.skip_tags = !params.node.promptValues.skip_tags || params.node.unifiedJobTemplate.skip_tags !== params.node.promptValues.skip_tags ? params.node.promptValues.skip_tags : null;
}
}
return sendableNodeData;
};
let continueRecursing = function(parentId) {
$scope.totalIteratedNodes++;
if($scope.totalIteratedNodes === $scope.workflowTree.data.totalNodes) {
// We're done recursing, lets move on
completionCallback();
}
else {
if(params.node.children && params.node.children.length > 0) {
_.forEach(params.node.children, function(child) {
if(child.edgeType === "success") {
recursiveNodeUpdates({
parentId: parentId,
node: child
}, completionCallback);
}
else if(child.edgeType === "failure") {
recursiveNodeUpdates({
parentId: parentId,
node: child
}, completionCallback);
}
else if(child.edgeType === "always") {
recursiveNodeUpdates({
parentId: parentId,
node: child
}, completionCallback);
}
});
}
}
};
if(params.node.isNew) {
TemplatesService.addWorkflowNode({
url: generatePostUrl(),
data: buildSendableNodeData()
})
.then(function(data) {
continueRecursing(data.data.id);
}, function(error) {
ProcessErrors($scope, error.data, error.status, form, {
hdr: 'Error!',
msg: 'Failed to add workflow node. ' +
'POST returned status: ' +
error.status
});
});
}
else {
if(params.node.edited || !params.node.originalParentId || (params.node.originalParentId && params.parentId !== params.node.originalParentId)) {
if(params.node.edited) {
$scope.editRequests.push({
id: params.node.nodeId,
data: buildSendableNodeData()
});
}
if((params.node.originalParentId && params.parentId !== params.node.originalParentId) || params.node.originalEdge !== params.node.edgeType) {//beep
$scope.disassociateRequests.push({
parentId: params.node.originalParentId,
nodeId: params.node.nodeId,
edge: params.node.originalEdge
});
// Can only associate if we have a parent.
// If we don't have a parent then this is a root node
// and the act of disassociating will make it a root node
if(params.parentId) {
$scope.associateRequests.push({
parentId: params.parentId,
nodeId: params.node.nodeId,
edge: params.node.edgeType
});
}
}
else if(!params.node.originalParentId && params.parentId) {
// This used to be a root node but is now not a root node
$scope.associateRequests.push({
parentId: params.parentId,
nodeId: params.node.nodeId,
edge: params.node.edgeType
});
}
}
continueRecursing(params.node.nodeId);
}
}
$scope.openWorkflowMaker = function() {
$state.go('.workflowMaker');
};
@ -392,231 +236,97 @@
.filter("[data-label-is-present=true]")
.map((i, val) => ({name: $(val).text()}));
$scope.totalIteratedNodes = 0;
TemplatesService.updateWorkflowJobTemplate({
id: id,
data: data
}).then(function(){
// TODO: this is the only way that I could figure out to get
// these promise arrays to play nicely. I tried to just append
// a single promise to deletePromises but it just wasn't working
let editWorkflowJobTemplate = [id].map(function(id) {
return TemplatesService.updateWorkflowJobTemplate({
id: id,
data: data
});
});
var orgDefer = $q.defer();
var associationDefer = $q.defer();
var associatedLabelsDefer = $q.defer();
if($scope.workflowTree && $scope.workflowTree.data && $scope.workflowTree.data.children && $scope.workflowTree.data.children.length > 0) {
let completionCallback = function() {
let disassociatePromises = $scope.disassociateRequests.map(function(request) {
return TemplatesService.disassociateWorkflowNode({
parentId: request.parentId,
nodeId: request.nodeId,
edge: request.edge
var getNext = function(data, arr, resolve) {
Rest.setUrl(data.next);
Rest.get()
.success(function (data) {
if (data.next) {
getNext(data, arr.concat(data.results), resolve);
} else {
resolve.resolve(arr.concat(data.results));
}
});
});
let editNodePromises = $scope.editRequests.map(function(request) {
return TemplatesService.editWorkflowNode({
id: request.id,
data: request.data
});
});
$q.all(disassociatePromises.concat(editNodePromises).concat(editWorkflowJobTemplate))
.then(function() {
let associatePromises = $scope.associateRequests.map(function(request) {
return TemplatesService.associateWorkflowNode({
parentId: request.parentId,
nodeId: request.nodeId,
edge: request.edge
});
});
let deletePromises = $scope.workflowTree.data.deletedNodes.map(function(nodeId) {
return TemplatesService.deleteWorkflowJobTemplateNode(nodeId);
});
$q.all(associatePromises.concat(deletePromises))
.then(function() {
var orgDefer = $q.defer();
var associationDefer = $q.defer();
var associatedLabelsDefer = $q.defer();
var getNext = function(data, arr, resolve) {
Rest.setUrl(data.next);
Rest.get()
.success(function (data) {
if (data.next) {
getNext(data, arr.concat(data.results), resolve);
} else {
resolve.resolve(arr.concat(data.results));
}
});
};
Rest.setUrl($scope.workflow_job_template_obj.related.labels);
Rest.get()
.success(function(data) {
if (data.next) {
getNext(data, data.results, associatedLabelsDefer);
} else {
associatedLabelsDefer.resolve(data.results);
}
});
associatedLabelsDefer.promise.then(function (current) {
current = current.map(data => data.id);
var labelsToAdd = $scope.labels
.map(val => val.value);
var labelsToDisassociate = current
.filter(val => labelsToAdd
.indexOf(val) === -1)
.map(val => ({id: val, disassociate: true}));
var labelsToAssociate = labelsToAdd
.filter(val => current
.indexOf(val) === -1)
.map(val => ({id: val, associate: true}));
var pass = labelsToDisassociate
.concat(labelsToAssociate);
associationDefer.resolve(pass);
});
Rest.setUrl(GetBasePath("organizations"));
Rest.get()
.success(function(data) {
orgDefer.resolve(data.results[0].id);
});
orgDefer.promise.then(function(orgId) {
var toPost = [];
$scope.newLabels = $scope.newLabels
.map(function(i, val) {
val.organization = orgId;
return val;
});
$scope.newLabels.each(function(i, val) {
toPost.push(val);
});
associationDefer.promise.then(function(arr) {
toPost = toPost
.concat(arr);
Rest.setUrl($scope.workflow_job_template_obj.related.labels);
var defers = [];
for (var i = 0; i < toPost.length; i++) {
defers.push(Rest.post(toPost[i]));
}
$q.all(defers)
.then(function() {
$state.go('templates.editWorkflowJobTemplate', {id: id}, {reload: true});
});
});
});
});
});
};
_.forEach($scope.workflowTree.data.children, function(child) {
recursiveNodeUpdates({
node: child
}, completionCallback);
});
}
else {
Rest.setUrl($scope.workflow_job_template_obj.related.labels);
let deletePromises = $scope.workflowTree.data.deletedNodes.map(function(nodeId) {
return TemplatesService.deleteWorkflowJobTemplateNode(nodeId);
});
$q.all(deletePromises.concat(editWorkflowJobTemplate))
.then(function() {
var orgDefer = $q.defer();
var associationDefer = $q.defer();
var associatedLabelsDefer = $q.defer();
var getNext = function(data, arr, resolve) {
Rest.setUrl(data.next);
Rest.get()
.success(function (data) {
if (data.next) {
getNext(data, arr.concat(data.results), resolve);
} else {
resolve.resolve(arr.concat(data.results));
}
});
};
Rest.setUrl($scope.workflow_job_template_obj.related.labels);
Rest.get()
.success(function(data) {
if (data.next) {
getNext(data, data.results, associatedLabelsDefer);
} else {
associatedLabelsDefer.resolve(data.results);
}
});
associatedLabelsDefer.promise.then(function (current) {
current = current.map(data => data.id);
var labelsToAdd = $scope.labels
.map(val => val.value);
var labelsToDisassociate = current
.filter(val => labelsToAdd
.indexOf(val) === -1)
.map(val => ({id: val, disassociate: true}));
var labelsToAssociate = labelsToAdd
.filter(val => current
.indexOf(val) === -1)
.map(val => ({id: val, associate: true}));
var pass = labelsToDisassociate
.concat(labelsToAssociate);
associationDefer.resolve(pass);
Rest.get()
.success(function(data) {
if (data.next) {
getNext(data, data.results, associatedLabelsDefer);
} else {
associatedLabelsDefer.resolve(data.results);
}
});
Rest.setUrl(GetBasePath("organizations"));
Rest.get()
.success(function(data) {
orgDefer.resolve(data.results[0].id);
associatedLabelsDefer.promise.then(function (current) {
current = current.map(data => data.id);
var labelsToAdd = $scope.labels
.map(val => val.value);
var labelsToDisassociate = current
.filter(val => labelsToAdd
.indexOf(val) === -1)
.map(val => ({id: val, disassociate: true}));
var labelsToAssociate = labelsToAdd
.filter(val => current
.indexOf(val) === -1)
.map(val => ({id: val, associate: true}));
var pass = labelsToDisassociate
.concat(labelsToAssociate);
associationDefer.resolve(pass);
});
Rest.setUrl(GetBasePath("organizations"));
Rest.get()
.success(function(data) {
orgDefer.resolve(data.results[0].id);
});
orgDefer.promise.then(function(orgId) {
var toPost = [];
$scope.newLabels = $scope.newLabels
.map(function(i, val) {
val.organization = orgId;
return val;
});
orgDefer.promise.then(function(orgId) {
var toPost = [];
$scope.newLabels = $scope.newLabels
.map(function(i, val) {
val.organization = orgId;
return val;
$scope.newLabels.each(function(i, val) {
toPost.push(val);
});
associationDefer.promise.then(function(arr) {
toPost = toPost
.concat(arr);
Rest.setUrl($scope.workflow_job_template_obj.related.labels);
var defers = [];
for (var i = 0; i < toPost.length; i++) {
defers.push(Rest.post(toPost[i]));
}
$q.all(defers)
.then(function() {
$state.go('templates.editWorkflowJobTemplate', {id: id}, {reload: true});
});
$scope.newLabels.each(function(i, val) {
toPost.push(val);
});
associationDefer.promise.then(function(arr) {
toPost = toPost
.concat(arr);
Rest.setUrl($scope.workflow_job_template_obj.related.labels);
var defers = [];
for (var i = 0; i < toPost.length; i++) {
defers.push(Rest.post(toPost[i]));
}
$q.all(defers)
.then(function() {
$state.go('templates.editWorkflowJobTemplate', {id: id}, {reload: true});
});
});
});
});
}
}, function(error){
ProcessErrors($scope, error.data, error.status, form, {
hdr: 'Error!',
msg: 'Failed to update workflow job template. PUT returned ' +
'status: ' + error.status
});
});
} catch (err) {
Wait('stop');

View File

@ -29,6 +29,11 @@
fill: @default-interface-txt;
}
.WorkflowChart-startText {
fill: @default-bg;
cursor: default;
}
.node .rect {
fill: @default-secondary-bg;
}
@ -97,3 +102,6 @@
width: 90px;
color: @default-interface-txt;
}
.WorkflowChart-activeNode {
fill: @default-link;
}

View File

@ -84,6 +84,25 @@ export default [ '$state',
}
}
function rounded_rect(x, y, w, h, r, tl, tr, bl, br) {
var retval;
retval = "M" + (x + r) + "," + y;
retval += "h" + (w - 2*r);
if (tr) { retval += "a" + r + "," + r + " 0 0 1 " + r + "," + r; }
else { retval += "h" + r; retval += "v" + r; }
retval += "v" + (h - 2*r);
if (br) { retval += "a" + r + "," + r + " 0 0 1 " + -r + "," + r; }
else { retval += "v" + r; retval += "h" + -r; }
retval += "h" + (2*r - w);
if (bl) { retval += "a" + r + "," + r + " 0 0 1 " + -r + "," + -r; }
else { retval += "h" + -r; retval += "v" + -r; }
retval += "v" + (2*r - h);
if (tl) { retval += "a" + r + "," + r + " 0 0 1 " + r + "," + -r; }
else { retval += "v" + -r; retval += "h" + r; }
retval += "z";
return retval;
}
// This is the zoom function called by using the mousewheel/click and drag
function naturalZoom() {
let scale = d3.event.scale,
@ -163,20 +182,13 @@ export default [ '$state',
.attr("fill", "#5cb85c")
.attr("class", "WorkflowChart-rootNode")
.call(add_node);
thisNode.append("path")
.style("fill", "white")
.attr("transform", function() { return "translate(" + 30 + "," + 30 + ")"; })
.attr("d", d3.svg.symbol()
.size(120)
.type("cross")
)
.call(add_node);
thisNode.append("text")
.attr("x", 14)
.attr("y", 0)
.attr("x", 13)
.attr("y", 30)
.attr("dy", ".35em")
.attr("class", "WorkflowChart-defaultText")
.text(function () { return "START"; });
.attr("class", "WorkflowChart-startText")
.text(function () { return "START"; })
.call(add_node);
}
else {
thisNode.append("rect")
@ -184,12 +196,32 @@ export default [ '$state',
.attr("height", rectH)
.attr("rx", 5)
.attr("ry", 5)
.attr('stroke', function(d) { return d.isActiveEdit ? "#337ab7" : "#D7D7D7"; })
.attr('stroke-width', function(d){ return d.isActiveEdit ? "2px" : "1px"; })
.attr('stroke', function(d) {
if(d.edgeType) {
if(d.edgeType === "failure") {
return "#d9534f";
}
else if(d.edgeType === "success") {
return "#5cb85c";
}
else if(d.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, rectH, 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.jobStatus) ? 20 : rectW / 2; })
.attr("y", function(d){ return (scope.mode === 'details' && d.job && d.job.jobStatus) ? 10 : rectH / 2; })
@ -517,8 +549,22 @@ export default [ '$state',
.attr("transform", function(d) { return "translate(" + (d.target.y + d.source.y + rectW) / 2 + "," + (d.target.x + d.source.x + rectH) / 2 + ")"; });
t.selectAll(".rect")
.attr('stroke', function(d) { return d.isActiveEdit ? "#337ab7" : "#D7D7D7"; })
.attr('stroke-width', function(d){ return d.isActiveEdit ? "2px" : "1px"; })
.attr('stroke', function(d) {
if(d.edgeType) {
if(d.edgeType === "failure") {
return "#d9534f";
}
else if(d.edgeType === "success") {
return "#5cb85c";
}
else if(d.edgeType === "always"){
return "#337ab7";
}
}
else {
return "#D7D7D7";
}
})
.attr("class", function(d) {
return d.placeholder ? "rect placeholder" : "rect";
});
@ -601,6 +647,9 @@ export default [ '$state',
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"; });
}
function add_node() {

View File

@ -154,7 +154,7 @@
padding-left: 20px;
}
.WorkflowLegend-maker--right {
flex: 0 0 182px;
flex: 0 0 206px;
text-align: right;
padding-right: 20px;
position: relative;
@ -226,7 +226,7 @@
}
.WorkflowMaker-manualControls {
position: absolute;
left: -122px;
left: -86px;
height: 60px;
width: 293px;
background-color: @default-bg;

View File

@ -35,6 +35,10 @@ export default ['$scope', 'WorkflowService', 'generateList', 'TemplateList', 'Pr
showTypeOptions: false
};
$scope.editRequests = [];
$scope.associateRequests = [];
$scope.disassociateRequests = [];
function init() {
$scope.treeDataMaster = angular.copy($scope.treeData.data);
$scope.showManualControls = false;
@ -55,6 +59,160 @@ export default ['$scope', 'WorkflowService', 'generateList', 'TemplateList', 'Pr
$scope.workflowMakerFormConfig.activeTab = "jobs";
}
function recursiveNodeUpdates(params, completionCallback) {
// params.parentId
// params.node
let generatePostUrl = function(){
let base = (params.parentId) ? GetBasePath('workflow_job_template_nodes') + params.parentId : $scope.treeData.workflow_job_template_obj.related.workflow_nodes;
if(params.parentId) {
if(params.node.edgeType === 'success') {
base += "/success_nodes";
}
else if(params.node.edgeType === 'failure') {
base += "/failure_nodes";
}
else if(params.node.edgeType === 'always') {
base += "/always_nodes";
}
}
return base;
};
let buildSendableNodeData = function() {
// Create the node
let sendableNodeData = {
unified_job_template: params.node.unifiedJobTemplate.id
};
// Check to see if the user has provided any prompt values that are different
// from the defaults in the job template
if(params.node.unifiedJobTemplate.type === "job_template" && params.node.promptValues) {
if(params.node.unifiedJobTemplate.ask_credential_on_launch) {
sendableNodeData.credential = !params.node.promptValues.credential || params.node.unifiedJobTemplate.summary_fields.credential.id !== params.node.promptValues.credential.id ? params.node.promptValues.credential.id : null;
}
if(params.node.unifiedJobTemplate.ask_inventory_on_launch) {
sendableNodeData.inventory = !params.node.promptValues.inventory || params.node.unifiedJobTemplate.summary_fields.inventory.id !== params.node.promptValues.inventory.id ? params.node.promptValues.inventory.id : null;
}
if(params.node.unifiedJobTemplate.ask_limit_on_launch) {
sendableNodeData.limit = !params.node.promptValues.limit || params.node.unifiedJobTemplate.limit !== params.node.promptValues.limit ? params.node.promptValues.limit : null;
}
if(params.node.unifiedJobTemplate.ask_job_type_on_launch) {
sendableNodeData.job_type = !params.node.promptValues.job_type || params.node.unifiedJobTemplate.job_type !== params.node.promptValues.job_type ? params.node.promptValues.job_type : null;
}
if(params.node.unifiedJobTemplate.ask_tags_on_launch) {
sendableNodeData.job_tags = !params.node.promptValues.job_tags || params.node.unifiedJobTemplate.job_tags !== params.node.promptValues.job_tags ? params.node.promptValues.job_tags : null;
}
if(params.node.unifiedJobTemplate.ask_skip_tags_on_launch) {
sendableNodeData.skip_tags = !params.node.promptValues.skip_tags || params.node.unifiedJobTemplate.skip_tags !== params.node.promptValues.skip_tags ? params.node.promptValues.skip_tags : null;
}
}
return sendableNodeData;
};
let continueRecursing = function(parentId) {
$scope.totalIteratedNodes++;
if($scope.totalIteratedNodes === $scope.treeData.data.totalNodes) {
// We're done recursing, lets move on
completionCallback();
}
else {
if(params.node.children && params.node.children.length > 0) {
_.forEach(params.node.children, function(child) {
if(child.edgeType === "success") {
recursiveNodeUpdates({
parentId: parentId,
node: child
}, completionCallback);
}
else if(child.edgeType === "failure") {
recursiveNodeUpdates({
parentId: parentId,
node: child
}, completionCallback);
}
else if(child.edgeType === "always") {
recursiveNodeUpdates({
parentId: parentId,
node: child
}, completionCallback);
}
});
}
}
};
if(params.node.isNew) {
TemplatesService.addWorkflowNode({
url: generatePostUrl(),
data: buildSendableNodeData()
})
.then(function(data) {
continueRecursing(data.data.id);
}, function(error) {
ProcessErrors($scope, error.data, error.status, form, {
hdr: 'Error!',
msg: 'Failed to add workflow node. ' +
'POST returned status: ' +
error.status
});
});
}
else {
if(params.node.edited || !params.node.originalParentId || (params.node.originalParentId && params.parentId !== params.node.originalParentId)) {
if(params.node.edited) {
$scope.editRequests.push({
id: params.node.nodeId,
data: buildSendableNodeData()
});
}
if((params.node.originalParentId && params.parentId !== params.node.originalParentId) || params.node.originalEdge !== params.node.edgeType) {//beep
$scope.disassociateRequests.push({
parentId: params.node.originalParentId,
nodeId: params.node.nodeId,
edge: params.node.originalEdge
});
// Can only associate if we have a parent.
// If we don't have a parent then this is a root node
// and the act of disassociating will make it a root node
if(params.parentId) {
$scope.associateRequests.push({
parentId: params.parentId,
nodeId: params.node.nodeId,
edge: params.node.edgeType
});
}
}
else if(!params.node.originalParentId && params.parentId) {
// This used to be a root node but is now not a root node
$scope.associateRequests.push({
parentId: params.parentId,
nodeId: params.node.nodeId,
edge: params.node.edgeType
});
}
}
continueRecursing(params.node.nodeId);
}
}
$scope.lookUpInventory = function(){
$state.go('.inventory');
};
@ -70,7 +228,66 @@ export default ['$scope', 'WorkflowService', 'generateList', 'TemplateList', 'Pr
};
$scope.saveWorkflowMaker = function() {
$scope.closeDialog();
$scope.totalIteratedNodes = 0;
if($scope.treeData && $scope.treeData.data && $scope.treeData.data.children && $scope.treeData.data.children.length > 0) {
let completionCallback = function() {
let disassociatePromises = $scope.disassociateRequests.map(function(request) {
return TemplatesService.disassociateWorkflowNode({
parentId: request.parentId,
nodeId: request.nodeId,
edge: request.edge
});
});
let editNodePromises = $scope.editRequests.map(function(request) {
return TemplatesService.editWorkflowNode({
id: request.id,
data: request.data
});
});
$q.all(disassociatePromises.concat(editNodePromises))
.then(function() {
let associatePromises = $scope.associateRequests.map(function(request) {
return TemplatesService.associateWorkflowNode({
parentId: request.parentId,
nodeId: request.nodeId,
edge: request.edge
});
});
let deletePromises = $scope.treeData.data.deletedNodes.map(function(nodeId) {
return TemplatesService.deleteWorkflowJobTemplateNode(nodeId);
});
$q.all(associatePromises.concat(deletePromises))
.then(function() {
$scope.closeDialog();
});
});
};
_.forEach($scope.treeData.data.children, function(child) {
recursiveNodeUpdates({
node: child
}, completionCallback);
});
}
else {
let deletePromises = $scope.treeData.data.deletedNodes.map(function(nodeId) {
return TemplatesService.deleteWorkflowJobTemplateNode(nodeId);
});
$q.all(deletePromises)
.then(function() {
$scope.closeDialog();
});
}
};
/* ADD NODE FUNCTIONS */
@ -575,7 +792,7 @@ export default ['$scope', 'WorkflowService', 'generateList', 'TemplateList', 'Pr
edgeFlags: $scope.edgeFlags
});
}
$scope.toggleManualControls = function() {
$scope.showManualControls = !$scope.showManualControls;
};

View File

@ -47,10 +47,10 @@ describe('Controller: WorkflowMaker', () => {
}));
describe('scope.saveWorkflowMaker()', () => {
describe('scope.closeWorkflowMaker()', () => {
it('should close the dialog', ()=>{
scope.saveWorkflowMaker();
scope.closeWorkflowMaker();
expect(scope.closeDialog).toHaveBeenCalled();
});