diff options
author | Travis Tripp <travis.tripp@hp.com> | 2015-04-02 23:49:29 -0600 |
---|---|---|
committer | Travis Tripp <travis.tripp@hp.com> | 2015-04-09 21:00:47 -0600 |
commit | 3a59bec6a71aca166a9f23c714529fd9836d9e23 (patch) | |
tree | 50c9ebd275a97635d2ed54a0cd7e3f883fe26d61 /horizon | |
parent | d7abcbfeec3c66a88801c6c73a57ef769eb5af5d (diff) | |
download | horizon-3a59bec6a71aca166a9f23c714529fd9836d9e23.tar.gz |
[Launch Instance Fix] Settings for volume name
This patch provides the rest API needed to
get settings that allow conditionally
displaying the volume device name
and admin password.
Examples:
settingsService.ifEnabled('setting').then(doThis);
settingsService.ifEnabled('setting', 'expected', 'default').then(doThis, elseThis);
Change-Id: I8d16f4b974786157c5aa562e2675e6e60aabc412
Partial-Bug: #1439906
Partial-Bug: #1439905
Diffstat (limited to 'horizon')
-rw-r--r-- | horizon/static/horizon/js/angular/services/hz.api.config.js | 271 | ||||
-rw-r--r-- | horizon/static/horizon/js/angular/services/hz.api.config.spec.js | 253 | ||||
-rw-r--r-- | horizon/test/jasmine/jasmine_tests.py | 2 |
3 files changed, 512 insertions, 14 deletions
diff --git a/horizon/static/horizon/js/angular/services/hz.api.config.js b/horizon/static/horizon/js/angular/services/hz.api.config.js index 09a710669..e7315ef7a 100644 --- a/horizon/static/horizon/js/angular/services/hz.api.config.js +++ b/horizon/static/horizon/js/angular/services/hz.api.config.js @@ -1,18 +1,19 @@ /* -Copyright 2015, Rackspace, US, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Copyright 2015 IBM Corp + * (c) Copyright 2015 Hewlett-Packard Development Company, L.P. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ (function () { 'use strict'; @@ -61,10 +62,252 @@ limitations under the License. horizon.alert('error', gettext('Unable to retrieve admin configuration.')); }); }; + } // Register it with the API module so that anybody using the // API module will have access to the Config APIs. angular.module('hz.api') .service('configAPI', ['apiService', ConfigAPI]); + + /** + * @ngdoc service + * @name hz.api.settingsService + * @description + * Provides utilities to the cached settings data. This helps + * with asynchronous data loading. + * + * The cache in current horizon (Kilo non-single page app) only has a + * lifetime of the current page. The cache is reloaded every time you change + * panels. It also happens when you change the region selector at the top + * of the page, and when you log back in. + * + * So, at least for now, this seems to be a reliable way that will + * make only a single request to get user information for a + * particular page or modal. Making this a service allows it to be injected + * and used transparently where needed without making every single use of it + * pass it through as an argument. + */ + function settingsService($q, apiService) { + + var service = {}; + + /** + * @name hz.api.configAPI.getSettings + * @description + * Gets all the allowed settings + * + * Returns an object with settings. + */ + service.getSettings = function (suppressError) { + + function onError() { + var message = gettext('Unable to retrieve settings.'); + if (!suppressError && horizon.alert) { + horizon.alert('error', message); + } + + return message; + } + + // The below ensures that errors are handled like other + // service errors (for better or worse), but when successful + // unwraps the success result data for direct consumption. + return apiService.get('/api/settings/', {cache: true}) + .error(onError) + .then(function (response) { + return response.data; + }); + }; + + /** + * @name hz.api.settingsService.getSetting + * @description + * This retrieves a specific setting. + * + * If the setting isn't found, it will return undefined unless a default + * is specified. In that case, the default will be returned. + * + * @param {string} setting The path to the setting to get. + * + * local_settings.py allows you to create settings such as: + * + * OPENSTACK_HYPERVISOR_FEATURES = { + * 'can_set_mount_point': True, + * 'can_set_password': False, + * } + * + * To access a specific setting, use a simplified path where a . (dot) + * separates elements in the path. So in the above example, the paths + * would be: + * + * OPENSTACK_HYPERVISOR_FEATURES.can_set_mount_point + * OPENSTACK_HYPERVISOR_FEATURES.can_set_password + * + * @param {object=} defaultSetting If the requested setting does not exist, + * the defaultSetting will be returned. This is optional. + * + * @example + * + * Using the OPENSTACK_HYPERVISOR_FEATURES mentioned above, the following + * would call doSomething and pass the setting value into doSomething. + * + ```js + settingsService.getSetting('OPENSTACK_HYPERVISOR_FEATURES.can_set_mount_point') + .then(doSomething); + ``` + */ + service.getSetting = function (path, defaultSetting) { + var deferred = $q.defer(), + pathElements = path.split("."), + settingAtRequestedPath; + + function onSettingsLoaded(settings) { + // This recursively traverses the object hierarchy until either all the + // path elements are traversed or until the next element in the path + // does not have the requested child object. + settingAtRequestedPath = pathElements.reduce( + function (setting, nextPathElement) { + return setting ? setting[nextPathElement] : undefined; + }, settings); + + if (typeof settingAtRequestedPath === "undefined" && + (typeof defaultSetting !== "undefined")) { + settingAtRequestedPath = defaultSetting; + } + + deferred.resolve(settingAtRequestedPath); + } + + function onSettingsFailure(message) { + deferred.reject(message); + } + + service.getSettings() + .then(onSettingsLoaded, onSettingsFailure); + + return deferred.promise; + }; + + /** + * @name hz.api.settingsService.ifEnabled + * @description + * Checks if the desired setting is enabled. This returns a promise. + * If the setting is enabled, the promise will be resolved. + * If it is not enabled, the promise will be rejected. Use it like you + * would normal promises. + * + * @param {string} setting The path to the setting to check. + * local_settings.py allows you to create settings such as: + * + * OPENSTACK_HYPERVISOR_FEATURES = { + * 'can_set_mount_point': True, + * 'can_set_password': False, + * } + * + * To access a specific setting, use a simplified path where a . (dot) + * separates elements in the path. So in the above example, the paths + * would be: + * + * OPENSTACK_HYPERVISOR_FEATURES.can_set_mount_point + * OPENSTACK_HYPERVISOR_FEATURES.can_set_password + * + * @param (object=} expected Used to determine if the setting is + * enabled. The actual setting will be evaluated against the expected + * value using angular.equals(). If they are equal, then it will be + * considered enabled. This is optional and defaults to True. + * + * @param {object=} defaultSetting If the requested setting does not exist, + * the defaultSetting will be used for evaluation. This is optional. If + * not specified and the setting is not specified, then the setting will + * not be considered to be enabled. + * + * @example + * Simple true / false example: + * + * Using the OPENSTACK_HYPERVISOR_FEATURES mentioned above, the following + * would call the "setMountPoint" function only if + * OPENSTACK_HYPERVISOR_FEATURES.can_set_mount_point is set to true. + * + ```js + settingsService.ifEnabled('OPENSTACK_HYPERVISOR_FEATURES.can_set_mount_point') + .then(setMountPoint); + ``` + * + * Evaluating other types of settings: + * + * local_settings.py allows you optionally set the enabled OpenStack + * Service API versions with the following setting: + * + * OPENSTACK_API_VERSIONS = { + * "data-processing": 1.1, + * "identity": 3, + * "volume": 2, + * } + * + * The above is a nested object structure. The simplified path to the + * volume service version is OPENSTACK_API_VERSIONS.volume + * + * It is not uncommon for different OpenStack deployments to have + * different versions of the service enabled for various reasons. + * + * So, now, assume that if version 2 of the volume service (Cinder) is + * enabled that you want to do something. If it isn't, then you will do + * something else. + * + * Assume doSomethingIfVersion2 is a function you want to call if version 2 + * is enabled. + * + * Assume doSomethingElse is a function that does something else if + * version 2 is not enabled (optional) + * + ```js + settingsService.ifEnabled('OPENSTACK_API_VERSIONS.volume', 2) + .then(doSomethingIfVersion2, doSomethingElse); + ``` + * + * Now assume that if nothing is set in local_settings, that you want to + * treat the result as if version 1 is enabled (default when nothing set). + * + ```js + settingsService.ifEnabled('OPENSTACK_API_VERSIONS.volume', 2, 1) + .then(doSomethingIfVersion2, doSomethingElse); + ``` + */ + service.ifEnabled = function (setting, expected, defaultSetting) { + var deferred = $q.defer(); + + // If expected is not defined, we default to expecting the setting + // to be 'true' in order for it to be considered enabled. + expected = (typeof expected === "undefined") ? true : expected; + + function onSettingLoaded(setting) { + if (angular.equals(expected, setting)) { + deferred.resolve(); + } else { + deferred.reject(interpolate( + gettext('Setting is not enabled: %(setting)s'), + {setting: setting}, + true)); + } + + deferred.resolve(setting); + } + + function onSettingFailure(message) { + deferred.reject(message); + } + + service.getSetting(setting, defaultSetting) + .then(onSettingLoaded, onSettingFailure); + + return deferred.promise; + }; + + return service; + } + + angular.module('hz.api') + .factory('settingsService', ['$q', 'apiService', settingsService]); + }()); diff --git a/horizon/static/horizon/js/angular/services/hz.api.config.spec.js b/horizon/static/horizon/js/angular/services/hz.api.config.spec.js new file mode 100644 index 000000000..7a4397668 --- /dev/null +++ b/horizon/static/horizon/js/angular/services/hz.api.config.spec.js @@ -0,0 +1,253 @@ +/* + * (c) Copyright 2015 Hewlett-Packard Development Company, L.P. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/*global angular,describe,it,expect,inject,module,beforeEach,afterEach*/ +(function () { + 'use strict'; + + describe('settingsService', function () { + var settingsService, + $httpBackend, + responseMockOpts = {succeed: true}, + testData = { + isTrue: true, + isFalse: false, + versions: {one: 1, two: 2}, + deep: {nest: { foo: 'bar' } }, + isNull: null + }; + + beforeEach(module('hz.api')); + beforeEach(inject(function (_$httpBackend_, _settingsService_) { + responseMockOpts.succeed = true; + settingsService = _settingsService_; + $httpBackend = _$httpBackend_; + $httpBackend.whenGET('/api/settings/').respond( + function () { + return responseMockOpts.succeed ? + [200, testData, {}] : [500, 'Fail', {}]; + }); + $httpBackend.expectGET('/api/settings/'); + })); + + afterEach(function () { + $httpBackend.verifyNoOutstandingExpectation(); + $httpBackend.verifyNoOutstandingRequest(); + }); + + describe('getSettings', function () { + + it('should return all settings', function () { + settingsService.getSettings().then( + function (actual) { + expect(actual).toEqual(testData); + } + ); + $httpBackend.flush(); + }); + + it('should fail when error response', function () { + responseMockOpts.succeed = false; + settingsService.getSettings().then( + function (actual) { + fail('Should not have succeeded: ' + angular.toJson(actual)); + }, + function (actual) { + expect(actual).toBeDefined(); + } + ); + $httpBackend.flush(); + }); + + }); + + describe('getSetting', function () { + + it('nested deep object is found', function () { + settingsService.getSetting('deep.nest.foo') + .then(function (actual) { + expect(actual).toEqual('bar'); + }); + $httpBackend.flush(); + }); + + it("is undefined when doesn't exist", function () { + settingsService.getSetting('will.not.exist') + .then(function (actual) { + expect(actual).toBeUndefined(); + }); + $httpBackend.flush(); + }); + + it("default is returned when doesn't exist", function () { + var actual; + settingsService.getSetting('will.not.exist', 'hello') + .then(function (actual) { + expect(actual).toEqual('hello'); + }); + $httpBackend.flush(); + }); + + it('should return true', function () { + settingsService.getSetting('isTrue') + .then(function (actual) { + expect(actual).toEqual(true); + }); + $httpBackend.flush(); + }); + + it('should fail when error response', function () { + responseMockOpts.succeed = false; + settingsService.getSetting('isTrue').then( + function (actual) { + fail('Should not have succeeded: ' + angular.toJson(actual)); + }, + function (actual) { + expect(actual).toBeDefined(); + } + ); + $httpBackend.flush(); + }); + + }); + + describe('ifEnabled', function () { + + var expectedResult = {}; + + var enabled = function () { + expectedResult.enabled = true; + }; + + var notEnabled = function () { + expectedResult.enabled = false; + }; + + beforeEach(inject(function () { + expectedResult = {enabled: null}; + })); + + function meetsExpectations(expected) { + $httpBackend.flush(); + expect(expectedResult.enabled).toBe(expected); + } + + it('should fail when error response', function () { + responseMockOpts.succeed = false; + settingsService.ifEnabled('isTrue').then( + function (actual) { + fail('Should not have succeeded: ' + angular.toJson(actual)); + }, + function (actual) { + expect(actual).toBeDefined(); + } + ); + $httpBackend.flush(); + }); + + it('boolean is enabled when true', function () { + settingsService.ifEnabled('isTrue').then(enabled, notEnabled); + meetsExpectations(true); + }); + + it('boolean is enabled when true expected', function () { + settingsService.ifEnabled('isTrue', true).then(enabled, notEnabled); + meetsExpectations(true); + }); + + it('boolean is not enabled when false expected', function () { + settingsService.ifEnabled('isTrue', false).then(enabled, notEnabled); + meetsExpectations(false); + }); + + it('boolean is not enabled when false', function () { + settingsService.ifEnabled('isFalse').then(enabled, notEnabled); + meetsExpectations(false); + }); + + it('boolean is enabled when false expected', function () { + settingsService.ifEnabled('isFalse', false).then(enabled, notEnabled); + meetsExpectations(true); + }); + + it('nested object is enabled when expected', function () { + settingsService.ifEnabled('versions.one', 1).then(enabled, notEnabled); + meetsExpectations(true); + }); + + it('nested object is not enabled', function () { + settingsService.ifEnabled('versions.two', 1).then(enabled, notEnabled); + meetsExpectations(false); + }); + + it('nested object is not enabled when not found', function () { + settingsService.ifEnabled('no-exist.two', 1).then(enabled, notEnabled); + meetsExpectations(false); + }); + + it('nested deep object is enabled when expected', function () { + settingsService.ifEnabled('deep.nest.foo', 'bar').then(enabled, notEnabled); + meetsExpectations(true); + }); + + it('nested deep object is not enabled when not expected', function () { + settingsService.ifEnabled('deep.nest.foo', 'wrong').then(enabled, notEnabled); + meetsExpectations(false); + }); + + it('null is not enabled', function () { + settingsService.ifEnabled('isNull').then(enabled, notEnabled); + meetsExpectations(false); + }); + + it('null is enabled when expected', function () { + settingsService.ifEnabled('isNull', null).then(enabled, notEnabled); + meetsExpectations(true); + }); + + it('true is enabled when not found and true default', function () { + settingsService.ifEnabled('nonExistent', true, true).then(enabled, notEnabled); + meetsExpectations(true); + }); + + it('true is not enabled when not found and false default', function () { + settingsService.ifEnabled('nonExistent', true, false).then(enabled, notEnabled); + meetsExpectations(false); + }); + + it('true is not enabled when not found and no default', function () { + settingsService.ifEnabled('nonExistent', true).then(enabled, notEnabled); + meetsExpectations(false); + }); + + it('false is enabled when not found and expected w/ default', function () { + settingsService.ifEnabled('nonExistent', false, false).then(enabled, notEnabled); + meetsExpectations(true); + }); + + it('bar is enabled when expected not found and bar default', function () { + settingsService.ifEnabled('nonExistent', 'bar', 'bar').then(enabled, notEnabled); + meetsExpectations(true); + }); + + it('bar is not enabled when expected not found and not default', function () { + settingsService.ifEnabled('nonExistent', 'bar', 'foo').then(enabled, notEnabled); + meetsExpectations(false); + }); + }); + + }); + +})(); diff --git a/horizon/test/jasmine/jasmine_tests.py b/horizon/test/jasmine/jasmine_tests.py index 01f41a2ce..fd6511564 100644 --- a/horizon/test/jasmine/jasmine_tests.py +++ b/horizon/test/jasmine/jasmine_tests.py @@ -22,6 +22,7 @@ class ServicesTests(test.JasmineTests): 'horizon/js/angular/services/horizon.utils.js', 'horizon/js/angular/hz.api.module.js', 'horizon/js/angular/services/hz.api.service.js', + 'horizon/js/angular/services/hz.api.config.js', 'angular/widget.module.js', 'angular/action-list/action-list.js', 'angular/action-list/button-tooltip.js', @@ -47,6 +48,7 @@ class ServicesTests(test.JasmineTests): ] specs = [ 'horizon/js/angular/services/hz.api.service.spec.js', + 'horizon/js/angular/services/hz.api.config.spec.js', 'horizon/tests/jasmine/utils.spec.js', 'angular/action-list/action-list.spec.js', 'angular/bind-scope/bind-scope.spec.js', |