Skip to content

Commit

Permalink
Merge pull request #7359 from w33ble/backport/navbar-injection
Browse files Browse the repository at this point in the history
[Backport] Navbar injection to 4.x
  • Loading branch information
epixa committed Jun 6, 2016
2 parents f5094b9 + 1b1479d commit 31bdad8
Show file tree
Hide file tree
Showing 13 changed files with 310 additions and 7 deletions.
3 changes: 2 additions & 1 deletion src/plugins/kibana/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ module.exports = function (kibana) {
main: 'plugins/kibana/kibana',
uses: [
'visTypes',
'spyModes'
'spyModes',
'navbarExtensions'
],

autoload: kibana.autoload.require.concat(
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/kibana/public/dashboard/index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div dashboard-app class="app-container dashboard-container">
<navbar ng-show="chrome.getVisible()">
<navbar ng-show="chrome.getVisible()" name="dashboard">
<span class="name" ng-if="dash.id" ng-bind="::dash.title" tooltip="{{::dash.title}}"></span>

<form name="queryInput"
Expand Down
1 change: 1 addition & 0 deletions src/plugins/kibana/public/dashboard/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ define(function (require) {
require('ui/config');
require('ui/notify');
require('ui/typeahead');
require('ui/navbar');
require('ui/share');

require('plugins/kibana/dashboard/directives/grid');
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/kibana/public/discover/index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div ng-controller="discover" class="app-container">
<navbar>
<navbar name="discover">
<form role="form" class="fill inline-form" ng-submit="fetch()" name="discoverSearch">
<div class="typeahead" kbn-typeahead="discover">
<div class="input-group"
Expand Down
5 changes: 3 additions & 2 deletions src/plugins/kibana/public/discover/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
define(function (require, module, exports) {
require('plugins/kibana/discover/saved_searches/saved_searches');
require('plugins/kibana/discover/directives/timechart');
require('ui/navbar');
require('ui/collapsible_sidebar');
require('plugins/kibana/discover/components/field_chooser/field_chooser');
require('plugins/kibana/discover/controllers/discover');
Expand All @@ -9,6 +10,6 @@ define(function (require, module, exports) {
// preload
require('ui/doc_table/components/table_row');

require('ui/saved_objects/saved_object_registry').register(require('plugins/kibana/discover/saved_searches/saved_search_register'));

var savedObjectRegistry = require('ui/saved_objects/saved_object_registry');
savedObjectRegistry.register(require('plugins/kibana/discover/saved_searches/saved_search_register'));
});
3 changes: 1 addition & 2 deletions src/plugins/kibana/public/visualize/editor/editor.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<div ng-controller="VisEditor" class="vis-editor vis-type-{{ vis.type.name }}">

<navbar ng-if="chrome.getVisible()">
<navbar ng-if="chrome.getVisible()" name="visualize">
<div class="fill bitty-modal-container">
<div ng-if="vis.type.requiresSearch && $state.linked && !unlinking"
ng-dblclick="unlink()"
Expand Down
1 change: 1 addition & 0 deletions src/plugins/kibana/public/visualize/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ define(function (require) {
require('plugins/kibana/visualize/editor/sidebar');
require('plugins/kibana/visualize/editor/agg_filter');

require('ui/navbar');
require('ui/visualize');
require('ui/collapsible_sidebar');
require('ui/share');
Expand Down
1 change: 1 addition & 0 deletions src/ui/UiExports.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class UiExports {
case 'fieldFormats':
case 'spyModes':
case 'chromeNavControls':
case 'navbarExtensions':
return (plugin, spec) => {
this.aliases[type] = _.union(this.aliases[type] || [], spec);
};
Expand Down
140 changes: 140 additions & 0 deletions src/ui/public/navbar/__tests__/navbar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
const ngMock = require('ngMock');
const sinon = require('sinon');
const expect = require('expect.js');
const angular = require('angular');
const _ = require('lodash');

require('ui/navbar');
const navbarExtensionsRegistry = require('ui/registry/navbar_extensions');
const Registry = require('ui/registry/_registry');

const defaultMarkup = `
<navbar name="testing">
<div class="button-group" role="toolbar">
<button>
<i aria-hidden="true" class="fa fa-file-new-o"></i>
</button>
<button>
<i aria-hidden="true" class="fa fa-save"></i>
</button>
<button>
<i aria-hidden="true" class="fa fa-folder-open-o"></i>
</button>
</div>
</navbar>`;


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 = `<navbar><div class="button-group" role="toolbar"></div></navbar>`;
expect(() => init(markup)).to.throwException(/requires a name attribute/);
});

it('should throw if missing a button group', function () {
const markup = `<navbar name="testing"></navbar>`;
expect(() => init(markup)).to.throwException(/must have exactly 1 button group/);
});

it('should throw if multiple button groups', function () {
const markup = ` <navbar name="testing">
<div class="button-group" role="toolbar">
<button>
<i aria-hidden="true" class="fa fa-file-new-o"></i>
</button>
<button>
<i aria-hidden="true" class="fa fa-save"></i>
</button>
</div>
<div class="button-group" role="toolbar">
<button>
<i aria-hidden="true" class="fa fa-folder-open-o"></i>
</button>
</div>
</navbar>`;
expect(() => init(markup)).to.throwException(/must have exactly 1 button group/);
});

it('should throw if button group not direct child', function () {
const markup = `<navbar><div><div class="button-group" role="toolbar"></div></div></navbar>`;
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: `
<button class="test-button">
<i aria-hidden="true" class="fa fa-rocket"></i>
</button>`
});
});
}

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();
});
});
});
56 changes: 56 additions & 0 deletions src/ui/public/navbar/navbar.js
Original file line number Diff line number Diff line change
@@ -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 = $(`<render-directive definition="navbar.extensions[${index}]"></render-directive>`);
$ext.html(extension.template);
$buttonGroup.append($ext);
});

return $el.html();
},
controllerAs: 'navbar',
controller: function ($attrs) {
this.extensions = getExtensions($attrs.name);
}
};
});
8 changes: 8 additions & 0 deletions src/ui/public/registry/navbar_extensions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
define(function (require) {
return require('ui/registry/_registry')({
name: 'navbarExtensions',
index: ['name'],
group: ['appName'],
order: ['order']
});
});
65 changes: 65 additions & 0 deletions src/ui/public/render_directive/__tests__/render_directive.js
Original file line number Diff line number Diff line change
@@ -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('<render-directive>');
$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 = '<p>hello world</p>';
const definition = {
link: sinon.stub(),
};

init(markup, definition);

sinon.assert.callCount(definition.link, 1);
});

it('should call controller method', function () {
const markup = '<p>hello world</p>';
const definition = {
controller: sinon.stub(),
};

init(markup, definition);

sinon.assert.callCount(definition.controller, 1);
});
});
});
30 changes: 30 additions & 0 deletions src/ui/public/render_directive/render_directive.js
Original file line number Diff line number Diff line change
@@ -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);
}
}
};
});

0 comments on commit 31bdad8

Please sign in to comment.