Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Uptime] Redirect to error page when Heartbeat mappings are missing #110857

Merged
merged 19 commits into from
Sep 23, 2021
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions x-pack/plugins/uptime/common/constants/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,50 @@

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<Props> = ({ esFilters }: Props) => {
const history = useHistory();
const { lastRefresh } = useContext(UptimeRefreshContext);

const { filters: overviewFilters, loading } = useSelector(overviewFiltersSelector);
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();
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/uptime/public/hooks/use_telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { API_URLS } from '../../common/constants';

export enum UptimePage {
Overview = 'Overview',
MappingError = 'MappingError',
Monitor = 'Monitor',
Settings = 'Settings',
Certificates = 'Certificates',
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/uptime/public/pages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
78 changes: 78 additions & 0 deletions x-pack/plugins/uptime/public/pages/mapping_error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* 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 { EuiCode, EuiEmptyPrompt, EuiLink, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';

import React from 'react';

import { useKibana } from '../../../../../src/plugins/kibana_react/public';
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 });

const docLinks = useKibana().services.docLinks;

useBreadcrumbs([
{
text: i18n.translate('xpack.uptime.mappingErrorRoute.breadcrumb', {
defaultMessage: 'Mapping error',
}),
},
]);

return (
<EuiEmptyPrompt
data-test-subj="xpack.uptime.mappingsErrorPage"
iconColor="danger"
iconType="cross"
title={
<EuiTitle>
<h3>
<FormattedMessage
id="xpack.uptime.public.pages.mappingError.title"
defaultMessage="Heartbeat mappings missing"
/>
</h3>
</EuiTitle>
}
body={
<div>
<p>
<FormattedMessage
id="xpack.uptime.public.pages.mappingError.bodyMessage"
defaultMessage="Incorrect mappings detected! Perhaps you forgot to run the heartbeat {setup} command?"
values={{ setup: <EuiCode>setup</EuiCode> }}
/>
</p>
{docLinks && (
<p>
<FormattedMessage
id="xpack.uptime.public.pages.mappingError.bodyDocsLink"
defaultMessage="You can learn how to troubleshoot this issue in the {docsLink}."
values={{
docsLink: (
<EuiLink
href={`${docLinks.ELASTIC_WEBSITE_URL}guide/en/observability/${docLinks.DOC_LINK_VERSION}/troubleshoot-uptime-mapping-issues.html`}
target="_blank"
>
docs
</EuiLink>
),
}}
/>
</p>
)}
</div>
}
/>
);
};
23 changes: 22 additions & 1 deletion x-pack/plugins/uptime/public/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -144,6 +145,26 @@ const Routes: RouteProps[] = [
rightSideItems: [<UptimeDatePicker />],
},
},
{
title: i18n.translate('xpack.uptime.mappingErrorRoute.title', {
defaultMessage: 'Synthetics | mapping error',
}),
path: MAPPING_ERROR_ROUTE,
component: MappingErrorPage,
dataTestSubj: 'uptimeMappingErrorPage',
telemetryId: UptimePage.MappingError,
pageHeader: {
pageTitle: (
<div>
<FormattedMessage
id="xpack.uptime.mappingErrorRoute.pageHeader.title"
defaultMessage="Mapping error"
/>
</div>
),
rightSideItems: [],
},
},
];

const RouteInit: React.FC<Pick<RouteProps, 'path' | 'title' | 'telemetryId'>> = ({
Expand Down
6 changes: 5 additions & 1 deletion x-pack/plugins/uptime/public/state/api/overview_filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,29 @@ export const createGetOverviewFilters: UMRestApiRouteFactory = (libs: UMServerLi
}
}

return await libs.requests.getFilterBar({
uptimeEsClient,
dateRangeStart,
dateRangeEnd,
search: parsedSearch,
filterOptions: objectValuesToArrays<string>({
locations,
ports,
schemes,
tags,
}),
});
try {
return await libs.requests.getFilterBar({
uptimeEsClient,
dateRangeStart,
dateRangeEnd,
search: parsedSearch,
filterOptions: objectValuesToArrays<string>({
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;
}
},
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This route has been , infact this whole API has been removed in our filters API, so i wonder if this is the correct appraoch to handle this kind of error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I overlooked that - a dedicated route may be the way to go. The inclination for this approach is, we don't want to slow down the happy path that 99% of users will experience for the sake of this one other case. While it's a common error, it's rare for people to experience it.

Perhaps we should depend on the filters PR and adjust the logic in this PR to fit the error in that one, since the error should still occur with the new fetch procedure.

13 changes: 13 additions & 0 deletions x-pack/test/functional/apps/uptime/certificates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,27 @@ 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');

const esArchiver = getService('esArchiver');
const es = getService('es');

describe('certificates', function () {
describe('empty certificates', function () {
before(async () => {
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();
Expand All @@ -34,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 });
});
Expand Down
4 changes: 4 additions & 0 deletions x-pack/test/functional/apps/uptime/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,9 @@ export default ({ loadTestFile, getService }: FtrProviderContext) => {
loadTestFile(require.resolve('./ml_anomaly'));
loadTestFile(require.resolve('./feature_controls'));
});

describe('mappings error state', () => {
loadTestFile(require.resolve('./missing_mappings'));
});
});
};
26 changes: 26 additions & 0 deletions x-pack/test/functional/apps/uptime/missing_mappings.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
};
3 changes: 3 additions & 0 deletions x-pack/test/functional/services/uptime/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,5 +104,8 @@ export function UptimeCommonProvider({ getService, getPageObjects }: FtrProvider
await testSubjects.missingOrFail('data-missing');
});
},
async hasMappingsError() {
return testSubjects.exists('xpack.uptime.mappingsErrorPage');
},
};
}