mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Remove Notifier's directive and banner methods (#20870)
* Convert xpack license check to use banners service. * Remove notifier directive method. * Remove notifier banner directive. * Simplify pullMessageFromUrl and move it into its own appRedirect module.
This commit is contained in:
parent
1a99df72ba
commit
494c267cd9
14 changed files with 233 additions and 430 deletions
|
@ -57,7 +57,7 @@ import 'ui/vislib';
|
|||
import 'ui/agg_response';
|
||||
import 'ui/agg_types';
|
||||
import 'ui/timepicker';
|
||||
import { Notifier } from 'ui/notify';
|
||||
import { showAppRedirectNotification } from 'ui/notify';
|
||||
import 'leaflet';
|
||||
import { KibanaRootController } from './kibana_root_controller';
|
||||
|
||||
|
@ -70,4 +70,4 @@ routes
|
|||
|
||||
chrome.setRootController('kibana', KibanaRootController);
|
||||
|
||||
uiModules.get('kibana').run(Notifier.pullMessageFromUrl);
|
||||
uiModules.get('kibana').run(showAppRedirectNotification);
|
||||
|
|
37
src/ui/public/chrome/api/angular.js
vendored
37
src/ui/public/chrome/api/angular.js
vendored
|
@ -17,11 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { Fragment } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { format as formatUrl, parse as parseUrl } from 'url';
|
||||
|
||||
import { uiModules } from '../../modules';
|
||||
import { Notifier } from '../../notify';
|
||||
import { toastNotifications } from '../../notify';
|
||||
import { UrlOverflowServiceProvider } from '../../error_url_overflow';
|
||||
|
||||
import { directivesProvider } from '../directives';
|
||||
|
@ -71,28 +72,28 @@ export function initAngularApi(chrome, internals) {
|
|||
return $location.path().split('/')[1];
|
||||
};
|
||||
|
||||
const notify = new Notifier();
|
||||
const urlOverflow = Private(UrlOverflowServiceProvider);
|
||||
const check = () => {
|
||||
// disable long url checks when storing state in session storage
|
||||
if (config.get('state:storeInSessionStorage')) return;
|
||||
if ($location.path() === '/error/url-overflow') return;
|
||||
// disable long url checks when storing state in session storage
|
||||
if (config.get('state:storeInSessionStorage')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($location.path() === '/error/url-overflow') {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (urlOverflow.check($location.absUrl()) <= URL_LIMIT_WARN_WITHIN) {
|
||||
notify.directive({
|
||||
template: `
|
||||
<p>
|
||||
The URL has gotten big and may cause Kibana
|
||||
to stop working. Please either enable the
|
||||
<code>state:storeInSessionStorage</code>
|
||||
option in the <a href="#/management/kibana/settings">advanced
|
||||
settings</a> or simplify the onscreen visuals.
|
||||
</p>
|
||||
`
|
||||
}, {
|
||||
type: 'error',
|
||||
actions: [{ text: 'close' }]
|
||||
toastNotifications.addWarning({
|
||||
title: 'The URL is big and Kibana might stop working',
|
||||
text: (
|
||||
<Fragment>
|
||||
Either enable the <code>state:storeInSessionStorage</code> option
|
||||
in <a href="#/management/kibana/settings">advanced settings</a> or
|
||||
simplify the onscreen visuals.
|
||||
</Fragment>
|
||||
),
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
|
@ -163,178 +163,3 @@ 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;
|
||||
});
|
||||
|
||||
notifier = new Notifier({ location: 'directiveFoo' });
|
||||
directiveNotification = notifier.directive(directiveParam, customParams);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Notifier.prototype._notifs.length = 0;
|
||||
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;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
48
src/ui/public/notify/app_redirect/app_redirect.js
Normal file
48
src/ui/public/notify/app_redirect/app_redirect.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
// Use the util instead of the export from ui/url because that module is tightly coupled with
|
||||
// Angular.
|
||||
import { modifyUrl } from '../../../../utils/modify_url';
|
||||
import { toastNotifications } from '../toasts';
|
||||
|
||||
const APP_REDIRECT_MESSAGE_PARAM = 'app_redirect_message';
|
||||
|
||||
export function addAppRedirectMessageToUrl(url, message) {
|
||||
return modifyUrl(url, urlParts => {
|
||||
urlParts.hash = modifyUrl(urlParts.hash || '', hashParts => {
|
||||
hashParts.query[APP_REDIRECT_MESSAGE_PARAM] = message;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// If an app needs to redirect, e.g. due to an expired license, it can surface a message via
|
||||
// the URL query params.
|
||||
export function showAppRedirectNotification($location) {
|
||||
const queryString = $location.search();
|
||||
|
||||
if (!queryString[APP_REDIRECT_MESSAGE_PARAM]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message = queryString[APP_REDIRECT_MESSAGE_PARAM];
|
||||
$location.search(APP_REDIRECT_MESSAGE_PARAM, null);
|
||||
|
||||
toastNotifications.addDanger(message);
|
||||
}
|
59
src/ui/public/notify/app_redirect/app_redirect.test.js
Normal file
59
src/ui/public/notify/app_redirect/app_redirect.test.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { addAppRedirectMessageToUrl, showAppRedirectNotification } from './app_redirect';
|
||||
|
||||
let isToastAdded = false;
|
||||
|
||||
jest.mock('../toasts', () => ({
|
||||
toastNotifications: {
|
||||
addDanger: () => {
|
||||
isToastAdded = true;
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
describe('addAppRedirectMessageToUrl', () => {
|
||||
test('adds a message to the URL', () => {
|
||||
const url = addAppRedirectMessageToUrl('', 'redirect message');
|
||||
expect(url).toBe('#?app_redirect_message=redirect%20message');
|
||||
});
|
||||
});
|
||||
|
||||
describe('showAppRedirectNotification', () => {
|
||||
beforeEach(() => {
|
||||
isToastAdded = false;
|
||||
});
|
||||
|
||||
test(`adds a toast when there's a message in the URL`, () => {
|
||||
showAppRedirectNotification({
|
||||
search: () => ({ app_redirect_message: 'redirect message' }),
|
||||
});
|
||||
|
||||
expect(isToastAdded).toBe(true);
|
||||
});
|
||||
|
||||
test(`doesn't add a toast when there's no message in the URL`, () => {
|
||||
showAppRedirectNotification({
|
||||
search: () => ({ app_redirect_message: '' }),
|
||||
});
|
||||
|
||||
expect(isToastAdded).toBe(false);
|
||||
});
|
||||
});
|
20
src/ui/public/notify/app_redirect/index.js
Normal file
20
src/ui/public/notify/app_redirect/index.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export { addAppRedirectMessageToUrl, showAppRedirectNotification } from './app_redirect';
|
|
@ -22,3 +22,4 @@ export { Notifier } from './notifier';
|
|||
export { fatalError, addFatalErrorCallback } from './fatal_error';
|
||||
export { GlobalToastList, toastNotifications } from './toasts';
|
||||
export { GlobalBannerList, banners } from './banners';
|
||||
export { addAppRedirectMessageToUrl, showAppRedirectNotification } from './app_redirect';
|
||||
|
|
|
@ -17,21 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import angular from 'angular';
|
||||
import MarkdownIt from 'markdown-it';
|
||||
import { metadata } from '../metadata';
|
||||
import { formatMsg, formatStack } from './lib';
|
||||
import { fatalError } from './fatal_error';
|
||||
import { banners } from './banners';
|
||||
import '../render_directive';
|
||||
|
||||
import {
|
||||
EuiCallOut,
|
||||
EuiButton,
|
||||
} from '@elastic/eui';
|
||||
|
||||
const notifs = [];
|
||||
|
||||
const {
|
||||
|
@ -205,33 +195,6 @@ Notifier.applyConfig = function (config) {
|
|||
_.merge(Notifier.config, config);
|
||||
};
|
||||
|
||||
// "Constants"
|
||||
Notifier.QS_PARAM_MESSAGE = 'notif_msg';
|
||||
Notifier.QS_PARAM_LEVEL = 'notif_lvl';
|
||||
Notifier.QS_PARAM_LOCATION = 'notif_loc';
|
||||
|
||||
Notifier.pullMessageFromUrl = ($location) => {
|
||||
const queryString = $location.search();
|
||||
if (!queryString.notif_msg) {
|
||||
return;
|
||||
}
|
||||
const message = queryString[Notifier.QS_PARAM_MESSAGE];
|
||||
const config = queryString[Notifier.QS_PARAM_LOCATION] ? { location: queryString[Notifier.QS_PARAM_LOCATION] } : {};
|
||||
const level = queryString[Notifier.QS_PARAM_LEVEL] || 'info';
|
||||
|
||||
$location.search(Notifier.QS_PARAM_MESSAGE, null);
|
||||
$location.search(Notifier.QS_PARAM_LOCATION, null);
|
||||
$location.search(Notifier.QS_PARAM_LEVEL, null);
|
||||
|
||||
const notifier = new Notifier(config);
|
||||
|
||||
if (level === 'fatal') {
|
||||
fatalError(message);
|
||||
} else {
|
||||
notifier[level](message);
|
||||
}
|
||||
};
|
||||
|
||||
// simply a pointer to the global notif list
|
||||
Notifier.prototype._notifs = notifs;
|
||||
|
||||
|
@ -259,156 +222,3 @@ Notifier.prototype.error = function (err, opts, cb) {
|
|||
}, _.pick(opts, overridableOptions));
|
||||
return add(config, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* Display a banner message
|
||||
* @param {String} content
|
||||
* @param {String} name
|
||||
*/
|
||||
let bannerId;
|
||||
let bannerTimeoutId;
|
||||
Notifier.prototype.banner = function (content = '', name = '') {
|
||||
const BANNER_PRIORITY = 100;
|
||||
|
||||
const dismissBanner = () => {
|
||||
banners.remove(bannerId);
|
||||
clearTimeout(bannerTimeoutId);
|
||||
};
|
||||
|
||||
const markdownIt = new MarkdownIt({
|
||||
html: false,
|
||||
linkify: true
|
||||
});
|
||||
|
||||
const banner = (
|
||||
<EuiCallOut
|
||||
title="Attention"
|
||||
iconType="help"
|
||||
>
|
||||
<div
|
||||
/*
|
||||
* Justification for dangerouslySetInnerHTML:
|
||||
* The notifier relies on `markdown-it` to produce safe and correct HTML.
|
||||
*/
|
||||
dangerouslySetInnerHTML={{ __html: markdownIt.render(content) }} //eslint-disable-line react/no-danger
|
||||
data-test-subj={name ? `banner-${name}` : null}
|
||||
/>
|
||||
|
||||
<EuiButton type="primary" size="s" onClick={dismissBanner}>
|
||||
Dismiss
|
||||
</EuiButton>
|
||||
</EuiCallOut>
|
||||
);
|
||||
|
||||
bannerId = banners.set({
|
||||
component: banner,
|
||||
id: bannerId,
|
||||
priority: BANNER_PRIORITY,
|
||||
});
|
||||
|
||||
clearTimeout(bannerTimeoutId);
|
||||
bannerTimeoutId = setTimeout(() => {
|
||||
dismissBanner();
|
||||
}, Notifier.config.bannerLifetime);
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper for common behavior in custom and directive types
|
||||
*/
|
||||
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');
|
||||
}
|
||||
|
||||
// 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 'danger':
|
||||
return Notifier.config.errorLifetime;
|
||||
default: // info
|
||||
return Notifier.config.infoLifetime;
|
||||
}
|
||||
};
|
||||
|
||||
const customConfig = _.assign({
|
||||
type: 'info',
|
||||
title: 'Notification',
|
||||
lifetime: getLifetime(config.type)
|
||||
}, config);
|
||||
|
||||
const hasActions = _.get(customConfig, 'actions.length');
|
||||
if (hasActions) {
|
||||
customConfig.customActions = customConfig.actions;
|
||||
delete customConfig.actions;
|
||||
} else {
|
||||
customConfig.actions = ['accept'];
|
||||
}
|
||||
|
||||
return customConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
};
|
||||
|
|
|
@ -17,15 +17,23 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { MarkdownSimple } from 'ui/markdown';
|
||||
import { uiModules } from '../modules';
|
||||
import { fatalError } from './fatal_error';
|
||||
import { Notifier } from './notifier';
|
||||
import { metadata } from '../metadata';
|
||||
import { fatalError } from './fatal_error';
|
||||
import { banners } from './banners';
|
||||
import { Notifier } from './notifier';
|
||||
import template from './partials/toaster.html';
|
||||
import './notify.less';
|
||||
import '../filters/markdown';
|
||||
import '../directives/truncated';
|
||||
|
||||
import {
|
||||
EuiCallOut,
|
||||
EuiButton,
|
||||
} from '@elastic/eui';
|
||||
|
||||
const module = uiModules.get('kibana/notify');
|
||||
|
||||
module.directive('kbnNotifications', function () {
|
||||
|
@ -70,18 +78,52 @@ if (!!metadata.kbnIndex) {
|
|||
});
|
||||
}
|
||||
|
||||
let bannerId;
|
||||
let bannerTimeoutId;
|
||||
|
||||
function applyConfig(config) {
|
||||
Notifier.applyConfig({
|
||||
bannerLifetime: config.get('notifications:lifetime:banner'),
|
||||
errorLifetime: config.get('notifications:lifetime:error'),
|
||||
warningLifetime: config.get('notifications:lifetime:warning'),
|
||||
infoLifetime: config.get('notifications:lifetime:info')
|
||||
});
|
||||
|
||||
const banner = config.get('notifications:banner');
|
||||
// Show user-defined banner.
|
||||
const bannerContent = config.get('notifications:banner');
|
||||
const bannerLifetime = config.get('notifications:lifetime:banner');
|
||||
|
||||
if (typeof banner === 'string' && banner.trim()) {
|
||||
notify.banner(banner, 'notifications:banner');
|
||||
if (typeof bannerContent === 'string' && bannerContent.trim()) {
|
||||
const BANNER_PRIORITY = 100;
|
||||
|
||||
const dismissBanner = () => {
|
||||
banners.remove(bannerId);
|
||||
clearTimeout(bannerTimeoutId);
|
||||
};
|
||||
|
||||
const banner = (
|
||||
<EuiCallOut
|
||||
title="Attention"
|
||||
iconType="help"
|
||||
>
|
||||
<MarkdownSimple data-test-subj="userDefinedBanner">
|
||||
{bannerContent}
|
||||
</MarkdownSimple>
|
||||
|
||||
<EuiButton type="primary" size="s" onClick={dismissBanner}>
|
||||
Close
|
||||
</EuiButton>
|
||||
</EuiCallOut>
|
||||
);
|
||||
|
||||
bannerId = banners.set({
|
||||
component: banner,
|
||||
id: bannerId,
|
||||
priority: BANNER_PRIORITY,
|
||||
});
|
||||
|
||||
bannerTimeoutId = setTimeout(() => {
|
||||
dismissBanner();
|
||||
}, bannerLifetime);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ import 'ui/agg_types';
|
|||
import 'ui/timepicker';
|
||||
import 'leaflet';
|
||||
|
||||
import { Notifier } from 'ui/notify';
|
||||
import { showAppRedirectNotification } from 'ui/notify';
|
||||
import { DashboardConstants, createDashboardEditUrl } from 'plugins/kibana/dashboard/dashboard_constants';
|
||||
import { KibanaRootController } from 'plugins/kibana/kibana_root_controller';
|
||||
|
||||
|
@ -51,7 +51,7 @@ chrome
|
|||
$controller(KibanaRootController, { $scope, courier, config });
|
||||
});
|
||||
|
||||
uiModules.get('kibana').run(Notifier.pullMessageFromUrl);
|
||||
uiModules.get('kibana').run(showAppRedirectNotification);
|
||||
|
||||
// If there is a configured kbnDefaultAppId, and it is a dashboard ID, we'll
|
||||
// show that dashboard, otherwise, we'll show the default dasbhoard landing page.
|
||||
|
|
|
@ -18,7 +18,7 @@ import 'ui/directives/saved_object_finder';
|
|||
import chrome from 'ui/chrome';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import uiRoutes from 'ui/routes';
|
||||
import { notify, Notifier, fatalError, toastNotifications } from 'ui/notify';
|
||||
import { notify, addAppRedirectMessageToUrl, fatalError, toastNotifications } from 'ui/notify';
|
||||
import { IndexPatternsProvider } from 'ui/index_patterns/index_patterns';
|
||||
import { SavedObjectsClientProvider } from 'ui/saved_objects';
|
||||
import { KibanaParsedUrl } from 'ui/url/kibana_parsed_url';
|
||||
|
@ -50,10 +50,8 @@ function checkLicense(Private, Promise, kbnBaseUrl) {
|
|||
const licenseAllowsToShowThisPage = xpackInfo.get('features.graph.showAppLink') && xpackInfo.get('features.graph.enableAppLink');
|
||||
if (!licenseAllowsToShowThisPage) {
|
||||
const message = xpackInfo.get('features.graph.message');
|
||||
const queryString = `?${Notifier.QS_PARAM_LOCATION}=Graph&${Notifier.QS_PARAM_LEVEL}=error&${Notifier.QS_PARAM_MESSAGE}=${message}`;
|
||||
const url = `${chrome.addBasePath(kbnBaseUrl)}#${queryString}`;
|
||||
|
||||
window.location.href = url;
|
||||
const newUrl = addAppRedirectMessageToUrl(chrome.addBasePath(kbnBaseUrl), message);
|
||||
window.location.href = newUrl;
|
||||
return Promise.halt();
|
||||
}
|
||||
|
||||
|
@ -68,7 +66,6 @@ app.directive('focusOn', function () {
|
|||
};
|
||||
});
|
||||
|
||||
|
||||
if (uiRoutes.enable) {
|
||||
uiRoutes.enable();
|
||||
}
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
|
||||
import React from 'react';
|
||||
import { XPackInfoProvider } from 'plugins/xpack_main/services/xpack_info';
|
||||
import { Notifier, banners } from 'ui/notify';
|
||||
import { banners, addAppRedirectMessageToUrl } from 'ui/notify';
|
||||
|
||||
import chrome from 'ui/chrome';
|
||||
import { EuiCallOut } from '@elastic/eui';
|
||||
|
||||
|
@ -20,12 +21,9 @@ export function checkLicense(Private, kbnBaseUrl) {
|
|||
|
||||
const licenseAllowsToShowThisPage = features.isAvailable;
|
||||
if (!licenseAllowsToShowThisPage) {
|
||||
const message = features.message;
|
||||
let queryString = `?${Notifier.QS_PARAM_LOCATION}=Machine Learning&`;
|
||||
queryString += `${Notifier.QS_PARAM_LEVEL}=error&${Notifier.QS_PARAM_MESSAGE}=${message}`;
|
||||
const url = `${chrome.addBasePath(kbnBaseUrl)}#${queryString}`;
|
||||
|
||||
window.location.href = url;
|
||||
const { message } = features;
|
||||
const newUrl = addAppRedirectMessageToUrl(chrome.addBasePath(kbnBaseUrl), message);
|
||||
window.location.href = newUrl;
|
||||
return Promise.halt();
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import ngMock from 'ng_mock';
|
||||
import sinon from 'sinon';
|
||||
import { Notifier } from 'ui/notify';
|
||||
import { banners } from 'ui/notify';
|
||||
|
||||
const XPACK_INFO_SIG_KEY = 'xpackMain.infoSignature';
|
||||
const XPACK_INFO_KEY = 'xpackMain.info';
|
||||
|
@ -42,7 +42,7 @@ describe('CheckXPackInfoChange Factory', () => {
|
|||
// like the one related to the session expiration.
|
||||
$http.defaults.headers.common['kbn-system-api'] = 'x';
|
||||
|
||||
sandbox.stub(Notifier.prototype, 'directive');
|
||||
sandbox.stub(banners, 'add');
|
||||
}));
|
||||
|
||||
afterEach(function () {
|
||||
|
@ -68,7 +68,7 @@ describe('CheckXPackInfoChange Factory', () => {
|
|||
$httpBackend.flush();
|
||||
$timeout.flush();
|
||||
|
||||
sinon.assert.notCalled(Notifier.prototype.directive);
|
||||
sinon.assert.notCalled(banners.add);
|
||||
});
|
||||
|
||||
it('shows "license expired" banner if license is expired only once.', async () => {
|
||||
|
@ -87,21 +87,16 @@ describe('CheckXPackInfoChange Factory', () => {
|
|||
$httpBackend.flush();
|
||||
$timeout.flush();
|
||||
|
||||
sinon.assert.calledOnce(Notifier.prototype.directive);
|
||||
sinon.assert.calledWithExactly(Notifier.prototype.directive, {
|
||||
template: sinon.match('Your diamond license is currently expired')
|
||||
}, {
|
||||
type: 'error'
|
||||
});
|
||||
sinon.assert.calledOnce(banners.add);
|
||||
|
||||
// If license didn't change banner shouldn't be displayed.
|
||||
Notifier.prototype.directive.resetHistory();
|
||||
banners.add.resetHistory();
|
||||
mockSessionStorage.getItem.withArgs(XPACK_INFO_SIG_KEY).returns('bar');
|
||||
|
||||
$http.post('/api/test');
|
||||
$httpBackend.flush();
|
||||
$timeout.flush();
|
||||
|
||||
sinon.assert.notCalled(Notifier.prototype.directive);
|
||||
sinon.assert.notCalled(banners.add);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,10 +4,12 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { identity } from 'lodash';
|
||||
import { EuiCallOut } from '@elastic/eui';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import chrome from 'ui/chrome';
|
||||
import { Notifier } from 'ui/notify';
|
||||
import { banners } from 'ui/notify';
|
||||
import { DebounceProvider } from 'ui/debounce';
|
||||
import { PathProvider } from 'plugins/xpack_main/services/path';
|
||||
import { XPackInfoProvider } from 'plugins/xpack_main/services/xpack_info';
|
||||
|
@ -20,8 +22,8 @@ module.factory('checkXPackInfoChange', ($q, Private) => {
|
|||
const xpackInfoSignature = Private(XPackInfoSignatureProvider);
|
||||
const debounce = Private(DebounceProvider);
|
||||
const isLoginOrLogout = Private(PathProvider).isLoginOrLogout();
|
||||
let isLicenseExpirationBannerShown = false;
|
||||
|
||||
const notify = new Notifier();
|
||||
const notifyIfLicenseIsExpired = debounce(() => {
|
||||
const license = xpackInfo.get('license');
|
||||
if (license.isActive) {
|
||||
|
@ -29,21 +31,26 @@ module.factory('checkXPackInfoChange', ($q, Private) => {
|
|||
}
|
||||
|
||||
const uploadLicensePath = `${chrome.getBasePath()}/app/kibana#/management/elasticsearch/license_management/upload_license`;
|
||||
notify.directive({
|
||||
template: `
|
||||
<p>
|
||||
Your ${license.type} license is currently expired. Please contact your administrator or
|
||||
<a href="${uploadLicensePath}">update your license</a> directly.
|
||||
</p>
|
||||
`
|
||||
}, {
|
||||
type: 'error'
|
||||
});
|
||||
|
||||
if (!isLicenseExpirationBannerShown) {
|
||||
isLicenseExpirationBannerShown = true;
|
||||
banners.add({
|
||||
component: (
|
||||
<EuiCallOut
|
||||
iconType="help"
|
||||
color="warning"
|
||||
title={`Your ${license.type} license is expired`}
|
||||
>
|
||||
Contact your administrator or <a href={uploadLicensePath}>update your license</a> directly.
|
||||
</EuiCallOut>
|
||||
),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Intercept each network response to look for the kbn-xpack-sig header.
|
||||
* When that header is detected, compare it's value with the value cached
|
||||
* When that header is detected, compare its value with the value cached
|
||||
* in the browser storage. When the value is new, call `xpackInfo.refresh()`
|
||||
* so that it will pull down the latest x-pack info
|
||||
*
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue