diff --git a/src/ui/public/notify/__tests__/notifier.js b/src/ui/public/notify/__tests__/notifier.js index 1d0e5290c9120..8d8755529a59e 100644 --- a/src/ui/public/notify/__tests__/notifier.js +++ b/src/ui/public/notify/__tests__/notifier.js @@ -2,13 +2,25 @@ describe('Notifier', function () { let _ = require('lodash'); let ngMock = require('ngMock'); let expect = require('expect.js'); + let sinon = require('sinon'); let Notifier = require('ui/notify/notifier'); - let message = 'Oh, the humanity!'; let notifier; let params; - let version = window.__KBN__.version; - let buildNum = window.__KBN__.buildNum; + const version = window.__KBN__.version; + const buildNum = window.__KBN__.buildNum; + const message = 'Oh, the humanity!'; + const customText = 'fooMarkup'; + const customParams = { + title: 'fooTitle', + actions:[{ + text: 'Cancel', + callback: sinon.spy() + }, { + text: 'OK', + callback: sinon.spy() + }] + }; beforeEach(ngMock.module('kibana')); @@ -138,6 +150,117 @@ describe('Notifier', function () { }); }); + describe('#custom', function () { + let customNotification; + + beforeEach(() => { + customNotification = notifier.custom(customText, customParams); + }); + + afterEach(() => { + customNotification.clear(); + }); + + it('throws if second param is not an object', function () { + // destroy the default custom notification, avoid duplicate handling + customNotification.clear(); + + function callCustomIncorrectly() { + const badParam = null; + 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'); + }); + + }); + + it('has a custom function to make notifications', function () { + expect(notifier.custom).to.be.a('function'); + }); + + it('properly merges options', function () { + // destroy the default custom notification, avoid duplicate handling + customNotification.clear(); + + const explicitLifetimeParams = _.defaults({ lifetime: 20000 }, customParams); + customNotification = notifier.custom(customText, explicitLifetimeParams); + + expect(customNotification).to.have.property('type', 'info'); // default + expect(customNotification).to.have.property('title', explicitLifetimeParams.title); // passed in + expect(customNotification).to.have.property('lifetime', explicitLifetimeParams.lifetime); // passed in + + expect(explicitLifetimeParams.type).to.be(undefined); + expect(explicitLifetimeParams.title).to.be.a('string'); + expect(explicitLifetimeParams.lifetime).to.be.a('number'); + }); + + it('sets the content', function () { + expect(customNotification).to.have.property('content', `${params.location}: ${customText}`); + expect(customNotification.content).to.be.a('string'); + }); + + it('uses custom actions', function () { + expect(customNotification).to.have.property('customActions'); + expect(customNotification.customActions).to.have.length(customParams.actions.length); + }); + + it('gives a default action if none are provided', function () { + // destroy the default custom notification, avoid duplicate handling + customNotification.clear(); + + const noActionParams = _.defaults({ actions: [] }, customParams); + customNotification = notifier.custom(customText, noActionParams); + expect(customNotification).to.have.property('actions'); + expect(customNotification.actions).to.have.length(1); + }); + + it('defaults type and lifetime for "info" config', function () { + expect(customNotification.type).to.be('info'); + expect(customNotification.lifetime).to.be(5000); + }); + + it('dynamic lifetime for "warning" config', function () { + // destroy the default custom notification, avoid duplicate handling + customNotification.clear(); + + const errorTypeParams = _.defaults({ type: 'warning' }, customParams); + customNotification = notifier.custom(customText, errorTypeParams); + expect(customNotification.type).to.be('warning'); + expect(customNotification.lifetime).to.be(10000); + }); + + it('dynamic type and lifetime for "error" config', function () { + // destroy the default custom notification, avoid duplicate handling + customNotification.clear(); + + const errorTypeParams = _.defaults({ type: 'error' }, customParams); + customNotification = notifier.custom(customText, errorTypeParams); + expect(customNotification.type).to.be('danger'); + expect(customNotification.lifetime).to.be(300000); + }); + + it('dynamic type and lifetime for "danger" config', function () { + // destroy the default custom notification, avoid duplicate handling + customNotification.clear(); + + const errorTypeParams = _.defaults({ type: 'danger' }, customParams); + customNotification = notifier.custom(customText, errorTypeParams); + expect(customNotification.type).to.be('danger'); + expect(customNotification.lifetime).to.be(300000); + }); + + it('should wrap the callback functions in a close function', function () { + customNotification.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; + }); + }); + }); + function notify(fnName) { notifier[fnName](message); return latestNotification(); diff --git a/src/ui/public/notify/notifier.js b/src/ui/public/notify/notifier.js index 091a2e0f4b0a6..04a29f197ea10 100644 --- a/src/ui/public/notify/notifier.js +++ b/src/ui/public/notify/notifier.js @@ -34,10 +34,10 @@ define(function (require) { return Date.now(); } - function closeNotif(cb, key) { + function closeNotif(notif, cb, key) { return function () { // this === notif - let i = notifs.indexOf(this); + let i = notifs.indexOf(notif); if (i !== -1) notifs.splice(i, 1); if (this.timerId) this.timerId = clearTO(this.timerId); if (typeof cb === 'function') cb(key); @@ -48,16 +48,24 @@ define(function (require) { _.set(notif, 'info.version', version); _.set(notif, 'info.buildNum', buildNum); - if (notif.lifetime !== Infinity) { + if (notif.lifetime !== Infinity && notif.lifetime > 0) { notif.timerId = setTO(function () { - closeNotif(cb, 'ignore').call(notif); + closeNotif(notif, cb, 'ignore').call(notif); }, notif.lifetime); } - notif.clear = closeNotif(); + notif.clear = closeNotif(notif); if (notif.actions) { notif.actions.forEach(function (action) { - notif[action] = closeNotif(cb, action); + notif[action] = closeNotif(notif, cb, action); + }); + } else if (notif.customActions) { + // wrap all of the custom functions in a close + notif.customActions = notif.customActions.map(action => { + return { + key: action.text, + callback: closeNotif(notif, action.callback, action.text) + }; }); } @@ -251,6 +259,67 @@ define(function (require) { }, 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(); } + * }] + * } + */ + Notifier.prototype.custom = function (msg, config, cb) { + // 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'); + } + + // workaround to allow callers to send `config.type` as `error` instead of + // reveal internal implementation that error notifications use a `danger` + // style + if (config.type === 'error') { + config.type = 'danger'; + } + + const getLifetime = (type) => { + switch (type) { + case 'warning': + return 10000; + case 'danger': + return 300000; + default: // info + return 5000; + } + }; + + const mergedConfig = _.assign({ + type: 'info', + title: 'Notification', + content: formatMsg(msg, this.from), + lifetime: getLifetime(config.type) + }, config); + + const hasActions = _.get(mergedConfig, 'actions.length'); + if (hasActions) { + mergedConfig.customActions = mergedConfig.actions; + delete mergedConfig.actions; + } else { + mergedConfig.actions = ['accept']; + } + + return add(mergedConfig, cb); + }; + /** * Display a debug message * @param {String} msg [description] diff --git a/src/ui/public/notify/partials/toaster.html b/src/ui/public/notify/partials/toaster.html index 52a2e83a067db..f9b0f4e48551f 100644 --- a/src/ui/public/notify/partials/toaster.html +++ b/src/ui/public/notify/partials/toaster.html @@ -30,6 +30,14 @@ ng-class="'btn-' + notif.type" ng-click="notif.address()" >Fix it +