diff --git a/openstack_dashboard/static/app/core/cloud-services/hz-if-version.directive.js b/openstack_dashboard/static/app/core/cloud-services/hz-if-version.directive.js new file mode 100644 index 0000000000..35ef1b9fa3 --- /dev/null +++ b/openstack_dashboard/static/app/core/cloud-services/hz-if-version.directive.js @@ -0,0 +1,127 @@ +/* + * Copyright 2015 IBM Corp. + * + * 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'; + + angular + .module('horizon.app.core.cloud-services') + .directive('hzIfApiVersion', hzIfApiVersion); + + hzIfApiVersion.$inject = [ + '$q', + 'hzPromiseToggleTemplateDirective', + 'horizon.app.core.openstack-service-api.glance', + 'horizon.app.core.openstack-service-api.keystone' + ]; + + /** + * @ngdoc directive + * @name horizon.app.core.cloud-services:hzIfApiVersion + * @module horizon.app.core.cloud-services + * + * @description + * Add this directive to any element containing content which should only be + * evaluated if the api version satisfies the given condition. + * + * In addition, the element and everything contained by it will + * be removed completely, leaving a simple HTML comment. + * + * This is evaluated once per page load. In current horizon, this means it + * will get re-evaluated with events like the user opening another panel, + * changing logins, or changing their region. + * + * The hz-if-api-version attribute is an object that contains two properties, e.g. + * (1) "": + * the API name and the version number to check + * (2) "operator": + * evaluate the comparison between the actual and expected api version + * number using this operator (optional) + * + * @example + * + * In the first code example below, if keystone v3 is not enabled, then the + * div element with hz-if-api-version and all of the elements inside of it will + * be removed and never evaluated by the angular compiler. + * + * Optionally, you may pass in an operator for a simple API version comparison + * (<=, <, =, >, >=), as well as compound several API version checks. + * + * + ```html +
+ +
+ +
+ +
+ +
+ +
+ ``` + */ + function hzIfApiVersion($q, hzPromiseToggle, glance, keystone) { + return angular.extend(hzPromiseToggle[0], { + singlePromiseResolver: ifVersionEnabled, + name: 'hzIfApiVersion' + }); + + function ifVersionEnabled(expected) { + var deferred = $q.defer(); + + var type = Object.keys(expected)[0]; + var operator = expected[Object.keys(expected)[1]]; + + switch (type) { + case "glance": glance.getVersion().then(onSuccess); break; + case "keystone": keystone.getVersion().then(onSuccess); break; + } + + function onSuccess(actual) { + // if operator is passed in, handle simple comparisons + // check data types to avoid mishandling bad content + var actualVersion = actual.data.version; + var expectedVersion = expected[type]; + var isVersion = false; + + if (operator) { + if (angular.isNumber(actualVersion) && + angular.isNumber(expectedVersion) && + ["<=", "<", "==", ">", ">="].indexOf(operator) > -1) { + var expr = actualVersion + operator + expectedVersion; + /*eslint-disable no-eval */ + isVersion = eval(expr); + /*eslint-enable no-eval */ + } + } else { + if (angular.equals(actualVersion, expectedVersion)) { + isVersion = true; + } + } + + if (isVersion) { + deferred.resolve(); + } else { + deferred.reject(); + } + } + return deferred.promise; + } + } + +})(); diff --git a/openstack_dashboard/static/app/core/cloud-services/hz-if-version.directive.spec.js b/openstack_dashboard/static/app/core/cloud-services/hz-if-version.directive.spec.js new file mode 100644 index 0000000000..a55f2a80e8 --- /dev/null +++ b/openstack_dashboard/static/app/core/cloud-services/hz-if-version.directive.spec.js @@ -0,0 +1,153 @@ +/* + * Copyright 2015 IBM Corp. + * + * 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'; + + describe('horizon.app.core.cloud-services.hzIfApiVersion', function() { + + function fakeAPI() { + return { + then: function(callback) { + var actual = { data: { version: 3 } }; + callback(actual); + } + }; + } + + var $compile, $scope, keystoneAPI; + + var $mockHtmlInput = '$mock-input'; + var baseHtml = [ + '
', + '
', + '
', + '
', + '
', + '
' + ].join(''); + + /////////////////////// + + beforeEach(module('horizon.app.core.openstack-service-api')); + beforeEach(module('horizon.app.core.cloud-services')); + beforeEach(module('horizon.framework.conf')); + beforeEach(module('horizon.framework.util.http')); + beforeEach(module('horizon.framework.util.promise-toggle')); + beforeEach(module('horizon.framework.widgets.toast')); + + beforeEach(inject(function($injector) { + keystoneAPI = $injector.get('horizon.app.core.openstack-service-api.keystone'); + spyOn(keystoneAPI, 'getVersion').and.callFake(fakeAPI); + })); + + beforeEach(inject(function($injector) { + $compile = $injector.get('$compile'); + $scope = $injector.get('$rootScope'); + })); + + it('should evaluate child elements when version is correct', function () { + var params = '{\"keystone\": 3}'; + var template = baseHtml.replace($mockHtmlInput, params); + var element = $compile(template)($scope); + expect(element.children().length).toBe(0); + + $scope.$apply(); + expect(element.children().length).toBe(1); + }); + + it('should not evaluate child elements when version is wrong', function () { + var params = '{\"keystone\": 1}'; + var template = baseHtml.replace($mockHtmlInput, params); + var element = $compile(template)($scope); + expect(element.children().length).toBe(0); + + $scope.$apply(); + expect(element.children().length).toBe(0); + }); + + it('should evaluate child elements when version <= given value', function () { + var params = '{\"keystone\": 4, \"operator\": \"<\"}'; + var template = baseHtml.replace($mockHtmlInput, params); + var element = $compile(template)($scope); + expect(element.children().length).toBe(0); + + $scope.$apply(); + expect(element.children().length).toBe(1); + }); + + it('should evaluate child elements when version < given value', function () { + var params = '{\"keystone\": 4, \"operator\": \"<\"}'; + var template = baseHtml.replace($mockHtmlInput, params); + var element = $compile(template)($scope); + expect(element.children().length).toBe(0); + + $scope.$apply(); + expect(element.children().length).toBe(1); + }); + + it('should NOT evaluate child elements when version != given value', function () { + var params = '{\"keystone\": 4, \"operator\": \"==\"}'; + var template = baseHtml.replace($mockHtmlInput, params); + var element = $compile(template)($scope); + expect(element.children().length).toBe(0); + + $scope.$apply(); + expect(element.children().length).toBe(0); + }); + + it('should evaluate child elements when version > given value', function () { + var params = '{\"keystone\": 4, \"operator\": \">\"}'; + var template = baseHtml.replace($mockHtmlInput, params); + var element = $compile(template)($scope); + expect(element.children().length).toBe(0); + + $scope.$apply(); + expect(element.children().length).toBe(0); + }); + + it('should evaluate child elements when version >= given value', function () { + var params = '{\"keystone\": 3, \"operator\": \">=\"}'; + var template = baseHtml.replace($mockHtmlInput, params); + var element = $compile(template)($scope); + expect(element.children().length).toBe(0); + + $scope.$apply(); + expect(element.children().length).toBe(1); + }); + + it('should NOT evaluate child elements when operator attr is wrong', function () { + var params = '{\"keystone\": 3, \"operator\": \"hi\"}'; + var template = baseHtml.replace($mockHtmlInput, params); + var element = $compile(template)($scope); + expect(element.children().length).toBe(0); + + $scope.$apply(); + expect(element.children().length).toBe(0); + }); + + it('should NOT evaluate child elements when attrs are wrong', function () { + var params = '{\"pine\": \"apple\"}'; + var template = baseHtml.replace($mockHtmlInput, params); + var element = $compile(template)($scope); + expect(element.children().length).toBe(0); + + $scope.$apply(); + expect(element.children().length).toBe(0); + }); + + }); +})();