diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index 65868b0b7cd46..f7f88603b8332 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -64,6 +64,7 @@ const { } = getServices(); import { getRootBreadcrumbs, getSavedSearchBreadcrumbs } from '../helpers/breadcrumbs'; +import { validateTimeRange } from '../helpers/validate_time_range'; import { esFilters, indexPatterns as indexPatternsUtils, @@ -784,6 +785,10 @@ function discoverController( if (!init.complete) return; $scope.fetchCounter++; $scope.fetchError = undefined; + if (!validateTimeRange(timefilter.getTime(), toastNotifications)) { + $scope.resultState = 'none'; + return; + } // Abort any in-progress requests before fetching again if (abortController) abortController.abort(); @@ -916,14 +921,18 @@ function discoverController( } $scope.updateTime = function () { - //this is the timerange for the histogram, should be refactored + const { from, to } = timefilter.getTime(); + // this is the timerange for the histogram, should be refactored $scope.timeRange = { - from: dateMath.parse(timefilter.getTime().from), - to: dateMath.parse(timefilter.getTime().to, { roundUp: true }), + from: dateMath.parse(from), + to: dateMath.parse(to, { roundUp: true }), }; }; $scope.toMoment = function (datetime) { + if (!datetime) { + return; + } return moment(datetime).format(config.get('dateFormat')); }; diff --git a/src/plugins/discover/public/application/helpers/validate_time_range.test.ts b/src/plugins/discover/public/application/helpers/validate_time_range.test.ts new file mode 100644 index 0000000000000..a61a729caa22b --- /dev/null +++ b/src/plugins/discover/public/application/helpers/validate_time_range.test.ts @@ -0,0 +1,47 @@ +/* + * 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 { validateTimeRange } from './validate_time_range'; +import { notificationServiceMock } from '../../../../../core/public/mocks'; + +describe('Discover validateTimeRange', () => { + test('validates given time ranges correctly', async () => { + const { toasts } = notificationServiceMock.createStartContract(); + [ + { from: '', to: '', result: false }, + { from: 'now', to: 'now+1h', result: true }, + { from: 'now', to: 'lala+1h', result: false }, + { from: '', to: 'now', result: false }, + { from: 'now', to: '', result: false }, + { from: ' 2020-06-02T13:36:13.689Z', to: 'now', result: true }, + { from: ' 2020-06-02T13:36:13.689Z', to: '2020-06-02T13:36:13.690Z', result: true }, + ].map((test) => { + expect(validateTimeRange({ from: test.from, to: test.to }, toasts)).toEqual(test.result); + }); + }); + + test('displays a toast when invalid data is entered', async () => { + const { toasts } = notificationServiceMock.createStartContract(); + expect(validateTimeRange({ from: 'now', to: 'null' }, toasts)).toEqual(false); + expect(toasts.addDanger).toHaveBeenCalledWith({ + title: 'Invalid time range', + text: "The provided time range is invalid. (from: 'now', to: 'null')", + }); + }); +}); diff --git a/src/plugins/discover/public/application/helpers/validate_time_range.ts b/src/plugins/discover/public/application/helpers/validate_time_range.ts new file mode 100644 index 0000000000000..411147f827333 --- /dev/null +++ b/src/plugins/discover/public/application/helpers/validate_time_range.ts @@ -0,0 +1,49 @@ +/* + * 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 dateMath from '@elastic/datemath'; +import { i18n } from '@kbn/i18n'; +import { ToastsStart } from 'kibana/public'; + +/** + * Validates a given time filter range, provided by URL or UI + * Unless valid, it returns false and displays a notification + */ +export function validateTimeRange( + { from, to }: { from: string; to: string }, + toastNotifications: ToastsStart +): boolean { + const fromMoment = dateMath.parse(from); + const toMoment = dateMath.parse(to); + if (!fromMoment || !toMoment || !fromMoment.isValid() || !toMoment.isValid()) { + toastNotifications.addDanger({ + title: i18n.translate('discover.notifications.invalidTimeRangeTitle', { + defaultMessage: `Invalid time range`, + }), + text: i18n.translate('discover.notifications.invalidTimeRangeText', { + defaultMessage: `The provided time range is invalid. (from: '{from}', to: '{to}')`, + values: { + from, + to, + }, + }), + }); + return false; + } + return true; +} diff --git a/test/functional/apps/discover/_discover.js b/test/functional/apps/discover/_discover.js index ecaa5aa2da97f..de9606f3d02ed 100644 --- a/test/functional/apps/discover/_discover.js +++ b/test/functional/apps/discover/_discover.js @@ -257,5 +257,16 @@ export default function ({ getService, getPageObjects }) { expect(refreshedTimeString).not.to.be(initialTimeString); }); }); + + describe('invalid time range in URL', function () { + it('should display a "Invalid time range toast"', async function () { + await PageObjects.common.navigateToUrl('discover', '#/?_g=(time:(from:now-15m,to:null))', { + useActualUrl: true, + }); + await PageObjects.header.awaitKibanaChrome(); + const toastMessage = await PageObjects.common.closeToast(); + expect(toastMessage).to.be('Invalid time range'); + }); + }); }); } diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts index 236b2fb9f2f1e..8c5a99204bab6 100644 --- a/test/functional/page_objects/common_page.ts +++ b/test/functional/page_objects/common_page.ts @@ -399,7 +399,7 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo const toast = await find.byCssSelector('.euiToast', 2 * defaultFindTimeout); await toast.moveMouseTo(); const title = await (await find.byCssSelector('.euiToastHeader__title')).getVisibleText(); - log.debug(`Toast title: ${title}`); + await find.clickByCssSelector('.euiToast__closeButton'); return title; }