mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[APM] Add 404 page (#149471)
- 404 page <img width="1644" alt="Screenshot 2023-01-24 at 6 46 44 PM" src="https://user-images.githubusercontent.com/55978943/214448388-86d97ac9-f246-45f4-a5cb-58c6230faa83.png"> - Shows the error that happened while validating the params instead of a blank page <img width="1630" alt="Screenshot 2023-01-25 at 3 52 06 PM" src="https://user-images.githubusercontent.com/55978943/214689993-e23dd588-9106-40dc-a447-4d20821b1bb2.png"> - Renders known routes <img width="1650" alt="Screenshot 2023-01-24 at 6 46 57 PM" src="https://user-images.githubusercontent.com/55978943/214448567-e2154db5-ff9d-47b8-834c-655b3d904631.png"> --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
82146cbd90
commit
74fc4f99d7
6 changed files with 137 additions and 37 deletions
|
@ -22,6 +22,12 @@ function toReactRouterPath(path: string) {
|
|||
return path.replace(/(?:{([^\/]+)})/g, ':$1');
|
||||
}
|
||||
|
||||
export class NotFoundRouteException extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
export function createRouter<TRoutes extends RouteMap>(routes: TRoutes): Router<TRoutes> {
|
||||
const routesByReactRouterConfig = new Map<ReactRouterConfig, Route>();
|
||||
const reactRouterConfigsByRoute = new Map<Route, ReactRouterConfig>();
|
||||
|
@ -110,6 +116,11 @@ export function createRouter<TRoutes extends RouteMap>(routes: TRoutes): Router<
|
|||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const hasExactMatch = matches.some((match) => match.match.isExact);
|
||||
if (!hasExactMatch) {
|
||||
throw new NotFoundRouteException('No route was matched');
|
||||
}
|
||||
|
||||
return matches.slice(0, matchIndex + 1).map((matchedRoute) => {
|
||||
const route = routesByReactRouterConfig.get(matchedRoute.route);
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
import { last } from 'lodash';
|
||||
import { NotFoundRouteException } from './create_router';
|
||||
import { useMatchRoutes } from './use_match_routes';
|
||||
import { useRouter } from './use_router';
|
||||
|
||||
|
@ -14,7 +15,7 @@ export function useRoutePath() {
|
|||
const lastRouteMatch = last(useMatchRoutes());
|
||||
const router = useRouter();
|
||||
if (!lastRouteMatch) {
|
||||
throw new Error('No route was matched');
|
||||
throw new NotFoundRouteException('No route was matched');
|
||||
}
|
||||
|
||||
return router.getRoutePath(lastRouteMatch.route);
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
describe('404', () => {
|
||||
beforeEach(() => {
|
||||
cy.loginAsViewerUser();
|
||||
});
|
||||
|
||||
it('Shows 404 page', () => {
|
||||
cy.visitKibana('/app/apm/foo');
|
||||
|
||||
cy.contains('Page not found');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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 { NotFoundRouteException } from '@kbn/typed-react-router-config';
|
||||
import { EuiErrorBoundary } from '@elastic/eui';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import React from 'react';
|
||||
import { NotFoundPrompt } from '@kbn/shared-ux-prompt-not-found';
|
||||
import { ApmPluginStartDeps } from '../../plugin';
|
||||
|
||||
export class ApmErrorBoundary extends React.Component<
|
||||
{ children?: React.ReactNode },
|
||||
{ error?: Error },
|
||||
{}
|
||||
> {
|
||||
public state: { error?: Error } = {
|
||||
error: undefined,
|
||||
};
|
||||
|
||||
static getDerivedStateFromError(error: Error) {
|
||||
return { error };
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.error) {
|
||||
return <ErrorWithTemplate error={this.state.error} />;
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
const pageHeader = {
|
||||
pageTitle: 'APM',
|
||||
};
|
||||
|
||||
function ErrorWithTemplate({ error }: { error: Error }) {
|
||||
const { services } = useKibana<ApmPluginStartDeps>();
|
||||
const { observability } = services;
|
||||
|
||||
const ObservabilityPageTemplate = observability.navigation.PageTemplate;
|
||||
|
||||
if (error instanceof NotFoundRouteException) {
|
||||
return (
|
||||
<ObservabilityPageTemplate pageHeader={pageHeader}>
|
||||
<NotFoundPrompt />
|
||||
</ObservabilityPageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ObservabilityPageTemplate pageHeader={pageHeader}>
|
||||
<EuiErrorBoundary>
|
||||
<DummyComponent error={error} />
|
||||
</EuiErrorBoundary>
|
||||
</ObservabilityPageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function DummyComponent({ error }: { error: Error }) {
|
||||
throw error;
|
||||
return <div />;
|
||||
}
|
|
@ -5,23 +5,22 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { euiLightVars, euiDarkVars } from '@kbn/ui-theme';
|
||||
import { RouteRenderer, RouterProvider } from '@kbn/typed-react-router-config';
|
||||
import React from 'react';
|
||||
import { Route } from 'react-router-dom';
|
||||
import { DefaultTheme, ThemeProvider } from 'styled-components';
|
||||
import { APP_WRAPPER_CLASS } from '@kbn/core/public';
|
||||
import {
|
||||
KibanaContextProvider,
|
||||
RedirectAppLinks,
|
||||
useUiSetting$,
|
||||
} from '@kbn/kibana-react-plugin/public';
|
||||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import {
|
||||
HeaderMenuPortal,
|
||||
InspectorContextProvider,
|
||||
} from '@kbn/observability-plugin/public';
|
||||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import { ScrollToTopOnPathChange } from '../app/main/scroll_to_top_on_path_change';
|
||||
import { RouteRenderer, RouterProvider } from '@kbn/typed-react-router-config';
|
||||
import { euiDarkVars, euiLightVars } from '@kbn/ui-theme';
|
||||
import React from 'react';
|
||||
import { Route } from 'react-router-dom';
|
||||
import { DefaultTheme, ThemeProvider } from 'styled-components';
|
||||
import { AnomalyDetectionJobsContextProvider } from '../../context/anomaly_detection_jobs/anomaly_detection_jobs_context';
|
||||
import {
|
||||
ApmPluginContext,
|
||||
|
@ -33,13 +32,15 @@ import { LicenseProvider } from '../../context/license/license_context';
|
|||
import { TimeRangeIdContextProvider } from '../../context/time_range_id/time_range_id_context';
|
||||
import { UrlParamsProvider } from '../../context/url_params_context/url_params_context';
|
||||
import { ApmPluginStartDeps } from '../../plugin';
|
||||
import { ScrollToTopOnPathChange } from '../app/main/scroll_to_top_on_path_change';
|
||||
import { ApmHeaderActionMenu } from '../shared/apm_header_action_menu';
|
||||
import { RedirectWithDefaultDateRange } from '../shared/redirect_with_default_date_range';
|
||||
import { apmRouter } from './apm_route_config';
|
||||
import { TrackPageview } from './track_pageview';
|
||||
import { RedirectWithDefaultEnvironment } from '../shared/redirect_with_default_environment';
|
||||
import { RedirectWithOffset } from '../shared/redirect_with_offset';
|
||||
import { ApmErrorBoundary } from './apm_error_boundary';
|
||||
import { apmRouter } from './apm_route_config';
|
||||
import { RedirectDependenciesToDependenciesInventory } from './home/redirect_dependencies_to_dependencies_inventory';
|
||||
import { TrackPageview } from './track_pageview';
|
||||
|
||||
const storage = new Storage(localStorage);
|
||||
|
||||
|
@ -66,34 +67,36 @@ export function ApmAppRoot({
|
|||
<i18nCore.Context>
|
||||
<TimeRangeIdContextProvider>
|
||||
<RouterProvider history={history} router={apmRouter as any}>
|
||||
<RedirectDependenciesToDependenciesInventory>
|
||||
<RedirectWithDefaultEnvironment>
|
||||
<RedirectWithDefaultDateRange>
|
||||
<RedirectWithOffset>
|
||||
<TrackPageview>
|
||||
<BreadcrumbsContextProvider>
|
||||
<UrlParamsProvider>
|
||||
<LicenseProvider>
|
||||
<AnomalyDetectionJobsContextProvider>
|
||||
<InspectorContextProvider>
|
||||
<ApmThemeProvider>
|
||||
<MountApmHeaderActionMenu />
|
||||
<ApmErrorBoundary>
|
||||
<RedirectDependenciesToDependenciesInventory>
|
||||
<RedirectWithDefaultEnvironment>
|
||||
<RedirectWithDefaultDateRange>
|
||||
<RedirectWithOffset>
|
||||
<TrackPageview>
|
||||
<BreadcrumbsContextProvider>
|
||||
<UrlParamsProvider>
|
||||
<LicenseProvider>
|
||||
<AnomalyDetectionJobsContextProvider>
|
||||
<InspectorContextProvider>
|
||||
<ApmThemeProvider>
|
||||
<MountApmHeaderActionMenu />
|
||||
|
||||
<Route
|
||||
component={ScrollToTopOnPathChange}
|
||||
/>
|
||||
<RouteRenderer />
|
||||
</ApmThemeProvider>
|
||||
</InspectorContextProvider>
|
||||
</AnomalyDetectionJobsContextProvider>
|
||||
</LicenseProvider>
|
||||
</UrlParamsProvider>
|
||||
</BreadcrumbsContextProvider>
|
||||
</TrackPageview>
|
||||
</RedirectWithOffset>
|
||||
</RedirectWithDefaultDateRange>
|
||||
</RedirectWithDefaultEnvironment>
|
||||
</RedirectDependenciesToDependenciesInventory>
|
||||
<Route
|
||||
component={ScrollToTopOnPathChange}
|
||||
/>
|
||||
<RouteRenderer />
|
||||
</ApmThemeProvider>
|
||||
</InspectorContextProvider>
|
||||
</AnomalyDetectionJobsContextProvider>
|
||||
</LicenseProvider>
|
||||
</UrlParamsProvider>
|
||||
</BreadcrumbsContextProvider>
|
||||
</TrackPageview>
|
||||
</RedirectWithOffset>
|
||||
</RedirectWithDefaultDateRange>
|
||||
</RedirectWithDefaultEnvironment>
|
||||
</RedirectDependenciesToDependenciesInventory>
|
||||
</ApmErrorBoundary>
|
||||
</RouterProvider>
|
||||
</TimeRangeIdContextProvider>
|
||||
</i18nCore.Context>
|
||||
|
|
|
@ -76,6 +76,7 @@
|
|||
"@kbn/babel-register",
|
||||
"@kbn/core-saved-objects-migration-server-internal",
|
||||
"@kbn/core-elasticsearch-server",
|
||||
"@kbn/shared-ux-prompt-not-found",
|
||||
"@kbn/core-saved-objects-api-server",
|
||||
],
|
||||
"exclude": [
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue