diff --git a/src/plugins/dashboard/public/application/dashboard_router.tsx b/src/plugins/dashboard/public/application/dashboard_router.tsx
index 1ea6355b9c558..baa7ffc5b8de3 100644
--- a/src/plugins/dashboard/public/application/dashboard_router.tsx
+++ b/src/plugins/dashboard/public/application/dashboard_router.tsx
@@ -41,6 +41,7 @@ import {
PluginInitializerContext,
ScopedHistory,
} from '../services/core';
+import { DashboardNoMatch } from './listing/dashboard_no_match';
export const dashboardUrlParams = {
showTopMenu: 'show-top-menu',
@@ -77,6 +78,7 @@ export async function mountApp({
const {
navigation,
savedObjects,
+ urlForwarding,
data: dataStart,
share: shareStart,
embeddable: embeddableStart,
@@ -88,6 +90,7 @@ export async function mountApp({
navigation,
onAppLeave,
savedObjects,
+ urlForwarding,
usageCollection,
core: coreStart,
data: dataStart,
@@ -180,6 +183,10 @@ export async function mountApp({
);
};
+ const renderNoMatch = (routeProps: RouteComponentProps) => {
+ return ;
+ };
+
// make sure the index pattern list is up to date
await dataStart.indexPatterns.clearCache();
@@ -202,9 +209,10 @@ export async function mountApp({
render={renderDashboard}
/>
-
+
+
diff --git a/src/plugins/dashboard/public/application/listing/dashboard_listing.test.tsx b/src/plugins/dashboard/public/application/listing/dashboard_listing.test.tsx
index 3aee05554b0d9..8172be46e9f3a 100644
--- a/src/plugins/dashboard/public/application/listing/dashboard_listing.test.tsx
+++ b/src/plugins/dashboard/public/application/listing/dashboard_listing.test.tsx
@@ -39,6 +39,7 @@ import { dataPluginMock } from '../../../../data/public/mocks';
import { chromeServiceMock, coreMock } from '../../../../../core/public/mocks';
import { I18nProvider } from '@kbn/i18n/react';
import React from 'react';
+import { UrlForwardingStart } from '../../../../url_forwarding/public';
function makeDefaultServices(): DashboardAppServices {
const core = coreMock.createStart();
@@ -71,6 +72,7 @@ function makeDefaultServices(): DashboardAppServices {
scopedHistory: () => ({} as ScopedHistory),
savedQueryService: {} as SavedQueryService,
setHeaderActionMenu: (mountPoint) => {},
+ urlForwarding: {} as UrlForwardingStart,
uiSettings: {} as IUiSettingsClient,
restorePreviousUrl: () => {},
onAppLeave: (handler) => {},
diff --git a/src/plugins/dashboard/public/application/listing/dashboard_no_match.tsx b/src/plugins/dashboard/public/application/listing/dashboard_no_match.tsx
new file mode 100644
index 0000000000000..a0f13af92ff77
--- /dev/null
+++ b/src/plugins/dashboard/public/application/listing/dashboard_no_match.tsx
@@ -0,0 +1,76 @@
+/*
+ * 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 React, { useEffect } from 'react';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiCallOut } from '@elastic/eui';
+
+import { RouteComponentProps } from 'react-router-dom';
+import { useKibana, toMountPoint } from '../../services/kibana_react';
+import { DashboardAppServices } from '../types';
+import { DashboardConstants } from '../..';
+
+let bannerId: string | undefined;
+
+export const DashboardNoMatch = ({ history }: { history: RouteComponentProps['history'] }) => {
+ const { services } = useKibana();
+
+ useEffect(() => {
+ services.restorePreviousUrl();
+
+ const { navigated } = services.urlForwarding.navigateToLegacyKibanaUrl(
+ history.location.pathname
+ );
+
+ if (!navigated) {
+ const bannerMessage = i18n.translate('dashboard.noMatchRoute.bannerTitleText', {
+ defaultMessage: 'Page not found',
+ });
+
+ bannerId = services.core.overlays.banners.replace(
+ bannerId,
+ toMountPoint(
+
+
+
+
+
+ )
+ );
+
+ // hide the message after the user has had a chance to acknowledge it -- so it doesn't permanently stick around
+ setTimeout(() => {
+ if (bannerId) {
+ services.core.overlays.banners.remove(bannerId);
+ }
+ }, 15000);
+
+ history.replace(DashboardConstants.LANDING_PAGE_PATH);
+ }
+ }, [services, history]);
+
+ return null;
+};
diff --git a/src/plugins/dashboard/public/application/types.ts b/src/plugins/dashboard/public/application/types.ts
index d1caaa349d80b..75620fd73360d 100644
--- a/src/plugins/dashboard/public/application/types.ts
+++ b/src/plugins/dashboard/public/application/types.ts
@@ -33,6 +33,7 @@ import { NavigationPublicPluginStart } from '../services/navigation';
import { SavedObjectsTaggingApi } from '../services/saved_objects_tagging_oss';
import { DataPublicPluginStart, IndexPatternsContract } from '../services/data';
import { SavedObjectLoader, SavedObjectsStart } from '../services/saved_objects';
+import { UrlForwardingStart } from '../../../url_forwarding/public';
export type DashboardRedirect = (props: RedirectToProps) => void;
export type RedirectToProps =
@@ -75,6 +76,7 @@ export interface DashboardAppServices {
uiSettings: IUiSettingsClient;
restorePreviousUrl: () => void;
savedObjects: SavedObjectsStart;
+ urlForwarding: UrlForwardingStart;
savedDashboards: SavedObjectLoader;
scopedHistory: () => ScopedHistory;
indexPatterns: IndexPatternsContract;
diff --git a/test/functional/apps/dashboard/legacy_urls.ts b/test/functional/apps/dashboard/legacy_urls.ts
index 6bb8d808e8daa..2a30bbf5e1f0a 100644
--- a/test/functional/apps/dashboard/legacy_urls.ts
+++ b/test/functional/apps/dashboard/legacy_urls.ts
@@ -92,6 +92,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.visualize.clickMarkdownWidget();
await PageObjects.visEditor.setMarkdownTxt(`[abc](#/dashboard/${testDashboardId})`);
await PageObjects.visEditor.clickGo();
+
+ await PageObjects.visualize.saveVisualizationExpectSuccess('legacy url markdown');
+
(await find.byLinkText('abc')).click();
await PageObjects.header.waitUntilLoadingHasFinished();
@@ -109,6 +112,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.visEditor.expectMarkdownTextArea();
await browser.goForward();
});
+
+ it('resolves markdown link from dashboard', async () => {
+ await PageObjects.common.navigateToApp('dashboard');
+ await PageObjects.dashboard.clickNewDashboard();
+ await dashboardAddPanel.addVisualization('legacy url markdown');
+ (await find.byLinkText('abc')).click();
+ await PageObjects.header.waitUntilLoadingHasFinished();
+ await PageObjects.timePicker.setDefaultDataRange();
+
+ await PageObjects.dashboard.waitForRenderComplete();
+ await pieChart.expectPieSliceCount(5);
+ });
});
});
}