diff --git a/awx/ui/client/lib/components/modal/_index.less b/awx/ui/client/lib/components/modal/_index.less index ba5ac620ec..1d3b2be342 100644 --- a/awx/ui/client/lib/components/modal/_index.less +++ b/awx/ui/client/lib/components/modal/_index.less @@ -1,6 +1,6 @@ .at-Modal-body { font-size: @at-font-size; - padding: 0; + padding: @at-padding-panel 0; } .at-Modal-dismiss { diff --git a/awx/ui/client/lib/components/modal/modal.directive.js b/awx/ui/client/lib/components/modal/modal.directive.js index 2733bb2e1e..604f9f0495 100644 --- a/awx/ui/client/lib/components/modal/modal.directive.js +++ b/awx/ui/client/lib/components/modal/modal.directive.js @@ -10,13 +10,15 @@ function atModalLink (scope, el, attrs, controllers) { }); } -function AtModalController (eventService) { +function AtModalController (eventService, strings) { let vm = this; let overlay; let modal; let listeners; + vm.strings = strings; + vm.init = (scope, el) => { overlay = el[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) { return { diff --git a/awx/ui/client/lib/models/Base.js b/awx/ui/client/lib/models/Base.js index b728e93ca1..db2851bc53 100644 --- a/awx/ui/client/lib/models/Base.js +++ b/awx/ui/client/lib/models/Base.js @@ -1,28 +1,55 @@ let $http; let $q; +let cache; function request (method, resource) { - if (Array.isArray(method) && Array.isArray(resource)) { - let promises = method.map((value, i) => this.http[value](resource[i])); + if (Array.isArray(method)) { + let promises = method.map((_method_, i) => { + return this.request(_method_, Array.isArray(resource) ? resource[i] : resource); + }); return $q.all(promises); } - + + if (this.isCacheable(method, resource)) { + return this.requestWithCache(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 * 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 * 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. * - * @returns {Promise} - $http - * @yields {(Boolean|object)} + * @yields {boolean} - Indicating a match has been found. If so, the results + * are set on the model. */ function search (params, config) { let req = { @@ -279,6 +306,14 @@ function isCreatable () { return false; } +function isCacheable () { + if (this.settings.cache === true) { + return true; + } + + return false; +} + function graft (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); } -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) { return this; } - this.promise = this.request(method, resource); + this.promise = this.request(method, resource, config); if (graft) { return this; @@ -306,19 +356,30 @@ function create (method, resource, graft) { .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.find = find; this.get = get; this.graft = graft; this.has = has; this.isEditable = isEditable; + this.isCacheable = isCacheable; this.isCreatable = isCreatable; this.match = match; - this.model = {}; this.normalizePath = normalizePath; this.options = options; this.request = request; + this.requestWithCache = requestWithCache; this.search = search; this.set = set; this.unset = unset; @@ -330,16 +391,19 @@ function BaseModel (path) { put: httpPut.bind(this), }; + this.model = {}; this.path = this.normalizePath(path); + this.settings = settings || {}; }; -function BaseModelLoader (_$http_, _$q_) { +function BaseModelLoader (_$http_, _$q_, _cache_) { $http = _$http_; $q = _$q_; + cache = _cache_; return BaseModel; } -BaseModelLoader.$inject = ['$http', '$q']; +BaseModelLoader.$inject = ['$http', '$q', 'CacheService']; export default BaseModelLoader; diff --git a/awx/ui/client/lib/models/Config.js b/awx/ui/client/lib/models/Config.js new file mode 100644 index 0000000000..777a9f2bd4 --- /dev/null +++ b/awx/ui/client/lib/models/Config.js @@ -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; diff --git a/awx/ui/client/lib/models/index.js b/awx/ui/client/lib/models/index.js index 9c48b5922b..0734340dae 100644 --- a/awx/ui/client/lib/models/index.js +++ b/awx/ui/client/lib/models/index.js @@ -1,4 +1,5 @@ import Base from './Base'; +import Config from './Config'; import Credential from './Credential'; import CredentialType from './CredentialType'; import Me from './Me'; @@ -7,6 +8,7 @@ import Organization from './Organization'; angular .module('at.lib.models', []) .service('BaseModel', Base) + .service('ConfigModel', Config) .service('CredentialModel', Credential) .service('CredentialTypeModel', CredentialType) .service('MeModel', Me) diff --git a/awx/ui/client/lib/services/cache.service.js b/awx/ui/client/lib/services/cache.service.js new file mode 100644 index 0000000000..9cc526f907 --- /dev/null +++ b/awx/ui/client/lib/services/cache.service.js @@ -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; diff --git a/awx/ui/client/lib/services/index.js b/awx/ui/client/lib/services/index.js index 85930d852d..0c7e1d0234 100644 --- a/awx/ui/client/lib/services/index.js +++ b/awx/ui/client/lib/services/index.js @@ -1,3 +1,4 @@ +import CacheService from './cache.service'; import EventService from './event.service'; import PathService from './path.service'; import BaseStringService from './base-string.service'; @@ -5,7 +6,8 @@ import AppStrings from './app.strings'; angular .module('at.lib.services', []) - .service('EventService', EventService) - .service('PathService', PathService) + .service('AppStrings', AppStrings) .service('BaseStringService', BaseStringService) - .service('AppStrings', AppStrings); + .service('CacheService', CacheService) + .service('EventService', EventService) + .service('PathService', PathService); diff --git a/awx/ui/client/src/shared/smart-search/smart-search.controller.js b/awx/ui/client/src/shared/smart-search/smart-search.controller.js index 69b55a45a8..7ddbfd42c7 100644 --- a/awx/ui/client/src/shared/smart-search/smart-search.controller.js +++ b/awx/ui/client/src/shared/smart-search/smart-search.controller.js @@ -1,14 +1,19 @@ -export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', 'SmartSearchService', 'i18n', - function($stateParams, $scope, $state, GetBasePath, qs, SmartSearchService, i18n) { +export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', 'SmartSearchService', 'i18n', 'ConfigModel', + function($stateParams, $scope, $state, GetBasePath, qs, SmartSearchService, i18n, ConfigModel) { let path, defaults, queryset, - stateChangeSuccessListener; + stateChangeSuccessListener, + configModel = new ConfigModel(); - init(); + configModel.request('get') + .then(() => 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) { defaults = $scope.defaultParams; diff --git a/awx/ui/client/src/shared/smart-search/smart-search.partial.html b/awx/ui/client/src/shared/smart-search/smart-search.partial.html index 2dba935b0f..1f31adcf9e 100644 --- a/awx/ui/client/src/shared/smart-search/smart-search.partial.html +++ b/awx/ui/client/src/shared/smart-search/smart-search.partial.html @@ -50,7 +50,7 @@