diff --git a/awx/ui/client/src/shared/main.js b/awx/ui/client/src/shared/main.js index eccf67be4e..3fdf3bdf95 100644 --- a/awx/ui/client/src/shared/main.js +++ b/awx/ui/client/src/shared/main.js @@ -23,6 +23,7 @@ import apiLoader from './api-loader'; import variables from './variables/main'; import parse from './parse/main'; import loadconfig from './load-config/main'; +import nextpage from './next-page/main'; import Modal from './Modal'; import moment from './moment/main'; import config from './config/main'; @@ -50,6 +51,7 @@ angular.module('shared', [listGenerator.name, variables.name, parse.name, loadconfig.name, + nextpage.name, Modal.name, moment.name, config.name, diff --git a/awx/ui/client/src/shared/next-page/main.js b/awx/ui/client/src/shared/next-page/main.js new file mode 100644 index 0000000000..8d9e83f28a --- /dev/null +++ b/awx/ui/client/src/shared/next-page/main.js @@ -0,0 +1,5 @@ +import NextPage from './next-page.factory'; + +export default + angular.module('nextpage', []) + .factory('NextPage', NextPage); diff --git a/awx/ui/client/src/shared/next-page/next-page.factory.js b/awx/ui/client/src/shared/next-page/next-page.factory.js new file mode 100644 index 0000000000..fa5bce3dc7 --- /dev/null +++ b/awx/ui/client/src/shared/next-page/next-page.factory.js @@ -0,0 +1,28 @@ +export default + function NextPage(Rest, $q) { + return function(params) { + + let getNext = function(getNextParams){ + Rest.setUrl(getNextParams.url); + return Rest.get() + .then(function (res) { + if (res.data.next) { + return getNext({ + url: res.data.next, + arrayOfValues: getNextParams.arrayOfValues.concat(res.data.results) + }); + } else { + return $q.resolve(getNextParams.arrayOfValues.concat(res.data.results)); + } + }) + .catch(function(response) { + return $q.reject( response ); + }); + }; + + return getNext(params); + + }; + } + +NextPage.$inject = ['Rest', '$q']; diff --git a/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js b/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js index a76ac90481..e9b2176471 100644 --- a/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js +++ b/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js @@ -8,25 +8,16 @@ [ '$filter', '$scope', '$stateParams', 'JobTemplateForm', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'GetBasePath', 'md5Setup', 'ParseTypeChange', 'Wait', - 'Empty', 'ToJSON', 'CallbackHelpInit', 'GetChoices', '$state', - 'CreateSelect2', '$q', 'i18n', 'Inventory', 'Project', 'InstanceGroupsService', 'MultiCredentialService', + 'Empty', 'ToJSON', 'CallbackHelpInit', 'GetChoices', '$state', 'availableLabels', + 'CreateSelect2', '$q', 'i18n', 'Inventory', 'Project', 'InstanceGroupsService', 'MultiCredentialService', function( $filter, $scope, $stateParams, JobTemplateForm, GenerateForm, Rest, Alert, ProcessErrors, GetBasePath, md5Setup, ParseTypeChange, Wait, Empty, ToJSON, CallbackHelpInit, GetChoices, - $state, CreateSelect2, $q, i18n, Inventory, Project, InstanceGroupsService, MultiCredentialService + $state, availableLabels, CreateSelect2, $q, i18n, Inventory, Project, InstanceGroupsService, MultiCredentialService ) { - - Rest.setUrl(GetBasePath('job_templates')); - Rest.options() - .success(function(data) { - if (!data.actions.POST) { - $state.go("^"); - Alert(i18n._('Permission Error'), i18n._('You do not have permission to add a job template.'), 'alert-info'); - } - }); - + // Inject dynamic view let defaultUrl = GetBasePath('job_templates'), form = JobTemplateForm(), @@ -61,7 +52,6 @@ $scope[form.name + '_form'].$setDirty(); }; - var selectCount = 0; if ($scope.removeChoicesReady) { @@ -118,20 +108,9 @@ callback: 'choicesReadyVerbosity' }); - Rest.setUrl(GetBasePath('labels')); - Rest.get() - .success(function (data) { - $scope.labelOptions = data.results - .map((i) => ({label: i.name, value: i.id})); - $scope.$emit("choicesReadyVerbosity"); - }) - .error(function (data, status) { - ProcessErrors($scope, data, status, form, { - hdr: 'Error!', - msg: 'Failed to get labels. GET returned ' + - 'status: ' + status - }); - }); + $scope.labelOptions = availableLabels + .map((i) => ({label: i.name, value: i.id})); + $scope.$emit("choicesReadyVerbosity"); function sync_playbook_select2() { CreateSelect2({ @@ -236,131 +215,6 @@ $state.go('templates.editJobTemplate', {job_template_id: id}, {reload: true}); } - - if ($scope.removeTemplateSaveSuccess) { - $scope.removeTemplateSaveSuccess(); - } - $scope.removeTemplateSaveSuccess = $scope.$on('templateSaveSuccess', function(e, data) { - Wait('stop'); - if (data.related && - data.related.callback) { - Alert('Callback URL', -`Host callbacks are enabled for this template. The callback URL is: -

- - ${$scope.callback_server_path} - ${data.related.callback} - -

-

The host configuration key is: - - ${$filter('sanitize')(data.host_config_key)} - -

`, - 'alert-danger', saveCompleted, null, null, - null, true); - } - - MultiCredentialService - .saveExtraCredentials({ - creds: $scope.selectedCredentials.extra, - url: data.related.extra_credentials - }); - - var orgDefer = $q.defer(); - var associationDefer = $q.defer(); - Rest.setUrl(data.related.labels); - - var currentLabels = Rest.get() - .then(function(data) { - return data.data.results - .map(val => val.id); - }); - - currentLabels.then(function (current) { - var labelsToAdd = ($scope.labels || []) - .map(val => val.value); - var labelsToDisassociate = current - .filter(val => labelsToAdd - .indexOf(val) === -1) - .map(val => ({id: val, disassociate: true})); - var labelsToAssociate = labelsToAdd - .filter(val => current - .indexOf(val) === -1) - .map(val => ({id: val, associate: true})); - var pass = labelsToDisassociate - .concat(labelsToAssociate); - associationDefer.resolve(pass); - }); - - Rest.setUrl(GetBasePath("organizations")); - Rest.get() - .success(function(data) { - orgDefer.resolve(data.results[0].id); - }); - - orgDefer.promise.then(function(orgId) { - var toPost = []; - $scope.newLabels = $scope.newLabels - .map(function(i, val) { - val.organization = orgId; - return val; - }); - - $scope.newLabels.each(function(i, val) { - toPost.push(val); - }); - - associationDefer.promise.then(function(arr) { - toPost = toPost - .concat(arr); - - Rest.setUrl(data.related.labels); - - var defers = []; - for (var i = 0; i < toPost.length; i++) { - defers.push(Rest.post(toPost[i])); - } - $q.all(defers) - .then(function() { - $scope.addedItem = data.id; - - if($scope.survey_questions && - $scope.survey_questions.length > 0){ - //once the job template information - // is saved we submit the survey - // info to the correct endpoint - var url = data.url+ 'survey_spec/'; - Rest.setUrl(url); - Rest.post({ name: $scope.survey_name, - description: $scope.survey_description, - spec: $scope.survey_questions }) - .success(function () { - Wait('stop'); - }) - .error(function (data, - status) { - ProcessErrors( - $scope, - data, - status, - form, - { - hdr: 'Error!', - msg: 'Failed to add new ' + - 'survey. Post returned ' + - 'status: ' + - status - }); - }); - } - - saveCompleted(data.id); - }); - }); - }); - }); - // Save $scope.formSave = function () { var fld, data = {}; @@ -443,7 +297,124 @@ Rest.setUrl(defaultUrl); Rest.post(data) .then(({data}) => { - $scope.$emit('templateSaveSuccess', data); + + Wait('stop'); + if (data.related && data.related.callback) { + Alert('Callback URL', + `Host callbacks are enabled for this template. The callback URL is: +

+ + ${$scope.callback_server_path} + ${data.related.callback} + +

+

The host configuration key is: + + ${$filter('sanitize')(data.host_config_key)} + +

`, + 'alert-danger', saveCompleted, null, null, + null, true); + } + + MultiCredentialService + .saveExtraCredentials({ + creds: $scope.selectedCredentials.extra, + url: data.related.extra_credentials + }); + + var orgDefer = $q.defer(); + var associationDefer = $q.defer(); + Rest.setUrl(data.related.labels); + + var currentLabels = Rest.get() + .then(function(data) { + return data.data.results + .map(val => val.id); + }); + + currentLabels.then(function (current) { + var labelsToAdd = ($scope.labels || []) + .map(val => val.value); + var labelsToDisassociate = current + .filter(val => labelsToAdd + .indexOf(val) === -1) + .map(val => ({id: val, disassociate: true})); + var labelsToAssociate = labelsToAdd + .filter(val => current + .indexOf(val) === -1) + .map(val => ({id: val, associate: true})); + var pass = labelsToDisassociate + .concat(labelsToAssociate); + associationDefer.resolve(pass); + }); + + Rest.setUrl(GetBasePath("organizations")); + Rest.get() + .success(function(data) { + orgDefer.resolve(data.results[0].id); + }); + + orgDefer.promise.then(function(orgId) { + var toPost = []; + $scope.newLabels = $scope.newLabels + .map(function(i, val) { + val.organization = orgId; + return val; + }); + + $scope.newLabels.each(function(i, val) { + toPost.push(val); + }); + + associationDefer.promise.then(function(arr) { + toPost = toPost + .concat(arr); + + Rest.setUrl(data.related.labels); + + var defers = []; + for (var i = 0; i < toPost.length; i++) { + defers.push(Rest.post(toPost[i])); + } + $q.all(defers) + .then(function() { + $scope.addedItem = data.id; + + if($scope.survey_questions && + $scope.survey_questions.length > 0){ + //once the job template information + // is saved we submit the survey + // info to the correct endpoint + var url = data.url+ 'survey_spec/'; + Rest.setUrl(url); + Rest.post({ name: $scope.survey_name, + description: $scope.survey_description, + spec: $scope.survey_questions }) + .success(function () { + Wait('stop'); + }) + .error(function (data, + status) { + ProcessErrors( + $scope, + data, + status, + form, + { + hdr: 'Error!', + msg: 'Failed to add new ' + + 'survey. Post returned ' + + 'status: ' + + status + }); + }); + } + + saveCompleted(data.id); + }); + }); + }); const instance_group_url = data.related.instance_groups; InstanceGroupsService.addInstanceGroups(instance_group_url, $scope.instance_groups) 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 a0173af964..ff88cf9473 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 @@ -14,17 +14,17 @@ export default [ '$filter', '$scope', '$rootScope', '$location', '$stateParams', 'JobTemplateForm', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'GetBasePath', 'md5Setup', - 'ParseTypeChange', 'Wait', + 'ParseTypeChange', 'Wait', 'selectedLabels', 'Empty', 'Prompt', 'ToJSON', 'GetChoices', 'CallbackHelpInit', 'InitiatePlaybookRun' , 'initSurvey', '$state', 'CreateSelect2', - 'ToggleNotification','$q', 'InstanceGroupsService', 'InstanceGroupsData', 'MultiCredentialService', + 'ToggleNotification','$q', 'InstanceGroupsService', 'InstanceGroupsData', 'MultiCredentialService', 'availableLabels', function( $filter, $scope, $rootScope, $location, $stateParams, JobTemplateForm, GenerateForm, Rest, Alert, ProcessErrors, GetBasePath, md5Setup, - ParseTypeChange, Wait, + ParseTypeChange, Wait, selectedLabels, Empty, Prompt, ToJSON, GetChoices, CallbackHelpInit, InitiatePlaybookRun, SurveyControllerInit, $state, - CreateSelect2, ToggleNotification, $q, InstanceGroupsService, InstanceGroupsData, MultiCredentialService + CreateSelect2, ToggleNotification, $q, InstanceGroupsService, InstanceGroupsData, MultiCredentialService, availableLabels ) { $scope.$watch('job_template_obj.summary_fields.user_capabilities.edit', function(val) { @@ -39,7 +39,7 @@ export default base = $location.path().replace(/^\//, '').split('/')[0], master = {}, id = $stateParams.job_template_id, - checkSCMStatus, getPlaybooks, callback, + callback, choicesCount = 0, instance_group_url = defaultUrl + id + '/instance_groups'; @@ -61,6 +61,88 @@ export default id: id, templateType: 'job_template' }); + + $scope.$watch('project', function (newValue, oldValue) { + if (newValue !== oldValue) { + var url; + if ($scope.playbook) { + $scope.playbook_options = [$scope.playbook]; + } + + if (!Empty($scope.project)) { + let promises = []; + url = GetBasePath('projects') + $scope.project + '/playbooks/'; + Wait('start'); + Rest.setUrl(url); + promises.push(Rest.get() + .success(function (data) { + $scope.disablePlaybookBecausePermissionDenied = false; + $scope.playbook_options = []; + var playbookNotFound = true; + for (var i = 0; i < data.length; i++) { + $scope.playbook_options.push(data[i]); + if (data[i] === $scope.playbook) { + $scope.job_template_form.playbook.$setValidity('required', true); + playbookNotFound = false; + } + } + $scope.playbookNotFound = playbookNotFound; + sync_playbook_select2(); + if ($scope.playbook) { + jobTemplateLoadFinished(); + } + }) + .error(function (ret,status_code) { + if (status_code === 403) { + /* user doesn't have access to see the project, no big deal. */ + $scope.disablePlaybookBecausePermissionDenied = true; + } else { + Alert('Missing Playbooks', 'Unable to retrieve the list of playbooks for this project. Choose a different ' + + ' project or make the playbooks available on the file system.', 'alert-info'); + } + Wait('stop'); + })); + + + Rest.setUrl(GetBasePath('projects') + $scope.project + '/'); + promises.push(Rest.get() + .success(function (data) { + var msg; + switch (data.status) { + case 'failed': + msg = "
The Project selected has a status of \"failed\". You must run a successful update before you can select a playbook. You will not be able to save this Job Template without a valid playbook."; + break; + case 'never updated': + msg = "
The Project selected has a status of \"never updated\". You must run a successful update before you can select a playbook. You will not be able to save this Job Template without a valid playbook."; + break; + case 'missing': + msg = '
The selected project has a status of \"missing\". Please check the server and make sure ' + + ' the directory exists and file permissions are set correctly.
'; + break; + } + if (msg) { + Alert('Warning', msg, 'alert-info alert-info--noTextTransform', null, null, null, null, true); + } + }) + .error(function (data, status) { + if (status === 403) { + /* User doesn't have read access to the project, no problem. */ + } else { + ProcessErrors($scope, data, status, form, { hdr: 'Error!', msg: 'Failed to get project ' + $scope.project + + '. GET returned status: ' + status }); + } + })); + + $q.all(promises) + .then(function(){ + Wait('stop'); + }); + } + } + }); + + // watch for changes to 'verbosity', ensure we keep our select2 in sync when it changes. + $scope.$watch('verbosity', sync_verbosity_select2); } callback = function() { @@ -82,51 +164,17 @@ export default }); } - getPlaybooks = function (project) { - var url; - if ($scope.playbook) { - $scope.playbook_options = [$scope.playbook]; - } + function jobTemplateLoadFinished(){ + CreateSelect2({ + element:'#job_template_job_type', + multiple: false + }); - if (!Empty(project)) { - url = GetBasePath('projects') + project + '/playbooks/'; - Wait('start'); - Rest.setUrl(url); - Rest.get() - .success(function (data) { - $scope.disablePlaybookBecausePermissionDenied = false; - $scope.playbook_options = []; - var playbookNotFound = true; - for (var i = 0; i < data.length; i++) { - $scope.playbook_options.push(data[i]); - if (data[i] === $scope.playbook) { - $scope.job_template_form.playbook.$setValidity('required', true); - playbookNotFound = false; - } - } - $scope.playbookNotFound = playbookNotFound; - sync_playbook_select2(); - if ($scope.playbook) { - $scope.$emit('jobTemplateLoadFinished'); - } else { - Wait('stop'); - } - }) - .error(function (ret,status_code) { - if (status_code === 403) { - /* user doesn't have access to see the project, no big deal. */ - $scope.disablePlaybookBecausePermissionDenied = true; - } else { - Alert('Missing Playbooks', 'Unable to retrieve the list of playbooks for this project. Choose a different ' + - ' project or make the playbooks available on the file system.', 'alert-info'); - } - Wait('stop'); - }); - } - else { - Wait('stop'); - } - }; + CreateSelect2({ + element:'#playbook-select', + multiple: false + }); + } $scope.jobTypeChange = function() { sync_playbook_select2(); @@ -149,72 +197,6 @@ export default }); }; - // Detect and alert user to potential SCM status issues - checkSCMStatus = function () { - if (!Empty($scope.project)) { - Wait('start'); - Rest.setUrl(GetBasePath('projects') + $scope.project + '/'); - Rest.get() - .success(function (data) { - var msg; - switch (data.status) { - case 'failed': - msg = "
The Project selected has a status of \"failed\". You must run a successful update before you can select a playbook. You will not be able to save this Job Template without a valid playbook."; - break; - case 'never updated': - msg = "
The Project selected has a status of \"never updated\". You must run a successful update before you can select a playbook. You will not be able to save this Job Template without a valid playbook."; - break; - case 'missing': - msg = '
The selected project has a status of \"missing\". Please check the server and make sure ' + - ' the directory exists and file permissions are set correctly.
'; - break; - } - Wait('stop'); - if (msg) { - Alert('Warning', msg, 'alert-info alert-info--noTextTransform', null, null, null, null, true); - } - }) - .error(function (data, status) { - if (status === 403) { - /* User doesn't have read access to the project, no problem. */ - } else { - ProcessErrors($scope, data, status, form, { hdr: 'Error!', msg: 'Failed to get project ' + $scope.project + - '. GET returned status: ' + status }); - } - }); - } - }; - - // Register a watcher on project_name. Refresh the playbook list on change. - if ($scope.watchProjectUnregister) { - $scope.watchProjectUnregister(); - } - $scope.watchProjectUnregister = $scope.$watch('project', function (newValue, oldValue) { - if (newValue !== oldValue) { - getPlaybooks($scope.project); - checkSCMStatus(); - } - }); - - // watch for changes to 'verbosity', ensure we keep our select2 in sync when it changes. - $scope.$watch('verbosity', sync_verbosity_select2); - - if ($scope.removeJobTemplateLoadFinished) { - $scope.removeJobTemplateLoadFinished(); - } - $scope.removeJobTemplateLoadFinished = $scope.$on('jobTemplateLoadFinished', function () { - CreateSelect2({ - element:'#job_template_job_type', - multiple: false - }); - - CreateSelect2({ - element:'#playbook-select', - multiple: false - }); - - }); - // Retrieve each related set and populate the playbook list if ($scope.jobTemplateLoadedRemove) { $scope.jobTemplateLoadedRemove(); @@ -224,8 +206,6 @@ export default master = masterObject; - getPlaybooks($scope.project); - dft = ($scope.host_config_key === "" || $scope.host_config_key === null) ? false : true; md5Setup({ scope: $scope, @@ -236,7 +216,7 @@ export default ParseTypeChange({ scope: $scope, field_id: 'job_template_variables', onChange: callback }); - $scope.$emit('jobTemplateLoadFinished'); + jobTemplateLoadFinished(); }); Wait('start'); @@ -302,68 +282,21 @@ export default callback: 'choicesReady' }); - Rest.setUrl(GetBasePath('labels')); - Wait("start"); - Rest.get() - .success(function (data) { - $scope.labelOptions = data.results - .map((i) => ({label: i.name, value: i.id})); + $scope.labelOptions = availableLabels + .map((i) => ({label: i.name, value: i.id})); - var seeMoreResolve = $q.defer(); + var opts = selectedLabels + .map(i => ({id: i.id + "", + test: i.name})); - var getNext = function(data, arr, resolve) { - Rest.setUrl(data.next); - Rest.get() - .success(function (data) { - if (data.next) { - getNext(data, arr.concat(data.results), resolve); - } else { - resolve.resolve(arr.concat(data.results)); - } - }); - }; - if($state.params.job_template_id !== "null"){ - Rest.setUrl(defaultUrl + $state.params.job_template_id + - "/labels"); - Rest.get() - .success(function(data) { - if (data.next) { - getNext(data, data.results, seeMoreResolve); - } else { - seeMoreResolve.resolve(data.results); - } + CreateSelect2({ + element:'#job_template_labels', + multiple: true, + addNew: true, + opts: opts + }); - seeMoreResolve.promise.then(function (labels) { - $scope.$emit("choicesReady"); - var opts = labels - .map(i => ({id: i.id + "", - test: i.name})); - CreateSelect2({ - element:'#job_template_labels', - multiple: true, - addNew: true, - opts: opts - }); - Wait("stop"); - }); - }).error(function(){ - // job template id is null in this case - $scope.$emit("choicesReady"); - }); - } - else { - // job template doesn't exist - $scope.$emit("choicesReady"); - } - - }) - .error(function (data, status) { - ProcessErrors($scope, data, status, form, { - hdr: 'Error!', - msg: 'Failed to get labels. GET returned ' + - 'status: ' + status - }); - }); + $scope.$emit("choicesReady"); function saveCompleted() { $state.go($state.current, {}, {reload: true}); diff --git a/awx/ui/client/src/templates/main.js b/awx/ui/client/src/templates/main.js index 0480584109..ecb07913c9 100644 --- a/awx/ui/client/src/templates/main.js +++ b/awx/ui/client/src/templates/main.js @@ -77,7 +77,36 @@ angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplates. }); }); } - }] + }], + availableLabels: ['ProcessErrors', 'TemplatesService', + function(ProcessErrors, TemplatesService) { + return TemplatesService.getAllLabelOptions() + .then(function(labels){ + return labels; + }).catch(function(response){ + ProcessErrors(null, response.data, response.status, null, { + hdr: 'Error!', + msg: 'Failed to get labels. GET returned status: ' + + response.status + }); + }); + }], + checkPermissions: ['Rest', 'GetBasePath', 'TemplatesService', 'Alert', 'ProcessErrors', '$state', + function(Rest, GetBasePath, TemplatesService, Alert, ProcessErrors, $state) { + return TemplatesService.getJobTemplateOptions() + .then(function(data) { + if (!data.actions.POST) { + $state.go("^"); + Alert('Permission Error', 'You do not have permission to add a job template.', 'alert-info'); + } + }).catch(function(response){ + ProcessErrors(null, response.data, response.status, null, { + hdr: 'Error!', + msg: 'Failed to get job template options. OPTIONS returned status: ' + + response.status + }); + }); + }] } } }); @@ -117,6 +146,32 @@ angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplates. 'status: ' + status }); }); + }], + availableLabels: ['Rest', '$stateParams', 'GetBasePath', 'ProcessErrors', 'TemplatesService', + function(Rest, $stateParams, GetBasePath, ProcessErrors, TemplatesService) { + return TemplatesService.getAllLabelOptions() + .then(function(labels){ + return labels; + }).catch(function(response){ + ProcessErrors(null, response.data, response.status, null, { + hdr: 'Error!', + msg: 'Failed to get labels. GET returned status: ' + + response.status + }); + }); + }], + selectedLabels: ['Rest', '$stateParams', 'GetBasePath', 'TemplatesService', 'ProcessErrors', + function(Rest, $stateParams, GetBasePath, TemplatesService, ProcessErrors) { + return TemplatesService.getAllJobTemplateLabels($stateParams.job_template_id) + .then(function(labels){ + return labels; + }).catch(function(response){ + ProcessErrors(null, response.data, response.status, null, { + hdr: 'Error!', + msg: 'Failed to get workflow job template labels. GET returned status: ' + + response.status + }); + }); }] } } @@ -129,6 +184,39 @@ angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplates. form: 'WorkflowForm', controllers: { add: 'WorkflowAdd' + }, + resolve: { + add: { + availableLabels: ['Rest', '$stateParams', 'GetBasePath', 'ProcessErrors', 'TemplatesService', + function(Rest, $stateParams, GetBasePath, ProcessErrors, TemplatesService) { + return TemplatesService.getAllLabelOptions() + .then(function(labels){ + return labels; + }).catch(function(response){ + ProcessErrors(null, response.data, response.status, null, { + hdr: 'Error!', + msg: 'Failed to get labels. GET returned status: ' + + response.status + }); + }); + }], + checkPermissions: ['Rest', 'GetBasePath', 'TemplatesService', 'Alert', 'ProcessErrors', '$state', + function(Rest, GetBasePath, TemplatesService, Alert, ProcessErrors, $state) { + return TemplatesService.getWorkflowJobTemplateOptions() + .then(function(data) { + if (!data.actions.POST) { + $state.go("^"); + Alert('Permission Error', 'You do not have permission to add a workflow job template.', 'alert-info'); + } + }).catch(function(response){ + ProcessErrors(null, response.data, response.status, null, { + hdr: 'Error!', + msg: 'Failed to get workflow job template options. OPTIONS returned status: ' + + response.status + }); + }); + }] + } } }); @@ -144,6 +232,49 @@ angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplates. activityStream: true, activityStreamTarget: 'workflow_job_template', activityStreamId: 'workflow_job_template_id' + }, + resolve: { + edit: { + availableLabels: ['Rest', '$stateParams', 'GetBasePath', 'ProcessErrors', 'TemplatesService', + function(Rest, $stateParams, GetBasePath, ProcessErrors, TemplatesService) { + return TemplatesService.getAllLabelOptions() + .then(function(labels){ + return labels; + }).catch(function(response){ + ProcessErrors(null, response.data, response.status, null, { + hdr: 'Error!', + msg: 'Failed to get labels. GET returned status: ' + + response.status + }); + }); + }], + selectedLabels: ['Rest', '$stateParams', 'GetBasePath', 'TemplatesService', 'ProcessErrors', + function(Rest, $stateParams, GetBasePath, TemplatesService, ProcessErrors) { + return TemplatesService.getAllWorkflowJobTemplateLabels($stateParams.workflow_job_template_id) + .then(function(labels){ + return labels; + }).catch(function(response){ + ProcessErrors(null, response.data, response.status, null, { + hdr: 'Error!', + msg: 'Failed to get workflow job template labels. GET returned status: ' + + response.status + }); + }); + }], + workflowJobTemplateData: ['$stateParams', 'TemplatesService', 'ProcessErrors', + function($stateParams, TemplatesService, ProcessErrors) { + return TemplatesService.getWorkflowJobTemplate($stateParams.workflow_job_template_id) + .then(function(res) { + return res.data; + }).catch(function(response){ + ProcessErrors(null, response.data, response.status, null, { + hdr: 'Error!', + msg: 'Failed to get workflow job template. GET returned status: ' + + response.status + }); + }); + }] + } } }); diff --git a/awx/ui/client/src/templates/templates.service.js b/awx/ui/client/src/templates/templates.service.js index 825dc5deb6..1c9940cb0f 100644 --- a/awx/ui/client/src/templates/templates.service.js +++ b/awx/ui/client/src/templates/templates.service.js @@ -4,7 +4,7 @@ * All Rights Reserved *************************************************/ -export default ['Rest', 'GetBasePath', '$q', function(Rest, GetBasePath, $q){ +export default ['Rest', 'GetBasePath', '$q', 'NextPage', function(Rest, GetBasePath, $q, NextPage){ return { deleteJobTemplate: function(id){ var url = GetBasePath('job_templates'); @@ -34,23 +34,69 @@ export default ['Rest', 'GetBasePath', '$q', function(Rest, GetBasePath, $q){ Rest.setUrl(url); return Rest.post(data); }, - getLabelOptions: function(){ - var url = GetBasePath('labels'); - - var deferred = $q.defer(); - - Rest.setUrl(url); - Rest.get() - .success(function(data) { - // Turn the labels into something consumable - var labels = data.results.map((i) => ({label: i.name, value: i.id})); - deferred.resolve(labels); - }).error(function(msg, code) { - deferred.reject(msg, code); - }); - - return deferred.promise; + getAllLabelOptions: function() { + Rest.setUrl(GetBasePath('labels') + '?page_size=200'); + return Rest.get() + .then(function(res) { + if (res.data.next) { + return NextPage({ + url: res.data.next, + arrayOfValues: res.data.results + }).then(function(labels) { + return labels; + }).catch(function(response){ + return $q.reject( response ); + }); + } + else { + return $q.resolve( res.data.results ); + } + }).catch(function(response){ + return $q.reject( response ); + }); + }, + getAllJobTemplateLabels: function(id) { + Rest.setUrl(GetBasePath('job_templates') + id + "/labels?page_size=20"); + return Rest.get() + .then(function(res) { + if (res.data.next) { + return NextPage({ + url: res.data.next, + arrayOfValues: res.data.results + }).then(function(labels) { + return labels; + }).catch(function(response){ + return $q.reject( response ); + }); + } + else { + return $q.resolve( res.data.results ); + } + }).catch(function(response){ + return $q.reject( response ); + }); + }, + getAllWorkflowJobTemplateLabels: function(id) { + Rest.setUrl(GetBasePath('workflow_job_templates') + id + "/labels?page_size=200"); + return Rest.get() + .then(function(res) { + if (res.data.next) { + return NextPage({ + url: res.data.next, + arrayOfValues: res.data.results + }).then(function(labels) { + return labels; + }).catch(function(response){ + return $q.reject( response ); + }); + } + else { + return $q.resolve( res.data.results ); + } + }).catch(function(response){ + return $q.reject( response ); + }); }, getJobTemplate: function(id) { var url = GetBasePath('job_templates'); @@ -205,6 +251,36 @@ export default ['Rest', 'GetBasePath', '$q', function(Rest, GetBasePath, $q){ Rest.setUrl(url); return Rest.post(); + }, + getWorkflowJobTemplateOptions: function() { + var deferred = $q.defer(); + + let url = GetBasePath('workflow_job_templates'); + + Rest.setUrl(url); + Rest.options() + .success(function(data) { + deferred.resolve(data); + }).error(function(msg, code) { + deferred.reject(msg, code); + }); + + return deferred.promise; + }, + getJobTemplateOptions: function() { + var deferred = $q.defer(); + + let url = GetBasePath('job_templates'); + + Rest.setUrl(url); + Rest.options() + .success(function(data) { + deferred.resolve(data); + }).error(function(msg, code) { + deferred.reject(msg, code); + }); + + return deferred.promise; } }; }]; diff --git a/awx/ui/client/src/templates/workflows/add-workflow/workflow-add.controller.js b/awx/ui/client/src/templates/workflows/add-workflow/workflow-add.controller.js index 255ab47faf..9c32fe652a 100644 --- a/awx/ui/client/src/templates/workflows/add-workflow/workflow-add.controller.js +++ b/awx/ui/client/src/templates/workflows/add-workflow/workflow-add.controller.js @@ -7,19 +7,10 @@ export default [ '$scope', 'WorkflowForm', 'GenerateForm', 'Alert', 'ProcessErrors', 'Wait', '$state', 'CreateSelect2', 'TemplatesService', - 'ToJSON', 'ParseTypeChange', '$q', 'Rest', 'GetBasePath', + 'ToJSON', 'ParseTypeChange', '$q', 'Rest', 'GetBasePath', 'availableLabels', function($scope, WorkflowForm, GenerateForm, Alert, ProcessErrors, Wait, $state, CreateSelect2, TemplatesService, ToJSON, - ParseTypeChange, $q, Rest, GetBasePath) { - - Rest.setUrl(GetBasePath('workflow_job_templates')); - Rest.options() - .success(function(data) { - if (!data.actions.POST) { - $state.go("^"); - Alert('Permission Error', 'You do not have permission to add a workflow job template.', 'alert-info'); - } - }); + ParseTypeChange, $q, Rest, GetBasePath, availableLabels) { // Inject dynamic view let form = WorkflowForm(), @@ -42,24 +33,14 @@ export default [ } }); - // Go out and grab the possible labels - TemplatesService.getLabelOptions() - .then(function(data){ - $scope.labelOptions = data; - // select2-ify the labels input - CreateSelect2({ - element:'#workflow_job_template_labels', - multiple: true, - addNew: true - }); - }, function(error){ - ProcessErrors($scope, error.data, error.status, form, { - hdr: 'Error!', - msg: 'Failed to get labels. GET returned ' + - 'status: ' + error.status - }); - }); + $scope.labelOptions = availableLabels + .map((i) => ({label: i.name, value: i.id})); + CreateSelect2({ + element:'#workflow_job_template_labels', + multiple: true, + addNew: true + }); } $scope.formSave = function () { diff --git a/awx/ui/client/src/templates/workflows/edit-workflow/workflow-edit.controller.js b/awx/ui/client/src/templates/workflows/edit-workflow/workflow-edit.controller.js index 2f74ba7b81..d61beadd78 100644 --- a/awx/ui/client/src/templates/workflows/edit-workflow/workflow-edit.controller.js +++ b/awx/ui/client/src/templates/workflows/edit-workflow/workflow-edit.controller.js @@ -9,12 +9,12 @@ export default [ 'ProcessErrors', 'GetBasePath', '$q', 'ParseTypeChange', 'Wait', 'Empty', 'ToJSON', 'initSurvey', '$state', 'CreateSelect2', 'ParseVariableString', 'TemplatesService', 'Rest', 'ToggleNotification', - 'OrgAdminLookup', + 'OrgAdminLookup', 'availableLabels', 'selectedLabels', 'workflowJobTemplateData', function($scope, $stateParams, WorkflowForm, GenerateForm, Alert, ProcessErrors, GetBasePath, $q, ParseTypeChange, Wait, Empty, ToJSON, SurveyControllerInit, $state, CreateSelect2, ParseVariableString, - TemplatesService, Rest, ToggleNotification, OrgAdminLookup) { - + TemplatesService, Rest, ToggleNotification, OrgAdminLookup, availableLabels, selectedLabels, workflowJobTemplateData) { + $scope.$watch('workflow_job_template_obj.summary_fields.user_capabilities.edit', function(val) { if (val === false) { $scope.canAddWorkflowJobTemplate = false; @@ -46,125 +46,74 @@ export default [ templateType: 'workflow_job_template' }); - Rest.setUrl(GetBasePath('labels')); - Wait("start"); - Rest.get() - .success(function (data) { - $scope.labelOptions = data.results - .map((i) => ({label: i.name, value: i.id})); + $scope.labelOptions = availableLabels + .map((i) => ({label: i.name, value: i.id})); - var seeMoreResolve = $q.defer(); + var opts = selectedLabels + .map(i => ({id: i.id + "", + test: i.name})); - var getNext = function(data, arr, resolve) { - Rest.setUrl(data.next); - Rest.get() - .success(function (data) { - if (data.next) { - getNext(data, arr.concat(data.results), resolve); - } else { - resolve.resolve(arr.concat(data.results)); + CreateSelect2({ + element:'#workflow_job_template_labels', + multiple: true, + addNew: true, + opts: opts + }); + + $scope.workflow_job_template_obj = workflowJobTemplateData; + $scope.name = workflowJobTemplateData.name; + $scope.can_edit = workflowJobTemplateData.summary_fields.user_capabilities.edit; + let fld, i; + for (fld in form.fields) { + if (fld !== 'variables' && fld !== 'survey' && workflowJobTemplateData[fld] !== null && workflowJobTemplateData[fld] !== undefined) { + if (form.fields[fld].type === 'select') { + if ($scope[fld + '_options'] && $scope[fld + '_options'].length > 0) { + for (i = 0; i < $scope[fld + '_options'].length; i++) { + if (workflowJobTemplateData[fld] === $scope[fld + '_options'][i].value) { + $scope[fld] = $scope[fld + '_options'][i]; } - }); - }; - - Rest.setUrl(GetBasePath('workflow_job_templates') + id + - "/labels"); - Rest.get() - .success(function(data) { - if (data.next) { - getNext(data, data.results, seeMoreResolve); - } else { - seeMoreResolve.resolve(data.results); - } - - seeMoreResolve.promise.then(function (labels) { - $scope.$emit("choicesReady"); - var opts = labels - .map(i => ({id: i.id + "", - test: i.name})); - CreateSelect2({ - element:'#workflow_job_template_labels', - multiple: true, - addNew: true, - opts: opts - }); - Wait("stop"); - }); - }).error(function(){ - // job template id is null in this case - $scope.$emit("choicesReady"); - }); - - }) - .error(function (data, status) { - ProcessErrors($scope, data, status, form, { - hdr: 'Error!', - msg: 'Failed to get labels. GET returned ' + - 'status: ' + status - }); - }); - - // Go out and GET the workflow job temlate data needed to populate the form - TemplatesService.getWorkflowJobTemplate(id) - .then(function(data){ - let workflowJobTemplateData = data.data; - $scope.workflow_job_template_obj = workflowJobTemplateData; - $scope.name = workflowJobTemplateData.name; - $scope.can_edit = workflowJobTemplateData.summary_fields.user_capabilities.edit; - let fld, i; - for (fld in form.fields) { - if (fld !== 'variables' && fld !== 'survey' && workflowJobTemplateData[fld] !== null && workflowJobTemplateData[fld] !== undefined) { - if (form.fields[fld].type === 'select') { - if ($scope[fld + '_options'] && $scope[fld + '_options'].length > 0) { - for (i = 0; i < $scope[fld + '_options'].length; i++) { - if (workflowJobTemplateData[fld] === $scope[fld + '_options'][i].value) { - $scope[fld] = $scope[fld + '_options'][i]; - } - } - } else { - $scope[fld] = workflowJobTemplateData[fld]; } } else { $scope[fld] = workflowJobTemplateData[fld]; - if(!Empty(workflowJobTemplateData.summary_fields.survey)) { - $scope.survey_exists = true; - } + } + } else { + $scope[fld] = workflowJobTemplateData[fld]; + if(!Empty(workflowJobTemplateData.summary_fields.survey)) { + $scope.survey_exists = true; } } - if (fld === 'variables') { - // Parse extra_vars, converting to YAML. - $scope.variables = ParseVariableString(workflowJobTemplateData.extra_vars); - - ParseTypeChange({ scope: $scope, field_id: 'workflow_job_template_variables' }); - } - if (form.fields[fld].type === 'lookup' && workflowJobTemplateData.summary_fields[form.fields[fld].sourceModel]) { - $scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] = - workflowJobTemplateData.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField]; - } } + if (fld === 'variables') { + // Parse extra_vars, converting to YAML. + $scope.variables = ParseVariableString(workflowJobTemplateData.extra_vars); - if(workflowJobTemplateData.organization) { - OrgAdminLookup.checkForAdminAccess({organization: workflowJobTemplateData.organization}) - .then(function(canEditOrg){ - $scope.canEditOrg = canEditOrg; - }); + ParseTypeChange({ scope: $scope, field_id: 'workflow_job_template_variables' }); } - else { - $scope.canEditOrg = true; + if (form.fields[fld].type === 'lookup' && workflowJobTemplateData.summary_fields[form.fields[fld].sourceModel]) { + $scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] = + workflowJobTemplateData.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField]; } + } - Wait('stop'); - $scope.url = workflowJobTemplateData.url; - $scope.survey_enabled = workflowJobTemplateData.survey_enabled; - - $scope.includeWorkflowMaker = true; - - }, function(error){ - ProcessErrors($scope, error.data, error.status, form, { - hdr: 'Error!', - msg: 'Failed to get workflow job template. GET returned ' + - 'status: ' + error.status + if(workflowJobTemplateData.organization) { + OrgAdminLookup.checkForAdminAccess({organization: workflowJobTemplateData.organization}) + .then(function(canEditOrg){ + $scope.canEditOrg = canEditOrg; }); + } + else { + $scope.canEditOrg = true; + } + + $scope.url = workflowJobTemplateData.url; + $scope.survey_enabled = workflowJobTemplateData.survey_enabled; + + $scope.includeWorkflowMaker = true; + + $scope.$on('SurveySaved', function() { + Wait('stop'); + $scope.survey_exists = true; + $scope.invalid_survey = false; }); } @@ -337,15 +286,6 @@ export default [ }); }; - if ($scope.removeSurveySaved) { - $scope.removeSurveySaved(); - } - $scope.removeSurveySaved = $scope.$on('SurveySaved', function() { - Wait('stop'); - $scope.survey_exists = true; - $scope.invalid_survey = false; - }); - init(); } ]; diff --git a/awx/ui/tests/spec/workflows/workflow-add.controller-test.js b/awx/ui/tests/spec/workflows/workflow-add.controller-test.js index 697e093a67..7884f4c0a7 100644 --- a/awx/ui/tests/spec/workflows/workflow-add.controller-test.js +++ b/awx/ui/tests/spec/workflows/workflow-add.controller-test.js @@ -9,14 +9,14 @@ describe('Controller: WorkflowAdd', () => { GenerateForm, TemplatesService, q, - getLabelsDeferred, createWorkflowJobTemplateDeferred, httpBackend, ProcessErrors, CreateSelect2, Wait, ParseTypeChange, - ToJSON; + ToJSON, + availableLabels; beforeEach(angular.mock.module('Tower')); beforeEach(angular.mock.module('RestServices')); @@ -44,6 +44,11 @@ describe('Controller: WorkflowAdd', () => { } }; + availableLabels = [{ + name: "foo", + id: "1" + }]; + Alert = jasmine.createSpy('Alert'); ProcessErrors = jasmine.createSpy('ProcessErrors'); CreateSelect2 = jasmine.createSpy('CreateSelect2'); @@ -59,9 +64,10 @@ describe('Controller: WorkflowAdd', () => { $provide.value('Wait', Wait); $provide.value('ParseTypeChange', ParseTypeChange); $provide.value('ToJSON', ToJSON); + $provide.value('availableLabels', availableLabels); })); - beforeEach(angular.mock.inject( ($rootScope, $controller, $q, $httpBackend, _state_, _ConfigService_, _GetChoices_, _Alert_, _GenerateForm_, _ProcessErrors_, _CreateSelect2_, _Wait_, _ParseTypeChange_, _ToJSON_) => { + beforeEach(angular.mock.inject( ($rootScope, $controller, $q, $httpBackend, _state_, _ConfigService_, _GetChoices_, _Alert_, _GenerateForm_, _ProcessErrors_, _CreateSelect2_, _Wait_, _ParseTypeChange_, _ToJSON_, _availableLabels_) => { scope = $rootScope.$new(); state = _state_; q = $q; @@ -71,10 +77,10 @@ describe('Controller: WorkflowAdd', () => { ProcessErrors = _ProcessErrors_; CreateSelect2 = _CreateSelect2_; Wait = _Wait_; - getLabelsDeferred = q.defer(); createWorkflowJobTemplateDeferred = q.defer(); ParseTypeChange = _ParseTypeChange_; ToJSON = _ToJSON_; + availableLabels = _availableLabels_; $httpBackend .whenGET(/^\/api\/?$/) @@ -84,7 +90,6 @@ describe('Controller: WorkflowAdd', () => { .whenGET(/\/static\/*/) .respond(200, {}); - TemplatesService.getLabelOptions = jasmine.createSpy('getLabelOptions').and.returnValue(getLabelsDeferred.promise); TemplatesService.createWorkflowJobTemplate = jasmine.createSpy('createWorkflowJobTemplate').and.returnValue(createWorkflowJobTemplateDeferred.promise); WorkflowAdd = $controller('WorkflowAdd', { @@ -97,21 +102,19 @@ describe('Controller: WorkflowAdd', () => { CreateSelect2: CreateSelect2, Wait: Wait, ParseTypeChange: ParseTypeChange, + availableLabels: availableLabels, ToJSON }); })); it('should get/set the label options and select2-ify the input', ()=>{ - // Resolve TemplatesService.getLabelsForJobTemplate - getLabelsDeferred.resolve({ - foo: "bar" - }); // We expect the digest cycle to fire off this call to /static/config.js so we go ahead and handle it httpBackend.expectGET('/static/config.js').respond(200); scope.$digest(); - expect(scope.labelOptions).toEqual({ - foo: "bar" - }); + expect(scope.labelOptions).toEqual([{ + label: "foo", + value: "1" + }]); expect(CreateSelect2).toHaveBeenCalledWith({ element:'#workflow_job_template_labels', multiple: true, @@ -119,18 +122,6 @@ describe('Controller: WorkflowAdd', () => { }); }); - it('should call ProcessErrors when getLabelsForJobTemplate returns a rejected promise', ()=>{ - // Reject TemplatesService.getLabelsForJobTemplate - getLabelsDeferred.reject({ - data: "mockedData", - status: 400 - }); - // We expect the digest cycle to fire off this call to /static/config.js so we go ahead and handle it - httpBackend.expectGET('/static/config.js').respond(200); - scope.$digest(); - expect(ProcessErrors).toHaveBeenCalled(); - }); - describe('scope.formSave()', () => { it('should call TemplatesService.createWorkflowJobTemplate', ()=>{