Convert various uses of notifier to toastNotifications. (#20420) (#20520)

* Add MarkdownSimple component.
* Replace Context use of notifier with toastNotifications.
* Replace field.js use of notifier with toastNotifications.
* Replace State use of notifier with toastNotifications.
* Replace reporting use of notifier with toastNotifications.
* Convert Logstash to use toastNotifications.
* Convert Security to use toastNotifications.
This commit is contained in:
CJ Cenizal 2018-07-06 11:09:26 -07:00 committed by GitHub
parent ef32b06296
commit 55228fbd81
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 131 additions and 71 deletions

View file

@ -18,14 +18,16 @@
*/
import _ from 'lodash';
import React from 'react';
import { MarkdownSimple } from 'ui/markdown';
import { toastNotifications } from 'ui/notify';
import { fetchAnchorProvider } from '../api/anchor';
import { fetchContextProvider } from '../api/context';
import { QueryParameterActionsProvider } from '../query_parameters';
import { FAILURE_REASONS, LOADING_STATUS } from './constants';
export function QueryActionsProvider(courier, Notifier, Private, Promise) {
export function QueryActionsProvider(courier, Private, Promise) {
const fetchAnchor = Private(fetchAnchorProvider);
const { fetchPredecessors, fetchSuccessors } = Private(fetchContextProvider);
const {
@ -36,10 +38,6 @@ export function QueryActionsProvider(courier, Notifier, Private, Promise) {
setSuccessorCount,
} = Private(QueryParameterActionsProvider);
const notifier = new Notifier({
location: 'Context',
});
const setFailedStatus = (state) => (subject, details = {}) => (
state.loadingStatus[subject] = {
status: LOADING_STATUS.FAILED,
@ -82,7 +80,10 @@ export function QueryActionsProvider(courier, Notifier, Private, Promise) {
},
(error) => {
setFailedStatus(state)('anchor', { error });
notifier.error(error);
toastNotifications.addDanger({
title: 'Unable to load the anchor document',
text: <MarkdownSimple>{error.message}</MarkdownSimple>,
});
throw error;
}
);
@ -123,7 +124,10 @@ export function QueryActionsProvider(courier, Notifier, Private, Promise) {
},
(error) => {
setFailedStatus(state)('predecessors', { error });
notifier.error(error);
toastNotifications.addDanger({
title: 'Unable to load documents',
text: <MarkdownSimple>{error.message}</MarkdownSimple>,
});
throw error;
},
);
@ -164,7 +168,10 @@ export function QueryActionsProvider(courier, Notifier, Private, Promise) {
},
(error) => {
setFailedStatus(state)('successors', { error });
notifier.error(error);
toastNotifications.addDanger({
title: 'Unable to load documents',
text: <MarkdownSimple>{error.message}</MarkdownSimple>,
});
throw error;
},
);

View file

@ -17,19 +17,17 @@
* under the License.
*/
import { SavedObjectNotFound } from '../../errors';
import _ from 'lodash';
import { SavedObjectNotFound } from '../../errors';
import editorHtml from '../controls/field.html';
import { BaseParamType } from './base';
import '../../filters/field_type';
import { IndexedArray } from '../../indexed_array';
import { Notifier } from '../../notify';
import { toastNotifications } from '../../notify';
import { propFilter } from '../../filters/_prop_filter';
import { createLegacyClass } from '../../utils/legacy_class';
const notifier = new Notifier();
export function FieldParamType(config) {
FieldParamType.Super.call(this, config);
}
@ -100,7 +98,7 @@ FieldParamType.prototype.deserialize = function (fieldName, aggConfig) {
const validField = this.getFieldOptions(aggConfig).byName[fieldName];
if (!validField) {
notifier.error(`Saved "field" parameter is now invalid. Please select a new field.`);
toastNotifications.addDanger(`Saved "field" parameter is now invalid. Please select a new field.`);
}
return validField;

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 { MarkdownSimple } from './markdown_simple';

View file

@ -0,0 +1,35 @@
/*
* 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 React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import ReactMarkdown from 'react-markdown';
const markdownRenderers = {
root: Fragment,
};
// Render markdown string into JSX inside of a Fragment.
export const MarkdownSimple = ({ children }) => (
<ReactMarkdown renderers={markdownRenderers}>{children}</ReactMarkdown>
);
MarkdownSimple.propTypes = {
children: PropTypes.string,
};

View file

@ -22,7 +22,7 @@ import expect from 'expect.js';
import ngMock from 'ng_mock';
import { encode as encodeRison } from 'rison-node';
import '../../private';
import { Notifier, fatalErrorInternals } from '../../notify';
import { fatalErrorInternals, toastNotifications } from '../../notify';
import { StateProvider } from '../state';
import {
unhashQueryString,
@ -37,7 +37,6 @@ import { EventsProvider } from '../../events';
describe('State Management', () => {
describe('Enabled', () => {
const notifier = new Notifier();
let $rootScope;
let $location;
let Events;
@ -49,25 +48,22 @@ describe('State Management', () => {
$location = _$location_;
$rootScope = _$rootScope_;
Events = Private(EventsProvider);
Notifier.prototype._notifs.splice(0);
setup = opts => {
const { param, initial, storeInHash } = (opts || {});
sinon.stub(config, 'get').withArgs('state:storeInSessionStorage').returns(!!storeInHash);
const store = new StubBrowserStorage();
const hashedItemStore = new HashedItemStore(store);
const state = new State(param, initial, hashedItemStore, notifier);
const state = new State(param, initial, hashedItemStore);
const getUnhashedSearch = state => {
return unhashQueryString($location.search(), [ state ]);
};
return { notifier, store, hashedItemStore, state, getUnhashedSearch };
return { store, hashedItemStore, state, getUnhashedSearch };
};
}));
afterEach(() => Notifier.prototype._notifs.splice(0));
describe('Provider', () => {
it('should reset the state to the defaults', () => {
const { state, getUnhashedSearch } = setup({ initial: { message: ['test'] } });
@ -272,17 +268,25 @@ describe('State Management', () => {
describe('error handling', () => {
it('notifies the user when a hash value does not map to a stored value', () => {
const { state, notifier } = setup({ storeInHash: true });
// Ideally, state.js shouldn't be tightly coupled to toastNotifications. Instead, it
// should notify its consumer of this error state and the consumer should be responsible
// for notifying the user of the error. This test verifies the side effect of the error
// until we can remove this coupling.
// Clear existing toasts.
toastNotifications.list.splice(0);
const { state } = setup({ storeInHash: true });
const search = $location.search();
const badHash = createStateHash('{"a": "b"}', () => null);
search[state.getQueryParamName()] = badHash;
$location.search(search);
expect(notifier._notifs).to.have.length(0);
expect(toastNotifications.list).to.have.length(0);
state.fetch();
expect(notifier._notifs).to.have.length(1);
expect(notifier._notifs[0].content).to.match(/use the share functionality/i);
expect(toastNotifications.list).to.have.length(1);
expect(toastNotifications.list[0].title).to.match(/use the share functionality/i);
});
it('throws error linking to github when setting item fails', () => {
@ -330,7 +334,6 @@ describe('State Management', () => {
const State = Private(StateProvider);
$location = _$location_;
$rootScope = _$rootScope_;
Notifier.prototype._notifs.splice(0);
sinon.stub(config, 'get').withArgs('state:storeInSessionStorage').returns(false);
@ -343,8 +346,6 @@ describe('State Management', () => {
state = new MockPersistedState(stateParam);
}));
afterEach(() => Notifier.prototype._notifs.splice(0));
describe('changing state', () => {
const methods = ['save', 'replace', 'reset'];

View file

@ -30,7 +30,7 @@ import angular from 'angular';
import rison from 'rison-node';
import { applyDiff } from '../utils/diff_object';
import { EventsProvider } from '../events';
import { fatalError, Notifier } from '../notify';
import { fatalError, toastNotifications } from '../notify';
import './config_provider';
import { createLegacyClass } from '../utils/legacy_class';
import { callEach } from '../utils/function';
@ -48,14 +48,12 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon
function State(
urlParam,
defaults,
hashedItemStore = HashedItemStoreSingleton,
notifier = new Notifier()
hashedItemStore = HashedItemStoreSingleton
) {
State.Super.call(this);
this.setDefaults(defaults);
this._urlParam = urlParam || '_s';
this._notifier = notifier;
this._hashedItemStore = hashedItemStore;
// When the URL updates we need to fetch the values from the URL
@ -105,7 +103,7 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon
}
if (unableToParse) {
this._notifier.error('Unable to parse URL');
toastNotifications.addDanger('Unable to parse URL');
search[this._urlParam] = this.toQueryParam(this._defaults);
$location.search(search).replace();
}
@ -244,7 +242,7 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon
State.prototype._parseStateHash = function (stateHash) {
const json = this._hashedItemStore.getItem(stateHash);
if (json === null) {
this._notifier.error('Unable to completely restore the URL, be sure to use the share functionality.');
toastNotifications.addDanger('Unable to completely restore the URL, be sure to use the share functionality.');
}
return JSON.parse(json);

View file

@ -7,7 +7,7 @@
import { isEmpty } from 'lodash';
import { uiModules } from 'ui/modules';
import { InitAfterBindingsWorkaround } from 'ui/compat';
import { Notifier, toastNotifications } from 'ui/notify';
import { toastNotifications } from 'ui/notify';
import template from './pipeline_edit.html';
import 'plugins/logstash/services/license';
import 'plugins/logstash/services/security';
@ -36,7 +36,6 @@ app.directive('pipelineEdit', function ($injector) {
controller: class PipelineEditController extends InitAfterBindingsWorkaround {
initAfterBindings($scope) {
this.originalPipeline = { ...this.pipeline };
this.notifier = new Notifier({ location: 'Logstash' });
this.isNewPipeline = isEmpty(this.pipeline.id);
// only if security is enabled and available, we tack on the username.
if (securityService.isSecurityEnabled) {
@ -82,7 +81,7 @@ app.directive('pipelineEdit', function ($injector) {
.catch(err => {
return licenseService
.checkValidity()
.then(() => this.notifier.error(err));
.then(() => toastNotifications.addDanger(err));
});
};
@ -112,7 +111,7 @@ app.directive('pipelineEdit', function ($injector) {
.catch(err => {
return licenseService
.checkValidity()
.then(() => this.notifier.error(err));
.then(() => toastNotifications.addDanger(err));
});
};

View file

@ -5,7 +5,7 @@
*/
import routes from 'ui/routes';
import { Notifier } from 'ui/notify';
import { toastNotifications } from 'ui/notify';
import template from './pipeline_edit_route.html';
import 'plugins/logstash/services/pipeline';
import 'plugins/logstash/services/license';
@ -40,8 +40,6 @@ routes
const licenseService = $injector.get('logstashLicenseService');
const kbnUrl = $injector.get('kbnUrl');
const notifier = new Notifier({ location: 'Logstash' });
const pipelineId = $route.current.params.id;
if (!pipelineId) return new Pipeline();
@ -52,7 +50,7 @@ routes
return licenseService.checkValidity()
.then(() => {
if (err.status !== 403) {
notifier.error(err);
toastNotifications.addDanger(`Couldn't load pipeline. Error: '${err.statusText}'.`);
}
kbnUrl.redirect('/management/logstash/pipelines');

View file

@ -6,7 +6,7 @@
import pluralize from 'pluralize';
import { uiModules } from 'ui/modules';
import { Notifier, toastNotifications } from 'ui/notify';
import { toastNotifications } from 'ui/notify';
import template from './pipeline_list.html';
import '../pipeline_table';
import { PAGINATION } from 'plugins/logstash/../common/constants';
@ -50,7 +50,6 @@ app.directive('pipelineList', function ($injector) {
this.sortField = 'status.sortOrder';
this.sortReverse = false;
this.notifier = new Notifier({ location: 'Logstash' });
this.pager = pagerFactory.create(this.pipelines.length, PAGINATION.PAGE_SIZE, 1);
// load pipelines
@ -96,7 +95,7 @@ app.directive('pipelineList', function ($injector) {
}
} else {
this.isForbidden = false;
this.notifier.error(err);
toastNotifications.addDanger(`Couldn't load pipeline. Error: '${err.statusText}'.`);
}
});
});
@ -191,7 +190,7 @@ app.directive('pipelineList', function ($injector) {
})
.catch(err => {
return licenseService.checkValidity()
.then(() => this.notifier.error(err));
.then(() => toastNotifications.addDanger(err));
});
}

View file

@ -4,16 +4,16 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import { toastNotifications } from 'ui/notify';
import { MarkdownSimple } from 'ui/markdown';
import { PLUGIN } from '../../../common/constants';
import { Notifier } from 'ui/notify';
export class LogstashLicenseService {
constructor(xpackInfoService, kbnUrlService, $timeout) {
this.xpackInfoService = xpackInfoService;
this.kbnUrlService = kbnUrlService;
this.$timeout = $timeout;
this.notifier = new Notifier({ location: 'Logstash' });
}
get enableLinks() {
@ -33,7 +33,9 @@ export class LogstashLicenseService {
}
notifyAndRedirect() {
this.notifier.error(this.xpackInfoService.get(`features.${PLUGIN.ID}.message`));
toastNotifications.addDanger({
title: <MarkdownSimple>{this.xpackInfoService.get(`features.${PLUGIN.ID}.message`)}</MarkdownSimple>,
});
this.kbnUrlService.redirect('/management');
}

View file

@ -7,7 +7,7 @@
import 'angular-paging';
import 'plugins/reporting/services/job_queue';
import 'plugins/reporting/less/main.less';
import { Notifier } from 'ui/notify';
import { toastNotifications } from 'ui/notify';
import { XPackInfoProvider } from 'plugins/xpack_main/services/xpack_info';
import routes from 'ui/routes';
@ -39,7 +39,6 @@ routes.when('/management/kibana/reporting', {
controllerAs: 'jobsCtrl',
controller($scope, $route, $window, $interval, reportingJobQueue, kbnUrl, Private, reportingPollConfig) {
const { jobsRefresh } = reportingPollConfig;
const notifier = new Notifier({ location: 'Reporting' });
const xpackInfo = Private(XPackInfoProvider);
this.loading = false;
@ -53,7 +52,7 @@ routes.when('/management/kibana/reporting', {
};
const notifyAndRedirectToManagementOverviewPage = () => {
notifier.error(xpackInfo.get('features.reporting.management.message'));
toastNotifications.addDanger(xpackInfo.get('features.reporting.management.message'));
kbnUrl.redirect('/management');
return Promise.reject();
};
@ -77,7 +76,7 @@ routes.when('/management/kibana/reporting', {
}
if (err.status !== 401 && err.status !== 403) {
notifier.error(err.statusText || 'Request failed');
toastNotifications.addDanger(err.statusText || 'Request failed');
}
return {

View file

@ -30,7 +30,7 @@ routes.when(`${EDIT_ROLES_PATH}/:name?`, {
gateKeeper.redirectAndNotifyIfTribe();
},
role($route, ShieldRole, kbnUrl, Promise, Notifier) {
role($route, ShieldRole, kbnUrl, Promise) {
const name = $route.current.params.name;
if (name != null) {
return ShieldRole.get({ name }).$promise
@ -40,8 +40,7 @@ routes.when(`${EDIT_ROLES_PATH}/:name?`, {
return fatalError(response);
}
const notifier = new Notifier();
notifier.error(`No "${name}" role found.`);
toastNotifications.addDanger(`No "${name}" role found.`);
kbnUrl.redirect(ROLES_PATH);
return Promise.halt();
});
@ -68,7 +67,6 @@ routes.when(`${EDIT_ROLES_PATH}/:name?`, {
const $route = $injector.get('$route');
const kbnUrl = $injector.get('kbnUrl');
const shieldPrivileges = $injector.get('shieldPrivileges');
const Notifier = $injector.get('Notifier');
const Private = $injector.get('Private');
const confirmModal = $injector.get('confirmModal');
const shieldIndices = $injector.get('shieldIndices');
@ -82,14 +80,12 @@ routes.when(`${EDIT_ROLES_PATH}/:name?`, {
this.isNewRole = $route.current.params.name == null;
this.fieldOptions = {};
const notifier = new Notifier();
$scope.deleteRole = (role) => {
const doDelete = () => {
role.$delete()
.then(() => toastNotifications.addSuccess('Deleted role'))
.then($scope.goToRoleList)
.catch(error => notifier.error(_.get(error, 'data.message')));
.catch(error => toastNotifications.addDanger(_.get(error, 'data.message')));
};
const confirmModalOptions = {
confirmButtonText: 'Delete role',
@ -104,7 +100,7 @@ routes.when(`${EDIT_ROLES_PATH}/:name?`, {
return role.$save()
.then(() => toastNotifications.addSuccess('Updated role'))
.then($scope.goToRoleList)
.catch(error => notifier.error(_.get(error, 'data.message')));
.catch(error => toastNotifications.addDanger(_.get(error, 'data.message')));
};
$scope.goToRoleList = () => {

View file

@ -28,7 +28,7 @@ routes.when(`${EDIT_USERS_PATH}/:username?`, {
return ShieldUser.getCurrent();
},
user($route, ShieldUser, kbnUrl, Promise, Notifier) {
user($route, ShieldUser, kbnUrl, Promise) {
const username = $route.current.params.username;
if (username != null) {
return ShieldUser.get({ username }).$promise
@ -37,8 +37,7 @@ routes.when(`${EDIT_USERS_PATH}/:username?`, {
return fatalError(response);
}
const notifier = new Notifier();
notifier.error(`No "${username}" user found.`);
toastNotifications.addDanger(`No "${username}" user found.`);
kbnUrl.redirect(USERS_PATH);
return Promise.halt();
});
@ -62,14 +61,12 @@ routes.when(`${EDIT_USERS_PATH}/:username?`, {
this.isNewUser = $route.current.params.username == null;
const notifier = new Notifier();
$scope.deleteUser = (user) => {
const doDelete = () => {
user.$delete()
.then(() => toastNotifications.addSuccess('Deleted user'))
.then($scope.goToUserList)
.catch(error => notifier.error(_.get(error, 'data.message')));
.catch(error => toastNotifications.addDanger(_.get(error, 'data.message')));
};
const confirmModalOptions = {
confirmButtonText: 'Delete user',
@ -84,7 +81,7 @@ routes.when(`${EDIT_USERS_PATH}/:username?`, {
user.$save()
.then(() => toastNotifications.addSuccess('User updated'))
.then($scope.goToUserList)
.catch(error => notifier.error(_.get(error, 'data.message')));
.catch(error => toastNotifications.addDanger(_.get(error, 'data.message')));
};
$scope.goToUserList = () => {
@ -105,7 +102,7 @@ routes.when(`${EDIT_USERS_PATH}/:username?`, {
if (error.status === 401) {
onIncorrectPassword();
}
else notifier.error(_.get(error, 'data.message'));
else toastNotifications.addDanger(_.get(error, 'data.message'));
});
};
}

View file

@ -8531,6 +8531,13 @@ markdown-it@^8.4.1:
mdurl "^1.0.1"
uc.micro "^1.0.5"
markdown-to-jsx@^6.6.8:
version "6.6.8"
resolved "https://registry.yarnpkg.com/markdown-to-jsx/-/markdown-to-jsx-6.6.8.tgz#fcda66fc5d7ab5ca8d4bab3abc61ba70ca319cac"
dependencies:
prop-types "^15.5.10"
unquote "^1.1.0"
marked-text-renderer@0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/marked-text-renderer/-/marked-text-renderer-0.1.0.tgz#2342d66e9a84f55c2fb6711a818387e1bb00f847"
@ -13128,6 +13135,10 @@ unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
unquote@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544"
unset-value@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559"