Add Config model

* Add ability to configurably cache API responses per model
* Fix general error display on credentials
* Add current version from API to the documentation link
This commit is contained in:
gconsidine
2017-07-31 17:07:25 -04:00
parent 59157565bd
commit bdb2bbbd41
9 changed files with 171 additions and 24 deletions

View File

@@ -1,6 +1,6 @@
.at-Modal-body { .at-Modal-body {
font-size: @at-font-size; font-size: @at-font-size;
padding: 0; padding: @at-padding-panel 0;
} }
.at-Modal-dismiss { .at-Modal-dismiss {

View File

@@ -10,13 +10,15 @@ function atModalLink (scope, el, attrs, controllers) {
}); });
} }
function AtModalController (eventService) { function AtModalController (eventService, strings) {
let vm = this; let vm = this;
let overlay; let overlay;
let modal; let modal;
let listeners; let listeners;
vm.strings = strings;
vm.init = (scope, el) => { vm.init = (scope, el) => {
overlay = el[0]; overlay = el[0];
modal = el.find('.at-Modal-window')[0]; modal = el.find('.at-Modal-window')[0];
@@ -67,7 +69,10 @@ function AtModalController (eventService) {
}; };
} }
AtModalController.$inject = ['EventService']; AtModalController.$inject = [
'EventService',
'ComponentsStrings'
]
function atModal (pathService) { function atModal (pathService) {
return { return {

View File

@@ -1,28 +1,55 @@
let $http; let $http;
let $q; let $q;
let cache;
function request (method, resource) { function request (method, resource) {
if (Array.isArray(method) && Array.isArray(resource)) { if (Array.isArray(method)) {
let promises = method.map((value, i) => this.http[value](resource[i])); let promises = method.map((_method_, i) => {
return this.request(_method_, Array.isArray(resource) ? resource[i] : resource);
});
return $q.all(promises); return $q.all(promises);
} }
if (this.isCacheable(method, resource)) {
return this.requestWithCache(method, resource);
}
return this.http[method](resource); return this.http[method](resource);
} }
function requestWithCache (method, resource) {
let key = cache.createKey(method, this.path, resource);
return cache.get(key)
.then(data => {
if (data) {
this.model[method.toUpperCase()] = data;
return data;
}
return this.http[method](resource)
.then(res => {
cache.put(key, res.data);
return res;
});
});
}
/** /**
* Intended to be useful in searching and filtering results using params * Intended to be useful in searching and filtering results using params
* supported by the API. * supported by the API.
* *
* @param {object} params - An object of keys and values to to format and * @arg {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 * to the URL as a query string. Refer to the API documentation for the
* resource in use for specifics. * resource in use for specifics.
* @param {object} config - Configuration specific to the UI to accommodate * @arg {Object} config - Configuration specific to the UI to accommodate
* common use cases. * common use cases.
* *
* @returns {Promise} - $http * @yields {boolean} - Indicating a match has been found. If so, the results
* @yields {(Boolean|object)} * are set on the model.
*/ */
function search (params, config) { function search (params, config) {
let req = { let req = {
@@ -279,6 +306,14 @@ function isCreatable () {
return false; return false;
} }
function isCacheable () {
if (this.settings.cache === true) {
return true;
}
return false;
}
function graft (id) { function graft (id) {
let item = this.get('results').filter(result => result.id === id); let item = this.get('results').filter(result => result.id === id);
@@ -291,12 +326,27 @@ function graft (id) {
return new this.Constructor('get', item, true); return new this.Constructor('get', item, true);
} }
function create (method, resource, graft) { /**
* `create` is called on instantiation of every model. Models can be
* instantiated empty or with `GET` and/or `OPTIONS` requests that yield data.
* If model data already exists a new instance can be created (see: `graft`)
* with existing data.
*
* @arg {string=} method - Populate the model with `GET` or `OPTIONS` data.
* @arg {(string|Object)=} resource - An `id` reference to a particular
* resource or an existing model's data.
* @arg {boolean=} graft - Create a new instance from existing model data.
*
* @returns {(Object|Promise)} - Returns a reference to the model instance
* if an empty instance or graft is created. Otherwise, a promise yielding
* a model instance is returned.
*/
function create (method, resource, graft, config) {
if (!method) { if (!method) {
return this; return this;
} }
this.promise = this.request(method, resource); this.promise = this.request(method, resource, config);
if (graft) { if (graft) {
return this; return this;
@@ -306,19 +356,30 @@ function create (method, resource, graft) {
.then(() => this); .then(() => this);
} }
function BaseModel (path) { /**
* Base functionality for API interaction.
*
* @arg {string} path - The API resource for the model extending BaseModel to
* use.
* @arg {Object=} settings - Configuration applied to all instances of the
* extending model.
* @arg {boolean=} settings.cache - Cache the model data.
*
*/
function BaseModel (path, settings) {
this.create = create; this.create = create;
this.find = find; this.find = find;
this.get = get; this.get = get;
this.graft = graft; this.graft = graft;
this.has = has; this.has = has;
this.isEditable = isEditable; this.isEditable = isEditable;
this.isCacheable = isCacheable;
this.isCreatable = isCreatable; this.isCreatable = isCreatable;
this.match = match; this.match = match;
this.model = {};
this.normalizePath = normalizePath; this.normalizePath = normalizePath;
this.options = options; this.options = options;
this.request = request; this.request = request;
this.requestWithCache = requestWithCache;
this.search = search; this.search = search;
this.set = set; this.set = set;
this.unset = unset; this.unset = unset;
@@ -330,16 +391,19 @@ function BaseModel (path) {
put: httpPut.bind(this), put: httpPut.bind(this),
}; };
this.model = {};
this.path = this.normalizePath(path); this.path = this.normalizePath(path);
this.settings = settings || {};
}; };
function BaseModelLoader (_$http_, _$q_) { function BaseModelLoader (_$http_, _$q_, _cache_) {
$http = _$http_; $http = _$http_;
$q = _$q_; $q = _$q_;
cache = _cache_;
return BaseModel; return BaseModel;
} }
BaseModelLoader.$inject = ['$http', '$q']; BaseModelLoader.$inject = ['$http', '$q', 'CacheService'];
export default BaseModelLoader; export default BaseModelLoader;

View File

@@ -0,0 +1,32 @@
let BaseModel;
function getTruncatedVersion () {
let version;
try {
version = this.get('version').split('-')[0];
} catch (err) {
console.error(err);
}
return version;
}
function ConfigModel (method, resource, graft) {
BaseModel.call(this, 'config', { cache: true });
this.Constructor = ConfigModel;
this.getTruncatedVersion = getTruncatedVersion;
return this.create(method, resource, graft);
}
function ConfigModelLoader (_BaseModel_) {
BaseModel = _BaseModel_;
return ConfigModel;
}
ConfigModelLoader.$inject = ['BaseModel'];
export default ConfigModelLoader;

View File

@@ -1,4 +1,5 @@
import Base from './Base'; import Base from './Base';
import Config from './Config';
import Credential from './Credential'; import Credential from './Credential';
import CredentialType from './CredentialType'; import CredentialType from './CredentialType';
import Me from './Me'; import Me from './Me';
@@ -7,6 +8,7 @@ import Organization from './Organization';
angular angular
.module('at.lib.models', []) .module('at.lib.models', [])
.service('BaseModel', Base) .service('BaseModel', Base)
.service('ConfigModel', Config)
.service('CredentialModel', Credential) .service('CredentialModel', Credential)
.service('CredentialTypeModel', CredentialType) .service('CredentialTypeModel', CredentialType)
.service('MeModel', Me) .service('MeModel', Me)

View File

@@ -0,0 +1,37 @@
function CacheService ($cacheFactory, $q) {
let cache = $cacheFactory('api');
this.put = (key, data) => {
return cache.put(key, data);
};
this.get = (key) => {
return $q.resolve(cache.get(key));
};
this.remove = (key) => {
if (!key) {
return cache.removeAll();
}
return cache.remove(key);
};
this.createKey = (method, path, resource) => {
let key = `${method.toUpperCase()}.${path}`;
if (resource) {
if (resource.id) {
key += `${resource.id}/`;
} else if (Number(resource)) {
key += `${resource}/`;
}
}
return key;
}
}
CacheService.$inject = ['$cacheFactory', '$q'];
export default CacheService;

View File

@@ -1,3 +1,4 @@
import CacheService from './cache.service';
import EventService from './event.service'; import EventService from './event.service';
import PathService from './path.service'; import PathService from './path.service';
import BaseStringService from './base-string.service'; import BaseStringService from './base-string.service';
@@ -5,7 +6,8 @@ import AppStrings from './app.strings';
angular angular
.module('at.lib.services', []) .module('at.lib.services', [])
.service('EventService', EventService) .service('AppStrings', AppStrings)
.service('PathService', PathService)
.service('BaseStringService', BaseStringService) .service('BaseStringService', BaseStringService)
.service('AppStrings', AppStrings); .service('CacheService', CacheService)
.service('EventService', EventService)
.service('PathService', PathService);

View File

@@ -1,14 +1,19 @@
export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', 'SmartSearchService', 'i18n', export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', 'SmartSearchService', 'i18n', 'ConfigModel',
function($stateParams, $scope, $state, GetBasePath, qs, SmartSearchService, i18n) { function($stateParams, $scope, $state, GetBasePath, qs, SmartSearchService, i18n, ConfigModel) {
let path, let path,
defaults, defaults,
queryset, queryset,
stateChangeSuccessListener; stateChangeSuccessListener,
configModel = new ConfigModel();
init(); configModel.request('get')
.then(() => init());
function init() { function init() {
let version = configModel.getTruncatedVersion() || 'latest';
$scope.documentationLink = `http://docs.ansible.com/ansible-tower/${version}/html/userguide/search_sort.html`;
if($scope.defaultParams) { if($scope.defaultParams) {
defaults = $scope.defaultParams; defaults = $scope.defaultParams;

View File

@@ -50,7 +50,7 @@
<div class="SmartSearch-keyRow"> <div class="SmartSearch-keyRow">
<b>{{ 'ADDITIONAL INFORMATION' | translate }}:</b> <b>{{ 'ADDITIONAL INFORMATION' | translate }}:</b>
<span>{{ 'For additional information on advanced search search syntax please see the Ansible Tower' | translate }} <span>{{ 'For additional information on advanced search search syntax please see the Ansible Tower' | translate }}
<a href="http://docs.ansible.com/ansible-tower/3.2.0/html/userguide/search_sort.html" target="_blank"> {{ 'documentation' | translate }}</a>.</span> <a ng-attr-href="{{ documentationLink || undefined }}" target="_blank"> {{ 'documentation' | translate }}</a>.</span>
</div> </div>
</div> </div>
</div> </div>