mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Extract fatal notification into fatalError service, add support for EuiToast notifications (#15749)
* Reorganize notify/lib files. Extract fatal notification into a fatalError service. * Convert notify/lib tests to use Jest. * Add ToastNotifications, GlobalToastList, and documentation. * Remove notify.info method. * Add createFirstIndexPatternPrompt. * Update testSubjects.exists to accept a timeout argument. * Skip some flaky tests.
This commit is contained in:
parent
7a0c69232f
commit
6e9fc7328b
96 changed files with 1059 additions and 464 deletions
|
@ -35,6 +35,7 @@ out an open PR:
|
|||
|
||||
- [CONTRIBUTING.md](CONTRIBUTING.md) will help you get Kibana up and running.
|
||||
- If you would like to contribute code, please follow our [STYLEGUIDE.md](STYLEGUIDE.md).
|
||||
- Learn more about our UI code with [UI_SYSTEMS.md](src/ui/public/UI_SYSTEMS.md).
|
||||
- For all other questions, check out the [FAQ.md](FAQ.md) and
|
||||
[wiki](https://github.com/elastic/kibana/wiki).
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import angular from 'angular';
|
|||
import { uiModules } from 'ui/modules';
|
||||
import chrome from 'ui/chrome';
|
||||
import { applyTheme } from 'ui/theme';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
|
||||
import 'ui/query_bar';
|
||||
|
||||
|
@ -183,14 +184,18 @@ app.directive('dashboardApp', function ($injector) {
|
|||
$scope.addVis = function (hit, showToast = true) {
|
||||
dashboardStateManager.addNewPanel(hit.id, 'visualization');
|
||||
if (showToast) {
|
||||
notify.info(`Visualization successfully added to your dashboard`);
|
||||
toastNotifications.addSuccess('Added visualization to your dashboard');
|
||||
}
|
||||
};
|
||||
|
||||
$scope.addSearch = function (hit) {
|
||||
dashboardStateManager.addNewPanel(hit.id, 'search');
|
||||
notify.info(`Search successfully added to your dashboard`);
|
||||
toastNotifications.addSuccess({
|
||||
title: 'Added saved search to your dashboard',
|
||||
'data-test-subj': 'addSavedSearchToDashboardSuccess',
|
||||
});
|
||||
};
|
||||
|
||||
$scope.$watch('model.hidePanelTitles', () => {
|
||||
dashboardStateManager.setHidePanelTitles($scope.model.hidePanelTitles);
|
||||
});
|
||||
|
@ -268,7 +273,11 @@ app.directive('dashboardApp', function ($injector) {
|
|||
.then(function (id) {
|
||||
$scope.kbnTopNav.close('save');
|
||||
if (id) {
|
||||
notify.info(`Saved Dashboard as "${dash.title}"`);
|
||||
toastNotifications.addSuccess({
|
||||
title: `Saved '${dash.title}'`,
|
||||
'data-test-subj': 'saveDashboardSuccess',
|
||||
});
|
||||
|
||||
if (dash.id !== $routeParams.id) {
|
||||
kbnUrl.change(createDashboardEditUrl(dash.id));
|
||||
} else {
|
||||
|
|
|
@ -3,7 +3,7 @@ import 'plugins/kibana/dashboard/saved_dashboard/saved_dashboards';
|
|||
import 'plugins/kibana/dashboard/styles/index.less';
|
||||
import 'plugins/kibana/dashboard/dashboard_config';
|
||||
import uiRoutes from 'ui/routes';
|
||||
import { notify } from 'ui/notify';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
|
||||
import dashboardTemplate from 'plugins/kibana/dashboard/dashboard_app.html';
|
||||
import dashboardListingTemplate from './listing/dashboard_listing.html';
|
||||
|
@ -71,8 +71,7 @@ uiRoutes
|
|||
if (error instanceof SavedObjectNotFound && id === 'create') {
|
||||
// Note "new AppState" is neccessary so the state in the url is preserved through the redirect.
|
||||
kbnUrl.redirect(DashboardConstants.CREATE_NEW_DASHBOARD_URL, {}, new AppState());
|
||||
notify.error(
|
||||
'The url "dashboard/create" is deprecated and will be removed in 6.0. Please update your bookmarks.');
|
||||
toastNotifications.addWarning('The url "dashboard/create" was removed in 6.0. Please update your bookmarks.');
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import * as filterActions from 'ui/doc_table/actions/filter';
|
|||
import dateMath from '@elastic/datemath';
|
||||
import 'ui/doc_table';
|
||||
import 'ui/visualize';
|
||||
import 'ui/notify';
|
||||
import 'ui/fixed_scroll';
|
||||
import 'ui/directives/validate_json';
|
||||
import 'ui/filters/moment';
|
||||
|
@ -16,6 +15,7 @@ import 'ui/state_management/app_state';
|
|||
import 'ui/timefilter';
|
||||
import 'ui/share';
|
||||
import 'ui/query_bar';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
import { VisProvider } from 'ui/vis';
|
||||
import { BasicResponseHandlerProvider } from 'ui/vis/response_handlers/basic';
|
||||
import { DocTitleProvider } from 'ui/doc_title';
|
||||
|
@ -416,7 +416,11 @@ function discoverController(
|
|||
$scope.kbnTopNav.close('save');
|
||||
|
||||
if (id) {
|
||||
notify.info('Saved Data Source "' + savedSearch.title + '"');
|
||||
toastNotifications.addSuccess({
|
||||
title: `Saved '${savedSearch.title}'`,
|
||||
'data-test-subj': 'saveSearchSuccess',
|
||||
});
|
||||
|
||||
if (savedSearch.id !== $route.current.params.id) {
|
||||
kbnUrl.change('/discover/{{id}}', { id: savedSearch.id });
|
||||
} else {
|
||||
|
|
|
@ -18,7 +18,7 @@ import 'ui/vislib';
|
|||
import 'ui/agg_response';
|
||||
import 'ui/agg_types';
|
||||
import 'ui/timepicker';
|
||||
import { Notifier } from 'ui/notify/notifier';
|
||||
import { Notifier } from 'ui/notify';
|
||||
import 'leaflet';
|
||||
import { KibanaRootController } from './kibana_root_controller';
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import _ from 'lodash';
|
||||
import { fatalError } from 'ui/notify';
|
||||
import { IndexPatternMissingIndices } from 'ui/errors';
|
||||
import 'ui/directives/validate_index_pattern';
|
||||
import 'ui/directives/auto_select_if_only_one';
|
||||
|
@ -288,7 +289,7 @@ uiModules.get('apps/management')
|
|||
return notify.error(`Couldn't locate any indices matching that pattern. Please add the index to Elasticsearch`);
|
||||
}
|
||||
|
||||
notify.fatal(err);
|
||||
fatalError(err);
|
||||
}).finally(() => {
|
||||
this.isCreatingIndexPattern = false;
|
||||
});
|
||||
|
|
|
@ -6,6 +6,7 @@ import './scripted_field_editor';
|
|||
import './source_filters_table';
|
||||
import { KbnUrlProvider } from 'ui/url';
|
||||
import { IndicesEditSectionsProvider } from './edit_sections';
|
||||
import { fatalError } from 'ui/notify';
|
||||
import uiRoutes from 'ui/routes';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import template from './edit_index_pattern.html';
|
||||
|
@ -116,7 +117,7 @@ uiModules.get('apps/management')
|
|||
.then(function () {
|
||||
$location.url('/management/kibana/index');
|
||||
})
|
||||
.catch(notify.fatal);
|
||||
.catch(fatalError);
|
||||
}
|
||||
|
||||
const confirmModalOptions = {
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'ui/field_editor';
|
|||
import { IndexPatternsFieldProvider } from 'ui/index_patterns/_field';
|
||||
import { KbnUrlProvider } from 'ui/url';
|
||||
import uiRoutes from 'ui/routes';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
import template from './scripted_field_editor.html';
|
||||
|
||||
uiRoutes
|
||||
|
@ -29,9 +30,8 @@ uiRoutes
|
|||
}
|
||||
},
|
||||
controllerAs: 'fieldSettings',
|
||||
controller: function FieldEditorPageController($route, Private, Notifier, docTitle) {
|
||||
controller: function FieldEditorPageController($route, Private, docTitle) {
|
||||
const Field = Private(IndexPatternsFieldProvider);
|
||||
const notify = new Notifier({ location: 'Field Editor' });
|
||||
const kbnUrl = Private(KbnUrlProvider);
|
||||
|
||||
this.mode = $route.current.mode;
|
||||
|
@ -43,7 +43,8 @@ uiRoutes
|
|||
this.field = this.indexPattern.fields.byName[fieldName];
|
||||
|
||||
if (!this.field) {
|
||||
notify.error(this.indexPattern + ' does not have a "' + fieldName + '" field.');
|
||||
toastNotifications.add(`'${this.indexPattern.title}' index pattern doesn't have a scripted field called '${fieldName}'`);
|
||||
|
||||
kbnUrl.redirectToRoute(this.indexPattern, 'edit');
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -4,17 +4,16 @@ import 'ui/paginated_table';
|
|||
import fieldControlsHtml from '../field_controls.html';
|
||||
import { dateScripts } from './date_scripts';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
import template from './scripted_fields_table.html';
|
||||
import { getSupportedScriptingLanguages, getDeprecatedScriptingLanguages } from 'ui/scripting_languages';
|
||||
import { documentationLinks } from 'ui/documentation_links/documentation_links';
|
||||
|
||||
uiModules.get('apps/management')
|
||||
.directive('scriptedFieldsTable', function (kbnUrl, Notifier, $filter, confirmModal) {
|
||||
.directive('scriptedFieldsTable', function (kbnUrl, $filter, confirmModal) {
|
||||
const rowScopes = []; // track row scopes, so they can be destroyed as needed
|
||||
const filter = $filter('filter');
|
||||
|
||||
const notify = new Notifier();
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
template,
|
||||
|
@ -82,11 +81,17 @@ uiModules.get('apps/management')
|
|||
});
|
||||
|
||||
if (fieldsAdded > 0) {
|
||||
notify.info(fieldsAdded + ' script fields created');
|
||||
toastNotifications.addSuccess({
|
||||
title: 'Created script fields',
|
||||
text: `Created ${fieldsAdded}`,
|
||||
});
|
||||
}
|
||||
|
||||
if (conflictFields.length > 0) {
|
||||
notify.info('Not adding ' + conflictFields.length + ' duplicate fields: ' + conflictFields.join(', '));
|
||||
toastNotifications.addWarning({
|
||||
title: `Didn't add duplicate fields`,
|
||||
text: `${conflictFields.length} fields: ${conflictFields.join(', ')}`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { find, each, escape, invoke, size, without } from 'lodash';
|
||||
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { Notifier } from 'ui/notify/notifier';
|
||||
import { Notifier } from 'ui/notify';
|
||||
import { FieldWildcardProvider } from 'ui/field_wildcard';
|
||||
|
||||
import controlsHtml from './controls.html';
|
||||
|
|
|
@ -5,21 +5,23 @@ import { savedObjectManagementRegistry } from 'plugins/kibana/management/saved_o
|
|||
import objectViewHTML from 'plugins/kibana/management/sections/objects/_view.html';
|
||||
import uiRoutes from 'ui/routes';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { fatalError, toastNotifications } from 'ui/notify';
|
||||
import 'ui/accessibility/kbn_ui_ace_keyboard_mode';
|
||||
import { castEsToKbnFieldTypeName } from '../../../../../../utils';
|
||||
import { SavedObjectsClientProvider } from 'ui/saved_objects';
|
||||
|
||||
const location = 'SavedObject view';
|
||||
|
||||
uiRoutes
|
||||
.when('/management/kibana/objects/:service/:id', {
|
||||
template: objectViewHTML
|
||||
});
|
||||
|
||||
uiModules.get('apps/management')
|
||||
.directive('kbnManagementObjectsView', function (kbnIndex, Notifier, confirmModal) {
|
||||
.directive('kbnManagementObjectsView', function (kbnIndex, confirmModal) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
controller: function ($scope, $injector, $routeParams, $location, $window, $rootScope, Private) {
|
||||
const notify = new Notifier({ location: 'SavedObject view' });
|
||||
const serviceObj = savedObjectManagementRegistry.get($routeParams.service);
|
||||
const service = $injector.get(serviceObj.service);
|
||||
const savedObjectsClient = Private(SavedObjectsClientProvider);
|
||||
|
@ -122,7 +124,7 @@ uiModules.get('apps/management')
|
|||
return (orderIndex > -1) ? orderIndex : Infinity;
|
||||
});
|
||||
})
|
||||
.catch(notify.fatal);
|
||||
.catch(error => fatalError(error, location));
|
||||
|
||||
// This handles the validation of the Ace Editor. Since we don't have any
|
||||
// other hooks into the editors to tell us if the content is valid or not
|
||||
|
@ -173,7 +175,7 @@ uiModules.get('apps/management')
|
|||
.then(function () {
|
||||
return redirectHandler('deleted');
|
||||
})
|
||||
.catch(notify.fatal);
|
||||
.catch(error => fatalError(error, location));
|
||||
}
|
||||
const confirmModalOptions = {
|
||||
onConfirm: doDelete,
|
||||
|
@ -207,18 +209,17 @@ uiModules.get('apps/management')
|
|||
.then(function () {
|
||||
return redirectHandler('updated');
|
||||
})
|
||||
.catch(notify.fatal);
|
||||
.catch(error => fatalError(error, location));
|
||||
};
|
||||
|
||||
function redirectHandler(action) {
|
||||
const msg = 'You successfully ' + action + ' the "' + $scope.obj.attributes.title + '" ' + $scope.title.toLowerCase() + ' object';
|
||||
|
||||
$location.path('/management/kibana/objects').search({
|
||||
_a: rison.encode({
|
||||
tab: serviceObj.title
|
||||
})
|
||||
});
|
||||
notify.info(msg);
|
||||
|
||||
toastNotifications.addSuccess(`${_.capitalize(action)} '${$scope.obj.attributes.title}' ${$scope.title.toLowerCase()} object`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import 'ui/elastic_textarea';
|
||||
import 'ui/filters/markdown';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { fatalError } from 'ui/notify';
|
||||
import { keyCodes } from '@elastic/eui';
|
||||
import advancedRowTemplate from 'plugins/kibana/management/sections/settings/advanced_row.html';
|
||||
|
||||
uiModules.get('apps/management')
|
||||
.directive('advancedRow', function (config, Notifier) {
|
||||
.directive('advancedRow', function (config) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
replace: true,
|
||||
|
@ -15,8 +16,6 @@ uiModules.get('apps/management')
|
|||
configs: '='
|
||||
},
|
||||
link: function ($scope) {
|
||||
const notify = new Notifier();
|
||||
|
||||
// To allow passing form validation state back
|
||||
$scope.forms = {};
|
||||
|
||||
|
@ -27,7 +26,7 @@ uiModules.get('apps/management')
|
|||
.then(function () {
|
||||
conf.loading = conf.editing = false;
|
||||
})
|
||||
.catch(notify.fatal);
|
||||
.catch(fatalError);
|
||||
};
|
||||
|
||||
$scope.maybeCancel = function ($event, conf) {
|
||||
|
|
|
@ -8,7 +8,7 @@ import 'ui/share';
|
|||
import 'ui/query_bar';
|
||||
import chrome from 'ui/chrome';
|
||||
import angular from 'angular';
|
||||
import { Notifier } from 'ui/notify/notifier';
|
||||
import { Notifier, toastNotifications } from 'ui/notify';
|
||||
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
|
||||
import { DocTitleProvider } from 'ui/doc_title';
|
||||
import { FilterBarQueryFilterProvider } from 'ui/filter_bar/query_filter';
|
||||
|
@ -251,7 +251,11 @@ function VisEditor($scope, $route, timefilter, AppState, $window, kbnUrl, courie
|
|||
$scope.kbnTopNav.close('save');
|
||||
|
||||
if (id) {
|
||||
notify.info('Saved Visualization "' + savedVis.title + '"');
|
||||
toastNotifications.addSuccess({
|
||||
title: `Saved '${savedVis.title}'`,
|
||||
'data-test-subj': 'saveVisualizationSuccess',
|
||||
});
|
||||
|
||||
if ($scope.isAddToDashMode()) {
|
||||
const savedVisualizationParsedUrl = new KibanaParsedUrl({
|
||||
basePath: chrome.getBasePath(),
|
||||
|
@ -281,7 +285,7 @@ function VisEditor($scope, $route, timefilter, AppState, $window, kbnUrl, courie
|
|||
$scope.unlink = function () {
|
||||
if (!$state.linked) return;
|
||||
|
||||
notify.info(`Unlinked Visualization "${savedVis.title}" from Saved Search "${savedVis.savedSearch.title}"`);
|
||||
toastNotifications.addSuccess(`Unlinked from saved search '${savedVis.savedSearch.title}'`);
|
||||
|
||||
$state.linked = false;
|
||||
const parent = searchSource.getParent(true);
|
||||
|
|
|
@ -11,14 +11,14 @@ import labDisabledTemplate from './visualize_lab_disabled.html';
|
|||
import chrome from 'ui/chrome';
|
||||
|
||||
export class VisualizeEmbeddableFactory extends EmbeddableFactory {
|
||||
constructor(savedVisualizations, timefilter, Notifier, Promise, Private, config) {
|
||||
constructor(savedVisualizations, timefilter, Promise, Private, config) {
|
||||
super();
|
||||
this._config = config;
|
||||
this.savedVisualizations = savedVisualizations;
|
||||
this.name = 'visualization';
|
||||
this.Promise = Promise;
|
||||
this.brushEvent = utilsBrushEventProvider(timefilter);
|
||||
this.filterBarClickHandler = filterBarClickHandlerProvider(Notifier, Private);
|
||||
this.filterBarClickHandler = filterBarClickHandlerProvider(Private);
|
||||
}
|
||||
|
||||
getEditPath(panelId) {
|
||||
|
|
|
@ -5,11 +5,10 @@ export function visualizeEmbeddableFactoryProvider(Private) {
|
|||
const VisualizeEmbeddableFactoryProvider = (
|
||||
savedVisualizations,
|
||||
timefilter,
|
||||
Notifier,
|
||||
Promise,
|
||||
Private,
|
||||
config) => {
|
||||
return new VisualizeEmbeddableFactory(savedVisualizations, timefilter, Notifier, Promise, Private, config);
|
||||
return new VisualizeEmbeddableFactory(savedVisualizations, timefilter, Promise, Private, config);
|
||||
};
|
||||
return Private(VisualizeEmbeddableFactoryProvider);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import moment from 'moment-timezone';
|
|||
|
||||
import { DocTitleProvider } from 'ui/doc_title';
|
||||
import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry';
|
||||
import { notify } from 'ui/notify';
|
||||
import { notify, fatalError, toastNotifications } from 'ui/notify';
|
||||
import { timezoneProvider } from 'ui/vis/lib/timezone';
|
||||
|
||||
require('ui/autoload/all');
|
||||
|
@ -47,6 +47,8 @@ require('ui/routes')
|
|||
}
|
||||
});
|
||||
|
||||
const location = 'Timelion';
|
||||
|
||||
app.controller('timelion', function (
|
||||
$http,
|
||||
$route,
|
||||
|
@ -75,7 +77,7 @@ app.controller('timelion', function (
|
|||
timefilter.enableTimeRangeSelector();
|
||||
|
||||
const notify = new Notifier({
|
||||
location: 'Timelion'
|
||||
location
|
||||
});
|
||||
|
||||
const savedVisualizations = Private(SavedObjectRegistryProvider).byLoaderPropertiesName.visualizations;
|
||||
|
@ -110,9 +112,9 @@ app.controller('timelion', function (
|
|||
const title = savedSheet.title;
|
||||
function doDelete() {
|
||||
savedSheet.delete().then(() => {
|
||||
notify.info('Deleted ' + title);
|
||||
toastNotifications.addSuccess(`Deleted '${title}'`);
|
||||
kbnUrl.change('/');
|
||||
}).catch(notify.fatal);
|
||||
}).catch(error => fatalError(error, location));
|
||||
}
|
||||
|
||||
const confirmModalOptions = {
|
||||
|
@ -261,7 +263,7 @@ app.controller('timelion', function (
|
|||
savedSheet.timelion_rows = $scope.state.rows;
|
||||
savedSheet.save().then(function (id) {
|
||||
if (id) {
|
||||
notify.info('Saved sheet as "' + savedSheet.title + '"');
|
||||
toastNotifications.addSuccess(`Saved sheet '${savedSheet.title}'`);
|
||||
if (savedSheet.id !== $routeParams.id) {
|
||||
kbnUrl.change('/{{id}}', { id: savedSheet.id });
|
||||
}
|
||||
|
@ -278,7 +280,9 @@ app.controller('timelion', function (
|
|||
savedExpression.title = title;
|
||||
savedExpression.visState.title = title;
|
||||
savedExpression.save().then(function (id) {
|
||||
if (id) notify.info('Saved expression as "' + savedExpression.title + '"');
|
||||
if (id) {
|
||||
toastNotifications.addSuccess(`Saved expression '${savedExpression.title}'`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ export const schema = Joi.object().keys({
|
|||
bail: Joi.boolean().default(false),
|
||||
grep: Joi.string(),
|
||||
slow: Joi.number().default(30000),
|
||||
timeout: Joi.number().default(INSPECTING ? Infinity : 120000),
|
||||
timeout: Joi.number().default(INSPECTING ? Infinity : 180000),
|
||||
ui: Joi.string().default('bdd'),
|
||||
}).default(),
|
||||
|
||||
|
|
11
src/ui/public/UI_SYSTEMS.md
Normal file
11
src/ui/public/UI_SYSTEMS.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# UI Systems
|
||||
|
||||
In this directory you'll find various UI systems you can use to craft effective user experiences within Kibana.
|
||||
|
||||
## ui/notify
|
||||
|
||||
* [toastNotifications](notify/toasts/TOAST_NOTIFICATIONS.md)
|
||||
|
||||
## ui/vislib
|
||||
|
||||
* [VisLib](vislib/VISLIB.md)
|
|
@ -4,7 +4,7 @@ import editorHtml from '../controls/field.html';
|
|||
import { BaseParamTypeProvider } from './base';
|
||||
import 'ui/filters/field_type';
|
||||
import { IndexedArray } from 'ui/indexed_array';
|
||||
import { Notifier } from 'ui/notify/notifier';
|
||||
import { Notifier } from 'ui/notify';
|
||||
|
||||
export function FieldParamTypeProvider(Private, $filter) {
|
||||
const BaseParamType = Private(BaseParamTypeProvider);
|
||||
|
|
2
src/ui/public/chrome/api/angular.js
vendored
2
src/ui/public/chrome/api/angular.js
vendored
|
@ -2,7 +2,7 @@ import _ from 'lodash';
|
|||
import { format as formatUrl, parse as parseUrl } from 'url';
|
||||
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { Notifier } from 'ui/notify/notifier';
|
||||
import { Notifier } from 'ui/notify';
|
||||
import { UrlOverflowServiceProvider } from '../../error_url_overflow';
|
||||
|
||||
import { directivesProvider } from '../directives';
|
||||
|
|
|
@ -10,8 +10,31 @@
|
|||
|
||||
<div class="app-wrapper" ng-class="{ 'hidden-chrome': !chrome.getVisible() }">
|
||||
<div class="app-wrapper-panel">
|
||||
<kbn-notifications list="notifList"></kbn-notifications>
|
||||
<kbn-notifications
|
||||
list="notifList"
|
||||
></kbn-notifications>
|
||||
|
||||
<div ng-if="createFirstIndexPatternPrompt.isVisible" class="euiCallOut euiCallOut--warning noIndicesMessage">
|
||||
<div class="euiCallOutHeader">
|
||||
<svg class="euiIcon euiCallOutHeader__icon euiIcon--medium" aria-hidden="true" width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.3 10.717H6.7v-4h1.6v4zm-1.6-5.71a.83.83 0 0 1 .207-.578c.137-.153.334-.229.59-.229.256 0 .454.076.594.23.14.152.209.345.209.576 0 .228-.07.417-.21.568-.14.15-.337.226-.593.226-.256 0-.453-.075-.59-.226a.81.81 0 0 1-.207-.568zM7.5 13A5.506 5.506 0 0 1 2 7.5C2 4.467 4.467 2 7.5 2S13 4.467 13 7.5 10.533 13 7.5 13m0-12a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13"
|
||||
fill-rule="evenodd" />
|
||||
</svg>
|
||||
|
||||
<span class="euiCallOutHeader__title">
|
||||
In order to visualize and explore data in Kibana, you'll need to create an index pattern to retrieve data from Elasticsearch.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<global-toast-list
|
||||
toasts="toastNotifications"
|
||||
dismiss-toast="dismissToast"
|
||||
toast-life-time-ms="TOAST_LIFE_TIME_MS"
|
||||
></global-toast-list>
|
||||
|
||||
<kbn-loading-indicator></kbn-loading-indicator>
|
||||
|
||||
<div
|
||||
class="application"
|
||||
ng-class="'tab-' + chrome.getFirstPathSegment() + ' ' + chrome.getApplicationClasses()"
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
getUnhashableStatesProvider,
|
||||
unhashUrl,
|
||||
} from 'ui/state_management/state_hashing';
|
||||
import { notify } from 'ui/notify';
|
||||
import { notify, toastNotifications, createFirstIndexPatternPrompt } from 'ui/notify';
|
||||
import { SubUrlRouteFilterProvider } from './sub_url_route_filter';
|
||||
|
||||
export function kbnChromeProvider(chrome, internals) {
|
||||
|
@ -66,7 +66,14 @@ export function kbnChromeProvider(chrome, internals) {
|
|||
|
||||
// and some local values
|
||||
chrome.httpActive = $http.pendingRequests;
|
||||
|
||||
// Notifications
|
||||
$scope.notifList = notify._notifs;
|
||||
$scope.toastNotifications = toastNotifications.list;
|
||||
$scope.dismissToast = toastNotifications.remove;
|
||||
$scope.TOAST_LIFE_TIME_MS = 6000;
|
||||
|
||||
$scope.createFirstIndexPatternPrompt = createFirstIndexPatternPrompt;
|
||||
|
||||
return chrome;
|
||||
}
|
||||
|
|
|
@ -31,3 +31,7 @@ body { overflow-x: hidden; }
|
|||
.app-wrapper-panel {
|
||||
.flex-parent(@shrink: 0);
|
||||
}
|
||||
|
||||
.noIndicesMessage {
|
||||
margin: 12px;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import angular from 'angular';
|
||||
import { cloneDeep, defaultsDeep, isPlainObject } from 'lodash';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { Notifier } from 'ui/notify/notifier';
|
||||
import { Notifier } from 'ui/notify';
|
||||
import { ConfigDelayedUpdaterProvider } from 'ui/config/_delayed_updater';
|
||||
const module = uiModules.get('kibana/config');
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ export function RedirectWhenMissingProvider($location, kbnUrl, Notifier, Promise
|
|||
|
||||
url += (url.indexOf('?') >= 0 ? '&' : '?') + `notFound=${err.savedObjectType}`;
|
||||
|
||||
notify.info(err);
|
||||
notify.warning(err);
|
||||
kbnUrl.redirect(url);
|
||||
return Promise.halt();
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@ import 'ui/es';
|
|||
import 'ui/promises';
|
||||
import 'ui/index_patterns';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { Notifier } from 'ui/notify/notifier';
|
||||
import { addFatalErrorCallback } from 'ui/notify';
|
||||
|
||||
import { SearchSourceProvider } from './data_source/search_source';
|
||||
import { requestQueue } from './_request_queue';
|
||||
|
@ -111,7 +111,7 @@ uiModules.get('kibana/courier')
|
|||
});
|
||||
|
||||
const closeOnFatal = _.once(self.close);
|
||||
Notifier.fatalCallbacks.push(closeOnFatal);
|
||||
addFatalErrorCallback(closeOnFatal);
|
||||
}
|
||||
|
||||
return new Courier();
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { courierNotifier } from './notifier';
|
||||
import { fatalError } from 'ui/notify';
|
||||
import { CallClientProvider } from './call_client';
|
||||
import { CallResponseHandlersProvider } from './call_response_handlers';
|
||||
import { ContinueIncompleteProvider } from './continue_incomplete';
|
||||
import { RequestStatus } from './req_status';
|
||||
import { location } from './notifier';
|
||||
|
||||
/**
|
||||
* Fetch now provider should be used if you want the results searched and returned immediately.
|
||||
|
@ -30,7 +31,7 @@ export function FetchNowProvider(Private, Promise) {
|
|||
if (!req.started) return req;
|
||||
return req.retry();
|
||||
}))
|
||||
.catch(courierNotifier.fatal);
|
||||
.catch(error => fatalError(error, location));
|
||||
}
|
||||
|
||||
function fetchSearchResults(requests) {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { Notifier } from 'ui/notify/notifier';
|
||||
import { Notifier } from 'ui/notify';
|
||||
|
||||
export const location = 'Courier fetch';
|
||||
|
||||
export const courierNotifier = new Notifier({
|
||||
location: 'Courier Fetch'
|
||||
location,
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import _ from 'lodash';
|
||||
import { Notifier } from 'ui/notify/notifier';
|
||||
import { Notifier } from 'ui/notify';
|
||||
import { SearchRequestProvider } from './search_request';
|
||||
import { SegmentedHandleProvider } from './segmented_handle';
|
||||
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import _ from 'lodash';
|
||||
|
||||
import 'ui/promises';
|
||||
import { Notifier } from 'ui/notify/notifier';
|
||||
import { fatalError } from 'ui/notify';
|
||||
|
||||
export function LooperProvider($timeout, Promise) {
|
||||
const notify = new Notifier();
|
||||
|
||||
function Looper(ms, fn) {
|
||||
this._fn = fn;
|
||||
this._ms = ms === void 0 ? 1500 : ms;
|
||||
|
@ -144,7 +142,7 @@ export function LooperProvider($timeout, Promise) {
|
|||
})
|
||||
.catch(function (err) {
|
||||
self.stop();
|
||||
notify.fatal(err);
|
||||
fatalError(err);
|
||||
})
|
||||
.finally(function () {
|
||||
self.active = null;
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { Notifier } from 'ui/notify/notifier';
|
||||
import { fatalError } from 'ui/notify';
|
||||
import { SimpleEmitter } from 'ui/utils/simple_emitter';
|
||||
|
||||
export function EventsProvider(Private, Promise) {
|
||||
const notify = new Notifier({ location: 'EventEmitter' });
|
||||
const location = 'EventEmitter';
|
||||
|
||||
export function EventsProvider(Private, Promise) {
|
||||
_.class(Events).inherits(SimpleEmitter);
|
||||
function Events() {
|
||||
Events.Super.call(this);
|
||||
|
@ -40,7 +40,7 @@ export function EventsProvider(Private, Promise) {
|
|||
rebuildDefer();
|
||||
|
||||
// we ignore the completion of handlers, just watch for unhandled errors
|
||||
Promise.resolve(handler.apply(handler, args)).catch(notify.fatal);
|
||||
Promise.resolve(handler.apply(handler, args)).catch(error => fatalError(error, location));
|
||||
});
|
||||
}());
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import { RegistryFieldFormatsProvider } from 'ui/registry/field_formats';
|
|||
import { IndexPatternsFieldProvider } from 'ui/index_patterns/_field';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import fieldEditorTemplate from 'ui/field_editor/field_editor.html';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
import '../directives/documentation_href';
|
||||
import './field_editor.less';
|
||||
import {
|
||||
|
@ -38,9 +39,8 @@ uiModules
|
|||
getField: '&field'
|
||||
},
|
||||
controllerAs: 'editor',
|
||||
controller: function ($scope, Notifier, kbnUrl) {
|
||||
controller: function ($scope, kbnUrl) {
|
||||
const self = this;
|
||||
const notify = new Notifier({ location: 'Field Editor' });
|
||||
|
||||
getScriptingLangs().then((langs) => {
|
||||
self.scriptingLangs = langs;
|
||||
|
@ -81,7 +81,7 @@ uiModules
|
|||
|
||||
return indexPattern.save()
|
||||
.then(function () {
|
||||
notify.info('Saved Field "' + self.field.name + '"');
|
||||
toastNotifications.addSuccess(`Saved '${self.field.name}'`);
|
||||
redirectAway();
|
||||
});
|
||||
};
|
||||
|
@ -94,7 +94,7 @@ uiModules
|
|||
indexPattern.fields.remove({ name: field.name });
|
||||
return indexPattern.save()
|
||||
.then(function () {
|
||||
notify.info('Deleted Field "' + field.name + '"');
|
||||
toastNotifications.addSuccess(`Deleted '${self.field.name}'`);
|
||||
redirectAway();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import ngMock from 'ng_mock';
|
|||
import expect from 'expect.js';
|
||||
|
||||
import MockState from 'fixtures/mock_state';
|
||||
import { notify } from 'ui/notify';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
import AggConfigResult from 'ui/vis/agg_config_result';
|
||||
|
||||
import { VisProvider } from 'ui/vis';
|
||||
|
@ -40,22 +40,22 @@ describe('filterBarClickHandler', function () {
|
|||
}));
|
||||
|
||||
afterEach(function () {
|
||||
notify._notifs.splice(0);
|
||||
toastNotifications.list.splice(0);
|
||||
});
|
||||
|
||||
describe('on non-filterable fields', function () {
|
||||
it('warns about trying to filter on a non-filterable field', function () {
|
||||
const { clickHandler, aggConfigResult } = setup();
|
||||
expect(notify._notifs).to.have.length(0);
|
||||
expect(toastNotifications.list).to.have.length(0);
|
||||
clickHandler({ point: { aggConfigResult } });
|
||||
expect(notify._notifs).to.have.length(1);
|
||||
expect(toastNotifications.list).to.have.length(1);
|
||||
});
|
||||
|
||||
it('does not warn if the event is click is being simulated', function () {
|
||||
const { clickHandler, aggConfigResult } = setup();
|
||||
expect(notify._notifs).to.have.length(0);
|
||||
expect(toastNotifications.list).to.have.length(0);
|
||||
clickHandler({ point: { aggConfigResult } }, true);
|
||||
expect(notify._notifs).to.have.length(0);
|
||||
expect(toastNotifications.list).to.have.length(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,18 +2,16 @@ import _ from 'lodash';
|
|||
import { dedupFilters } from './lib/dedup_filters';
|
||||
import { uniqFilters } from './lib/uniq_filters';
|
||||
import { findByParam } from 'ui/utils/find_by_param';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
import { AddFiltersToKueryProvider } from './lib/add_filters_to_kuery';
|
||||
|
||||
export function FilterBarClickHandlerProvider(Notifier, Private) {
|
||||
export function FilterBarClickHandlerProvider(Private) {
|
||||
const addFiltersToKuery = Private(AddFiltersToKueryProvider);
|
||||
|
||||
return function ($state) {
|
||||
return function (event, simulate) {
|
||||
if (!$state) return;
|
||||
|
||||
const notify = new Notifier({
|
||||
location: 'Filter bar'
|
||||
});
|
||||
let aggConfigResult;
|
||||
|
||||
// Hierarchical and tabular data set their aggConfigResult parameter
|
||||
|
@ -45,7 +43,7 @@ export function FilterBarClickHandlerProvider(Notifier, Private) {
|
|||
return result.createFilter();
|
||||
} catch (e) {
|
||||
if (!simulate) {
|
||||
notify.warning(e.message);
|
||||
toastNotifications.addSuccess(e.message);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,16 +1,8 @@
|
|||
import _ from 'lodash';
|
||||
import { Notifier } from 'ui/notify/notifier';
|
||||
import { createFirstIndexPatternPrompt } from 'ui/notify';
|
||||
import { NoDefaultIndexPattern } from 'ui/errors';
|
||||
import { IndexPatternsGetProvider } from '../_get';
|
||||
import uiRoutes from 'ui/routes';
|
||||
const notify = new Notifier({
|
||||
location: 'Index Patterns'
|
||||
});
|
||||
|
||||
const NO_DEFAULT_INDEX_PATTERN_MSG = `
|
||||
In order to visualize and explore data in Kibana,
|
||||
you'll need to create an index pattern to retrieve data from Elasticsearch.
|
||||
`;
|
||||
|
||||
// eslint-disable-next-line @elastic/kibana-custom/no-default-export
|
||||
export default function (opts) {
|
||||
|
@ -57,7 +49,8 @@ export default function (opts) {
|
|||
|
||||
// Avoid being hostile to new users who don't have an index pattern setup yet
|
||||
// give them a friendly info message instead of a terse error message
|
||||
notify.info(NO_DEFAULT_INDEX_PATTERN_MSG, { lifetime: 15000 });
|
||||
createFirstIndexPatternPrompt.show();
|
||||
setTimeout(createFirstIndexPatternPrompt.hide, 15000);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
<div
|
||||
class="kuiLocalBreadcrumb"
|
||||
ng-if="pageTitle"
|
||||
data-test-subj="breadcrumbPageTitle"
|
||||
>
|
||||
{{ pageTitle }}
|
||||
</div>
|
||||
|
|
|
@ -2,7 +2,7 @@ import _ from 'lodash';
|
|||
import ngMock from 'ng_mock';
|
||||
import expect from 'expect.js';
|
||||
import sinon from 'sinon';
|
||||
import { Notifier } from 'ui/notify/notifier';
|
||||
import { Notifier } from 'ui/notify';
|
||||
|
||||
describe('Notifier', function () {
|
||||
let $interval;
|
||||
|
@ -179,58 +179,6 @@ describe('Notifier', function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe('#info', function () {
|
||||
testVersionInfo('info');
|
||||
|
||||
it('prepends location to message for content', function () {
|
||||
expect(notify('info').content).to.equal(params.location + ': ' + message);
|
||||
});
|
||||
|
||||
it('sets type to "info"', function () {
|
||||
expect(notify('info').type).to.equal('info');
|
||||
});
|
||||
|
||||
it('sets icon to "info-circle"', function () {
|
||||
expect(notify('info').icon).to.equal('info-circle');
|
||||
});
|
||||
|
||||
it('sets title to "Debug"', function () {
|
||||
expect(notify('info').title).to.equal('Debug');
|
||||
});
|
||||
|
||||
it('defaults lifetime to 5000', function () {
|
||||
expect(notify('info').lifetime).to.equal(5000);
|
||||
});
|
||||
|
||||
it('allows setting custom lifetime with opts', function () {
|
||||
const customLifetime = 10000;
|
||||
expect(notify('info', { lifetime: customLifetime }).lifetime).to.equal(customLifetime);
|
||||
});
|
||||
|
||||
it('does not allow reporting', function () {
|
||||
const includesReport = _.includes(notify('info').actions, 'report');
|
||||
expect(includesReport).to.false;
|
||||
});
|
||||
|
||||
it('allows accepting', function () {
|
||||
const includesAccept = _.includes(notify('info').actions, 'accept');
|
||||
expect(includesAccept).to.true;
|
||||
});
|
||||
|
||||
it('does not include stack', function () {
|
||||
expect(notify('info').stack).not.to.be.defined;
|
||||
});
|
||||
|
||||
it('has css class helper functions', function () {
|
||||
expect(notify('info').getIconClass()).to.equal('fa fa-info-circle');
|
||||
expect(notify('info').getButtonClass()).to.equal('kuiButton--primary');
|
||||
expect(notify('info').getAlertClassStack()).to.equal('toast-stack alert alert-info');
|
||||
expect(notify('info').getAlertClass()).to.equal('toast alert alert-info');
|
||||
expect(notify('info').getButtonGroupClass()).to.equal('toast-controls');
|
||||
expect(notify('info').getToastMessageClass()).to.equal('toast-message');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#custom', function () {
|
||||
let customNotification;
|
||||
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
import './lib/_format_es_msg';
|
||||
import './lib/_format_msg';
|
||||
describe('Notifier', function () {
|
||||
|
||||
describe('Message formatters', function () {
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
class CreateFirstIndexPatternPrompt {
|
||||
constructor() {
|
||||
this.isVisible = false;
|
||||
}
|
||||
|
||||
show = () => {
|
||||
this.isVisible = true;
|
||||
}
|
||||
|
||||
hide = () => {
|
||||
this.isVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
export const createFirstIndexPatternPrompt = new CreateFirstIndexPatternPrompt();
|
|
@ -0,0 +1 @@
|
|||
export { createFirstIndexPatternPrompt } from './create_first_index_pattern_prompt';
|
|
@ -1,18 +0,0 @@
|
|||
import { uiModules } from 'ui/modules';
|
||||
import toasterTemplate from 'ui/notify/partials/toaster.html';
|
||||
import 'ui/notify/notify.less';
|
||||
import 'ui/filters/markdown';
|
||||
import 'ui/directives/truncated';
|
||||
|
||||
const notify = uiModules.get('kibana/notify');
|
||||
|
||||
notify.directive('kbnNotifications', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
list: '=list'
|
||||
},
|
||||
replace: true,
|
||||
template: toasterTemplate
|
||||
};
|
||||
});
|
88
src/ui/public/notify/fatal_error.js
Normal file
88
src/ui/public/notify/fatal_error.js
Normal file
|
@ -0,0 +1,88 @@
|
|||
import _ from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import { metadata } from 'ui/metadata';
|
||||
import { formatMsg, formatStack } from './lib';
|
||||
import fatalSplashScreen from './partials/fatal_splash_screen.html';
|
||||
|
||||
const {
|
||||
version,
|
||||
buildNum,
|
||||
} = metadata;
|
||||
|
||||
// used to identify the first call to fatal, set to false there
|
||||
let firstFatal = true;
|
||||
|
||||
const fatalToastTemplate = (function lazyTemplate(tmpl) {
|
||||
let compiled;
|
||||
return function (vars) {
|
||||
return (compiled || (compiled = _.template(tmpl)))(vars);
|
||||
};
|
||||
}(require('./partials/fatal.html')));
|
||||
|
||||
// to be notified when the first fatal error occurs, push a function into this array.
|
||||
const fatalCallbacks = [];
|
||||
|
||||
export const addFatalErrorCallback = callback => {
|
||||
fatalCallbacks.push(callback);
|
||||
};
|
||||
|
||||
function formatInfo() {
|
||||
const info = [];
|
||||
|
||||
if (!_.isUndefined(version)) {
|
||||
info.push(`Version: ${version}`);
|
||||
}
|
||||
|
||||
if (!_.isUndefined(buildNum)) {
|
||||
info.push(`Build: ${buildNum}`);
|
||||
}
|
||||
|
||||
return info.join('\n');
|
||||
}
|
||||
|
||||
// We're exporting this because state_management/state.js calls fatalError, which makes it
|
||||
// impossible to test unless we stub this stuff out.
|
||||
export const fatalErrorInternals = {
|
||||
show: (err, location) => {
|
||||
if (firstFatal) {
|
||||
_.callEach(fatalCallbacks);
|
||||
firstFatal = false;
|
||||
window.addEventListener('hashchange', function () {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
|
||||
const html = fatalToastTemplate({
|
||||
info: formatInfo(),
|
||||
msg: formatMsg(err, location),
|
||||
stack: formatStack(err)
|
||||
});
|
||||
|
||||
let $container = $('#fatal-splash-screen');
|
||||
|
||||
if (!$container.length) {
|
||||
$(document.body)
|
||||
// in case the app has not completed boot
|
||||
.removeAttr('ng-cloak')
|
||||
.html(fatalSplashScreen);
|
||||
|
||||
$container = $('#fatal-splash-screen');
|
||||
}
|
||||
|
||||
$container.append(html);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Kill the page, display an error, then throw the error.
|
||||
* Used as a last-resort error back in many promise chains
|
||||
* so it rethrows the error that's displayed on the page.
|
||||
*
|
||||
* @param {Error} err - The error that occured
|
||||
*/
|
||||
export function fatalError(err, location) {
|
||||
fatalErrorInternals.show(err, location);
|
||||
console.error(err.stack); // eslint-disable-line no-console
|
||||
|
||||
throw err;
|
||||
}
|
|
@ -1,2 +1,5 @@
|
|||
export { notify } from './notify';
|
||||
export { Notifier } from './notifier';
|
||||
export { fatalError, fatalErrorInternals, addFatalErrorCallback } from './fatal_error';
|
||||
export { toastNotifications } from './toasts';
|
||||
export { createFirstIndexPatternPrompt } from './create_first_index_pattern_prompt';
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { formatESMsg } from 'ui/notify/lib/_format_es_msg';
|
||||
import { formatESMsg } from './format_es_msg';
|
||||
import expect from 'expect.js';
|
||||
describe('formatESMsg', function () {
|
||||
|
||||
it('should return undefined if passed a basic error', function () {
|
||||
describe('formatESMsg', () => {
|
||||
test('should return undefined if passed a basic error', () => {
|
||||
const err = new Error('This is a normal error');
|
||||
|
||||
const actual = formatESMsg(err);
|
||||
|
@ -10,7 +10,7 @@ describe('formatESMsg', function () {
|
|||
expect(actual).to.be(undefined);
|
||||
});
|
||||
|
||||
it('should return undefined if passed a string', function () {
|
||||
test('should return undefined if passed a string', () => {
|
||||
const err = 'This is a error string';
|
||||
|
||||
const actual = formatESMsg(err);
|
||||
|
@ -18,7 +18,7 @@ describe('formatESMsg', function () {
|
|||
expect(actual).to.be(undefined);
|
||||
});
|
||||
|
||||
it('should return the root_cause if passed an extended elasticsearch', function () {
|
||||
test('should return the root_cause if passed an extended elasticsearch', () => {
|
||||
const err = new Error('This is an elasticsearch error');
|
||||
err.resp = {
|
||||
error: {
|
||||
|
@ -35,7 +35,7 @@ describe('formatESMsg', function () {
|
|||
expect(actual).to.equal('I am the detailed message');
|
||||
});
|
||||
|
||||
it('should combine the reason messages if more than one is returned.', function () {
|
||||
test('should combine the reason messages if more than one is returned.', () => {
|
||||
const err = new Error('This is an elasticsearch error');
|
||||
err.resp = {
|
||||
error: {
|
|
@ -1,5 +1,5 @@
|
|||
import _ from 'lodash';
|
||||
import { formatESMsg } from 'ui/notify/lib/_format_es_msg';
|
||||
import { formatESMsg } from './format_es_msg';
|
||||
const has = _.has;
|
||||
|
||||
/**
|
|
@ -1,27 +1,27 @@
|
|||
import { formatMsg } from 'ui/notify/lib/_format_msg';
|
||||
import { formatMsg } from './format_msg';
|
||||
import expect from 'expect.js';
|
||||
describe('formatMsg', function () {
|
||||
|
||||
it('should prepend the second argument to result', function () {
|
||||
describe('formatMsg', () => {
|
||||
test('should prepend the second argument to result', () => {
|
||||
const actual = formatMsg('error message', 'unit_test');
|
||||
|
||||
expect(actual).to.equal('unit_test: error message');
|
||||
});
|
||||
|
||||
it('should handle a simple string', function () {
|
||||
test('should handle a simple string', () => {
|
||||
const actual = formatMsg('error message');
|
||||
|
||||
expect(actual).to.equal('error message');
|
||||
});
|
||||
|
||||
it('should handle a simple Error object', function () {
|
||||
test('should handle a simple Error object', () => {
|
||||
const err = new Error('error message');
|
||||
const actual = formatMsg(err);
|
||||
|
||||
expect(actual).to.equal('error message');
|
||||
});
|
||||
|
||||
it('should handle a simple Angular $http error object', function () {
|
||||
test('should handle a simple Angular $http error object', () => {
|
||||
const err = {
|
||||
data: {
|
||||
statusCode: 403,
|
||||
|
@ -37,7 +37,7 @@ describe('formatMsg', function () {
|
|||
expect(actual).to.equal('Error 403 Forbidden: [security_exception] action [indices:data/read/msearch] is unauthorized for user [user]');
|
||||
});
|
||||
|
||||
it('should handle an extended elasticsearch error', function () {
|
||||
test('should handle an extended elasticsearch error', () => {
|
||||
const err = {
|
||||
resp: {
|
||||
error: {
|
||||
|
@ -54,5 +54,4 @@ describe('formatMsg', function () {
|
|||
|
||||
expect(actual).to.equal('I am the detailed message');
|
||||
});
|
||||
|
||||
});
|
7
src/ui/public/notify/lib/format_stack.js
Normal file
7
src/ui/public/notify/lib/format_stack.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
// browsers format Error.stack differently; always include message
|
||||
export function formatStack(err) {
|
||||
if (err.stack && !~err.stack.indexOf(err.message)) {
|
||||
return 'Error: ' + err.message + '\n' + err.stack;
|
||||
}
|
||||
return err.stack;
|
||||
}
|
3
src/ui/public/notify/lib/index.js
Normal file
3
src/ui/public/notify/lib/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
export { formatESMsg } from './format_es_msg';
|
||||
export { formatMsg } from './format_msg';
|
||||
export { formatStack } from './format_stack';
|
|
@ -1,29 +1,21 @@
|
|||
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 { formatMsg, formatStack } from './lib';
|
||||
import { fatalError } from './fatal_error';
|
||||
import 'ui/render_directive';
|
||||
/* eslint no-console: 0 */
|
||||
|
||||
const notifs = [];
|
||||
const version = metadata.version;
|
||||
const buildNum = metadata.buildNum;
|
||||
|
||||
const {
|
||||
version,
|
||||
buildNum,
|
||||
} = metadata;
|
||||
|
||||
const consoleGroups = ('group' in window.console) && ('groupCollapsed' in window.console) && ('groupEnd' in window.console);
|
||||
|
||||
const log = _.bindKey(console, 'log');
|
||||
|
||||
// used to identify the first call to fatal, set to false there
|
||||
let firstFatal = true;
|
||||
|
||||
const fatalToastTemplate = (function lazyTemplate(tmpl) {
|
||||
let compiled;
|
||||
return function (vars) {
|
||||
return (compiled || (compiled = _.template(tmpl)))(vars);
|
||||
};
|
||||
}(require('ui/notify/partials/fatal.html')));
|
||||
|
||||
function now() {
|
||||
if (window.performance && window.performance.now) {
|
||||
return window.performance.now();
|
||||
|
@ -188,28 +180,6 @@ function set(opts, cb) {
|
|||
Notifier.prototype.add = add;
|
||||
Notifier.prototype.set = set;
|
||||
|
||||
function formatInfo() {
|
||||
const info = [];
|
||||
|
||||
if (!_.isUndefined(version)) {
|
||||
info.push(`Version: ${version}`);
|
||||
}
|
||||
|
||||
if (!_.isUndefined(buildNum)) {
|
||||
info.push(`Build: ${buildNum}`);
|
||||
}
|
||||
|
||||
return info.join('\n');
|
||||
}
|
||||
|
||||
// browsers format Error.stack differently; always include message
|
||||
function formatStack(err) {
|
||||
if (err.stack && !~err.stack.indexOf(err.message)) {
|
||||
return 'Error: ' + err.message + '\n' + err.stack;
|
||||
}
|
||||
return err.stack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Functionality to check that
|
||||
*/
|
||||
|
@ -220,7 +190,16 @@ export function Notifier(opts) {
|
|||
// label type thing to say where notifications came from
|
||||
self.from = opts.location;
|
||||
|
||||
'event lifecycle timed fatal error warning info banner'.split(' ').forEach(function (m) {
|
||||
const notificationLevels = [
|
||||
'event',
|
||||
'lifecycle',
|
||||
'timed',
|
||||
'error',
|
||||
'warning',
|
||||
'banner',
|
||||
];
|
||||
|
||||
notificationLevels.forEach(function (m) {
|
||||
self[m] = _.bind(self[m], self);
|
||||
});
|
||||
}
|
||||
|
@ -238,9 +217,6 @@ Notifier.applyConfig = function (config) {
|
|||
_.merge(Notifier.config, config);
|
||||
};
|
||||
|
||||
// to be notified when the first fatal error occurs, push a function into this array.
|
||||
Notifier.fatalCallbacks = [];
|
||||
|
||||
// "Constants"
|
||||
Notifier.QS_PARAM_MESSAGE = 'notif_msg';
|
||||
Notifier.QS_PARAM_LEVEL = 'notif_lvl';
|
||||
|
@ -260,7 +236,12 @@ Notifier.pullMessageFromUrl = ($location) => {
|
|||
$location.search(Notifier.QS_PARAM_LEVEL, null);
|
||||
|
||||
const notifier = new Notifier(config);
|
||||
notifier[level](message);
|
||||
|
||||
if (level === 'fatal') {
|
||||
fatalError(message);
|
||||
} else {
|
||||
notifier[level](message);
|
||||
}
|
||||
};
|
||||
|
||||
// simply a pointer to the global notif list
|
||||
|
@ -309,55 +290,6 @@ Notifier.prototype.timed = function (name, fn) {
|
|||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Kill the page, display an error, then throw the error.
|
||||
* Used as a last-resort error back in many promise chains
|
||||
* so it rethrows the error that's displayed on the page.
|
||||
*
|
||||
* @param {Error} err - The error that occured
|
||||
*/
|
||||
Notifier.prototype.fatal = function (err) {
|
||||
this._showFatal(err);
|
||||
throw err;
|
||||
};
|
||||
|
||||
/**
|
||||
* Display an error that destroys the entire app. Broken out so that
|
||||
* global error handlers can display fatal errors without throwing another
|
||||
* error like in #fatal()
|
||||
*
|
||||
* @param {Error} err - The fatal error that occured
|
||||
*/
|
||||
Notifier.prototype._showFatal = function (err) {
|
||||
if (firstFatal) {
|
||||
_.callEach(Notifier.fatalCallbacks);
|
||||
firstFatal = false;
|
||||
window.addEventListener('hashchange', function () {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
|
||||
const html = fatalToastTemplate({
|
||||
info: formatInfo(),
|
||||
msg: formatMsg(err, this.from),
|
||||
stack: formatStack(err)
|
||||
});
|
||||
|
||||
let $container = $('#fatal-splash-screen');
|
||||
|
||||
if (!$container.length) {
|
||||
$(document.body)
|
||||
// in case the app has not completed boot
|
||||
.removeAttr('ng-cloak')
|
||||
.html(fatalSplashScreen);
|
||||
|
||||
$container = $('#fatal-splash-screen');
|
||||
}
|
||||
|
||||
$container.append(html);
|
||||
console.error(err.stack);
|
||||
};
|
||||
|
||||
const overrideableOptions = ['lifetime', 'icon'];
|
||||
|
||||
/**
|
||||
|
@ -405,28 +337,6 @@ Notifier.prototype.warning = function (msg, opts, cb) {
|
|||
return add(config, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* Display a debug message
|
||||
* @param {String} msg
|
||||
* @param {Function} cb
|
||||
*/
|
||||
Notifier.prototype.info = function (msg, opts, cb) {
|
||||
if (_.isFunction(opts)) {
|
||||
cb = opts;
|
||||
opts = {};
|
||||
}
|
||||
|
||||
const config = _.assign({
|
||||
type: 'info',
|
||||
content: formatMsg(msg, this.from),
|
||||
icon: 'info-circle',
|
||||
title: 'Debug',
|
||||
lifetime: _.get(opts, 'lifetime', Notifier.config.infoLifetime),
|
||||
actions: ['accept']
|
||||
}, _.pick(opts, overrideableOptions));
|
||||
return add(config, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* Display a banner message
|
||||
* @param {String} msg
|
||||
|
@ -623,13 +533,13 @@ function createGroupLogger(type, opts) {
|
|||
|
||||
if (consoleGroups) {
|
||||
if (status) {
|
||||
console.log(status);
|
||||
console.groupEnd();
|
||||
console.log(status); // eslint-disable-line no-console
|
||||
console.groupEnd(); // eslint-disable-line no-console
|
||||
} else {
|
||||
if (opts.open) {
|
||||
console.group(name);
|
||||
console.group(name); // eslint-disable-line no-console
|
||||
} else {
|
||||
console.groupCollapsed(name);
|
||||
console.groupCollapsed(name); // eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -1,9 +1,25 @@
|
|||
import { uiModules } from 'ui/modules';
|
||||
import { Notifier } from 'ui/notify/notifier';
|
||||
import 'ui/notify/directives';
|
||||
import { fatalError } from './fatal_error';
|
||||
import { Notifier } from './notifier';
|
||||
import { metadata } from 'ui/metadata';
|
||||
import template from './partials/toaster.html';
|
||||
import './notify.less';
|
||||
import 'ui/filters/markdown';
|
||||
import 'ui/directives/truncated';
|
||||
|
||||
const module = uiModules.get('kibana/notify');
|
||||
|
||||
module.directive('kbnNotifications', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
list: '=list'
|
||||
},
|
||||
replace: true,
|
||||
template
|
||||
};
|
||||
});
|
||||
|
||||
export const notify = new Notifier();
|
||||
|
||||
module.factory('createNotifier', function () {
|
||||
|
@ -46,7 +62,7 @@ function applyConfig(config) {
|
|||
}
|
||||
|
||||
window.onerror = function (err, url, line) {
|
||||
notify.fatal(new Error(err + ' (' + url + ':' + line + ')'));
|
||||
fatalError(new Error(`${err} (${url}:${line})`));
|
||||
return true;
|
||||
};
|
||||
|
||||
|
@ -59,3 +75,4 @@ if (window.addEventListener) {
|
|||
notifier.log(`Detected an unhandled Promise rejection.\n${e.reason}`);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
100
src/ui/public/notify/toasts/TOAST_NOTIFICATIONS.md
Normal file
100
src/ui/public/notify/toasts/TOAST_NOTIFICATIONS.md
Normal file
|
@ -0,0 +1,100 @@
|
|||
# Toast notifications
|
||||
|
||||
Use this service to surface toasts in the bottom-right corner of the screen. After a brief delay, they'll disappear. They're useful for notifying the user of state changes. See [the EUI docs](elastic.github.io/eui/) for more information on toasts and their role within the UI.
|
||||
|
||||
## Importing the module
|
||||
|
||||
```js
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
```
|
||||
|
||||
## Interface
|
||||
|
||||
### Adding toasts
|
||||
|
||||
For convenience, there are several methods which predefine the appearance of different types of toasts. Use these methods so that the same types of toasts look similar to the user.
|
||||
|
||||
#### Default
|
||||
|
||||
Neutral toast. Tell the user a change in state has occurred, which is not necessarily good or bad.
|
||||
|
||||
```js
|
||||
toastNotifications.add('Copied to clipboard');
|
||||
```
|
||||
|
||||
#### Success
|
||||
|
||||
Let the user know that an action was successful, such as saving or deleting an object.
|
||||
|
||||
```js
|
||||
toastNotifications.addSuccess('Saved document');
|
||||
```
|
||||
|
||||
#### Warning
|
||||
|
||||
If something OK or good happened, but perhaps wasn't perfect, show a warning toast.
|
||||
|
||||
```js
|
||||
toastNotifications.addWarning('Saved document, but not edit history');
|
||||
```
|
||||
|
||||
#### Danger
|
||||
|
||||
When the user initiated an action but the action failed, show them a danger toast.
|
||||
|
||||
```js
|
||||
toastNotifications.addDanger('An error caused your document to be lost');
|
||||
```
|
||||
|
||||
### Removing a toast
|
||||
|
||||
Toasts will automatically be dismissed after a brief delay, but if for some reason you want to dismiss a toast, you can use the returned toast from one of the `add` methods and then pass it to `remove`.
|
||||
|
||||
```js
|
||||
const toast = toastNotifications.add('Saved document');
|
||||
toastNotifications.remove(toast);
|
||||
```
|
||||
|
||||
### Configuration options
|
||||
|
||||
If you want to configure the toast further you can provide an object instead of a string. The properties of this object correspond to the `propTypes` accepted by the `EuiToast` component. Refer to [the EUI docs](elastic.github.io/eui/) for info on these `propTypes`.
|
||||
|
||||
```js
|
||||
toastNotifications.add({
|
||||
title: 'Saved document',
|
||||
text: 'Only you have access to this document',
|
||||
color: 'success',
|
||||
iconType: 'check',
|
||||
'data-test-subj': 'saveDocumentSuccess',
|
||||
});
|
||||
```
|
||||
|
||||
Because the underlying components are React, you can use JSX to pass in React elements to the `text` prop. This gives you total flexibility over the content displayed within the toast.
|
||||
|
||||
```js
|
||||
toastNotifications.add({
|
||||
title: 'Saved document',
|
||||
text: (
|
||||
<div>
|
||||
<p>
|
||||
Only you have access to this document. <a href="/documents">Edit permissions.</a>
|
||||
</p>
|
||||
|
||||
<button onClick={() => deleteDocument()}}>
|
||||
Delete document
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
```
|
||||
|
||||
## Use in functional tests
|
||||
|
||||
Functional tests are commonly used to verify that a user action yielded a sucessful outcome. if you surface a toast to notify the user of this successful outcome, you can place a `data-test-subj` attribute on the toast and use it to check if the toast exists inside of your functional test. This acts as a proxy for verifying the sucessful outcome.
|
||||
|
||||
```js
|
||||
toastNotifications.addSuccess({
|
||||
title: 'Saved document',
|
||||
'data-test-subj': 'saveDocumentSuccess',
|
||||
});
|
||||
```
|
|
@ -0,0 +1,146 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`GlobalToastList is rendered 1`] = `
|
||||
<div
|
||||
class="euiGlobalToastList"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`GlobalToastList props toasts is rendered 1`] = `
|
||||
<div
|
||||
class="euiGlobalToastList"
|
||||
>
|
||||
<div
|
||||
class="euiToast euiToast--success euiGlobalToastListItem"
|
||||
data-test-subj="a"
|
||||
id="a"
|
||||
>
|
||||
<div
|
||||
class="euiToastHeader euiToastHeader--withBody"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="euiIcon euiToastHeader__icon euiIcon--medium"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<defs>
|
||||
<path
|
||||
d="M6.5 12a.502.502 0 0 1-.354-.146l-4-4a.502.502 0 0 1 .708-.708L6.5 10.793l6.646-6.647a.502.502 0 0 1 .708.708l-7 7A.502.502 0 0 1 6.5 12"
|
||||
id="check-a"
|
||||
/>
|
||||
</defs>
|
||||
<use
|
||||
href="#check-a"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="euiToastHeader__title"
|
||||
>
|
||||
A
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
aria-label="Dismiss toast"
|
||||
class="euiToast__closeButton"
|
||||
data-test-subj="toastCloseButton"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="euiIcon euiIcon--medium"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<defs>
|
||||
<path
|
||||
d="M7.293 8l-4.147 4.146a.5.5 0 0 0 .708.708L8 8.707l4.146 4.147a.5.5 0 0 0 .708-.708L8.707 8l4.147-4.146a.5.5 0 0 0-.708-.708L8 7.293 3.854 3.146a.5.5 0 1 0-.708.708L7.293 8z"
|
||||
id="cross-a"
|
||||
/>
|
||||
</defs>
|
||||
<use
|
||||
fill-rule="nonzero"
|
||||
href="#cross-a"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<div
|
||||
class="euiText euiText--small"
|
||||
>
|
||||
a
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiToast euiToast--danger euiGlobalToastListItem"
|
||||
data-test-subj="b"
|
||||
id="b"
|
||||
>
|
||||
<div
|
||||
class="euiToastHeader euiToastHeader--withBody"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="euiIcon euiToastHeader__icon euiIcon--medium"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g
|
||||
fill-rule="evenodd"
|
||||
>
|
||||
<path
|
||||
d="M7.5 2.236L1.618 14h11.764L7.5 2.236zm.894-.447l5.882 11.764A1 1 0 0 1 13.382 15H1.618a1 1 0 0 1-.894-1.447L6.606 1.789a1 1 0 0 1 1.788 0z"
|
||||
/>
|
||||
<path
|
||||
d="M7 6h1v5H7zM7 12h1v1H7z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
<span
|
||||
class="euiToastHeader__title"
|
||||
>
|
||||
B
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
aria-label="Dismiss toast"
|
||||
class="euiToast__closeButton"
|
||||
data-test-subj="toastCloseButton"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="euiIcon euiIcon--medium"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<defs>
|
||||
<path
|
||||
d="M7.293 8l-4.147 4.146a.5.5 0 0 0 .708.708L8 8.707l4.146 4.147a.5.5 0 0 0 .708-.708L8.707 8l4.147-4.146a.5.5 0 0 0-.708-.708L8 7.293 3.854 3.146a.5.5 0 1 0-.708.708L7.293 8z"
|
||||
id="cross-a"
|
||||
/>
|
||||
</defs>
|
||||
<use
|
||||
fill-rule="nonzero"
|
||||
href="#cross-a"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<div
|
||||
class="euiText euiText--small"
|
||||
>
|
||||
b
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
137
src/ui/public/notify/toasts/global_toast_list.js
Normal file
137
src/ui/public/notify/toasts/global_toast_list.js
Normal file
|
@ -0,0 +1,137 @@
|
|||
import React, {
|
||||
Component,
|
||||
} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import 'ngreact';
|
||||
import { uiModules } from 'ui/modules';
|
||||
|
||||
import {
|
||||
EuiGlobalToastList,
|
||||
EuiGlobalToastListItem,
|
||||
EuiToast,
|
||||
} from '@elastic/eui';
|
||||
|
||||
export const TOAST_FADE_OUT_MS = 250;
|
||||
|
||||
export class GlobalToastList extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
toastIdToDismissedMap: {}
|
||||
};
|
||||
|
||||
this.timeoutIds = [];
|
||||
this.toastIdToScheduledForDismissalMap = {};
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
toasts: PropTypes.array,
|
||||
dismissToast: PropTypes.func.isRequired,
|
||||
toastLifeTimeMs: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
toasts: [],
|
||||
};
|
||||
|
||||
scheduleAllToastsForDismissal = () => {
|
||||
this.props.toasts.forEach(toast => {
|
||||
if (!this.toastIdToScheduledForDismissalMap[toast.id]) {
|
||||
this.scheduleToastForDismissal(toast);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
scheduleToastForDismissal = (toast, isImmediate = false) => {
|
||||
this.toastIdToScheduledForDismissalMap[toast.id] = true;
|
||||
const toastLifeTimeMs = isImmediate ? 0 : this.props.toastLifeTimeMs;
|
||||
|
||||
// Start fading the toast out once its lifetime elapses.
|
||||
this.timeoutIds.push(setTimeout(() => {
|
||||
this.startDismissingToast(toast);
|
||||
}, toastLifeTimeMs));
|
||||
|
||||
// Remove the toast after it's done fading out.
|
||||
this.timeoutIds.push(setTimeout(() => {
|
||||
this.props.dismissToast(toast);
|
||||
this.setState(prevState => {
|
||||
const toastIdToDismissedMap = { ...prevState.toastIdToDismissedMap };
|
||||
delete toastIdToDismissedMap[toast.id];
|
||||
delete this.toastIdToScheduledForDismissalMap[toast.id];
|
||||
|
||||
return {
|
||||
toastIdToDismissedMap,
|
||||
};
|
||||
});
|
||||
}, toastLifeTimeMs + TOAST_FADE_OUT_MS));
|
||||
};
|
||||
|
||||
startDismissingToast(toast) {
|
||||
this.setState(prevState => {
|
||||
const toastIdToDismissedMap = {
|
||||
...prevState.toastIdToDismissedMap,
|
||||
[toast.id]: true,
|
||||
};
|
||||
|
||||
return {
|
||||
toastIdToDismissedMap,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.scheduleAllToastsForDismissal();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.timeoutIds.forEach(clearTimeout);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.scheduleAllToastsForDismissal();
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
toasts,
|
||||
} = this.props;
|
||||
|
||||
const renderedToasts = toasts.map(toast => {
|
||||
const {
|
||||
text,
|
||||
...rest
|
||||
} = toast;
|
||||
|
||||
return (
|
||||
<EuiGlobalToastListItem
|
||||
key={toast.id}
|
||||
isDismissed={this.state.toastIdToDismissedMap[toast.id]}
|
||||
>
|
||||
<EuiToast
|
||||
onClose={this.scheduleToastForDismissal.bind(toast, true)}
|
||||
{...rest}
|
||||
>
|
||||
{text}
|
||||
</EuiToast>
|
||||
</EuiGlobalToastListItem>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiGlobalToastList>
|
||||
{renderedToasts}
|
||||
</EuiGlobalToastList>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const app = uiModules.get('app/kibana', ['react']);
|
||||
|
||||
app.directive('globalToastList', function (reactDirective) {
|
||||
return reactDirective(GlobalToastList, [
|
||||
'toasts',
|
||||
'toastLifeTimeMs',
|
||||
['dismissToast', { watchDepth: 'reference' }],
|
||||
]);
|
||||
});
|
103
src/ui/public/notify/toasts/global_toast_list.test.js
Normal file
103
src/ui/public/notify/toasts/global_toast_list.test.js
Normal file
|
@ -0,0 +1,103 @@
|
|||
import React from 'react';
|
||||
import { render, mount } from 'enzyme';
|
||||
import sinon from 'sinon';
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
|
||||
import {
|
||||
GlobalToastList,
|
||||
TOAST_FADE_OUT_MS,
|
||||
} from './global_toast_list';
|
||||
|
||||
describe('GlobalToastList', () => {
|
||||
test('is rendered', () => {
|
||||
const component = render(
|
||||
<GlobalToastList
|
||||
dismissToast={() => {}}
|
||||
toastLifeTimeMs={5}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component)
|
||||
.toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('props', () => {
|
||||
describe('toasts', () => {
|
||||
test('is rendered', () => {
|
||||
const toasts = [{
|
||||
title: 'A',
|
||||
text: 'a',
|
||||
color: 'success',
|
||||
iconType: 'check',
|
||||
'data-test-subj': 'a',
|
||||
id: 'a',
|
||||
}, {
|
||||
title: 'B',
|
||||
text: 'b',
|
||||
color: 'danger',
|
||||
iconType: 'alert',
|
||||
'data-test-subj': 'b',
|
||||
id: 'b',
|
||||
}];
|
||||
|
||||
const component = render(
|
||||
<GlobalToastList
|
||||
toasts={toasts}
|
||||
dismissToast={() => {}}
|
||||
toastLifeTimeMs={5}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component)
|
||||
.toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('dismissToast', () => {
|
||||
test('is called when a toast is clicked', done => {
|
||||
const dismissToastSpy = sinon.spy();
|
||||
const component = mount(
|
||||
<GlobalToastList
|
||||
toasts={[{
|
||||
'data-test-subj': 'b',
|
||||
id: 'b',
|
||||
}]}
|
||||
dismissToast={dismissToastSpy}
|
||||
toastLifeTimeMs={100}
|
||||
/>
|
||||
);
|
||||
|
||||
const toastB = findTestSubject(component, 'b');
|
||||
const closeButton = findTestSubject(toastB, 'toastCloseButton');
|
||||
closeButton.simulate('click');
|
||||
|
||||
// The callback is invoked once the toast fades from view.
|
||||
setTimeout(() => {
|
||||
expect(dismissToastSpy.called).toBe(true);
|
||||
done();
|
||||
}, TOAST_FADE_OUT_MS + 1);
|
||||
});
|
||||
|
||||
test('is called when the toast lifetime elapses', done => {
|
||||
const TOAST_LIFE_TIME_MS = 5;
|
||||
const dismissToastSpy = sinon.spy();
|
||||
mount(
|
||||
<GlobalToastList
|
||||
toasts={[{
|
||||
'data-test-subj': 'b',
|
||||
id: 'b',
|
||||
}]}
|
||||
dismissToast={dismissToastSpy}
|
||||
toastLifeTimeMs={TOAST_LIFE_TIME_MS}
|
||||
/>
|
||||
);
|
||||
|
||||
// The callback is invoked once the toast fades from view.
|
||||
setTimeout(() => {
|
||||
expect(dismissToastSpy.called).toBe(true);
|
||||
done();
|
||||
}, TOAST_LIFE_TIME_MS + TOAST_FADE_OUT_MS);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
2
src/ui/public/notify/toasts/index.js
Normal file
2
src/ui/public/notify/toasts/index.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
import './global_toast_list';
|
||||
export { toastNotifications } from './toast_notifications';
|
56
src/ui/public/notify/toasts/toast_notifications.js
Normal file
56
src/ui/public/notify/toasts/toast_notifications.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
const normalizeToast = toastOrTitle => {
|
||||
if (typeof toastOrTitle === 'string') {
|
||||
return {
|
||||
title: toastOrTitle,
|
||||
};
|
||||
}
|
||||
|
||||
return toastOrTitle;
|
||||
};
|
||||
|
||||
export class ToastNotifications {
|
||||
constructor() {
|
||||
this.list = [];
|
||||
this.idCounter = 0;
|
||||
}
|
||||
|
||||
add = toastOrTitle => {
|
||||
const toast = {
|
||||
id: this.idCounter++,
|
||||
...normalizeToast(toastOrTitle),
|
||||
};
|
||||
this.list.push(toast);
|
||||
return toast;
|
||||
};
|
||||
|
||||
remove = toast => {
|
||||
const index = this.list.indexOf(toast);
|
||||
this.list.splice(index, 1);
|
||||
};
|
||||
|
||||
addSuccess = toastOrTitle => {
|
||||
return this.add({
|
||||
color: 'success',
|
||||
iconType: 'check',
|
||||
...normalizeToast(toastOrTitle),
|
||||
});
|
||||
};
|
||||
|
||||
addWarning = toastOrTitle => {
|
||||
return this.add({
|
||||
color: 'warning',
|
||||
iconType: 'help',
|
||||
...normalizeToast(toastOrTitle),
|
||||
});
|
||||
};
|
||||
|
||||
addDanger = toastOrTitle => {
|
||||
return this.add({
|
||||
color: 'danger',
|
||||
iconType: 'alert',
|
||||
...normalizeToast(toastOrTitle),
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export const toastNotifications = new ToastNotifications();
|
65
src/ui/public/notify/toasts/toast_notifications.test.js
Normal file
65
src/ui/public/notify/toasts/toast_notifications.test.js
Normal file
|
@ -0,0 +1,65 @@
|
|||
import {
|
||||
ToastNotifications,
|
||||
} from './toast_notifications';
|
||||
|
||||
describe('ToastNotifications', () => {
|
||||
describe('interface', () => {
|
||||
let toastNotifications;
|
||||
|
||||
beforeEach(() => {
|
||||
toastNotifications = new ToastNotifications();
|
||||
});
|
||||
|
||||
describe('add method', () => {
|
||||
test('adds a toast', () => {
|
||||
toastNotifications.add({});
|
||||
expect(toastNotifications.list.length).toBe(1);
|
||||
});
|
||||
|
||||
test('adds a toast with an ID property', () => {
|
||||
toastNotifications.add({});
|
||||
expect(toastNotifications.list[0].id).toBe(0);
|
||||
});
|
||||
|
||||
test('increments the toast ID', () => {
|
||||
toastNotifications.add({});
|
||||
toastNotifications.add({});
|
||||
expect(toastNotifications.list[1].id).toBe(1);
|
||||
});
|
||||
|
||||
test('accepts a string', () => {
|
||||
toastNotifications.add('New toast');
|
||||
expect(toastNotifications.list[0].title).toBe('New toast');
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove method', () => {
|
||||
test('removes a toast', () => {
|
||||
const toast = toastNotifications.add('Test');
|
||||
toastNotifications.remove(toast);
|
||||
expect(toastNotifications.list.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addSuccess method', () => {
|
||||
test('adds a success toast', () => {
|
||||
toastNotifications.addSuccess({});
|
||||
expect(toastNotifications.list[0].color).toBe('success');
|
||||
});
|
||||
});
|
||||
|
||||
describe('addWarning method', () => {
|
||||
test('adds a warning toast', () => {
|
||||
toastNotifications.addWarning({});
|
||||
expect(toastNotifications.list[0].color).toBe('warning');
|
||||
});
|
||||
});
|
||||
|
||||
describe('addDanger method', () => {
|
||||
test('adds a danger toast', () => {
|
||||
toastNotifications.addDanger({});
|
||||
expect(toastNotifications.list[0].color).toBe('danger');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -11,9 +11,11 @@ import {
|
|||
import { uiModules } from 'ui/modules';
|
||||
|
||||
const app = uiModules.get('app/kibana', ['react']);
|
||||
|
||||
app.directive('toolBarSearchBox', function (reactDirective) {
|
||||
return reactDirective(KuiToolBarSearchBox);
|
||||
});
|
||||
|
||||
app.directive('confirmModal', function (reactDirective) {
|
||||
return reactDirective(EuiConfirmModal);
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { includes, mapValues } from 'lodash';
|
||||
import { Notifier } from 'ui/notify/notifier';
|
||||
import { Notifier } from 'ui/notify';
|
||||
|
||||
/*
|
||||
* Caches notification attempts so each one is only actually sent to the
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import chrome from 'ui/chrome';
|
||||
import { Notifier } from 'ui/notify/notifier';
|
||||
import { Notifier } from 'ui/notify';
|
||||
|
||||
const notify = new Notifier({ location: 'Scripting Language Service' });
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
getUnhashableStatesProvider,
|
||||
unhashUrl,
|
||||
} from 'ui/state_management/state_hashing';
|
||||
import { Notifier } from 'ui/notify/notifier';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
|
||||
import { UrlShortenerProvider } from '../lib/url_shortener';
|
||||
|
||||
|
@ -145,10 +145,6 @@ app.directive('share', function (Private) {
|
|||
};
|
||||
|
||||
this.copyToClipboard = selector => {
|
||||
const notify = new Notifier({
|
||||
location: `Share ${$scope.objectType}`,
|
||||
});
|
||||
|
||||
// Select the text to be copied. If the copy fails, the user can easily copy it manually.
|
||||
const copyTextarea = $document.find(selector)[0];
|
||||
copyTextarea.select();
|
||||
|
@ -156,12 +152,21 @@ app.directive('share', function (Private) {
|
|||
try {
|
||||
const isCopied = document.execCommand('copy');
|
||||
if (isCopied) {
|
||||
notify.info('URL copied to clipboard.');
|
||||
toastNotifications.add({
|
||||
title: 'URL copied to clipboard',
|
||||
'data-test-subj': 'shareCopyToClipboardSuccess',
|
||||
});
|
||||
} else {
|
||||
notify.info('URL selected. Press Ctrl+C to copy.');
|
||||
toastNotifications.add({
|
||||
title: 'URL selected. Press Ctrl+C to copy.',
|
||||
'data-test-subj': 'shareCopyToClipboardSuccess',
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
notify.info('URL selected. Press Ctrl+C to copy.');
|
||||
toastNotifications.add({
|
||||
title: 'URL selected. Press Ctrl+C to copy.',
|
||||
'data-test-subj': 'shareCopyToClipboardSuccess',
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import expect from 'expect.js';
|
|||
import ngMock from 'ng_mock';
|
||||
import { encode as encodeRison } from 'rison-node';
|
||||
import 'ui/private';
|
||||
import { Notifier } from 'ui/notify/notifier';
|
||||
import { Notifier, fatalErrorInternals } from 'ui/notify';
|
||||
import { StateProvider } from 'ui/state_management/state';
|
||||
import {
|
||||
unhashQueryString,
|
||||
|
@ -266,18 +266,13 @@ describe('State Management', () => {
|
|||
expect(notifier._notifs[0].content).to.match(/use the share functionality/i);
|
||||
});
|
||||
|
||||
it('presents fatal error linking to github when setting item fails', () => {
|
||||
const { state, hashedItemStore, notifier } = setup({ storeInHash: true });
|
||||
const fatalStub = sinon.stub(notifier, 'fatal').throws();
|
||||
it('throws error linking to github when setting item fails', () => {
|
||||
const { state, hashedItemStore } = setup({ storeInHash: true });
|
||||
sinon.stub(fatalErrorInternals, 'show');
|
||||
sinon.stub(hashedItemStore, 'setItem').returns(false);
|
||||
|
||||
expect(() => {
|
||||
state.toQueryParam();
|
||||
}).to.throwError();
|
||||
|
||||
sinon.assert.calledOnce(fatalStub);
|
||||
expect(fatalStub.firstCall.args[0]).to.be.an(Error);
|
||||
expect(fatalStub.firstCall.args[0].message).to.match(/github\.com/);
|
||||
}).to.throwError(/github\.com/);
|
||||
});
|
||||
|
||||
it('translateHashToRison should gracefully fallback if parameter can not be parsed', () => {
|
||||
|
|
|
@ -11,7 +11,7 @@ import angular from 'angular';
|
|||
import rison from 'rison-node';
|
||||
import { applyDiff } from 'ui/utils/diff_object';
|
||||
import { EventsProvider } from 'ui/events';
|
||||
import { Notifier } from 'ui/notify/notifier';
|
||||
import { fatalError, Notifier } from 'ui/notify';
|
||||
import 'ui/state_management/config_provider';
|
||||
|
||||
import {
|
||||
|
@ -270,7 +270,7 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon
|
|||
}
|
||||
|
||||
// If we ran out of space trying to persist the state, notify the user.
|
||||
this._notifier.fatal(
|
||||
fatalError(
|
||||
new Error(
|
||||
'Kibana is unable to store history items in your session ' +
|
||||
'because it is full and there don\'t seem to be items any items safe ' +
|
||||
|
|
|
@ -3,7 +3,7 @@ import chrome from 'ui/chrome';
|
|||
|
||||
import { parse as parseUrl } from 'url';
|
||||
import sinon from 'sinon';
|
||||
import { Notifier } from 'ui/notify/notifier';
|
||||
import { Notifier } from 'ui/notify';
|
||||
|
||||
import './test_harness.less';
|
||||
import 'ng_mock';
|
||||
|
|
|
@ -8,7 +8,7 @@ import { relativeOptions } from './relative_options';
|
|||
import { parseRelativeParts } from './parse_relative_parts';
|
||||
import dateMath from '@elastic/datemath';
|
||||
import moment from 'moment';
|
||||
import { Notifier } from 'ui/notify/notifier';
|
||||
import { Notifier } from 'ui/notify';
|
||||
import 'ui/timepicker/timepicker.less';
|
||||
import 'ui/directives/input_datetime';
|
||||
import 'ui/directives/inequality';
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import 'ui/notify/directives';
|
||||
import { uiModules } from 'ui/modules';
|
||||
const typeahead = uiModules.get('kibana/typeahead');
|
||||
|
||||
|
||||
typeahead.directive('kbnTypeaheadInput', function () {
|
||||
|
||||
return {
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import listTemplate from 'ui/typeahead/partials/typeahead-items.html';
|
||||
import 'ui/notify/directives';
|
||||
import { uiModules } from 'ui/modules';
|
||||
const typeahead = uiModules.get('kibana/typeahead');
|
||||
|
||||
|
||||
typeahead.directive('kbnTypeaheadItems', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
|
|
|
@ -149,6 +149,7 @@ module.exports = function (grunt) {
|
|||
'--server.port=' + kibanaTestServerUrlParts.port,
|
||||
'--elasticsearch.url=' + esTestConfig.getUrl(),
|
||||
'--dev',
|
||||
'--dev_mode.enabled=false',
|
||||
'--no-base-path',
|
||||
'--optimize.watchPort=5611',
|
||||
'--optimize.watchPrebuild=true',
|
||||
|
|
|
@ -52,7 +52,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
|
||||
it('loads a saved dashboard', async function () {
|
||||
await PageObjects.dashboard.saveDashboard('saved with colors', { storeTimeWithDashboard: true });
|
||||
await PageObjects.header.clickToastOK();
|
||||
|
||||
const id = await PageObjects.dashboard.getDashboardIdFromCurrentUrl();
|
||||
const url = `${kibanaBaseUrl}#/dashboard/${id}`;
|
||||
|
|
|
@ -57,7 +57,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
it('should save and load dashboard', async function saveAndLoadDashboard() {
|
||||
const dashboardName = 'Dashboard Test 1';
|
||||
await PageObjects.dashboard.saveDashboard(dashboardName);
|
||||
await PageObjects.header.clickToastOK();
|
||||
await PageObjects.dashboard.gotoDashboardLandingPage();
|
||||
|
||||
await retry.try(function () {
|
||||
|
@ -247,7 +246,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
await PageObjects.visualize.clickAreaChart();
|
||||
await PageObjects.visualize.clickNewSearch();
|
||||
await PageObjects.visualize.saveVisualization('visualization from add new link');
|
||||
await PageObjects.header.clickToastOK();
|
||||
|
||||
return retry.try(async () => {
|
||||
const panelCount = await PageObjects.dashboard.getPanelCount();
|
||||
|
|
|
@ -20,7 +20,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
it('creates a new dashboard', async function () {
|
||||
await PageObjects.dashboard.clickCreateDashboardPrompt();
|
||||
await PageObjects.dashboard.saveDashboard(dashboardName);
|
||||
await PageObjects.header.clickToastOK();
|
||||
|
||||
await PageObjects.dashboard.gotoDashboardLandingPage();
|
||||
const countOfDashboards = await PageObjects.dashboard.getDashboardCountWithName(dashboardName);
|
||||
|
@ -72,7 +71,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
await PageObjects.dashboard.clearSearchValue();
|
||||
await PageObjects.dashboard.clickCreateDashboardPrompt();
|
||||
await PageObjects.dashboard.saveDashboard('Two Words');
|
||||
await PageObjects.header.clickToastOK();
|
||||
});
|
||||
|
||||
it('matches on the first word', async function () {
|
||||
|
|
|
@ -36,8 +36,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
||||
await PageObjects.visualize.saveVisualization(PIE_CHART_VIS_NAME);
|
||||
await PageObjects.header.clickToastOK();
|
||||
|
||||
await PageObjects.header.clickDashboard();
|
||||
|
||||
await dashboardExpect.pieSliceCount(2);
|
||||
|
@ -63,7 +61,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
await PageObjects.visualize.selectField('memory');
|
||||
await PageObjects.visualize.clickGo();
|
||||
await PageObjects.visualize.saveVisualization('memory with bytes < 90 pie');
|
||||
await PageObjects.header.clickToastOK();
|
||||
|
||||
await dashboardExpect.pieSliceCount(3);
|
||||
});
|
||||
|
|
|
@ -18,7 +18,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
it('warns on duplicate name for new dashboard', async function () {
|
||||
await PageObjects.dashboard.clickNewDashboard();
|
||||
await PageObjects.dashboard.saveDashboard(dashboardName);
|
||||
await PageObjects.header.clickToastOK();
|
||||
|
||||
let isConfirmOpen = await PageObjects.common.isConfirmModalOpen();
|
||||
expect(isConfirmOpen).to.equal(false);
|
||||
|
@ -44,7 +43,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
await PageObjects.dashboard.enterDashboardTitleAndClickSave(dashboardName);
|
||||
|
||||
await PageObjects.common.clickConfirmOnModal();
|
||||
await PageObjects.header.clickToastOK();
|
||||
|
||||
// This is important since saving a new dashboard will cause a refresh of the page. We have to
|
||||
// wait till it finishes reloading or it might reload the url after simulating the
|
||||
|
@ -61,7 +59,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
await PageObjects.header.isGlobalLoadingIndicatorHidden();
|
||||
await PageObjects.dashboard.clickEdit();
|
||||
await PageObjects.dashboard.saveDashboard(dashboardName);
|
||||
await PageObjects.header.clickToastOK();
|
||||
|
||||
const isConfirmOpen = await PageObjects.common.isConfirmModalOpen();
|
||||
expect(isConfirmOpen).to.equal(false);
|
||||
|
|
|
@ -32,7 +32,6 @@ export default function ({ getService, getPageObjects, updateBaselines }) {
|
|||
await PageObjects.dashboard.setTimepickerInDataRange();
|
||||
await dashboardVisualizations.createAndAddTSVBVisualization('TSVB');
|
||||
await PageObjects.dashboard.saveDashboard('tsvb');
|
||||
await PageObjects.header.clickToastOK();
|
||||
|
||||
await PageObjects.dashboard.clickFullScreenMode();
|
||||
await PageObjects.dashboard.toggleExpandPanel();
|
||||
|
@ -51,7 +50,6 @@ export default function ({ getService, getPageObjects, updateBaselines }) {
|
|||
await PageObjects.dashboard.setTimepickerInDataRange();
|
||||
await PageObjects.dashboard.addVisualizations([AREA_CHART_VIS_NAME]);
|
||||
await PageObjects.dashboard.saveDashboard('area');
|
||||
await PageObjects.header.clickToastOK();
|
||||
|
||||
await PageObjects.dashboard.clickFullScreenMode();
|
||||
await PageObjects.dashboard.toggleExpandPanel();
|
||||
|
|
|
@ -68,7 +68,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
|
||||
await PageObjects.dashboard.addVisualizations([AREA_CHART_VIS_NAME]);
|
||||
await PageObjects.dashboard.saveDashboard('Overridden colors');
|
||||
await PageObjects.header.clickToastOK();
|
||||
|
||||
await PageObjects.dashboard.clickEdit();
|
||||
await PageObjects.visualize.clickLegendOption('Count');
|
||||
|
@ -90,20 +89,17 @@ export default function ({ getService, getPageObjects }) {
|
|||
await PageObjects.discover.clickFieldListItemAdd('bytes');
|
||||
await PageObjects.discover.saveSearch('my search');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.header.clickToastOK();
|
||||
|
||||
await PageObjects.header.clickDashboard();
|
||||
await PageObjects.dashboard.clickNewDashboard();
|
||||
|
||||
await PageObjects.dashboard.addSavedSearch('my search');
|
||||
await PageObjects.dashboard.saveDashboard('No local edits');
|
||||
await PageObjects.header.clickToastOK();
|
||||
|
||||
await PageObjects.header.clickDiscover();
|
||||
await PageObjects.discover.clickFieldListItemAdd('agent');
|
||||
await PageObjects.discover.saveSearch('my search');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.header.clickToastOK();
|
||||
|
||||
await PageObjects.header.clickDashboard();
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
@ -118,13 +114,11 @@ export default function ({ getService, getPageObjects }) {
|
|||
await PageObjects.discover.removeHeaderColumn('bytes');
|
||||
await PageObjects.dashboard.clickEdit();
|
||||
await PageObjects.dashboard.saveDashboard('Has local edits');
|
||||
await PageObjects.header.clickToastOK();
|
||||
|
||||
await PageObjects.header.clickDiscover();
|
||||
await PageObjects.discover.clickFieldListItemAdd('clientip');
|
||||
await PageObjects.discover.saveSearch('my search');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.header.clickToastOK();
|
||||
|
||||
await PageObjects.header.clickDashboard();
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
@ -142,7 +136,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
|
||||
await PageObjects.dashboard.addVisualizations(['Visualization TileMap']);
|
||||
await PageObjects.dashboard.saveDashboard('No local edits');
|
||||
await PageObjects.header.clickToastOK();
|
||||
|
||||
await testSubjects.moveMouseTo('dashboardPanel');
|
||||
await PageObjects.visualize.openSpyPanel();
|
||||
|
@ -159,7 +152,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
await PageObjects.visualize.clickMapZoomIn();
|
||||
|
||||
await PageObjects.visualize.saveVisualization('Visualization TileMap');
|
||||
await PageObjects.header.clickToastOK();
|
||||
|
||||
await PageObjects.header.clickDashboard();
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@ export default function ({ getPageObjects }) {
|
|||
await PageObjects.dashboard.clickNewDashboard();
|
||||
await PageObjects.dashboard.addVisualizations([PageObjects.dashboard.getTestVisualizationNames()[0]]);
|
||||
await PageObjects.dashboard.saveDashboard(dashboardName, { storeTimeWithDashboard: false });
|
||||
await PageObjects.header.clickToastOK();
|
||||
});
|
||||
|
||||
it('Does not set the time picker on open', async function () {
|
||||
|
@ -43,7 +42,6 @@ export default function ({ getPageObjects }) {
|
|||
await PageObjects.dashboard.clickEdit();
|
||||
await PageObjects.header.setQuickTime('Today');
|
||||
await PageObjects.dashboard.saveDashboard(dashboardName, { storeTimeWithDashboard: true });
|
||||
await PageObjects.header.clickToastOK();
|
||||
});
|
||||
|
||||
it('sets quick time on open', async function () {
|
||||
|
@ -59,7 +57,6 @@ export default function ({ getPageObjects }) {
|
|||
await PageObjects.dashboard.clickEdit();
|
||||
await PageObjects.header.setAbsoluteRange(fromTime, toTime);
|
||||
await PageObjects.dashboard.saveDashboard(dashboardName, { storeTimeWithDashboard: true });
|
||||
await PageObjects.header.clickToastOK();
|
||||
});
|
||||
|
||||
it('sets absolute time on open', async function () {
|
||||
|
|
|
@ -39,7 +39,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
|
||||
it('are hidden in view mode', async function () {
|
||||
await PageObjects.dashboard.saveDashboard(dashboardName);
|
||||
await PageObjects.header.clickToastOK();
|
||||
const panelToggleMenu = await testSubjects.exists('dashboardPanelToggleMenuIcon');
|
||||
expect(panelToggleMenu).to.equal(false);
|
||||
});
|
||||
|
@ -59,7 +58,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
|
||||
// Based off an actual bug encountered in a PR where a hard refresh in edit mode did not show the edit mode
|
||||
// controls.
|
||||
it ('are shown in edit mode after a hard refresh', async () => {
|
||||
it('are shown in edit mode after a hard refresh', async () => {
|
||||
const currentUrl = await remote.getCurrentUrl();
|
||||
// the second parameter of true will include the timestamp in the url and trigger a hard refresh.
|
||||
await remote.get(currentUrl.toString(), true);
|
||||
|
@ -79,7 +78,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
describe('on an expanded panel', function () {
|
||||
it('are hidden in view mode', async function () {
|
||||
await PageObjects.dashboard.saveDashboard(dashboardName);
|
||||
await PageObjects.header.clickToastOK();
|
||||
await PageObjects.dashboard.toggleExpandPanel();
|
||||
|
||||
const panelToggleMenu = await testSubjects.exists('dashboardPanelToggleMenuIcon');
|
||||
|
@ -127,7 +125,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
await PageObjects.discover.clickFieldListItemAdd('bytes');
|
||||
await PageObjects.discover.saveSearch('my search');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.header.clickToastOK();
|
||||
await PageObjects.header.clickDashboard();
|
||||
await PageObjects.dashboard.addSavedSearch('my search');
|
||||
|
||||
|
|
|
@ -30,7 +30,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
await PageObjects.dashboard.clickNewDashboard();
|
||||
await PageObjects.dashboard.addVisualizations(PageObjects.dashboard.getTestVisualizationNames());
|
||||
await PageObjects.dashboard.saveDashboard(dashboardName);
|
||||
await PageObjects.header.clickToastOK();
|
||||
});
|
||||
|
||||
it('existing dashboard opens in view mode', async function () {
|
||||
|
@ -45,7 +44,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
it('auto exits out of edit mode', async function () {
|
||||
await PageObjects.dashboard.gotoDashboardEditMode(dashboardName);
|
||||
await PageObjects.dashboard.saveDashboard(dashboardName);
|
||||
await PageObjects.header.clickToastOK();
|
||||
const isViewMode = await PageObjects.dashboard.getIsInViewMode();
|
||||
expect(isViewMode).to.equal(true);
|
||||
});
|
||||
|
@ -58,12 +56,11 @@ export default function ({ getService, getPageObjects }) {
|
|||
});
|
||||
|
||||
|
||||
it('when time changed is stored with dashboard', async function () {
|
||||
it.skip('when time changed is stored with dashboard', async function () {
|
||||
const originalFromTime = '2015-09-19 06:31:44.000';
|
||||
const originalToTime = '2015-09-19 06:31:44.000';
|
||||
await PageObjects.header.setAbsoluteRange(originalFromTime, originalToTime);
|
||||
await PageObjects.dashboard.saveDashboard(dashboardName, { storeTimeWithDashboard: true });
|
||||
await PageObjects.header.clickToastOK();
|
||||
|
||||
await PageObjects.dashboard.clickEdit();
|
||||
await PageObjects.header.setAbsoluteRange('2013-09-19 06:31:44.000', '2013-09-19 06:31:44.000');
|
||||
|
@ -97,7 +94,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
await PageObjects.dashboard.setTimepickerInDataRange();
|
||||
await PageObjects.dashboard.filterOnPieSlice();
|
||||
await PageObjects.dashboard.saveDashboard(dashboardName);
|
||||
await PageObjects.header.clickToastOK();
|
||||
|
||||
// This may seem like a pointless line but there was a bug that only arose when the dashboard
|
||||
// was loaded initially
|
||||
|
@ -133,7 +129,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
await PageObjects.visualize.clickAreaChart();
|
||||
await PageObjects.visualize.clickNewSearch();
|
||||
await PageObjects.visualize.saveVisualization('new viz panel');
|
||||
await PageObjects.header.clickToastOK();
|
||||
|
||||
await PageObjects.dashboard.clickCancelOutOfEditMode();
|
||||
|
||||
|
@ -159,20 +154,18 @@ export default function ({ getService, getPageObjects }) {
|
|||
});
|
||||
|
||||
describe('and preserves edits on cancel', function () {
|
||||
it('when time changed is stored with dashboard', async function () {
|
||||
it.skip('when time changed is stored with dashboard', async function () {
|
||||
await PageObjects.dashboard.gotoDashboardEditMode(dashboardName);
|
||||
const newFromTime = '2015-09-19 06:31:44.000';
|
||||
const newToTime = '2015-09-19 06:31:44.000';
|
||||
await PageObjects.header.setAbsoluteRange('2013-09-19 06:31:44.000', '2013-09-19 06:31:44.000');
|
||||
await PageObjects.dashboard.saveDashboard(dashboardName, true);
|
||||
await PageObjects.header.clickToastOK();
|
||||
await PageObjects.dashboard.clickEdit();
|
||||
await PageObjects.header.setAbsoluteRange(newToTime, newToTime);
|
||||
await PageObjects.dashboard.clickCancelOutOfEditMode();
|
||||
|
||||
await PageObjects.common.clickCancelOnModal();
|
||||
await PageObjects.dashboard.saveDashboard(dashboardName, { storeTimeWithDashboard: true });
|
||||
await PageObjects.header.clickToastOK();
|
||||
|
||||
await PageObjects.dashboard.loadSavedDashboard(dashboardName);
|
||||
|
||||
|
@ -192,14 +185,12 @@ export default function ({ getService, getPageObjects }) {
|
|||
const newToTime = '2015-09-19 06:31:44.000';
|
||||
await PageObjects.header.setAbsoluteRange('2013-09-19 06:31:44.000', '2013-09-19 06:31:44.000');
|
||||
await PageObjects.dashboard.saveDashboard(dashboardName, true);
|
||||
await PageObjects.header.clickToastOK();
|
||||
await PageObjects.dashboard.clickEdit();
|
||||
await PageObjects.header.setAbsoluteRange(newToTime, newToTime);
|
||||
await PageObjects.dashboard.clickCancelOutOfEditMode();
|
||||
|
||||
await PageObjects.common.clickCancelOnModal();
|
||||
await PageObjects.dashboard.saveDashboard(dashboardName, { storeTimeWithDashboard: true });
|
||||
await PageObjects.header.clickToastOK();
|
||||
|
||||
await PageObjects.dashboard.loadSavedDashboard(dashboardName);
|
||||
|
||||
|
@ -215,7 +206,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
it('when time changed is not stored with dashboard', async function () {
|
||||
await PageObjects.dashboard.gotoDashboardEditMode(dashboardName);
|
||||
await PageObjects.dashboard.saveDashboard(dashboardName, { storeTimeWithDashboard: false });
|
||||
await PageObjects.header.clickToastOK();
|
||||
await PageObjects.dashboard.clickEdit();
|
||||
await PageObjects.header.setAbsoluteRange('2013-10-19 06:31:44.000', '2013-12-19 06:31:44.000');
|
||||
await PageObjects.dashboard.clickCancelOutOfEditMode();
|
||||
|
@ -229,7 +219,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
await PageObjects.dashboard.setTimepickerInDataRange();
|
||||
await PageObjects.dashboard.filterOnPieSlice();
|
||||
await PageObjects.dashboard.saveDashboard(dashboardName);
|
||||
await PageObjects.header.clickToastOK();
|
||||
await PageObjects.dashboard.clickEdit();
|
||||
await PageObjects.dashboard.clickCancelOutOfEditMode();
|
||||
|
||||
|
|
|
@ -47,14 +47,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
|
||||
it('save query should show toast message and display query name', async function () {
|
||||
await PageObjects.discover.saveSearch(queryName1);
|
||||
const toastMessage = await PageObjects.header.getToastMessage();
|
||||
|
||||
const expectedToastMessage = `Discover: Saved Data Source "${queryName1}"`;
|
||||
expect(toastMessage).to.be(expectedToastMessage);
|
||||
|
||||
await PageObjects.header.waitForToastMessageGone();
|
||||
const actualQueryNameString = await PageObjects.discover.getCurrentQueryName();
|
||||
|
||||
expect(actualQueryNameString).to.be(queryName1);
|
||||
});
|
||||
|
||||
|
|
|
@ -9,11 +9,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
|
||||
describe('shared links', function describeIndexTests() {
|
||||
let baseUrl;
|
||||
// The message changes for Firefox < 41 and Firefox >= 41
|
||||
// var expectedToastMessage = 'Share search: URL selected. Press Ctrl+C to copy.';
|
||||
// var expectedToastMessage = 'Share search: URL copied to clipboard.';
|
||||
// Pass either one.
|
||||
const expectedToastMessage = /Share search: URL (selected\. Press Ctrl\+C to copy\.|copied to clipboard\.)/;
|
||||
|
||||
before(function () {
|
||||
baseUrl = PageObjects.common.getHostPort();
|
||||
|
@ -83,17 +78,8 @@ export default function ({ getService, getPageObjects }) {
|
|||
});
|
||||
});
|
||||
|
||||
it('should show toast message for copy to clipboard', function () {
|
||||
return PageObjects.discover.clickCopyToClipboard()
|
||||
.then(function () {
|
||||
return PageObjects.header.getToastMessage();
|
||||
})
|
||||
.then(function (toastMessage) {
|
||||
expect(toastMessage).to.match(expectedToastMessage);
|
||||
})
|
||||
.then(function () {
|
||||
return PageObjects.header.waitForToastMessageGone();
|
||||
});
|
||||
it('gets copied to clipboard', async function () {
|
||||
return await PageObjects.discover.clickCopyToClipboard();
|
||||
});
|
||||
|
||||
// TODO: verify clipboard contents
|
||||
|
@ -111,17 +97,8 @@ export default function ({ getService, getPageObjects }) {
|
|||
});
|
||||
|
||||
// NOTE: This test has to run immediately after the test above
|
||||
it('should show toast message for copy to clipboard of short URL', function () {
|
||||
return PageObjects.discover.clickCopyToClipboard()
|
||||
.then(function () {
|
||||
return PageObjects.header.getToastMessage();
|
||||
})
|
||||
.then(function (toastMessage) {
|
||||
expect(toastMessage).to.match(expectedToastMessage);
|
||||
})
|
||||
.then(function () {
|
||||
return PageObjects.header.waitForToastMessageGone();
|
||||
});
|
||||
it('copies short URL to clipboard', async function () {
|
||||
return await PageObjects.discover.clickCopyToClipboard();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -62,9 +62,12 @@ export default function ({ getService, getPageObjects }) {
|
|||
it('should save and load with special characters', function () {
|
||||
const vizNamewithSpecialChars = vizName1 + '/?&=%';
|
||||
return PageObjects.visualize.saveVisualization(vizNamewithSpecialChars)
|
||||
.then(function (message) {
|
||||
log.debug(`Saved viz message = ${message}`);
|
||||
expect(message).to.be(`Visualization Editor: Saved Visualization "${vizNamewithSpecialChars}"`);
|
||||
.then(() => {
|
||||
return PageObjects.common.getBreadcrumbPageTitle();
|
||||
})
|
||||
.then(pageTitle => {
|
||||
log.debug(`Save viz page title is ${pageTitle}`);
|
||||
expect(pageTitle).to.contain(vizNamewithSpecialChars);
|
||||
})
|
||||
.then(function testVisualizeWaitForToastMessageGone() {
|
||||
return PageObjects.header.waitForToastMessageGone();
|
||||
|
@ -73,19 +76,22 @@ export default function ({ getService, getPageObjects }) {
|
|||
|
||||
it('should save and load with non-ascii characters', async function () {
|
||||
const vizNamewithSpecialChars = `${vizName1} with Umlaut ä`;
|
||||
const message = await PageObjects.visualize.saveVisualization(vizNamewithSpecialChars);
|
||||
const pageTitle = await PageObjects.visualize.saveVisualization(vizNamewithSpecialChars).then(() => {
|
||||
return PageObjects.common.getBreadcrumbPageTitle();
|
||||
});
|
||||
|
||||
log.debug(`Saved viz message with umlaut = ${message}`);
|
||||
expect(message).to.be(`Visualization Editor: Saved Visualization "${vizNamewithSpecialChars}"`);
|
||||
|
||||
await PageObjects.header.waitForToastMessageGone();
|
||||
log.debug(`Saved viz page title with umlaut is ${pageTitle}`);
|
||||
expect(pageTitle).to.contain(vizNamewithSpecialChars);
|
||||
});
|
||||
|
||||
it('should save and load', function () {
|
||||
return PageObjects.visualize.saveVisualization(vizName1)
|
||||
.then(function (message) {
|
||||
log.debug('Saved viz message = ' + message);
|
||||
expect(message).to.be('Visualization Editor: Saved Visualization \"' + vizName1 + '\"');
|
||||
.then(() => {
|
||||
return PageObjects.common.getBreadcrumbPageTitle();
|
||||
})
|
||||
.then(pageTitle => {
|
||||
log.debug(`Saved viz page title is ${pageTitle}`);
|
||||
expect(pageTitle).to.contain(vizName1);
|
||||
})
|
||||
.then(function testVisualizeWaitForToastMessageGone() {
|
||||
return PageObjects.header.waitForToastMessageGone();
|
||||
|
@ -96,9 +102,9 @@ export default function ({ getService, getPageObjects }) {
|
|||
.then(function () {
|
||||
return PageObjects.visualize.waitForVisualization();
|
||||
})
|
||||
// We have to sleep sometime between loading the saved visTitle
|
||||
// and trying to access the chart below with getXAxisLabels
|
||||
// otherwise it hangs.
|
||||
// We have to sleep sometime between loading the saved visTitle
|
||||
// and trying to access the chart below with getXAxisLabels
|
||||
// otherwise it hangs.
|
||||
.then(function sleep() {
|
||||
return PageObjects.common.sleep(2000);
|
||||
});
|
||||
|
|
|
@ -53,9 +53,12 @@ export default function ({ getService, getPageObjects }) {
|
|||
|
||||
it('should be able to save and load', function () {
|
||||
return PageObjects.visualize.saveVisualization(vizName1)
|
||||
.then(function (message) {
|
||||
log.debug('Saved viz message = ' + message);
|
||||
expect(message).to.be('Visualization Editor: Saved Visualization \"' + vizName1 + '\"');
|
||||
.then(() => {
|
||||
return PageObjects.common.getBreadcrumbPageTitle();
|
||||
})
|
||||
.then(pageTitle => {
|
||||
log.debug(`Save viz page title is ${pageTitle}`);
|
||||
expect(pageTitle).to.contain(vizName1);
|
||||
})
|
||||
.then(function testVisualizeWaitForToastMessageGone() {
|
||||
return PageObjects.header.waitForToastMessageGone();
|
||||
|
|
|
@ -52,9 +52,12 @@ export default function ({ getService, getPageObjects }) {
|
|||
|
||||
it('should save and load', function () {
|
||||
return PageObjects.visualize.saveVisualization(vizName1)
|
||||
.then(function (message) {
|
||||
log.debug('Saved viz message = ' + message);
|
||||
expect(message).to.be('Visualization Editor: Saved Visualization \"' + vizName1 + '\"');
|
||||
.then(() => {
|
||||
return PageObjects.common.getBreadcrumbPageTitle();
|
||||
})
|
||||
.then(pageTitle => {
|
||||
log.debug(`Save viz page title is ${pageTitle}`);
|
||||
expect(pageTitle).to.contain(vizName1);
|
||||
})
|
||||
.then(function testVisualizeWaitForToastMessageGone() {
|
||||
return PageObjects.header.waitForToastMessageGone();
|
||||
|
|
|
@ -109,7 +109,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
});
|
||||
});
|
||||
|
||||
|
||||
it('should show correct data, ordered by Term', function () {
|
||||
|
||||
const expectedChartData = ['png', '1,373', 'php', '445', 'jpg', '9,109', 'gif', '918', 'css', '2,159'];
|
||||
|
@ -124,13 +123,14 @@ export default function ({ getService, getPageObjects }) {
|
|||
});
|
||||
});
|
||||
|
||||
|
||||
it('should be able to save and load', function () {
|
||||
|
||||
return PageObjects.visualize.saveVisualization(vizName1)
|
||||
.then(function (message) {
|
||||
log.debug('Saved viz message = ' + message);
|
||||
expect(message).to.be('Visualization Editor: Saved Visualization \"' + vizName1 + '\"');
|
||||
.then(() => {
|
||||
return PageObjects.common.getBreadcrumbPageTitle();
|
||||
})
|
||||
.then(pageTitle => {
|
||||
log.debug(`Save viz page title is ${pageTitle}`);
|
||||
expect(pageTitle).to.contain(vizName1);
|
||||
})
|
||||
.then(function testVisualizeWaitForToastMessageGone() {
|
||||
return PageObjects.header.waitForToastMessageGone();
|
||||
|
@ -142,9 +142,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
return PageObjects.visualize.waitForVisualization();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -59,9 +59,12 @@ export default function ({ getService, getPageObjects }) {
|
|||
|
||||
it('should save and load', function () {
|
||||
return PageObjects.visualize.saveVisualization(vizName1)
|
||||
.then(function (message) {
|
||||
log.debug('Saved viz message = ' + message);
|
||||
expect(message).to.be('Visualization Editor: Saved Visualization \"' + vizName1 + '\"');
|
||||
.then(() => {
|
||||
return PageObjects.common.getBreadcrumbPageTitle();
|
||||
})
|
||||
.then(pageTitle => {
|
||||
log.debug(`Save viz page title is ${pageTitle}`);
|
||||
expect(pageTitle).to.contain(vizName1);
|
||||
})
|
||||
.then(function testVisualizeWaitForToastMessageGone() {
|
||||
return PageObjects.header.waitForToastMessageGone();
|
||||
|
|
|
@ -100,9 +100,12 @@ export default function ({ getService, getPageObjects }) {
|
|||
|
||||
it('should save and load', function () {
|
||||
return PageObjects.visualize.saveVisualization(vizName1)
|
||||
.then(function (message) {
|
||||
log.debug('Saved viz message = ' + message);
|
||||
expect(message).to.be('Visualization Editor: Saved Visualization \"' + vizName1 + '\"');
|
||||
.then(() => {
|
||||
return PageObjects.common.getBreadcrumbPageTitle();
|
||||
})
|
||||
.then(pageTitle => {
|
||||
log.debug(`Save viz page title is ${pageTitle}`);
|
||||
expect(pageTitle).to.contain(vizName1);
|
||||
})
|
||||
.then(function testVisualizeWaitForToastMessageGone() {
|
||||
return PageObjects.header.waitForToastMessageGone();
|
||||
|
|
|
@ -160,7 +160,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
|
||||
await PageObjects.visualize.closeSpyPanel();
|
||||
await PageObjects.visualize.saveVisualization(vizName1);
|
||||
await PageObjects.header.waitForToastMessageGone();
|
||||
|
||||
const afterSaveMapBounds = await PageObjects.visualize.getMapBounds();
|
||||
|
||||
|
|
|
@ -90,13 +90,13 @@ export default function ({ getService, getPageObjects }) {
|
|||
expect(text).to.be('1442901600000');
|
||||
});
|
||||
|
||||
it('should allow printing raw value of data', async () => {
|
||||
it.skip('should allow printing raw value of data', async () => {
|
||||
await PageObjects.visualBuilder.enterMarkdown('{{ count.data.raw.[0].[1] }}');
|
||||
const text = await PageObjects.visualBuilder.getMarkdownText();
|
||||
expect(text).to.be('6');
|
||||
});
|
||||
|
||||
describe('allow time offsets', () => {
|
||||
describe.skip('allow time offsets', () => {
|
||||
before(async () => {
|
||||
await PageObjects.visualBuilder.enterMarkdown('{{ count.data.raw.[0].[0] }}#{{ count.data.raw.[0].[1] }}');
|
||||
await PageObjects.visualBuilder.clickMarkdownData();
|
||||
|
|
|
@ -52,9 +52,12 @@ export default function ({ getService, getPageObjects }) {
|
|||
|
||||
it('should save and load', function () {
|
||||
return PageObjects.visualize.saveVisualization(vizName1)
|
||||
.then(function (message) {
|
||||
log.debug('Saved viz message = ' + message);
|
||||
expect(message).to.be('Visualization Editor: Saved Visualization \"' + vizName1 + '\"');
|
||||
.then(() => {
|
||||
return PageObjects.common.getBreadcrumbPageTitle();
|
||||
})
|
||||
.then(pageTitle => {
|
||||
log.debug(`Save viz page title is ${pageTitle}`);
|
||||
expect(pageTitle).to.contain(vizName1);
|
||||
})
|
||||
.then(function testVisualizeWaitForToastMessageGone() {
|
||||
return PageObjects.header.waitForToastMessageGone();
|
||||
|
|
|
@ -246,6 +246,10 @@ export function CommonPageProvider({ getService, getPageObjects }) {
|
|||
return await testSubjects.exists('confirmModalCancelButton', 2000);
|
||||
}
|
||||
|
||||
async getBreadcrumbPageTitle() {
|
||||
return await testSubjects.getVisibleText('breadcrumbPageTitle');
|
||||
}
|
||||
|
||||
async doesCssSelectorExist(selector) {
|
||||
log.debug(`doesCssSelectorExist ${selector}`);
|
||||
|
||||
|
|
|
@ -290,7 +290,7 @@ export function DashboardPageProvider({ getService, getPageObjects }) {
|
|||
await this.filterEmbeddableNames(searchName);
|
||||
|
||||
await find.clickByPartialLinkText(searchName);
|
||||
await PageObjects.header.clickToastOK();
|
||||
await testSubjects.exists('addSavedSearchToDashboardSuccess');
|
||||
await this.clickAddVisualization();
|
||||
}
|
||||
|
||||
|
@ -299,7 +299,6 @@ export function DashboardPageProvider({ getService, getPageObjects }) {
|
|||
log.debug('filter visualization (' + vizName + ')');
|
||||
await this.filterEmbeddableNames(vizName);
|
||||
await this.clickVizNameLink(vizName);
|
||||
await PageObjects.header.clickToastOK();
|
||||
// this second click of 'Add' collapses the Add Visualization pane
|
||||
await this.clickAddVisualization();
|
||||
}
|
||||
|
@ -324,9 +323,8 @@ export function DashboardPageProvider({ getService, getPageObjects }) {
|
|||
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
||||
// verify that green message at the top of the page.
|
||||
// it's only there for about 5 seconds
|
||||
return await PageObjects.header.getToastMessage();
|
||||
// Confirm that the Dashboard has been saved.
|
||||
return await testSubjects.exists('saveDashboardSuccess');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -42,6 +42,9 @@ export function DiscoverPageProvider({ getService, getPageObjects }) {
|
|||
.then(() => {
|
||||
log.debug('--find save button');
|
||||
return testSubjects.click('discoverSaveSearchButton');
|
||||
})
|
||||
.then(async () => {
|
||||
return await testSubjects.exists('saveSearchSuccess', 2000);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -197,8 +200,11 @@ export function DiscoverPageProvider({ getService, getPageObjects }) {
|
|||
return testSubjects.click('sharedSnapshotShortUrlButton');
|
||||
}
|
||||
|
||||
clickCopyToClipboard() {
|
||||
return testSubjects.click('sharedSnapshotCopyButton');
|
||||
async clickCopyToClipboard() {
|
||||
testSubjects.click('sharedSnapshotCopyButton');
|
||||
|
||||
// Confirm that the content was copied to the clipboard.
|
||||
return await testSubjects.exists('shareCopyToClipboardSuccess');
|
||||
}
|
||||
|
||||
async getShareCaption() {
|
||||
|
|
|
@ -461,8 +461,7 @@ export function VisualizePageProvider({ getService, getPageObjects }) {
|
|||
log.debug('click submit button');
|
||||
await testSubjects.click('saveVisualizationButton');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
||||
return await PageObjects.header.getToastMessage();
|
||||
return await testSubjects.exists('saveVisualizationSuccess');
|
||||
}
|
||||
|
||||
async clickLoadSavedVisButton() {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
export function DashboardVisualizationProvider({ getService, getPageObjects }) {
|
||||
const log = getService('log');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const PageObjects = getPageObjects(['dashboard', 'visualize', 'header', 'discover']);
|
||||
|
||||
return new class DashboardVisualizations {
|
||||
|
@ -14,7 +15,6 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }) {
|
|||
await PageObjects.dashboard.clickAddNewVisualizationLink();
|
||||
await PageObjects.visualize.clickVisualBuilder();
|
||||
await PageObjects.visualize.saveVisualization(name);
|
||||
await PageObjects.header.clickToastOK();
|
||||
}
|
||||
|
||||
async createSavedSearch({ name, query, fields }) {
|
||||
|
@ -36,7 +36,7 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }) {
|
|||
|
||||
await PageObjects.discover.saveSearch(name);
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.header.clickToastOK();
|
||||
await testSubjects.exists('saveSearchSuccess');
|
||||
}
|
||||
|
||||
async createAndAddSavedSearch({ name, query, fields }) {
|
||||
|
|
|
@ -13,9 +13,9 @@ export function TestSubjectsProvider({ getService }) {
|
|||
const defaultFindTimeout = config.get('timeouts.find');
|
||||
|
||||
class TestSubjects {
|
||||
async exists(selector) {
|
||||
async exists(selector, timeout = defaultFindTimeout) {
|
||||
log.debug(`TestSubjects.exists(${selector})`);
|
||||
return await find.existsByDisplayedByCssSelector(testSubjSelector(selector));
|
||||
return await find.existsByDisplayedByCssSelector(testSubjSelector(selector), timeout);
|
||||
}
|
||||
|
||||
async append(selector, text) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue