From fb6826c3b1b65e6076d41fd50afc39edaf896b7a Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Wed, 1 Sep 2021 13:43:59 -0400 Subject: [PATCH 01/13] Initial PoC of redirect on mapping error is working. --- x-pack/plugins/uptime/common/constants/ui.ts | 2 + .../filter_group/filter_group_container.tsx | 26 ++++++++++++- .../uptime/public/hooks/use_telemetry.ts | 1 + x-pack/plugins/uptime/public/pages/index.ts | 1 + .../uptime/public/pages/mapping_error.tsx | 38 +++++++++++++++++++ x-pack/plugins/uptime/public/routes.tsx | 18 ++++++++- .../public/state/api/overview_filters.ts | 6 ++- .../overview_filters/get_overview_filters.ts | 36 ++++++++++++------ 8 files changed, 113 insertions(+), 15 deletions(-) create mode 100644 x-pack/plugins/uptime/public/pages/mapping_error.tsx diff --git a/x-pack/plugins/uptime/common/constants/ui.ts b/x-pack/plugins/uptime/common/constants/ui.ts index dcaf4bb310ad7..97fc4ffcd446b 100644 --- a/x-pack/plugins/uptime/common/constants/ui.ts +++ b/x-pack/plugins/uptime/common/constants/ui.ts @@ -17,6 +17,8 @@ export const STEP_DETAIL_ROUTE = '/journey/:checkGroupId/step/:stepIndex'; export const SYNTHETIC_CHECK_STEPS_ROUTE = '/journey/:checkGroupId/steps'; +export const MAPPING_ERROR_ROUTE = '/mapping-error'; + export enum STATUS { UP = 'up', DOWN = 'down', diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group_container.tsx b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group_container.tsx index db1892526a1e6..34eb0cb4ade2a 100644 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group_container.tsx +++ b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group_container.tsx @@ -7,21 +7,45 @@ import React, { useContext, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; +import { useHistory } from 'react-router-dom'; import { useGetUrlParams } from '../../../hooks'; import { parseFiltersMap } from './parse_filter_map'; import { fetchOverviewFilters } from '../../../state/actions'; import { FilterGroupComponent } from './index'; import { UptimeRefreshContext } from '../../../contexts'; import { esKuerySelector, overviewFiltersSelector } from '../../../state/selectors'; +import { MAPPING_ERROR_ROUTE } from '../../../../common/constants/ui'; interface Props { esFilters?: string; } +export function shouldRedirectToMappingErrorPage(errors: Error[]) { + return ( + errors.filter( + (e) => + /** + * These are elements of the Elasticsearch error we are trying to catch. + */ + e.message.indexOf('search_phase_execution_exception') !== -1 || + e.message.indexOf('Please use a keyword field instead.') || + e.message.indexOf('set fielddata=true') + ).length > 0 + ); +} + export const FilterGroup: React.FC = ({ esFilters }: Props) => { + const history = useHistory(); const { lastRefresh } = useContext(UptimeRefreshContext); - const { filters: overviewFilters, loading } = useSelector(overviewFiltersSelector); + const { filters: overviewFilters, loading, errors } = useSelector(overviewFiltersSelector); + + if (errors.length > 0 && shouldRedirectToMappingErrorPage(errors)) { + // TODO: clear the errors list, or allow the user to try and navigate + // back to the overview page again + history.push(MAPPING_ERROR_ROUTE); + } + const esKuery = useSelector(esKuerySelector); const { dateRangeStart, dateRangeEnd, statusFilter, filters: urlFilters } = useGetUrlParams(); diff --git a/x-pack/plugins/uptime/public/hooks/use_telemetry.ts b/x-pack/plugins/uptime/public/hooks/use_telemetry.ts index 4ba0179bb54ba..5a4bffc2351a7 100644 --- a/x-pack/plugins/uptime/public/hooks/use_telemetry.ts +++ b/x-pack/plugins/uptime/public/hooks/use_telemetry.ts @@ -12,6 +12,7 @@ import { API_URLS } from '../../common/constants'; export enum UptimePage { Overview = 'Overview', + MappingError = 'MappingError', Monitor = 'Monitor', Settings = 'Settings', Certificates = 'Certificates', diff --git a/x-pack/plugins/uptime/public/pages/index.ts b/x-pack/plugins/uptime/public/pages/index.ts index 5624f61c3abb5..352ceb39123e8 100644 --- a/x-pack/plugins/uptime/public/pages/index.ts +++ b/x-pack/plugins/uptime/public/pages/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +export { MappingErrorPage } from './mapping_error'; export { MonitorPage } from './monitor'; export { StepDetailPage } from './synthetics/step_detail_page'; export { SettingsPage } from './settings'; diff --git a/x-pack/plugins/uptime/public/pages/mapping_error.tsx b/x-pack/plugins/uptime/public/pages/mapping_error.tsx new file mode 100644 index 0000000000000..773a164f5f3ef --- /dev/null +++ b/x-pack/plugins/uptime/public/pages/mapping_error.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiEmptyPrompt } from '@elastic/eui'; + +import React from 'react'; + +import { i18n } from '@kbn/i18n'; + +import { useBreadcrumbs } from '../hooks/use_breadcrumbs'; +import { useTrackPageview } from '../../../observability/public'; + +export const MappingErrorPage = () => { + useTrackPageview({ app: 'uptime', path: 'mapping-error' }); + useTrackPageview({ app: 'uptime', path: 'mapping-error', delay: 15000 }); + + useBreadcrumbs([ + { + text: i18n.translate('xpack.uptime.mappingErrorRoute.breadcrumb', { + defaultMessage: 'Mapping error', + }), + }, + ]); + + return ( + Heartbeat mappings are not installed} + body={
You need to stop Heartbeat, delete your indices, and restart Heartbeat.
} + /> + ); +}; diff --git a/x-pack/plugins/uptime/public/routes.tsx b/x-pack/plugins/uptime/public/routes.tsx index d111d44f08c2d..7b0a68c89c874 100644 --- a/x-pack/plugins/uptime/public/routes.tsx +++ b/x-pack/plugins/uptime/public/routes.tsx @@ -12,13 +12,14 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { CERTIFICATES_ROUTE, + MAPPING_ERROR_ROUTE, MONITOR_ROUTE, OVERVIEW_ROUTE, SETTINGS_ROUTE, STEP_DETAIL_ROUTE, SYNTHETIC_CHECK_STEPS_ROUTE, } from '../common/constants'; -import { MonitorPage, StepDetailPage, NotFoundPage, SettingsPage } from './pages'; +import { MappingErrorPage, MonitorPage, StepDetailPage, NotFoundPage, SettingsPage } from './pages'; import { CertificatesPage } from './pages/certificates'; import { UptimePage, useUptimeTelemetry } from './hooks'; import { OverviewPageComponent } from './pages/overview'; @@ -144,6 +145,21 @@ const Routes: RouteProps[] = [ rightSideItems: [], }, }, + { + title: i18n.translate('xpack.uptime.mappingErrorRoute.title', { + // TODO: placeholder copy + defaultMessage: 'Synthetics | mapping error', + }), + path: MAPPING_ERROR_ROUTE, + component: MappingErrorPage, + dataTestSubj: 'uptimeMappingErrorPage', + telemetryId: UptimePage.MappingError, + pageHeader: { + // TODO: placeholder copy + pageTitle:
Mapping error
, + rightSideItems: [], + }, + }, ]; const RouteInit: React.FC> = ({ diff --git a/x-pack/plugins/uptime/public/state/api/overview_filters.ts b/x-pack/plugins/uptime/public/state/api/overview_filters.ts index 16e6841a26cd1..252854f7bf688 100644 --- a/x-pack/plugins/uptime/public/state/api/overview_filters.ts +++ b/x-pack/plugins/uptime/public/state/api/overview_filters.ts @@ -29,5 +29,9 @@ export const fetchOverviewFilters = async ({ search, }; - return await apiService.get(API_URLS.FILTERS, queryParams, OverviewFiltersType); + try { + return await apiService.get(API_URLS.FILTERS, queryParams, OverviewFiltersType); + } catch (e) { + return new Error(e.body.message); + } }; diff --git a/x-pack/plugins/uptime/server/rest_api/overview_filters/get_overview_filters.ts b/x-pack/plugins/uptime/server/rest_api/overview_filters/get_overview_filters.ts index 354c57c365115..8179f24e12519 100644 --- a/x-pack/plugins/uptime/server/rest_api/overview_filters/get_overview_filters.ts +++ b/x-pack/plugins/uptime/server/rest_api/overview_filters/get_overview_filters.ts @@ -42,17 +42,29 @@ export const createGetOverviewFilters: UMRestApiRouteFactory = (libs: UMServerLi } } - return await libs.requests.getFilterBar({ - uptimeEsClient, - dateRangeStart, - dateRangeEnd, - search: parsedSearch, - filterOptions: objectValuesToArrays({ - locations, - ports, - schemes, - tags, - }), - }); + try { + return await libs.requests.getFilterBar({ + uptimeEsClient, + dateRangeStart, + dateRangeEnd, + search: parsedSearch, + filterOptions: objectValuesToArrays({ + locations, + ports, + schemes, + tags, + }), + }); + } catch (e) { + /** + * This particular error is usually indicative of a mapping problem within the user's + * indices. It's relevant for the UI because we will be able to provide the user with a + * tailored message to help them remediate this problem on their own with minimal effort. + */ + if (e.name === 'ResponseError') { + return response.badRequest({ body: e }); + } + throw e; + } }, }); From 96b8de90e6d05b5f076e018a94f8bdf7fd3c8cab Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 7 Sep 2021 15:33:41 -0400 Subject: [PATCH 02/13] Update copy. Add comments. --- .../filter_group/filter_group_container.tsx | 9 ++++++-- .../uptime/public/pages/mapping_error.tsx | 21 +++++++++++++++---- x-pack/plugins/uptime/public/routes.tsx | 11 +++++++--- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group_container.tsx b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group_container.tsx index 34eb0cb4ade2a..cfc3622545e47 100644 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group_container.tsx +++ b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group_container.tsx @@ -40,9 +40,14 @@ export const FilterGroup: React.FC = ({ esFilters }: Props) => { const { filters: overviewFilters, loading, errors } = useSelector(overviewFiltersSelector); + /** + * Because this component renders on the Overview Page, and this component typically fetches + * its data fastest, there is a check whether the Heartbeat mappings are correctly installed. + * + * If the mappings are missing, we re-direct to a page with instructions on how to resolve + * the missing mappings issue. + */ if (errors.length > 0 && shouldRedirectToMappingErrorPage(errors)) { - // TODO: clear the errors list, or allow the user to try and navigate - // back to the overview page again history.push(MAPPING_ERROR_ROUTE); } diff --git a/x-pack/plugins/uptime/public/pages/mapping_error.tsx b/x-pack/plugins/uptime/public/pages/mapping_error.tsx index 773a164f5f3ef..6c24364d01e84 100644 --- a/x-pack/plugins/uptime/public/pages/mapping_error.tsx +++ b/x-pack/plugins/uptime/public/pages/mapping_error.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import { EuiEmptyPrompt } from '@elastic/eui'; +import { EuiCode, EuiEmptyPrompt } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; @@ -30,9 +31,21 @@ export const MappingErrorPage = () => { Heartbeat mappings are not installed} - body={
You need to stop Heartbeat, delete your indices, and restart Heartbeat.
} + title={ +
+ +
+ } + body={ + setup }} + /> + } /> ); }; diff --git a/x-pack/plugins/uptime/public/routes.tsx b/x-pack/plugins/uptime/public/routes.tsx index 7b0a68c89c874..b23bf7b885805 100644 --- a/x-pack/plugins/uptime/public/routes.tsx +++ b/x-pack/plugins/uptime/public/routes.tsx @@ -147,7 +147,6 @@ const Routes: RouteProps[] = [ }, { title: i18n.translate('xpack.uptime.mappingErrorRoute.title', { - // TODO: placeholder copy defaultMessage: 'Synthetics | mapping error', }), path: MAPPING_ERROR_ROUTE, @@ -155,8 +154,14 @@ const Routes: RouteProps[] = [ dataTestSubj: 'uptimeMappingErrorPage', telemetryId: UptimePage.MappingError, pageHeader: { - // TODO: placeholder copy - pageTitle:
Mapping error
, + pageTitle: ( +
+ +
+ ), rightSideItems: [], }, }, From 630618a1e355074eb6a7d3f5b64875c38e743da6 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 7 Sep 2021 15:40:36 -0400 Subject: [PATCH 03/13] Include headline element for page title. --- .../uptime/public/pages/mapping_error.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/uptime/public/pages/mapping_error.tsx b/x-pack/plugins/uptime/public/pages/mapping_error.tsx index 6c24364d01e84..825161f01c83b 100644 --- a/x-pack/plugins/uptime/public/pages/mapping_error.tsx +++ b/x-pack/plugins/uptime/public/pages/mapping_error.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiCode, EuiEmptyPrompt } from '@elastic/eui'; +import { EuiCode, EuiEmptyPrompt, EuiTitle } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; @@ -32,12 +32,14 @@ export const MappingErrorPage = () => { iconColor="danger" iconType="cross" title={ -
- -
+ +

+ +

+
} body={ Date: Wed, 8 Sep 2021 12:16:38 -0400 Subject: [PATCH 04/13] Create mappings for failing functional tests. --- x-pack/test/functional/apps/uptime/certificates.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/test/functional/apps/uptime/certificates.ts b/x-pack/test/functional/apps/uptime/certificates.ts index 70affdf836072..e6d99cfca2d01 100644 --- a/x-pack/test/functional/apps/uptime/certificates.ts +++ b/x-pack/test/functional/apps/uptime/certificates.ts @@ -13,11 +13,13 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const { uptime } = getPageObjects(['uptime']); const uptimeService = getService('uptime'); + const esArchiver = getService('esArchiver'); const es = getService('es'); describe('certificates', function () { describe('empty certificates', function () { before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/uptime/blank'); await makeCheck({ es }); await uptime.goToRoot(true); }); From f6a4159ce76cded0a29bfe5ec8fe1ff98af3214e Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Wed, 8 Sep 2021 13:02:59 -0400 Subject: [PATCH 05/13] Add functional test for mappings error page. --- .../uptime/public/pages/mapping_error.tsx | 1 + x-pack/test/functional/apps/uptime/index.ts | 4 +++ .../apps/uptime/missing_mappings.ts | 26 +++++++++++++++++++ .../test/functional/services/uptime/common.ts | 3 +++ 4 files changed, 34 insertions(+) create mode 100644 x-pack/test/functional/apps/uptime/missing_mappings.ts diff --git a/x-pack/plugins/uptime/public/pages/mapping_error.tsx b/x-pack/plugins/uptime/public/pages/mapping_error.tsx index 825161f01c83b..b95973e8129f5 100644 --- a/x-pack/plugins/uptime/public/pages/mapping_error.tsx +++ b/x-pack/plugins/uptime/public/pages/mapping_error.tsx @@ -29,6 +29,7 @@ export const MappingErrorPage = () => { return ( { loadTestFile(require.resolve('./ml_anomaly')); loadTestFile(require.resolve('./feature_controls')); }); + + describe('mappings error state', () => { + loadTestFile(require.resolve('./missing_mappings')); + }); }); }; diff --git a/x-pack/test/functional/apps/uptime/missing_mappings.ts b/x-pack/test/functional/apps/uptime/missing_mappings.ts new file mode 100644 index 0000000000000..2483aa45ecef9 --- /dev/null +++ b/x-pack/test/functional/apps/uptime/missing_mappings.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; +import { makeCheck } from '../../../api_integration/apis/uptime/rest/helper/make_checks'; + +export default ({ getPageObjects, getService }: FtrProviderContext) => { + const { common } = getPageObjects(['common']); + const uptimeService = getService('uptime'); + + const es = getService('es'); + describe('missing mappings', function () { + before(async () => { + await makeCheck({ es }); + await common.navigateToApp('uptime'); + }); + + it('redirects to mappings error page', async () => { + await uptimeService.common.hasMappingsError(); + }); + }); +}; diff --git a/x-pack/test/functional/services/uptime/common.ts b/x-pack/test/functional/services/uptime/common.ts index 6a28b32f4ca6d..64a026238a47b 100644 --- a/x-pack/test/functional/services/uptime/common.ts +++ b/x-pack/test/functional/services/uptime/common.ts @@ -104,5 +104,8 @@ export function UptimeCommonProvider({ getService, getPageObjects }: FtrProvider await testSubjects.missingOrFail('data-missing'); }); }, + async hasMappingsError() { + return testSubjects.exists('xpack.uptime.mappingsErrorPage'); + }, }; } From 1cb42f80e890c1f0e3ec3833b3cba9b1592dbdac Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Wed, 8 Sep 2021 15:47:21 -0400 Subject: [PATCH 06/13] Add mapping for certs check. --- x-pack/test/functional/apps/uptime/certificates.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/uptime/certificates.ts b/x-pack/test/functional/apps/uptime/certificates.ts index e6d99cfca2d01..610f07c183782 100644 --- a/x-pack/test/functional/apps/uptime/certificates.ts +++ b/x-pack/test/functional/apps/uptime/certificates.ts @@ -9,6 +9,8 @@ import { FtrProviderContext } from '../../ftr_provider_context'; import { makeCheck } from '../../../api_integration/apis/uptime/rest/helper/make_checks'; import { getSha256 } from '../../../api_integration/apis/uptime/rest/helper/make_tls'; +const BLANK_INDEX_PATH = 'x-pack/test/functional/es_archives/uptime/blank'; + export default ({ getPageObjects, getService }: FtrProviderContext) => { const { uptime } = getPageObjects(['uptime']); const uptimeService = getService('uptime'); @@ -19,11 +21,15 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { describe('certificates', function () { describe('empty certificates', function () { before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/uptime/blank'); + await esArchiver.load(BLANK_INDEX_PATH); await makeCheck({ es }); await uptime.goToRoot(true); }); + after(async () => { + await esArchiver.unload(BLANK_INDEX_PATH); + }); + it('go to certs page', async () => { await uptimeService.common.waitUntilDataIsLoaded(); await uptimeService.cert.hasViewCertButton(); @@ -36,10 +42,15 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { describe('with certs', function () { before(async () => { + await esArchiver.load(BLANK_INDEX_PATH); await makeCheck({ es, tls: true }); await uptime.goToRoot(true); }); + after(async () => { + await esArchiver.unload(BLANK_INDEX_PATH); + }); + beforeEach(async () => { await makeCheck({ es, tls: true }); }); From 160b0e8f424c31d60fa8042f7d2f70f07e44d076 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Fri, 10 Sep 2021 15:31:33 -0400 Subject: [PATCH 07/13] Add docs link. --- .../uptime/public/pages/mapping_error.tsx | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/uptime/public/pages/mapping_error.tsx b/x-pack/plugins/uptime/public/pages/mapping_error.tsx index b95973e8129f5..7a4eba12c8eab 100644 --- a/x-pack/plugins/uptime/public/pages/mapping_error.tsx +++ b/x-pack/plugins/uptime/public/pages/mapping_error.tsx @@ -5,13 +5,13 @@ * 2.0. */ -import { EuiCode, EuiEmptyPrompt, EuiTitle } from '@elastic/eui'; +import { EuiCode, EuiEmptyPrompt, EuiLink, EuiTitle } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; -import { i18n } from '@kbn/i18n'; - +import { useKibana } from '../../../../../src/plugins/kibana_react/public'; import { useBreadcrumbs } from '../hooks/use_breadcrumbs'; import { useTrackPageview } from '../../../observability/public'; @@ -19,6 +19,8 @@ export const MappingErrorPage = () => { useTrackPageview({ app: 'uptime', path: 'mapping-error' }); useTrackPageview({ app: 'uptime', path: 'mapping-error', delay: 15000 }); + const docLinks = useKibana().services.docLinks; + useBreadcrumbs([ { text: i18n.translate('xpack.uptime.mappingErrorRoute.breadcrumb', { @@ -43,11 +45,33 @@ export const MappingErrorPage = () => { } body={ - setup }} - /> +
+

+ setup }} + /> +

+ {docLinks && ( +

+ + docs + + ), + }} + /> +

+ )} +
} /> ); From d25960844ecc0b1c7d6cb9f8ba69eee773ed2b01 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Fri, 10 Sep 2021 15:45:41 -0400 Subject: [PATCH 08/13] Add missing word to copy. --- x-pack/plugins/uptime/public/pages/mapping_error.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/uptime/public/pages/mapping_error.tsx b/x-pack/plugins/uptime/public/pages/mapping_error.tsx index 7a4eba12c8eab..9c234700136b0 100644 --- a/x-pack/plugins/uptime/public/pages/mapping_error.tsx +++ b/x-pack/plugins/uptime/public/pages/mapping_error.tsx @@ -57,7 +57,7 @@ export const MappingErrorPage = () => {

Date: Tue, 21 Sep 2021 10:51:28 -0400 Subject: [PATCH 09/13] Add conditional redirect hook for monitor list. --- .../monitor_list/monitor_list_container.tsx | 2 + .../public/hooks/use_mapping_check.test.ts | 53 +++++++++++++++++++ .../uptime/public/hooks/use_mapping_check.ts | 43 +++++++++++++++ .../server/rest_api/monitors/monitor_list.ts | 53 +++++++++++++------ 4 files changed, 134 insertions(+), 17 deletions(-) create mode 100644 x-pack/plugins/uptime/public/hooks/use_mapping_check.test.ts create mode 100644 x-pack/plugins/uptime/public/hooks/use_mapping_check.ts diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_container.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_container.tsx index 835a89e8f7272..726ef59827f9e 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_container.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_container.tsx @@ -13,6 +13,7 @@ import { MonitorListComponent } from './monitor_list'; import { useUrlParams } from '../../../hooks'; import { UptimeRefreshContext } from '../../../contexts'; import { getConnectorsAction, getMonitorAlertsAction } from '../../../state/alerts/alerts'; +import { useMappingCheck } from '../../../hooks/use_mapping_check'; export interface MonitorListProps { filters?: string; @@ -41,6 +42,7 @@ export const MonitorList: React.FC = (props) => { const { lastRefresh } = useContext(UptimeRefreshContext); const monitorList = useSelector(monitorListSelector); + useMappingCheck(monitorList.error); useEffect(() => { dispatch( diff --git a/x-pack/plugins/uptime/public/hooks/use_mapping_check.test.ts b/x-pack/plugins/uptime/public/hooks/use_mapping_check.test.ts new file mode 100644 index 0000000000000..5f17e65d102b4 --- /dev/null +++ b/x-pack/plugins/uptime/public/hooks/use_mapping_check.test.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { shouldRedirect } from './use_mapping_check'; + +describe('useMappingCheck', () => { + describe('should redirect', () => { + it('returns true for appropriate error', () => { + const error = { + request: {}, + response: {}, + body: { + statusCode: 400, + error: 'Bad Request', + message: + '[search_phase_execution_exception: [illegal_argument_exception] Reason: Text fields are not optimised for operations that require per-document field data like aggregations and sorting, so these operations are disabled by default. Please use a keyword field instead. Alternatively, set fielddata=true on [monitor.id] in order to load field data by uninverting the inverted index. Note that this can use significant memory.]: all shards failed', + }, + name: 'Error', + req: {}, + res: {}, + }; + expect(shouldRedirect(error)).toBe(true); + }); + + it('returns false for undefined', () => { + expect(shouldRedirect(undefined)).toBe(false); + }); + + it('returns false for missing body', () => { + expect(shouldRedirect({})).toBe(false); + }); + + it('returns false for incorrect error string', () => { + expect(shouldRedirect({ body: { error: 'not the right type' } })).toBe(false); + }); + + it('returns false for missing body message', () => { + expect(shouldRedirect({ body: { error: 'Bad Request' } })).toBe(false); + }); + + it('returns false for incorrect error message', () => { + expect( + shouldRedirect({ + body: { error: 'Bad Request', message: 'Not the correct kind of error message' }, + }) + ); + }); + }); +}); diff --git a/x-pack/plugins/uptime/public/hooks/use_mapping_check.ts b/x-pack/plugins/uptime/public/hooks/use_mapping_check.ts new file mode 100644 index 0000000000000..88be4080ef502 --- /dev/null +++ b/x-pack/plugins/uptime/public/hooks/use_mapping_check.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useEffect } from 'react'; +import { useHistory } from 'react-router'; +import { MAPPING_ERROR_ROUTE } from '../../common/constants'; + +interface EsBadRequestError { + body?: { + error?: string; + message?: string; + }; +} + +function contains(message: string, phrase: string) { + return message.indexOf(phrase) !== -1; +} + +export function shouldRedirect(error?: EsBadRequestError) { + if (!error || !error.body || error.body.error !== 'Bad Request' || !error.body.message) { + return false; + } + const { message } = error.body; + return ( + contains(message, 'search_phase_execution_exception') || + contains(message, 'Please use a keyword field instead.') || + contains(message, 'set fielddata=true') + ); +} + +export function useMappingCheck(error?: EsBadRequestError) { + const history = useHistory(); + + useEffect(() => { + if (shouldRedirect(error)) { + history.push(MAPPING_ERROR_ROUTE); + } + }, [error, history]); +} diff --git a/x-pack/plugins/uptime/server/rest_api/monitors/monitor_list.ts b/x-pack/plugins/uptime/server/rest_api/monitors/monitor_list.ts index d28645bcb21a1..a07fb4db32b74 100644 --- a/x-pack/plugins/uptime/server/rest_api/monitors/monitor_list.ts +++ b/x-pack/plugins/uptime/server/rest_api/monitors/monitor_list.ts @@ -27,28 +27,47 @@ export const createMonitorListRoute: UMRestApiRouteFactory = (libs) => ({ options: { tags: ['access:uptime-read'], }, - handler: async ({ uptimeEsClient, request }): Promise => { - const { dateRangeStart, dateRangeEnd, filters, pagination, statusFilter, pageSize, query } = - request.query; + handler: async ({ uptimeEsClient, request, response }): Promise => { + const { + dateRangeStart, + dateRangeEnd, + filters, + pagination, + statusFilter, + pageSize, + query, + } = request.query; const decodedPagination = pagination ? JSON.parse(decodeURIComponent(pagination)) : CONTEXT_DEFAULTS.CURSOR_PAGINATION; - const result = await libs.requests.getMonitorStates({ - uptimeEsClient, - dateRangeStart, - dateRangeEnd, - pagination: decodedPagination, - pageSize, - filters, - query, - // this is added to make typescript happy, - // this sort of reassignment used to be further downstream but I've moved it here - // because this code is going to be decomissioned soon - statusFilter: statusFilter || undefined, - }); + try { + const result = await libs.requests.getMonitorStates({ + uptimeEsClient, + dateRangeStart, + dateRangeEnd, + pagination: decodedPagination, + pageSize, + filters, + query, + // this is added to make typescript happy, + // this sort of reassignment used to be further downstream but I've moved it here + // because this code is going to be decomissioned soon + statusFilter: statusFilter || undefined, + }); - return result; + return result; + } catch (e) { + /** + * This particular error is usually indicative of a mapping problem within the user's + * indices. It's relevant for the UI because we will be able to provide the user with a + * tailored message to help them remediate this problem on their own with minimal effort. + */ + if (e.name === 'ResponseError') { + return response.badRequest({ body: e }); + } + throw e; + } }, }); From 8b96641d7f16917baa48bbbc6dc7701aea091e36 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 21 Sep 2021 10:57:50 -0400 Subject: [PATCH 10/13] Delete obsolete files from bad merge. --- .../filter_group/filter_group_container.tsx | 86 ------------------- .../public/state/api/overview_filters.ts | 37 -------- .../overview_filters/get_overview_filters.ts | 70 --------------- 3 files changed, 193 deletions(-) delete mode 100644 x-pack/plugins/uptime/public/components/overview/filter_group/filter_group_container.tsx delete mode 100644 x-pack/plugins/uptime/public/state/api/overview_filters.ts delete mode 100644 x-pack/plugins/uptime/server/rest_api/overview_filters/get_overview_filters.ts diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group_container.tsx b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group_container.tsx deleted file mode 100644 index cfc3622545e47..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group_container.tsx +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useContext, useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { useHistory } from 'react-router-dom'; -import { useGetUrlParams } from '../../../hooks'; -import { parseFiltersMap } from './parse_filter_map'; -import { fetchOverviewFilters } from '../../../state/actions'; -import { FilterGroupComponent } from './index'; -import { UptimeRefreshContext } from '../../../contexts'; -import { esKuerySelector, overviewFiltersSelector } from '../../../state/selectors'; -import { MAPPING_ERROR_ROUTE } from '../../../../common/constants/ui'; - -interface Props { - esFilters?: string; -} - -export function shouldRedirectToMappingErrorPage(errors: Error[]) { - return ( - errors.filter( - (e) => - /** - * These are elements of the Elasticsearch error we are trying to catch. - */ - e.message.indexOf('search_phase_execution_exception') !== -1 || - e.message.indexOf('Please use a keyword field instead.') || - e.message.indexOf('set fielddata=true') - ).length > 0 - ); -} - -export const FilterGroup: React.FC = ({ esFilters }: Props) => { - const history = useHistory(); - const { lastRefresh } = useContext(UptimeRefreshContext); - - const { filters: overviewFilters, loading, errors } = useSelector(overviewFiltersSelector); - - /** - * Because this component renders on the Overview Page, and this component typically fetches - * its data fastest, there is a check whether the Heartbeat mappings are correctly installed. - * - * If the mappings are missing, we re-direct to a page with instructions on how to resolve - * the missing mappings issue. - */ - if (errors.length > 0 && shouldRedirectToMappingErrorPage(errors)) { - history.push(MAPPING_ERROR_ROUTE); - } - - const esKuery = useSelector(esKuerySelector); - - const { dateRangeStart, dateRangeEnd, statusFilter, filters: urlFilters } = useGetUrlParams(); - - const dispatch = useDispatch(); - - useEffect(() => { - const filterSelections = parseFiltersMap(urlFilters); - dispatch( - fetchOverviewFilters({ - dateRangeStart, - dateRangeEnd, - locations: filterSelections.locations ?? [], - ports: filterSelections.ports ?? [], - schemes: filterSelections.schemes ?? [], - search: esKuery, - statusFilter, - tags: filterSelections.tags ?? [], - }) - ); - }, [ - lastRefresh, - dateRangeStart, - dateRangeEnd, - esKuery, - esFilters, - statusFilter, - urlFilters, - dispatch, - ]); - - return ; -}; diff --git a/x-pack/plugins/uptime/public/state/api/overview_filters.ts b/x-pack/plugins/uptime/public/state/api/overview_filters.ts deleted file mode 100644 index 252854f7bf688..0000000000000 --- a/x-pack/plugins/uptime/public/state/api/overview_filters.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { GetOverviewFiltersPayload } from '../actions'; -import { OverviewFiltersType } from '../../../common/runtime_types'; -import { apiService } from './utils'; -import { API_URLS } from '../../../common/constants'; - -export const fetchOverviewFilters = async ({ - dateRangeStart, - dateRangeEnd, - search, - schemes, - locations, - ports, - tags, -}: GetOverviewFiltersPayload) => { - const queryParams = { - dateRangeStart, - dateRangeEnd, - schemes, - locations, - ports, - tags, - search, - }; - - try { - return await apiService.get(API_URLS.FILTERS, queryParams, OverviewFiltersType); - } catch (e) { - return new Error(e.body.message); - } -}; diff --git a/x-pack/plugins/uptime/server/rest_api/overview_filters/get_overview_filters.ts b/x-pack/plugins/uptime/server/rest_api/overview_filters/get_overview_filters.ts deleted file mode 100644 index 8179f24e12519..0000000000000 --- a/x-pack/plugins/uptime/server/rest_api/overview_filters/get_overview_filters.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { schema } from '@kbn/config-schema'; -import { UMServerLibs } from '../../lib/lib'; -import { UMRestApiRouteFactory } from '../types'; -import { objectValuesToArrays } from '../../lib/helper'; -import { API_URLS } from '../../../common/constants'; - -const arrayOrStringType = schema.maybe( - schema.oneOf([schema.string(), schema.arrayOf(schema.string())]) -); - -export const createGetOverviewFilters: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ - method: 'GET', - path: API_URLS.FILTERS, - validate: { - query: schema.object({ - dateRangeStart: schema.string(), - dateRangeEnd: schema.string(), - search: schema.maybe(schema.string()), - locations: arrayOrStringType, - schemes: arrayOrStringType, - ports: arrayOrStringType, - tags: arrayOrStringType, - _inspect: schema.maybe(schema.boolean()), - }), - }, - handler: async ({ uptimeEsClient, request, response }): Promise => { - const { dateRangeStart, dateRangeEnd, locations, schemes, search, ports, tags } = request.query; - - let parsedSearch: Record | undefined; - if (search) { - try { - parsedSearch = JSON.parse(search); - } catch (e) { - return response.badRequest({ body: { message: e.message } }); - } - } - - try { - return await libs.requests.getFilterBar({ - uptimeEsClient, - dateRangeStart, - dateRangeEnd, - search: parsedSearch, - filterOptions: objectValuesToArrays({ - locations, - ports, - schemes, - tags, - }), - }); - } catch (e) { - /** - * This particular error is usually indicative of a mapping problem within the user's - * indices. It's relevant for the UI because we will be able to provide the user with a - * tailored message to help them remediate this problem on their own with minimal effort. - */ - if (e.name === 'ResponseError') { - return response.badRequest({ body: e }); - } - throw e; - } - }, -}); From 03e9ae13164e53231f8866a754e533b614e40c78 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 21 Sep 2021 11:07:20 -0400 Subject: [PATCH 11/13] Remove useless comment. --- .../plugins/uptime/server/rest_api/monitors/monitor_list.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/x-pack/plugins/uptime/server/rest_api/monitors/monitor_list.ts b/x-pack/plugins/uptime/server/rest_api/monitors/monitor_list.ts index a07fb4db32b74..6c2eb78696a18 100644 --- a/x-pack/plugins/uptime/server/rest_api/monitors/monitor_list.ts +++ b/x-pack/plugins/uptime/server/rest_api/monitors/monitor_list.ts @@ -51,10 +51,7 @@ export const createMonitorListRoute: UMRestApiRouteFactory = (libs) => ({ pageSize, filters, query, - // this is added to make typescript happy, - // this sort of reassignment used to be further downstream but I've moved it here - // because this code is going to be decomissioned soon - statusFilter: statusFilter || undefined, + statusFilter, }); return result; From be3ad062c3274f322233f1dfedacbeadda18a47b Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 21 Sep 2021 12:57:07 -0400 Subject: [PATCH 12/13] Fix broken code style due to prettier update. --- .../uptime/server/rest_api/monitors/monitor_list.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/uptime/server/rest_api/monitors/monitor_list.ts b/x-pack/plugins/uptime/server/rest_api/monitors/monitor_list.ts index 6c2eb78696a18..36bc5a80ef47a 100644 --- a/x-pack/plugins/uptime/server/rest_api/monitors/monitor_list.ts +++ b/x-pack/plugins/uptime/server/rest_api/monitors/monitor_list.ts @@ -28,15 +28,8 @@ export const createMonitorListRoute: UMRestApiRouteFactory = (libs) => ({ tags: ['access:uptime-read'], }, handler: async ({ uptimeEsClient, request, response }): Promise => { - const { - dateRangeStart, - dateRangeEnd, - filters, - pagination, - statusFilter, - pageSize, - query, - } = request.query; + const { dateRangeStart, dateRangeEnd, filters, pagination, statusFilter, pageSize, query } = + request.query; const decodedPagination = pagination ? JSON.parse(decodeURIComponent(pagination)) From c011e767534a6dce0a496647009f583881d301a1 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 21 Sep 2021 17:38:11 -0400 Subject: [PATCH 13/13] Fix import name. --- x-pack/plugins/uptime/public/hooks/use_mapping_check.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/uptime/public/hooks/use_mapping_check.ts b/x-pack/plugins/uptime/public/hooks/use_mapping_check.ts index 88be4080ef502..d8a7e0fac4065 100644 --- a/x-pack/plugins/uptime/public/hooks/use_mapping_check.ts +++ b/x-pack/plugins/uptime/public/hooks/use_mapping_check.ts @@ -6,7 +6,7 @@ */ import { useEffect } from 'react'; -import { useHistory } from 'react-router'; +import { useHistory } from 'react-router-dom'; import { MAPPING_ERROR_ROUTE } from '../../common/constants'; interface EsBadRequestError {