Add initial support for workflow pause approve

This commit is contained in:
mabashian 2019-07-11 15:50:14 -04:00 committed by Ryan Petrello
parent 82e0b2121b
commit 0720857022
No known key found for this signature in database
GPG Key ID: F2AA5F2122351777
22 changed files with 917 additions and 429 deletions

View File

@ -117,7 +117,7 @@ function TemplatesStrings (BaseString) {
TOTAL_NODES: t.s('TOTAL NODES'),
ADD_A_NODE: t.s('ADD A NODE'),
EDIT_TEMPLATE: t.s('EDIT TEMPLATE'),
JOBS: t.s('JOBS'),
JOBS: t.s('Jobs'),
PLEASE_CLICK_THE_START_BUTTON: t.s('Please click the start button to build your workflow.'),
PLEASE_HOVER_OVER_A_TEMPLATE: t.s('Please hover over a template for additional options.'),
EDIT_LINK_TOOLTIP: t.s('Click to edit link'),
@ -144,7 +144,8 @@ function TemplatesStrings (BaseString) {
UNSAVED_CHANGES_PROMPT_TEXT: t.s('Are you sure you want to exit the Workflow Creator without saving your changes?'),
EXIT: t.s('EXIT'),
CANCEL: t.s('CANCEL'),
SAVE_AND_EXIT: t.s('SAVE & EXIT')
SAVE_AND_EXIT: t.s('SAVE & EXIT'),
PAUSE_NODE: t.s('Pause Node')
};
}

View File

@ -1981,11 +1981,6 @@ tr td button i {
box-shadow: none !important;
}
.select2-container {
margin-left: 2px;
margin-top: 2px;
}
.form-control + .select2-container--disabled .select2-selection {
background-color: @ebgrey !important;
}

View File

@ -1,4 +1,5 @@
@import 'action/_index';
@import 'approvalsDrawer/_index';
@import 'dialog/_index';
@import 'input/_index';
@import 'launchTemplateButton/_index';

View File

@ -0,0 +1,56 @@
.at-ApprovalsDrawer {
position: absolute;
right: 0px;
top: 0;
height: 100%;
width: 540px;
background-color: white;
z-index: 1000000;
animation-duration: 0.5s;
// TODO: fix animation?
// animation-name: slidein;
padding: 20px;
box-shadow: -3px 0px 8px -2px #aaaaaa;
&-header {
display: flex;
width: 100%;
margin-bottom: 20px;
}
&-title {
flex: 1 0 auto;
color: #606060;
font-size: 14px;
font-weight: bold;
width: calc(82%);
}
&-exit {
justify-content: flex-end;
display: flex;
button {
height: 20px;
font-size: 20px;
color: #D7D7D7;
line-height: 1;
opacity: 1;
}
button:hover{
color: @default-icon;
opacity: 1;
}
}
}
@keyframes slidein {
from {
width: 0px;
}
to {
width: 540px;
}
}

View File

@ -0,0 +1,86 @@
const templateUrl = require('~components/approvalsDrawer/approvalsDrawer.partial.html');
function AtApprovalsDrawerController (strings, Rest, GetBasePath, $rootScope) {
const vm = this || {};
const toolbarSortDefault = {
label: `${strings.get('sort.CREATED_ASCENDING')}`,
value: 'created'
};
vm.toolbarSortValue = toolbarSortDefault;
// This will probably need to be expanded
vm.toolbarSortOptions = [
toolbarSortDefault,
{ label: `${strings.get('sort.CREATED_DESCENDING')}`, value: '-created' }
];
vm.queryset = {
page_size: 5
};
vm.emptyListReason = strings.get('approvals.NONE');
const loadTheList = () => {
Rest.setUrl(`${GetBasePath('workflow_approval')}?page_size=5&order_by=created&status=pending`);
Rest.get()
.then(({ data }) => {
vm.dataset = data;
vm.approvals = data.results;
vm.count = data.count;
$rootScope.pendingApprovalCount = data.count;
vm.listLoaded = true;
});
};
loadTheList();
vm.onToolbarSort = (sort) => {
vm.toolbarSortValue = sort;
// TODO: this...
// const queryParams = Object.assign(
// {},
// $state.params.user_search,
// paginateQuerySet,
// { order_by: sort.value }
// );
// // Update URL with params
// $state.go('.', {
// user_search: queryParams
// }, { notify: false, location: 'replace' });
// rather than ^^ we want to just re-load the data based on new params
};
vm.approve = (approval) => {
Rest.setUrl(`${GetBasePath('workflow_approval')}${approval.id}/approve`);
Rest.post()
.then(() => loadTheList());
};
vm.deny = (approval) => {
Rest.setUrl(`${GetBasePath('workflow_approval')}${approval.id}/deny`);
Rest.post()
.then(() => loadTheList());
};
}
AtApprovalsDrawerController.$inject = ['ComponentsStrings', 'Rest', 'GetBasePath', '$rootScope'];
function atApprovalsDrawer () {
return {
restrict: 'E',
transclude: true,
templateUrl,
controller: AtApprovalsDrawerController,
controllerAs: 'vm',
scope: {
closeApprovals: '&'
},
};
}
export default atApprovalsDrawer;

View File

@ -0,0 +1,79 @@
<div style="width: 100%;height: 100%;position: fixed;top: 0;right: 0;opacity: 0.5;background-color: black;z-index: 10000;"></div>
<div class="at-ApprovalsDrawer" ng-if="vm.listLoaded">
<div class="at-ApprovalsDrawer-header">
<div class="at-ApprovalsDrawer-title">
<span>
NOTIFICATIONS
</span>
<span class="at-Panel-headingTitleBadge">
{{vm.count}}
</span>
</div>
<div class="at-ApprovalsDrawer-exit">
<button class="close" ng-click="closeApprovals()">
<i class="fa fa-times-circle"></i>
</button>
</div>
</div>
<at-list-toolbar
ng-if="vm.approvals.length > 0"
sort-only="true"
sort-value="vm.toolbarSortValue"
sort-options="vm.toolbarSortOptions"
on-sort="vm.onToolbarSort">
</at-list-toolbar>
<at-list results="vm.approvals" id="approvals_list" empty-list-reason="{{ vm.emptyListReason }}">
<at-row ng-repeat="approval in vm.approvals"
id="approval-row-{{ approval.id }}">
<div class="at-Row-items">
<div class="at-Row-container">
<div class="at-Row-container">
<!-- TODO: translate header tag -->
<at-row-item
header-value="{{ approval.summary_fields.source_workflow_job.name }}"
header-state="workflowResults({id: {{approval.summary_fields.source_workflow_job.id}}})"
header-tag="Workflow Template">
</at-row-item>
</div>
</div>
<div class="at-Row-container--wrapped">
<at-row-item
value-bind-html="{{ approval.created | longDate }}">
</at-row-item>
<at-row-item
style="color: red;"
value-bind-html="Expires: Never">
</at-row-item>
<!-- <at-row-item
style="color: red;"
value-bind-html="Expires {{ approval.created | longDate }}">
</at-row-item> -->
</div>
<div class="at-Row-container--wrapped">
<div style="display: flex; justify-content: flex-end; width: 100%; margin-top: 10px;">
<div>Continue workflow job?</div>
<button class="btn at-Button--success"
style="margin-left: 15px;"
ng-click="vm.approve(approval)"
type="button">
APPROVE
</button>
<button class="btn at-Button--error"
style="margin-left: 15px;"
ng-click="vm.deny(approval)"
type="button">
DENY
</button>
</div>
</div>
</div>
</at-row>
</at-list>
<paginate
collection="vm.approvals"
dataset="vm.dataset"
iterator="template"
base-path="unified_job_templates"
query-set="vm.queryset">
</paginate>
</div>

View File

@ -119,6 +119,10 @@ function ComponentsStrings (BaseString) {
EXPANDED: t.s('Expanded'),
SORT_BY: t.s('SORT BY')
};
ns.approvals = {
NONE: t.s('There are no jobs awaiting approval')
};
}
ComponentsStrings.$inject = ['BaseStringService'];

View File

@ -2,6 +2,7 @@ import atLibServices from '~services';
import actionGroup from '~components/action/action-group.directive';
import actionButton from '~components/action/action-button.directive';
import approvalsDrawer from '~components/approvalsDrawer/approvalsDrawer.directive';
import dialog from '~components/dialog/dialog.component';
import divider from '~components/utility/divider.directive';
import dynamicSelect from '~components/input/dynamic-select.directive';
@ -60,6 +61,7 @@ angular
])
.directive('atActionGroup', actionGroup)
.directive('atActionButton', actionButton)
.directive('atApprovalsDrawer', approvalsDrawer)
.component('atDialog', dialog)
.directive('atDivider', divider)
.directive('atDynamicSelect', dynamicSelect)

View File

@ -81,6 +81,23 @@
opacity: 0;
}
}
.at-Layout-topNavApprovals {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
div {
margin-left: 10px;
padding: 5px;
border-radius: 3px;
background-color: red;
color: white;
height: 15px;
font-size: 10px;
}
}
}
&-sideContainer {

View File

@ -25,6 +25,10 @@ function AtLayoutController ($scope, $http, strings, ProcessErrors, $transitions
}
});
$scope.$watch('$root.pendingApprovalCount', () => {
vm.approvalsCount = _.get($scope, '$root.pendingApprovalCount') || 0;
});
$scope.$watch('$root.socketStatus', (newStatus) => {
vm.socketState = newStatus;
vm.socketIconClass = `icon-socket-${vm.socketState}`;
@ -42,6 +46,14 @@ function AtLayoutController ($scope, $http, strings, ProcessErrors, $transitions
}
};
vm.openApprovals = () => {
vm.showApprovals = true;
};
vm.closeApprovals = () => {
vm.showApprovals = false;
};
function checkOrgAdmin () {
const usersPath = `/api/v2/users/${vm.currentUserId}/admin_of_organizations/`;
$http.get(usersPath)

View File

@ -14,6 +14,12 @@
<span>{{ $parent.layoutVm.currentUsername }}</span>
</a>
</at-top-nav-item>
<at-top-nav-item ng-click="vm.openApprovals()">
<div class="at-Layout-topNavApprovals">
<i class="fa fa-bell" alt="{{ vm.getString('NOTIFICATIONS') }}"></i>
<div>{{vm.approvalsCount}}</div>
</div>
</at-top-nav-item>
<at-top-nav-item>
<a ui-sref="about">
<i class="fa fa-info-circle" alt="{{ vm.getString('ABOUT') }}"></i>
@ -104,4 +110,5 @@
<ng-transclude></ng-transclude>
</div>
</div>
<at-approvals-drawer ng-if="vm.showApprovals" close-approvals="vm.closeApprovals()"></at-approvals-drawer>
</div>

View File

@ -1,7 +1,7 @@
import atLibServices from '~services';
import Application from '~models/Application';
import AdHocCommand from '~models/AdHocCommand';
import Application from '~models/Application';
import Base from '~models/Base';
import Config from '~models/Config';
import Credential from '~models/Credential';
@ -19,16 +19,16 @@ import Me from '~models/Me';
import NotificationTemplate from '~models/NotificationTemplate';
import Organization from '~models/Organization';
import Project from '~models/Project';
import Schedule from '~models/Schedule';
import ProjectUpdate from '~models/ProjectUpdate';
import Schedule from '~models/Schedule';
import SystemJob from '~models/SystemJob';
import Token from '~models/Token';
import UnifiedJob from '~models/UnifiedJob';
import UnifiedJobTemplate from '~models/UnifiedJobTemplate';
import User from '~models/User';
import WorkflowJob from '~models/WorkflowJob';
import WorkflowJobTemplate from '~models/WorkflowJobTemplate';
import WorkflowJobTemplateNode from '~models/WorkflowJobTemplateNode';
import UnifiedJob from '~models/UnifiedJob';
import User from '~models/User';
import ModelsStrings from '~models/models.strings';
@ -38,8 +38,8 @@ angular
.module(MODULE_NAME, [
atLibServices
])
.service('ApplicationModel', Application)
.service('AdHocCommandModel', AdHocCommand)
.service('ApplicationModel', Application)
.service('BaseModel', Base)
.service('ConfigModel', Config)
.service('CredentialModel', Credential)
@ -54,19 +54,19 @@ angular
.service('JobModel', Job)
.service('JobTemplateModel', JobTemplate)
.service('MeModel', Me)
.service('ModelsStrings', ModelsStrings)
.service('NotificationTemplate', NotificationTemplate)
.service('OrganizationModel', Organization)
.service('ProjectModel', Project)
.service('ScheduleModel', Schedule)
.service('UnifiedJobModel', UnifiedJob)
.service('ProjectUpdateModel', ProjectUpdate)
.service('ScheduleModel', Schedule)
.service('SystemJobModel', SystemJob)
.service('TokenModel', Token)
.service('UnifiedJobModel', UnifiedJob)
.service('UnifiedJobTemplateModel', UnifiedJobTemplate)
.service('UserModel', User)
.service('WorkflowJobModel', WorkflowJob)
.service('WorkflowJobTemplateModel', WorkflowJobTemplate)
.service('WorkflowJobTemplateNodeModel', WorkflowJobTemplateNode)
.service('UserModel', User)
.service('ModelsStrings', ModelsStrings);
.service('WorkflowJobTemplateNodeModel', WorkflowJobTemplateNode);
export default MODULE_NAME;

View File

@ -161,16 +161,16 @@ angular
// })
}
])
.run(['$stateExtender', '$q', '$compile', '$cookies', '$rootScope', '$log', '$stateParams',
.run(['$q', '$cookies', '$rootScope', '$log', '$stateParams',
'CheckLicense', '$location', 'Authorization', 'LoadBasePaths', 'Timer',
'LoadConfig', 'Store', 'pendoService', 'Prompt', 'Rest',
'Wait', 'ProcessErrors', '$state', 'GetBasePath', 'ConfigService',
'$filter', 'SocketService', 'AppStrings', '$transitions',
function($stateExtender, $q, $compile, $cookies, $rootScope, $log, $stateParams,
'LoadConfig', 'Store', 'pendoService', 'Rest',
'$state', 'GetBasePath', 'ConfigService',
'SocketService', 'AppStrings', '$transitions',
function($q, $cookies, $rootScope, $log, $stateParams,
CheckLicense, $location, Authorization, LoadBasePaths, Timer,
LoadConfig, Store, pendoService, Prompt, Rest, Wait,
ProcessErrors, $state, GetBasePath, ConfigService,
$filter, SocketService, AppStrings, $transitions) {
LoadConfig, Store, pendoService, Rest,
$state, GetBasePath, ConfigService,
SocketService, AppStrings, $transitions) {
$rootScope.$state = $state;
$rootScope.$state.matches = function(stateName) {
@ -387,6 +387,15 @@ angular
}
});
});
Rest.setUrl(`${GetBasePath('workflow_approval')}?status=pending&page_size=1`);
Rest.get()
.then(({data}) => {
$rootScope.pendingApprovalCount = data.count;
})
.catch(() => {
// TODO: handle this
});
}
}

View File

@ -39,14 +39,14 @@
* This is usage information.
*/
export default ['$log', '$cookies', '$compile', '$rootScope',
export default ['$log', '$cookies', '$rootScope',
'$location', 'Authorization', 'Alert', 'Wait', 'Timer',
'Empty', '$scope', 'pendoService', 'ConfigService',
'CheckLicense', 'SocketService',
function ($log, $cookies, $compile, $rootScope, $location,
Authorization, Alert, Wait, Timer, Empty,
scope, pendoService, ConfigService, CheckLicense,
SocketService) {
'CheckLicense', 'SocketService', 'Rest', 'GetBasePath',
function ($log, $cookies, $rootScope,
$location, Authorization, Alert, Wait, Timer,
Empty, scope, pendoService, ConfigService,
CheckLicense, SocketService, Rest, GetBasePath) {
var lastPath, lastUser, sessionExpired, loginAgain, preAuthUrl;
loginAgain = function() {
@ -139,6 +139,15 @@ export default ['$log', '$cookies', '$compile', '$rootScope',
Alert('Error', 'Failed to access user information. GET returned status: ' + status, 'alert-danger', loginAgain);
});
});
Rest.setUrl(`${GetBasePath('workflow_approval')}?status=pending&page_size=1`);
Rest.get()
.then(({data}) => {
$rootScope.pendingApprovalCount = data.count;
})
.catch(() => {
// TODO: handle this
});
});
// Call the API to get an auth token

View File

@ -75,221 +75,230 @@ export default ['Rest', 'GetBasePath', '$q', 'NextPage', function(Rest, GetBaseP
}).catch(function(response){
return $q.reject( response );
});
},
},
getAllWorkflowJobTemplateLabels: function(id) {
Rest.setUrl(GetBasePath('workflow_job_templates') + id + "/labels?page_size=200");
return Rest.get()
.then(function(res) {
if (res.data.next) {
return NextPage({
url: res.data.next,
arrayOfValues: res.data.results
}).then(function(labels) {
return labels;
}).catch(function(response){
return $q.reject( response );
});
}
else {
return $q.resolve( res.data.results );
}
}).catch(function(response){
return $q.reject( response );
});
},
getJobTemplate: function(id) {
var url = GetBasePath('job_templates');
getAllWorkflowJobTemplateLabels: function(id) {
Rest.setUrl(GetBasePath('workflow_job_templates') + id + "/labels?page_size=200");
return Rest.get()
.then(function(res) {
if (res.data.next) {
return NextPage({
url: res.data.next,
arrayOfValues: res.data.results
}).then(function(labels) {
return labels;
}).catch(function(response){
return $q.reject( response );
});
}
else {
return $q.resolve( res.data.results );
}
}).catch(function(response){
return $q.reject( response );
});
},
getJobTemplate: function(id) {
var url = GetBasePath('job_templates');
url = url + id;
url = url + id;
Rest.setUrl(url);
return Rest.get();
},
addWorkflowNode: function(params) {
// params.url
// params.data
Rest.setUrl(url);
return Rest.get();
},
addWorkflowNode: function(params) {
// params.url
// params.data
Rest.setUrl(params.url);
return Rest.post(params.data);
},
editWorkflowNode: function(params) {
// params.id
// params.data
Rest.setUrl(params.url);
return Rest.post(params.data);
},
editWorkflowNode: function(params) {
// params.id
// params.data
var url = GetBasePath('workflow_job_template_nodes') + params.id;
var url = GetBasePath('workflow_job_template_nodes') + params.id;
Rest.setUrl(url);
return Rest.put(params.data);
},
getJobTemplateLaunchInfo: function(id) {
var url = GetBasePath('job_templates');
Rest.setUrl(url);
return Rest.put(params.data);
},
getJobTemplateLaunchInfo: function(id) {
var url = GetBasePath('job_templates');
url = url + id + '/launch';
url = url + id + '/launch';
Rest.setUrl(url);
return Rest.get();
},
getWorkflowJobTemplateNodes: function(id, page) {
var url = GetBasePath('workflow_job_templates');
Rest.setUrl(url);
return Rest.get();
},
getWorkflowJobTemplateNodes: function(id, page) {
var url = GetBasePath('workflow_job_templates');
url = url + id + '/workflow_nodes?page_size=200';
url = url + id + '/workflow_nodes?page_size=200';
if(page) {
url += '&page=' + page;
}
if(page) {
url += '&page=' + page;
}
Rest.setUrl(url);
return Rest.get();
},
updateWorkflowJobTemplate: function(params) {
// params.id
// params.data
Rest.setUrl(url);
return Rest.get();
},
updateWorkflowJobTemplate: function(params) {
// params.id
// params.data
var url = GetBasePath('workflow_job_templates');
var url = GetBasePath('workflow_job_templates');
url = url + params.id;
url = url + params.id;
Rest.setUrl(url);
return Rest.patch(params.data);
},
getWorkflowJobTemplate: function(id) {
var url = GetBasePath('workflow_job_templates');
Rest.setUrl(url);
return Rest.patch(params.data);
},
getWorkflowJobTemplate: function(id) {
var url = GetBasePath('workflow_job_templates');
url = url + id;
url = url + id;
Rest.setUrl(url);
return Rest.get();
},
deleteWorkflowJobTemplateNode: function(id) {
var url = GetBasePath('workflow_job_template_nodes') + id;
Rest.setUrl(url);
return Rest.get();
},
deleteWorkflowJobTemplateNode: function(id) {
var url = GetBasePath('workflow_job_template_nodes') + id;
Rest.setUrl(url);
return Rest.destroy();
},
disassociateWorkflowNode: function(params) {
//params.parentId
//params.nodeId
//params.edge
Rest.setUrl(url);
return Rest.destroy();
},
disassociateWorkflowNode: function(params) {
//params.parentId
//params.nodeId
//params.edge
var url = GetBasePath('workflow_job_template_nodes') + params.parentId;
var url = GetBasePath('workflow_job_template_nodes') + params.parentId;
if(params.edge === 'success') {
url = url + '/success_nodes';
}
else if(params.edge === 'failure') {
url = url + '/failure_nodes';
}
else if(params.edge === 'always') {
url = url + '/always_nodes';
}
if(params.edge === 'success') {
url = url + '/success_nodes';
}
else if(params.edge === 'failure') {
url = url + '/failure_nodes';
}
else if(params.edge === 'always') {
url = url + '/always_nodes';
}
Rest.setUrl(url);
return Rest.post({
"id": params.nodeId,
"disassociate": true
});
},
associateWorkflowNode: function(params) {
//params.parentId
//params.nodeId
//params.edge
Rest.setUrl(url);
return Rest.post({
"id": params.nodeId,
"disassociate": true
});
},
associateWorkflowNode: function(params) {
//params.parentId
//params.nodeId
//params.edge
var url = GetBasePath('workflow_job_template_nodes') + params.parentId;
var url = GetBasePath('workflow_job_template_nodes') + params.parentId;
if(params.edge === 'success') {
url = url + '/success_nodes';
}
else if(params.edge === 'failure') {
url = url + '/failure_nodes';
}
else if(params.edge === 'always') {
url = url + '/always_nodes';
}
if(params.edge === 'success') {
url = url + '/success_nodes';
}
else if(params.edge === 'failure') {
url = url + '/failure_nodes';
}
else if(params.edge === 'always') {
url = url + '/always_nodes';
}
Rest.setUrl(url);
return Rest.post({
id: params.nodeId
});
},
getUnifiedJobTemplate: function(id) {
var url = GetBasePath('unified_job_templates');
Rest.setUrl(url);
return Rest.post({
id: params.nodeId
});
},
getUnifiedJobTemplate: function(id) {
var url = GetBasePath('unified_job_templates');
url = url + "?id=" + id;
url = url + "?id=" + id;
Rest.setUrl(url);
return Rest.get();
},
getCredential: function(id) {
var url = GetBasePath('credentials');
Rest.setUrl(url);
return Rest.get();
},
getCredential: function(id) {
var url = GetBasePath('credentials');
url = url + id;
url = url + id;
Rest.setUrl(url);
return Rest.get();
},
getInventory: function(id) {
var url = GetBasePath('inventory');
Rest.setUrl(url);
return Rest.get();
},
getInventory: function(id) {
var url = GetBasePath('inventory');
url = url + id;
url = url + id;
Rest.setUrl(url);
return Rest.get();
},
getWorkflowCopy: function(id) {
let url = GetBasePath('workflow_job_templates');
Rest.setUrl(url);
return Rest.get();
},
getWorkflowCopy: function(id) {
let url = GetBasePath('workflow_job_templates');
url = url + id + '/copy';
url = url + id + '/copy';
Rest.setUrl(url);
return Rest.get();
},
copyWorkflow: function(id) {
let url = GetBasePath('workflow_job_templates');
Rest.setUrl(url);
return Rest.get();
},
copyWorkflow: function(id) {
let url = GetBasePath('workflow_job_templates');
url = url + id + '/copy';
url = url + id + '/copy';
Rest.setUrl(url);
return Rest.post();
},
getWorkflowJobTemplateOptions: function() {
var deferred = $q.defer();
Rest.setUrl(url);
return Rest.post();
},
getWorkflowJobTemplateOptions: function() {
var deferred = $q.defer();
let url = GetBasePath('workflow_job_templates');
let url = GetBasePath('workflow_job_templates');
Rest.setUrl(url);
Rest.options()
.then(({data}) => {
deferred.resolve(data);
}).catch(({msg, code}) => {
deferred.reject(msg, code);
});
Rest.setUrl(url);
Rest.options()
.then(({data}) => {
deferred.resolve(data);
}).catch(({msg, code}) => {
deferred.reject(msg, code);
});
return deferred.promise;
},
getJobTemplateOptions: function() {
var deferred = $q.defer();
return deferred.promise;
},
getJobTemplateOptions: function() {
var deferred = $q.defer();
let url = GetBasePath('job_templates');
let url = GetBasePath('job_templates');
Rest.setUrl(url);
Rest.options()
.then(({data}) => {
deferred.resolve(data);
}).catch(({msg, code}) => {
deferred.reject(msg, code);
});
Rest.setUrl(url);
Rest.options()
.then(({data}) => {
deferred.resolve(data);
}).catch(({msg, code}) => {
deferred.reject(msg, code);
});
return deferred.promise;
},
postWorkflowNodeCredential: function(params) {
// params.id
// params.data
return deferred.promise;
},
postWorkflowNodeCredential: function(params) {
// params.id
// params.data
var url = GetBasePath('workflow_job_template_nodes') + params.id + '/credentials';
var url = GetBasePath('workflow_job_template_nodes') + params.id + '/credentials';
Rest.setUrl(url);
return Rest.post(params.data);
}
Rest.setUrl(url);
return Rest.post(params.data);
},
createApprovalTemplate: (params) => {
params = params || {};
Rest.setUrl(GetBasePath('workflow_approval_templates'));
return Rest.post(params);
},
patchApprovalTemplate: ({id, data}) => {
Rest.setUrl(`${GetBasePath('workflow_approval_templates')}/${id}`);
return Rest.patch(data);
}
};
}];

View File

@ -142,8 +142,10 @@
}
.WorkflowChart-deletedText {
width: 90px;
width: 180px;
height: 14px;
color: @default-interface-txt;
text-align: center;
}
.WorkflowChart-activeNode {
fill: @default-link;
@ -159,7 +161,11 @@
}
.WorkflowChart-nameText {
width: 180px;
height: 20px;
line-height: 18px;
font-size: 10px;
text-align: center;
}
.WorkflowChart-tooltip {

View File

@ -770,12 +770,23 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
});
baseSvg.selectAll(".WorkflowChart-nameText")
.attr("x", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? 20 : nodeW / 2; })
.attr("y", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? 10 : nodeH / 2; })
.attr("x", 0)
.attr("y", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? 10 : (nodeH / 2) - 10; })
.attr("text-anchor", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? "inherit" : "middle"; })
.text(function (d) {
.html(function (d) {
const name = _.get(d, 'unifiedJobTemplate.name');
return name ? wrap(name) : "";
const wrappedName = name ? wrap(name) : "";
// TODO: clean this up
if (d.unifiedJobTemplate && d.unifiedJobTemplate.unified_job_type === 'workflow_approval') {
return `<span>
<div style="background-color: #EBEBEB;height: 20px;width: 20px;border-radius: 50%;display: inline-block;margin-right:5px;">
<span style="color:#707070" class="fa fa-pause"></span>
</div>
<span>${wrappedName}</span>
</span>`;
} else {
return `<span>${wrappedName}</span>`;
}
});
baseSvg.selectAll(".WorkflowChart-detailsLink")
@ -884,19 +895,31 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
.attr("class", "WorkflowChart-activeNode")
.style("display", function(d) { return d.id === scope.graphState.nodeBeingEdited ? null : "none"; });
thisNode.append("text")
.attr("x", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? 20 : nodeW / 2; })
.attr("y", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? 10 : nodeH / 2; })
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; })
.attr("dy", ".35em")
.attr("text-anchor", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? "inherit" : "middle"; })
.attr("class", "WorkflowChart-defaultText WorkflowChart-nameText")
.text(function (d) {
.html(function (d) {
const name = _.get(d, 'unifiedJobTemplate.name');
return name ? wrap(name) : "";
const wrappedName = name ? wrap(name) : "";
// TODO: clean this up
if (d.unifiedJobTemplate && d.unifiedJobTemplate.unified_job_type === 'workflow_approval') {
return `<span>
<div style="background-color: #EBEBEB;height: 20px;width: 20px;border-radius: 50%;display: inline-block;margin-right:5px;">
<span style="color:#707070" class="fa fa-pause"></span>
</div>
<span>${wrappedName}</span>
</span>`;
} else {
return `<span>${wrappedName}</span>`;
}
});
thisNode.append("foreignObject")
.attr("x", 62)
.attr("x", 0)
.attr("y", 22)
.attr("dy", ".35em")
.attr("text-anchor", "middle")

View File

@ -32,6 +32,7 @@ export default ['$scope', 'TemplatesService', 'JobTemplateModel', 'PromptService
$scope.strings = TemplatesStrings;
$scope.editNodeHelpMessage = null;
$scope.pauseNode = {};
let templateList = _.cloneDeep(TemplateList);
delete templateList.actions;
@ -463,6 +464,7 @@ export default ['$scope', 'TemplatesService', 'JobTemplateModel', 'PromptService
}
$scope.promptData = null;
$scope.pauseNode = {};
$scope.editNodeHelpMessage = getEditNodeHelpMessage(selectedTemplate, $scope.workflowJobTemplateObj);
if (selectedTemplate.type === "job_template" || selectedTemplate.type === "workflow_job_template") {
@ -616,26 +618,48 @@ export default ['$scope', 'TemplatesService', 'JobTemplateModel', 'PromptService
})
);
CreateSelect2({
element: '#workflow-node-types',
multiple: false
});
$q.all(listPromises)
.then(() => {
if ($scope.nodeConfig.mode === "edit") {
// Make sure that we have the full unified job template object
if (!$scope.nodeConfig.node.fullUnifiedJobTemplateObject) {
// This is a node that we got back from the api with an incomplete
// unified job template so we're going to pull down the whole object
TemplatesService.getUnifiedJobTemplate($scope.nodeConfig.node.originalNodeObject.summary_fields.unified_job_template.id)
.then(({data}) => {
$scope.nodeConfig.node.fullUnifiedJobTemplateObject = data.results[0];
finishConfiguringEdit();
}, (error) => {
ProcessErrors($scope, error.data, error.status, null, {
hdr: 'Error!',
msg: 'Failed to get unified job template. GET returned ' +
'status: ' + error.status
});
});
if ($scope.nodeConfig.node.unifiedJobTemplate && $scope.nodeConfig.node.unifiedJobTemplate.unified_job_type === "workflow_approval") {
$scope.selectedTemplate = null;
$scope.activeTab = "pause";
CreateSelect2({
element: '#workflow_node_edge',
multiple: false
});
$scope.pauseNode = {
isPauseNode: true,
name: $scope.nodeConfig.node.unifiedJobTemplate.name,
description: $scope.nodeConfig.node.unifiedJobTemplate.description,
};
$scope.nodeFormDataLoaded = true;
} else {
finishConfiguringEdit();
// Make sure that we have the full unified job template object
if (!$scope.nodeConfig.node.fullUnifiedJobTemplateObject) {
// This is a node that we got back from the api with an incomplete
// unified job template so we're going to pull down the whole object
TemplatesService.getUnifiedJobTemplate($scope.nodeConfig.node.originalNodeObject.summary_fields.unified_job_template.id)
.then(({data}) => {
$scope.nodeConfig.node.fullUnifiedJobTemplateObject = data.results[0];
finishConfiguringEdit();
}, (error) => {
ProcessErrors($scope, error.data, error.status, null, {
hdr: 'Error!',
msg: 'Failed to get unified job template. GET returned ' +
'status: ' + error.status
});
});
} else {
finishConfiguringEdit();
}
}
} else {
finishConfiguringAdd();

View File

@ -1,9 +1,18 @@
<div ng-show="nodeFormDataLoaded">
<div class="WorkflowMaker-formTitle">{{nodeConfig.mode === 'edit' ? nodeConfig.node.fullUnifiedJobTemplateObject.name || nodeConfig.node.unifiedJobTemplate.name : strings.get('workflow_maker.ADD_A_NODE')}}</div>
<div class="Form-tabHolder" ng-show="!readOnly">
<div class="Form-tab WorkflowMaker-formTab" ng-class="{'is-selected': activeTab === 'jobs'}" ng-click="activeTab = 'jobs'">{{strings.get('workflow_maker.JOBS')}}</div>
<div class="Form-tab WorkflowMaker-formTab" ng-class="{'is-selected': activeTab === 'project_syncs'}" ng-click="activeTab = 'project_syncs'">{{strings.get('workflow_maker.PROJECT_SYNC')}}</div>
<div class="Form-tab WorkflowMaker-formTab" ng-class="{'is-selected': activeTab === 'inventory_syncs'}" ng-click="activeTab = 'inventory_syncs'">{{strings.get('workflow_maker.INVENTORY_SYNC')}}</div>
<div class="WorkflowMaker-formTypeDropdown" ng-show="!readOnly">
<select
id="workflow-node-types"
ng-model="activeTab"
class="form-control Form-dropDown"
name="activeTab"
aria-hidden="true"
>
<option value="jobs" selected="selected">{{strings.get('workflow_maker.JOBS')}}</option>
<option value="project_syncs" selected="selected">{{strings.get('workflow_maker.PROJECT_SYNC')}}</option>
<option value="inventory_syncs" selected="selected">{{strings.get('workflow_maker.INVENTORY_SYNC')}}</option>
<option value="pause" selected="selected">{{strings.get('workflow_maker.PAUSE_NODE')}}</option>
</select>
</div>
<div class="WorkflowMaker-formLists" ng-show="!readOnly">
<div id="workflow-jobs-list" ng-show="activeTab === 'jobs'">
@ -103,6 +112,36 @@
</div>
<paginate base-path="inventory_sources" collection="wf_maker_inventory_sources" dataset="wf_maker_inventory_source_dataset" iterator="wf_maker_inventory_source" query-set="wf_maker_inventory_source_queryset" hide-view-per-page="true" max-visible-pages="5"></paginate>
</div>
<div id="workflow-pause" ng-show="activeTab === 'pause'">
<label class="WorkflowMaker-pauseCheckbox">
<input type="checkbox" ng-model="pauseNode.isPauseNode" />
<!-- translate -->
<span class="Form-inputLabel">Make this a pause node</span>
</label>
<div ng-hide="!pauseNode.isPauseNode">
<div class="form-group Form-formGroup Form-formGroup--singleColumn">
<label for="pauseName" class="Form-inputLabelContainer">
<span class="Form-requiredAsterisk">*</span>
<!-- translate -->
<span class="Form-inputLabel">Name</span>
</label>
<div>
<input type="text" ng-model="pauseNode.name" name="pauseName" id="workflow_job_template_pauseName" class="form-control Form-textInput" />
</div>
</label>
</div>
<div class="form-group Form-formGroup Form-formGroup--singleColumn"></div>
<label for="pauseDesc" class="Form-inputLabelContainer">
<!-- translate -->
<span class="Form-inputLabel">Description</span>
</label>
<div>
<input type="text" ng-model="pauseNode.description" name="pauseDesc" id="workflow_job_template_pauseDesc" class="form-control Form-textInput" />
</div>
</label>
</div>
</div>
</div>
</div>
<div ng-if="selectedTemplate && selectedTemplateInvalid">
<div class="WorkflowMaker-invalidJobTemplateWarning">
@ -238,7 +277,8 @@
<button type="button" class="btn btn-sm Form-primaryButton Form-primaryButton--noMargin" id="workflow_maker_prompt_btn" ng-show="showPromptButton && activeTab == 'jobs' " ng-click="openPromptModal()"> {{:: strings.get('prompt.PROMPT') }}</button>
<button type="button" class="btn btn-sm Form-cancelButton" id="workflow_maker_cancel_node_btn" ng-show="!readOnly" ng-click="cancel()"> {{:: strings.get('CANCEL') }}</button>
<button type="button" class="btn btn-sm Form-cancelButton" id="workflow_maker_close_node_btn" ng-show="readOnly" ng-click="cancel()"> {{:: strings.get('CLOSE') }}</button>
<button type="button" class="btn btn-sm Form-saveButton" id="workflow_maker_select_node_btn" ng-show="!readOnly" ng-click="select({selectedTemplate, promptData, edgeType})" ng-disabled="!selectedTemplate || promptModalMissingReqFields || credentialRequiresPassword || selectedTemplateInvalid"> {{:: strings.get('workflow_maker.SELECT') }}</button>
<!-- need to figure out how to disable this button when pause node is incomplete -->
<button type="button" class="btn btn-sm Form-saveButton" id="workflow_maker_select_node_btn" ng-show="!readOnly" ng-click="select({selectedTemplate, promptData, edgeType, pauseNode})" ng-disabled="!(selectedTemplate || pauseNode.isPauseNode) || promptModalMissingReqFields || credentialRequiresPassword || selectedTemplateInvalid || (pauseNode.isPauseNode && !pauseNode.name)"> {{:: strings.get('workflow_maker.SELECT') }}</button>
</div>
<prompt prompt-data="promptData" action-text="{{:: strings.get('prompt.CONFIRM')}}" prevent-creds-with-passwords="preventCredsWithPasswords" read-only-prompts="readOnly"></prompt>
</div>

View File

@ -296,8 +296,8 @@
border-bottom-left-radius: 5px;
}
.WorkflowMaker-formTab {
margin-right: 10px;
.WorkflowMaker-formTypeDropdown {
margin-bottom: 20px;
}
.WorkflowMaker-preventBodyScrolling {
@ -314,6 +314,13 @@
margin-bottom: 20px;
}
.WorkflowMaker-pauseCheckbox {
input {
margin-right: 5px;
}
margin-bottom: 20px;
}
.Key-list {
margin: 0;
padding: 20px;

View File

@ -140,59 +140,126 @@ export default ['$scope', 'TemplatesService',
};
if ($scope.graphState.arrayOfNodesForChart.length > 1) {
let approvalTemplatePromises = [];
let addPromises = [];
let editPromises = [];
let credentialRequests = [];
Object.keys(nodeRef).map((workflowMakerNodeId) => {
if (nodeRef[workflowMakerNodeId].isNew) {
addPromises.push(TemplatesService.addWorkflowNode({
url: $scope.workflowJobTemplateObj.related.workflow_nodes,
data: buildSendableNodeData(nodeRef[workflowMakerNodeId])
}).then(({data}) => {
nodeRef[workflowMakerNodeId].originalNodeObject = data;
nodeIdToChartNodeIdMapping[data.id] = parseInt(workflowMakerNodeId);
if (_.get(nodeRef[workflowMakerNodeId], 'promptData.launchConf.ask_credential_on_launch')) {
// This finds the credentials that were selected in the prompt but don't occur
// in the template defaults
let credentialIdsToPost = nodeRef[workflowMakerNodeId].promptData.prompts.credentials.value.filter((credFromPrompt) => {
let defaultCreds = _.get(nodeRef[workflowMakerNodeId], 'promptData.launchConf.defaults.credentials', []);
return !defaultCreds.some((defaultCred) => {
return credFromPrompt.id === defaultCred.id;
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,
data: {
unified_job_template: approvalTemplateData.id
}
}).then(({data: nodeData}) => {
node.originalNodeObject = nodeData;
nodeIdToChartNodeIdMapping[nodeData.id] = parseInt(workflowMakerNodeId);
}).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')
});
credentialIdsToPost.forEach((credentialToPost) => {
credentialRequests.push({
id: data.id,
}));
} else {
addPromises.push(TemplatesService.addWorkflowNode({
url: $scope.workflowJobTemplateObj.related.workflow_nodes,
data: buildSendableNodeData(node)
}).then(({data}) => {
node.originalNodeObject = data;
nodeIdToChartNodeIdMapping[data.id] = parseInt(workflowMakerNodeId);
if (_.get(node, 'promptData.launchConf.ask_credential_on_launch')) {
// This finds the credentials that were selected in the prompt but don't occur
// in the template defaults
let credentialIdsToPost = node.promptData.prompts.credentials.value.filter((credFromPrompt) => {
let defaultCreds = _.get(node, 'promptData.launchConf.defaults.credentials', []);
return !defaultCreds.some((defaultCred) => {
return credFromPrompt.id === defaultCred.id;
});
});
credentialIdsToPost.forEach((credentialToPost) => {
credentialRequests.push({
id: data.id,
data: {
id: credentialToPost.id
}
});
});
}
}).catch(({ data, status }) => {
Wait('stop');
ProcessErrors($scope, data, status, null, {
hdr: $scope.strings.get('error.HEADER')
});
}));
}
} else if (node.isEdited) {
if (node.unifiedJobTemplate && node.unifiedJobTemplate.unified_job_type === "workflow_approval") {
if (node.originalNodeObject.summary_fields.unified_job_template.unified_job_type === "workflow_approval") {
approvalTemplatePromises.push(TemplatesService.patchApprovalTemplate({
id: node.originalNodeObject.summary_fields.unified_job_template.id,
data: {
name: node.unifiedJobTemplate.name,
description: node.unifiedJobTemplate.description
}
}).catch(({ data, status }) => {
Wait('stop');
ProcessErrors($scope, data, status, null, {
hdr: $scope.strings.get('error.HEADER')
});
}));
} 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({
url: $scope.workflowJobTemplateObj.related.workflow_nodes,
data: {
id: credentialToPost.id
unified_job_template: approvalTemplateData.id
}
}).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')
});
}));
} else if (nodeRef[workflowMakerNodeId].isEdited) {
editPromises.push(TemplatesService.editWorkflowNode({
id: nodeRef[workflowMakerNodeId].originalNodeObject.id,
data: buildSendableNodeData(nodeRef[workflowMakerNodeId])
}));
} else {
editPromises.push(TemplatesService.editWorkflowNode({
id: node.originalNodeObject.id,
data: buildSendableNodeData(node)
}));
}
if (_.get(nodeRef[workflowMakerNodeId], 'promptData.launchConf.ask_credential_on_launch')) {
let credentialsNotInPriorCredentials = nodeRef[workflowMakerNodeId].promptData.prompts.credentials.value.filter((credFromPrompt) => {
let defaultCreds = _.get(nodeRef[workflowMakerNodeId], 'promptData.launchConf.defaults.credentials', []);
if (_.get(node, 'promptData.launchConf.ask_credential_on_launch')) {
let credentialsNotInPriorCredentials = node.promptData.prompts.credentials.value.filter((credFromPrompt) => {
let defaultCreds = _.get(node, 'promptData.launchConf.defaults.credentials', []);
return !defaultCreds.some((defaultCred) => {
return credFromPrompt.id === defaultCred.id;
});
});
let credentialsToAdd = credentialsNotInPriorCredentials.filter((credNotInPrior) => {
let previousOverrides = _.get(nodeRef[workflowMakerNodeId], 'promptData.prompts.credentials.previousOverrides', []);
let previousOverrides = _.get(node, 'promptData.prompts.credentials.previousOverrides', []);
return !previousOverrides.some((priorCred) => {
return credNotInPrior.id === priorCred.id;
});
@ -200,8 +267,8 @@ export default ['$scope', 'TemplatesService',
let credentialsToRemove = [];
if (_.has(nodeRef[workflowMakerNodeId], 'promptData.prompts.credentials.previousOverrides')) {
credentialsToRemove = nodeRef[workflowMakerNodeId].promptData.prompts.credentials.previousOverrides.filter((priorCred) => {
if (_.has(node, 'promptData.prompts.credentials.previousOverrides')) {
credentialsToRemove = node.promptData.prompts.credentials.previousOverrides.filter((priorCred) => {
return !credentialsNotInPriorCredentials.some((credNotInPrior) => {
return priorCred.id === credNotInPrior.id;
});
@ -210,7 +277,7 @@ export default ['$scope', 'TemplatesService',
credentialsToAdd.forEach((credentialToAdd) => {
credentialRequests.push({
id: nodeRef[workflowMakerNodeId].originalNodeObject.id,
id: node.originalNodeObject.id,
data: {
id: credentialToAdd.id
}
@ -219,7 +286,7 @@ export default ['$scope', 'TemplatesService',
credentialsToRemove.forEach((credentialToRemove) => {
credentialRequests.push({
id: nodeRef[workflowMakerNodeId].originalNodeObject.id,
id: node.originalNodeObject.id,
data: {
id: credentialToRemove.id,
disassociate: true
@ -235,172 +302,177 @@ export default ['$scope', 'TemplatesService',
return TemplatesService.deleteWorkflowJobTemplateNode(nodeId);
});
$q.all(addPromises.concat(editPromises, deletePromises))
$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] = {};
$q.all(addPromises.concat(editPromises, deletePromises))
.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] = {};
}
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 {
let deletePromises = deletedNodeIds.map((nodeId) => {
@ -511,17 +583,27 @@ export default ['$scope', 'TemplatesService',
$scope.formState.showNodeForm = true;
};
$scope.confirmNodeForm = (selectedTemplate, promptData, edgeType) => {
$scope.confirmNodeForm = (selectedTemplate, promptData, edgeType, pauseNode) => {
$scope.workflowChangesUnsaved = true;
const nodeId = $scope.nodeConfig.nodeId;
if ($scope.nodeConfig.mode === "add") {
if (selectedTemplate && edgeType && edgeType.value) {
nodeRef[$scope.nodeConfig.nodeId] = {
fullUnifiedJobTemplateObject: selectedTemplate,
promptData,
isNew: true
};
if (edgeType && edgeType.value) {
if (selectedTemplate) {
nodeRef[$scope.nodeConfig.nodeId] = {
fullUnifiedJobTemplateObject: selectedTemplate,
promptData,
isNew: true
};
} else if (pauseNode && pauseNode.isPauseNode) {
nodeRef[$scope.nodeConfig.nodeId] = {
unifiedJobTemplate: {
name: pauseNode.name,
description: pauseNode.description,
unified_job_type: "workflow_approval"
},
isNew: true
};
}
$scope.graphState.nodeBeingAdded = null;
$scope.graphState.arrayOfLinksForChart.map( (link) => {
@ -534,6 +616,7 @@ export default ['$scope', 'TemplatesService',
} else if ($scope.nodeConfig.mode === "edit") {
if (selectedTemplate) {
nodeRef[$scope.nodeConfig.nodeId].fullUnifiedJobTemplateObject = selectedTemplate;
nodeRef[$scope.nodeConfig.nodeId].unifiedJobTemplate = selectedTemplate;
nodeRef[$scope.nodeConfig.nodeId].promptData = _.cloneDeep(promptData);
nodeRef[$scope.nodeConfig.nodeId].isEdited = true;
$scope.graphState.nodeBeingEdited = null;
@ -546,12 +629,30 @@ export default ['$scope', 'TemplatesService',
link.source.unifiedJobTemplate = selectedTemplate;
}
});
} else if (pauseNode && pauseNode.isPauseNode) {
// If it's a _new_ pause node then we'll want to create the new ujt
// If it's an existing pause node then we'll want to update the ujt
nodeRef[$scope.nodeConfig.nodeId].unifiedJobTemplate = {
name: pauseNode.name,
description: pauseNode.description,
unified_job_type: "workflow_approval"
},
nodeRef[$scope.nodeConfig.nodeId].isEdited = true;
}
}
$scope.graphState.arrayOfNodesForChart.map( (node) => {
if (node.id === nodeId) {
node.unifiedJobTemplate = selectedTemplate;
if (pauseNode && pauseNode.isPauseNode) {
node.unifiedJobTemplate = {
unified_job_type: 'workflow_approval',
name: pauseNode.name,
description: pauseNode.description
};
} else {
node.unifiedJobTemplate = selectedTemplate;
}
}
});

View File

@ -128,7 +128,7 @@
</div>
<div class="WorkflowMaker-contentRight">
<span ng-if="formState.showNodeForm">
<workflow-node-form node-config="nodeConfig" workflow-job-template-obj="workflowJobTemplateObj" select="confirmNodeForm(selectedTemplate, promptData, edgeType)" cancel="cancelNodeForm()" read-only="!workflowJobTemplateObj.summary_fields.user_capabilities.edit"/>
<workflow-node-form node-config="nodeConfig" workflow-job-template-obj="workflowJobTemplateObj" select="confirmNodeForm(selectedTemplate, promptData, edgeType, pauseNode)" cancel="cancelNodeForm()" read-only="!workflowJobTemplateObj.summary_fields.user_capabilities.edit"/>
</span>
<span ng-if="formState.showLinkForm">
<workflow-link-form link-config="linkConfig" read-only="!workflowJobTemplateObj.summary_fields.user_capabilities.edit" select="confirmLinkForm(edgeType)" cancel="cancelLinkForm()" unlink="unlink()"/>