From 4a6a3b27fa26d7fe63e34b542e98daa712342c27 Mon Sep 17 00:00:00 2001 From: mabashian Date: Wed, 14 Nov 2018 16:48:32 -0500 Subject: [PATCH] Fixed a number of workflow visualizer bugs. Added loading spinners while data is being loaded/processed. --- .../features/templates/templates.strings.js | 4 +- .../workflow-chart.directive.js | 8 +- .../workflow-chart/workflow-chart.service.js | 39 +- .../forms/workflow-node-form.controller.js | 419 ++++++++++-------- .../forms/workflow-node-form.partial.html | 98 +++- .../workflow-maker/workflow-maker.block.less | 4 + .../workflow-maker.controller.js | 89 +++- .../workflow-results.controller.js | 4 +- 8 files changed, 397 insertions(+), 268 deletions(-) diff --git a/awx/ui/client/features/templates/templates.strings.js b/awx/ui/client/features/templates/templates.strings.js index baaae1f783..ab77d0d7b1 100644 --- a/awx/ui/client/features/templates/templates.strings.js +++ b/awx/ui/client/features/templates/templates.strings.js @@ -128,7 +128,9 @@ function TemplatesStrings (BaseString) { EDIT_LINK: t.s('EDIT LINK'), VIEW_LINK: t.s('VIEW LINK'), NEW_LINK: t.s('Please click on an available node to form a new link.'), - UNLINK: t.s('UNLINK') + UNLINK: t.s('UNLINK'), + READ_ONLY_PROMPT_VALUES: t.s('The following promptable values were provided when this node was created:'), + READ_ONLY_NO_PROMPT_VALUES: t.s('No promptable values were provided when this node was created.') }; } diff --git a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js index d4b6ca208e..6d6520a1fa 100644 --- a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js +++ b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js @@ -114,7 +114,11 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge // There's something off with the math on the root node... if (d.source.id === 1) { - sourceY = sourceY + 10; + if (scope.mode === "details") { + sourceY = sourceY + 17; + } else { + sourceY = sourceY + 10; + } } let points = [{ @@ -1279,7 +1283,7 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge function node_click() { this.on("click", function(d) { - if(d.id !== scope.graphState.nodeBeingAdded && !scope.readOnly){ + if(d.id !== scope.graphState.nodeBeingAdded){ if(scope.graphState.isLinkMode && !d.isInvalidLinkTarget && scope.graphState.addLinkSource !== d.id) { $('.WorkflowChart-potentialLink').remove(); scope.selectNodeForLinking({ diff --git a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.service.js b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.service.js index 4c51ecc1d1..2973dc1e70 100644 --- a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.service.js +++ b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.service.js @@ -1,42 +1,5 @@ export default [function(){ return { - generateDepthMap: (arrayOfLinks) => { - let depthMap = {}; - let nodesWithChildren = {}; - - let walkBranch = (nodeId, depth) => { - depthMap[nodeId] = depthMap[nodeId] ? (depth > depthMap[nodeId] ? depth : depthMap[nodeId]) : depth; - if (nodesWithChildren[nodeId]) { - _.forEach(nodesWithChildren[nodeId].children, (childNodeId) => { - walkBranch(childNodeId, depth+1); - }); - } - }; - - let rootNodeIds = []; - arrayOfLinks.forEach(link => { - // link.source.index of 0 is our artificial start node - if (link.source.index !== 0) { - if (!nodesWithChildren[link.source.id]) { - nodesWithChildren[link.source.id] = { - children: [] - }; - } - - nodesWithChildren[link.source.id].children.push(link.target.id); - } else { - // Store the fact that might be a root node - rootNodeIds.push(link.target.id); - } - }); - - _.forEach(rootNodeIds, function(rootNodeId) { - walkBranch(rootNodeId, 1); - depthMap[rootNodeId] = 1; - }); - - return depthMap; - }, generateArraysOfNodesAndLinks: function(allNodes) { let nonRootNodeIds = []; let allNodeIds = []; @@ -77,7 +40,7 @@ export default [function(){ nodeObj.job = node.summary_fields.job; } if(node.summary_fields.unified_job_template) { - nodeObj.unifiedJobTemplate = node.summary_fields.unified_job_template; + nodeRef[nodeIdCounter].unifiedJobTemplate = nodeObj.unifiedJobTemplate = node.summary_fields.unified_job_template; } arrayOfNodesForChart.push(nodeObj); diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.controller.js b/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.controller.js index 2f876da2e0..0faf9af2be 100644 --- a/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.controller.js +++ b/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.controller.js @@ -7,11 +7,11 @@ export default ['$scope', 'TemplatesService', 'JobTemplateModel', 'PromptService', 'Rest', '$q', 'TemplatesStrings', 'CreateSelect2', 'Empty', 'generateList', 'QuerySet', 'GetBasePath', 'TemplateList', 'ProjectList', 'InventorySourcesList', 'ProcessErrors', - 'i18n', + 'i18n', 'ParseTypeChange', function($scope, TemplatesService, JobTemplate, PromptService, Rest, $q, TemplatesStrings, CreateSelect2, Empty, generateList, qs, GetBasePath, TemplateList, ProjectList, InventorySourcesList, ProcessErrors, - i18n + i18n, ParseTypeChange ) { let promptWatcher, credentialsWatcher, surveyQuestionWatcher, listPromises = []; @@ -139,229 +139,254 @@ export default ['$scope', 'TemplatesService', 'JobTemplateModel', 'PromptService const finishConfiguringEdit = () => { - let jobTemplate = new JobTemplate(); + if (!$scope.readOnly) { + let jobTemplate = new JobTemplate(); - if (_.get($scope, 'nodeConfig.node.promptData') && !_.isEmpty($scope.nodeConfig.node.promptData)) { - $scope.promptData = _.cloneDeep($scope.nodeConfig.node.promptData); - const launchConf = $scope.promptData.launchConf; + if (_.get($scope, 'nodeConfig.node.promptData') && !_.isEmpty($scope.nodeConfig.node.promptData)) { + $scope.promptData = _.cloneDeep($scope.nodeConfig.node.promptData); + const launchConf = $scope.promptData.launchConf; - if (!launchConf.survey_enabled && - !launchConf.ask_inventory_on_launch && - !launchConf.ask_credential_on_launch && - !launchConf.ask_verbosity_on_launch && - !launchConf.ask_job_type_on_launch && - !launchConf.ask_limit_on_launch && - !launchConf.ask_tags_on_launch && - !launchConf.ask_skip_tags_on_launch && - !launchConf.ask_diff_mode_on_launch && - !launchConf.credential_needed_to_start && - !launchConf.ask_variables_on_launch && - launchConf.variables_needed_to_start.length === 0) { - $scope.showPromptButton = false; - $scope.promptModalMissingReqFields = false; - } else { - $scope.showPromptButton = true; - - if (launchConf.ask_inventory_on_launch && !_.has(launchConf, 'defaults.inventory') && !_.has($scope, 'nodeConfig.node.originalNodeObject.summary_fields.inventory')) { - $scope.promptModalMissingReqFields = true; + if (!launchConf.survey_enabled && + !launchConf.ask_inventory_on_launch && + !launchConf.ask_credential_on_launch && + !launchConf.ask_verbosity_on_launch && + !launchConf.ask_job_type_on_launch && + !launchConf.ask_limit_on_launch && + !launchConf.ask_tags_on_launch && + !launchConf.ask_skip_tags_on_launch && + !launchConf.ask_diff_mode_on_launch && + !launchConf.credential_needed_to_start && + !launchConf.ask_variables_on_launch && + launchConf.variables_needed_to_start.length === 0) { + $scope.showPromptButton = false; + $scope.promptModalMissingReqFields = false; } else { - $scope.promptModalMissingReqFields = false; - } - } - $scope.nodeFormDataLoaded = true; - } else if ( - _.get($scope, 'nodeConfig.node.fullUnifiedJobTemplateObject.unified_job_type') === 'job_template' || - _.get($scope, 'nodeConfig.node.fullUnifiedJobTemplateObject.type') === 'job_template' - ) { - let promises = [jobTemplate.optionsLaunch($scope.nodeConfig.node.fullUnifiedJobTemplateObject.id), jobTemplate.getLaunch($scope.nodeConfig.node.fullUnifiedJobTemplateObject.id)]; + $scope.showPromptButton = true; - if (_.has($scope, 'nodeConfig.node.originalNodeObject.related.credentials')) { - Rest.setUrl($scope.nodeConfig.node.originalNodeObject.related.credentials); - promises.push(Rest.get()); - } - - $q.all(promises) - .then((responses) => { - let launchOptions = responses[0].data, - launchConf = responses[1].data, - workflowNodeCredentials = responses[2] ? responses[2].data.results : []; - - let prompts = PromptService.processPromptValues({ - launchConf: responses[1].data, - launchOptions: responses[0].data, - currentValues: $scope.nodeConfig.node.originalNodeObject - }); - - let defaultCredsWithoutOverrides = []; - - prompts.credentials.previousOverrides = _.cloneDeep(workflowNodeCredentials); - - const credentialHasScheduleOverride = (templateDefaultCred) => { - let credentialHasOverride = false; - workflowNodeCredentials.forEach((scheduleCred) => { - if (templateDefaultCred.credential_type === scheduleCred.credential_type) { - if ( - (!templateDefaultCred.vault_id && !scheduleCred.inputs.vault_id) || - (templateDefaultCred.vault_id && scheduleCred.inputs.vault_id && templateDefaultCred.vault_id === scheduleCred.inputs.vault_id) - ) { - credentialHasOverride = true; - } - } - }); - - return credentialHasOverride; - }; - - if (_.has(launchConf, 'defaults.credentials')) { - launchConf.defaults.credentials.forEach((defaultCred) => { - if (!credentialHasScheduleOverride(defaultCred)) { - defaultCredsWithoutOverrides.push(defaultCred); - } - }); - } - - prompts.credentials.value = workflowNodeCredentials.concat(defaultCredsWithoutOverrides); - - if ((!$scope.nodeConfig.node.fullUnifiedJobTemplateObject.inventory && !launchConf.ask_inventory_on_launch) || !$scope.nodeConfig.node.fullUnifiedJobTemplateObject.project) { - $scope.selectedTemplateInvalid = true; + if (launchConf.ask_inventory_on_launch && !_.has(launchConf, 'defaults.inventory') && !_.has($scope, 'nodeConfig.node.originalNodeObject.summary_fields.inventory')) { + $scope.promptModalMissingReqFields = true; } else { - $scope.selectedTemplateInvalid = false; + $scope.promptModalMissingReqFields = false; } + } + $scope.nodeFormDataLoaded = true; + } else if ( + _.get($scope, 'nodeConfig.node.fullUnifiedJobTemplateObject.unified_job_type') === 'job_template' || + _.get($scope, 'nodeConfig.node.fullUnifiedJobTemplateObject.type') === 'job_template' + ) { + let promises = [jobTemplate.optionsLaunch($scope.nodeConfig.node.fullUnifiedJobTemplateObject.id), jobTemplate.getLaunch($scope.nodeConfig.node.fullUnifiedJobTemplateObject.id)]; - let credentialRequiresPassword = false; + if (_.has($scope, 'nodeConfig.node.originalNodeObject.related.credentials')) { + Rest.setUrl($scope.nodeConfig.node.originalNodeObject.related.credentials); + promises.push(Rest.get()); + } - prompts.credentials.value.forEach((credential) => { - if(credential.inputs) { - if ((credential.inputs.password && credential.inputs.password === "ASK") || - (credential.inputs.become_password && credential.inputs.become_password === "ASK") || - (credential.inputs.ssh_key_unlock && credential.inputs.ssh_key_unlock === "ASK") || - (credential.inputs.vault_password && credential.inputs.vault_password === "ASK") - ) { + $q.all(promises) + .then((responses) => { + let launchOptions = responses[0].data, + launchConf = responses[1].data, + workflowNodeCredentials = responses[2] ? responses[2].data.results : []; + + let prompts = PromptService.processPromptValues({ + launchConf: responses[1].data, + launchOptions: responses[0].data, + currentValues: $scope.nodeConfig.node.originalNodeObject + }); + + let defaultCredsWithoutOverrides = []; + + prompts.credentials.previousOverrides = _.cloneDeep(workflowNodeCredentials); + + const credentialHasScheduleOverride = (templateDefaultCred) => { + let credentialHasOverride = false; + workflowNodeCredentials.forEach((scheduleCred) => { + if (templateDefaultCred.credential_type === scheduleCred.credential_type) { + if ( + (!templateDefaultCred.vault_id && !scheduleCred.inputs.vault_id) || + (templateDefaultCred.vault_id && scheduleCred.inputs.vault_id && templateDefaultCred.vault_id === scheduleCred.inputs.vault_id) + ) { + credentialHasOverride = true; + } + } + }); + + return credentialHasOverride; + }; + + if (_.has(launchConf, 'defaults.credentials')) { + launchConf.defaults.credentials.forEach((defaultCred) => { + if (!credentialHasScheduleOverride(defaultCred)) { + defaultCredsWithoutOverrides.push(defaultCred); + } + }); + } + + prompts.credentials.value = workflowNodeCredentials.concat(defaultCredsWithoutOverrides); + + if ((!$scope.nodeConfig.node.fullUnifiedJobTemplateObject.inventory && !launchConf.ask_inventory_on_launch) || !$scope.nodeConfig.node.fullUnifiedJobTemplateObject.project) { + $scope.selectedTemplateInvalid = true; + } else { + $scope.selectedTemplateInvalid = false; + } + + let credentialRequiresPassword = false; + + prompts.credentials.value.forEach((credential) => { + if(credential.inputs) { + if ((credential.inputs.password && credential.inputs.password === "ASK") || + (credential.inputs.become_password && credential.inputs.become_password === "ASK") || + (credential.inputs.ssh_key_unlock && credential.inputs.ssh_key_unlock === "ASK") || + (credential.inputs.vault_password && credential.inputs.vault_password === "ASK") + ) { + credentialRequiresPassword = true; + } + } else if (credential.passwords_needed && credential.passwords_needed.length > 0) { credentialRequiresPassword = true; } - } else if (credential.passwords_needed && credential.passwords_needed.length > 0) { - credentialRequiresPassword = true; - } - }); + }); - $scope.credentialRequiresPassword = credentialRequiresPassword; + $scope.credentialRequiresPassword = credentialRequiresPassword; - if (!launchConf.survey_enabled && - !launchConf.ask_inventory_on_launch && - !launchConf.ask_credential_on_launch && - !launchConf.ask_verbosity_on_launch && - !launchConf.ask_job_type_on_launch && - !launchConf.ask_limit_on_launch && - !launchConf.ask_tags_on_launch && - !launchConf.ask_skip_tags_on_launch && - !launchConf.ask_diff_mode_on_launch && - !launchConf.credential_needed_to_start && - !launchConf.ask_variables_on_launch && - launchConf.variables_needed_to_start.length === 0) { - $scope.showPromptButton = false; - $scope.promptModalMissingReqFields = false; - $scope.nodeFormDataLoaded = true; - } else { - $scope.showPromptButton = true; - - if (launchConf.ask_inventory_on_launch && !_.has(launchConf, 'defaults.inventory') && !_.has($scope, 'nodeConfig.node.originalNodeObject.summary_fields.inventory')) { - $scope.promptModalMissingReqFields = true; + if (!launchConf.survey_enabled && + !launchConf.ask_inventory_on_launch && + !launchConf.ask_credential_on_launch && + !launchConf.ask_verbosity_on_launch && + !launchConf.ask_job_type_on_launch && + !launchConf.ask_limit_on_launch && + !launchConf.ask_tags_on_launch && + !launchConf.ask_skip_tags_on_launch && + !launchConf.ask_diff_mode_on_launch && + !launchConf.credential_needed_to_start && + !launchConf.ask_variables_on_launch && + launchConf.variables_needed_to_start.length === 0) { + $scope.showPromptButton = false; + $scope.promptModalMissingReqFields = false; + $scope.nodeFormDataLoaded = true; } else { - $scope.promptModalMissingReqFields = false; - } + $scope.showPromptButton = true; - if (responses[1].data.survey_enabled) { - // go out and get the survey questions - jobTemplate.getSurveyQuestions($scope.nodeConfig.node.fullUnifiedJobTemplateObject.id) - .then((surveyQuestionRes) => { + if (launchConf.ask_inventory_on_launch && !_.has(launchConf, 'defaults.inventory') && !_.has($scope, 'nodeConfig.node.originalNodeObject.summary_fields.inventory')) { + $scope.promptModalMissingReqFields = true; + } else { + $scope.promptModalMissingReqFields = false; + } - let processed = PromptService.processSurveyQuestions({ - surveyQuestions: surveyQuestionRes.data.spec, - extra_data: _.cloneDeep($scope.nodeConfig.node.originalNodeObject.extra_data) - }); + if (responses[1].data.survey_enabled) { + // go out and get the survey questions + jobTemplate.getSurveyQuestions($scope.nodeConfig.node.fullUnifiedJobTemplateObject.id) + .then((surveyQuestionRes) => { - $scope.missingSurveyValue = processed.missingSurveyValue; - - $scope.extraVars = (processed.extra_data === '' || _.isEmpty(processed.extra_data)) ? '---' : '---\n' + jsyaml.safeDump(processed.extra_data); - - $scope.nodeConfig.node.promptData = $scope.promptData = { - launchConf: launchConf, - launchOptions: launchOptions, - prompts: prompts, - surveyQuestions: surveyQuestionRes.data.spec, - template: $scope.nodeConfig.node.fullUnifiedJobTemplateObject.id - }; - - surveyQuestionWatcher = $scope.$watch('promptData.surveyQuestions', () => { - let missingSurveyValue = false; - _.each($scope.promptData.surveyQuestions, (question) => { - if (question.required && (Empty(question.model) || question.model === [])) { - missingSurveyValue = true; - } + let processed = PromptService.processSurveyQuestions({ + surveyQuestions: surveyQuestionRes.data.spec, + extra_data: _.cloneDeep($scope.nodeConfig.node.originalNodeObject.extra_data) }); - $scope.missingSurveyValue = missingSurveyValue; - }, true); - checkCredentialsForRequiredPasswords(); + $scope.missingSurveyValue = processed.missingSurveyValue; - watchForPromptChanges(); + $scope.extraVars = (processed.extra_data === '' || _.isEmpty(processed.extra_data)) ? '---' : '---\n' + jsyaml.safeDump(processed.extra_data); - $scope.nodeFormDataLoaded = true; - }); - } else { - $scope.nodeConfig.node.promptData = $scope.promptData = { - launchConf: launchConf, - launchOptions: launchOptions, - prompts: prompts, - template: $scope.nodeConfig.node.fullUnifiedJobTemplateObject.id - }; + $scope.nodeConfig.node.promptData = $scope.promptData = { + launchConf: launchConf, + launchOptions: launchOptions, + prompts: prompts, + surveyQuestions: surveyQuestionRes.data.spec, + template: $scope.nodeConfig.node.fullUnifiedJobTemplateObject.id + }; - checkCredentialsForRequiredPasswords(); + surveyQuestionWatcher = $scope.$watch('promptData.surveyQuestions', () => { + let missingSurveyValue = false; + _.each($scope.promptData.surveyQuestions, (question) => { + if (question.required && (Empty(question.model) || question.model === [])) { + missingSurveyValue = true; + } + }); + $scope.missingSurveyValue = missingSurveyValue; + }, true); - watchForPromptChanges(); + checkCredentialsForRequiredPasswords(); - $scope.nodeFormDataLoaded = true; + watchForPromptChanges(); + + $scope.nodeFormDataLoaded = true; + }); + } else { + $scope.nodeConfig.node.promptData = $scope.promptData = { + launchConf: launchConf, + launchOptions: launchOptions, + prompts: prompts, + template: $scope.nodeConfig.node.fullUnifiedJobTemplateObject.id + }; + + checkCredentialsForRequiredPasswords(); + + watchForPromptChanges(); + + $scope.nodeFormDataLoaded = true; + } } - } - }); - } else { - $scope.nodeFormDataLoaded = true; - } + }); + } else { + $scope.nodeFormDataLoaded = true; + } - if (_.get($scope, 'nodeConfig.node.fullUnifiedJobTemplateObject')) { - if (_.get($scope, 'nodeConfig.node.fullUnifiedJobTemplateObject.type') === "job_template") { + if (_.get($scope, 'nodeConfig.node.fullUnifiedJobTemplateObject')) { + if (_.get($scope, 'nodeConfig.node.fullUnifiedJobTemplateObject.type') === "job_template") { + $scope.activeTab = "jobs"; + } + + $scope.selectedTemplate = $scope.nodeConfig.node.fullUnifiedJobTemplateObject; + + if ($scope.selectedTemplate.unified_job_type) { + switch ($scope.selectedTemplate.unified_job_type) { + case "job": + $scope.activeTab = "jobs"; + break; + case "project_update": + $scope.activeTab = "project_syncs"; + break; + case "inventory_update": + $scope.activeTab = "inventory_syncs"; + break; + } + } else if ($scope.selectedTemplate.type) { + switch ($scope.selectedTemplate.type) { + case "job_template": + $scope.activeTab = "jobs"; + break; + case "project": + $scope.activeTab = "project_syncs"; + break; + case "inventory_source": + $scope.activeTab = "inventory_syncs"; + break; + } + } + } else { $scope.activeTab = "jobs"; } - - $scope.selectedTemplate = $scope.nodeConfig.node.fullUnifiedJobTemplateObject; - - if ($scope.selectedTemplate.unified_job_type) { - switch ($scope.selectedTemplate.unified_job_type) { - case "job": - $scope.activeTab = "jobs"; - break; - case "project_update": - $scope.activeTab = "project_syncs"; - break; - case "inventory_update": - $scope.activeTab = "inventory_syncs"; - break; - } - } else if ($scope.selectedTemplate.type) { - switch ($scope.selectedTemplate.type) { - case "job_template": - $scope.activeTab = "jobs"; - break; - case "project": - $scope.activeTab = "project_syncs"; - break; - case "inventory_source": - $scope.activeTab = "inventory_syncs"; - break; - } - } } else { - $scope.activeTab = "jobs"; + $scope.jobTags = $scope.nodeConfig.node.originalNodeObject.job_tags ? $scope.nodeConfig.node.originalNodeObject.job_tags.split(',').map((tag) => (tag)) : []; + $scope.skipTags = $scope.nodeConfig.node.originalNodeObject.skip_tags ? $scope.nodeConfig.node.originalNodeObject.skip_tags.split(',').map((tag) => (tag)) : []; + $scope.showJobTags = true; + $scope.showSkipTags = true; + + if (!$.isEmptyObject($scope.nodeConfig.node.originalNodeObject.extra_data)) { + $scope.extraVars = '---\n' + jsyaml.safeDump($scope.nodeConfig.node.originalNodeObject.extra_data); + $scope.showExtraVars = true; + $scope.parseType = 'yaml'; + + ParseTypeChange({ + scope: $scope, + variable: 'extraVars', + field_id: 'workflow_node_form_extra_vars', + readOnly: true + }); + } else { + $scope.extraVars = null; + $scope.showExtraVars = false; + } + + $scope.nodeFormDataLoaded = true; } }; diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.partial.html b/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.partial.html index 0732e14819..b8866b47b1 100644 --- a/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.partial.html +++ b/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.partial.html @@ -1,11 +1,11 @@
-
{{nodeConfig.mode === 'edit' ? node.unifiedJobTemplate.name : strings.get('workflow_maker.ADD_A_TEMPLATE')}}
-
+
{{nodeConfig.mode === 'edit' ? nodeConfig.node.unifiedJobTemplate.name : strings.get('workflow_maker.ADD_A_TEMPLATE')}}
+
{{strings.get('workflow_maker.JOBS')}}
{{strings.get('workflow_maker.PROJECT_SYNC')}}
{{strings.get('workflow_maker.INVENTORY_SYNC')}}
-
+
@@ -141,6 +141,98 @@
+
+
+ {{:: strings.get('workflow_maker.READ_ONLY_PROMPT_VALUES')}} +
+
+ {{:: strings.get('workflow_maker.READ_ONLY_NO_PROMPT_VALUES')}} +
+
+
{{:: strings.get('prompt.JOB_TYPE') }}
+
+ {{:: strings.get('prompt.PLAYBOOK_RUN') }} + {{:: strings.get('prompt.CHECK') }} +
+
+
+
{{:: strings.get('prompt.INVENTORY') }}
+
+
+
+
{{:: strings.get('prompt.LIMIT') }}
+
+
+
+
{{:: strings.get('prompt.VERBOSITY') }}
+
+
+
+
+ {{:: strings.get('prompt.JOB_TAGS') }}  + + + + +
+
+
+
+ {{tag}} +
+
+
+
+
+
+ {{:: strings.get('prompt.SKIP_TAGS') }}  + + + + +
+
+
+
+ {{tag}} +
+
+
+
+
+
{{:: strings.get('prompt.SHOW_CHANGES') }}
+
+ {{:: strings.get('ON') }} + {{:: strings.get('OFF') }} +
+
+
+
{{:: strings.get('prompt.EXTRA_VARIABLES') }}
+
+ +
+
+
diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less index 3dd307a426..e2f0e39a2e 100644 --- a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less +++ b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less @@ -309,6 +309,10 @@ color: @default-err; } +.WorkflowMaker-readOnlyPromptText { + margin-bottom: 20px; +} + .Key-list { margin: 0; padding: 20px; diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.controller.js b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.controller.js index b0fcf857a6..85ebb2aaa7 100644 --- a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.controller.js +++ b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.controller.js @@ -7,9 +7,12 @@ export default ['$scope', 'TemplatesService', 'ProcessErrors', 'CreateSelect2', '$q', 'JobTemplateModel', 'Empty', 'PromptService', 'Rest', 'TemplatesStrings', 'WorkflowChartService', + 'Wait', function ($scope, TemplatesService, ProcessErrors, CreateSelect2, $q, JobTemplate, - Empty, PromptService, Rest, TemplatesStrings, WorkflowChartService) { + Empty, PromptService, Rest, TemplatesStrings, WorkflowChartService, + Wait + ) { $scope.strings = TemplatesStrings; $scope.preventCredsWithPasswords = true; @@ -90,10 +93,12 @@ export default ['$scope', 'TemplatesService', $scope.saveWorkflowMaker = function () { + Wait('start'); + if ($scope.graphState.arrayOfNodesForChart.length > 1) { let addPromises = []; let editPromises = []; - let credentialsToPost = []; + let credentialRequests = []; Object.keys(nodeRef).map((workflowMakerNodeId) => { if (nodeRef[workflowMakerNodeId].isNew) { @@ -114,8 +119,8 @@ export default ['$scope', 'TemplatesService', }); credentialIdsToPost.forEach((credentialToPost) => { - credentialsToPost.push({ - id: data.id, + credentialRequests.push({ + id: data.data.id, data: { id: credentialToPost.id } @@ -128,6 +133,51 @@ export default ['$scope', 'TemplatesService', id: nodeRef[workflowMakerNodeId].originalNodeObject.id, data: buildSendableNodeData(nodeRef[workflowMakerNodeId]) })); + + if (_.get(nodeRef[workflowMakerNodeId], 'promptData.launchConf.ask_credential_on_launch')) { + let credentialsNotInPriorCredentials = nodeRef[workflowMakerNodeId].promptData.prompts.credentials.value.filter(function (credFromPrompt) { + let defaultCreds = _.get(nodeRef[workflowMakerNodeId], 'promptData.launchConf.defaults.credentials', []); + return !defaultCreds.some(function (defaultCred) { + return credFromPrompt.id === defaultCred.id; + }); + }); + + let credentialsToAdd = credentialsNotInPriorCredentials.filter(function (credNotInPrior) { + let previousOverrides = _.get(nodeRef[workflowMakerNodeId], 'promptData.prompts.credentials.previousOverrides', []); + return !previousOverrides.some(function (priorCred) { + return credNotInPrior.id === priorCred.id; + }); + }); + + let credentialsToRemove = []; + + if (_.has(nodeRef[workflowMakerNodeId], 'promptData.prompts.credentials.previousOverrides')) { + credentialsToRemove = nodeRef[workflowMakerNodeId].promptData.prompts.credentials.previousOverrides.filter(function (priorCred) { + return !credentialsNotInPriorCredentials.some(function (credNotInPrior) { + return priorCred.id === credNotInPrior.id; + }); + }); + } + + credentialsToAdd.forEach((credentialToAdd) => { + credentialRequests.push({ + id: nodeRef[workflowMakerNodeId].originalNodeObject.id, + data: { + id: credentialToAdd.id + } + }); + }); + + credentialsToRemove.forEach((credentialToRemove) => { + credentialRequests.push({ + id: nodeRef[workflowMakerNodeId].originalNodeObject.id, + data: { + id: credentialToRemove.id, + disassociate: true + } + }); + }); + } } }); @@ -222,7 +272,7 @@ export default ['$scope', 'TemplatesService', case "success": if ( !nodeRef[sourceChartNodeId].originalNodeObject.success_nodes || - !nodeRef[sourceChartNodeId].originalNodeObject.success_nodes.includes(nodeRef[targetChartNodeId].id) + !nodeRef[sourceChartNodeId].originalNodeObject.success_nodes.includes(nodeRef[targetChartNodeId].originalNodeObject.id) ) { associatePromises.push( TemplatesService.associateWorkflowNode({ @@ -236,7 +286,7 @@ export default ['$scope', 'TemplatesService', case "failure": if ( !nodeRef[sourceChartNodeId].originalNodeObject.failure_nodes || - !nodeRef[sourceChartNodeId].originalNodeObject.failure_nodes.includes(nodeRef[targetChartNodeId].id) + !nodeRef[sourceChartNodeId].originalNodeObject.failure_nodes.includes(nodeRef[targetChartNodeId].originalNodeObject.id) ) { associatePromises.push( TemplatesService.associateWorkflowNode({ @@ -250,7 +300,7 @@ export default ['$scope', 'TemplatesService', case "always": if ( !nodeRef[sourceChartNodeId].originalNodeObject.always_nodes || - !nodeRef[sourceChartNodeId].originalNodeObject.always_nodes.includes(nodeRef[targetChartNodeId].id) + !nodeRef[sourceChartNodeId].originalNodeObject.always_nodes.includes(nodeRef[targetChartNodeId].originalNodeObject.id) ) { associatePromises.push( TemplatesService.associateWorkflowNode({ @@ -267,7 +317,7 @@ export default ['$scope', 'TemplatesService', $q.all(disassociatePromises) .then(function () { - let credentialPromises = credentialsToPost.map(function (request) { + let credentialPromises = credentialRequests.map(function (request) { return TemplatesService.postWorkflowNodeCredential({ id: request.id, data: request.data @@ -276,12 +326,14 @@ export default ['$scope', 'TemplatesService', return $q.all(associatePromises.concat(credentialPromises)) .then(function () { + Wait('stop'); $scope.closeDialog(); }); }).catch(({ data, status }) => { + Wait('stop'); ProcessErrors($scope, data, status, null, {}); }); }); @@ -294,6 +346,7 @@ export default ['$scope', 'TemplatesService', $q.all(deletePromises) .then(function () { + Wait('stop'); $scope.closeDialog(); $state.transitionTo('templates'); }); @@ -335,8 +388,6 @@ export default ['$scope', 'TemplatesService', workflowMakerNodeIdCounter++; - $scope.graphState.depthMap = WorkflowChartService.generateDepthMap($scope.graphState.arrayOfLinksForChart); - $scope.$broadcast("refreshWorkflowChart"); $scope.formState.showNodeForm = true; @@ -383,8 +434,6 @@ export default ['$scope', 'TemplatesService', workflowMakerNodeIdCounter++; - $scope.graphState.depthMap = WorkflowChartService.generateDepthMap($scope.graphState.arrayOfLinksForChart); - $scope.$broadcast("refreshWorkflowChart"); $scope.formState.showNodeForm = true; @@ -480,7 +529,6 @@ export default ['$scope', 'TemplatesService', } } - $scope.graphState.depthMap = WorkflowChartService.generateDepthMap($scope.graphState.arrayOfLinksForChart); } else if ($scope.nodeConfig.mode === "edit") { $scope.graphState.nodeBeingEdited = null; } @@ -560,7 +608,6 @@ export default ['$scope', 'TemplatesService', // User is going from editing one link to editing another if ($scope.linkConfig.mode === "add") { $scope.graphState.arrayOfLinksForChart.splice($scope.graphState.arrayOfLinksForChart.length-1, 1); - $scope.graphState.depthMap = WorkflowChartService.generateDepthMap($scope.graphState.arrayOfLinksForChart); } setupLinkEdit(); } @@ -603,8 +650,6 @@ export default ['$scope', 'TemplatesService', } }); - $scope.graphState.depthMap = WorkflowChartService.generateDepthMap($scope.graphState.arrayOfLinksForChart); - $scope.graphState.isLinkMode = false; } else { // This is the first node selected @@ -689,8 +734,6 @@ export default ['$scope', 'TemplatesService', } } - $scope.graphState.depthMap = WorkflowChartService.generateDepthMap($scope.graphState.arrayOfLinksForChart); - $scope.formState.showLinkForm = false; $scope.linkConfig = null; $scope.$broadcast("refreshWorkflowChart"); @@ -713,7 +756,6 @@ export default ['$scope', 'TemplatesService', edgeType: "always" }); } - $scope.graphState.depthMap = WorkflowChartService.generateDepthMap($scope.graphState.arrayOfLinksForChart); } $scope.graphState.linkBeingEdited = null; $scope.graphState.addLinkSource = null; @@ -804,8 +846,6 @@ export default ['$scope', 'TemplatesService', $scope.deleteOverlayVisible = false; - $scope.graphState.depthMap = WorkflowChartService.generateDepthMap($scope.graphState.arrayOfLinksForChart); - $scope.nodeToBeDeleted = null; $scope.deleteOverlayVisible = false; @@ -848,7 +888,7 @@ export default ['$scope', 'TemplatesService', let page = 1; let getNodes = function () { - // Get the workflow nodes + Wait('start'); TemplatesService.getWorkflowJobTemplateNodes($scope.workflowJobTemplateObj.id, page) .then(function (data) { for (var i = 0; i < data.data.results.length; i++) { @@ -864,11 +904,12 @@ export default ['$scope', 'TemplatesService', ({arrayOfNodesForChart, arrayOfLinksForChart, chartNodeIdToIndexMapping, nodeIdToChartNodeIdMapping, nodeRef, workflowMakerNodeIdCounter} = WorkflowChartService.generateArraysOfNodesAndLinks(allNodes)); - let depthMap = WorkflowChartService.generateDepthMap(arrayOfLinksForChart); + $scope.graphState = { arrayOfNodesForChart, arrayOfLinksForChart }; - $scope.graphState = { arrayOfNodesForChart, arrayOfLinksForChart, depthMap }; + Wait('stop'); } }, function ({ data, status, config }) { + Wait('stop'); ProcessErrors($scope, data, status, null, { hdr: $scope.strings.get('error.HEADER'), msg: $scope.strings.get('error.CALL', { diff --git a/awx/ui/client/src/workflow-results/workflow-results.controller.js b/awx/ui/client/src/workflow-results/workflow-results.controller.js index 8f7eef7ae8..7096bed720 100644 --- a/awx/ui/client/src/workflow-results/workflow-results.controller.js +++ b/awx/ui/client/src/workflow-results/workflow-results.controller.js @@ -175,9 +175,7 @@ export default ['workflowData', 'workflowResultsService', 'workflowDataOptions', ({arrayOfNodesForChart, arrayOfLinksForChart, chartNodeIdToIndexMapping, nodeIdToChartNodeIdMapping} = WorkflowChartService.generateArraysOfNodesAndLinks(workflowNodes)); - let depthMap = WorkflowChartService.generateDepthMap(arrayOfLinksForChart); - - $scope.graphState = { arrayOfNodesForChart, arrayOfLinksForChart, depthMap }; + $scope.graphState = { arrayOfNodesForChart, arrayOfLinksForChart }; } $scope.toggleStdoutFullscreen = function() {