diff --git a/awx/ui/client/features/templates/list-templates.controller.js b/awx/ui/client/features/templates/list-templates.controller.js index f6f9123272..550a6fbe9c 100644 --- a/awx/ui/client/features/templates/list-templates.controller.js +++ b/awx/ui/client/features/templates/list-templates.controller.js @@ -16,14 +16,12 @@ function ListTemplatesController( $state, Alert, Dataset, - InitiatePlaybookRun, ProcessErrors, Prompt, PromptService, resolvedModels, strings, - Wait, - Empty + Wait ) { const vm = this || {}; const [jobTemplate, workflowTemplate] = resolvedModels; @@ -42,7 +40,7 @@ function ListTemplatesController( position: 'right', arrowHeight: 15 } - } + }; $scope.canAddJobTemplate = jobTemplate.options('actions.POST'); $scope.canAddWorkflowJobTemplate = workflowTemplate.options('actions.POST'); @@ -347,6 +345,7 @@ function ListTemplatesController( launchConf: launchData.data, launchOptions: launchOptions.data, template: template.id, + templateType: template.type, prompts: PromptService.processPromptValues({ launchConf: launchData.data, launchOptions: launchOptions.data @@ -368,79 +367,75 @@ function ListTemplatesController( } function runWorkflowTemplate(template) { - InitiatePlaybookRun({ scope: $scope, id: template.id, job_type: 'workflow_job_template' }); + const selectedWorkflowJobTemplate = workflowTemplate.create(); + const preLaunchPromises = [ + selectedWorkflowJobTemplate.getLaunch(template.id), + selectedWorkflowJobTemplate.optionsLaunch(template.id), + ]; + + Promise.all(preLaunchPromises) + .then(([launchData, launchOptions]) => { + if (selectedWorkflowJobTemplate.canLaunchWithoutPrompt()) { + return selectedWorkflowJobTemplate + .postLaunch({ id: template.id }) + .then(({ data }) => { + $state.go('workflowResults', { id: data.workflow_job }, { reload: true }); + }); + } + + const promptData = { + launchConf: launchData.data, + launchOptions: launchOptions.data, + template: template.id, + templateType: template.type, + prompts: PromptService.processPromptValues({ + launchConf: launchData.data, + launchOptions: launchOptions.data + }), + triggerModalOpen: true, + }; + + if (launchData.data.survey_enabled) { + selectedWorkflowJobTemplate.getSurveyQuestions(template.id) + .then(({ data }) => { + const processed = PromptService.processSurveyQuestions({ surveyQuestions: data.spec }); + promptData.surveyQuestions = processed.surveyQuestions; + $scope.promptData = promptData; + }); + } else { + $scope.promptData = promptData; + } + }); } $scope.launchJob = () => { - const jobLaunchData = { - extra_vars: $scope.promptData.extraVars - }; - - if ($scope.promptData.launchConf.ask_tags_on_launch){ - jobLaunchData.job_tags = $scope.promptData.prompts.tags.value.map(a => a.value).join(); - } - - if ($scope.promptData.launchConf.ask_skip_tags_on_launch){ - jobLaunchData.skip_tags = $scope.promptData.prompts.skipTags.value.map(a => a.value).join(); - } - - if ($scope.promptData.launchConf.ask_limit_on_launch && _.has($scope, 'promptData.prompts.limit.value')){ - jobLaunchData.limit = $scope.promptData.prompts.limit.value; - } - - if ($scope.promptData.launchConf.ask_job_type_on_launch && _.has($scope, 'promptData.prompts.jobType.value.value')) { - jobLaunchData.job_type = $scope.promptData.prompts.jobType.value.value; - } - - if ($scope.promptData.launchConf.ask_verbosity_on_launch && _.has($scope, 'promptData.prompts.verbosity.value.value')) { - jobLaunchData.verbosity = $scope.promptData.prompts.verbosity.value.value; - } - - if ($scope.promptData.launchConf.ask_inventory_on_launch && !Empty($scope.promptData.prompts.inventory.value.id)){ - jobLaunchData.inventory_id = $scope.promptData.prompts.inventory.value.id; - } - - if ($scope.promptData.launchConf.ask_credential_on_launch){ - jobLaunchData.credentials = []; - $scope.promptData.prompts.credentials.value.forEach((credential) => { - jobLaunchData.credentials.push(credential.id); - }); - } - - if ($scope.promptData.launchConf.ask_diff_mode_on_launch && _.has($scope, 'promptData.prompts.diffMode.value')) { - jobLaunchData.diff_mode = $scope.promptData.prompts.diffMode.value; - } - - if ($scope.promptData.prompts.credentials.passwords) { - _.forOwn($scope.promptData.prompts.credentials.passwords, (val, key) => { - if (!jobLaunchData.credential_passwords) { - jobLaunchData.credential_passwords = {}; - } - if (key === "ssh_key_unlock") { - jobLaunchData.credential_passwords.ssh_key_unlock = val.value; - } else if (key !== "vault") { - jobLaunchData.credential_passwords[`${key}_password`] = val.value; - } else { - _.each(val, (vaultCred) => { - jobLaunchData.credential_passwords[vaultCred.vault_id ? `${key}_password.${vaultCred.vault_id}` : `${key}_password`] = vaultCred.value; - }); - } - }); - } + const jobLaunchData = PromptService.bundlePromptDataForLaunch($scope.promptData); // If the extra_vars dict is empty, we don't want to include it if we didn't prompt for anything. if(_.isEmpty(jobLaunchData.extra_vars) && !($scope.promptData.launchConf.ask_variables_on_launch && $scope.promptData.launchConf.survey_enabled && $scope.promptData.surveyQuestions.length > 0)){ delete jobLaunchData.extra_vars; } - jobTemplate.create().postLaunch({ - id: $scope.promptData.template, - launchData: jobLaunchData - }) - .then((launchRes) => { - $state.go('jobResult', { id: launchRes.data.job }, { reload: true }); - }) - .catch(createErrorHandler('launch job template', 'POST')); + if($scope.promptData.templateType === 'job_template') { + jobTemplate.create().postLaunch({ + id: $scope.promptData.template, + launchData: jobLaunchData + }) + .then((launchRes) => { + $state.go('jobResult', { id: launchRes.data.job }, { reload: true }); + }) + .catch(createErrorHandler('launch job template', 'POST')); + } else if($scope.promptData.templateType === 'workflow_job_template') { + workflowTemplate.create().postLaunch({ + id: $scope.promptData.template, + launchData: jobLaunchData + }) + .then((launchRes) => { + $state.go('workflowResults', { id: launchRes.data.workflow_job }, { reload: true }); + }) + .catch(createErrorHandler('launch workflow job template', 'POST')); + } + }; } @@ -450,14 +445,12 @@ ListTemplatesController.$inject = [ '$state', 'Alert', 'Dataset', - 'InitiatePlaybookRun', 'ProcessErrors', 'Prompt', 'PromptService', 'resolvedModels', 'TemplatesStrings', - 'Wait', - 'Empty' + 'Wait' ]; export default ListTemplatesController; diff --git a/awx/ui/client/lib/components/relaunchButton/relaunchButton.component.js b/awx/ui/client/lib/components/relaunchButton/relaunchButton.component.js index 465404c10e..dc69ef47c4 100644 --- a/awx/ui/client/lib/components/relaunchButton/relaunchButton.component.js +++ b/awx/ui/client/lib/components/relaunchButton/relaunchButton.component.js @@ -5,17 +5,108 @@ const atRelaunch = { bindings: { state: '<' }, - controller: ['RelaunchJob', 'InitiatePlaybookRun', 'ComponentsStrings', '$scope', atRelaunchCtrl], + controller: ['ProcessErrors', 'AdhocRun', 'ComponentsStrings', + 'ProjectModel', 'InventorySourceModel', 'WorkflowJobModel', 'Alert', + 'AdHocCommandModel', 'JobModel', 'JobTemplateModel', 'PromptService', + 'GetBasePath', '$state', '$q', '$scope', atRelaunchCtrl + ], controllerAs: 'vm' }; -function atRelaunchCtrl (RelaunchJob, InitiatePlaybookRun, strings, $scope) { +function atRelaunchCtrl ( + ProcessErrors, AdhocRun, strings, + Project, InventorySource, WorkflowJob, Alert, + AdHocCommand, Job, JobTemplate, PromptService, + GetBasePath, $state, $q, $scope +) { const vm = this; const scope = $scope.$parent; const job = _.get(scope, 'job') || _.get(scope, 'completed_job'); + const jobObj = new Job(); + const jobTemplate = new JobTemplate(); + + const checkRelaunchPlaybook = (option) => { + jobObj.getRelaunch({ + id: job.id + }).then((getRelaunchRes) => { + if ( + getRelaunchRes.data.passwords_needed_to_start && + getRelaunchRes.data.passwords_needed_to_start.length > 0 + ) { + const jobPromises = [ + jobObj.request('get', job.id), + jobTemplate.optionsLaunch(job.unified_job_template) + ]; + + $q.all(jobPromises) + .then(([jobRes, launchOptions]) => { + const populatedJob = jobRes.data; + const jobTypeChoices = _.get( + launchOptions, + 'data.actions.POST.job_type.choices', + [] + ).map(c => ({ label: c[1], value: c[0] })); + const verbosityChoices = _.get( + launchOptions, + 'data.actions.POST.verbosity.choices', + [] + ).map(c => ({ label: c[1], value: c[0] })); + + vm.promptData = { + launchConf: { + passwords_needed_to_start: getRelaunchRes.data.passwords_needed_to_start + }, + launchOptions: launchOptions.data, + job: job.id, + relaunchHostType: option ? (option.name).toLowerCase() : null, + prompts: { + credentials: { + value: populatedJob.summary_fields.credentials ? populatedJob.summary_fields.credentials : [] + }, + variables: { + value: populatedJob.extra_vars + }, + inventory: { + value: populatedJob.summary_fields.inventory ? populatedJob.summary_fields.inventory : null + }, + verbosity: { + value: _.find(verbosityChoices, item => item.value === populatedJob.verbosity), + choices: verbosityChoices + }, + jobType: { + value: _.find(jobTypeChoices, item => item.value === populatedJob.job_type), + choices: jobTypeChoices + }, + limit: { + value: populatedJob.limit + }, + tags: { + value: populatedJob.job_tags + }, + skipTags: { + value: populatedJob.skip_tags + }, + diffMode: { + value: populatedJob.diff_mode + } + }, + triggerModalOpen: true + }; + }); + } else { + jobObj.postRelaunch({ + id: job.id + }).then((launchRes) => { + if (!$state.includes('jobs')) { + $state.go('jobResult', { id: launchRes.data.id }, { reload: true }); + } + }); + } + }); + }; vm.$onInit = () => { - vm.showRelaunch = !(job.type === 'system_job') && job.summary_fields.user_capabilities.start; + vm.showRelaunch = job.type !== 'system_job' && job.summary_fields.user_capabilities.start; vm.showDropdown = job.type === 'job' && job.failed === true; vm.createDropdown(); @@ -46,27 +137,97 @@ function atRelaunchCtrl (RelaunchJob, InitiatePlaybookRun, strings, $scope) { }; vm.relaunchJob = () => { - let typeId; - if (job.type === 'inventory_update') { - typeId = job.inventory_source; - } else if (job.type === 'project_update') { - typeId = job.project; - } else if (job.type === 'job' || job.type === 'system_job' - || job.type === 'ad_hoc_command' || job.type === 'workflow_job') { - typeId = job.id; - } + const inventorySource = new InventorySource(); - RelaunchJob({ scope, id: typeId, type: job.type, name: job.name }); + inventorySource.getUpdate(job.inventory_source) + .then((getUpdateRes) => { + if (getUpdateRes.data.can_update) { + inventorySource.postUpdate(job.inventory_source) + .then((postUpdateRes) => { + if (!$state.includes('jobs')) { + $state.go('inventorySyncStdout', { id: postUpdateRes.data.id }, { reload: true }); + } + }); + } else { + Alert( + 'Permission Denied', 'You do not have permission to sync this inventory source. Please contact your system administrator.', + 'alert-danger' + ); + } + }); + } else if (job.type === 'project_update') { + const project = new Project(); + + project.getUpdate(job.project) + .then((getUpdateRes) => { + if (getUpdateRes.data.can_update) { + project.postUpdate(job.project) + .then((postUpdateRes) => { + if (!$state.includes('jobs')) { + $state.go('scmUpdateStdout', { id: postUpdateRes.data.id }, { reload: true }); + } + }); + } else { + Alert( + 'Permission Denied', 'You do not have access to update this project. Please contact your system administrator.', + 'alert-danger' + ); + } + }); + } else if (job.type === 'workflow_job') { + const workflowJob = new WorkflowJob(); + + workflowJob.postRelaunch({ + id: job.id + }).then((launchRes) => { + if (!$state.includes('jobs')) { + $state.go('workflowResults', { id: launchRes.data.id }, { reload: true }); + } + }); + } else if (job.type === 'ad_hoc_command') { + const adHocCommand = new AdHocCommand(); + + adHocCommand.getRelaunch({ + id: job.id + }).then((getRelaunchRes) => { + if ( + getRelaunchRes.data.passwords_needed_to_start && + getRelaunchRes.data.passwords_needed_to_start.length > 0 + ) { + AdhocRun({ scope, project_id: job.id, relaunch: true }); + } else { + adHocCommand.postRelaunch({ + id: job.id + }).then((launchRes) => { + if (!$state.includes('jobs')) { + $state.go('adHocJobStdout', { id: launchRes.data.id }, { reload: true }); + } + }); + } + }); + } else if (job.type === 'job') { + checkRelaunchPlaybook(); + } }; vm.relaunchOn = (option) => { - InitiatePlaybookRun({ - scope, - id: job.id, - relaunch: true, - job_type: job.type, - host_type: (option.name).toLowerCase() + checkRelaunchPlaybook(option); + }; + + vm.relaunchJobWithPassword = () => { + jobObj.postRelaunch({ + id: vm.promptData.job, + relaunchData: PromptService.bundlePromptDataForRelaunch(vm.promptData) + }).then((launchRes) => { + if (!$state.includes('jobs')) { + $state.go('jobResult', { id: launchRes.data.job }, { reload: true }); + } + }).catch(({ data, status }) => { + ProcessErrors($scope, data, status, null, { + hdr: 'Error!', + msg: `Error relaunching job. POST returned status: ${status}` + }); }); }; } diff --git a/awx/ui/client/lib/components/relaunchButton/relaunchButton.partial.html b/awx/ui/client/lib/components/relaunchButton/relaunchButton.partial.html index 5ce4c45c17..69b3eea711 100644 --- a/awx/ui/client/lib/components/relaunchButton/relaunchButton.partial.html +++ b/awx/ui/client/lib/components/relaunchButton/relaunchButton.partial.html @@ -30,4 +30,5 @@ ng-if="!vm.showDropdown"> - \ No newline at end of file + + diff --git a/awx/ui/client/lib/models/AdHocCommand.js b/awx/ui/client/lib/models/AdHocCommand.js new file mode 100644 index 0000000000..c398219531 --- /dev/null +++ b/awx/ui/client/lib/models/AdHocCommand.js @@ -0,0 +1,44 @@ +let Base; +let $http; + +function getRelaunch (params) { + const req = { + method: 'GET', + url: `${this.path}${params.id}/relaunch/` + }; + + return $http(req); +} + +function postRelaunch (params) { + const req = { + method: 'POST', + url: `${this.path}${params.id}/relaunch/` + }; + + return $http(req); +} + +function AdHocCommandModel (method, resource, config) { + Base.call(this, 'ad_hoc_commands'); + + this.Constructor = AdHocCommandModel; + this.postRelaunch = postRelaunch.bind(this); + this.getRelaunch = getRelaunch.bind(this); + + return this.create(method, resource, config); +} + +function AdHocCommandModelLoader (BaseModel, _$http_) { + Base = BaseModel; + $http = _$http_; + + return AdHocCommandModel; +} + +AdHocCommandModelLoader.$inject = [ + 'BaseModel', + '$http' +]; + +export default AdHocCommandModelLoader; diff --git a/awx/ui/client/lib/models/InventorySource.js b/awx/ui/client/lib/models/InventorySource.js index 27ec05b6e9..0d68a11780 100644 --- a/awx/ui/client/lib/models/InventorySource.js +++ b/awx/ui/client/lib/models/InventorySource.js @@ -1,5 +1,6 @@ let Base; let WorkflowJobTemplateNode; +let $http; function setDependentResources (id) { this.dependentResources = [ @@ -12,28 +13,51 @@ function setDependentResources (id) { ]; } +function getUpdate (id) { + const req = { + method: 'GET', + url: `${this.path}${id}/update/` + }; + + return $http(req); +} + +function postUpdate (id) { + const req = { + method: 'POST', + url: `${this.path}${id}/update/` + }; + + return $http(req); +} + function InventorySourceModel (method, resource, config) { Base.call(this, 'inventory_sources'); this.Constructor = InventorySourceModel; this.setDependentResources = setDependentResources.bind(this); + this.getUpdate = getUpdate.bind(this); + this.postUpdate = postUpdate.bind(this); return this.create(method, resource, config); } function InventorySourceModelLoader ( BaseModel, - WorkflowJobTemplateNodeModel + WorkflowJobTemplateNodeModel, + _$http_ ) { Base = BaseModel; WorkflowJobTemplateNode = WorkflowJobTemplateNodeModel; + $http = _$http_; return InventorySourceModel; } InventorySourceModelLoader.$inject = [ 'BaseModel', - 'WorkflowJobTemplateNodeModel' + 'WorkflowJobTemplateNodeModel', + '$http' ]; export default InventorySourceModelLoader; diff --git a/awx/ui/client/lib/models/Job.js b/awx/ui/client/lib/models/Job.js index 9be420b2f9..7d87f82330 100644 --- a/awx/ui/client/lib/models/Job.js +++ b/awx/ui/client/lib/models/Job.js @@ -1,21 +1,48 @@ let Base; +let $http; + +function getRelaunch (params) { + const req = { + method: 'GET', + url: `${this.path}${params.id}/relaunch/` + }; + + return $http(req); +} + +function postRelaunch (params) { + const req = { + method: 'POST', + url: `${this.path}${params.id}/relaunch/` + }; + + if (params.relaunchData) { + req.data = params.relaunchData; + } + + return $http(req); +} function JobModel (method, resource, config) { Base.call(this, 'jobs'); this.Constructor = JobModel; + this.postRelaunch = postRelaunch.bind(this); + this.getRelaunch = getRelaunch.bind(this); return this.create(method, resource, config); } -function JobModelLoader (BaseModel) { +function JobModelLoader (BaseModel, _$http_) { Base = BaseModel; + $http = _$http_; return JobModel; } JobModelLoader.$inject = [ - 'BaseModel' + 'BaseModel', + '$http' ]; export default JobModelLoader; diff --git a/awx/ui/client/lib/models/Project.js b/awx/ui/client/lib/models/Project.js index bb08c09179..02606c3dcd 100644 --- a/awx/ui/client/lib/models/Project.js +++ b/awx/ui/client/lib/models/Project.js @@ -2,6 +2,7 @@ let Base; let JobTemplate; let WorkflowJobTemplateNode; let InventorySource; +let $http; function setDependentResources (id) { this.dependentResources = [ @@ -26,11 +27,31 @@ function setDependentResources (id) { ]; } +function getUpdate (id) { + const req = { + method: 'GET', + url: `${this.path}${id}/update/` + }; + + return $http(req); +} + +function postUpdate (id) { + const req = { + method: 'POST', + url: `${this.path}${id}/update/` + }; + + return $http(req); +} + function ProjectModel (method, resource, config) { Base.call(this, 'projects'); this.Constructor = ProjectModel; this.setDependentResources = setDependentResources.bind(this); + this.getUpdate = getUpdate.bind(this); + this.postUpdate = postUpdate.bind(this); return this.create(method, resource, config); } @@ -40,11 +61,13 @@ function ProjectModelLoader ( JobTemplateModel, WorkflowJobTemplateNodeModel, InventorySourceModel, + _$http_ ) { Base = BaseModel; JobTemplate = JobTemplateModel; WorkflowJobTemplateNode = WorkflowJobTemplateNodeModel; InventorySource = InventorySourceModel; + $http = _$http_; return ProjectModel; } @@ -53,7 +76,8 @@ ProjectModelLoader.$inject = [ 'BaseModel', 'JobTemplateModel', 'WorkflowJobTemplateNodeModel', - 'InventorySourceModel' + 'InventorySourceModel', + '$http' ]; export default ProjectModelLoader; diff --git a/awx/ui/client/lib/models/WorkflowJob.js b/awx/ui/client/lib/models/WorkflowJob.js index 06ed451883..02ea3582cc 100644 --- a/awx/ui/client/lib/models/WorkflowJob.js +++ b/awx/ui/client/lib/models/WorkflowJob.js @@ -1,21 +1,34 @@ let Base; +let $http; + +function postRelaunch (params) { + const req = { + method: 'POST', + url: `${this.path}${params.id}/relaunch/` + }; + + return $http(req); +} function WorkflowJobModel (method, resource, config) { Base.call(this, 'workflow_jobs'); this.Constructor = WorkflowJobModel; + this.postRelaunch = postRelaunch.bind(this); return this.create(method, resource, config); } -function WorkflowJobModelLoader (BaseModel) { +function WorkflowJobModelLoader (BaseModel, _$http_) { Base = BaseModel; + $http = _$http_; return WorkflowJobModel; } WorkflowJobModelLoader.$inject = [ - 'BaseModel' + 'BaseModel', + '$http' ]; export default WorkflowJobModelLoader; diff --git a/awx/ui/client/lib/models/WorkflowJobTemplate.js b/awx/ui/client/lib/models/WorkflowJobTemplate.js index d0ff06fc78..79198bbe82 100644 --- a/awx/ui/client/lib/models/WorkflowJobTemplate.js +++ b/awx/ui/client/lib/models/WorkflowJobTemplate.js @@ -1,21 +1,85 @@ let Base; +let $http; + +function optionsLaunch (id) { + const req = { + method: 'OPTIONS', + url: `${this.path}${id}/launch/` + }; + + return $http(req); +} + +function getLaunch (id) { + const req = { + method: 'GET', + url: `${this.path}${id}/launch/` + }; + + return $http(req) + .then(res => { + this.model.launch.GET = res.data; + + return res; + }); +} + +function postLaunch (params) { + const req = { + method: 'POST', + url: `${this.path}${params.id}/launch/` + }; + + if (params.launchData) { + req.data = params.launchData; + } + + return $http(req); +} + +function getSurveyQuestions (id) { + const req = { + method: 'GET', + url: `${this.path}${id}/survey_spec/` + }; + + return $http(req); +} + +function canLaunchWithoutPrompt () { + const launchData = this.model.launch.GET; + + return ( + launchData.can_start_without_user_input && + !launchData.survey_enabled + ); +} function WorkflowJobTemplateModel (method, resource, config) { Base.call(this, 'workflow_job_templates'); this.Constructor = WorkflowJobTemplateModel; + this.optionsLaunch = optionsLaunch.bind(this); + this.getLaunch = getLaunch.bind(this); + this.postLaunch = postLaunch.bind(this); + this.getSurveyQuestions = getSurveyQuestions.bind(this); + this.canLaunchWithoutPrompt = canLaunchWithoutPrompt.bind(this); + + this.model.launch = {}; return this.create(method, resource, config); } -function WorkflowJobTemplateModelLoader (BaseModel) { +function WorkflowJobTemplateModelLoader (BaseModel, _$http_) { Base = BaseModel; + $http = _$http_; return WorkflowJobTemplateModel; } WorkflowJobTemplateModelLoader.$inject = [ - 'BaseModel' + 'BaseModel', + '$http' ]; export default WorkflowJobTemplateModelLoader; diff --git a/awx/ui/client/lib/models/index.js b/awx/ui/client/lib/models/index.js index 9f3e57b3cd..3efc8d5299 100644 --- a/awx/ui/client/lib/models/index.js +++ b/awx/ui/client/lib/models/index.js @@ -1,6 +1,7 @@ import atLibServices from '~services'; import Application from '~models/Application'; +import AdHocCommand from '~models/AdHocCommand'; import Base from '~models/Base'; import Config from '~models/Config'; import Credential from '~models/Credential'; @@ -30,6 +31,7 @@ angular atLibServices ]) .service('ApplicationModel', Application) + .service('AdHocCommandModel', AdHocCommand) .service('BaseModel', Base) .service('ConfigModel', Config) .service('CredentialModel', Credential) diff --git a/awx/ui/client/src/home/dashboard/lists/job-templates/job-templates-list.directive.js b/awx/ui/client/src/home/dashboard/lists/job-templates/job-templates-list.directive.js index 1a34984a79..0456388539 100644 --- a/awx/ui/client/src/home/dashboard/lists/job-templates/job-templates-list.directive.js +++ b/awx/ui/client/src/home/dashboard/lists/job-templates/job-templates-list.directive.js @@ -1,10 +1,13 @@ /* jshint unused: vars */ export default - [ 'InitiatePlaybookRun', - 'templateUrl', + [ 'templateUrl', '$state', 'Alert', - function JobTemplatesList(InitiatePlaybookRun, templateUrl, $state, Alert) { + 'JobTemplateModel', + 'WorkflowJobTemplateModel', + 'PromptService', + 'ProcessErrors', + function JobTemplatesList(templateUrl, $state, Alert, JobTemplate, WorkflowJobTemplate, PromptService, ProcessErrors) { return { restrict: 'E', link: link, @@ -15,6 +18,9 @@ export default }; function link(scope, element, attr) { + const jobTemplate = new JobTemplate(); + const workflowTemplate = new WorkflowJobTemplate(); + scope.$watch("data", function(data) { if (data) { if (data.length > 0) { @@ -42,21 +48,131 @@ export default scope.launchTemplate = function(template){ if(template) { - if(template.type && (template.type === 'Job Template' || template.type === 'job_template')) { - InitiatePlaybookRun({ scope: scope, id: template.id, job_type: 'job_template' }); - } - else if(template.type && (template.type === 'Workflow Job Template' || template.type === 'workflow_job_template')) { - InitiatePlaybookRun({ scope: scope, id: template.id, job_type: 'workflow_job_template' }); - } - else { - // Something went wrong - Let the user know that we're unable to launch because we don't know - // what type of job template this is - Alert('Error: Unable to determine template type', 'We were unable to determine this template\'s type while launching.'); - } + if(template.type && (template.type === 'Job Template' || template.type === 'job_template')) { + const selectedJobTemplate = jobTemplate.create(); + const preLaunchPromises = [ + selectedJobTemplate.getLaunch(template.id), + selectedJobTemplate.optionsLaunch(template.id), + ]; + + Promise.all(preLaunchPromises) + .then(([launchData, launchOptions]) => { + if (selectedJobTemplate.canLaunchWithoutPrompt()) { + return selectedJobTemplate + .postLaunch({ id: template.id }) + .then(({ data }) => { + $state.go('jobResult', { id: data.job }, { reload: true }); + }); + } + + const promptData = { + launchConf: launchData.data, + launchOptions: launchOptions.data, + template: template.id, + templateType: template.type, + prompts: PromptService.processPromptValues({ + launchConf: launchData.data, + launchOptions: launchOptions.data + }), + triggerModalOpen: true, + }; + + if (launchData.data.survey_enabled) { + selectedJobTemplate.getSurveyQuestions(template.id) + .then(({ data }) => { + const processed = PromptService.processSurveyQuestions({ surveyQuestions: data.spec }); + promptData.surveyQuestions = processed.surveyQuestions; + scope.promptData = promptData; + }); + } else { + scope.promptData = promptData; + } + }); + } + else if(template.type && (template.type === 'Workflow Job Template' || template.type === 'workflow_job_template')) { + const selectedWorkflowJobTemplate = workflowTemplate.create(); + const preLaunchPromises = [ + selectedWorkflowJobTemplate.getLaunch(template.id), + selectedWorkflowJobTemplate.optionsLaunch(template.id), + ]; + + Promise.all(preLaunchPromises) + .then(([launchData, launchOptions]) => { + if (selectedWorkflowJobTemplate.canLaunchWithoutPrompt()) { + return selectedWorkflowJobTemplate + .postLaunch({ id: template.id }) + .then(({ data }) => { + $state.go('workflowResults', { id: data.workflow_job }, { reload: true }); + }); + } + + const promptData = { + launchConf: launchData.data, + launchOptions: launchOptions.data, + template: template.id, + templateType: template.type, + prompts: PromptService.processPromptValues({ + launchConf: launchData.data, + launchOptions: launchOptions.data + }), + triggerModalOpen: true, + }; + + if (launchData.data.survey_enabled) { + selectedWorkflowJobTemplate.getSurveyQuestions(template.id) + .then(({ data }) => { + const processed = PromptService.processSurveyQuestions({ surveyQuestions: data.spec }); + promptData.surveyQuestions = processed.surveyQuestions; + scope.promptData = promptData; + }); + } else { + scope.promptData = promptData; + } + }); } else { - Alert('Error: Unable to launch template', 'Template parameter is missing'); + // Something went wrong - Let the user know that we're unable to launch because we don't know + // what type of job template this is + Alert('Error: Unable to determine template type', 'We were unable to determine this template\'s type while launching.'); } + } + else { + Alert('Error: Unable to launch template', 'Template parameter is missing'); + } + }; + + scope.launchJob = () => { + const jobLaunchData = PromptService.bundlePromptDataForLaunch(scope.promptData); + + // If the extra_vars dict is empty, we don't want to include it if we didn't prompt for anything. + if(_.isEmpty(jobLaunchData.extra_vars) && !(scope.promptData.launchConf.ask_variables_on_launch && scope.promptData.launchConf.survey_enabled && scope.promptData.surveyQuestions.length > 0)){ + delete jobLaunchData.extra_vars; + } + + if(scope.promptData.templateType === 'job_template') { + jobTemplate.create().postLaunch({ + id: scope.promptData.template, + launchData: jobLaunchData + }) + .then((launchRes) => { + $state.go('jobResult', { id: launchRes.data.job }, { reload: true }); + }) + .catch(({data, status}) => { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Failed to launch job template: ' + status }); + }); + } else if(scope.promptData.templateType === 'workflow_job_template') { + workflowTemplate.create().postLaunch({ + id: scope.promptData.template, + launchData: jobLaunchData + }) + .then((launchRes) => { + $state.go('workflowResults', { id: launchRes.data.workflow_job }, { reload: true }); + }) + .catch(({data, status}) => { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Failed to launch workflow job template: ' + status }); + }); + } + }; scope.editTemplate = function (template) { diff --git a/awx/ui/client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html b/awx/ui/client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html index 760f01a08d..67a1795f7d 100644 --- a/awx/ui/client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html +++ b/awx/ui/client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html @@ -56,3 +56,4 @@ You can create a job template here.

+ diff --git a/awx/ui/client/src/inventories-hosts/inventories/adhoc/adhoc.controller.js b/awx/ui/client/src/inventories-hosts/inventories/adhoc/adhoc.controller.js index 0d3d79df37..5aa5938714 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/adhoc/adhoc.controller.js +++ b/awx/ui/client/src/inventories-hosts/inventories/adhoc/adhoc.controller.js @@ -230,25 +230,25 @@ function adhocController($q, $scope, $stateParams, $scope.removeStartAdhocRun(); } $scope.removeStartAdhocRun = $scope.$on('StartAdhocRun', function() { - var password; - for (password in $scope.passwords) { - data[$scope.passwords[password]] = $scope[ - $scope.passwords[password] - ]; - } - // Launch the adhoc job - Rest.setUrl(GetBasePath('inventory') + id + '/ad_hoc_commands/'); - Rest.post(data) - .then(({data}) => { - Wait('stop'); - $state.go('adHocJobStdout', {id: data.id}); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, adhocForm, { - hdr: 'Error!', - msg: 'Failed to launch adhoc command. POST ' + - 'returned status: ' + status }); - }); + var password; + for (password in $scope.passwords) { + data[$scope.passwords[password]] = $scope[ + $scope.passwords[password] + ]; + } + // Launch the adhoc job + Rest.setUrl(GetBasePath('inventory') + id + '/ad_hoc_commands/'); + Rest.post(data) + .then(({data}) => { + Wait('stop'); + $state.go('adHocJobStdout', {id: data.id}); + }) + .catch(({data, status}) => { + ProcessErrors($scope, data, status, adhocForm, { + hdr: 'Error!', + msg: 'Failed to launch adhoc command. POST ' + + 'returned status: ' + status }); + }); }); if ($scope.removeCreateLaunchDialog) { diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/completed-jobs/completed-jobs.list.js b/awx/ui/client/src/inventories-hosts/inventories/related/completed-jobs/completed-jobs.list.js index 8580524392..5ab048c55e 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/related/completed-jobs/completed-jobs.list.js +++ b/awx/ui/client/src/inventories-hosts/inventories/related/completed-jobs/completed-jobs.list.js @@ -71,12 +71,8 @@ export default ['i18n', function(i18n) { columnClass: 'col-lg-2 col-md-2 col-sm-3 col-xs-4', submit: { - icon: 'icon-rocket', - mode: 'all', - ngClick: 'relaunchJob($event, completed_job.id)', - awToolTip: i18n._('Relaunch using the same parameters'), - dataPlacement: 'top', ngShow: "!completed_job.type == 'system_job' || completed_job.summary_fields.user_capabilities.start", + // uses the at-relaunch directive relaunch: true }, "delete": { diff --git a/awx/ui/client/src/job-results/job-results.controller.js b/awx/ui/client/src/job-results/job-results.controller.js index b85bdbc8f9..e7e8f2c716 100644 --- a/awx/ui/client/src/job-results/job-results.controller.js +++ b/awx/ui/client/src/job-results/job-results.controller.js @@ -237,10 +237,6 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy jobResultsService.cancelJob($scope.job); }; - $scope.relaunchJob = function() { - jobResultsService.relaunchJob($scope); - }; - $scope.lessLabels = false; $scope.toggleLessLabels = function() { if (!$scope.lessLabels) { diff --git a/awx/ui/client/src/job-results/job-results.route.js b/awx/ui/client/src/job-results/job-results.route.js index bbfd0282c0..60c06de7cd 100644 --- a/awx/ui/client/src/job-results/job-results.route.js +++ b/awx/ui/client/src/job-results/job-results.route.js @@ -47,7 +47,7 @@ export default { return [preScope, eventOn]; }], // the GET for the particular job - jobData: ['Rest', 'GetBasePath', '$stateParams', '$q', '$state', 'Alert', 'jobResultsService', function(Rest, GetBasePath, $stateParams, $q, $state, Alert, jobResultsService) { + jobData: ['jobResultsService', '$stateParams', function(jobResultsService, $stateParams) { return jobResultsService.getJobData($stateParams.id); }], Dataset: ['QuerySet', '$stateParams', 'jobData', diff --git a/awx/ui/client/src/job-results/job-results.service.js b/awx/ui/client/src/job-results/job-results.service.js index 5175aa373e..6b77575fff 100644 --- a/awx/ui/client/src/job-results/job-results.service.js +++ b/awx/ui/client/src/job-results/job-results.service.js @@ -5,8 +5,8 @@ *************************************************/ -export default ['$q', 'Prompt', '$filter', 'Wait', 'Rest', '$state', 'ProcessErrors', 'InitiatePlaybookRun', 'GetBasePath', 'Alert', '$rootScope', 'i18n', -function ($q, Prompt, $filter, Wait, Rest, $state, ProcessErrors, InitiatePlaybookRun, GetBasePath, Alert, $rootScope, i18n) { +export default ['$q', 'Prompt', '$filter', 'Wait', 'Rest', '$state', 'ProcessErrors', 'GetBasePath', 'Alert', '$rootScope', 'i18n', +function ($q, Prompt, $filter, Wait, Rest, $state, ProcessErrors, GetBasePath, Alert, $rootScope, i18n) { var val = { // the playbook_on_stats event returns the count data in a weird format. // format to what we need! @@ -162,10 +162,6 @@ function ($q, Prompt, $filter, Wait, Rest, $state, ProcessErrors, InitiatePlaybo actionText: i18n._('PROCEED') }); }, - relaunchJob: function(scope) { - InitiatePlaybookRun({ scope: scope, id: scope.job.id, - relaunch: true }); - }, getJobData: function(id){ var val = $q.defer(); diff --git a/awx/ui/client/src/job-submission/job-submission-factories/inventory-update.factory.js b/awx/ui/client/src/job-submission/job-submission-factories/inventory-update.factory.js index ddb1f67ab7..f218ab2767 100644 --- a/awx/ui/client/src/job-submission/job-submission-factories/inventory-update.factory.js +++ b/awx/ui/client/src/job-submission/job-submission-factories/inventory-update.factory.js @@ -24,13 +24,6 @@ export default } }); - if (scope.removePromptForPasswords) { - scope.removePromptForPasswords(); - } - scope.removePromptForPasswords = scope.$on('PromptForPasswords', function() { - PromptForPasswords({ scope: scope, passwords: inventory_source.passwords_needed_to_update, callback: 'StartTheUpdate' }); - }); - if (scope.removeStartTheUpdate) { scope.removeStartTheUpdate(); } @@ -49,13 +42,7 @@ export default else { inventory_source = data; if (data.can_update) { - if (data.passwords_needed_to_update) { - Wait('stop'); - scope.$emit('PromptForPasswords'); - } - else { - scope.$emit('StartTheUpdate', {}); - } + scope.$emit('StartTheUpdate', {}); } else { Wait('stop'); Alert('Error Launching Sync', 'Unable to execute the inventory sync. Please contact your system administrator.', diff --git a/awx/ui/client/src/job-submission/job-submission-factories/project-update.factory.js b/awx/ui/client/src/job-submission/job-submission-factories/project-update.factory.js index be732e0047..be7fe069fa 100644 --- a/awx/ui/client/src/job-submission/job-submission-factories/project-update.factory.js +++ b/awx/ui/client/src/job-submission/job-submission-factories/project-update.factory.js @@ -25,13 +25,6 @@ export default } }); - if (scope.removePromptForPasswords) { - scope.removePromptForPasswords(); - } - scope.removePromptForPasswords = scope.$on('PromptForPasswords', function() { - PromptForPasswords({ scope: scope, passwords: project.passwords_needed_to_update, callback: 'StartTheUpdate' }); - }); - if (scope.removeStartTheUpdate) { scope.removeStartTheUpdate(); } @@ -46,13 +39,7 @@ export default .then(({data}) => { project = data; if (project.can_update) { - if (project.passwords_needed_to_updated) { - Wait('stop'); - scope.$emit('PromptForPasswords'); - } - else { - scope.$emit('StartTheUpdate', {}); - } + scope.$emit('StartTheUpdate', {}); } else { Alert('Permission Denied', 'You do not have access to update this project. Please contact your system administrator.', diff --git a/awx/ui/client/src/jobs/all-jobs.list.js b/awx/ui/client/src/jobs/all-jobs.list.js index f7737903a0..08cb3b99f7 100644 --- a/awx/ui/client/src/jobs/all-jobs.list.js +++ b/awx/ui/client/src/jobs/all-jobs.list.js @@ -92,12 +92,8 @@ export default ['i18n', function(i18n) { dataPlacement: "top" }, submit: { - icon: 'icon-rocket', - mode: 'all', - ngClick: 'relaunchJob($event, job.id)', - awToolTip: i18n._('Relaunch using the same parameters'), - dataPlacement: 'top', ngShow: "!(job.type == 'system_job') && job.summary_fields.user_capabilities.start", + // uses the at-relaunch directive relaunch: true, }, cancel: { diff --git a/awx/ui/client/src/jobs/factories/job-status-tool-tip.factory.js b/awx/ui/client/src/jobs/factories/job-status-tool-tip.factory.js deleted file mode 100644 index 126c9977d3..0000000000 --- a/awx/ui/client/src/jobs/factories/job-status-tool-tip.factory.js +++ /dev/null @@ -1,31 +0,0 @@ -export default - function JobStatusToolTip() { - return function(status) { - var toolTip; - switch (status) { - case 'successful': - case 'success': - toolTip = 'There were no failed tasks.'; - break; - case 'failed': - toolTip = 'Some tasks encountered errors.'; - break; - case 'canceled': - toolTip = 'Stopped by user request.'; - break; - case 'new': - toolTip = 'In queue, waiting on task manager.'; - break; - case 'waiting': - toolTip = 'SCM Update or Inventory Update is executing.'; - break; - case 'pending': - toolTip = 'Not in queue, waiting on task manager.'; - break; - case 'running': - toolTip = 'Playbook tasks executing.'; - break; - } - return toolTip; - }; - } diff --git a/awx/ui/client/src/jobs/factories/jobs-list-update.factory.js b/awx/ui/client/src/jobs/factories/jobs-list-update.factory.js deleted file mode 100644 index 5f950e458c..0000000000 --- a/awx/ui/client/src/jobs/factories/jobs-list-update.factory.js +++ /dev/null @@ -1,49 +0,0 @@ -export default - function JobsListUpdate() { - return function(params) { - var scope = params.scope, - parent_scope = params.parent_scope, - list = params.list; - - scope[list.name].forEach(function(item, item_idx) { - var fld, field, - itm = scope[list.name][item_idx]; - - //if (item.type === 'inventory_update') { - // itm.name = itm.name.replace(/^.*?:/,'').replace(/^: /,''); - //} - - // Set the item type label - if (list.fields.type) { - parent_scope.type_choices.forEach(function(choice) { - if (choice.value === item.type) { - itm.type_label = choice.label; - } - }); - } - // Set the job status label - parent_scope.status_choices.forEach(function(status) { - if (status.value === item.status) { - itm.status_label = status.label; - } - }); - - if (list.name === 'completed_jobs' || list.name === 'running_jobs') { - itm.status_tip = itm.status_label + '. Click for details.'; - } - else if (list.name === 'queued_jobs') { - itm.status_tip = 'Pending'; - } - - // Copy summary_field values - for (field in list.fields) { - fld = list.fields[field]; - if (fld.sourceModel) { - if (itm.summary_fields[fld.sourceModel]) { - itm[field] = itm.summary_fields[fld.sourceModel][fld.sourceField]; - } - } - } - }); - }; - } diff --git a/awx/ui/client/src/jobs/factories/relaunch-adhoc.factory.js b/awx/ui/client/src/jobs/factories/relaunch-adhoc.factory.js deleted file mode 100644 index 462501f097..0000000000 --- a/awx/ui/client/src/jobs/factories/relaunch-adhoc.factory.js +++ /dev/null @@ -1,11 +0,0 @@ -export default - function RelaunchAdhoc(AdhocRun) { - return function(params) { - var scope = params.scope, - id = params.id; - AdhocRun({ scope: scope, project_id: id, relaunch: true }); - }; - } - -RelaunchAdhoc.$inject = - [ 'AdhocRun' ]; diff --git a/awx/ui/client/src/jobs/factories/relaunch-inventory.factory.js b/awx/ui/client/src/jobs/factories/relaunch-inventory.factory.js deleted file mode 100644 index 3c9b3ecc4d..0000000000 --- a/awx/ui/client/src/jobs/factories/relaunch-inventory.factory.js +++ /dev/null @@ -1,26 +0,0 @@ -export default - function RelaunchInventory(Wait, Rest, InventoryUpdate, ProcessErrors, GetBasePath) { - return function(params) { - var scope = params.scope, - id = params.id, - url = GetBasePath('inventory_sources') + id + '/'; - Wait('start'); - Rest.setUrl(url); - Rest.get() - .then(({data}) => { - InventoryUpdate({ - scope: scope, - url: data.related.update - }); - }) - .catch(({data, status}) => { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve inventory source: ' + - url + ' GET returned: ' + status }); - }); - }; - } - -RelaunchInventory.$inject = - [ 'Wait', 'Rest', - 'InventoryUpdate', 'ProcessErrors', 'GetBasePath' - ]; diff --git a/awx/ui/client/src/jobs/factories/relaunch-job.factory.js b/awx/ui/client/src/jobs/factories/relaunch-job.factory.js deleted file mode 100644 index 0190cf9c79..0000000000 --- a/awx/ui/client/src/jobs/factories/relaunch-job.factory.js +++ /dev/null @@ -1,28 +0,0 @@ -export default - function RelaunchJob(RelaunchInventory, RelaunchPlaybook, RelaunchSCM, RelaunchAdhoc) { - return function(params) { - var scope = params.scope, - id = params.id, - type = params.type, - name = params.name; - if (type === 'inventory_update') { - RelaunchInventory({ scope: scope, id: id}); - } - else if (type === 'ad_hoc_command') { - RelaunchAdhoc({ scope: scope, id: id, name: name }); - } - else if (type === 'job' || type === 'system_job' || type === 'workflow_job') { - RelaunchPlaybook({ scope: scope, id: id, name: name, job_type: type }); - } - else if (type === 'project_update') { - RelaunchSCM({ scope: scope, id: id }); - } - }; - } - -RelaunchJob.$inject = - [ 'RelaunchInventory', - 'RelaunchPlaybook', - 'RelaunchSCM', - 'RelaunchAdhoc' - ]; diff --git a/awx/ui/client/src/jobs/factories/relaunch-playbook.factory.js b/awx/ui/client/src/jobs/factories/relaunch-playbook.factory.js deleted file mode 100644 index 78431b17be..0000000000 --- a/awx/ui/client/src/jobs/factories/relaunch-playbook.factory.js +++ /dev/null @@ -1,12 +0,0 @@ -export default - function RelaunchPlaybook(InitiatePlaybookRun) { - return function(params) { - var scope = params.scope, - id = params.id, - job_type = params.job_type; - InitiatePlaybookRun({ scope: scope, id: id, relaunch: true, job_type: job_type }); - }; - } - -RelaunchPlaybook.$inject = - [ 'InitiatePlaybookRun' ]; diff --git a/awx/ui/client/src/jobs/factories/relaunch-scm.factory.js b/awx/ui/client/src/jobs/factories/relaunch-scm.factory.js deleted file mode 100644 index b37e90e9ca..0000000000 --- a/awx/ui/client/src/jobs/factories/relaunch-scm.factory.js +++ /dev/null @@ -1,11 +0,0 @@ -export default - function RelaunchSCM(ProjectUpdate) { - return function(params) { - var scope = params.scope, - id = params.id; - ProjectUpdate({ scope: scope, project_id: id }); - }; - } - -RelaunchSCM.$inject = - [ 'ProjectUpdate' ]; diff --git a/awx/ui/client/src/jobs/jobs-list.controller.js b/awx/ui/client/src/jobs/jobs-list.controller.js index 7944e210f8..4ef6475023 100644 --- a/awx/ui/client/src/jobs/jobs-list.controller.js +++ b/awx/ui/client/src/jobs/jobs-list.controller.js @@ -11,11 +11,15 @@ */ export default ['$state', '$rootScope', '$scope', '$stateParams', - 'Find', 'DeleteJob', 'RelaunchJob', + 'Find', 'DeleteJob', 'GetBasePath', 'Dataset', 'QuerySet', 'ListDefinition', '$interpolate', + 'WorkflowJobModel', 'ProjectModel', 'Alert', 'InventorySourceModel', + 'AdHocCommandModel', 'JobModel', function($state, $rootScope, $scope, $stateParams, - Find, DeleteJob, RelaunchJob, - GetBasePath, Dataset, qs, ListDefinition, $interpolate) { + Find, DeleteJob, + GetBasePath, Dataset, qs, ListDefinition, $interpolate, + WorkflowJob, Project, Alert, InventorySource, + AdHocCommand, Job) { var list = ListDefinition; @@ -95,42 +99,6 @@ DeleteJob({ scope: $scope, id: id }); }; - $scope.relaunchJob = function(event, id) { - let job, typeId, jobs; - try { - $(event.target).tooltip('hide'); - } catch (e) { - //ignore - } - - if ($scope.completed_jobs) { - jobs = $scope.completed_jobs; - } - else if ($scope.running_jobs) { - jobs = $scope.running_jobs; - } - else if ($scope.queued_jobs) { - jobs = $scope.queued_jobs; - } - else if ($scope.all_jobs) { - jobs = $scope.all_jobs; - } - else if ($scope.jobs) { - jobs = $scope.jobs; - } - - job = Find({list: jobs, key: 'id', val: id }); - - if (job.type === 'inventory_update') { - typeId = job.inventory_source; - } else if (job.type === 'project_update') { - typeId = job.project; - } else if (job.type === 'job' || job.type === "system_job" || job.type === 'ad_hoc_command' || job.type === 'workflow_job') { - typeId = job.id; - } - RelaunchJob({ scope: $scope, id: typeId, type: job.type, name: job.name }); - }; - $scope.viewjobResults = function(job) { var goTojobResults = function(state) { diff --git a/awx/ui/client/src/jobs/main.js b/awx/ui/client/src/jobs/main.js index 2ee309875d..7aaf97035c 100644 --- a/awx/ui/client/src/jobs/main.js +++ b/awx/ui/client/src/jobs/main.js @@ -7,13 +7,6 @@ import jobsList from './jobs-list.controller'; import jobsRoute from './jobs.route'; import DeleteJob from './factories/delete-job.factory'; -import JobStatusToolTip from './factories/job-status-tool-tip.factory'; -import JobsListUpdate from './factories/jobs-list-update.factory'; -import RelaunchAdhoc from './factories/relaunch-adhoc.factory'; -import RelaunchInventory from './factories/relaunch-inventory.factory'; -import RelaunchJob from './factories/relaunch-job.factory'; -import RelaunchPlaybook from './factories/relaunch-playbook.factory'; -import RelaunchSCM from './factories/relaunch-scm.factory'; import AllJobsList from './all-jobs.list'; export default @@ -23,11 +16,4 @@ export default }]) .controller('JobsList', jobsList) .factory('DeleteJob', DeleteJob) - .factory('JobStatusToolTip', JobStatusToolTip) - .factory('JobsListUpdate', JobsListUpdate) - .factory('RelaunchAdhoc', RelaunchAdhoc) - .factory('RelaunchInventory', RelaunchInventory) - .factory('RelaunchJob', RelaunchJob) - .factory('RelaunchPlaybook', RelaunchPlaybook) - .factory('RelaunchSCM', RelaunchSCM) .factory('AllJobsList', AllJobsList); diff --git a/awx/ui/client/src/organizations/linkout/controllers/organizations-job-templates.controller.js b/awx/ui/client/src/organizations/linkout/controllers/organizations-job-templates.controller.js index 8673874890..3caeb4425b 100644 --- a/awx/ui/client/src/organizations/linkout/controllers/organizations-job-templates.controller.js +++ b/awx/ui/client/src/organizations/linkout/controllers/organizations-job-templates.controller.js @@ -6,11 +6,11 @@ export default ['$scope', '$rootScope', '$stateParams', 'Rest', 'ProcessErrors', - 'GetBasePath', 'InitiatePlaybookRun', 'Wait', 'TemplateCopyService', + 'GetBasePath', 'InitiatePlaybookRun', 'Wait', '$state', 'OrgJobTemplateList', 'OrgJobTemplateDataset', 'QuerySet', function($scope, $rootScope, $stateParams, Rest, ProcessErrors, - GetBasePath, InitiatePlaybookRun, Wait, TemplateCopyService, + GetBasePath, InitiatePlaybookRun, Wait, $state, OrgJobTemplateList, Dataset, qs) { var list = OrgJobTemplateList, @@ -81,24 +81,24 @@ export default ['$scope', '$rootScope', $state.go('jobTemplateSchedules', { id: id }); }; - $scope.copyTemplate = function(id) { - Wait('start'); - TemplateCopyService.get(id) - .then((data) => { - TemplateCopyService.set(data.results) - .then((results) => { - Wait('stop'); - if(results.type && results.type === 'job_template') { - $state.go('templates.editJobTemplate', {job_template_id: results.id}, {reload: true}); - } - }); - }) - .catch(({data, status}) => { - ProcessErrors($rootScope, data, status, null, {hdr: 'Error!', - msg: 'Call failed. Return status: '+ status}); - }); - - }; + // $scope.copyTemplate = function(id) { + // Wait('start'); + // TemplateCopyService.get(id) + // .then((data) => { + // TemplateCopyService.set(data.results) + // .then((results) => { + // Wait('stop'); + // if(results.type && results.type === 'job_template') { + // $state.go('templates.editJobTemplate', {job_template_id: results.id}, {reload: true}); + // } + // }); + // }) + // .catch(({data, status}) => { + // ProcessErrors($rootScope, data, status, null, {hdr: 'Error!', + // msg: 'Call failed. Return status: '+ status}); + // }); + // + // }; } ]; diff --git a/awx/ui/client/src/portal-mode/portal-mode-job-templates.controller.js b/awx/ui/client/src/portal-mode/portal-mode-job-templates.controller.js index 5453d9a248..65032896cd 100644 --- a/awx/ui/client/src/portal-mode/portal-mode-job-templates.controller.js +++ b/awx/ui/client/src/portal-mode/portal-mode-job-templates.controller.js @@ -4,23 +4,79 @@ * All Rights Reserved *************************************************/ -export function PortalModeJobTemplatesController($scope, PortalJobTemplateList, InitiatePlaybookRun, Dataset) { +export function PortalModeJobTemplatesController($scope, PortalJobTemplateList, Dataset, $state, PromptService, JobTemplate, ProcessErrors) { var list = PortalJobTemplateList; + // search init + $scope.list = list; + $scope[`${list.iterator}_dataset`] = Dataset.data; + $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - init(); - - function init() { - // search init - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - } + const jobTemplate = new JobTemplate(); $scope.submitJob = function(id) { - InitiatePlaybookRun({ scope: $scope, id: id, job_type: 'job_template' }); + const selectedJobTemplate = jobTemplate.create(); + const preLaunchPromises = [ + selectedJobTemplate.getLaunch(id), + selectedJobTemplate.optionsLaunch(id), + ]; + + Promise.all(preLaunchPromises) + .then(([launchData, launchOptions]) => { + if (selectedJobTemplate.canLaunchWithoutPrompt()) { + return selectedJobTemplate + .postLaunch({ id: id }) + .then(({ data }) => { + $state.go('jobResult', { id: data.job }, { reload: true }); + }); + } + + const promptData = { + launchConf: launchData.data, + launchOptions: launchOptions.data, + template: id, + templateType: 'job_template', + prompts: PromptService.processPromptValues({ + launchConf: launchData.data, + launchOptions: launchOptions.data + }), + triggerModalOpen: true, + }; + + if (launchData.data.survey_enabled) { + selectedJobTemplate.getSurveyQuestions(id) + .then(({ data }) => { + const processed = PromptService.processSurveyQuestions({ surveyQuestions: data.spec }); + promptData.surveyQuestions = processed.surveyQuestions; + $scope.promptData = promptData; + }); + } else { + $scope.promptData = promptData; + } + }); + }; + + $scope.launchJob = () => { + const jobLaunchData = PromptService.bundlePromptDataForLaunch($scope.promptData); + + // If the extra_vars dict is empty, we don't want to include it if we didn't prompt for anything. + if(_.isEmpty(jobLaunchData.extra_vars) && !($scope.promptData.launchConf.ask_variables_on_launch && $scope.promptData.launchConf.survey_enabled && $scope.promptData.surveyQuestions.length > 0)){ + delete jobLaunchData.extra_vars; + } + + jobTemplate.create().postLaunch({ + id: $scope.promptData.template, + launchData: jobLaunchData + }) + .then((launchRes) => { + $state.go('jobResult', { id: launchRes.data.job }, { reload: true }); + }) + .catch(({data, status}) => { + ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to launch job template: ' + status }); + }); + }; } -PortalModeJobTemplatesController.$inject = ['$scope','PortalJobTemplateList', 'InitiatePlaybookRun', 'job_templatesDataset']; +PortalModeJobTemplatesController.$inject = ['$scope','PortalJobTemplateList', 'job_templatesDataset', '$state', 'PromptService', 'JobTemplateModel', 'ProcessErrors']; diff --git a/awx/ui/client/src/portal-mode/portal-mode.route.js b/awx/ui/client/src/portal-mode/portal-mode.route.js index 024f6a7609..cb347821b3 100644 --- a/awx/ui/client/src/portal-mode/portal-mode.route.js +++ b/awx/ui/client/src/portal-mode/portal-mode.route.js @@ -50,7 +50,7 @@ export default { list: PortalJobTemplateList, mode: 'edit' }); - return html; + return html + ''; }, controller: PortalModeJobTemplatesController } diff --git a/awx/ui/client/src/scheduler/factories/schedule-post.factory.js b/awx/ui/client/src/scheduler/factories/schedule-post.factory.js index 0244f68068..bea921a8be 100644 --- a/awx/ui/client/src/scheduler/factories/schedule-post.factory.js +++ b/awx/ui/client/src/scheduler/factories/schedule-post.factory.js @@ -1,5 +1,5 @@ export default - function SchedulePost(Rest, ProcessErrors, RRuleToAPI, Wait, $q, Schedule) { + function SchedulePost(Rest, ProcessErrors, RRuleToAPI, Wait, $q, Schedule, PromptService) { return function(params) { var scope = params.scope, url = params.url, @@ -36,57 +36,10 @@ export default } if(promptData) { - if(promptData.launchConf.survey_enabled){ - for (var i=0; i < promptData.surveyQuestions.length; i++){ - var fld = promptData.surveyQuestions[i].variable; - // grab all survey questions that have answers - if(promptData.surveyQuestions[i].required || (promptData.surveyQuestions[i].required === false && promptData.surveyQuestions[i].model.toString()!=="")) { - if(!scheduleData.extra_data) { - scheduleData.extra_data = {}; - } - scheduleData.extra_data[fld] = promptData.surveyQuestions[i].model; - } - - if(promptData.surveyQuestions[i].required === false && _.isEmpty(promptData.surveyQuestions[i].model)) { - switch (promptData.surveyQuestions[i].type) { - // for optional text and text-areas, submit a blank string if min length is 0 - // -- this is confusing, for an explanation see: - // http://docs.ansible.com/ansible-tower/latest/html/userguide/job_templates.html#optional-survey-questions - // - case "text": - case "textarea": - if (promptData.surveyQuestions[i].min === 0) { - scheduleData.extra_data[fld] = ""; - } - break; - } - } - } - } - - if(_.has(promptData, 'prompts.jobType.value.value') && _.get(promptData, 'launchConf.ask_job_type_on_launch')) { - scheduleData.job_type = promptData.launchConf.defaults.job_type && promptData.launchConf.defaults.job_type === promptData.prompts.jobType.value.value ? null : promptData.prompts.jobType.value.value; - } - if(_.has(promptData, 'prompts.tags.value') && _.get(promptData, 'launchConf.ask_tags_on_launch')){ - const templateDefaultJobTags = promptData.launchConf.defaults.job_tags.split(','); - scheduleData.job_tags = (_.isEqual(templateDefaultJobTags.sort(), promptData.prompts.tags.value.map(a => a.value).sort())) ? null : promptData.prompts.tags.value.map(a => a.value).join(); - } - if(_.has(promptData, 'prompts.skipTags.value') && _.get(promptData, 'launchConf.ask_skip_tags_on_launch')){ - const templateDefaultSkipTags = promptData.launchConf.defaults.skip_tags.split(','); - scheduleData.skip_tags = (_.isEqual(templateDefaultSkipTags.sort(), promptData.prompts.skipTags.value.map(a => a.value).sort())) ? null : promptData.prompts.skipTags.value.map(a => a.value).join(); - } - if(_.has(promptData, 'prompts.limit.value') && _.get(promptData, 'launchConf.ask_limit_on_launch')){ - scheduleData.limit = promptData.launchConf.defaults.limit && promptData.launchConf.defaults.limit === promptData.prompts.limit.value ? null : promptData.prompts.limit.value; - } - if(_.has(promptData, 'prompts.verbosity.value.value') && _.get(promptData, 'launchConf.ask_verbosity_on_launch')){ - scheduleData.verbosity = promptData.launchConf.defaults.verbosity && promptData.launchConf.defaults.verbosity === promptData.prompts.verbosity.value.value ? null : promptData.prompts.verbosity.value.value; - } - if(_.has(promptData, 'prompts.inventory.value') && _.get(promptData, 'launchConf.ask_inventory_on_launch')){ - scheduleData.inventory = promptData.launchConf.defaults.inventory && promptData.launchConf.defaults.inventory.id === promptData.prompts.inventory.value.id ? null : promptData.prompts.inventory.value.id; - } - if(_.has(promptData, 'prompts.diffMode.value') && _.get(promptData, 'launchConf.ask_diff_mode_on_launch')){ - scheduleData.diff_mode = promptData.launchConf.defaults.diff_mode && promptData.launchConf.defaults.diff_mode === promptData.prompts.diffMode.value ? null : promptData.prompts.diffMode.value; - } + scheduleData = PromptService.bundlePromptDataForSaving({ + promptData: promptData, + dataToSave: scheduleData + }); } Rest.setUrl(url); @@ -212,5 +165,6 @@ SchedulePost.$inject = 'RRuleToAPI', 'Wait', '$q', - 'ScheduleModel' + 'ScheduleModel', + 'PromptService' ]; diff --git a/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.partial.html b/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.partial.html index 2591deced8..7800872be2 100644 --- a/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.partial.html +++ b/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.partial.html @@ -8,7 +8,9 @@ RESULTS
- +
+ +
diff --git a/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.route.js b/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.route.js index e294116d78..899a98e9f7 100644 --- a/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.route.js +++ b/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.route.js @@ -23,5 +23,14 @@ export default { "ad_hoc_command_events": [] } } + }, + resolve: { + jobData: ['Rest', 'GetBasePath', '$stateParams', function(Rest, GetBasePath, $stateParams) { + Rest.setUrl(GetBasePath('base') + 'ad_hoc_commands/' + $stateParams.id + '/'); + return Rest.get() + .then(({data}) => { + return data; + }); + }] } }; diff --git a/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.partial.html b/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.partial.html index 4cd0ec3d82..2b2ebbadf0 100644 --- a/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.partial.html +++ b/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.partial.html @@ -8,7 +8,7 @@ RESULTS
- +
diff --git a/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.route.js b/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.route.js index b3931d345c..bdd1a9a2b1 100644 --- a/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.route.js +++ b/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.route.js @@ -25,5 +25,14 @@ export default { } }, jobType: 'inventory_updates' + }, + resolve: { + jobData: ['Rest', 'GetBasePath', '$stateParams', function(Rest, GetBasePath, $stateParams) { + Rest.setUrl(GetBasePath('base') + 'inventory_updates/' + $stateParams.id + '/'); + return Rest.get() + .then(({data}) => { + return data; + }); + }] } }; diff --git a/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.route.js b/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.route.js index 950801990c..e3a59e884d 100644 --- a/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.route.js +++ b/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.route.js @@ -23,5 +23,14 @@ export default { "system_job_events": [], } } + }, + resolve: { + jobData: ['Rest', 'GetBasePath', '$stateParams', function(Rest, GetBasePath, $stateParams) { + Rest.setUrl(GetBasePath('base') + 'system_jobs/' + $stateParams.id + '/'); + return Rest.get() + .then(({data}) => { + return data; + }); + }] } }; diff --git a/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.partial.html b/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.partial.html index 008516a2ad..2921df86b4 100644 --- a/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.partial.html +++ b/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.partial.html @@ -8,7 +8,7 @@ RESULTS
- +
diff --git a/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.route.js b/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.route.js index c2e43be9ca..818509ccc7 100644 --- a/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.route.js +++ b/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.route.js @@ -25,5 +25,14 @@ export default { "project_update_events": [], } }, + }, + resolve: { + jobData: ['Rest', 'GetBasePath', '$stateParams', function(Rest, GetBasePath, $stateParams) { + Rest.setUrl(GetBasePath('base') + 'project_updates/' + $stateParams.id + '/'); + return Rest.get() + .then(({data}) => { + return data; + }); + }] } }; diff --git a/awx/ui/client/src/standard-out/standard-out.block.less b/awx/ui/client/src/standard-out/standard-out.block.less index bf0ca8727b..fe7e0d2757 100644 --- a/awx/ui/client/src/standard-out/standard-out.block.less +++ b/awx/ui/client/src/standard-out/standard-out.block.less @@ -131,6 +131,10 @@ standard-out-log { font-size: 20px; } +.StandardOut-actions { + display: flex; +} + .StandardOut-actionButton { font-size: 16px; height: 30px; diff --git a/awx/ui/client/src/standard-out/standard-out.controller.js b/awx/ui/client/src/standard-out/standard-out.controller.js index 220eead31c..1a707aa706 100644 --- a/awx/ui/client/src/standard-out/standard-out.controller.js +++ b/awx/ui/client/src/standard-out/standard-out.controller.js @@ -12,8 +12,9 @@ export function JobStdoutController ($rootScope, $scope, $state, $stateParams, GetBasePath, Rest, ProcessErrors, Empty, GetChoices, LookUpName, - ParseTypeChange, ParseVariableString, RelaunchJob, DeleteJob, Wait, i18n, - fieldChoices, fieldLabels) { + ParseTypeChange, ParseVariableString, DeleteJob, Wait, i18n, + fieldChoices, fieldLabels, Project, Alert, InventorySource, + jobData) { var job_id = $stateParams.id, jobType = $state.current.data.jobType; @@ -34,7 +35,12 @@ export function JobStdoutController ($rootScope, $scope, $state, $stateParams, if (data.status === 'failed' || data.status === 'canceled' || data.status === 'error' || data.status === 'successful') { // Go out and refresh the job details - getjobResults(); + + Rest.setUrl(GetBasePath('base') + jobType + '/' + job_id + '/'); + Rest.get() + .then(({data}) => { + updateJobObj(data); + }); } }); @@ -72,149 +78,142 @@ export function JobStdoutController ($rootScope, $scope, $state, $stateParams, // Set the parse type so that CodeMirror knows how to display extra params YAML/JSON $scope.parseType = 'yaml'; - function getjobResults() { + function updateJobObj(updatedJobData) { // Go out and get the job details based on the job type. jobType gets defined // in the data block of the route declaration for each of the different types // of stdout jobs. - Rest.setUrl(GetBasePath('base') + jobType + '/' + job_id + '/'); - Rest.get() - .then(({data}) => { - $scope.job = data; - $scope.job_template_name = data.name; - $scope.created_by = data.summary_fields.created_by; - $scope.project_name = (data.summary_fields.project) ? data.summary_fields.project.name : ''; - $scope.inventory_name = (data.summary_fields.inventory) ? data.summary_fields.inventory.name : ''; - $scope.job_template_url = '/#/templates/' + data.unified_job_template; - if($scope.inventory_name && data.inventory && data.summary_fields.inventory && data.summary_fields.inventory.kind) { - if(data.summary_fields.inventory.kind === '') { - $scope.inventory_url = '/#/inventories/inventory' + data.inventory; - } - else if(data.summary_fields.inventory.kind === 'smart') { - $scope.inventory_url = '/#/inventories/smart_inventory' + data.inventory; - } - } - else { - $scope.inventory_url = ''; - } - $scope.project_url = ($scope.project_name && data.project) ? '/#/projects/' + data.project : ''; - $scope.credential_name = (data.summary_fields.credential) ? data.summary_fields.credential.name : ''; - $scope.credential_url = (data.credential) ? '/#/credentials/' + data.credential : ''; - $scope.cloud_credential_url = (data.cloud_credential) ? '/#/credentials/' + data.cloud_credential : ''; - if(data.summary_fields && data.summary_fields.source_workflow_job && - data.summary_fields.source_workflow_job.id){ - $scope.workflow_result_link = `/#/workflows/${data.summary_fields.source_workflow_job.id}`; - } - $scope.playbook = data.playbook; - $scope.credential = data.credential; - $scope.cloud_credential = data.cloud_credential; - $scope.forks = data.forks; - $scope.limit = data.limit; - $scope.verbosity = data.verbosity; - $scope.job_tags = data.job_tags; - $scope.job.module_name = data.module_name; - if (data.extra_vars) { - $scope.variables = ParseVariableString(data.extra_vars); - } - $scope.$on('getInventorySource', function(e, d) { - $scope.inv_manage_group_link = '/#/inventories/inventory/' + d.inventory + '/inventory_sources/edit/' + d.id; + $scope.job = updatedJobData; + $scope.job_template_name = updatedJobData.name; + $scope.created_by = updatedJobData.summary_fields.created_by; + $scope.project_name = (updatedJobData.summary_fields.project) ? updatedJobData.summary_fields.project.name : ''; + $scope.inventory_name = (updatedJobData.summary_fields.inventory) ? updatedJobData.summary_fields.inventory.name : ''; + $scope.job_template_url = '/#/templates/' + updatedJobData.unified_job_template; + if($scope.inventory_name && updatedJobData.inventory && updatedJobData.summary_fields.inventory && updatedJobData.summary_fields.inventory.kind) { + if(updatedJobData.summary_fields.inventory.kind === '') { + $scope.inventory_url = '/#/inventories/inventory' + updatedJobData.inventory; + } + else if(updatedJobData.summary_fields.inventory.kind === 'smart') { + $scope.inventory_url = '/#/inventories/smart_inventory' + updatedJobData.inventory; + } + } + else { + $scope.inventory_url = ''; + } + $scope.project_url = ($scope.project_name && updatedJobData.project) ? '/#/projects/' + updatedJobData.project : ''; + $scope.credential_name = (updatedJobData.summary_fields.credential) ? updatedJobData.summary_fields.credential.name : ''; + $scope.credential_url = (updatedJobData.credential) ? '/#/credentials/' + updatedJobData.credential : ''; + $scope.cloud_credential_url = (updatedJobData.cloud_credential) ? '/#/credentials/' + updatedJobData.cloud_credential : ''; + if(updatedJobData.summary_fields && updatedJobData.summary_fields.source_workflow_job && + updatedJobData.summary_fields.source_workflow_job.id){ + $scope.workflow_result_link = `/#/workflows/${updatedJobData.summary_fields.source_workflow_job.id}`; + } + $scope.playbook = updatedJobData.playbook; + $scope.credential = updatedJobData.credential; + $scope.cloud_credential = updatedJobData.cloud_credential; + $scope.forks = updatedJobData.forks; + $scope.limit = updatedJobData.limit; + $scope.verbosity = updatedJobData.verbosity; + $scope.job_tags = updatedJobData.job_tags; + $scope.job.module_name = updatedJobData.module_name; + if (updatedJobData.extra_vars) { + $scope.variables = ParseVariableString(updatedJobData.extra_vars); + } + + $scope.$on('getInventorySource', function(e, d) { + $scope.inv_manage_group_link = '/#/inventories/inventory/' + d.inventory + '/inventory_sources/edit/' + d.id; + }); + + // If we have a source then we have to go get the source choices from the server + if (!Empty(updatedJobData.source)) { + if ($scope.removeChoicesReady) { + $scope.removeChoicesReady(); + } + $scope.removeChoicesReady = $scope.$on('ChoicesReady', function() { + $scope.source_choices.every(function(e) { + if (e.value === updatedJobData.source) { + $scope.source = e.label; + return false; + } + return true; }); - - // If we have a source then we have to go get the source choices from the server - if (!Empty(data.source)) { - if ($scope.removeChoicesReady) { - $scope.removeChoicesReady(); - } - $scope.removeChoicesReady = $scope.$on('ChoicesReady', function() { - $scope.source_choices.every(function(e) { - if (e.value === data.source) { - $scope.source = e.label; - return false; - } - return true; - }); - }); - // GetChoices can be found in the helper: Utilities.js - // It attaches the source choices to $scope.source_choices. - // Then, when the callback is fired, $scope.source is bound - // to the corresponding label. - GetChoices({ - scope: $scope, - url: GetBasePath('inventory_sources'), - field: 'source', - variable: 'source_choices', - choice_name: 'choices', - callback: 'ChoicesReady' - }); - } - - // LookUpName can be found in the lookup-name.factory - // It attaches the name that it gets (based on the url) - // to the $scope variable defined by the attribute scope_var. - if (!Empty(data.credential)) { - LookUpName({ - scope: $scope, - scope_var: 'credential', - url: GetBasePath('credentials') + data.credential + '/', - ignore_403: true - }); - } - - if (!Empty(data.inventory)) { - LookUpName({ - scope: $scope, - scope_var: 'inventory', - url: GetBasePath('inventory') + data.inventory + '/' - }); - } - - if (!Empty(data.project)) { - LookUpName({ - scope: $scope, - scope_var: 'project', - url: GetBasePath('projects') + data.project + '/' - }); - } - - if (!Empty(data.cloud_credential)) { - LookUpName({ - scope: $scope, - scope_var: 'cloud_credential', - url: GetBasePath('credentials') + data.cloud_credential + '/', - ignore_403: true - }); - } - - if (!Empty(data.inventory_source)) { - LookUpName({ - scope: $scope, - scope_var: 'inventory_source', - url: GetBasePath('inventory_sources') + data.inventory_source + '/', - callback: 'getInventorySource' - }); - } - - if (data.extra_vars) { - ParseTypeChange({ - scope: $scope, - field_id: 'pre-formatted-variables', - readOnly: true - }); - } - - // If the job isn't running we want to clear out the interval that goes out and checks for stdout updates. - // This interval is defined in the standard out log directive controller. - if (data.status === 'successful' || data.status === 'failed' || data.status === 'error' || data.status === 'canceled') { - if ($rootScope.jobStdOutInterval) { - window.clearInterval($rootScope.jobStdOutInterval); - } - } - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to retrieve job: ' + job_id + '. GET returned: ' + status }); }); + // GetChoices can be found in the helper: Utilities.js + // It attaches the source choices to $scope.source_choices. + // Then, when the callback is fired, $scope.source is bound + // to the corresponding label. + GetChoices({ + scope: $scope, + url: GetBasePath('inventory_sources'), + field: 'source', + variable: 'source_choices', + choice_name: 'choices', + callback: 'ChoicesReady' + }); + } + + // LookUpName can be found in the lookup-name.factory + // It attaches the name that it gets (based on the url) + // to the $scope variable defined by the attribute scope_var. + if (!Empty(updatedJobData.credential)) { + LookUpName({ + scope: $scope, + scope_var: 'credential', + url: GetBasePath('credentials') + updatedJobData.credential + '/', + ignore_403: true + }); + } + + if (!Empty(updatedJobData.inventory)) { + LookUpName({ + scope: $scope, + scope_var: 'inventory', + url: GetBasePath('inventory') + updatedJobData.inventory + '/' + }); + } + + if (!Empty(updatedJobData.project)) { + LookUpName({ + scope: $scope, + scope_var: 'project', + url: GetBasePath('projects') + updatedJobData.project + '/' + }); + } + + if (!Empty(updatedJobData.cloud_credential)) { + LookUpName({ + scope: $scope, + scope_var: 'cloud_credential', + url: GetBasePath('credentials') + updatedJobData.cloud_credential + '/', + ignore_403: true + }); + } + + if (!Empty(updatedJobData.inventory_source)) { + LookUpName({ + scope: $scope, + scope_var: 'inventory_source', + url: GetBasePath('inventory_sources') + updatedJobData.inventory_source + '/', + callback: 'getInventorySource' + }); + } + + if (updatedJobData.extra_vars) { + ParseTypeChange({ + scope: $scope, + field_id: 'pre-formatted-variables', + readOnly: true + }); + } + + // If the job isn't running we want to clear out the interval that goes out and checks for stdout updates. + // This interval is defined in the standard out log directive controller. + if (updatedJobData.status === 'successful' || updatedJobData.status === 'failed' || updatedJobData.status === 'error' || updatedJobData.status === 'canceled') { + if ($rootScope.jobStdOutInterval) { + window.clearInterval($rootScope.jobStdOutInterval); + } + } } @@ -255,26 +254,13 @@ export function JobStdoutController ($rootScope, $scope, $state, $stateParams, }); }; - $scope.relaunchJob = function() { - var typeId, job = $scope.job; - if (job.type === 'inventory_update') { - typeId = job.inventory_source; - } - else if (job.type === 'project_update') { - typeId = job.project; - } - else if (job.type === 'job' || job.type === "system_job" || job.type === 'ad_hoc_command') { - typeId = job.id; - } - RelaunchJob({ scope: $scope, id: typeId, type: job.type, name: job.name }); - }; - - getjobResults(); + updateJobObj(jobData); } JobStdoutController.$inject = [ '$rootScope', '$scope', '$state', '$stateParams', 'GetBasePath', 'Rest', 'ProcessErrors', 'Empty', 'GetChoices', 'LookUpName', 'ParseTypeChange', - 'ParseVariableString', 'RelaunchJob', 'DeleteJob', 'Wait', 'i18n', - 'fieldChoices', 'fieldLabels']; + 'ParseVariableString', 'DeleteJob', 'Wait', 'i18n', + 'fieldChoices', 'fieldLabels', 'ProjectModel', 'Alert', 'InventorySourceModel', + 'jobData']; diff --git a/awx/ui/client/src/templates/completed-jobs.list.js b/awx/ui/client/src/templates/completed-jobs.list.js index 8be715f2dd..54ba53a48e 100644 --- a/awx/ui/client/src/templates/completed-jobs.list.js +++ b/awx/ui/client/src/templates/completed-jobs.list.js @@ -72,12 +72,9 @@ export default ['i18n', function(i18n) { columnClass: 'col-lg-2 col-md-2 col-sm-3 col-xs-4', submit: { - icon: 'icon-rocket', - mode: 'all', - ngClick: 'relaunchJob($event, completed_job.id)', - awToolTip: i18n._('Relaunch using the same parameters'), - dataPlacement: 'top', - ngShow: "!completed_job.type == 'system_job' || completed_job.summary_fields.user_capabilities.start" + ngShow: "!completed_job.type == 'system_job' || completed_job.summary_fields.user_capabilities.start", + // uses the at-relaunch directive + relaunch: true }, "delete": { mode: 'all', diff --git a/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js b/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js index 38b32b2942..5b05fd426b 100644 --- a/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js +++ b/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js @@ -16,7 +16,7 @@ export default 'Rest', 'Alert', 'ProcessErrors', 'GetBasePath', 'md5Setup', 'ParseTypeChange', 'Wait', 'selectedLabels', 'i18n', 'Empty', 'Prompt', 'ToJSON', 'GetChoices', 'CallbackHelpInit', - 'InitiatePlaybookRun' , 'initSurvey', '$state', 'CreateSelect2', + 'initSurvey', '$state', 'CreateSelect2', 'ToggleNotification','$q', 'InstanceGroupsService', 'InstanceGroupsData', 'MultiCredentialService', 'availableLabels', 'projectGetPermissionDenied', 'inventoryGetPermissionDenied', 'jobTemplateData', 'ParseVariableString', 'ConfigData', @@ -26,7 +26,7 @@ export default ProcessErrors, GetBasePath, md5Setup, ParseTypeChange, Wait, selectedLabels, i18n, Empty, Prompt, ToJSON, GetChoices, CallbackHelpInit, - InitiatePlaybookRun, SurveyControllerInit, $state, CreateSelect2, + SurveyControllerInit, $state, CreateSelect2, ToggleNotification, $q, InstanceGroupsService, InstanceGroupsData, MultiCredentialService, availableLabels, projectGetPermissionDenied, inventoryGetPermissionDenied, jobTemplateData, ParseVariableString, ConfigData @@ -718,50 +718,5 @@ export default $scope.formCancel = function () { $state.go('templates'); }; - - // Related set: Add button - $scope.add = function (set) { - $rootScope.flashMessage = null; - $location.path('/' + base + '/' + $stateParams.job_template_id + '/' + set); - }; - - // Related set: Edit button - $scope.edit = function (set, id) { - $rootScope.flashMessage = null; - $location.path('/' + set + '/' + id); - }; - - // Launch a job using the selected template - $scope.launch = function() { - - if ($scope.removePromptForSurvey) { - $scope.removePromptForSurvey(); - } - $scope.removePromptForSurvey = $scope.$on('PromptForSurvey', function() { - var action = function () { - // $scope.$emit("GatherFormFields"); - Wait('start'); - $('#prompt-modal').modal('hide'); - $scope.addSurvey(); - - }; - Prompt({ - hdr: 'Incomplete Survey', - body: '
Do you want to create a survey before proceeding?
', - action: action - }); - }); - if($scope.survey_enabled === true && $scope.survey_exists!==true){ - $scope.$emit("PromptForSurvey"); - } - else { - - InitiatePlaybookRun({ - scope: $scope, - id: id, - job_type: 'job_template' - }); - } - }; } ]; diff --git a/awx/ui/client/src/templates/prompt/prompt.controller.js b/awx/ui/client/src/templates/prompt/prompt.controller.js index f2f08f9207..79df58c51c 100644 --- a/awx/ui/client/src/templates/prompt/prompt.controller.js +++ b/awx/ui/client/src/templates/prompt/prompt.controller.js @@ -65,37 +65,30 @@ export default [ 'Rest', 'GetBasePath', 'ProcessErrors', 'CredentialTypeModel', } })); - vm.promptData.prompts.credentials.passwordsNeededToStart = vm.promptData.launchConf.passwords_needed_to_start; vm.promptData.prompts.credentials.passwords = {}; - vm.promptData.prompts.credentials.value.forEach((credential) => { - if (credential.passwords_needed && credential.passwords_needed.length > 0) { - credential.passwords_needed.forEach(passwordNeeded => { - let credPassObj = { - id: credential.id, - name: credential.name - }; + vm.promptData.launchConf.passwords_needed_to_start.forEach((passwordNeeded) => { + if(passwordNeeded === "ssh_password") { + vm.promptData.prompts.credentials.passwords.ssh = {}; + } + if(passwordNeeded === "become_password") { + vm.promptData.prompts.credentials.passwords.become = {}; + } + if(passwordNeeded === "ssh_key_unlock") { + vm.promptData.prompts.credentials.passwords.ssh_key_unlock = {}; + } + if(passwordNeeded.startsWith("vault_password")) { + let vault_id; + if(passwordNeeded.includes('.')) { + vault_id = passwordNeeded.split(/\.(.+)/)[1]; + } - if(passwordNeeded === "ssh_password") { - vm.promptData.prompts.credentials.passwords.ssh = credPassObj; - } - if(passwordNeeded === "become_password") { - vm.promptData.prompts.credentials.passwords.become = credPassObj; - } - if(passwordNeeded === "ssh_key_unlock") { - vm.promptData.prompts.credentials.passwords.ssh_key_unlock = credPassObj; - } - if(passwordNeeded.startsWith("vault_password")) { - if(passwordNeeded.includes('.')) { - credPassObj.vault_id = passwordNeeded.split(/\.(.+)/)[1]; - } + if(!vm.promptData.prompts.credentials.passwords.vault) { + vm.promptData.prompts.credentials.passwords.vault = []; + } - if(!vm.promptData.prompts.credentials.passwords.vault) { - vm.promptData.prompts.credentials.passwords.vault = []; - } - - vm.promptData.prompts.credentials.passwords.vault.push(credPassObj); - } + vm.promptData.prompts.credentials.passwords.vault.push({ + vault_id: vault_id }); } }); diff --git a/awx/ui/client/src/templates/prompt/prompt.service.js b/awx/ui/client/src/templates/prompt/prompt.service.js index 5ab5002c27..2044b7f440 100644 --- a/awx/ui/client/src/templates/prompt/prompt.service.js +++ b/awx/ui/client/src/templates/prompt/prompt.service.js @@ -1,7 +1,7 @@ function PromptService (Empty, $filter) { this.processPromptValues = (params) => { - let prompts = { + const prompts = { credentials: {}, inventory: {}, variables: {}, @@ -16,10 +16,23 @@ function PromptService (Empty, $filter) { prompts.credentials.value = _.has(params, 'launchConf.defaults.credentials') ? _.cloneDeep(params.launchConf.defaults.credentials) : []; prompts.inventory.value = _.has(params, 'currentValues.summary_fields.inventory') ? params.currentValues.summary_fields.inventory : (_.has(params, 'launchConf.defaults.inventory') ? params.launchConf.defaults.inventory : null); - let skipTags = _.has(params, 'currentValues.skip_tags') && params.currentValues.skip_tags ? params.currentValues.skip_tags : (_.has(params, 'launchConf.defaults.skip_tags') ? params.launchConf.defaults.skip_tags : ""); - let jobTags = _.has(params, 'currentValues.job_tags') && params.currentValues.job_tags ? params.currentValues.job_tags : (_.has(params, 'launchConf.defaults.job_tags') ? params.launchConf.defaults.job_tags : ""); + const skipTags = _.has(params, 'currentValues.skip_tags') && params.currentValues.skip_tags ? params.currentValues.skip_tags : (_.has(params, 'launchConf.defaults.skip_tags') ? params.launchConf.defaults.skip_tags : ""); + const jobTags = _.has(params, 'currentValues.job_tags') && params.currentValues.job_tags ? params.currentValues.job_tags : (_.has(params, 'launchConf.defaults.job_tags') ? params.launchConf.defaults.job_tags : ""); - prompts.variables.value = _.has(params, 'launchConf.defaults.extra_vars') && params.launchConf.defaults.extra_vars !== "" ? params.launchConf.defaults.extra_vars : "---"; + let extraVars = ''; + + const hasCurrentExtraVars = _.get(params, 'currentValues.extra_data'), + hasDefaultExtraVars = _.get(params, 'launchConf.defaults.extra_vars'); + + if(hasCurrentExtraVars && hasDefaultExtraVars) { + extraVars = _.merge(jsyaml.safeLoad(params.launchConf.defaults.extra_vars), params.currentValues.extra_data); + } else if(hasCurrentExtraVars) { + extraVars = params.currentValues.extra_data; + } else if(hasDefaultExtraVars) { + extraVars = jsyaml.safeLoad(params.launchConf.defaults.extra_vars); + } + + prompts.variables.value = extraVars && extraVars !== '' ? '---\n' + jsyaml.safeDump(extraVars) : '---\n'; prompts.verbosity.choices = _.get(params, 'launchOptions.actions.POST.verbosity.choices', []).map(c => ({label: c[1], value: c[0]})); prompts.verbosity.value = _.has(params, 'currentValues.verbosity') && params.currentValues.verbosity ? _.find(prompts.verbosity.choices, item => item.value === params.currentValues.verbosity) : _.find(prompts.verbosity.choices, item => item.value === params.launchConf.defaults.verbosity); prompts.jobType.choices = _.get(params, 'launchOptions.actions.POST.job_type.choices', []).map(c => ({label: c[1], value: c[0]})); @@ -27,7 +40,7 @@ function PromptService (Empty, $filter) { prompts.limit.value = _.has(params, 'currentValues.limit') && params.currentValues.limit ? params.currentValues.limit : (_.has(params, 'launchConf.defaults.limit') ? params.launchConf.defaults.limit : ""); prompts.tags.options = prompts.tags.value = (jobTags && jobTags !== "") ? jobTags.split(',').map((i) => ({name: i, label: i, value: i})) : []; prompts.skipTags.options = prompts.skipTags.value = (skipTags && skipTags !== "") ? skipTags.split(',').map((i) => ({name: i, label: i, value: i})) : []; - prompts.diffMode.value = _.has(params, 'currentValues.diff_mode') && typeof params.currentValues.diff_mode === 'boolean' ? params.currentValues.diff_mode : (_.has(params, 'launchConf.defaults.diff_mode') ? params.launchConf.defaults.diff_mode : false); + prompts.diffMode.value = _.has(params, 'currentValues.diff_mode') && typeof params.currentValues.diff_mode === 'boolean' ? params.currentValues.diff_mode : (_.has(params, 'launchConf.defaults.diff_mode') ? params.launchConf.defaults.diff_mode : null); return prompts; }; @@ -117,6 +130,144 @@ function PromptService (Empty, $filter) { missingSurveyValue: missingSurveyValue }; }; + + this.bundlePromptDataForLaunch = (promptData) => { + const launchData = { + extra_vars: promptData.extraVars + }; + + if (promptData.launchConf.ask_tags_on_launch){ + launchData.job_tags = promptData.prompts.tags.value.map(a => a.value).join(); + } + if (promptData.launchConf.ask_skip_tags_on_launch){ + launchData.skip_tags = promptData.prompts.skipTags.value.map(a => a.value).join(); + } + if (promptData.launchConf.ask_limit_on_launch && _.has(promptData, 'prompts.limit.value')){ + launchData.limit = promptData.prompts.limit.value; + } + if (promptData.launchConf.ask_job_type_on_launch && _.has(promptData, 'prompts.jobType.value.value')) { + launchData.job_type = promptData.prompts.jobType.value.value; + } + if (promptData.launchConf.ask_verbosity_on_launch && _.has(promptData, 'prompts.verbosity.value.value')) { + launchData.verbosity = promptData.prompts.verbosity.value.value; + } + if (promptData.launchConf.ask_inventory_on_launch && !Empty(promptData.prompts.inventory.value.id)){ + launchData.inventory_id = promptData.prompts.inventory.value.id; + } + if (promptData.launchConf.ask_credential_on_launch){ + launchData.credentials = []; + promptData.prompts.credentials.value.forEach((credential) => { + launchData.credentials.push(credential.id); + }); + } + if (promptData.launchConf.ask_diff_mode_on_launch && _.has(promptData, 'prompts.diffMode.value')) { + launchData.diff_mode = promptData.prompts.diffMode.value; + } + if (promptData.prompts.credentials.passwords) { + _.forOwn(promptData.prompts.credentials.passwords, (val, key) => { + if (!launchData.credential_passwords) { + launchData.credential_passwords = {}; + } + if (key === "ssh_key_unlock") { + launchData.credential_passwords.ssh_key_unlock = val.value; + } else if (key !== "vault") { + launchData.credential_passwords[`${key}_password`] = val.value; + } else { + _.each(val, (vaultCred) => { + launchData.credential_passwords[vaultCred.vault_id ? `${key}_password.${vaultCred.vault_id}` : `${key}_password`] = vaultCred.value; + }); + } + }); + } + + return launchData; + }; + + this.bundlePromptDataForRelaunch = (promptData) => { + const launchData = {}; + + if(promptData.relaunchHostType) { + launchData.hosts = promptData.relaunchHostType; + } + + if (promptData.prompts.credentials.passwords) { + _.forOwn(promptData.prompts.credentials.passwords, (val, key) => { + if (!launchData.credential_passwords) { + launchData.credential_passwords = {}; + } + if (key === "ssh_key_unlock") { + launchData.credential_passwords.ssh_key_unlock = val.value; + } else if (key !== "vault") { + launchData.credential_passwords[`${key}_password`] = val.value; + } else { + _.each(val, (vaultCred) => { + launchData.credential_passwords[vaultCred.vault_id ? `${key}_password.${vaultCred.vault_id}` : `${key}_password`] = vaultCred.value; + }); + } + }); + } + + return launchData; + }; + + this.bundlePromptDataForSaving = (params) => { + + const promptDataToSave = params.dataToSave ? params.dataToSave : {}; + + if(params.promptData.launchConf.survey_enabled){ + for (var i=0; i < params.promptData.surveyQuestions.length; i++){ + var fld = params.promptData.surveyQuestions[i].variable; + // grab all survey questions that have answers + if(params.promptData.surveyQuestions[i].required || (params.promptData.surveyQuestions[i].required === false && params.promptData.surveyQuestions[i].model.toString()!=="")) { + if(!promptDataToSave.extra_data) { + promptDataToSave.extra_data = {}; + } + promptDataToSave.extra_data[fld] = params.promptData.surveyQuestions[i].model; + } + + if(params.promptData.surveyQuestions[i].required === false && _.isEmpty(params.promptData.surveyQuestions[i].model)) { + switch (params.promptData.surveyQuestions[i].type) { + // for optional text and text-areas, submit a blank string if min length is 0 + // -- this is confusing, for an explanation see: + // http://docs.ansible.com/ansible-tower/latest/html/userguide/job_templates.html#optional-survey-questions + // + case "text": + case "textarea": + if (params.promptData.surveyQuestions[i].min === 0) { + promptDataToSave.extra_data[fld] = ""; + } + break; + } + } + } + } + + if(_.has(params, 'promptData.prompts.jobType.value.value') && _.get(params, 'promptData.launchConf.ask_job_type_on_launch')) { + promptDataToSave.job_type = params.promptData.launchConf.defaults.job_type && params.promptData.launchConf.defaults.job_type === params.promptData.prompts.jobType.value.value ? null : params.promptData.prompts.jobType.value.value; + } + if(_.has(params, 'promptData.prompts.tags.value') && _.get(params, 'promptData.launchConf.ask_tags_on_launch')){ + const templateDefaultJobTags = params.promptData.launchConf.defaults.job_tags.split(','); + promptDataToSave.job_tags = (_.isEqual(templateDefaultJobTags.sort(), params.promptData.prompts.tags.value.map(a => a.value).sort())) ? null : params.promptData.prompts.tags.value.map(a => a.value).join(); + } + if(_.has(params, 'promptData.prompts.skipTags.value') && _.get(params, 'promptData.launchConf.ask_skip_tags_on_launch')){ + const templateDefaultSkipTags = params.promptData.launchConf.defaults.skip_tags.split(','); + promptDataToSave.skip_tags = (_.isEqual(templateDefaultSkipTags.sort(), params.promptData.prompts.skipTags.value.map(a => a.value).sort())) ? null : params.promptData.prompts.skipTags.value.map(a => a.value).join(); + } + if(_.has(params, 'promptData.prompts.limit.value') && _.get(params, 'promptData.launchConf.ask_limit_on_launch')){ + promptDataToSave.limit = params.promptData.launchConf.defaults.limit && params.promptData.launchConf.defaults.limit === params.promptData.prompts.limit.value ? null : params.promptData.prompts.limit.value; + } + if(_.has(params, 'promptData.prompts.verbosity.value.value') && _.get(params, 'promptData.launchConf.ask_verbosity_on_launch')){ + promptDataToSave.verbosity = params.promptData.launchConf.defaults.verbosity && params.promptData.launchConf.defaults.verbosity === params.promptData.prompts.verbosity.value.value ? null : params.promptData.prompts.verbosity.value.value; + } + if(_.has(params, 'promptData.prompts.inventory.value') && _.get(params, 'promptData.launchConf.ask_inventory_on_launch')){ + promptDataToSave.inventory = params.promptData.launchConf.defaults.inventory && params.promptData.launchConf.defaults.inventory.id === params.promptData.prompts.inventory.value.id ? null : params.promptData.prompts.inventory.value.id; + } + if(_.has(params, 'promptData.prompts.diffMode.value') && _.get(params, 'promptData.launchConf.ask_diff_mode_on_launch')){ + promptDataToSave.diff_mode = params.promptData.launchConf.defaults.diff_mode && params.promptData.launchConf.defaults.diff_mode === params.promptData.prompts.diffMode.value ? null : params.promptData.prompts.diffMode.value; + } + + return promptDataToSave; + }; } PromptService.$inject = ['Empty', '$filter']; diff --git a/awx/ui/client/src/templates/prompt/steps/preview/prompt-preview.controller.js b/awx/ui/client/src/templates/prompt/steps/preview/prompt-preview.controller.js index 66e7e00600..eb88095284 100644 --- a/awx/ui/client/src/templates/prompt/steps/preview/prompt-preview.controller.js +++ b/awx/ui/client/src/templates/prompt/steps/preview/prompt-preview.controller.js @@ -72,7 +72,7 @@ export default }); } - scope.promptExtraVars = $.isEmptyObject(scope.promptData.extraVars) ? '---' : jsyaml.safeDump(scope.promptData.extraVars); + scope.promptExtraVars = $.isEmptyObject(scope.promptData.extraVars) ? '---' : '---\n' + jsyaml.safeDump(scope.promptData.extraVars); ParseTypeChange({ scope: scope, diff --git a/awx/ui/client/src/templates/prompt/steps/preview/prompt-preview.partial.html b/awx/ui/client/src/templates/prompt/steps/preview/prompt-preview.partial.html index b2a20a541f..452aa6815c 100644 --- a/awx/ui/client/src/templates/prompt/steps/preview/prompt-preview.partial.html +++ b/awx/ui/client/src/templates/prompt/steps/preview/prompt-preview.partial.html @@ -1,5 +1,5 @@
-
+
{{:: vm.strings.get('prompt.JOB_TYPE') }}
{{:: vm.strings.get('prompt.PLAYBOOK_RUN') }} @@ -24,7 +24,7 @@
-
+
{{:: vm.strings.get('prompt.INVENTORY') }}
@@ -32,7 +32,7 @@
{{:: vm.strings.get('prompt.LIMIT') }}
-
+
{{:: vm.strings.get('prompt.VERBOSITY') }}
@@ -68,7 +68,7 @@
-
+
{{:: vm.strings.get('prompt.SHOW_CHANGES') }}
{{:: vm.strings.get('ON') }} 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 85e0336352..8f80fadf84 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 @@ -77,61 +77,36 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService', unified_job_template: params.node.unifiedJobTemplate.id }; + if(_.has(params, 'node.promptData.extraVars')) { + if(_.get(params, 'node.promptData.launchConf.defaults.extra_vars')) { + if(!sendableNodeData.extra_data) { + sendableNodeData.extra_data = {}; + } + + const defaultVars = jsyaml.safeLoad(params.node.promptData.launchConf.defaults.extra_vars); + + // Only include extra vars that differ from the template default vars + _.forOwn(params.node.promptData.extraVars, (value, key) => { + if(!defaultVars[key] || defaultVars[key] !== value) { + sendableNodeData.extra_data[key] = value; + } + }); + if(sendableNodeData.extra_data === {}) { + delete sendableNodeData.extra_data; + } + } else { + sendableNodeData.extra_data = params.node.promptData.extraVars; + } + } + // Check to see if the user has provided any prompt values that are different // from the defaults in the job template if(params.node.unifiedJobTemplate.type === "job_template" && params.node.promptData) { - if(params.node.promptData.launchConf.survey_enabled){ - for (var i=0; i < params.node.promptData.surveyQuestions.length; i++){ - var fld = params.node.promptData.surveyQuestions[i].variable; - // grab all survey questions that have answers - if(params.node.promptData.surveyQuestions[i].required || (params.node.promptData.surveyQuestions[i].required === false && params.node.promptData.surveyQuestions[i].model.toString()!=="")) { - if(!sendableNodeData.extra_data) { - sendableNodeData.extra_data = {}; - } - sendableNodeData.extra_data[fld] = params.node.promptData.surveyQuestions[i].model; - } - - if(params.node.promptData.surveyQuestions[i].required === false && _.isEmpty(params.node.promptData.surveyQuestions[i].model)) { - switch (params.node.promptData.surveyQuestions[i].type) { - // for optional text and text-areas, submit a blank string if min length is 0 - // -- this is confusing, for an explanation see: - // http://docs.ansible.com/ansible-tower/latest/html/userguide/job_templates.html#optional-survey-questions - // - case "text": - case "textarea": - if (params.node.promptData.surveyQuestions[i].min === 0) { - sendableNodeData.extra_data[fld] = ""; - } - break; - } - } - } - } - - if(_.has(params, 'node.promptData.prompts.jobType.value.value') && _.get(params, 'node.promptData.launchConf.ask_job_type_on_launch')) { - sendableNodeData.job_type = params.node.promptData.prompts.jobType.templateDefault === params.node.promptData.prompts.jobType.value.value ? null : params.node.promptData.prompts.jobType.value.value; - } - if(_.has(params, 'node.promptData.prompts.tags.value') && _.get(params, 'node.promptData.launchConf.ask_tags_on_launch')){ - let templateDefaultJobTags = params.node.promptData.prompts.tags.templateDefault.split(','); - sendableNodeData.job_tags = (_.isEqual(templateDefaultJobTags.sort(), params.node.promptData.prompts.tags.value.map(a => a.value).sort())) ? null : params.node.promptData.prompts.tags.value.map(a => a.value).join(); - } - if(_.has(params, 'node.promptData.prompts.skipTags.value') && _.get(params, 'node.promptData.launchConf.ask_skip_tags_on_launch')){ - let templateDefaultSkipTags = params.node.promptData.prompts.skipTags.templateDefault.split(','); - sendableNodeData.skip_tags = (_.isEqual(templateDefaultSkipTags.sort(), params.node.promptData.prompts.skipTags.value.map(a => a.value).sort())) ? null : params.node.promptData.prompts.skipTags.value.map(a => a.value).join(); - } - if(_.has(params, 'node.promptData.prompts.limit.value') && _.get(params, 'node.promptData.launchConf.ask_limit_on_launch')){ - sendableNodeData.limit = params.node.promptData.prompts.limit.templateDefault === params.node.promptData.prompts.limit.value ? null : params.node.promptData.prompts.limit.value; - } - if(_.has(params, 'node.promptData.prompts.verbosity.value.value') && _.get(params, 'node.promptData.launchConf.ask_verbosity_on_launch')){ - sendableNodeData.verbosity = params.node.promptData.prompts.verbosity.templateDefault === params.node.promptData.prompts.verbosity.value.value ? null : params.node.promptData.prompts.verbosity.value.value; - } - if(_.has(params, 'node.promptData.prompts.inventory.value') && _.get(params, 'node.promptData.launchConf.ask_inventory_on_launch')){ - sendableNodeData.inventory = _.has(params, 'node.promptData.prompts.inventory.templateDefault.id') && params.node.promptData.prompts.inventory.templateDefault.id === params.node.promptData.prompts.inventory.value.id ? null : params.node.promptData.prompts.inventory.value.id; - } - if(_.has(params, 'node.promptData.prompts.diffMode.value') && _.get(params, 'node.promptData.launchConf.ask_diff_mode_on_launch')){ - sendableNodeData.diff_mode = params.node.promptData.prompts.diffMode.templateDefault === params.node.promptData.prompts.diffMode.value ? null : params.node.promptData.prompts.diffMode.value; - } + sendableNodeData = PromptService.bundlePromptDataForSaving({ + promptData: params.node.promptData, + dataToSave: sendableNodeData + }); } return sendableNodeData; @@ -1028,7 +1003,7 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService', }), }; - $scope.$watch('promptData.surveyQuestions', () => { + surveyQuestionWatcher = $scope.$watch('promptData.surveyQuestions', () => { let missingSurveyValue = false; _.each($scope.promptData.surveyQuestions, (question) => { if(question.required && (Empty(question.model) || question.model === [])) { diff --git a/awx/ui/client/src/workflow-results/workflow-results.service.js b/awx/ui/client/src/workflow-results/workflow-results.service.js index 4db058a163..41bc410647 100644 --- a/awx/ui/client/src/workflow-results/workflow-results.service.js +++ b/awx/ui/client/src/workflow-results/workflow-results.service.js @@ -5,7 +5,7 @@ *************************************************/ -export default ['$q', 'Prompt', '$filter', 'Wait', 'Rest', '$state', 'ProcessErrors', 'InitiatePlaybookRun', '$interval', 'moment', function ($q, Prompt, $filter, Wait, Rest, $state, ProcessErrors, InitiatePlaybookRun, $interval, moment) { +export default ['$q', 'Prompt', '$filter', 'Wait', 'Rest', '$state', 'ProcessErrors', 'WorkflowJobModel', '$interval', 'moment', function ($q, Prompt, $filter, Wait, Rest, $state, ProcessErrors, WorkflowJob, $interval, moment) { var val = { getCounts: function(workflowNodes){ var nodeArr = []; @@ -107,8 +107,13 @@ export default ['$q', 'Prompt', '$filter', 'Wait', 'Rest', '$state', 'ProcessErr }); }, relaunchJob: function(scope) { - InitiatePlaybookRun({ scope: scope, id: scope.workflow.id, - relaunch: true, job_type: 'workflow_job' }); + const workflowJob = new WorkflowJob(); + + workflowJob.postRelaunch({ + id: scope.workflow.id + }).then((launchRes) => { + $state.go('workflowResults', { id: launchRes.data.id }, { reload: true }); + }); }, createOneSecondTimer: function(startTime, fn) { return $interval(function(){ diff --git a/awx/ui/test/spec/job-results/job-results.controller-test.js b/awx/ui/test/spec/job-results/job-results.controller-test.js index af3aeeae34..c05f2329f7 100644 --- a/awx/ui/test/spec/job-results/job-results.controller-test.js +++ b/awx/ui/test/spec/job-results/job-results.controller-test.js @@ -393,19 +393,6 @@ describe('Controller: jobResultsController', () => { }); }); - describe('$scope.relaunchJob', () => { - beforeEach(() => { - bootstrapTest(); - }); - - it('should relaunch the job', () => { - let scope = $scope; - $scope.relaunchJob(); - expect(jobResultsService.relaunchJob) - .toHaveBeenCalledWith(scope); - }); - }); - describe('count stuff', () => { beforeEach(() => { count = { diff --git a/awx/ui/test/spec/workflow--results/workflow-results.controller-test.js b/awx/ui/test/spec/workflow--results/workflow-results.controller-test.js index 91616b8a03..a6c89201a6 100644 --- a/awx/ui/test/spec/workflow--results/workflow-results.controller-test.js +++ b/awx/ui/test/spec/workflow--results/workflow-results.controller-test.js @@ -18,7 +18,7 @@ describe('Controller: workflowResults', () => { beforeEach(angular.mock.module('workflowResults', ($provide) => { ['PromptDialog', 'Prompt', 'Wait', 'Rest', '$state', 'ProcessErrors', - 'InitiatePlaybookRun', 'jobLabels', 'workflowNodes', 'count', + 'jobLabels', 'workflowNodes', 'count', ].forEach((item) => { $provide.value(item, {}); }); diff --git a/awx/ui/test/spec/workflow--results/workflow-results.service-test.js b/awx/ui/test/spec/workflow--results/workflow-results.service-test.js index 8f0e2ad843..45d1841ec1 100644 --- a/awx/ui/test/spec/workflow--results/workflow-results.service-test.js +++ b/awx/ui/test/spec/workflow--results/workflow-results.service-test.js @@ -6,7 +6,7 @@ describe('workflowResultsService', () => { let $interval; beforeEach(angular.mock.module('workflowResults', ($provide) => { - ['PromptDialog', 'Prompt', 'Wait', 'Rest', 'ProcessErrors', 'InitiatePlaybookRun', '$state'].forEach(function(item) { + ['PromptDialog', 'Prompt', 'Wait', 'Rest', 'ProcessErrors', '$state'].forEach(function(item) { $provide.value(item, {}); }); $provide.value('$stateExtender', { addState: jasmine.createSpy('addState'), });