Prompt bug cleanup. Filter workflow_approval jobs out of jobs list. Add initial support for timeout.

This commit is contained in:
mabashian 2019-07-30 13:34:50 -04:00 committed by Ryan Petrello
parent 3357c96774
commit 013792f0f8
No known key found for this signature in database
GPG Key ID: F2AA5F2122351777
11 changed files with 103 additions and 115 deletions

View File

@ -15,6 +15,7 @@ export default {
job_search: {
value: {
not__launch_type: 'sync',
not__type: 'workflow_approval',
order_by: '-finished'
},
dynamic: true,

View File

@ -148,7 +148,8 @@ function TemplatesStrings (BaseString) {
EXIT: t.s('EXIT'),
CANCEL: t.s('CANCEL'),
SAVE_AND_EXIT: t.s('SAVE & EXIT'),
APPROVAL: t.s('Approval')
APPROVAL: t.s('Approval'),
TIMEOUT_POPOVER: t.s('The amount of time (in seconds) to wait before this approval step is automatically denied. Defaults to 0 for no timeout.')
};
}

View File

@ -15,8 +15,6 @@
height: 100%;
width: 540px;
background-color: @default-bg;
animation-name: slidein;
animation-duration: 250ms;
padding: 20px;
overflow-y: scroll;
}
@ -63,14 +61,4 @@
opacity: 1;
}
}
}
@keyframes slidein {
from {
width: 0px;
}
to {
width: 540px;
}
}

View File

@ -28,7 +28,6 @@
<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}}})"
@ -40,10 +39,12 @@
<at-row-item
value-bind-html="{{ approval.created | longDate }}">
</at-row-item>
<!-- todo: clean this up if it becomes a real thing-->
<at-row-item
style="color: red;"
value-bind-html="Expires: Never">
</at-row-item>
<!-- todo: hook this up when timeout is active-->
<!-- <at-row-item
style="color: red;"
value-bind-html="Expires {{ approval.created | longDate }}">

View File

@ -92,8 +92,8 @@
margin-left: 10px;
padding: 5px;
border-radius: 3px;
background-color: red;
color: white;
background-color: @at-red-bright;
color: @at-white;
height: 15px;
font-size: 10px;
}

View File

@ -165,12 +165,12 @@ angular
'CheckLicense', '$location', 'Authorization', 'LoadBasePaths', 'Timer',
'LoadConfig', 'Store', 'pendoService', 'Rest',
'$state', 'GetBasePath', 'ConfigService',
'SocketService', 'AppStrings', '$transitions',
'SocketService', 'AppStrings', '$transitions', 'i18n',
function($q, $cookies, $rootScope, $log, $stateParams,
CheckLicense, $location, Authorization, LoadBasePaths, Timer,
LoadConfig, Store, pendoService, Rest,
$state, GetBasePath, ConfigService,
SocketService, AppStrings, $transitions) {
SocketService, AppStrings, $transitions, i18n) {
$rootScope.$state = $state;
$rootScope.$state.matches = function(stateName) {
@ -393,8 +393,11 @@ angular
.then(({data}) => {
$rootScope.pendingApprovalCount = data.count;
})
.catch(() => {
// TODO: handle this
.catch(({data, status}) => {
ProcessErrors($scope, data, status, null, {
hdr: i18n._('Error!'),
msg: i18n._('Failed to get workflow jobs pending approval. GET returned status: ') + status
});
});
}
}

View File

@ -42,11 +42,11 @@
export default ['$log', '$cookies', '$rootScope',
'$location', 'Authorization', 'Alert', 'Wait', 'Timer',
'Empty', '$scope', 'pendoService', 'ConfigService',
'CheckLicense', 'SocketService', 'Rest', 'GetBasePath',
'CheckLicense', 'SocketService', 'Rest', 'GetBasePath', 'i18n',
function ($log, $cookies, $rootScope,
$location, Authorization, Alert, Wait, Timer,
Empty, scope, pendoService, ConfigService,
CheckLicense, SocketService, Rest, GetBasePath) {
CheckLicense, SocketService, Rest, GetBasePath, i18n) {
var lastPath, lastUser, sessionExpired, loginAgain, preAuthUrl;
loginAgain = function() {
@ -145,8 +145,11 @@ export default ['$log', '$cookies', '$rootScope',
.then(({data}) => {
$rootScope.pendingApprovalCount = data.count;
})
.catch(() => {
// TODO: handle this
.catch(({data, status}) => {
ProcessErrors($scope, data, status, null, {
hdr: i18n._('Error!'),
msg: i18n._('Failed to get workflow jobs pending approval. GET returned status: ') + status
});
});
});

View File

@ -218,6 +218,7 @@ export default [ 'ProcessErrors', 'CredentialTypeModel', 'TemplatesStrings', '$f
if(vm.steps[step].tab) {
if(vm.steps[step].tab.order === currentTab.order) {
vm.steps[step].tab._active = false;
vm.steps[step].tab._disabled = true;
} else if(vm.steps[step].tab.order === currentTab.order + 1) {
activeTab = currentTab;
vm.steps[step].tab._active = true;

View File

@ -45,7 +45,7 @@
<div class="Prompt-footer">
<button id="prompt_cancel" class="Prompt-defaultButton" ng-click="vm.cancel()" ng-show="!vm.readOnlyPrompts">{{:: vm.strings.get('CANCEL') }}</button>
<button id="prompt_close" class="Prompt-defaultButton" ng-click="vm.cancel()" ng-show="vm.readOnlyPrompts">{{:: vm.strings.get('CLOSE') }}</button>
<button id="prompt_inventory_next" class="Prompt-actionButton" ng-show="vm.steps.inventory.tab._active" ng-click="vm.next(vm.steps.inventory.tab)" ng-disabled="vm.promptData.templateType === 'workflow_job_template' && !vm.promptDataClone.prompts.inventory.value.id && vm.promptDataClone.launchConf.defaults.inventory.id && !vm.readOnlyPrompts">{{:: vm.strings.get('NEXT') }}</button>
<button id="prompt_inventory_next" class="Prompt-actionButton" ng-show="vm.steps.inventory.tab._active" ng-click="vm.next(vm.steps.inventory.tab)" ng-disabled="((vm.promptData.templateType === 'job_template' && !vm.promptDataClone.prompts.inventory.value.id) || (vm.promptData.templateType === 'workflow_job_template' && !vm.promptDataClone.prompts.inventory.value.id && vm.promptDataClone.launchConf.defaults.inventory.id)) && !vm.readOnlyPrompts">{{:: vm.strings.get('NEXT') }}</button>
<button id="prompt_credential_next" class="Prompt-actionButton"
ng-show="vm.steps.credential.tab._active"
ng-click="vm.next(vm.steps.credential.tab)"

View File

@ -290,15 +290,6 @@ export default ['Rest', 'GetBasePath', '$q', 'NextPage', function(Rest, GetBaseP
Rest.setUrl(url);
return Rest.post(params.data);
},
createApprovalTemplate: ({url, data}) => {
data = data || {};
Rest.setUrl(url);
return Rest.post(data);
},
patchApprovalTemplate: ({id, data}) => {
Rest.setUrl(`/api/v2/workflow_approval_templates/${id}`);
return Rest.patch(data);
}
};
}];

View File

@ -5,11 +5,11 @@
*************************************************/
export default ['$scope', 'TemplatesService',
'ProcessErrors', '$q',
'ProcessErrors', '$q', 'Rest',
'PromptService', 'TemplatesStrings', 'WorkflowChartService',
'Wait', '$state',
function ($scope, TemplatesService,
ProcessErrors, $q,
ProcessErrors, $q, Rest,
PromptService, TemplatesStrings, WorkflowChartService,
Wait, $state
) {
@ -145,7 +145,6 @@ 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) {
@ -153,16 +152,15 @@ export default ['$scope', 'TemplatesService',
addPromises.push(TemplatesService.addWorkflowNode({
url: $scope.workflowJobTemplateObj.related.workflow_nodes,
data: {}
}).then(({data}) => {
approvalTemplatePromises.push(TemplatesService.createApprovalTemplate({
url: data.related.create_approval_template,
data: {
name: node.unifiedJobTemplate.name,
description: node.unifiedJobTemplate.description
}
}).then(({data: newNodeData}) => {
Rest.setUrl(newNodeData.related.create_approval_template);
approvalTemplatePromises.push(Rest.post({
name: node.unifiedJobTemplate.name,
description: node.unifiedJobTemplate.description,
timeout: node.unifiedJobTemplate.timeout
}).then(() => {
node.originalNodeObject = data;
nodeIdToChartNodeIdMapping[data.id] = parseInt(workflowMakerNodeId);
node.originalNodeObject = newNodeData;
nodeIdToChartNodeIdMapping[newNodeData.id] = parseInt(workflowMakerNodeId);
}).catch(({ data, status }) => {
Wait('stop');
ProcessErrors($scope, data, status, null, {
@ -179,9 +177,9 @@ export default ['$scope', 'TemplatesService',
addPromises.push(TemplatesService.addWorkflowNode({
url: $scope.workflowJobTemplateObj.related.workflow_nodes,
data: buildSendableNodeData(node)
}).then(({data}) => {
node.originalNodeObject = data;
nodeIdToChartNodeIdMapping[data.id] = parseInt(workflowMakerNodeId);
}).then(({data: newNodeData}) => {
node.originalNodeObject = newNodeData;
nodeIdToChartNodeIdMapping[newNodeData.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
@ -194,7 +192,7 @@ export default ['$scope', 'TemplatesService',
credentialIdsToPost.forEach((credentialToPost) => {
credentialRequests.push({
id: data.id,
id: newNodeData.id,
data: {
id: credentialToPost.id
}
@ -211,12 +209,11 @@ export default ['$scope', 'TemplatesService',
} 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
}
Rest.setUrl(node.originalNodeObject.related.unified_job_template);
approvalTemplatePromises.push(Rest.patch({
name: node.unifiedJobTemplate.name,
description: node.unifiedJobTemplate.description,
timeout: node.unifiedJobTemplate.timeout
}).catch(({ data, status }) => {
Wait('stop');
ProcessErrors($scope, data, status, null, {
@ -224,12 +221,11 @@ export default ['$scope', 'TemplatesService',
});
}));
} else {
approvalTemplatePromises.push(TemplatesService.createApprovalTemplate({
url: node.originalNodeObject.related.create_approval_template,
data: {
name: node.unifiedJobTemplate.name,
description: node.unifiedJobTemplate.description
}
Rest.setUrl(newNodeData.related.create_approval_template);
approvalTemplatePromises.push(Rest.post({
name: node.unifiedJobTemplate.name,
description: node.unifiedJobTemplate.description,
timeout: node.unifiedJobTemplate.timeout
}).catch(({ data, status }) => {
Wait('stop');
ProcessErrors($scope, data, status, null, {
@ -301,7 +297,6 @@ export default ['$scope', 'TemplatesService',
$q.all(approvalTemplatePromises)
.then(() => {
let disassociatePromises = [];
let associatePromises = [];
let linkMap = {};
// Build a link map for easy access
@ -376,59 +371,60 @@ export default ['$scope', 'TemplatesService',
}
});
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;
}
});
});
$q.all(disassociatePromises)
.then(() => {
let associatePromises = [];
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;
}
});
});
let credentialPromises = credentialRequests.map((request) => {
return TemplatesService.postWorkflowNodeCredential({
id: request.id,
@ -589,6 +585,7 @@ export default ['$scope', 'TemplatesService',
unifiedJobTemplate: {
name: selectedTemplate.name,
description: selectedTemplate.description,
timeout: selectedTemplate.timeout,
unified_job_type: "workflow_approval"
},
isNew: true
@ -631,6 +628,7 @@ export default ['$scope', 'TemplatesService',
nodeRef[$scope.nodeConfig.nodeId].unifiedJobTemplate = {
name: selectedTemplate.name,
description: selectedTemplate.description,
timeout: selectedTemplate.timeout,
unified_job_type: "workflow_approval"
};
nodeRef[$scope.nodeConfig.nodeId].isEdited = true;
@ -643,7 +641,8 @@ export default ['$scope', 'TemplatesService',
node.unifiedJobTemplate = {
unified_job_type: 'workflow_approval',
name: selectedTemplate.name,
description: selectedTemplate.description
description: selectedTemplate.description,
timeout: selectedTemplate.timeout,
};
} else {
node.unifiedJobTemplate = selectedTemplate;