From 98cf28d9f1cfc51720b25c03caf4ce9f939aaf6b Mon Sep 17 00:00:00 2001 From: gconsidine Date: Wed, 26 Jul 2017 17:49:05 -0400 Subject: [PATCH 1/4] Update model interaction --- .../credentials/add-credentials.controller.js | 5 +- .../edit-credentials.controller.js | 11 ++-- awx/ui/client/features/credentials/index.js | 10 +--- awx/ui/client/lib/models/Base.js | 52 +++++++++++++++++-- awx/ui/client/lib/models/Credential.js | 6 +-- awx/ui/client/lib/models/CredentialType.js | 29 ++++------- awx/ui/client/lib/models/Me.js | 6 +-- awx/ui/client/lib/models/Organization.js | 7 +-- .../list/credentials-list.controller.js | 2 +- 9 files changed, 82 insertions(+), 46 deletions(-) diff --git a/awx/ui/client/features/credentials/add-credentials.controller.js b/awx/ui/client/features/credentials/add-credentials.controller.js index 4e829f37ee..79fa9afd0c 100644 --- a/awx/ui/client/features/credentials/add-credentials.controller.js +++ b/awx/ui/client/features/credentials/add-credentials.controller.js @@ -31,9 +31,10 @@ function AddCredentialsController (models, $state, strings) { vm.form.inputs = { _get: id => { - let type = credentialType.getById(id); + let type = credentialType.graft(id); + type.mergeInputProperties(); - return credentialType.mergeInputProperties(type); + return type.get('inputs.fields'); }, _source: vm.form.credential_type, _reference: 'vm.form.inputs', diff --git a/awx/ui/client/features/credentials/edit-credentials.controller.js b/awx/ui/client/features/credentials/edit-credentials.controller.js index 19b05a834b..e49dfe51ce 100644 --- a/awx/ui/client/features/credentials/edit-credentials.controller.js +++ b/awx/ui/client/features/credentials/edit-credentials.controller.js @@ -56,14 +56,15 @@ function EditCredentialsController (models, $state, $scope, strings) { vm.form.inputs = { _get (id) { - let type = credentialType.getById(id); - let inputs = credentialType.mergeInputProperties(type); + let type = credentialType.graft(id); + + type.mergeInputProperties(); - if (type.id === credential.get('credential_type')) { - inputs = credential.assignInputGroupValues(inputs); + if (type.get('id') === credential.get('credential_type')) { + return credential.assignInputGroupValues(type.get('inputs.fields')); } - return inputs; + return type.get('inputs.fields'); }, _source: vm.form.credential_type, _reference: 'vm.form.inputs', diff --git a/awx/ui/client/features/credentials/index.js b/awx/ui/client/features/credentials/index.js index c939c98d5d..077e9562e1 100644 --- a/awx/ui/client/features/credentials/index.js +++ b/awx/ui/client/features/credentials/index.js @@ -5,7 +5,6 @@ import CredentialsStrings from './credentials.strings' function CredentialsResolve ($q, $stateParams, Me, Credential, CredentialType, Organization) { let id = $stateParams.credential_id; - let models; let promises = { me: new Me('get'), @@ -22,14 +21,9 @@ function CredentialsResolve ($q, $stateParams, Me, Credential, CredentialType, O promises.credential = new Credential(['get', 'options'], [id, id]); return $q.all(promises) - .then(_models_ => { - models = _models_; + .then(models => { let credentialTypeId = models.credential.get('credential_type'); - - return models.credentialType.graft(credentialTypeId); - }) - .then(selectedCredentialType => { - models.selectedCredentialType = selectedCredentialType; + models.selectedCredentialType = models.credentialType.graft(credentialTypeId); return models; }); diff --git a/awx/ui/client/lib/models/Base.js b/awx/ui/client/lib/models/Base.js index d1c8fd2936..0a4cef54fd 100644 --- a/awx/ui/client/lib/models/Base.js +++ b/awx/ui/client/lib/models/Base.js @@ -87,7 +87,34 @@ function get (keys) { return this.find('get', keys); } +function set (method, keys, value) { + if (!value) { + value = keys; + keys = method; + method = 'GET'; + } + + keys = keys.split('.'); + + if (keys.length === 1) { + model[keys[0]] = value; + } else { + let property = keys.splice(-1); + keys = keys.join('.'); + + let model = this.find(method, keys) + + model[property] = value; + } +} + function match (method, key, value) { + if(!value) { + value = key; + key = method; + method = 'GET'; + } + let model = this.model[method.toUpperCase()]; if (!model) { @@ -149,20 +176,39 @@ function normalizePath (resource) { return `${version}${resource}/`; } -function getById (id) { +function graft (id) { let item = this.get('results').filter(result => result.id === id); - return item ? item[0] : undefined; + item = item ? item[0] : undefined; + + if (!item) { + return undefined; + } + + return new this.Constructor('get', item, true); +} + +function create (method, resource, graft) { + this.promise = this.request(method, resource); + + if (graft) { + return this; + } + + return this.promise + .then(() => this); } function BaseModel (path) { this.model = {}; this.get = get; + this.set = set; this.options = options; this.find = find; this.match = match; this.normalizePath = normalizePath; - this.getById = getById; + this.graft = graft; + this.create = create; this.request = request; this.http = { get: httpGet.bind(this), diff --git a/awx/ui/client/lib/models/Credential.js b/awx/ui/client/lib/models/Credential.js index 19354bf8f0..9937ebe281 100644 --- a/awx/ui/client/lib/models/Credential.js +++ b/awx/ui/client/lib/models/Credential.js @@ -37,15 +37,15 @@ function clearTypeInputs () { delete this.model.GET.inputs; } -function CredentialModel (method, resource) { +function CredentialModel (method, resource, graft) { BaseModel.call(this, 'credentials'); + this.Constructor = CredentialModel; this.createFormSchema = createFormSchema.bind(this); this.assignInputGroupValues = assignInputGroupValues.bind(this); this.clearTypeInputs = clearTypeInputs.bind(this); - return this.request(method, resource) - .then(() => this); + return this.create(method, resource, graft); } function CredentialModelLoader (_BaseModel_ ) { diff --git a/awx/ui/client/lib/models/CredentialType.js b/awx/ui/client/lib/models/CredentialType.js index f0ab0d6f0f..e132c05ebb 100644 --- a/awx/ui/client/lib/models/CredentialType.js +++ b/awx/ui/client/lib/models/CredentialType.js @@ -14,33 +14,26 @@ function categorizeByKind () { })); } -function mergeInputProperties (type) { - return type.inputs.fields.map(field => { - if (!type.inputs.required || type.inputs.required.indexOf(field.id) === -1) { - field.required = false; - } else { - field.required = true; - } +function mergeInputProperties () { + let required = this.get('inputs.required'); - return field; + return this.get('inputs.fields').map((field, i) => { + if (!required || required.indexOf(field.id) === -1) { + this.set(`inputs.fields[${i}].required`, false); + } else { + this.set(`inputs.fields[${i}].required`, true); + } }); } -function graft (id) { - let data = this.getById(id); - - return new CredentialTypeModel('get', data); -} - -function CredentialTypeModel (method, id) { +function CredentialTypeModel (method, resource, graft) { BaseModel.call(this, 'credential_types'); + this.Constructor = CredentialTypeModel; this.categorizeByKind = categorizeByKind.bind(this); this.mergeInputProperties = mergeInputProperties.bind(this); - this.graft = graft.bind(this); - return this.request(method, id) - .then(() => this); + return this.create(method, resource, graft); } function CredentialTypeModelLoader (_BaseModel_) { diff --git a/awx/ui/client/lib/models/Me.js b/awx/ui/client/lib/models/Me.js index 2a36a5b2be..9393132e8e 100644 --- a/awx/ui/client/lib/models/Me.js +++ b/awx/ui/client/lib/models/Me.js @@ -4,13 +4,13 @@ function getSelf () { return this.get('results[0]'); } -function MeModel (method) { +function MeModel (method, resource, graft) { BaseModel.call(this, 'me'); + this.Constructor = MeModel; this.getSelf = getSelf.bind(this); - return this.request(method) - .then(() => this); + return this.create(method, resource, graft); } function MeModelLoader (_BaseModel_) { diff --git a/awx/ui/client/lib/models/Organization.js b/awx/ui/client/lib/models/Organization.js index 7dc7758f78..53448cd80f 100644 --- a/awx/ui/client/lib/models/Organization.js +++ b/awx/ui/client/lib/models/Organization.js @@ -1,10 +1,11 @@ let BaseModel; -function OrganizationModel (method) { +function OrganizationModel (method, resource, graft) { BaseModel.call(this, 'organizations'); - return this.request(method) - .then(() => this); + this.Constructor = OrganizationModel; + + return this.create(method, resource, graft); } function OrganizationModelLoader (_BaseModel_) { diff --git a/awx/ui/client/src/credentials/list/credentials-list.controller.js b/awx/ui/client/src/credentials/list/credentials-list.controller.js index 30acfc38c5..924f58e367 100644 --- a/awx/ui/client/src/credentials/list/credentials-list.controller.js +++ b/awx/ui/client/src/credentials/list/credentials-list.controller.js @@ -46,7 +46,7 @@ export default ['$scope', 'Rest', 'CredentialList', 'Prompt', 'ProcessErrors', ' } $scope[list.name].forEach(credential => { - credential.kind = credentialType.getById(credential.credential_type).name; + credential.kind = credentialType.match('id', credential.credential_type).name; }); } From bf42021a322b1528cfa030dd9b6c32cf329c52ba Mon Sep 17 00:00:00 2001 From: gconsidine Date: Thu, 27 Jul 2017 16:47:23 -0400 Subject: [PATCH 2/4] Add search to models w/ search on lookup input --- .../credentials/add-credentials.controller.js | 5 +- .../edit-credentials.controller.js | 19 ++-- awx/ui/client/features/credentials/index.js | 23 +++-- .../lib/components/input/base.controller.js | 24 ++++-- .../lib/components/input/lookup.directive.js | 81 ++++++++++++----- .../lib/components/input/lookup.partial.html | 2 +- awx/ui/client/lib/models/Base.js | 86 +++++++++++++++---- awx/ui/client/lib/models/Credential.js | 5 -- 8 files changed, 172 insertions(+), 73 deletions(-) diff --git a/awx/ui/client/features/credentials/add-credentials.controller.js b/awx/ui/client/features/credentials/add-credentials.controller.js index 79fa9afd0c..e1f925a60b 100644 --- a/awx/ui/client/features/credentials/add-credentials.controller.js +++ b/awx/ui/client/features/credentials/add-credentials.controller.js @@ -31,10 +31,9 @@ function AddCredentialsController (models, $state, strings) { vm.form.inputs = { _get: id => { - let type = credentialType.graft(id); - type.mergeInputProperties(); + credentialType.mergeInputProperties(); - return type.get('inputs.fields'); + return credentialType.get('inputs.fields'); }, _source: vm.form.credential_type, _reference: 'vm.form.inputs', diff --git a/awx/ui/client/features/credentials/edit-credentials.controller.js b/awx/ui/client/features/credentials/edit-credentials.controller.js index e49dfe51ce..8c2c79626a 100644 --- a/awx/ui/client/features/credentials/edit-credentials.controller.js +++ b/awx/ui/client/features/credentials/edit-credentials.controller.js @@ -5,7 +5,6 @@ function EditCredentialsController (models, $state, $scope, strings) { let credential = models.credential; let credentialType = models.credentialType; let organization = models.organization; - let selectedCredentialType = models.selectedCredentialType; vm.mode = 'edit'; vm.strings = strings; @@ -50,21 +49,19 @@ function EditCredentialsController (models, $state, $scope, strings) { vm.form.credential_type._resource = 'credential_type'; vm.form.credential_type._model = credentialType; vm.form.credential_type._route = 'credentials.edit.credentialType'; - vm.form.credential_type._value = selectedCredentialType.get('id'); - vm.form.credential_type._displayValue = selectedCredentialType.get('name'); + vm.form.credential_type._value = credentialType.get('id'); + vm.form.credential_type._displayValue = credentialType.get('name'); vm.form.credential_type._placeholder = strings.get('inputs.CREDENTIAL_TYPE_PLACEHOLDER'); vm.form.inputs = { _get (id) { - let type = credentialType.graft(id); - - type.mergeInputProperties(); + credentialType.mergeInputProperties(); - if (type.get('id') === credential.get('credential_type')) { - return credential.assignInputGroupValues(type.get('inputs.fields')); + if (credentialType.get('id') === credential.get('credential_type')) { + return credential.assignInputGroupValues(credentialType.get('inputs.fields')); } - return type.get('inputs.fields'); + return credentialType.get('inputs.fields'); }, _source: vm.form.credential_type, _reference: 'vm.form.inputs', @@ -73,8 +70,8 @@ function EditCredentialsController (models, $state, $scope, strings) { vm.form.save = data => { data.user = me.getSelf().id; - credential.clearTypeInputs(); - + credential.unset('inputs'); + return credential.request('put', data); }; diff --git a/awx/ui/client/features/credentials/index.js b/awx/ui/client/features/credentials/index.js index 077e9562e1..9e0662cdcd 100644 --- a/awx/ui/client/features/credentials/index.js +++ b/awx/ui/client/features/credentials/index.js @@ -7,13 +7,13 @@ function CredentialsResolve ($q, $stateParams, Me, Credential, CredentialType, O let id = $stateParams.credential_id; let promises = { - me: new Me('get'), - credentialType: new CredentialType('get'), - organization: new Organization('get') + me: new Me('get') }; if (!id) { promises.credential = new Credential('options'); + promises.credentialType = new CredentialType(); + promises.organization = new Organization(); return $q.all(promises) } @@ -22,10 +22,21 @@ function CredentialsResolve ($q, $stateParams, Me, Credential, CredentialType, O return $q.all(promises) .then(models => { - let credentialTypeId = models.credential.get('credential_type'); - models.selectedCredentialType = models.credentialType.graft(credentialTypeId); + let typeId = models.credential.get('credential_type'); + let orgId = models.credential.get('organization'); - return models; + let dependents = { + credentialType: new CredentialType('get', typeId), + organization: new Organization('get', orgId) + }; + + return $q.all(dependents) + .then(related => { + models.credentialType = related.credentialType; + models.organization = related.organization; + + return models; + }); }); } diff --git a/awx/ui/client/lib/components/input/base.controller.js b/awx/ui/client/lib/components/input/base.controller.js index 43a382210f..8d92711660 100644 --- a/awx/ui/client/lib/components/input/base.controller.js +++ b/awx/ui/client/lib/components/input/base.controller.js @@ -67,16 +67,22 @@ function BaseInputController (strings) { }; }; - vm.check = () => { - let result = vm.validate(); - - if (scope.state._touched || !scope.state._required) { - scope.state._rejected = !result.isValid; - scope.state._isValid = result.isValid; - scope.state._message = result.message; - - form.check(); + vm.updateValidationState = result => { + if (!scope.state._touched && scope.state._required) { + return; } + + scope.state._rejected = !result.isValid; + scope.state._isValid = result.isValid; + scope.state._message = result.message; + + form.check(); + }; + + vm.check = result => { + result = result || vm.validate(); + + vm.updateValidationState(result); }; vm.toggleRevertReplace = () => { diff --git a/awx/ui/client/lib/components/input/lookup.directive.js b/awx/ui/client/lib/components/input/lookup.directive.js index 362547a9d9..792709035b 100644 --- a/awx/ui/client/lib/components/input/lookup.directive.js +++ b/awx/ui/client/lib/components/input/lookup.directive.js @@ -1,3 +1,6 @@ +const DEFAULT_DEBOUNCE = 250; +const DEFAULT_KEY = 'name'; + function atInputLookupLink (scope, element, attrs, controllers) { let formController = controllers[0]; let inputController = controllers[1]; @@ -9,28 +12,40 @@ function atInputLookupLink (scope, element, attrs, controllers) { inputController.init(scope, element, formController); } -function AtInputLookupController (baseInputController, $state, $stateParams) { +function AtInputLookupController (baseInputController, $q, $state, $stateParams) { let vm = this || {}; let scope; + let model; + let search; vm.init = (_scope_, element, form) => { baseInputController.call(vm, 'input', _scope_, element, form); scope = _scope_; + model = scope.state._model; + scope.state._debounce = scope.state._debounce || DEFAULT_DEBOUNCE; + search = scope.state._search || { + key: DEFAULT_KEY, + config: { + unique: true + } + }; scope.$watch(scope.state._resource, vm.watchResource); - scope.state._validate = vm.checkOnInput; vm.check(); }; vm.watchResource = () => { + if (!scope[scope.state._resource]) { + return; + } + if (scope[scope.state._resource] !== scope.state._value) { - scope.state._value = scope[scope.state._resource]; scope.state._displayValue = scope[`${scope.state._resource}_name`]; - vm.check(); + vm.search(); } }; @@ -49,32 +64,54 @@ function AtInputLookupController (baseInputController, $state, $stateParams) { scope[scope.state._resource] = undefined; }; - vm.checkOnInput = () => { - if (!scope.state._touched) { - return { isValid: true }; + vm.searchAfterDebounce = () => { + vm.isDebouncing = true; + + vm.debounce = window.setTimeout(() => { + vm.isDebouncing = false; + vm.search(); + }, scope.state._debounce); + }; + + vm.resetDebounce = () => { + clearTimeout(vm.debounce); + vm.searchAfterDebounce(); + }; + + vm.search = () => { + return model.search({ [search.key]: scope.state._displayValue }, search.config) + .then(found => { + if (!found) { + return vm.reset(); + } + + scope[scope.state._resource] = model.get('id'); + scope.state._value = model.get('id'); + scope.state._displayValue = model.get('name'); + }) + .catch(() => vm.reset()) + .finally(() => { + let isValid = scope.state._value !== undefined; + let message = isValid ? '' : vm.strings.get('lookup.NOT_FOUND'); + + vm.check({ isValid, message }); + }); + }; + + vm.searchOnInput = () => { + if (vm.isDebouncing) { + return vm.resetDebounce(); } - let result = scope.state._model.match('get', 'name', scope.state._displayValue); + scope.state._touched = true; - if (result) { - scope[scope.state._resource] = result.id; - scope.state._value = result.id; - scope.state._displayValue = result.name; - - return { isValid: true }; - } - - vm.reset(); - - return { - isValid: false, - message: vm.strings.get('lookup.NOT_FOUND') - }; + vm.searchAfterDebounce(); }; } AtInputLookupController.$inject = [ 'BaseInputController', + '$q', '$state', '$stateParams' ]; diff --git a/awx/ui/client/lib/components/input/lookup.partial.html b/awx/ui/client/lib/components/input/lookup.partial.html index 39900afcc1..21ebf03b5d 100644 --- a/awx/ui/client/lib/components/input/lookup.partial.html +++ b/awx/ui/client/lib/components/input/lookup.partial.html @@ -16,7 +16,7 @@ ng-model="state._displayValue" ng-attr-tabindex="{{ tab || undefined }}" ng-attr-placeholder="{{::state._placeholder || undefined }}" - ng-change="vm.check()" + ng-change="vm.searchOnInput()" ng-disabled="state._disabled || form.disabled" /> diff --git a/awx/ui/client/lib/models/Base.js b/awx/ui/client/lib/models/Base.js index 0a4cef54fd..fea1fbd413 100644 --- a/awx/ui/client/lib/models/Base.js +++ b/awx/ui/client/lib/models/Base.js @@ -11,16 +11,41 @@ function request (method, resource) { return this.http[method](resource); } -function httpGet (resource) { - this.method = this.method || 'GET'; - +function search (params, config) { let req = { - method: this.method, + method: 'GET', + url: this.path, + params + }; + + return $http(req) + .then(res => { + if (!res.data.count) { + return false; + } + + if (config.unique) { + if (res.data.count !== 1) { + return false; + } + + this.model.GET = res.data.results[0]; + } else { + this.model.GET = res.data; + } + + return true; + }); +} + +function httpGet (resource) { + let req = { + method: 'GET', url: this.path }; if (typeof resource === 'object') { - this.model[this.method] = resource; + this.model.GET = resource; return $q.resolve(); } else if (resource) { @@ -43,9 +68,9 @@ function httpPost (data) { }; return $http(req).then(res => { - this.model.GET = res.data; + this.model.GET = res.data; - return res; + return res; }); } @@ -87,6 +112,28 @@ function get (keys) { return this.find('get', keys); } +function unset (method, keys) { + if (!keys) { + keys = method; + method = 'GET'; + } + + method = method.toUpperCase(); + keys = keys.split('.'); + + if (!keys.length) { + delete this.model[method]; + } else if (keys.length === 1) { + delete this.model[method][keys[0]]; + } else { + let property = keys.splice(-1); + keys = keys.join('.'); + + let model = this.find(method, keys) + delete model[property]; + } +} + function set (method, keys, value) { if (!value) { value = keys; @@ -189,6 +236,10 @@ function graft (id) { } function create (method, resource, graft) { + if (!method) { + return this; + } + this.promise = this.request(method, resource); if (graft) { @@ -200,21 +251,24 @@ function create (method, resource, graft) { } function BaseModel (path) { - this.model = {}; - this.get = get; - this.set = set; - this.options = options; - this.find = find; - this.match = match; - this.normalizePath = normalizePath; - this.graft = graft; this.create = create; + this.find = find; + this.get = get; + this.graft = graft; + this.match = match; + this.model = {}; + this.normalizePath = normalizePath; + this.options = options; this.request = request; + this.search = search; + this.set = set; + this.unset = unset; + this.http = { get: httpGet.bind(this), options: httpOptions.bind(this), post: httpPost.bind(this), - put: httpPut.bind(this) + put: httpPut.bind(this), }; this.path = this.normalizePath(path); diff --git a/awx/ui/client/lib/models/Credential.js b/awx/ui/client/lib/models/Credential.js index 9937ebe281..0599513b84 100644 --- a/awx/ui/client/lib/models/Credential.js +++ b/awx/ui/client/lib/models/Credential.js @@ -33,17 +33,12 @@ function assignInputGroupValues (inputs) { }); } -function clearTypeInputs () { - delete this.model.GET.inputs; -} - function CredentialModel (method, resource, graft) { BaseModel.call(this, 'credentials'); this.Constructor = CredentialModel; this.createFormSchema = createFormSchema.bind(this); this.assignInputGroupValues = assignInputGroupValues.bind(this); - this.clearTypeInputs = clearTypeInputs.bind(this); return this.create(method, resource, graft); } From 6239df6778ae89e7ee2274894fe1f41effb3330e Mon Sep 17 00:00:00 2001 From: gconsidine Date: Thu, 27 Jul 2017 18:07:06 -0400 Subject: [PATCH 3/4] Add read-only credential form depending on access --- .../client/features/credentials/_index.less | 2 +- .../credentials/add-credentials.controller.js | 2 + .../edit-credentials.controller.js | 4 +- .../lib/components/form/action.partial.html | 2 +- .../lib/components/form/form.directive.js | 2 + .../lib/components/input/lookup.directive.js | 2 + awx/ui/client/lib/models/Base.js | 46 +++++++++++++++++++ awx/ui/client/lib/models/Credential.js | 11 +++-- 8 files changed, 64 insertions(+), 7 deletions(-) diff --git a/awx/ui/client/features/credentials/_index.less b/awx/ui/client/features/credentials/_index.less index 4f4f37cd91..87f746b2c3 100644 --- a/awx/ui/client/features/credentials/_index.less +++ b/awx/ui/client/features/credentials/_index.less @@ -1,3 +1,3 @@ .at-CredentialsPermissions { - margin-top: 20px; + margin-top: 50px; } diff --git a/awx/ui/client/features/credentials/add-credentials.controller.js b/awx/ui/client/features/credentials/add-credentials.controller.js index e1f925a60b..c991f62b0f 100644 --- a/awx/ui/client/features/credentials/add-credentials.controller.js +++ b/awx/ui/client/features/credentials/add-credentials.controller.js @@ -19,6 +19,8 @@ function AddCredentialsController (models, $state, strings) { omit: ['user', 'team', 'inputs'] }); + vm.form.disabled = !credential.isCreatable(); + vm.form.organization._resource = 'organization'; vm.form.organization._route = 'credentials.add.organization'; vm.form.organization._model = organization; diff --git a/awx/ui/client/features/credentials/edit-credentials.controller.js b/awx/ui/client/features/credentials/edit-credentials.controller.js index 8c2c79626a..602972ff73 100644 --- a/awx/ui/client/features/credentials/edit-credentials.controller.js +++ b/awx/ui/client/features/credentials/edit-credentials.controller.js @@ -35,10 +35,12 @@ function EditCredentialsController (models, $state, $scope, strings) { // Only exists for permissions compatibility $scope.credential_obj = credential.get(); - vm.form = credential.createFormSchema('put', { + vm.form = credential.createFormSchema({ omit: ['user', 'team', 'inputs'] }); + vm.form.disabled = !credential.isEditable(); + vm.form.organization._resource = 'organization'; vm.form.organization._model = organization; vm.form.organization._route = 'credentials.edit.organization'; diff --git a/awx/ui/client/lib/components/form/action.partial.html b/awx/ui/client/lib/components/form/action.partial.html index 8affd3a414..245c649de1 100644 --- a/awx/ui/client/lib/components/form/action.partial.html +++ b/awx/ui/client/lib/components/form/action.partial.html @@ -1,5 +1,5 @@ diff --git a/awx/ui/client/lib/components/form/form.directive.js b/awx/ui/client/lib/components/form/form.directive.js index 7d7aa2e30d..e8b80898aa 100644 --- a/awx/ui/client/lib/components/form/form.directive.js +++ b/awx/ui/client/lib/components/form/form.directive.js @@ -27,6 +27,8 @@ function AtFormController (eventService, strings) { form = _form_; modal = scope[scope.ns].modal; + vm.state.disabled = scope.state.disabled; + vm.setListeners(); }; diff --git a/awx/ui/client/lib/components/input/lookup.directive.js b/awx/ui/client/lib/components/input/lookup.directive.js index 792709035b..58ab894737 100644 --- a/awx/ui/client/lib/components/input/lookup.directive.js +++ b/awx/ui/client/lib/components/input/lookup.directive.js @@ -79,6 +79,8 @@ function AtInputLookupController (baseInputController, $q, $state, $stateParams) }; vm.search = () => { + scope.state._touched = true; + return model.search({ [search.key]: scope.state._displayValue }, search.config) .then(found => { if (!found) { diff --git a/awx/ui/client/lib/models/Base.js b/awx/ui/client/lib/models/Base.js index fea1fbd413..4fa46e50a0 100644 --- a/awx/ui/client/lib/models/Base.js +++ b/awx/ui/client/lib/models/Base.js @@ -217,12 +217,55 @@ function find (method, keys) { return value; } +function has (method, keys) { + if (!keys) { + keys = method; + method = 'GET'; + } + + method = method.toUpperCase(); + + let value; + switch (method) { + case 'OPTIONS': + value = this.options(keys); + break; + default: + value = this.get(keys); + } + + return value !== undefined && value !== null; +} + function normalizePath (resource) { let version = '/api/v2/'; return `${version}${resource}/`; } +function isEditable () { + let canEdit = this.get('summary_fields.user_capabilities.edit'); + + if (canEdit) { + return true; + } + + if (this.has('options', 'actions.PUT')) { + return true; + } + + return false; + +} + +function isCreatable () { + if (this.has('options', 'actions.POST')) { + return true; + } + + return false; +} + function graft (id) { let item = this.get('results').filter(result => result.id === id); @@ -255,6 +298,9 @@ function BaseModel (path) { this.find = find; this.get = get; this.graft = graft; + this.has = has; + this.isEditable = isEditable; + this.isCreatable = isCreatable; this.match = match; this.model = {}; this.normalizePath = normalizePath; diff --git a/awx/ui/client/lib/models/Credential.js b/awx/ui/client/lib/models/Credential.js index 0599513b84..06b824b410 100644 --- a/awx/ui/client/lib/models/Credential.js +++ b/awx/ui/client/lib/models/Credential.js @@ -3,18 +3,21 @@ const ENCRYPTED_VALUE = '$encrypted$'; let BaseModel; function createFormSchema (method, config) { + if (!config) { + config = method; + method = 'GET'; + } + let schema = Object.assign({}, this.options(`actions.${method.toUpperCase()}`)); if (config && config.omit) { - config.omit.forEach(key => { - delete schema[key]; - }); + config.omit.forEach(key => delete schema[key]); } for (let key in schema) { schema[key].id = key; - if (method === 'put') { + if (this.has(key)) { schema[key]._value = this.get(key); } } From 6a7e01810077ba79522c4d41d874b2ff17a86266 Mon Sep 17 00:00:00 2001 From: gconsidine Date: Fri, 28 Jul 2017 15:23:08 -0400 Subject: [PATCH 4/4] Add comments and minor changes from PR feedback --- .../edit-credentials.controller.js | 5 ++++ .../lib/components/input/lookup.directive.js | 6 +++-- awx/ui/client/lib/models/Base.js | 23 +++++++++++++++---- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/awx/ui/client/features/credentials/edit-credentials.controller.js b/awx/ui/client/features/credentials/edit-credentials.controller.js index 602972ff73..ccdb687761 100644 --- a/awx/ui/client/features/credentials/edit-credentials.controller.js +++ b/awx/ui/client/features/credentials/edit-credentials.controller.js @@ -70,6 +70,11 @@ function EditCredentialsController (models, $state, $scope, strings) { _key: 'inputs' }; + /** + * If a credential's `credential_type` is changed while editing, the inputs associated with + * the old type need to be cleared before saving the inputs associated with the new type. + * Otherwise inputs are merged together making the request invalid. + */ vm.form.save = data => { data.user = me.getSelf().id; credential.unset('inputs'); diff --git a/awx/ui/client/lib/components/input/lookup.directive.js b/awx/ui/client/lib/components/input/lookup.directive.js index 58ab894737..b2c60e15bd 100644 --- a/awx/ui/client/lib/components/input/lookup.directive.js +++ b/awx/ui/client/lib/components/input/lookup.directive.js @@ -81,6 +81,10 @@ function AtInputLookupController (baseInputController, $q, $state, $stateParams) vm.search = () => { scope.state._touched = true; + if (scope.state._displayValue === '' && !scope.state._required) { + return vm.check({ isValid: true }); + } + return model.search({ [search.key]: scope.state._displayValue }, search.config) .then(found => { if (!found) { @@ -105,8 +109,6 @@ function AtInputLookupController (baseInputController, $q, $state, $stateParams) return vm.resetDebounce(); } - scope.state._touched = true; - vm.searchAfterDebounce(); }; } diff --git a/awx/ui/client/lib/models/Base.js b/awx/ui/client/lib/models/Base.js index 4fa46e50a0..b728e93ca1 100644 --- a/awx/ui/client/lib/models/Base.js +++ b/awx/ui/client/lib/models/Base.js @@ -11,6 +11,19 @@ function request (method, resource) { return this.http[method](resource); } +/** + * Intended to be useful in searching and filtering results using params + * supported by the API. + * + * @param {object} params - An object of keys and values to to format and + * to the URL as a query string. Refer to the API documentation for the + * resource in use for specifics. + * @param {object} config - Configuration specific to the UI to accommodate + * common use cases. + * + * @returns {Promise} - $http + * @yields {(Boolean|object)} + */ function search (params, config) { let req = { method: 'GET', @@ -19,19 +32,19 @@ function search (params, config) { }; return $http(req) - .then(res => { - if (!res.data.count) { + .then(({ data }) => { + if (!data.count) { return false; } if (config.unique) { - if (res.data.count !== 1) { + if (data.count !== 1) { return false; } - this.model.GET = res.data.results[0]; + this.model.GET = data.results[0]; } else { - this.model.GET = res.data; + this.model.GET = data; } return true;