From cc8b5bc80836f0c7f0299f2edaa8deb158fe9260 Mon Sep 17 00:00:00 2001 From: Jake McDermott Date: Fri, 10 Nov 2017 18:08:41 -0500 Subject: [PATCH 1/7] add file input component --- .../lib/components/components.strings.js | 4 + awx/ui/client/lib/components/index.js | 2 + .../client/lib/components/input/_index.less | 4 + .../lib/components/input/file.directive.js | 94 +++++++++++++++++++ .../lib/components/input/file.partial.html | 25 +++++ .../lib/components/input/group.directive.js | 2 + awx/ui/client/lib/theme/_variables.less | 2 + 7 files changed, 133 insertions(+) create mode 100644 awx/ui/client/lib/components/input/file.directive.js create mode 100644 awx/ui/client/lib/components/input/file.partial.html diff --git a/awx/ui/client/lib/components/components.strings.js b/awx/ui/client/lib/components/components.strings.js index db27701a46..cd4a573499 100644 --- a/awx/ui/client/lib/components/components.strings.js +++ b/awx/ui/client/lib/components/components.strings.js @@ -16,6 +16,10 @@ function ComponentsStrings (BaseString) { INVALID_INPUT: t.s('Invalid input for this type.') }; + ns.file = { + PLACEHOLDER: t.s('CHOOSE A FILE') + }; + ns.form = { SUBMISSION_ERROR_TITLE: t.s('Unable to Submit'), SUBMISSION_ERROR_MESSAGE: t.s('Unexpected server error. View the console for more information'), diff --git a/awx/ui/client/lib/components/index.js b/awx/ui/client/lib/components/index.js index 426b9244cd..3033812739 100644 --- a/awx/ui/client/lib/components/index.js +++ b/awx/ui/client/lib/components/index.js @@ -5,6 +5,7 @@ import divider from '~components/utility/divider.directive'; import form from '~components/form/form.directive'; import formAction from '~components/form/action.directive'; import inputCheckbox from '~components/input/checkbox.directive'; +import inputFile from '~components/input/file.directive'; import inputGroup from '~components/input/group.directive'; import inputLabel from '~components/input/label.directive'; import inputLookup from '~components/input/lookup.directive'; @@ -41,6 +42,7 @@ angular .directive('atForm', form) .directive('atFormAction', formAction) .directive('atInputCheckbox', inputCheckbox) + .directive('atInputFile', inputFile) .directive('atInputGroup', inputGroup) .directive('atInputLabel', inputLabel) .directive('atInputLookup', inputLookup) diff --git a/awx/ui/client/lib/components/input/_index.less b/awx/ui/client/lib/components/input/_index.less index 087eaddfcb..b92692c34e 100644 --- a/awx/ui/client/lib/components/input/_index.less +++ b/awx/ui/client/lib/components/input/_index.less @@ -15,6 +15,10 @@ border-color: @at-color-input-focus; } + &[readonly] { + background: @at-color-input-readonly; + } + &[disabled] { background: @at-color-input-disabled; } diff --git a/awx/ui/client/lib/components/input/file.directive.js b/awx/ui/client/lib/components/input/file.directive.js new file mode 100644 index 0000000000..bdc7b8ecde --- /dev/null +++ b/awx/ui/client/lib/components/input/file.directive.js @@ -0,0 +1,94 @@ +const templateUrl = require('~components/input/file.partial.html'); + +function atInputFileLink (scope, element, attrs, controllers) { + const formController = controllers[0]; + const inputController = controllers[1]; + + if (scope.tab === '1') { + element.find('input')[0].focus(); + } + + inputController.init(scope, element, formController); +} + +function AtInputFileController (baseInputController, eventService) { + const vm = this || {}; + + let input; + let scope; + + vm.init = (_scope_, element, form) => { + baseInputController.call(vm, 'input', _scope_, element, form); + + scope = _scope_; + [input] = element.find('input'); + + vm.listeners = vm.setFileListeners(input); + + vm.check(); + }; + + vm.onButtonClick = () => { + if (scope.state._value) { + vm.removeFile(); + } else { + input.click(); + } + }; + + vm.setFileListeners = inputEl => eventService.addListeners([ + [inputEl, 'change', event => vm.handleFileChangeEvent(inputEl, event)] + ]); + + vm.handleFileChangeEvent = (element, event) => { + if (element.files.length > 0) { + const reader = new FileReader(); + + reader.onload = () => vm.readFile(reader, event); + reader.readAsText(element.files[0]); + } else { + scope.$apply(vm.removeFile); + } + }; + + vm.readFile = (reader, event) => { + scope.$apply(() => { + scope.state._value = reader.result; + scope.state._displayValue = event.target.files[0].name; + + vm.check(); + }); + }; + + vm.removeFile = () => { + delete scope.state._value; + delete scope.state._displayValue; + + input.value = ''; + }; +} + +AtInputFileController.$inject = [ + 'BaseInputController', + 'EventService' +]; + +function atInputFile () { + return { + restrict: 'E', + transclude: true, + replace: true, + require: ['^^atForm', 'atInputFile'], + templateUrl, + controller: AtInputFileController, + controllerAs: 'vm', + link: atInputFileLink, + scope: { + state: '=', + col: '@', + tab: '@' + } + }; +} + +export default atInputFile; diff --git a/awx/ui/client/lib/components/input/file.partial.html b/awx/ui/client/lib/components/input/file.partial.html new file mode 100644 index 0000000000..7e13fcc47e --- /dev/null +++ b/awx/ui/client/lib/components/input/file.partial.html @@ -0,0 +1,25 @@ +
+
+ + +
+ + + + +
+ +
+
diff --git a/awx/ui/client/lib/components/input/group.directive.js b/awx/ui/client/lib/components/input/group.directive.js index c94dd99f04..7e6f1f2426 100644 --- a/awx/ui/client/lib/components/input/group.directive.js +++ b/awx/ui/client/lib/components/input/group.directive.js @@ -99,6 +99,8 @@ function AtInputGroupController ($scope, $compile) { config._component = 'at-input-number'; } else if (input.type === 'boolean') { config._component = 'at-input-checkbox'; + } else if (input.type === 'file') { + config._component = 'at-input-file'; } else if (input.choices) { config._component = 'at-input-select'; config._format = 'array'; diff --git a/awx/ui/client/lib/theme/_variables.less b/awx/ui/client/lib/theme/_variables.less index 5bdbec8fda..2ff0156021 100644 --- a/awx/ui/client/lib/theme/_variables.less +++ b/awx/ui/client/lib/theme/_variables.less @@ -131,6 +131,8 @@ @at-color-input-button: @at-gray-light-3x; @at-color-input-button-hover: @at-gray-light-2x; @at-color-input-disabled: @at-gray-light; +@at-color-input-readonly: @at-color-input-background; + @at-color-input-error: @at-color-error; @at-color-input-focus: @at-color-info; @at-color-input-hint: @at-gray-dark-4x; From 5baa371739616eaf01e0ca1068ad68797314f120 Mon Sep 17 00:00:00 2001 From: Jake McDermott Date: Mon, 13 Nov 2017 20:31:32 -0500 Subject: [PATCH 2/7] add unit test for file input component --- .../lib/components/input/file.directive.js | 2 +- .../lib/components/modal/modal.directive.js | 4 +- awx/ui/test/unit/components/file.unit.js | 61 +++++++++++++++++++ awx/ui/test/unit/components/index.js | 1 + 4 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 awx/ui/test/unit/components/file.unit.js diff --git a/awx/ui/client/lib/components/input/file.directive.js b/awx/ui/client/lib/components/input/file.directive.js index bdc7b8ecde..42aa2c4fba 100644 --- a/awx/ui/client/lib/components/input/file.directive.js +++ b/awx/ui/client/lib/components/input/file.directive.js @@ -21,7 +21,7 @@ function AtInputFileController (baseInputController, eventService) { baseInputController.call(vm, 'input', _scope_, element, form); scope = _scope_; - [input] = element.find('input'); + input = element.find('input')[0]; // eslint-disable-line prefer-destructuring vm.listeners = vm.setFileListeners(input); diff --git a/awx/ui/client/lib/components/modal/modal.directive.js b/awx/ui/client/lib/components/modal/modal.directive.js index f6e5510ce5..e1d1126121 100644 --- a/awx/ui/client/lib/components/modal/modal.directive.js +++ b/awx/ui/client/lib/components/modal/modal.directive.js @@ -22,8 +22,8 @@ function AtModalController (eventService, strings) { vm.strings = strings; vm.init = (scope, el) => { - [overlay] = el; - [modal] = el.find('.at-Modal-window'); + overlay = el[0]; // eslint-disable-line prefer-destructuring + modal = el.find('.at-Modal-window')[0]; // eslint-disable-line prefer-destructuring vm.modal = scope[scope.ns].modal; vm.modal.show = vm.show; diff --git a/awx/ui/test/unit/components/file.unit.js b/awx/ui/test/unit/components/file.unit.js new file mode 100644 index 0000000000..1bf7041b56 --- /dev/null +++ b/awx/ui/test/unit/components/file.unit.js @@ -0,0 +1,61 @@ +describe('Components | Input | File', () => { + let $scope; + let element; + let state; + let controller; + + const getMockFileEvent = file => ({ target: { files: [file] } }); + + beforeEach(() => { + angular.mock.module('at.lib.services'); + angular.mock.module('at.lib.components'); + }); + + describe('AtInputFileController', () => { + beforeEach(angular.mock.inject(($rootScope, $compile) => { + const component = ''; + const dom = angular.element(`${component}`); + + $scope = $rootScope.$new(); + $scope.vm = { form: { disabled: false, unit: {} } }; + + $compile(dom)($scope); + $scope.$digest(); + + element = dom.find('#unit'); + state = $scope.vm.form.unit; + controller = element.controller('atInputFile'); + })); + + it('should initialize without a value by default', () => { + expect(state._value).not.toBeDefined(); + expect(state._displayValue).not.toBeDefined(); + }); + + it('should update display value with file name when file is read', () => { + const name = 'notavirus.exe'; + const reader = { result: 'AAAAAAA' }; + + controller.check = jasmine.createSpy('check'); + + controller.readFile(reader, getMockFileEvent({ name })); + + $scope.$digest(); + + expect(state._value).toBeDefined(); + expect(state._displayValue).toEqual(name); + + expect(controller.check).toHaveBeenCalled(); + }); + + it('should notify handler on file input change event', () => { + controller.handleFileChangeEvent = jasmine.createSpy('handleFileChangeEvent'); + + element.find('input')[0].dispatchEvent(new Event('change')); + + $scope.$digest(); + + expect(controller.handleFileChangeEvent).toHaveBeenCalled(); + }); + }); +}); diff --git a/awx/ui/test/unit/components/index.js b/awx/ui/test/unit/components/index.js index 398445bc6f..8d75e3cf71 100644 --- a/awx/ui/test/unit/components/index.js +++ b/awx/ui/test/unit/components/index.js @@ -2,6 +2,7 @@ import 'angular-mocks'; // Import tests +import './file.unit'; import './layout.unit'; import './side-nav.unit'; import './side-nav-item.unit'; From 71fea2e36024393bbf031bc31d54429c1fbc5162 Mon Sep 17 00:00:00 2001 From: Jake McDermott Date: Tue, 14 Nov 2017 21:36:40 -0500 Subject: [PATCH 3/7] allow for programmatic input to text and textarea-secret fields --- .../client/lib/components/form/form.directive.js | 5 +++++ .../lib/components/input/base.controller.js | 4 +--- .../client/lib/components/input/text.directive.js | 8 ++++++-- .../client/lib/components/input/text.partial.html | 1 - .../components/input/textarea-secret.directive.js | 15 ++++++++++++--- .../components/input/textarea-secret.partial.html | 3 +-- 6 files changed, 25 insertions(+), 11 deletions(-) diff --git a/awx/ui/client/lib/components/form/form.directive.js b/awx/ui/client/lib/components/form/form.directive.js index 17b781debc..e2e2e746a4 100644 --- a/awx/ui/client/lib/components/form/form.directive.js +++ b/awx/ui/client/lib/components/form/form.directive.js @@ -38,6 +38,10 @@ function AtFormController (eventService, strings) { component.category = category; component.form = vm.state; + if (category === 'input') { + scope.state[component.state.id] = component.state; + } + vm.components.push(component); }; @@ -189,6 +193,7 @@ function AtFormController (eventService, strings) { for (let j = 0; j < vm.components.length; j++) { if (components[i] === vm.components[j].state) { vm.components.splice(j, 1); + delete scope.state[components[i].id]; break; } } diff --git a/awx/ui/client/lib/components/input/base.controller.js b/awx/ui/client/lib/components/input/base.controller.js index 13fa825a5d..5c27d903f0 100644 --- a/awx/ui/client/lib/components/input/base.controller.js +++ b/awx/ui/client/lib/components/input/base.controller.js @@ -85,9 +85,7 @@ function BaseInputController (strings) { vm.updateValidationState(result); }; - vm.toggleRevertReplace = () => { - scope.state._isBeingReplaced = !scope.state._isBeingReplaced; - + vm.onRevertReplaceToggle = () => { if (!scope.state._isBeingReplaced) { scope.state._buttonText = vm.strings.get('REPLACE'); scope.state._disabled = true; diff --git a/awx/ui/client/lib/components/input/text.directive.js b/awx/ui/client/lib/components/input/text.directive.js index e8d514fc7e..d28f4c2640 100644 --- a/awx/ui/client/lib/components/input/text.directive.js +++ b/awx/ui/client/lib/components/input/text.directive.js @@ -14,10 +14,14 @@ function atInputTextLink (scope, element, attrs, controllers) { function AtInputTextController (baseInputController) { const vm = this || {}; - vm.init = (scope, element, form) => { - baseInputController.call(vm, 'input', scope, element, form); + let scope; + + vm.init = (_scope_, element, form) => { + baseInputController.call(vm, 'input', _scope_, element, form); + scope = _scope_; vm.check(); + scope.$watch('state._value', () => vm.check()); }; } diff --git a/awx/ui/client/lib/components/input/text.partial.html b/awx/ui/client/lib/components/input/text.partial.html index c5140df834..8a1a22137e 100644 --- a/awx/ui/client/lib/components/input/text.partial.html +++ b/awx/ui/client/lib/components/input/text.partial.html @@ -8,7 +8,6 @@ ng-attr-maxlength="{{ state.max_length || undefined }}" ng-attr-tabindex="{{ tab || undefined }}" ng-attr-placeholder="{{::state._placeholder || undefined }}" - ng-change="vm.check()" ng-disabled="state._disabled || form.disabled" /> diff --git a/awx/ui/client/lib/components/input/textarea-secret.directive.js b/awx/ui/client/lib/components/input/textarea-secret.directive.js index 7e4cd964d6..2bcbe027d5 100644 --- a/awx/ui/client/lib/components/input/textarea-secret.directive.js +++ b/awx/ui/client/lib/components/input/textarea-secret.directive.js @@ -21,6 +21,7 @@ function AtInputTextareaSecretController (baseInputController, eventService) { baseInputController.call(vm, 'input', _scope_, element, form); scope = _scope_; + [textarea] = element.find('textarea'); if (scope.state.format === 'ssh_private_key') { @@ -38,10 +39,15 @@ function AtInputTextareaSecretController (baseInputController, eventService) { } vm.check(); + + scope.$watch('state[state._activeModel]', () => vm.check()); + scope.$watch('state._isBeingReplaced', () => vm.onIsBeingReplacedChanged()); }; - vm.toggle = () => { - vm.toggleRevertReplace(); + vm.onIsBeingReplacedChanged = () => { + if (!scope.state._touched) return; + + vm.onRevertReplaceToggle(); if (scope.state._isBeingReplaced) { scope.state._placeholder = ''; @@ -50,7 +56,10 @@ function AtInputTextareaSecretController (baseInputController, eventService) { } else { scope.state._displayHint = false; scope.state._placeholder = vm.strings.get('ENCRYPTED'); - eventService.remove(vm.listeners); + + if (vm.listeners) { + eventService.remove(vm.listeners); + } } }; diff --git a/awx/ui/client/lib/components/input/textarea-secret.partial.html b/awx/ui/client/lib/components/input/textarea-secret.partial.html index d7823a1547..152da65c8f 100644 --- a/awx/ui/client/lib/components/input/textarea-secret.partial.html +++ b/awx/ui/client/lib/components/input/textarea-secret.partial.html @@ -6,7 +6,7 @@
@@ -22,7 +22,6 @@ ng-attr-maxlength="{{ state.max_length || undefined }}" ng-attr-tabindex="{{ tab || undefined }}" ng-attr-placeholder="{{state._placeholder || undefined }}" - ng-change="vm.check()" ng-disabled="state._disabled || form.disabled" /> From f15b1ae54943eb7e3852e1b3e9866b06299fab35 Mon Sep 17 00:00:00 2001 From: Jake McDermott Date: Wed, 15 Nov 2017 22:19:23 -0500 Subject: [PATCH 4/7] disable textarea drag and drop when field is disabled --- awx/ui/client/lib/components/input/textarea-secret.partial.html | 1 + 1 file changed, 1 insertion(+) diff --git a/awx/ui/client/lib/components/input/textarea-secret.partial.html b/awx/ui/client/lib/components/input/textarea-secret.partial.html index 152da65c8f..e716cbf48d 100644 --- a/awx/ui/client/lib/components/input/textarea-secret.partial.html +++ b/awx/ui/client/lib/components/input/textarea-secret.partial.html @@ -11,6 +11,7 @@ Date: Thu, 16 Nov 2017 16:40:32 -0500 Subject: [PATCH 5/7] add gcp service account file input --- .../credentials/add-credentials.controller.js | 70 +++++++++++++++- .../add-edit-credentials.view.html | 4 +- .../credentials/credentials.strings.js | 4 +- .../edit-credentials.controller.js | 79 ++++++++++++++++++- 4 files changed, 147 insertions(+), 10 deletions(-) diff --git a/awx/ui/client/features/credentials/add-credentials.controller.js b/awx/ui/client/features/credentials/add-credentials.controller.js index f0e4445669..5c9abcfbba 100644 --- a/awx/ui/client/features/credentials/add-credentials.controller.js +++ b/awx/ui/client/features/credentials/add-credentials.controller.js @@ -1,4 +1,4 @@ -function AddCredentialsController (models, $state, strings) { +function AddCredentialsController (models, $state, $scope, strings, componentsStrings) { const vm = this || {}; const { me, credential, credentialType, organization } = models; @@ -28,11 +28,27 @@ function AddCredentialsController (models, $state, strings) { vm.form.credential_type._model = credentialType; vm.form.credential_type._placeholder = strings.get('inputs.CREDENTIAL_TYPE_PLACEHOLDER'); + const gceFileInputSchema = { + id: 'gce_service_account_key', + type: 'file', + label: strings.get('inputs.GCE_FILE_INPUT_LABEL'), + help_text: strings.get('inputs.GCE_FILE_INPUT_HELP_TEXT'), + }; + + let gceFileInputPreEditValues; + vm.form.inputs = { _get: () => { credentialType.mergeInputProperties(); - return credentialType.get('inputs.fields'); + const fields = credentialType.get('inputs.fields'); + + if (credentialType.get('name') === 'Google Compute Engine') { + fields.splice(2, 0, gceFileInputSchema); + $scope.$watch(`vm.form.${gceFileInputSchema.id}._value`, vm.gceOnFileInputChanged); + } + + return fields; }, _source: vm.form.credential_type, _reference: 'vm.form.inputs', @@ -42,18 +58,66 @@ function AddCredentialsController (models, $state, strings) { vm.form.save = data => { data.user = me.get('id'); + delete data.inputs[gceFileInputSchema.id]; + return credential.request('post', { data }); }; vm.form.onSaveSuccess = res => { $state.go('credentials.edit', { credential_id: res.data.id }, { reload: true }); }; + + vm.gceOnFileInputChanged = (value, oldValue) => { + if (value === oldValue) return; + + const gceFileIsLoaded = !!value; + const gceFileInputState = vm.form[gceFileInputSchema.id]; + const { obj, error } = vm.gceParseFileInput(value); + + gceFileInputState._isValid = !error; + gceFileInputState._message = error ? componentsStrings.get('message.INVALID_INPUT') : ''; + + vm.form.project._disabled = gceFileIsLoaded; + vm.form.username._disabled = gceFileIsLoaded; + vm.form.ssh_key_data._disabled = gceFileIsLoaded; + vm.form.ssh_key_data._displayHint = !vm.form.ssh_key_data._disabled; + + if (gceFileIsLoaded) { + gceFileInputPreEditValues = Object.assign({}, { + project: vm.form.project._value, + ssh_key_data: vm.form.ssh_key_data._value, + username: vm.form.username._value + }); + vm.form.project._value = _.get(obj, 'project_id', ''); + vm.form.ssh_key_data._value = _.get(obj, 'private_key', ''); + vm.form.username._value = _.get(obj, 'client_email', ''); + } else { + vm.form.project._value = gceFileInputPreEditValues.project; + vm.form.ssh_key_data._value = gceFileInputPreEditValues.ssh_key_data; + vm.form.username._value = gceFileInputPreEditValues.username; + } + }; + + vm.gceParseFileInput = value => { + let obj; + let error; + + try { + obj = angular.fromJson(value); + } catch (err) { + error = err; + } + + return { obj, error }; + }; } AddCredentialsController.$inject = [ 'resolvedModels', '$state', - 'CredentialsStrings' + '$scope', + 'CredentialsStrings', + 'ComponentsStrings' ]; export default AddCredentialsController; diff --git a/awx/ui/client/features/credentials/add-edit-credentials.view.html b/awx/ui/client/features/credentials/add-edit-credentials.view.html index 39a4a1b20e..2019f403f1 100644 --- a/awx/ui/client/features/credentials/add-edit-credentials.view.html +++ b/awx/ui/client/features/credentials/add-edit-credentials.view.html @@ -14,9 +14,9 @@ - + - + {{:: vm.strings.get('inputs.GROUP_TITLE') }} diff --git a/awx/ui/client/features/credentials/credentials.strings.js b/awx/ui/client/features/credentials/credentials.strings.js index 958282aa09..cb6260ce55 100644 --- a/awx/ui/client/features/credentials/credentials.strings.js +++ b/awx/ui/client/features/credentials/credentials.strings.js @@ -17,7 +17,9 @@ function CredentialsStrings (BaseString) { ns.inputs = { GROUP_TITLE: t.s('Type Details'), ORGANIZATION_PLACEHOLDER: t.s('SELECT AN ORGANIZATION'), - CREDENTIAL_TYPE_PLACEHOLDER: t.s('SELECT A CREDENTIAL TYPE') + CREDENTIAL_TYPE_PLACEHOLDER: t.s('SELECT A CREDENTIAL TYPE'), + GCE_FILE_INPUT_LABEL: t.s('Service Account JSON File'), + GCE_FILE_INPUT_HELP_TEXT: t.s('Provide account information using Google Compute Engine JSON credentials file.') }; ns.add = { diff --git a/awx/ui/client/features/credentials/edit-credentials.controller.js b/awx/ui/client/features/credentials/edit-credentials.controller.js index e0a36c106f..a7629a68e1 100644 --- a/awx/ui/client/features/credentials/edit-credentials.controller.js +++ b/awx/ui/client/features/credentials/edit-credentials.controller.js @@ -1,4 +1,4 @@ -function EditCredentialsController (models, $state, $scope, strings) { +function EditCredentialsController (models, $state, $scope, strings, componentsStrings) { const vm = this || {}; const { me, credential, credentialType, organization } = models; @@ -64,15 +64,35 @@ function EditCredentialsController (models, $state, $scope, strings) { vm.form.credential_type._displayValue = credentialType.get('name'); vm.form.credential_type._placeholder = strings.get('inputs.CREDENTIAL_TYPE_PLACEHOLDER'); + const gceFileInputSchema = { + id: 'gce_service_account_key', + type: 'file', + label: strings.get('inputs.GCE_FILE_INPUT_LABEL'), + help_text: strings.get('inputs.GCE_FILE_INPUT_HELP_TEXT'), + }; + + let gceFileInputPreEditValues; + vm.form.inputs = { _get () { + let fields; + credentialType.mergeInputProperties(); if (credentialType.get('id') === credential.get('credential_type')) { - return credential.assignInputGroupValues(credentialType.get('inputs.fields')); + fields = credential.assignInputGroupValues(credentialType.get('inputs.fields')); + } else { + fields = credentialType.get('inputs.fields'); } - return credentialType.get('inputs.fields'); + if (credentialType.get('name') === 'Google Compute Engine') { + fields.splice(2, 0, gceFileInputSchema); + + $scope.$watch(`vm.form.${gceFileInputSchema.id}._value`, vm.gceOnFileInputChanged); + $scope.$watch('vm.form.ssh_key_data._isBeingReplaced', vm.gceOnReplaceKeyChanged); + } + + return fields; }, _source: vm.form.credential_type, _reference: 'vm.form.inputs', @@ -88,19 +108,70 @@ function EditCredentialsController (models, $state, $scope, strings) { data.user = me.get('id'); credential.unset('inputs'); + delete data.inputs[gceFileInputSchema.id]; + return credential.request('put', { data }); }; vm.form.onSaveSuccess = () => { $state.go('credentials.edit', { credential_id: credential.get('id') }, { reload: true }); }; + + vm.gceOnReplaceKeyChanged = value => { + vm.form[gceFileInputSchema.id]._disabled = !value; + }; + + vm.gceOnFileInputChanged = (value, oldValue) => { + if (value === oldValue) return; + + const gceFileIsLoaded = !!value; + const gceFileInputState = vm.form[gceFileInputSchema.id]; + const { obj, error } = vm.gceParseFileInput(value); + + gceFileInputState._isValid = !error; + gceFileInputState._message = error ? componentsStrings.get('message.INVALID_INPUT') : ''; + + vm.form.project._disabled = gceFileIsLoaded; + vm.form.username._disabled = gceFileIsLoaded; + vm.form.ssh_key_data._disabled = gceFileIsLoaded; + vm.form.ssh_key_data._displayHint = !vm.form.ssh_key_data._disabled; + + if (gceFileIsLoaded) { + gceFileInputPreEditValues = Object.assign({}, { + project: vm.form.project._value, + ssh_key_data: vm.form.ssh_key_data._value, + username: vm.form.username._value + }); + vm.form.project._value = _.get(obj, 'project_id', ''); + vm.form.ssh_key_data._value = _.get(obj, 'private_key', ''); + vm.form.username._value = _.get(obj, 'client_email', ''); + } else { + vm.form.project._value = gceFileInputPreEditValues.project; + vm.form.ssh_key_data._value = gceFileInputPreEditValues.ssh_key_data; + vm.form.username._value = gceFileInputPreEditValues.username; + } + }; + + vm.gceParseFileInput = value => { + let obj; + let error; + + try { + obj = angular.fromJson(value); + } catch (err) { + error = err; + } + + return { obj, error }; + }; } EditCredentialsController.$inject = [ 'resolvedModels', '$state', '$scope', - 'CredentialsStrings' + 'CredentialsStrings', + 'ComponentsStrings' ]; export default EditCredentialsController; From 06a7c024fe88448be662719ad86473aa3866f923 Mon Sep 17 00:00:00 2001 From: Jake McDermott Date: Fri, 17 Nov 2017 14:21:01 -0500 Subject: [PATCH 6/7] add e2e test for gcp service account file input --- awx/ui/test/e2e/.eslintrc.js | 3 +- awx/ui/test/e2e/objects/credentials.js | 1 + .../e2e/objects/sections/createFormSection.js | 10 +- awx/ui/test/e2e/tests/gce.alt.json | 12 + awx/ui/test/e2e/tests/gce.invalid.json | 1 + awx/ui/test/e2e/tests/gce.json | 12 + awx/ui/test/e2e/tests/gce.missing.json | 11 + .../test-credentials-add-edit-gce-file.js | 269 ++++++++++++++++++ 8 files changed, 317 insertions(+), 2 deletions(-) create mode 100644 awx/ui/test/e2e/tests/gce.alt.json create mode 100644 awx/ui/test/e2e/tests/gce.invalid.json create mode 100644 awx/ui/test/e2e/tests/gce.json create mode 100644 awx/ui/test/e2e/tests/gce.missing.json create mode 100644 awx/ui/test/e2e/tests/test-credentials-add-edit-gce-file.js diff --git a/awx/ui/test/e2e/.eslintrc.js b/awx/ui/test/e2e/.eslintrc.js index 7ce4f5483c..02959f42e3 100644 --- a/awx/ui/test/e2e/.eslintrc.js +++ b/awx/ui/test/e2e/.eslintrc.js @@ -1,5 +1,6 @@ module.exports = { rules: { - 'no-unused-expressions': 'off' + 'no-unused-expressions': 'off', + 'no-unused-vars': 'off', } }; diff --git a/awx/ui/test/e2e/objects/credentials.js b/awx/ui/test/e2e/objects/credentials.js index ed825d9426..ad3cd3bb91 100644 --- a/awx/ui/test/e2e/objects/credentials.js +++ b/awx/ui/test/e2e/objects/credentials.js @@ -67,6 +67,7 @@ const gce = createFormSection({ email: 'Service Account Email Address', project: 'Project', sshKeyData: 'RSA Private Key', + serviceAccountFile: 'Service Account JSON File' } }); diff --git a/awx/ui/test/e2e/objects/sections/createFormSection.js b/awx/ui/test/e2e/objects/sections/createFormSection.js index 3a8ebb810b..97e4b73394 100644 --- a/awx/ui/test/e2e/objects/sections/createFormSection.js +++ b/awx/ui/test/e2e/objects/sections/createFormSection.js @@ -12,7 +12,7 @@ const inputContainerElements = { popover: '.at-Popover-container', yaml: 'input[type="radio", value="yaml"]', json: 'input[type="radio", value="json"]', - revert: 'a[class~="reset"]', + reset: 'a[class~="reset"]', down: 'span[class^="fa-angle-down"]', up: 'span[class^="fa-angle-up"]', prompt: { @@ -34,6 +34,14 @@ const inputContainerElements = { off: { locateStrategy: 'xpath', selector: `.//button[${normalized}='off']` + }, + replace: { + locateStrategy: 'xpath', + selector: `.//button[${normalized}='replace']` + }, + revert: { + locateStrategy: 'xpath', + selector: `.//button[${normalized}='revert']` } }; diff --git a/awx/ui/test/e2e/tests/gce.alt.json b/awx/ui/test/e2e/tests/gce.alt.json new file mode 100644 index 0000000000..1080223f07 --- /dev/null +++ b/awx/ui/test/e2e/tests/gce.alt.json @@ -0,0 +1,12 @@ +{ + "type": "service_account", + "project_id": "test321", + "private_key_id": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "private_key": "-----BEGIN PRIVATE KEY-----\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBB\n-----END PRIVATE KEY-----\n", + "client_email": "test321.iam.gserviceaccount.com", + "client_id": "321987654321987654321", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://accounts.google.com/o/oauth2/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/test321%40test.iam.gserviceaccount.com" +} diff --git a/awx/ui/test/e2e/tests/gce.invalid.json b/awx/ui/test/e2e/tests/gce.invalid.json new file mode 100644 index 0000000000..9977a2836c --- /dev/null +++ b/awx/ui/test/e2e/tests/gce.invalid.json @@ -0,0 +1 @@ +invalid diff --git a/awx/ui/test/e2e/tests/gce.json b/awx/ui/test/e2e/tests/gce.json new file mode 100644 index 0000000000..dd3055f059 --- /dev/null +++ b/awx/ui/test/e2e/tests/gce.json @@ -0,0 +1,12 @@ +{ + "type": "service_account", + "project_id": "test123", + "private_key_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "private_key": "-----BEGIN PRIVATE KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAA\n-----END PRIVATE KEY-----\n", + "client_email": "test123.iam.gserviceaccount.com", + "client_id": "123456789123456789123", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://accounts.google.com/o/oauth2/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/test123%40test.iam.gserviceaccount.com" +} diff --git a/awx/ui/test/e2e/tests/gce.missing.json b/awx/ui/test/e2e/tests/gce.missing.json new file mode 100644 index 0000000000..5d0d1e2968 --- /dev/null +++ b/awx/ui/test/e2e/tests/gce.missing.json @@ -0,0 +1,11 @@ +{ + "type": "service_account", + "project_id": "test654", + "private_key_id": "cccccccccccccccccccccccccccccccccccccccc", + "private_key": "-----BEGIN PRIVATE KEY-----\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCC\n-----END PRIVATE KEY-----\n", + "client_id": "654321987654321987654", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://accounts.google.com/o/oauth2/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/test654%40test.iam.gserviceaccount.com" +} diff --git a/awx/ui/test/e2e/tests/test-credentials-add-edit-gce-file.js b/awx/ui/test/e2e/tests/test-credentials-add-edit-gce-file.js new file mode 100644 index 0000000000..458b735630 --- /dev/null +++ b/awx/ui/test/e2e/tests/test-credentials-add-edit-gce-file.js @@ -0,0 +1,269 @@ +import path from 'path'; +import uuid from 'uuid'; + +const GCE_SERVICE_ACCOUNT_FILE = path.resolve(__dirname, 'gce.json'); +const GCE_SERVICE_ACCOUNT_FILE_ALT = path.resolve(__dirname, 'gce.alt.json'); +const GCE_SERVICE_ACCOUNT_FILE_INVALID = path.resolve(__dirname, 'gce.invalid.json'); +const GCE_SERVICE_ACCOUNT_FILE_MISSING = path.resolve(__dirname, 'gce.missing.json'); + +let credentials; + +module.exports = { + before: (client, done) => { + credentials = client.page.credentials(); + + client.login(); + client.waitForAngular(); + + credentials.section.navigation + .waitForElementVisible('@credentials') + .click('@credentials'); + + credentials + .waitForElementVisible('div.spinny') + .waitForElementNotVisible('div.spinny'); + + credentials.section.list + .waitForElementVisible('@add') + .click('@add'); + + credentials.section.add.section.details + .waitForElementVisible('@save') + .setValue('@name', `credential-${uuid().substr(0, 8)}`) + .setValue('@type', 'Google Compute Engine', done); + }, + 'expected fields are initially visible and enabled': client => { + const { details } = credentials.section.add.section; + const { gce } = details.section; + + details.expect.element('@name').visible; + details.expect.element('@description').visible; + details.expect.element('@organization').visible; + details.expect.element('@type').visible; + + gce.expect.element('@email').visible; + gce.expect.element('@sshKeyData').visible; + gce.expect.element('@project').visible; + gce.expect.element('@serviceAccountFile').visible; + + details.expect.element('@name').enabled; + details.expect.element('@description').enabled; + details.expect.element('@organization').enabled; + details.expect.element('@type').enabled; + + gce.expect.element('@email').enabled; + gce.expect.element('@sshKeyData').enabled; + gce.expect.element('@project').enabled; + gce.expect.element('@serviceAccountFile').enabled; + + gce.section.serviceAccountFile.expect.element('form i[class*="folder"]').visible; + gce.section.serviceAccountFile.expect.element('form i[class*="folder"]').enabled; + gce.section.serviceAccountFile.expect.element('form i[class*="trash"]').not.present; + }, + 'select valid credential file': client => { + const { details } = credentials.section.add.section; + const { gce } = details.section; + + gce.section.serviceAccountFile.setValue('form input[type="file"]', GCE_SERVICE_ACCOUNT_FILE); + + gce.expect.element('@email').not.enabled; + gce.expect.element('@sshKeyData').not.enabled; + gce.expect.element('@project').not.enabled; + gce.expect.element('@serviceAccountFile').enabled; + + gce.section.serviceAccountFile.expect.element('form i[class*="trash"]').visible; + gce.section.serviceAccountFile.expect.element('form i[class*="trash"]').enabled; + gce.section.serviceAccountFile.expect.element('form i[class*="folder"]').not.present; + }, + 'deselect valid credential file': client => { + const { details } = credentials.section.add.section; + const { gce } = details.section; + + gce.section.serviceAccountFile.click('form i[class*="trash"]'); + + gce.expect.element('@email').enabled; + gce.expect.element('@sshKeyData').enabled; + gce.expect.element('@project').enabled; + gce.expect.element('@serviceAccountFile').enabled; + + gce.section.serviceAccountFile.expect.element('form i[class*="folder"]').visible; + gce.section.serviceAccountFile.expect.element('form i[class*="folder"]').enabled; + gce.section.serviceAccountFile.expect.element('form i[class*="trash"]').not.present; + + gce.section.email.expect.element('@error').visible; + gce.section.sshKeyData.expect.element('@error').visible; + + gce.section.project.expect.element('@error').not.present; + gce.section.serviceAccountFile.expect.element('@error').not.present; + }, + 'select credential file with missing field': client => { + const { details } = credentials.section.add.section; + const { gce } = details.section; + + gce.section.serviceAccountFile.setValue('form input[type="file"]', GCE_SERVICE_ACCOUNT_FILE_MISSING); + + gce.expect.element('@email').not.enabled; + gce.expect.element('@sshKeyData').not.enabled; + gce.expect.element('@project').not.enabled; + gce.expect.element('@serviceAccountFile').enabled; + + gce.section.serviceAccountFile.expect.element('form i[class*="trash"]').visible; + gce.section.serviceAccountFile.expect.element('form i[class*="trash"]').enabled; + gce.section.serviceAccountFile.expect.element('form i[class*="folder"]').not.present; + + gce.section.email.expect.element('@error').visible; + + gce.section.project.expect.element('@error').not.present; + gce.section.serviceAccountFile.expect.element('@error').not.present; + gce.section.sshKeyData.expect.element('@error').not.present; + }, + 'deselect credential file with missing field': client => { + const { details } = credentials.section.add.section; + const { gce } = details.section; + + gce.section.serviceAccountFile.click('form i[class*="trash"]'); + + gce.expect.element('@email').enabled; + gce.expect.element('@sshKeyData').enabled; + gce.expect.element('@project').enabled; + gce.expect.element('@serviceAccountFile').enabled; + + gce.section.serviceAccountFile.expect.element('form i[class*="folder"]').visible; + gce.section.serviceAccountFile.expect.element('form i[class*="folder"]').enabled; + gce.section.serviceAccountFile.expect.element('form i[class*="trash"]').not.present; + + gce.section.email.expect.element('@error').visible; + gce.section.sshKeyData.expect.element('@error').visible; + + gce.section.project.expect.element('@error').not.present; + gce.section.serviceAccountFile.expect.element('@error').not.present; + }, + 'select invalid credential file': client => { + const { details } = credentials.section.add.section; + const { gce } = details.section; + + gce.section.serviceAccountFile.setValue('form input[type="file"]', GCE_SERVICE_ACCOUNT_FILE_INVALID); + + gce.expect.element('@email').not.enabled; + gce.expect.element('@sshKeyData').not.enabled; + gce.expect.element('@project').not.enabled; + gce.expect.element('@serviceAccountFile').enabled; + + gce.section.serviceAccountFile.expect.element('form i[class*="trash"]').visible; + gce.section.serviceAccountFile.expect.element('form i[class*="trash"]').enabled; + gce.section.serviceAccountFile.expect.element('form i[class*="folder"]').not.present; + + gce.section.email.expect.element('@error').visible; + gce.section.serviceAccountFile.expect.element('@error').visible; + gce.section.sshKeyData.expect.element('@error').visible; + + gce.section.project.expect.element('@error').not.present; + }, + 'deselect invalid credential file': client => { + const { details } = credentials.section.add.section; + const { gce } = details.section; + + gce.section.serviceAccountFile.click('form i[class*="trash"]'); + + gce.expect.element('@email').enabled; + gce.expect.element('@sshKeyData').enabled; + gce.expect.element('@project').enabled; + gce.expect.element('@serviceAccountFile').enabled; + + gce.section.serviceAccountFile.expect.element('form i[class*="folder"]').visible; + gce.section.serviceAccountFile.expect.element('form i[class*="folder"]').enabled; + gce.section.serviceAccountFile.expect.element('form i[class*="trash"]').not.present; + + gce.section.email.expect.element('@error').visible; + gce.section.sshKeyData.expect.element('@error').visible; + + gce.section.project.expect.element('@error').not.present; + gce.section.serviceAccountFile.expect.element('@error').not.present; + }, + 'save valid credential file': client => { + const add = credentials.section.add.section.details; + const edit = credentials.section.edit.section.details; + + add.section.gce.section.serviceAccountFile.setValue('form input[type="file"]', GCE_SERVICE_ACCOUNT_FILE); + + add.section.gce.expect.element('@email').not.enabled; + add.section.gce.expect.element('@sshKeyData').not.enabled; + add.section.gce.expect.element('@project').not.enabled; + add.section.gce.expect.element('@serviceAccountFile').enabled; + + add.section.gce.section.serviceAccountFile.expect.element('form i[class*="trash"]').visible; + add.section.gce.section.serviceAccountFile.expect.element('form i[class*="trash"]').enabled; + add.section.gce.section.serviceAccountFile.expect.element('form i[class*="folder"]').not.present; + + add.click('@save'); + + credentials + .waitForElementVisible('div.spinny') + .waitForElementNotVisible('div.spinny'); + + edit.section.gce.expect.element('@email').enabled; + edit.section.gce.expect.element('@project').enabled; + + edit.section.gce.expect.element('@serviceAccountFile').not.enabled; + edit.section.gce.expect.element('@sshKeyData').not.enabled; + }, + 'select and deselect credential file when replacing private key': client => { + const { gce } = credentials.section.edit.section.details.section; + + gce.section.sshKeyData.waitForElementVisible('@replace'); + gce.section.sshKeyData.click('@replace'); + + gce.expect.element('@email').enabled; + gce.expect.element('@project').enabled; + gce.expect.element('@sshKeyData').enabled; + gce.expect.element('@serviceAccountFile').enabled; + + gce.section.sshKeyData.expect.element('@error').visible; + + gce.section.email.expect.element('@error').not.present; + gce.section.project.expect.element('@error').not.present; + gce.section.serviceAccountFile.expect.element('@error').not.present; + + gce.section.serviceAccountFile.setValue('form input[type="file"]', GCE_SERVICE_ACCOUNT_FILE_ALT); + + gce.expect.element('@serviceAccountFile').enabled; + + gce.expect.element('@email').not.enabled; + gce.expect.element('@sshKeyData').not.enabled; + gce.expect.element('@project').not.enabled; + + gce.section.email.expect.element('@error').not.present; + gce.section.project.expect.element('@error').not.present; + gce.section.sshKeyData.expect.element('@error').not.present; + gce.section.serviceAccountFile.expect.element('@error').not.present; + + gce.section.sshKeyData.expect.element('@replace').not.present; + gce.section.sshKeyData.expect.element('@revert').present; + gce.section.sshKeyData.expect.element('@revert').not.enabled; + + gce.section.serviceAccountFile.click('form i[class*="trash"]'); + + gce.expect.element('@email').enabled; + gce.expect.element('@sshKeyData').enabled; + gce.expect.element('@project').enabled; + gce.expect.element('@serviceAccountFile').enabled; + + gce.section.sshKeyData.expect.element('@error').visible; + + gce.section.email.expect.element('@error').not.present; + gce.section.project.expect.element('@error').not.present; + gce.section.serviceAccountFile.expect.element('@error').not.present; + + gce.section.sshKeyData.expect.element('@revert').enabled; + + gce.section.sshKeyData.click('@revert'); + + gce.expect.element('@email').enabled; + gce.expect.element('@project').enabled; + + gce.expect.element('@serviceAccountFile').not.enabled; + gce.expect.element('@sshKeyData').not.enabled; + + client.end(); + } +}; From dbb9ffbaf40746a777511a5bec4a2a489ecf01b6 Mon Sep 17 00:00:00 2001 From: Jake McDermott Date: Wed, 29 Nov 2017 17:45:25 -0500 Subject: [PATCH 7/7] use settings when setting up user data --- awx/ui/test/e2e/fixtures.js | 8 +++++--- awx/ui/test/e2e/tests/test-auditor-read-only-forms.js | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/awx/ui/test/e2e/fixtures.js b/awx/ui/test/e2e/fixtures.js index 1cc8a6ea72..a0f886e074 100644 --- a/awx/ui/test/e2e/fixtures.js +++ b/awx/ui/test/e2e/fixtures.js @@ -1,5 +1,7 @@ import uuid from 'uuid'; +import { AWX_E2E_PASSWORD } from './settings'; + import { all, get, @@ -231,7 +233,7 @@ const getAuditor = (namespace = session) => getOrganization(namespace) email: 'null@ansible.com', is_superuser: false, is_system_auditor: true, - password: 'password' + password: AWX_E2E_PASSWORD })); const getUser = (namespace = session) => getOrganization(namespace) @@ -243,7 +245,7 @@ const getUser = (namespace = session) => getOrganization(namespace) email: 'null@ansible.com', is_superuser: false, is_system_auditor: false, - password: 'password' + password: AWX_E2E_PASSWORD })); const getJobTemplateAdmin = (namespace = session) => { @@ -259,7 +261,7 @@ const getJobTemplateAdmin = (namespace = session) => { email: 'null@ansible.com', is_superuser: false, is_system_auditor: false, - password: 'password' + password: AWX_E2E_PASSWORD })); const assignRolePromise = Promise.all([userPromise, rolePromise]) diff --git a/awx/ui/test/e2e/tests/test-auditor-read-only-forms.js b/awx/ui/test/e2e/tests/test-auditor-read-only-forms.js index f31d33e173..ecce0836c5 100644 --- a/awx/ui/test/e2e/tests/test-auditor-read-only-forms.js +++ b/awx/ui/test/e2e/tests/test-auditor-read-only-forms.js @@ -63,7 +63,7 @@ module.exports = { inventories = client.page.inventories(); teams = client.page.teams(); - client.login(data.auditor.username, data.auditor.password); + client.login(data.auditor.username); client.waitForAngular(); done();