From 089f369cc453df4e802df23b06b4027fa28761ed Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Wed, 30 Nov 2016 16:54:06 -0500 Subject: [PATCH 1/3] Implemented WFJT copy and moved JT copy away from a viewless route --- awx/ui/client/src/forms/Users.js | 22 ++-- awx/ui/client/src/lists/Templates.js | 2 +- awx/ui/client/src/shared/modal/modal.less | 15 +++ awx/ui/client/src/shared/prompt-dialog.js | 2 +- .../template-copy.service.js} | 18 +++- .../job-templates-copy.controller.js | 36 ------- .../job-templates-copy.route.js | 12 --- .../job_templates/copy-job-template/main.js | 17 --- .../list/templates-list.controller.js | 100 +++++++++++++++++- awx/ui/client/src/templates/main.js | 2 + .../client/src/templates/templates.service.js | 16 +++ 11 files changed, 161 insertions(+), 81 deletions(-) rename awx/ui/client/src/templates/{job_templates/copy-job-template/job-templates-copy.service.js => copy-template/template-copy.service.js} (82%) delete mode 100644 awx/ui/client/src/templates/job_templates/copy-job-template/job-templates-copy.controller.js delete mode 100644 awx/ui/client/src/templates/job_templates/copy-job-template/job-templates-copy.route.js delete mode 100644 awx/ui/client/src/templates/job_templates/copy-job-template/main.js diff --git a/awx/ui/client/src/forms/Users.js b/awx/ui/client/src/forms/Users.js index d96dd5b11b..5e95b6166f 100644 --- a/awx/ui/client/src/forms/Users.js +++ b/awx/ui/client/src/forms/Users.js @@ -27,21 +27,21 @@ export default first_name: { label: i18n._('First Name'), type: 'text', - ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || !canAdd)', + ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)', required: true, capitalize: true }, last_name: { label: i18n._('Last Name'), type: 'text', - ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || !canAdd)', + ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)', required: true, capitalize: true }, email: { label: i18n._('Email'), type: 'email', - ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || !canAdd)', + ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)', required: true, autocomplete: false }, @@ -53,7 +53,7 @@ export default init: true }, autocomplete: false, - ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || !canAdd)' + ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)' }, organization: { label: i18n._('Organization'), @@ -64,7 +64,7 @@ export default sourceField: 'name', required: true, excludeMode: 'edit', - ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || !canAdd)' + ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)' }, password: { label: i18n._('Password'), @@ -76,7 +76,7 @@ export default ngChange: "clearPWConfirm('password_confirm')", autocomplete: false, chkPass: true, - ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || !canAdd)' + ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)' }, password_confirm: { label: i18n._('Confirm Password'), @@ -88,7 +88,7 @@ export default awPassMatch: true, associated: 'password', autocomplete: false, - ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || !canAdd)' + ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)' }, user_type: { label: i18n._('User Type'), @@ -97,23 +97,23 @@ export default disableChooseOption: true, ngModel: 'user_type', ngShow: 'current_user["is_superuser"]', - ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || !canAdd)' + ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)' }, }, buttons: { cancel: { ngClick: 'formCancel()', - ngShow: '(user_obj.summary_fields.user_capabilities.edit || !canAdd)' + ngShow: '(user_obj.summary_fields.user_capabilities.edit || canAdd)' }, close: { ngClick: 'formCancel()', - ngShow: '!(user_obj.summary_fields.user_capabilities.edit || !canAdd)' + ngShow: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)' }, save: { ngClick: 'formSave()', ngDisabled: true, - ngShow: '(user_obj.summary_fields.user_capabilities.edit || !canAdd)' + ngShow: '(user_obj.summary_fields.user_capabilities.edit || canAdd)' } }, diff --git a/awx/ui/client/src/lists/Templates.js b/awx/ui/client/src/lists/Templates.js index 6a9aea0658..10a195f795 100644 --- a/awx/ui/client/src/lists/Templates.js +++ b/awx/ui/client/src/lists/Templates.js @@ -98,7 +98,7 @@ export default }, copy: { label: i18n._('Copy'), - 'ui-sref': 'templates.copy({id: template.id})', + ngClick: 'copyTemplate(template)', "class": 'btn-danger btn-xs', awToolTip: i18n._('Copy template'), dataPlacement: 'top', diff --git a/awx/ui/client/src/shared/modal/modal.less b/awx/ui/client/src/shared/modal/modal.less index b7288d0940..da254e07f6 100644 --- a/awx/ui/client/src/shared/modal/modal.less +++ b/awx/ui/client/src/shared/modal/modal.less @@ -99,6 +99,21 @@ color: @btn-txt-sel; } +.Modal-primaryButton { + background-color: @default-link; + color: @default-bg; + text-transform: uppercase; + border-radius: 5px; + transition: background-color 0.2s; + padding-left:15px; + padding-right: 15px; +} + +.Modal-primaryButton:hover { + background-color: @default-link-hov; + color: @default-bg; +} + .Modal-errorButton:focus { color: @btn-txt-sel; } diff --git a/awx/ui/client/src/shared/prompt-dialog.js b/awx/ui/client/src/shared/prompt-dialog.js index 45f92c8bc7..f0c7794e47 100644 --- a/awx/ui/client/src/shared/prompt-dialog.js +++ b/awx/ui/client/src/shared/prompt-dialog.js @@ -47,7 +47,7 @@ angular.module('PromptDialog', ['Utilities', 'sanitizeFilter']) cls = (params['class'] === null || params['class'] === undefined) ? 'Modal-errorButton' : params['class']; - $('#prompt_action_btn').removeClass(cls).addClass(cls); + $('#prompt_action_btn').removeClass('Modal-errorButton Modal-primaryButton').addClass(cls); // bootstrap modal's have an open defect with disallowing tab index's of the background of the modal // This will keep the tab indexing on the modal's focus. This is to fix an issue with tabbing working when diff --git a/awx/ui/client/src/templates/job_templates/copy-job-template/job-templates-copy.service.js b/awx/ui/client/src/templates/copy-template/template-copy.service.js similarity index 82% rename from awx/ui/client/src/templates/job_templates/copy-job-template/job-templates-copy.service.js rename to awx/ui/client/src/templates/copy-template/template-copy.service.js index a6f937cd58..931fd12423 100644 --- a/awx/ui/client/src/templates/job_templates/copy-job-template/job-templates-copy.service.js +++ b/awx/ui/client/src/templates/copy-template/template-copy.service.js @@ -39,7 +39,7 @@ return Rest.post(data.results[0]) .success(function(job_template_res){ // also copy any associated survey_spec - if (data.results[0].related.survey_spec){ + if (data.results[0].summary_fields.survey){ return self.copySurvey(data.results[0], job_template_res).success( () => job_template_res); } else{ @@ -54,6 +54,22 @@ buildName: function(name){ var result = name.split('@')[0]; return result; + }, + getWorkflowCopy: function(id) { + let url = GetBasePath('workflow_job_templates'); + + url = url + id + '/copy'; + + Rest.setUrl(url); + return Rest.get(); + }, + copyWorkflow: function(id) { + let url = GetBasePath('workflow_job_templates'); + + url = url + id + '/copy'; + + Rest.setUrl(url); + return Rest.post(); } }; } diff --git a/awx/ui/client/src/templates/job_templates/copy-job-template/job-templates-copy.controller.js b/awx/ui/client/src/templates/job_templates/copy-job-template/job-templates-copy.controller.js deleted file mode 100644 index c82f412bc5..0000000000 --- a/awx/ui/client/src/templates/job_templates/copy-job-template/job-templates-copy.controller.js +++ /dev/null @@ -1,36 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - export default - [ 'Wait', '$state', '$scope', 'jobTemplateCopyService', - 'ProcessErrors', '$rootScope', - function(Wait, $state, $scope, jobTemplateCopyService, - ProcessErrors, $rootScope){ - // GETs the job_template to copy - // POSTs a new job_template - // routes to JobTemplates.edit when finished - var init = function(){ - Wait('start'); - jobTemplateCopyService.get($state.params.id) - .success(function(res){ - jobTemplateCopyService.set(res) - .success(function(res){ - Wait('stop'); - if(res.type && res.type === 'job_template') { - $state.go('templates.editJobTemplate', {id: res.id}, {reload: true}); - } - // Workflow edit to be implemented post 3.1 but we'll need to handle the - // state transition for that here - }); - }) - .error(function(res, status){ - ProcessErrors($rootScope, res, status, null, {hdr: 'Error!', - msg: 'Call failed. Return status: '+ status}); - }); - }; - init(); - } - ]; diff --git a/awx/ui/client/src/templates/job_templates/copy-job-template/job-templates-copy.route.js b/awx/ui/client/src/templates/job_templates/copy-job-template/job-templates-copy.route.js deleted file mode 100644 index d5b9d5e7e3..0000000000 --- a/awx/ui/client/src/templates/job_templates/copy-job-template/job-templates-copy.route.js +++ /dev/null @@ -1,12 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - -export default { - name: 'templates.copy', - route: '/:id/copy', - controller: 'jobTemplateCopyController' -}; diff --git a/awx/ui/client/src/templates/job_templates/copy-job-template/main.js b/awx/ui/client/src/templates/job_templates/copy-job-template/main.js deleted file mode 100644 index 1c6cda958a..0000000000 --- a/awx/ui/client/src/templates/job_templates/copy-job-template/main.js +++ /dev/null @@ -1,17 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import controller from './job-templates-copy.controller'; -import route from './job-templates-copy.route'; -import service from './job-templates-copy.service'; - -export default - angular.module('templates.copy', []) - .service('jobTemplateCopyService', service) - .controller('jobTemplateCopyController', controller) - .run(['$stateExtender', function($stateExtender) { - $stateExtender.addState(route); - }]); diff --git a/awx/ui/client/src/templates/list/templates-list.controller.js b/awx/ui/client/src/templates/list/templates-list.controller.js index 1cdb4d83eb..f984dd57e3 100644 --- a/awx/ui/client/src/templates/list/templates-list.controller.js +++ b/awx/ui/client/src/templates/list/templates-list.controller.js @@ -7,12 +7,12 @@ export default ['$scope', '$rootScope', '$location', '$stateParams', 'Rest', 'Alert', 'TemplateList', 'Prompt', 'ClearScope', 'ProcessErrors', 'GetBasePath', 'InitiatePlaybookRun', 'Wait', '$state', '$filter', 'Dataset', 'rbacUiControlService', 'TemplatesService', - 'QuerySet', 'GetChoices', + 'QuerySet', 'GetChoices', 'TemplateCopyService', function( $scope, $rootScope, $location, $stateParams, Rest, Alert, TemplateList, Prompt, ClearScope, ProcessErrors, GetBasePath, InitiatePlaybookRun, Wait, $state, $filter, Dataset, rbacUiControlService, TemplatesService, - qs, GetChoices + qs, GetChoices, TemplateCopyService ) { ClearScope(); @@ -192,5 +192,101 @@ export default ['$scope', '$rootScope', '$location', '$stateParams', 'Rest', 'Al Alert('Error: Unable to schedule job', 'Template parameter is missing'); } }; + + $scope.copyTemplate = function(template) { + if(template) { + if(template.type && template.type === 'job_template') { + Wait('start'); + TemplateCopyService.get(template.id) + .success(function(res){ + TemplateCopyService.set(res) + .success(function(res){ + Wait('stop'); + if(res.type && res.type === 'job_template') { + $state.go('templates.editJobTemplate', {job_template_id: res.id}, {reload: true}); + } + }); + }) + .error(function(res, status){ + ProcessErrors($rootScope, res, status, null, {hdr: 'Error!', + msg: 'Call failed. Return status: '+ status}); + }); + } + else if(template.type && template.type === 'workflow_job_template') { + TemplateCopyService.getWorkflowCopy(template.id) + .then(function(result) { + + if(result.data.can_copy) { + if(!result.data.warnings || _.isEmpty(result.data.warnings)) { + // Go ahead and copy the workflow - the user has full priveleges on all the resources + TemplateCopyService.copyWorkflow(template.id) + .then(function(result) { + $state.go('templates.editWorkflowJobTemplate', {workflow_job_template_id: result.data.id}, {reload: true}); + }, function (data) { + Wait('stop'); + ProcessErrors($scope, data, status, null, { hdr: 'Error!', + msg: 'Call to copy template failed. POST returned status: ' + status }); + }); + } + else { + + let bodyHtml = ` +
+ You may not have access to all resources used by this workflow. Resources that you don\'t have access to will not be copied and may result in an incomplete workflow. +
+
`; + + // Go and grab all of the warning strings + _.forOwn(result.data.warnings, function(warning) { + if(warning) { + _.forOwn(warning, function(warningString) { + bodyHtml += '
' + warningString + '
'; + }); + } + } ); + + bodyHtml += '
'; + + + Prompt({ + hdr: 'Copy Workflow', + body: bodyHtml, + action: function() { + $('#prompt-modal').modal('hide'); + Wait('start'); + TemplateCopyService.copyWorkflow(template.id) + .then(function(result) { + Wait('stop'); + $state.go('templates.editWorkflowJobTemplate', {workflow_job_template_id: result.data.id}, {reload: true}); + }, function (data) { + Wait('stop'); + ProcessErrors($scope, data, status, null, { hdr: 'Error!', + msg: 'Call to copy template failed. POST returned status: ' + status }); + }); + }, + actionText: 'COPY', + class: 'Modal-primaryButton' + }); + } + } + else { + Alert('Error: Unable to copy workflow job template', 'You do not have permission to perform this action.'); + } + }, function (data) { + Wait('stop'); + ProcessErrors($scope, data, status, null, { hdr: 'Error!', + msg: 'Call to copy template failed. GET returned status: ' + status }); + }); + } + else { + // Something went wrong - Let the user know that we're unable to copy 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 copying.'); + } + } + else { + Alert('Error: Unable to copy job', 'Template parameter is missing'); + } + }; } ]; diff --git a/awx/ui/client/src/templates/main.js b/awx/ui/client/src/templates/main.js index 5e2591b3a2..b33e34b540 100644 --- a/awx/ui/client/src/templates/main.js +++ b/awx/ui/client/src/templates/main.js @@ -17,6 +17,7 @@ import workflowChart from './workflows/workflow-chart/main'; import workflowMaker from './workflows/workflow-maker/main'; import templatesListRoute from './list/templates-list.route'; import workflowService from './workflows/workflow.service'; +import templateCopyService from './copy-template/templay-copy.service'; export default angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplatesAdd.name, @@ -25,6 +26,7 @@ angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplatesA ]) .service('TemplatesService', templatesService) .service('WorkflowService', workflowService) + .service('TemplateCopyService', templateCopyService) .config(['$stateProvider', 'stateDefinitionsProvider', '$stateExtenderProvider', function($stateProvider, stateDefinitionsProvider, $stateExtenderProvider) { let stateTree, addJobTemplate, editJobTemplate, addWorkflow, editWorkflow, diff --git a/awx/ui/client/src/templates/templates.service.js b/awx/ui/client/src/templates/templates.service.js index b6205df4c3..a26e45f962 100644 --- a/awx/ui/client/src/templates/templates.service.js +++ b/awx/ui/client/src/templates/templates.service.js @@ -185,6 +185,22 @@ export default ['Rest', 'GetBasePath', '$q', function(Rest, GetBasePath, $q){ Rest.setUrl(url); return Rest.get(); + }, + getWorkflowCopy: function(id) { + let url = GetBasePath('workflow_job_templates'); + + url = url + id + '/copy'; + + Rest.setUrl(url); + return Rest.get(); + }, + copyWorkflow: function(id) { + let url = GetBasePath('workflow_job_templates'); + + url = url + id + '/copy'; + + Rest.setUrl(url); + return Rest.post(); } }; }]; From 50691b77db4817768e76b79b0a7c3797c1a83ad7 Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Wed, 30 Nov 2016 17:00:18 -0500 Subject: [PATCH 2/3] Typo's and leftover injections that are no longer used. --- awx/ui/client/src/templates/main.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/awx/ui/client/src/templates/main.js b/awx/ui/client/src/templates/main.js index b33e34b540..7bc7dab18b 100644 --- a/awx/ui/client/src/templates/main.js +++ b/awx/ui/client/src/templates/main.js @@ -9,7 +9,6 @@ import surveyMaker from './survey-maker/main'; import templatesList from './list/main'; import jobTemplatesAdd from './job_templates/add-job-template/main'; import jobTemplatesEdit from './job_templates/edit-job-template/main'; -import jobTemplatesCopy from './job_templates/copy-job-template/main'; import workflowAdd from './workflows/add-workflow/main'; import workflowEdit from './workflows/edit-workflow/main'; import labels from './labels/main'; @@ -17,11 +16,11 @@ import workflowChart from './workflows/workflow-chart/main'; import workflowMaker from './workflows/workflow-maker/main'; import templatesListRoute from './list/templates-list.route'; import workflowService from './workflows/workflow.service'; -import templateCopyService from './copy-template/templay-copy.service'; +import templateCopyService from './copy-template/template-copy.service'; export default angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplatesAdd.name, - jobTemplatesEdit.name, jobTemplatesCopy.name, labels.name, workflowAdd.name, workflowEdit.name, + jobTemplatesEdit.name, labels.name, workflowAdd.name, workflowEdit.name, workflowChart.name, workflowMaker.name ]) .service('TemplatesService', templatesService) From be3ec6d8d8106ae458e860673d294cde7bd92124 Mon Sep 17 00:00:00 2001 From: jaredevantabor Date: Wed, 30 Nov 2016 12:34:28 -0800 Subject: [PATCH 3/3] making jerkins happy --- .../src/job-results/job-results.controller.js | 21 +++++++++++-------- .../job-results.controller-test.js | 2 -- 2 files changed, 12 insertions(+), 11 deletions(-) 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 be4cc4bd41..dfef8fca10 100644 --- a/awx/ui/client/src/job-results/job-results.controller.js +++ b/awx/ui/client/src/job-results/job-results.controller.js @@ -9,21 +9,12 @@ export default ['jobData', 'jobDataOptions', 'jobLabels', 'jobFinished', 'count' } }; - $scope.job_template_link = `/#/templates/job_template/${$scope.job.summary_fields.job_template.id}`; $scope.created_by_link = getTowerLink('created_by'); $scope.inventory_link = getTowerLink('inventory'); $scope.project_link = getTowerLink('project'); $scope.machine_credential_link = getTowerLink('credential'); $scope.cloud_credential_link = getTowerLink('cloud_credential'); $scope.network_credential_link = getTowerLink('network_credential'); - if(jobData.summary_fields && jobData.summary_fields.project_update && - jobData.summary_fields.project_update.status){ - $scope.project_status = jobData.summary_fields.project_update.status; - } - if(jobData.summary_fields && jobData.summary_fields.project_update && - jobData.summary_fields.project_update.id){ - $scope.project_update_link = `/#/scm_update/${jobData.summary_fields.project_update.id}`; - } }; // uses options to set scope variables to their readable string @@ -57,6 +48,18 @@ export default ['jobData', 'jobDataOptions', 'jobLabels', 'jobFinished', 'count' // turn related api browser routes into tower routes getTowerLinks(); + if(jobData.summary_fields && jobData.summary_fields.job_template && + jobData.summary_fields.job_template.id){ + $scope.job_template_link = `/#/templates/job_template/${$scope.job.summary_fields.job_template.id}`; + } + if(jobData.summary_fields && jobData.summary_fields.project_update && + jobData.summary_fields.project_update.status){ + $scope.project_status = jobData.summary_fields.project_update.status; + } + if(jobData.summary_fields && jobData.summary_fields.project_update && + jobData.summary_fields.project_update.id){ + $scope.project_update_link = `/#/scm_update/${jobData.summary_fields.project_update.id}`; + } // use options labels to manipulate display of details getTowerLabels(); diff --git a/awx/ui/tests/spec/job-results/job-results.controller-test.js b/awx/ui/tests/spec/job-results/job-results.controller-test.js index d3b4405adf..5114bf64c8 100644 --- a/awx/ui/tests/spec/job-results/job-results.controller-test.js +++ b/awx/ui/tests/spec/job-results/job-results.controller-test.js @@ -130,7 +130,6 @@ describe('Controller: jobResultsController', () => { describe('getTowerLinks()', () => { beforeEach(() => { jobData.related = { - "job_template": "api/v1/job_templates/12", "created_by": "api/v1/users/12", "inventory": "api/v1/inventories/12", "project": "api/v1/projects/12", @@ -143,7 +142,6 @@ describe('Controller: jobResultsController', () => { }); it('should transform related links and set to scope var', () => { - expect($scope.job_template_link).toBe('/#/job_templates/12'); expect($scope.created_by_link).toBe('/#/users/12'); expect($scope.inventory_link).toBe('/#/inventories/12'); expect($scope.project_link).toBe('/#/projects/12');