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:
CJ Cenizal 2018-08-07 16:44:08 -07:00 committed by GitHub
parent 1a99df72ba
commit 494c267cd9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 233 additions and 430 deletions

View file

@ -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);

View file

@ -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) {

View file

@ -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;
});
});
});

View 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);
}

View 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);
});
});

View 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';

View file

@ -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';

View file

@ -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);
};

View file

@ -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);
}
}

View file

@ -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.

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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);
});
});

View file

@ -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
*