mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Merge pull request #7978 from tsullivan/notifications-render-directive
notifications: add directive notification type
This commit is contained in:
commit
1c7153c613
4 changed files with 298 additions and 38 deletions
|
@ -228,7 +228,7 @@ describe('Notifier', function () {
|
|||
customNotification = notifier.custom(customText, badParam);
|
||||
}
|
||||
expect(callCustomIncorrectly).to.throwException(function (e) {
|
||||
expect(e.message).to.be('config param is required, and must be an object');
|
||||
expect(e.message).to.be('Config param is required, and must be an object');
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -401,3 +401,179 @@ describe('Notifier', function () {
|
|||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('Directive Notification', function () {
|
||||
let notifier;
|
||||
let compile;
|
||||
let scope;
|
||||
|
||||
const directiveParam = {
|
||||
template: '<h1>Hello world {{ unit.message }}</h1>',
|
||||
controllerAs: 'unit',
|
||||
controller() {
|
||||
this.message = '🎉';
|
||||
}
|
||||
};
|
||||
const customParams = {
|
||||
title: 'fooTitle',
|
||||
actions:[{
|
||||
text: 'Cancel',
|
||||
callback: sinon.spy()
|
||||
}, {
|
||||
text: 'OK',
|
||||
callback: sinon.spy()
|
||||
}]
|
||||
};
|
||||
let directiveNotification;
|
||||
|
||||
beforeEach(() => {
|
||||
ngMock.module('kibana');
|
||||
ngMock.inject(function ($rootScope, $compile) {
|
||||
scope = $rootScope.$new();
|
||||
compile = $compile;
|
||||
compile;
|
||||
scope;
|
||||
});
|
||||
|
||||
while (Notifier.prototype._notifs.pop()); // clear global notifications
|
||||
|
||||
notifier = new Notifier({ location: 'directiveFoo' });
|
||||
directiveNotification = notifier.directive(directiveParam, customParams);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
directiveNotification.clear();
|
||||
scope.$destroy();
|
||||
});
|
||||
|
||||
describe('returns a renderable notification', () => {
|
||||
let element;
|
||||
|
||||
beforeEach(() => {
|
||||
scope.notif = notifier.directive(directiveParam, customParams);
|
||||
const template = `
|
||||
<render-directive
|
||||
definition="notif.directive"
|
||||
notif="notif"
|
||||
></render-directive>`;
|
||||
element = compile(template)(scope);
|
||||
scope.$apply();
|
||||
});
|
||||
|
||||
it('that renders with the provided template', () => {
|
||||
expect(element.find('h1').text()).to.contain('Hello world');
|
||||
});
|
||||
|
||||
it('that renders with the provided controller', () => {
|
||||
expect(element.text()).to.contain('🎉');
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if first param is not an object', () => {
|
||||
// destroy the default custom notification, avoid duplicate handling
|
||||
directiveNotification.clear();
|
||||
|
||||
function callDirectiveIncorrectly() {
|
||||
const badDirectiveParam = null;
|
||||
directiveNotification = notifier.directive(badDirectiveParam, {});
|
||||
}
|
||||
expect(callDirectiveIncorrectly).to.throwException(function (e) {
|
||||
expect(e.message).to.be('Directive param is required, and must be an object');
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if second param is not an object', () => {
|
||||
// destroy the default custom notification, avoid duplicate handling
|
||||
directiveNotification.clear();
|
||||
|
||||
function callDirectiveIncorrectly() {
|
||||
const badConfigParam = null;
|
||||
directiveNotification = notifier.directive(directiveParam, badConfigParam);
|
||||
}
|
||||
expect(callDirectiveIncorrectly).to.throwException(function (e) {
|
||||
expect(e.message).to.be('Config param is required, and must be an object');
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if directive param has scope definition instead of allow the helper to do its work', () => {
|
||||
// destroy the default custom notification, avoid duplicate handling
|
||||
directiveNotification.clear();
|
||||
|
||||
function callDirectiveIncorrectly() {
|
||||
const badDirectiveParam = {
|
||||
scope: {
|
||||
garbage: '='
|
||||
}
|
||||
};
|
||||
directiveNotification = notifier.directive(badDirectiveParam, customParams);
|
||||
}
|
||||
expect(callDirectiveIncorrectly).to.throwException(function (e) {
|
||||
expect(e.message).to.be('Directive should not have a scope definition. Notifier has an internal implementation.');
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if directive param has link function instead of allow the helper to do its work', () => {
|
||||
// destroy the default custom notification, avoid duplicate handling
|
||||
directiveNotification.clear();
|
||||
|
||||
function callDirectiveIncorrectly() {
|
||||
const badDirectiveParam = {
|
||||
link: ($scope) => {
|
||||
/*eslint-disable no-console*/
|
||||
console.log($scope.nothing);
|
||||
/*eslint-enable*/
|
||||
}
|
||||
};
|
||||
directiveNotification = notifier.directive(badDirectiveParam, customParams);
|
||||
}
|
||||
expect(callDirectiveIncorrectly).to.throwException(function (e) {
|
||||
expect(e.message).to.be('Directive should not have a link function. Notifier has an internal link function helper.');
|
||||
});
|
||||
});
|
||||
|
||||
it('has a directive function to make notifications with template and scope', () => {
|
||||
expect(notifier.directive).to.be.a('function');
|
||||
});
|
||||
|
||||
it('sets the scope property and link function', () => {
|
||||
expect(directiveNotification).to.have.property('directive');
|
||||
expect(directiveNotification.directive).to.be.an('object');
|
||||
|
||||
expect(directiveNotification.directive).to.have.property('scope');
|
||||
expect(directiveNotification.directive.scope).to.be.an('object');
|
||||
|
||||
expect(directiveNotification.directive).to.have.property('link');
|
||||
expect(directiveNotification.directive.link).to.be.an('function');
|
||||
});
|
||||
|
||||
/* below copied from custom notification tests */
|
||||
it('uses custom actions', () => {
|
||||
expect(directiveNotification).to.have.property('customActions');
|
||||
expect(directiveNotification.customActions).to.have.length(customParams.actions.length);
|
||||
});
|
||||
|
||||
it('gives a default action if none are provided', () => {
|
||||
// destroy the default custom notification, avoid duplicate handling
|
||||
directiveNotification.clear();
|
||||
|
||||
const noActionParams = _.defaults({ actions: [] }, customParams);
|
||||
directiveNotification = notifier.directive(directiveParam, noActionParams);
|
||||
expect(directiveNotification).to.have.property('actions');
|
||||
expect(directiveNotification.actions).to.have.length(1);
|
||||
});
|
||||
|
||||
it('defaults type and lifetime for "info" config', () => {
|
||||
expect(directiveNotification.type).to.be('info');
|
||||
expect(directiveNotification.lifetime).to.be(5000);
|
||||
});
|
||||
|
||||
it('should wrap the callback functions in a close function', () => {
|
||||
directiveNotification.customActions.forEach((action, idx) => {
|
||||
expect(action.callback).not.to.equal(customParams.actions[idx]);
|
||||
action.callback();
|
||||
});
|
||||
customParams.actions.forEach(action => {
|
||||
expect(action.callback.called).to.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import _ from 'lodash';
|
||||
import angular from 'angular';
|
||||
import $ from 'jquery';
|
||||
import metadata from 'ui/metadata';
|
||||
import formatMsg from 'ui/notify/lib/_format_msg';
|
||||
import fatalSplashScreen from 'ui/notify/partials/fatal_splash_screen.html';
|
||||
import 'ui/render_directive';
|
||||
/* eslint no-console: 0 */
|
||||
|
||||
let notifs = [];
|
||||
|
@ -109,9 +111,12 @@ function add(notif, cb) {
|
|||
return notif.timerId ? true : false;
|
||||
};
|
||||
|
||||
let dup = _.find(notifs, function (item) {
|
||||
return item.content === notif.content && item.lifetime === notif.lifetime;
|
||||
});
|
||||
let dup = null;
|
||||
if (notif.content) {
|
||||
dup = _.find(notifs, function (item) {
|
||||
return item.content === notif.content && item.lifetime === notif.lifetime;
|
||||
});
|
||||
}
|
||||
|
||||
if (dup) {
|
||||
dup.count += 1;
|
||||
|
@ -405,28 +410,13 @@ Notifier.prototype.banner = function (msg, cb) {
|
|||
};
|
||||
|
||||
/**
|
||||
* Display a custom message
|
||||
* @param {String} msg - required
|
||||
* @param {Object} config - required
|
||||
* @param {Function} cb - optional
|
||||
*
|
||||
* config = {
|
||||
* title: 'Some Title here',
|
||||
* type: 'info',
|
||||
* actions: [{
|
||||
* text: 'next',
|
||||
* callback: function() { next(); }
|
||||
* }, {
|
||||
* text: 'prev',
|
||||
* callback: function() { prev(); }
|
||||
* }]
|
||||
* }
|
||||
* Helper for common behavior in custom and directive types
|
||||
*/
|
||||
Notifier.prototype.custom = function (msg, config, cb) {
|
||||
function getDecoratedCustomConfig(config) {
|
||||
// There is no helper condition that will allow for 2 parameters, as the
|
||||
// other methods have. So check that config is an object
|
||||
if (!_.isPlainObject(config)) {
|
||||
throw new Error('config param is required, and must be an object');
|
||||
throw new Error('Config param is required, and must be an object');
|
||||
}
|
||||
|
||||
// workaround to allow callers to send `config.type` as `error` instead of
|
||||
|
@ -449,23 +439,104 @@ Notifier.prototype.custom = function (msg, config, cb) {
|
|||
}
|
||||
};
|
||||
|
||||
const mergedConfig = _.assign({
|
||||
const customConfig = _.assign({
|
||||
type: 'info',
|
||||
title: 'Notification',
|
||||
content: formatMsg(msg, this.from),
|
||||
truncationLength: config.truncationLength || Notifier.config.defaultTruncationLength,
|
||||
lifetime: getLifetime(config.type)
|
||||
}, config);
|
||||
|
||||
const hasActions = _.get(mergedConfig, 'actions.length');
|
||||
const hasActions = _.get(customConfig, 'actions.length');
|
||||
if (hasActions) {
|
||||
mergedConfig.customActions = mergedConfig.actions;
|
||||
delete mergedConfig.actions;
|
||||
customConfig.customActions = customConfig.actions;
|
||||
delete customConfig.actions;
|
||||
} else {
|
||||
mergedConfig.actions = ['accept'];
|
||||
customConfig.actions = ['accept'];
|
||||
}
|
||||
|
||||
return add(mergedConfig, cb);
|
||||
return customConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a custom message
|
||||
* @param {String} msg - required
|
||||
* @param {Object} config - required
|
||||
* @param {Function} cb - optional
|
||||
*
|
||||
* config = {
|
||||
* title: 'Some Title here',
|
||||
* type: 'info',
|
||||
* actions: [{
|
||||
* text: 'next',
|
||||
* callback: function() { next(); }
|
||||
* }, {
|
||||
* text: 'prev',
|
||||
* callback: function() { prev(); }
|
||||
* }]
|
||||
* }
|
||||
*/
|
||||
Notifier.prototype.custom = function (msg, config, cb) {
|
||||
const customConfig = getDecoratedCustomConfig(config);
|
||||
customConfig.content = formatMsg(msg, this.from);
|
||||
return add(customConfig, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* Display a scope-bound directive using template rendering in the message area
|
||||
* @param {Object} directive - required
|
||||
* @param {Object} config - required
|
||||
* @param {Function} cb - optional
|
||||
*
|
||||
* directive = {
|
||||
* template: `<p>Hello World! <a ng-click="example.clickHandler()">Click me</a>.`,
|
||||
* controllerAs: 'example',
|
||||
* controller() {
|
||||
* this.clickHandler = () {
|
||||
* // do something
|
||||
* };
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* config = {
|
||||
* title: 'Some Title here',
|
||||
* type: 'info',
|
||||
* actions: [{
|
||||
* text: 'next',
|
||||
* callback: function() { next(); }
|
||||
* }, {
|
||||
* text: 'prev',
|
||||
* callback: function() { prev(); }
|
||||
* }]
|
||||
* }
|
||||
*/
|
||||
Notifier.prototype.directive = function (directive, config, cb) {
|
||||
if (!_.isPlainObject(directive)) {
|
||||
throw new Error('Directive param is required, and must be an object');
|
||||
}
|
||||
if (!Notifier.$compile) {
|
||||
throw new Error('Unable to use the directive notification until Angular has initialized.');
|
||||
}
|
||||
if (directive.scope) {
|
||||
throw new Error('Directive should not have a scope definition. Notifier has an internal implementation.');
|
||||
}
|
||||
if (directive.link) {
|
||||
throw new Error('Directive should not have a link function. Notifier has an internal link function helper.');
|
||||
}
|
||||
|
||||
// make a local copy of the directive param (helps unit tests)
|
||||
const localDirective = _.clone(directive, true);
|
||||
|
||||
localDirective.scope = { notif: '=' };
|
||||
localDirective.link = function link($scope, $el) {
|
||||
const $template = angular.element($scope.notif.directive.template);
|
||||
const postLinkFunction = Notifier.$compile($template);
|
||||
$el.html($template);
|
||||
postLinkFunction($scope);
|
||||
};
|
||||
|
||||
const customConfig = getDecoratedCustomConfig(config);
|
||||
customConfig.directive = localDirective;
|
||||
return add(customConfig, cb);
|
||||
};
|
||||
|
||||
Notifier.prototype.describeError = formatMsg.describeError;
|
||||
|
|
|
@ -20,12 +20,13 @@ module.factory('Notifier', function () {
|
|||
});
|
||||
|
||||
// teach Notifier how to use angular interval services
|
||||
module.run(function (config, $interval) {
|
||||
module.run(function (config, $interval, $compile) {
|
||||
Notifier.applyConfig({
|
||||
setInterval: $interval,
|
||||
clearInterval: $interval.cancel
|
||||
});
|
||||
applyConfig(config);
|
||||
Notifier.$compile = $compile;
|
||||
});
|
||||
|
||||
// if kibana is not included then the notify service can't
|
||||
|
|
|
@ -7,7 +7,20 @@
|
|||
|
||||
<i class="fa" ng-class="'fa-' + notif.icon" tooltip="{{notif.title}}"></i>
|
||||
|
||||
<kbn-truncated source="{{notif.content | markdown}}" is-html="true" length="{{notif.truncationLength}}" class="toast-message" /></kbn-truncated>
|
||||
<kbn-truncated
|
||||
ng-if="notif.content"
|
||||
source="{{notif.content | markdown}}"
|
||||
is-html="true"
|
||||
length="{{notif.truncationLength}}"
|
||||
class="toast-message"
|
||||
></kbn-truncated>
|
||||
|
||||
<render-directive
|
||||
ng-if="notif.directive"
|
||||
definition="notif.directive"
|
||||
notif="notif"
|
||||
class="toast-message"
|
||||
></render-directive>
|
||||
|
||||
<div class="btn-group pull-right toast-controls">
|
||||
<button
|
||||
|
@ -16,36 +29,35 @@
|
|||
class="btn toaster-countdown"
|
||||
ng-class="'btn-' + notif.type"
|
||||
ng-click="notif.cancelTimer()"
|
||||
|
||||
><span class="badge" hover-text="stop">{{notif.timeRemaining}}s</span></button>
|
||||
><span class="badge" hover-text="stop">{{notif.timeRemaining}}s</span></button>
|
||||
<button
|
||||
type="button"
|
||||
ng-if="notif.stack && !notif.showStack"
|
||||
class="btn"
|
||||
ng-class="'btn-' + notif.type"
|
||||
ng-click="notif.cancelTimer(); notif.showStack = true"
|
||||
>More Info</button>
|
||||
>More Info</button>
|
||||
<button
|
||||
type="button"
|
||||
ng-if="notif.stack && notif.showStack"
|
||||
class="btn"
|
||||
ng-class="'btn-' + notif.type"
|
||||
ng-click="notif.showStack = false"
|
||||
>Less Info</button>
|
||||
>Less Info</button>
|
||||
<button
|
||||
type="button"
|
||||
ng-if="notif.accept"
|
||||
class="btn"
|
||||
ng-class="'btn-' + notif.type"
|
||||
ng-click="notif.accept()"
|
||||
>OK</button>
|
||||
>OK</button>
|
||||
<button
|
||||
type="button"
|
||||
ng-if="notif.address"
|
||||
class="btn"
|
||||
ng-class="'btn-' + notif.type"
|
||||
ng-click="notif.address()"
|
||||
>Fix it</button>
|
||||
>Fix it</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn"
|
||||
|
@ -53,7 +65,7 @@
|
|||
ng-class="'btn-' + notif.type"
|
||||
ng-click="action.callback()"
|
||||
ng-bind="action.key"
|
||||
></button>
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue