From a52a9bc3de896d0ba8eab90a652b908f711ecf97 Mon Sep 17 00:00:00 2001 From: Joe Fleming Date: Thu, 14 Jan 2016 11:26:48 -0700 Subject: [PATCH 1/3] Backport - uiExports navbarExtensions bdfc2e4722d7cce05ecea317d02e008e9a922c61 --- src/plugins/kibana/index.js | 3 ++- src/plugins/kibana/public/visualize/editor/editor.html | 1 - src/ui/UiExports.js | 1 + src/ui/public/registry/navbar_extensions.js | 8 ++++++++ 4 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 src/ui/public/registry/navbar_extensions.js diff --git a/src/plugins/kibana/index.js b/src/plugins/kibana/index.js index fa9504f9d339e5..8009bbc0bd0e9d 100644 --- a/src/plugins/kibana/index.js +++ b/src/plugins/kibana/index.js @@ -17,7 +17,8 @@ module.exports = function (kibana) { main: 'plugins/kibana/kibana', uses: [ 'visTypes', - 'spyModes' + 'spyModes', + 'navbarExtensions' ], autoload: kibana.autoload.require.concat( diff --git a/src/plugins/kibana/public/visualize/editor/editor.html b/src/plugins/kibana/public/visualize/editor/editor.html index 248663f3fd2c02..4549d1b2fe18bf 100644 --- a/src/plugins/kibana/public/visualize/editor/editor.html +++ b/src/plugins/kibana/public/visualize/editor/editor.html @@ -1,5 +1,4 @@
-
{ this.aliases[type] = _.union(this.aliases[type] || [], spec); }; diff --git a/src/ui/public/registry/navbar_extensions.js b/src/ui/public/registry/navbar_extensions.js new file mode 100644 index 00000000000000..085f85771faa45 --- /dev/null +++ b/src/ui/public/registry/navbar_extensions.js @@ -0,0 +1,8 @@ +define(function (require) { + return require('ui/registry/_registry')({ + name: 'navbarExtensions', + index: ['name'], + group: ['appName'], + order: ['order'] + }); +}); From 111dbc56b889cf1593194c9fcef4e031bbce54bf Mon Sep 17 00:00:00 2001 From: Joe Fleming Date: Fri, 15 Jan 2016 08:49:13 -0700 Subject: [PATCH 2/3] Backport - render directive add tests for render_directive sinon asserts, null instead of false --- .../__tests__/render_directive.js | 65 +++++++++++++++++++ .../render_directive/render_directive.js | 30 +++++++++ 2 files changed, 95 insertions(+) create mode 100644 src/ui/public/render_directive/__tests__/render_directive.js create mode 100644 src/ui/public/render_directive/render_directive.js diff --git a/src/ui/public/render_directive/__tests__/render_directive.js b/src/ui/public/render_directive/__tests__/render_directive.js new file mode 100644 index 00000000000000..1168bed286a4ff --- /dev/null +++ b/src/ui/public/render_directive/__tests__/render_directive.js @@ -0,0 +1,65 @@ +const angular = require('angular'); +const sinon = require('sinon'); +const expect = require('expect.js'); +const ngMock = require('ngMock'); + +require('ui/render_directive'); + +let $parentScope; +let $elem; +let $directiveScope; + +function init(markup = '', definition = {}) { + ngMock.module('kibana/render_directive'); + + // Create the scope + ngMock.inject(function ($rootScope, $compile) { + $parentScope = $rootScope; + + // create the markup + $elem = angular.element(''); + $elem.html(markup); + if (definition !== null) { + $parentScope.definition = definition; + $elem.attr('definition', 'definition'); + } + + // compile the directive + $compile($elem)($parentScope); + $parentScope.$apply(); + + $directiveScope = $elem.isolateScope(); + }); +} + +describe('render_directive', function () { + describe('directive requirements', function () { + it('should throw if not given a definition', function () { + expect(() => init('', null)).to.throwException(/must have a definition/); + }); + }); + + describe('rendering with definition', function () { + it('should call link method', function () { + const markup = '

hello world

'; + const definition = { + link: sinon.stub(), + }; + + init(markup, definition); + + sinon.assert.callCount(definition.link, 1); + }); + + it('should call controller method', function () { + const markup = '

hello world

'; + const definition = { + controller: sinon.stub(), + }; + + init(markup, definition); + + sinon.assert.callCount(definition.controller, 1); + }); + }); +}); diff --git a/src/ui/public/render_directive/render_directive.js b/src/ui/public/render_directive/render_directive.js new file mode 100644 index 00000000000000..21ee171e90e05f --- /dev/null +++ b/src/ui/public/render_directive/render_directive.js @@ -0,0 +1,30 @@ +const _ = require('lodash'); +const $ = require('jquery'); +const module = require('ui/modules').get('kibana/render_directive'); + +module.directive('renderDirective', function () { + return { + restrict: 'E', + scope: { + 'definition': '=' + }, + template: function ($el, $attrs) { + return $el.html(); + }, + controller: function ($scope, $element, $attrs, $transclude) { + if (!$scope.definition) throw new Error('render-directive must have a definition attribute'); + + const { controller, controllerAs } = $scope.definition; + if (controller) { + if (controllerAs) $scope[controllerAs] = this; + $scope.$eval(controller, { $scope, $element, $attrs, $transclude }); + } + }, + link: function ($scope, $el, $attrs) { + const { link } = $scope.definition; + if (link) { + link($scope, $el, $attrs); + } + } + }; +}); \ No newline at end of file From 1b1479dc41fabee29aa5645f1c30fbb4c52e2a4f Mon Sep 17 00:00:00 2001 From: Joe Fleming Date: Tue, 19 Jan 2016 13:25:51 -0700 Subject: [PATCH 3/3] Backport - update navbar directive consume navbar directive used anywhere the navbar is being used currently navbar directive tests --- .../kibana/public/dashboard/index.html | 2 +- src/plugins/kibana/public/dashboard/index.js | 1 + src/plugins/kibana/public/discover/index.html | 2 +- src/plugins/kibana/public/discover/index.js | 5 +- .../public/visualize/editor/editor.html | 2 +- .../kibana/public/visualize/editor/editor.js | 1 + src/ui/public/navbar/__tests__/navbar.js | 140 ++++++++++++++++++ src/ui/public/navbar/navbar.js | 56 +++++++ 8 files changed, 204 insertions(+), 5 deletions(-) create mode 100644 src/ui/public/navbar/__tests__/navbar.js create mode 100644 src/ui/public/navbar/navbar.js diff --git a/src/plugins/kibana/public/dashboard/index.html b/src/plugins/kibana/public/dashboard/index.html index d2a2688c801c31..9a3f94fa3832eb 100644 --- a/src/plugins/kibana/public/dashboard/index.html +++ b/src/plugins/kibana/public/dashboard/index.html @@ -1,5 +1,5 @@
- +
- +
- +
+ + `; + + +describe('navbar directive', function () { + let $rootScope; + let $compile; + let stubRegistry; + + beforeEach(function () { + ngMock.module('kibana', function (PrivateProvider) { + stubRegistry = new Registry({ + index: ['name'], + group: ['appName'], + order: ['order'] + }); + + PrivateProvider.swap(navbarExtensionsRegistry, stubRegistry); + }); + + ngMock.module('kibana/navbar'); + + // Create the scope + ngMock.inject(function ($injector) { + $rootScope = $injector.get('$rootScope'); + $compile = $injector.get('$compile'); + }); + }); + + function init(markup = defaultMarkup) { + // Give us a scope + const $el = angular.element(markup); + $compile($el)($rootScope); + $el.scope().$digest(); + return $el; + } + + describe('incorrect use', function () { + it('should throw if missing a name property', function () { + const markup = ``; + expect(() => init(markup)).to.throwException(/requires a name attribute/); + }); + + it('should throw if missing a button group', function () { + const markup = ``; + expect(() => init(markup)).to.throwException(/must have exactly 1 button group/); + }); + + it('should throw if multiple button groups', function () { + const markup = ` + + + `; + expect(() => init(markup)).to.throwException(/must have exactly 1 button group/); + }); + + it('should throw if button group not direct child', function () { + const markup = `
`; + expect(() => init(markup)).to.throwException(/must have exactly 1 button group/); + }); + }); + + describe('injecting extensions', function () { + function registerExtension(def = {}) { + stubRegistry.register(function () { + return _.defaults(def, { + name: 'exampleButton', + appName: 'testing', + order: 0, + template: ` + ` + }); + }); + } + + it('should use the default markup', function () { + var $el = init(); + expect($el.find('.button-group button').length).to.equal(3); + }); + + it('should append to end then order == 0', function () { + registerExtension({ order: 0 }); + var $el = init(); + + expect($el.find('.button-group button').length).to.equal(4); + expect($el.find('.button-group button').last().hasClass('test-button')).to.be.ok(); + }); + + it('should append to end then order > 0', function () { + registerExtension({ order: 1 }); + var $el = init(); + + expect($el.find('.button-group button').length).to.equal(4); + expect($el.find('.button-group button').last().hasClass('test-button')).to.be.ok(); + }); + + it('should append to end then order < 0', function () { + registerExtension({ order: -1 }); + var $el = init(); + + expect($el.find('.button-group button').length).to.equal(4); + expect($el.find('.button-group button').first().hasClass('test-button')).to.be.ok(); + }); + }); +}); diff --git a/src/ui/public/navbar/navbar.js b/src/ui/public/navbar/navbar.js new file mode 100644 index 00000000000000..2e49a760708bea --- /dev/null +++ b/src/ui/public/navbar/navbar.js @@ -0,0 +1,56 @@ +const _ = require('lodash'); +const $ = require('jquery'); +const navbar = require('ui/modules').get('kibana/navbar'); + +require('ui/render_directive'); + +navbar.directive('navbar', function (Private, $compile) { + const navbarExtensions = Private(require('ui/registry/navbar_extensions')); + const getExtensions = _.memoize(function (name) { + if (!name) throw new Error('navbar directive requires a name attribute'); + return _.sortBy(navbarExtensions.byAppName[name], 'order'); + }); + + return { + restrict: 'E', + template: function ($el, $attrs) { + const $buttonGroup = $el.children('.button-group'); + if ($buttonGroup.length !== 1) throw new Error('navbar must have exactly 1 button group'); + + const extensions = getExtensions($attrs.name); + const buttons = $buttonGroup.children().detach().toArray(); + const controls = [ + ...buttons.map(function (button) { + return { + order: 0, + $el: $(button), + }; + }), + ...extensions.map(function (extension, i) { + return { + order: extension.order, + index: i, + extension: extension, + }; + }), + ]; + + _.sortBy(controls, 'order').forEach(function (control) { + if (control.$el) { + return $buttonGroup.append(control.$el); + } + + const { extension, index } = control; + const $ext = $(``); + $ext.html(extension.template); + $buttonGroup.append($ext); + }); + + return $el.html(); + }, + controllerAs: 'navbar', + controller: function ($attrs) { + this.extensions = getExtensions($attrs.name); + } + }; +});