diff --git a/package.json b/package.json index d5214a2471481..eed07423f7263 100644 --- a/package.json +++ b/package.json @@ -266,7 +266,7 @@ "angular-mocks": "1.4.7", "babel-eslint": "8.1.2", "babel-jest": "^22.4.3", - "backport": "3.0.3", + "backport": "4.2.0", "chai": "3.5.0", "chance": "1.0.10", "cheerio": "0.22.0", diff --git a/src/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap b/src/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap index ea1f1ec62bf1f..0d01cb7ebd034 100644 --- a/src/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap +++ b/src/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap @@ -6,12 +6,12 @@ exports[`after fetch hideWriteControls 1`] = ` data-test-subj="dashboardLandingPage" restrictWidth={false} > - + - +
- +
- +
- +
- + `; diff --git a/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js b/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js index f7b9265960e75..e1a077c995c99 100644 --- a/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js +++ b/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js @@ -454,7 +454,7 @@ export class DashboardListing extends React.Component { } return ( - + {this.renderListingOrEmptyState()} ); @@ -463,7 +463,7 @@ export class DashboardListing extends React.Component { render() { return ( - + {this.renderPageContent()} diff --git a/src/core_plugins/kibana/public/dashboard/styles/index.less b/src/core_plugins/kibana/public/dashboard/styles/index.less index d9ca73adbe498..ea7b5b3c3bf88 100644 --- a/src/core_plugins/kibana/public/dashboard/styles/index.less +++ b/src/core_plugins/kibana/public/dashboard/styles/index.less @@ -438,8 +438,3 @@ dashboard-viewport-provider { min-height: 100vh; background: @globalColorLightestGray; } - -.dashboardLandingPage__content { - max-width: 1000px; - margin: auto; -} diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/components/header/__tests__/__snapshots__/header.test.js.snap b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/components/header/__tests__/__snapshots__/header.test.js.snap index eaf401d8a7426..3572d2d788dc3 100644 --- a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/components/header/__tests__/__snapshots__/header.test.js.snap +++ b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/components/header/__tests__/__snapshots__/header.test.js.snap @@ -44,6 +44,7 @@ exports[`Header should render normally 1`] = ` > @@ -99,6 +100,7 @@ exports[`Header should render without including system indices 1`] = ` > diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/components/header/header.js b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/components/header/header.js index 7264c606ea158..67e455e01f82c 100644 --- a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/components/header/header.js +++ b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/components/header/header.js @@ -50,6 +50,7 @@ export const Header = ({ row[0] === event); this._vis.API.events.addFilter(this._chartData.tables[0], 0, rowIndex, event); }); + this._choroplethLayer.on('styleChanged', (event) => { const shouldShowWarning = this._vis.params.isDisplayWarning && config.get('visualization:regionmap:showWarnings'); if (event.mismatches.length > 0 && shouldShowWarning) { - this._notify.warning(`Could not show ${event.mismatches.length} ${event.mismatches.length > 1 ? 'results' : 'result'} on the map.` - + ` To avoid this, ensure that each term can be matched to a corresponding shape on that shape's join field.` - + ` Could not match following terms: ${event.mismatches.join(',')}` - ); + toastNotifications.addWarning({ + title: `Unable to show ${event.mismatches.length} ${event.mismatches.length > 1 ? 'results' : 'result'} on map`, + text: `Ensure that each of these term matches a shape on that shape's join field: ${event.mismatches.join(', ')}`, + }); } }); diff --git a/src/core_plugins/tile_map/public/base_maps_visualization.js b/src/core_plugins/tile_map/public/base_maps_visualization.js index 6c6dbcdf424da..aafb19d9448db 100644 --- a/src/core_plugins/tile_map/public/base_maps_visualization.js +++ b/src/core_plugins/tile_map/public/base_maps_visualization.js @@ -22,7 +22,7 @@ import { KibanaMap } from 'ui/vis/map/kibana_map'; import * as Rx from 'rxjs'; import { filter, first } from 'rxjs/operators'; import 'ui/vis/map/service_settings'; - +import { toastNotifications } from 'ui/notify'; const MINZOOM = 0; const MAXZOOM = 22;//increase this to 22. Better for WMS @@ -142,7 +142,7 @@ export function BaseMapsVisualizationProvider(serviceSettings) { this._setTmsLayer(firstRoadMapLayer); } } catch (e) { - this._notify.warning(e.message); + toastNotifications.addWarning(e.message); return; } return; @@ -174,7 +174,7 @@ export function BaseMapsVisualizationProvider(serviceSettings) { } } catch (tmsLoadingError) { - this._notify.warning(tmsLoadingError.message); + toastNotifications.addWarning(tmsLoadingError.message); } diff --git a/src/core_plugins/vega/public/vega_visualization.js b/src/core_plugins/vega/public/vega_visualization.js index 83eaf6574ca0a..c51ebcbb2042e 100644 --- a/src/core_plugins/vega/public/vega_visualization.js +++ b/src/core_plugins/vega/public/vega_visualization.js @@ -17,7 +17,7 @@ * under the License. */ -import { Notifier } from 'ui/notify'; +import { toastNotifications, Notifier } from 'ui/notify'; import { VegaView } from './vega_view/vega_view'; import { VegaMapView } from './vega_view/vega_map_view'; import { SavedObjectsClientProvider, findObjectByTitle } from 'ui/saved_objects'; @@ -59,7 +59,7 @@ export function VegaVisualizationProvider(Private, vegaConfig, serviceSettings, */ async render(visData, status) { if (!visData && !this._vegaView) { - notify.warning('Unable to render without data'); + toastNotifications.addWarning('Unable to render without data'); return; } diff --git a/src/server/sample_data/data_sets/flights/index.js b/src/server/sample_data/data_sets/flights/index.js index 2439e7b4a6507..dd2e477ccc98f 100644 --- a/src/server/sample_data/data_sets/flights/index.js +++ b/src/server/sample_data/data_sets/flights/index.js @@ -113,7 +113,7 @@ export function flightsSpecProvider() { } }, timeFields: ['timestamp'], - currentTimeMarker: '2018-01-02T00:00:00', + currentTimeMarker: '2018-01-09T00:00:00', preserveDayOfWeekTimeOfDay: true, savedObjects: savedObjects, }; diff --git a/src/server/sample_data/routes/install.js b/src/server/sample_data/routes/install.js index 8e096992a1982..0c00bcc739063 100644 --- a/src/server/sample_data/routes/install.js +++ b/src/server/sample_data/routes/install.js @@ -21,7 +21,11 @@ import Joi from 'joi'; import { loadData } from './lib/load_data'; import { createIndexName } from './lib/create_index_name'; -import { adjustTimestamp } from './lib/adjust_timestamp'; +import { + dateToIso8601IgnoringTime, + translateTimeRelativeToDifference, + translateTimeRelativeToWeek +} from './lib/translate_timestamp'; export const createInstallRoute = () => ({ path: '/api/sample_data/{id}', @@ -80,12 +84,13 @@ export const createInstallRoute = () => ({ return reply(errMsg).code(err.status); } - const now = new Date(); - const currentTimeMarker = new Date(Date.parse(sampleDataset.currentTimeMarker)); + const nowReference = dateToIso8601IgnoringTime(new Date()); function updateTimestamps(doc) { sampleDataset.timeFields.forEach(timeFieldName => { if (doc[timeFieldName]) { - doc[timeFieldName] = adjustTimestamp(doc[timeFieldName], currentTimeMarker, now, sampleDataset.preserveDayOfWeekTimeOfDay); + doc[timeFieldName] = sampleDataset.preserveDayOfWeekTimeOfDay + ? translateTimeRelativeToWeek(doc[timeFieldName], sampleDataset.currentTimeMarker, nowReference) + : translateTimeRelativeToDifference(doc[timeFieldName], sampleDataset.currentTimeMarker, nowReference); } }); return doc; diff --git a/src/server/sample_data/routes/lib/adjust_timestamp.js b/src/server/sample_data/routes/lib/adjust_timestamp.js deleted file mode 100644 index 749b181cbe544..0000000000000 --- a/src/server/sample_data/routes/lib/adjust_timestamp.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - - -const MILLISECONDS_IN_DAY = 86400000; - -/** - * Convert timestamp to timestamp that is relative to now - * - * @param {String} timestamp ISO8601 formated data string YYYY-MM-dd'T'HH:mm:ss.SSS - * @param {Date} currentTimeMarker "now" reference marker in sample dataset - * @param {Date} now - * @param {Boolean} preserveDayOfWeekTimeOfDay - * @return {String} ISO8601 formated data string YYYY-MM-dd'T'HH:mm:ss.SSS of timestamp adjusted to now - */ -export function adjustTimestamp(timestamp, currentTimeMarker, now, preserveDayOfWeekTimeOfDay) { - const timestampDate = new Date(Date.parse(timestamp)); - - if (!preserveDayOfWeekTimeOfDay) { - // Move timestamp relative to now, preserving distance between currentTimeMarker and timestamp - const timeDelta = timestampDate.getTime() - currentTimeMarker.getTime(); - return (new Date(now.getTime() + timeDelta)).toISOString(); - } - - // Move timestamp to current week, preserving day of week and time of day - const weekDelta = Math.round((timestampDate.getTime() - currentTimeMarker.getTime()) / (MILLISECONDS_IN_DAY * 7)); - const dayOfWeekDelta = timestampDate.getDay() - now.getDay(); - const daysDelta = dayOfWeekDelta * MILLISECONDS_IN_DAY + (weekDelta * MILLISECONDS_IN_DAY * 7); - const yearMonthDay = (new Date(now.getTime() + daysDelta)).toISOString().substring(0, 10); - return `${yearMonthDay}T${timestamp.substring(11)}`; - -} diff --git a/src/server/sample_data/routes/lib/adjust_timestamp.test.js b/src/server/sample_data/routes/lib/adjust_timestamp.test.js deleted file mode 100644 index ee74537058ece..0000000000000 --- a/src/server/sample_data/routes/lib/adjust_timestamp.test.js +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - - -import { adjustTimestamp } from './adjust_timestamp'; - -const currentTimeMarker = new Date(Date.parse('2018-01-02T00:00:00Z')); -const now = new Date(Date.parse('2018-04-25T18:24:58.650Z')); // Wednesday - -describe('relative to now', () => { - test('adjusts time to 10 minutes in past from now', () => { - const originalTimestamp = '2018-01-01T23:50:00Z'; // -10 minutes relative to currentTimeMarker - const timestamp = adjustTimestamp(originalTimestamp, currentTimeMarker, now, false); - expect(timestamp).toBe('2018-04-25T18:14:58.650Z'); - }); - - test('adjusts time to 1 hour in future from now', () => { - const originalTimestamp = '2018-01-02T01:00:00Z'; // + 1 hour relative to currentTimeMarker - const timestamp = adjustTimestamp(originalTimestamp, currentTimeMarker, now, false); - expect(timestamp).toBe('2018-04-25T19:24:58.650Z'); - }); -}); - -describe('preserve day of week and time of day', () => { - test('adjusts time to monday of the same week as now', () => { - const originalTimestamp = '2018-01-01T23:50:00Z'; // Monday, same week relative to currentTimeMarker - const timestamp = adjustTimestamp(originalTimestamp, currentTimeMarker, now, true); - expect(timestamp).toBe('2018-04-23T23:50:00Z'); - }); - - test('adjusts time to friday of the same week as now', () => { - const originalTimestamp = '2017-12-29T23:50:00Z'; // Friday, same week relative to currentTimeMarker - const timestamp = adjustTimestamp(originalTimestamp, currentTimeMarker, now, true); - expect(timestamp).toBe('2018-04-27T23:50:00Z'); - }); - - test('adjusts time to monday of the previous week as now', () => { - const originalTimestamp = '2017-12-25T23:50:00Z'; // Monday, previous week relative to currentTimeMarker - const timestamp = adjustTimestamp(originalTimestamp, currentTimeMarker, now, true); - expect(timestamp).toBe('2018-04-16T23:50:00Z'); - }); - - test('adjusts time to friday of the week after now', () => { - const originalTimestamp = '2018-01-05T23:50:00Z'; // Friday, next week relative to currentTimeMarker - const timestamp = adjustTimestamp(originalTimestamp, currentTimeMarker, now, true); - expect(timestamp).toBe('2018-05-04T23:50:00Z'); - }); - - test('adjusts timestamp to correct day of week even when UTC day is on different day.', () => { - const currentTimeMarker = new Date(Date.parse('2018-01-02T00:00:00')); // Tuesday - const now = new Date(Date.parse('2018-06-14T10:38')); // Thurs - const originalTimestamp = '2018-01-01T17:57:25'; // Monday - const timestamp = adjustTimestamp(originalTimestamp, currentTimeMarker, now, true); - expect(timestamp).toBe('2018-06-11T17:57:25'); // Monday - }); -}); - diff --git a/src/server/sample_data/routes/lib/translate_timestamp.js b/src/server/sample_data/routes/lib/translate_timestamp.js new file mode 100644 index 0000000000000..f8d0f86e49709 --- /dev/null +++ b/src/server/sample_data/routes/lib/translate_timestamp.js @@ -0,0 +1,75 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const MILLISECONDS_IN_DAY = 86400000; + +function iso8601ToDateIgnoringTime(iso8601) { + const split = iso8601.split('-'); + if (split.length < 3) { + throw new Error('Unexpected timestamp format, expecting YYYY-MM-DDTHH:mm:ss'); + } + const year = parseInt(split[0]); + const month = parseInt(split[1]) - 1; // javascript months are zero-based indexed + const date = parseInt(split[2]); + return new Date(year, month, date); +} + +export function dateToIso8601IgnoringTime(date) { + // not using "Date.toISOString" because only using Date methods that deal with local time + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const monthString = month < 10 ? `0${month}` : `${month}`; + const dateString = date.getDate() < 10 ? `0${date.getDate()}` : `${date.getDate()}`; + return `${year}-${monthString}-${dateString}`; +} + +// Translate source timestamp by targetReference timestamp, +// perserving the distance between source and sourceReference +export function translateTimeRelativeToDifference(source, sourceReference, targetReference) { + const sourceDate = iso8601ToDateIgnoringTime(source); + const sourceReferenceDate = iso8601ToDateIgnoringTime(sourceReference); + const targetReferenceDate = iso8601ToDateIgnoringTime(targetReference); + + const timeDelta = sourceDate.getTime() - sourceReferenceDate.getTime(); + const translatedDate = (new Date(targetReferenceDate.getTime() + timeDelta)); + + return `${dateToIso8601IgnoringTime(translatedDate)}T${source.substring(11)}`; +} + +// Translate source timestamp by targetReference timestamp, +// perserving the week distance between source and sourceReference and day of week of the source timestamp +export function translateTimeRelativeToWeek(source, sourceReference, targetReference) { + const sourceReferenceDate = iso8601ToDateIgnoringTime(sourceReference); + const targetReferenceDate = iso8601ToDateIgnoringTime(targetReference); + + // If these dates were in the same week, how many days apart would they be? + const dayOfWeekDelta = sourceReferenceDate.getDay() - targetReferenceDate.getDay(); + + // If we pretend that the targetReference is actually the same day of the week as the + // sourceReference, then we can translate the source to the target while preserving their + // days of the week. + const normalizationDelta = dayOfWeekDelta * MILLISECONDS_IN_DAY; + const normalizedTargetReference = + dateToIso8601IgnoringTime(new Date(targetReferenceDate.getTime() + normalizationDelta)); + + return translateTimeRelativeToDifference( + source, + sourceReference, + normalizedTargetReference); +} diff --git a/src/server/sample_data/routes/lib/translate_timestamp.test.js b/src/server/sample_data/routes/lib/translate_timestamp.test.js new file mode 100644 index 0000000000000..348fd2b429167 --- /dev/null +++ b/src/server/sample_data/routes/lib/translate_timestamp.test.js @@ -0,0 +1,107 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + +import { translateTimeRelativeToWeek } from './translate_timestamp'; + +describe('translateTimeRelativeToWeek', () => { + const sourceReference = '2018-01-02T00:00:00'; //Tuesday + const targetReference = '2018-04-25T18:24:58.650'; // Wednesday + + describe('2 weeks before', () => { + test('should properly adjust timestamp when day is before targetReference day of week', () => { + const source = '2017-12-18T23:50:00'; // Monday, -2 week relative to sourceReference + const timestamp = translateTimeRelativeToWeek(source, sourceReference, targetReference); + expect(timestamp).toBe('2018-04-09T23:50:00'); // Monday 2 week before targetReference week + }); + + test('should properly adjust timestamp when day is same as targetReference day of week', () => { + const source = '2017-12-20T23:50:00'; // Wednesday, -2 week relative to sourceReference + const timestamp = translateTimeRelativeToWeek(source, sourceReference, targetReference); + expect(timestamp).toBe('2018-04-11T23:50:00'); // Wednesday 2 week before targetReference week + }); + + test('should properly adjust timestamp when day is after targetReference day of week', () => { + const source = '2017-12-22T16:16:50'; // Friday, -2 week relative to sourceReference + const timestamp = translateTimeRelativeToWeek(source, sourceReference, targetReference); + expect(timestamp).toBe('2018-04-13T16:16:50'); // Friday 2 week before targetReference week + }); + }); + + describe('week before', () => { + test('should properly adjust timestamp when day is before targetReference day of week', () => { + const source = '2017-12-25T23:50:00'; // Monday, -1 week relative to sourceReference + const timestamp = translateTimeRelativeToWeek(source, sourceReference, targetReference); + expect(timestamp).toBe('2018-04-16T23:50:00'); // Monday 1 week before targetReference week + }); + + test('should properly adjust timestamp when day is same as targetReference day of week', () => { + const source = '2017-12-27T23:50:00'; // Wednesday, -1 week relative to sourceReference + const timestamp = translateTimeRelativeToWeek(source, sourceReference, targetReference); + expect(timestamp).toBe('2018-04-18T23:50:00'); // Wednesday 1 week before targetReference week + }); + + test('should properly adjust timestamp when day is after targetReference day of week', () => { + const source = '2017-12-29T16:16:50'; // Friday, -1 week relative to sourceReference + const timestamp = translateTimeRelativeToWeek(source, sourceReference, targetReference); + expect(timestamp).toBe('2018-04-20T16:16:50'); // Friday 1 week before targetReference week + }); + }); + + describe('same week', () => { + test('should properly adjust timestamp when day is before targetReference day of week', () => { + const source = '2018-01-01T23:50:00'; // Monday, same week relative to sourceReference + const timestamp = translateTimeRelativeToWeek(source, sourceReference, targetReference); + expect(timestamp).toBe('2018-04-23T23:50:00'); // Monday same week as targetReference + }); + + test('should properly adjust timestamp when day is same as targetReference day of week', () => { + const source = '2018-01-03T23:50:00'; // Wednesday, same week relative to sourceReference + const timestamp = translateTimeRelativeToWeek(source, sourceReference, targetReference); + expect(timestamp).toBe('2018-04-25T23:50:00'); // Wednesday same week as targetReference + }); + + test('should properly adjust timestamp when day is after targetReference day of week', () => { + const source = '2018-01-05T16:16:50'; // Friday, same week relative to sourceReference + const timestamp = translateTimeRelativeToWeek(source, sourceReference, targetReference); + expect(timestamp).toBe('2018-04-27T16:16:50'); // Friday same week as targetReference + }); + }); + + describe('week after', () => { + test('should properly adjust timestamp when day is before targetReference day of week', () => { + const source = '2018-01-08T23:50:00'; // Monday, 1 week after relative to sourceReference + const timestamp = translateTimeRelativeToWeek(source, sourceReference, targetReference); + expect(timestamp).toBe('2018-04-30T23:50:00'); // Monday 1 week after targetReference week + }); + + test('should properly adjust timestamp when day is same as targetReference day of week', () => { + const source = '2018-01-10T23:50:00'; // Wednesday, same week relative to sourceReference + const timestamp = translateTimeRelativeToWeek(source, sourceReference, targetReference); + expect(timestamp).toBe('2018-05-02T23:50:00'); // Wednesday 1 week after targetReference week + }); + + test('should properly adjust timestamp when day is after targetReference day of week', () => { + const source = '2018-01-12T16:16:50'; // Friday, same week relative to sourceReference + const timestamp = translateTimeRelativeToWeek(source, sourceReference, targetReference); + expect(timestamp).toBe('2018-05-04T16:16:50'); // Friday 1 week after targetReference week + }); + }); +}); + diff --git a/src/ui/public/__tests__/errors.js b/src/ui/public/__tests__/errors.js index 7168b38e2a93d..d328a6dc4e0ca 100644 --- a/src/ui/public/__tests__/errors.js +++ b/src/ui/public/__tests__/errors.js @@ -19,10 +19,8 @@ import expect from 'expect.js'; import { - SearchTimeout, RequestFailure, FetchFailure, - ShardFailure, VersionConflict, MappingConflict, RestrictedMapping, @@ -46,10 +44,8 @@ import { describe('ui/errors', () => { const errors = [ - new SearchTimeout(), new RequestFailure('an error', { }), new FetchFailure({ }), - new ShardFailure({ '_shards': 5 }), new VersionConflict({ }), new MappingConflict({ }), new RestrictedMapping('field', 'indexPattern'), diff --git a/src/ui/public/courier/fetch/call_response_handlers.js b/src/ui/public/courier/fetch/call_response_handlers.js index 13b0dcc81e728..9d305959bc712 100644 --- a/src/ui/public/courier/fetch/call_response_handlers.js +++ b/src/ui/public/courier/fetch/call_response_handlers.js @@ -17,10 +17,9 @@ * under the License. */ -import { RequestFailure, SearchTimeout, ShardFailure } from '../../errors'; - +import { toastNotifications } from '../../notify'; +import { RequestFailure } from '../../errors'; import { RequestStatus } from './req_status'; -import { courierNotifier } from './notifier'; export function CallResponseHandlersProvider(Private, Promise) { const ABORTED = RequestStatus.ABORTED; @@ -35,11 +34,15 @@ export function CallResponseHandlersProvider(Private, Promise) { const response = responses[index]; if (response.timed_out) { - courierNotifier.warning(new SearchTimeout()); + toastNotifications.addWarning({ + title: 'Data might be incomplete because your request timed out', + }); } if (response._shards && response._shards.failed) { - courierNotifier.warning(new ShardFailure(response)); + toastNotifications.addWarning({ + title: '${response._shards.failed} of ${response._shards.total} shards failed', + }); } function progress() { diff --git a/src/ui/public/courier/fetch/fetch_now.js b/src/ui/public/courier/fetch/fetch_now.js index 73a670100cece..70aa20a26619c 100644 --- a/src/ui/public/courier/fetch/fetch_now.js +++ b/src/ui/public/courier/fetch/fetch_now.js @@ -22,7 +22,6 @@ 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. @@ -53,7 +52,7 @@ export function FetchNowProvider(Private, Promise) { return searchRequest.retry(); })) - .catch(error => fatalError(error, location)); + .catch(error => fatalError(error, 'Courier fetch')); } function fetchSearchResults(searchRequests) { diff --git a/src/ui/public/errors.js b/src/ui/public/errors.js index 5a33c6f40a9c9..20cb741857fdf 100644 --- a/src/ui/public/errors.js +++ b/src/ui/public/errors.js @@ -51,16 +51,6 @@ export class KbnError { // instanceof checks. createLegacyClass(KbnError).inherits(Error); -/** - * SearchTimeout error class - */ -export class SearchTimeout extends KbnError { - constructor() { - super('All or part of your request has timed out. The data shown may be incomplete.', - SearchTimeout); - } -} - /** * Request Failure - When an entire multi request fails * @param {Error} err - the Error that came back @@ -92,20 +82,6 @@ export class FetchFailure extends KbnError { } } -/** - * ShardFailure Error - when one or more shards fail - * @param {Object} resp - The response from es. - */ -export class ShardFailure extends KbnError { - constructor(resp) { - super( - `${resp._shards.failed} of ${resp._shards.total} shards failed.`, - ShardFailure); - - this.resp = resp; - } -} - /** * A doc was re-indexed but it was out of date. * @param {Object} resp - The response from es (one of the multi-response responses). diff --git a/src/ui/public/index_patterns/__tests__/_index_pattern.js b/src/ui/public/index_patterns/__tests__/_index_pattern.js index df63f35ce3b25..023e61bdf8a01 100644 --- a/src/ui/public/index_patterns/__tests__/_index_pattern.js +++ b/src/ui/public/index_patterns/__tests__/_index_pattern.js @@ -29,7 +29,7 @@ import { FixturesStubbedSavedObjectIndexPatternProvider } from 'fixtures/stubbed import { IndexPatternsIntervalsProvider } from '../_intervals'; import { IndexPatternProvider } from '../_index_pattern'; import NoDigestPromises from 'test_utils/no_digest_promises'; -import { Notifier } from '../../notify'; +import { toastNotifications } from '../../notify'; import { FieldsFetcherProvider } from '../fields_fetcher_provider'; import { StubIndexPatternsApiClientModule } from './stub_index_patterns_api_client'; @@ -37,8 +37,6 @@ import { IndexPatternsApiClientProvider } from '../index_patterns_api_client_pro import { IsUserAwareOfUnsupportedTimePatternProvider } from '../unsupported_time_patterns'; import { SavedObjectsClientProvider } from '../../saved_objects'; -const MARKDOWN_LINK_RE = /\[(.+?)\]\((.+?)\)/; - describe('index pattern', function () { NoDigestPromises.activateForSuite(); @@ -468,23 +466,22 @@ describe('index pattern', function () { } it('logs a warning when the index pattern source includes `intervalName`', async () => { - const indexPattern = await createUnsupportedTimePattern(); - expect(Notifier.prototype._notifs).to.have.length(1); - - const notif = Notifier.prototype._notifs.shift(); - expect(notif).to.have.property('type', 'warning'); - expect(notif.content).to.match(MARKDOWN_LINK_RE); - - const [, text, url] = notif.content.match(MARKDOWN_LINK_RE); - expect(text).to.contain(indexPattern.title); - expect(url).to.contain(indexPattern.id); - expect(url).to.contain('management/kibana/indices'); + await createUnsupportedTimePattern(); + expect(toastNotifications.list).to.have.length(1); }); it('does not notify if isUserAwareOfUnsupportedTimePattern() returns true', async () => { + // Ideally, _index_pattern.js shouldn't be tightly coupled to toastNotifications. Instead, it + // should notify its consumer of this state and the consumer should be responsible for + // notifying the user. This test verifies the side effect of the state until we can remove + // this coupling. + + // Clear existing toasts. + toastNotifications.list.splice(0); + isUserAwareOfUnsupportedTimePattern.returns(true); await createUnsupportedTimePattern(); - expect(Notifier.prototype._notifs).to.have.length(0); + expect(toastNotifications.list).to.have.length(0); }); }); }); diff --git a/src/ui/public/index_patterns/_index_pattern.js b/src/ui/public/index_patterns/_index_pattern.js index 8010d38d146d9..a158bba776bf3 100644 --- a/src/ui/public/index_patterns/_index_pattern.js +++ b/src/ui/public/index_patterns/_index_pattern.js @@ -17,6 +17,7 @@ * under the License. */ +import React, { Fragment } from 'react'; import _ from 'lodash'; import { SavedObjectNotFound, DuplicateField, IndexPatternMissingIndices } from '../errors'; import angular from 'angular'; @@ -119,13 +120,15 @@ export function IndexPatternProvider(Private, config, Promise, confirmModalPromi if (indexPattern.isUnsupportedTimePattern()) { if (!isUserAwareOfUnsupportedTimePattern(indexPattern)) { - const warning = ( - 'Support for time-intervals has been removed. ' + - `View the ["${indexPattern.title}" index pattern in management](` + - kbnUrl.getRouteHref(indexPattern, 'edit') + - ') for more information.' - ); - notify.warning(warning, { lifetime: Infinity }); + toastNotifications.addWarning({ + title: 'Support for time intervals was removed', + text: ( + + For more information, view the {' '} + {indexPattern.title} index pattern + + ), + }); } } diff --git a/src/ui/public/notify/__tests__/notifier.js b/src/ui/public/notify/__tests__/notifier.js index da31599d3f63f..6696dd831ab9e 100644 --- a/src/ui/public/notify/__tests__/notifier.js +++ b/src/ui/public/notify/__tests__/notifier.js @@ -29,17 +29,6 @@ describe('Notifier', function () { let notifier; let params; const message = 'Oh, the humanity!'; - const customText = 'fooMarkup'; - const customParams = { - title: 'fooTitle', - actions: [{ - text: 'Cancel', - callback: sinon.spy() - }, { - text: 'OK', - callback: sinon.spy() - }] - }; beforeEach(function () { ngMock.module('kibana'); @@ -150,176 +139,6 @@ describe('Notifier', function () { }); }); - describe('#warning', function () { - testVersionInfo('warning'); - - it('prepends location to message for content', function () { - expect(notify('warning').content).to.equal(params.location + ': ' + message); - }); - - it('sets type to "warning"', function () { - expect(notify('warning').type).to.equal('warning'); - }); - - it('sets icon to "warning"', function () { - expect(notify('warning').icon).to.equal('warning'); - }); - - it('sets title to "Warning"', function () { - expect(notify('warning').title).to.equal('Warning'); - }); - - it('sets lifetime to 10000', function () { - expect(notify('warning').lifetime).to.equal(10000); - }); - - it('does not allow reporting', function () { - const includesReport = _.includes(notify('warning').actions, 'report'); - expect(includesReport).to.false; - }); - - it('allows accepting', function () { - const includesAccept = _.includes(notify('warning').actions, 'accept'); - expect(includesAccept).to.true; - }); - - it('does not include stack', function () { - expect(notify('warning').stack).not.to.be.defined; - }); - - it('has css class helper functions', function () { - expect(notify('warning').getIconClass()).to.equal('fa fa-warning'); - expect(notify('warning').getButtonClass()).to.equal('kuiButton--warning'); - expect(notify('warning').getAlertClassStack()).to.equal('toast-stack alert alert-warning'); - expect(notify('warning').getAlertClass()).to.equal('toast alert alert-warning'); - expect(notify('warning').getButtonGroupClass()).to.equal('toast-controls'); - expect(notify('warning').getToastMessageClass()).to.equal('toast-message'); - }); - }); - - describe('#custom', function () { - let customNotification; - - beforeEach(() => { - customNotification = notifier.custom(customText, customParams); - }); - - afterEach(() => { - customNotification.clear(); - }); - - it('throws if second param is not an object', function () { - // destroy the default custom notification, avoid duplicate handling - customNotification.clear(); - - function callCustomIncorrectly() { - const badParam = null; - customNotification = notifier.custom(customText, badParam); - } - expect(callCustomIncorrectly).to.throwException(function (e) { - expect(e.message).to.be('Config param is required, and must be an object'); - }); - - }); - - it('has a custom function to make notifications', function () { - expect(notifier.custom).to.be.a('function'); - }); - - it('properly merges options', function () { - // destroy the default custom notification, avoid duplicate handling - customNotification.clear(); - - const overrideParams = _.defaults({ lifetime: 20000 }, customParams); - customNotification = notifier.custom(customText, overrideParams); - - expect(customNotification).to.have.property('type', 'info'); // default - expect(customNotification).to.have.property('title', overrideParams.title); // passed in thru customParams - expect(customNotification).to.have.property('lifetime', overrideParams.lifetime); // passed in thru overrideParams - - expect(overrideParams.type).to.be(undefined); - expect(overrideParams.title).to.be.a('string'); - expect(overrideParams.lifetime).to.be.a('number'); - }); - - it('sets the content', function () { - expect(customNotification).to.have.property('content', `${params.location}: ${customText}`); - expect(customNotification.content).to.be.a('string'); - }); - - it('uses custom actions', function () { - expect(customNotification).to.have.property('customActions'); - expect(customNotification.customActions).to.have.length(customParams.actions.length); - }); - - it('custom actions have getButtonClass method', function () { - customNotification.customActions.forEach((action, idx) => { - expect(action).to.have.property('getButtonClass'); - expect(action.getButtonClass).to.be.a('function'); - if (idx === 0) { - expect(action.getButtonClass()).to.be('kuiButton--primary kuiButton--primary'); - } else { - expect(action.getButtonClass()).to.be('kuiButton--basic kuiButton--primary'); - } - }); - }); - - it('gives a default action if none are provided', function () { - // destroy the default custom notification, avoid duplicate handling - customNotification.clear(); - - const noActionParams = _.defaults({ actions: [] }, customParams); - customNotification = notifier.custom(customText, noActionParams); - expect(customNotification).to.have.property('actions'); - expect(customNotification.actions).to.have.length(1); - }); - - it('defaults type and lifetime for "info" config', function () { - expect(customNotification.type).to.be('info'); - expect(customNotification.lifetime).to.be(5000); - }); - - it('dynamic lifetime for "warning" config', function () { - // destroy the default custom notification, avoid duplicate handling - customNotification.clear(); - - const errorTypeParams = _.defaults({ type: 'warning' }, customParams); - customNotification = notifier.custom(customText, errorTypeParams); - expect(customNotification.type).to.be('warning'); - expect(customNotification.lifetime).to.be(10000); - }); - - it('dynamic type and lifetime for "error" config', function () { - // destroy the default custom notification, avoid duplicate handling - customNotification.clear(); - - const errorTypeParams = _.defaults({ type: 'error' }, customParams); - customNotification = notifier.custom(customText, errorTypeParams); - expect(customNotification.type).to.be('danger'); - expect(customNotification.lifetime).to.be(300000); - }); - - it('dynamic type and lifetime for "danger" config', function () { - // destroy the default custom notification, avoid duplicate handling - customNotification.clear(); - - const errorTypeParams = _.defaults({ type: 'danger' }, customParams); - customNotification = notifier.custom(customText, errorTypeParams); - expect(customNotification.type).to.be('danger'); - expect(customNotification.lifetime).to.be(300000); - }); - - it('should wrap the callback functions in a close function', function () { - customNotification.customActions.forEach((action, idx) => { - expect(action.callback).not.to.equal(customParams.actions[idx]); - action.callback(); - }); - customParams.actions.forEach(action => { - expect(action.callback.called).to.true; - }); - }); - }); - function notify(fnName, opts) { notifier[fnName](message, opts); return latestNotification(); diff --git a/src/ui/public/notify/notifier.js b/src/ui/public/notify/notifier.js index 620d24df56364..6e94fb390b6cf 100644 --- a/src/ui/public/notify/notifier.js +++ b/src/ui/public/notify/notifier.js @@ -95,7 +95,6 @@ function restartNotifTimer(notif, cb) { const typeToButtonClassMap = { danger: 'kuiButton--danger', // NOTE: `error` type is internally named as `danger` - warning: 'kuiButton--warning', info: 'kuiButton--primary', }; const buttonHierarchyClass = (index) => { @@ -108,7 +107,6 @@ const buttonHierarchyClass = (index) => { }; const typeToAlertClassMap = { danger: `alert-danger`, - warning: `alert-warning`, info: `alert-info`, }; @@ -188,7 +186,6 @@ export function Notifier(opts) { const notificationLevels = [ 'error', - 'warning', ]; notificationLevels.forEach(function (m) { @@ -199,7 +196,6 @@ export function Notifier(opts) { Notifier.config = { bannerLifetime: 3000000, errorLifetime: 300000, - warningLifetime: 10000, infoLifetime: 5000, setInterval: window.setInterval, clearInterval: window.clearInterval @@ -264,28 +260,6 @@ Notifier.prototype.error = function (err, opts, cb) { return add(config, cb); }; -/** - * Warn the user abort something - * @param {String} msg - * @param {Function} cb - */ -Notifier.prototype.warning = function (msg, opts, cb) { - if (_.isFunction(opts)) { - cb = opts; - opts = {}; - } - - const config = _.assign({ - type: 'warning', - content: formatMsg(msg, this.from), - icon: 'warning', - title: 'Warning', - lifetime: Notifier.config.warningLifetime, - actions: ['accept'] - }, _.pick(opts, overridableOptions)); - return add(config, cb); -}; - /** * Display a banner message * @param {String} content @@ -357,8 +331,6 @@ function getDecoratedCustomConfig(config) { const getLifetime = (type) => { switch (type) { - case 'warning': - return Notifier.config.warningLifetime; case 'danger': return Notifier.config.errorLifetime; default: // info @@ -383,30 +355,6 @@ function getDecoratedCustomConfig(config) { return customConfig; } -/** - * Display a custom message - * @param {String} msg - required - * @param {Object} config - required - * @param {Function} cb - optional - * - * config = { - * title: 'Some Title here', - * type: 'info', - * actions: [{ - * text: 'next', - * callback: function() { next(); } - * }, { - * text: 'prev', - * callback: function() { prev(); } - * }] - * } - */ -Notifier.prototype.custom = function (msg, config, cb) { - const customConfig = getDecoratedCustomConfig(config); - customConfig.content = formatMsg(msg, this.from); - return add(customConfig, cb); -}; - /** * Display a scope-bound directive using template rendering in the message area * @param {Object} directive - required diff --git a/src/ui/public/visualize/components/index.js b/src/ui/public/persisted_state/index.d.ts similarity index 87% rename from src/ui/public/visualize/components/index.js rename to src/ui/public/persisted_state/index.d.ts index b11aba5ee8278..ab5a3e7be7d28 100644 --- a/src/ui/public/visualize/components/index.js +++ b/src/ui/public/persisted_state/index.d.ts @@ -17,6 +17,4 @@ * under the License. */ -export * from './visualization'; -export * from './visualization_chart'; -export * from './visualization_noresults'; +export { PersistedState } from './persisted_state'; diff --git a/src/ui/public/visualize/components/visualization_noresults.js b/src/ui/public/persisted_state/persisted_state.d.ts similarity index 66% rename from src/ui/public/visualize/components/visualization_noresults.js rename to src/ui/public/persisted_state/persisted_state.d.ts index 17ece5ffb4758..6a02df8f67f7b 100644 --- a/src/ui/public/visualize/components/visualization_noresults.js +++ b/src/ui/public/persisted_state/persisted_state.d.ts @@ -17,17 +17,6 @@ * under the License. */ -import React from 'react'; - -export function VisualizationNoResults() { - return ( -
-
-
-