diff --git a/horizon/static/angular/metadata-display/metadata-display.html b/horizon/static/angular/metadata-display/metadata-display.html new file mode 100644 index 0000000000..4f79ba9002 --- /dev/null +++ b/horizon/static/angular/metadata-display/metadata-display.html @@ -0,0 +1,33 @@ +
+
+
+
+ +
+
+ +
+
+
+
+ + + + +
+
+
+
+
+ +
+
+ +
+
+
+
+
\ No newline at end of file diff --git a/horizon/static/angular/metadata-display/metadata-display.js b/horizon/static/angular/metadata-display/metadata-display.js new file mode 100644 index 0000000000..3ad072ede0 --- /dev/null +++ b/horizon/static/angular/metadata-display/metadata-display.js @@ -0,0 +1,122 @@ +(function () { + 'use strict'; + + /** + * @ngdoc overview + * @name hz.widget.metadata-display + * @description + * + * # hz.widget.metadata-display + * + * The `hz.widget.metadata-display` provides widget displaying metadata. + * + * | Directives | + * |---------------------------------------------------------------------------------------------| + * | {@link hz.widget.metadata-display.directive:hzMetadataDisplay `hzMetadataDisplay`} | + * |---------------------------------------------------------------------------------------------| + * | Controllers | + * |---------------------------------------------------------------------------------------------| + * | {@link hz.widget.metadata-display.controller:hzMetadataDisplayCtrl `hzMetadataDisplayCtrl`} | + * + */ + angular.module('hz.widget.metadata-display', [ + 'hz.widget.metadata-tree' + ]) + + /** + * @ngdoc parameters + * @name hz.widget.metadata-display:metadataTreeDefaults + * @param {object} text Text constants + */ + .constant('metadataDisplayDefaults', { + text: { + detail: gettext('Detail Information') + } + }) + + /** + * @ngdoc directive + * @name hz.widget.metadata-display.directive:hzMetadataDisplay + * @scope + * + * @description + * The `hzMetadataDisplay` displays existing metadata. + * + * @param {object[]} available List of available namespaces + * @param {object} existing Key-value pairs with existing properties + * @param {object=} text Text override + */ + .directive('hzMetadataDisplay', ['basePath', + function (path) { + return { + scope: { + available: '=', + existing: '=', + text: '=?' + }, + controller: 'hzMetadataDisplayCtrl', + templateUrl: path + 'metadata-display/metadata-display.html' + }; + } + ]) + + /** + * @ngdoc controller + * @name hz.widget.metadata-display.controller:hzMetadataDisplayCtrl + * @description + * Controller used by `hzMetadataDisplay` + */ + .controller('hzMetadataDisplayCtrl', [ + '$scope', 'metadataTreeService', 'metadataDisplayDefaults', + function ($scope, metadataTreeService, defaults) { + + function init() { + $scope.tree = new metadataTreeService.Tree($scope.available, $scope.existing); + angular.forEach($scope.tree.flatTree, function (item) { + if(item.added) { + if(!item.leaf) { + item.added = false; + if (item.parent) { + item.parent.addedCount -= 1; + } + } + else if(!item.custom) { + $scope.hide = false; + } + } + + }); + // select first item + $scope.tree.tree.some(function (item) { + if($scope.listFilter(item)) { + $scope.selected = item; + item.expand(true); + return true; // break + } + }); + } + + $scope.onSelect = function (item) { + $scope.selected.collapse(); + item.expand(true); + $scope.selected = item; + }; + + $scope.childrenFilter = function (item) { + return item.visible && item.leaf && item.added; + }; + + $scope.listFilter = function (item) { + return item.addedCount > 0; + }; + + $scope.text = angular.extend({}, defaults.text, $scope.text); + $scope.tree = null; + $scope.selected = null; + $scope.hide = true; + + init(); + } + ]); + +}()); diff --git a/horizon/static/angular/metadata-display/metadata-display.scss b/horizon/static/angular/metadata-display/metadata-display.scss new file mode 100644 index 0000000000..32f5893433 --- /dev/null +++ b/horizon/static/angular/metadata-display/metadata-display.scss @@ -0,0 +1,29 @@ +.metadata-display { + .selector { + .selector-item { + border-top: 1px solid $metadata-display-separator-color; + padding: 10px; + color: $metadata-display-selector-color; + + &:first-child { + border-top: none; + } + + &:hover { + color: $metadata-display-selector-hover-color; + } + + &.active { + color: $metadata-display-selector-active-color; + } + } + } + + .description { + margin-top: 20px; + } + + .auto-width { + width: auto; + } +} \ No newline at end of file diff --git a/horizon/static/angular/metadata-display/metadata-display.spec.js b/horizon/static/angular/metadata-display/metadata-display.spec.js new file mode 100644 index 0000000000..00d93963e0 --- /dev/null +++ b/horizon/static/angular/metadata-display/metadata-display.spec.js @@ -0,0 +1,135 @@ +/* jshint globalstrict: true */ +'use strict'; + +describe('hz.widget.metadata-display module', function() { + it('should have been defined', function () { + expect(angular.module('hz.widget.metadata-display')).toBeDefined(); + }); + + var namespaces = [ + { + "display_name": "Test Namespace A", + "description": "Test namespace description", + "properties": { + "test:A:1": { + "title": "Test A.1 - string", + "type": "string", + "default": "foo", + "enum": [ + "option-1", "option-2", "option-3" + ] + }, + "test:A:2": { + "title": "Test A.2 - integer", + "type": "integer", + "default": "1", + "minimum": 0, + "maximum": 10 + }, + "test:A:3": { + "title": "Test A.3 - number", + "type": "number", + "default": "1.1", + "minimum": 0, + "maximum": 10 + }, + "test:A:4": { + "title": "Test A.4 - boolean", + "type": "boolean", + "default": "True" + }, + "test:A:5": { + "title": "Test A.5 - boolean", + "type": "boolean", + "default": "false" + }, + "test:A:6": { + "title": "Test A.6 - array", + "type": "array", + "items": { + "type": "string", + "enum": [ + "val-1", "val-2", "val-3" + ] + } + } + } + }, + { + "display_name": "Test Namespace B", + "description": "Test namespace description", + "objects": [ + { + "name": "Test Object A", + "description": "Test object description", + "properties": { + "test:B:A:1": { + "title": "Test B.A.1", + "description": "Test description" + }, + "test:B:A:2": {} + } + }, + { + "name": "Test Object B", + "description": "Test object description", + "properties": { + "test:B:B:1": {}, + "test:B:B:2": {} + } + } + ] + } + ]; + + var existing = { + 'test:A:1': 'option-2', + 'test:A:2': '5', + 'test:B:A:1': 'foo', + 'test:B:B:1': 'bar' + }; + + describe('hzMetadataDisplay directive', function () { + var $scope, $element; + + beforeEach(module('templates')); + beforeEach(module('hz')); + beforeEach(module('hz.widgets')); + beforeEach(module('hz.widget.metadata-tree')); + beforeEach(module('hz.widget.metadata-display')); + beforeEach(inject(function ($injector) { + var $compile = $injector.get('$compile'); + $scope = $injector.get('$rootScope').$new(); + + $scope.available = namespaces; + $scope.existing = existing; + + var markup = + ''; + + $element = angular.element(markup); + $compile($element)($scope); + $scope.$digest(); + })); + + it('should have 3 rows in selector list', function() { + expect($element.find('.selector .selector-item').length).toBe(3); + }); + + it('should have 2 items in first group', function() { + expect($element.find('div[ng-repeat] div.auto-width').length).toBe(2); + }); + + it('should have 1 item in second group', function() { + $element.find('.selector .selector-item:nth-child(2)').trigger('click'); + expect($element.find('div[ng-repeat] div.auto-width').length).toBe(1); + }); + + it('should have proper description', function() { + expect($element.find('span[ng-bind="selected.description"]').text()).toBe(namespaces[0].description); + $element.find('.selector .selector-item:nth-child(2)').trigger('click'); + expect($element.find('span[ng-bind="selected.description"]').text()).toBe(namespaces[1].objects[0].description); + }); + }); + +}); diff --git a/horizon/static/angular/styles.scss b/horizon/static/angular/styles.scss index 769c9fca64..17545f7b12 100644 --- a/horizon/static/angular/styles.scss +++ b/horizon/static/angular/styles.scss @@ -7,3 +7,4 @@ @import "action-list/action-list"; @import "modal-wait-spinner/modal-wait-spinner"; @import "metadata-tree/metadata-tree"; +@import "metadata-display/metadata-display"; diff --git a/horizon/static/angular/widget.module.js b/horizon/static/angular/widget.module.js index b2102829f5..96bf226775 100644 --- a/horizon/static/angular/widget.module.js +++ b/horizon/static/angular/widget.module.js @@ -12,7 +12,8 @@ 'hz.widget.transfer-table', 'hz.widget.charts', 'hz.widget.action-list', - 'hz.widget.metadata-tree' + 'hz.widget.metadata-tree', + 'hz.widget.metadata-display' ]) .constant('basePath', '/static/angular/'); diff --git a/horizon/templates/horizon/_scripts.html b/horizon/templates/horizon/_scripts.html index fc0a0c5fd5..6a1731e43c 100644 --- a/horizon/templates/horizon/_scripts.html +++ b/horizon/templates/horizon/_scripts.html @@ -52,6 +52,7 @@ + diff --git a/horizon/templates/horizon/common/_modal_form_update_metadata.html b/horizon/templates/horizon/common/_modal_form_update_metadata.html index 1c8e1c962c..e66ba3b8cb 100644 --- a/horizon/templates/horizon/common/_modal_form_update_metadata.html +++ b/horizon/templates/horizon/common/_modal_form_update_metadata.html @@ -10,6 +10,8 @@ +