diff --git a/docs/development/core/public/kibana-plugin-core-public.appbase.defaultpath.md b/docs/development/core/public/kibana-plugin-core-public.appbase.defaultpath.md
new file mode 100644
index 0000000000000..51492756ef232
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-core-public.appbase.defaultpath.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [AppBase](./kibana-plugin-core-public.appbase.md) > [defaultPath](./kibana-plugin-core-public.appbase.defaultpath.md)
+
+## AppBase.defaultPath property
+
+Allow to define the default path a user should be directed to when navigating to the app. When defined, this value will be used as a default for the `path` option when calling [navigateToApp](./kibana-plugin-core-public.applicationstart.navigatetoapp.md)\`, and will also be appended to the [application navLink](./kibana-plugin-core-public.chromenavlink.md) in the navigation bar.
+
+Signature:
+
+```typescript
+defaultPath?: string;
+```
diff --git a/docs/development/core/public/kibana-plugin-core-public.appbase.md b/docs/development/core/public/kibana-plugin-core-public.appbase.md
index b73785647f23c..7b624f12ac1df 100644
--- a/docs/development/core/public/kibana-plugin-core-public.appbase.md
+++ b/docs/development/core/public/kibana-plugin-core-public.appbase.md
@@ -18,6 +18,7 @@ export interface AppBase
| [capabilities](./kibana-plugin-core-public.appbase.capabilities.md) | Partial<Capabilities>
| Custom capabilities defined by the app. |
| [category](./kibana-plugin-core-public.appbase.category.md) | AppCategory
| The category definition of the product See [AppCategory](./kibana-plugin-core-public.appcategory.md) See DEFAULT\_APP\_CATEGORIES for more reference |
| [chromeless](./kibana-plugin-core-public.appbase.chromeless.md) | boolean
| Hide the UI chrome when the application is mounted. Defaults to false
. Takes precedence over chrome service visibility settings. |
+| [defaultPath](./kibana-plugin-core-public.appbase.defaultpath.md) | string
| Allow to define the default path a user should be directed to when navigating to the app. When defined, this value will be used as a default for the path
option when calling [navigateToApp](./kibana-plugin-core-public.applicationstart.navigatetoapp.md)\`, and will also be appended to the [application navLink](./kibana-plugin-core-public.chromenavlink.md) in the navigation bar. |
| [euiIconType](./kibana-plugin-core-public.appbase.euiicontype.md) | string
| A EUI iconType that will be used for the app's icon. This icon takes precendence over the icon
property. |
| [icon](./kibana-plugin-core-public.appbase.icon.md) | string
| A URL to an image file used as an icon. Used as a fallback if euiIconType
is not provided. |
| [id](./kibana-plugin-core-public.appbase.id.md) | string
| The unique identifier of the application |
diff --git a/docs/development/core/public/kibana-plugin-core-public.appupdatablefields.md b/docs/development/core/public/kibana-plugin-core-public.appupdatablefields.md
index cdf9171a46aed..3d8b5d115c8a2 100644
--- a/docs/development/core/public/kibana-plugin-core-public.appupdatablefields.md
+++ b/docs/development/core/public/kibana-plugin-core-public.appupdatablefields.md
@@ -9,5 +9,5 @@ Defines the list of fields that can be updated via an [AppUpdater](./kibana-plug
Signature:
```typescript
-export declare type AppUpdatableFields = Pick;
+export declare type AppUpdatableFields = Pick;
```
diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.md b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.md
index 1cc1a1194a537..a9fabb38df869 100644
--- a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.md
+++ b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.md
@@ -29,5 +29,5 @@ export interface ChromeNavLink
| [subUrlBase](./kibana-plugin-core-public.chromenavlink.suburlbase.md) | string
| A url base that legacy apps can set to match deep URLs to an application. |
| [title](./kibana-plugin-core-public.chromenavlink.title.md) | string
| The title of the application. |
| [tooltip](./kibana-plugin-core-public.chromenavlink.tooltip.md) | string
| A tooltip shown when hovering over an app link. |
-| [url](./kibana-plugin-core-public.chromenavlink.url.md) | string
| A url that legacy apps can set to deep link into their applications. |
+| [url](./kibana-plugin-core-public.chromenavlink.url.md) | string
| The route used to open the [default path](./kibana-plugin-core-public.appbase.defaultpath.md) of an application. If unset, baseUrl
will be used instead. |
diff --git a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.url.md b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.url.md
index 0c415ed1a7fad..1e0b890015993 100644
--- a/docs/development/core/public/kibana-plugin-core-public.chromenavlink.url.md
+++ b/docs/development/core/public/kibana-plugin-core-public.chromenavlink.url.md
@@ -4,11 +4,7 @@
## ChromeNavLink.url property
-> Warning: This API is now obsolete.
->
->
-
-A url that legacy apps can set to deep link into their applications.
+The route used to open the [default path](./kibana-plugin-core-public.appbase.defaultpath.md) of an application. If unset, `baseUrl` will be used instead.
Signature:
diff --git a/src/core/public/application/application_service.test.ts b/src/core/public/application/application_service.test.ts
index c25918c6b7328..e29837aecb125 100644
--- a/src/core/public/application/application_service.test.ts
+++ b/src/core/public/application/application_service.test.ts
@@ -87,7 +87,7 @@ describe('#setup()', () => {
).toThrowErrorMatchingInlineSnapshot(`"Applications cannot be registered after \\"setup\\""`);
});
- it('allows to register a statusUpdater for the application', async () => {
+ it('allows to register an AppUpdater for the application', async () => {
const setup = service.setup(setupDeps);
const pluginId = Symbol('plugin');
@@ -118,6 +118,7 @@ describe('#setup()', () => {
updater$.next(app => ({
status: AppStatus.inaccessible,
tooltip: 'App inaccessible due to reason',
+ defaultPath: 'foo/bar',
}));
applications = await applications$.pipe(take(1)).toPromise();
@@ -128,6 +129,7 @@ describe('#setup()', () => {
legacy: false,
navLinkStatus: AppNavLinkStatus.default,
status: AppStatus.inaccessible,
+ defaultPath: 'foo/bar',
tooltip: 'App inaccessible due to reason',
})
);
@@ -209,7 +211,7 @@ describe('#setup()', () => {
});
});
- describe('registerAppStatusUpdater', () => {
+ describe('registerAppUpdater', () => {
it('updates status fields', async () => {
const setup = service.setup(setupDeps);
@@ -413,6 +415,36 @@ describe('#setup()', () => {
})
);
});
+
+ it('allows to update the basePath', async () => {
+ const setup = service.setup(setupDeps);
+
+ const pluginId = Symbol('plugin');
+ setup.register(pluginId, createApp({ id: 'app1' }));
+
+ const updater = new BehaviorSubject(app => ({}));
+ setup.registerAppUpdater(updater);
+
+ const start = await service.start(startDeps);
+ await start.navigateToApp('app1');
+ expect(MockHistory.push).toHaveBeenCalledWith('/app/app1', undefined);
+ MockHistory.push.mockClear();
+
+ updater.next(app => ({ defaultPath: 'default-path' }));
+ await start.navigateToApp('app1');
+ expect(MockHistory.push).toHaveBeenCalledWith('/app/app1/default-path', undefined);
+ MockHistory.push.mockClear();
+
+ updater.next(app => ({ defaultPath: 'another-path' }));
+ await start.navigateToApp('app1');
+ expect(MockHistory.push).toHaveBeenCalledWith('/app/app1/another-path', undefined);
+ MockHistory.push.mockClear();
+
+ updater.next(app => ({}));
+ await start.navigateToApp('app1');
+ expect(MockHistory.push).toHaveBeenCalledWith('/app/app1', undefined);
+ MockHistory.push.mockClear();
+ });
});
it("`registerMountContext` calls context container's registerContext", () => {
@@ -676,6 +708,57 @@ describe('#start()', () => {
expect(MockHistory.push).toHaveBeenCalledWith('/custom/path#/hash/router/path', undefined);
});
+ it('preserves trailing slash when path contains a hash', async () => {
+ const { register } = service.setup(setupDeps);
+
+ register(Symbol(), createApp({ id: 'app2', appRoute: '/custom/app-path' }));
+
+ const { navigateToApp } = await service.start(startDeps);
+ await navigateToApp('app2', { path: '#/' });
+ expect(MockHistory.push).toHaveBeenCalledWith('/custom/app-path#/', undefined);
+ MockHistory.push.mockClear();
+
+ await navigateToApp('app2', { path: '#/foo/bar/' });
+ expect(MockHistory.push).toHaveBeenCalledWith('/custom/app-path#/foo/bar/', undefined);
+ MockHistory.push.mockClear();
+
+ await navigateToApp('app2', { path: '/path#/' });
+ expect(MockHistory.push).toHaveBeenCalledWith('/custom/app-path/path#/', undefined);
+ MockHistory.push.mockClear();
+
+ await navigateToApp('app2', { path: '/path#/hash/' });
+ expect(MockHistory.push).toHaveBeenCalledWith('/custom/app-path/path#/hash/', undefined);
+ MockHistory.push.mockClear();
+
+ await navigateToApp('app2', { path: '/path/' });
+ expect(MockHistory.push).toHaveBeenCalledWith('/custom/app-path/path', undefined);
+ MockHistory.push.mockClear();
+ });
+
+ it('appends the defaultPath when the path parameter is not specified', async () => {
+ const { register } = service.setup(setupDeps);
+
+ register(Symbol(), createApp({ id: 'app1', defaultPath: 'default/path' }));
+ register(
+ Symbol(),
+ createApp({ id: 'app2', appRoute: '/custom-app-path', defaultPath: '/my-base' })
+ );
+
+ const { navigateToApp } = await service.start(startDeps);
+
+ await navigateToApp('app1', { path: 'defined-path' });
+ expect(MockHistory.push).toHaveBeenCalledWith('/app/app1/defined-path', undefined);
+
+ await navigateToApp('app1', {});
+ expect(MockHistory.push).toHaveBeenCalledWith('/app/app1/default/path', undefined);
+
+ await navigateToApp('app2', { path: 'defined-path' });
+ expect(MockHistory.push).toHaveBeenCalledWith('/custom-app-path/defined-path', undefined);
+
+ await navigateToApp('app2', {});
+ expect(MockHistory.push).toHaveBeenCalledWith('/custom-app-path/my-base', undefined);
+ });
+
it('includes state if specified', async () => {
const { register } = service.setup(setupDeps);
diff --git a/src/core/public/application/application_service.tsx b/src/core/public/application/application_service.tsx
index 1c9492d81c7f6..bafa1932e5e92 100644
--- a/src/core/public/application/application_service.tsx
+++ b/src/core/public/application/application_service.tsx
@@ -46,6 +46,7 @@ import {
Mounter,
} from './types';
import { getLeaveAction, isConfirmAction } from './application_leave';
+import { appendAppPath } from './utils';
interface SetupDeps {
context: ContextSetup;
@@ -81,13 +82,7 @@ const getAppUrl = (mounters: Map, appId: string, path: string =
const appBasePath = mounters.get(appId)?.appRoute
? `/${mounters.get(appId)!.appRoute}`
: `/app/${appId}`;
-
- // Only preppend slash if not a hash or query path
- path = path.startsWith('#') || path.startsWith('?') ? path : `/${path}`;
-
- return `${appBasePath}${path}`
- .replace(/\/{2,}/g, '/') // Remove duplicate slashes
- .replace(/\/$/, ''); // Remove trailing slash
+ return appendAppPath(appBasePath, path);
};
const allApplicationsFilter = '__ALL__';
@@ -290,6 +285,9 @@ export class ApplicationService {
},
navigateToApp: async (appId, { path, state }: { path?: string; state?: any } = {}) => {
if (await this.shouldNavigate(overlays)) {
+ if (path === undefined) {
+ path = applications$.value.get(appId)?.defaultPath;
+ }
this.appLeaveHandlers.delete(this.currentAppId$.value!);
this.navigate!(getAppUrl(availableMounters, appId, path), state);
this.currentAppId$.next(appId);
diff --git a/src/core/public/application/types.ts b/src/core/public/application/types.ts
index 318afb652999e..0734e178033e2 100644
--- a/src/core/public/application/types.ts
+++ b/src/core/public/application/types.ts
@@ -66,6 +66,13 @@ export interface AppBase {
*/
navLinkStatus?: AppNavLinkStatus;
+ /**
+ * Allow to define the default path a user should be directed to when navigating to the app.
+ * When defined, this value will be used as a default for the `path` option when calling {@link ApplicationStart.navigateToApp | navigateToApp}`,
+ * and will also be appended to the {@link ChromeNavLink | application navLink} in the navigation bar.
+ */
+ defaultPath?: string;
+
/**
* An {@link AppUpdater} observable that can be used to update the application {@link AppUpdatableFields} at runtime.
*
@@ -187,7 +194,10 @@ export enum AppNavLinkStatus {
* Defines the list of fields that can be updated via an {@link AppUpdater}.
* @public
*/
-export type AppUpdatableFields = Pick;
+export type AppUpdatableFields = Pick<
+ AppBase,
+ 'status' | 'navLinkStatus' | 'tooltip' | 'defaultPath'
+>;
/**
* Updater for applications.
@@ -642,7 +652,8 @@ export interface ApplicationStart {
* Navigate to a given app
*
* @param appId
- * @param options.path - optional path inside application to deep link to
+ * @param options.path - optional path inside application to deep link to.
+ * If undefined, will use {@link AppBase.defaultPath | the app's default path}` as default.
* @param options.state - optional state to forward to the application
*/
navigateToApp(appId: string, options?: { path?: string; state?: any }): Promise;
diff --git a/src/core/public/application/utils.test.ts b/src/core/public/application/utils.test.ts
new file mode 100644
index 0000000000000..7ed0919f88c61
--- /dev/null
+++ b/src/core/public/application/utils.test.ts
@@ -0,0 +1,71 @@
+/*
+ * 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 { removeSlashes, appendAppPath } from './utils';
+
+describe('removeSlashes', () => {
+ it('only removes duplicates by default', () => {
+ expect(removeSlashes('/some//url//to//')).toEqual('/some/url/to/');
+ expect(removeSlashes('some/////other//url')).toEqual('some/other/url');
+ });
+
+ it('remove trailing slash when `trailing` is true', () => {
+ expect(removeSlashes('/some//url//to//', { trailing: true })).toEqual('/some/url/to');
+ });
+
+ it('remove leading slash when `leading` is true', () => {
+ expect(removeSlashes('/some//url//to//', { leading: true })).toEqual('some/url/to/');
+ });
+
+ it('does not removes duplicates when `duplicates` is false', () => {
+ expect(removeSlashes('/some//url//to/', { leading: true, duplicates: false })).toEqual(
+ 'some//url//to/'
+ );
+ expect(removeSlashes('/some//url//to/', { trailing: true, duplicates: false })).toEqual(
+ '/some//url//to'
+ );
+ });
+
+ it('accept mixed options', () => {
+ expect(
+ removeSlashes('/some//url//to/', { leading: true, duplicates: false, trailing: true })
+ ).toEqual('some//url//to');
+ expect(
+ removeSlashes('/some//url//to/', { leading: true, duplicates: true, trailing: true })
+ ).toEqual('some/url/to');
+ });
+});
+
+describe('appendAppPath', () => {
+ it('appends the appBasePath with given path', () => {
+ expect(appendAppPath('/app/my-app', '/some-path')).toEqual('/app/my-app/some-path');
+ expect(appendAppPath('/app/my-app/', 'some-path')).toEqual('/app/my-app/some-path');
+ expect(appendAppPath('/app/my-app', 'some-path')).toEqual('/app/my-app/some-path');
+ expect(appendAppPath('/app/my-app', '')).toEqual('/app/my-app');
+ });
+
+ it('preserves the trailing slash only if included in the hash', () => {
+ expect(appendAppPath('/app/my-app', '/some-path/')).toEqual('/app/my-app/some-path');
+ expect(appendAppPath('/app/my-app', '/some-path#/')).toEqual('/app/my-app/some-path#/');
+ expect(appendAppPath('/app/my-app', '/some-path#/hash/')).toEqual(
+ '/app/my-app/some-path#/hash/'
+ );
+ expect(appendAppPath('/app/my-app', '/some-path#/hash')).toEqual('/app/my-app/some-path#/hash');
+ });
+});
diff --git a/src/core/public/application/utils.ts b/src/core/public/application/utils.ts
new file mode 100644
index 0000000000000..048f195fe1223
--- /dev/null
+++ b/src/core/public/application/utils.ts
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+/**
+ * Utility to remove trailing, leading or duplicate slashes.
+ * By default will only remove duplicates.
+ */
+export const removeSlashes = (
+ url: string,
+ {
+ trailing = false,
+ leading = false,
+ duplicates = true,
+ }: { trailing?: boolean; leading?: boolean; duplicates?: boolean } = {}
+): string => {
+ if (duplicates) {
+ url = url.replace(/\/{2,}/g, '/');
+ }
+ if (trailing) {
+ url = url.replace(/\/$/, '');
+ }
+ if (leading) {
+ url = url.replace(/^\//, '');
+ }
+ return url;
+};
+
+export const appendAppPath = (appBasePath: string, path: string = '') => {
+ // Only prepend slash if not a hash or query path
+ path = path === '' || path.startsWith('#') || path.startsWith('?') ? path : `/${path}`;
+ // Do not remove trailing slash when in hashbang
+ const removeTrailing = path.indexOf('#') === -1;
+ return removeSlashes(`${appBasePath}${path}`, {
+ trailing: removeTrailing,
+ duplicates: true,
+ leading: false,
+ });
+};
diff --git a/src/core/public/chrome/nav_links/nav_link.ts b/src/core/public/chrome/nav_links/nav_link.ts
index d0ef2aeb265fe..fb2972735c2b7 100644
--- a/src/core/public/chrome/nav_links/nav_link.ts
+++ b/src/core/public/chrome/nav_links/nav_link.ts
@@ -44,6 +44,12 @@ export interface ChromeNavLink {
*/
readonly baseUrl: string;
+ /**
+ * The route used to open the {@link AppBase.defaultPath | default path } of an application.
+ * If unset, `baseUrl` will be used instead.
+ */
+ readonly url?: string;
+
/**
* An ordinal used to sort nav links relative to one another for display.
*/
@@ -99,18 +105,6 @@ export interface ChromeNavLink {
*/
readonly linkToLastSubUrl?: boolean;
- /**
- * A url that legacy apps can set to deep link into their applications.
- *
- * @internalRemarks
- * Currently used by the "lastSubUrl" feature legacy/ui/chrome. This should
- * be removed once the ApplicationService is implemented and mounting apps. At that
- * time, each app can handle opening to the previous location when they are mounted.
- *
- * @deprecated
- */
- readonly url?: string;
-
/**
* Indicates whether or not this app is currently on the screen.
*
diff --git a/src/core/public/chrome/nav_links/to_nav_link.test.ts b/src/core/public/chrome/nav_links/to_nav_link.test.ts
index 23fdabe0f3430..4c319873af804 100644
--- a/src/core/public/chrome/nav_links/to_nav_link.test.ts
+++ b/src/core/public/chrome/nav_links/to_nav_link.test.ts
@@ -85,6 +85,38 @@ describe('toNavLink', () => {
expect(link.properties.baseUrl).toEqual('http://localhost/base-path/my-route/my-path');
});
+ it('generates the `url` property', () => {
+ let link = toNavLink(
+ app({
+ appRoute: '/my-route/my-path',
+ }),
+ basePath
+ );
+ expect(link.properties.url).toEqual('http://localhost/base-path/my-route/my-path');
+
+ link = toNavLink(
+ app({
+ appRoute: '/my-route/my-path',
+ defaultPath: 'some/default/path',
+ }),
+ basePath
+ );
+ expect(link.properties.url).toEqual(
+ 'http://localhost/base-path/my-route/my-path/some/default/path'
+ );
+ });
+
+ it('does not generate `url` for legacy app', () => {
+ const link = toNavLink(
+ legacyApp({
+ appUrl: '/my-legacy-app/#foo',
+ defaultPath: '/some/default/path',
+ }),
+ basePath
+ );
+ expect(link.properties.url).toBeUndefined();
+ });
+
it('uses appUrl when converting legacy applications', () => {
expect(
toNavLink(
diff --git a/src/core/public/chrome/nav_links/to_nav_link.ts b/src/core/public/chrome/nav_links/to_nav_link.ts
index 18e4b7b26b6ba..f79b1df77f8e1 100644
--- a/src/core/public/chrome/nav_links/to_nav_link.ts
+++ b/src/core/public/chrome/nav_links/to_nav_link.ts
@@ -20,9 +20,11 @@
import { App, AppNavLinkStatus, AppStatus, LegacyApp } from '../../application';
import { IBasePath } from '../../http';
import { NavLinkWrapper } from './nav_link';
+import { appendAppPath } from '../../application/utils';
export function toNavLink(app: App | LegacyApp, basePath: IBasePath): NavLinkWrapper {
const useAppStatus = app.navLinkStatus === AppNavLinkStatus.default;
+ const baseUrl = isLegacyApp(app) ? basePath.prepend(app.appUrl) : basePath.prepend(app.appRoute!);
return new NavLinkWrapper({
...app,
hidden: useAppStatus
@@ -30,9 +32,12 @@ export function toNavLink(app: App | LegacyApp, basePath: IBasePath): NavLinkWra
: app.navLinkStatus === AppNavLinkStatus.hidden,
disabled: useAppStatus ? false : app.navLinkStatus === AppNavLinkStatus.disabled,
legacy: isLegacyApp(app),
- baseUrl: isLegacyApp(app)
- ? relativeToAbsolute(basePath.prepend(app.appUrl))
- : relativeToAbsolute(basePath.prepend(app.appRoute!)),
+ baseUrl: relativeToAbsolute(baseUrl),
+ ...(isLegacyApp(app)
+ ? {}
+ : {
+ url: relativeToAbsolute(appendAppPath(baseUrl, app.defaultPath)),
+ }),
});
}
diff --git a/src/core/public/chrome/ui/header/nav_link.tsx b/src/core/public/chrome/ui/header/nav_link.tsx
index 52b59c53b658c..d97ef477c2ee0 100644
--- a/src/core/public/chrome/ui/header/nav_link.tsx
+++ b/src/core/public/chrome/ui/header/nav_link.tsx
@@ -53,7 +53,7 @@ export function euiNavLink(
order,
tooltip,
} = navLink;
- let href = navLink.baseUrl;
+ let href = navLink.url ?? navLink.baseUrl;
if (legacy) {
href = url && !active ? url : baseUrl;
diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md
index b92bb209d2607..af06b207889c2 100644
--- a/src/core/public/public.api.md
+++ b/src/core/public/public.api.md
@@ -36,6 +36,7 @@ export interface AppBase {
capabilities?: Partial;
category?: AppCategory;
chromeless?: boolean;
+ defaultPath?: string;
euiIconType?: string;
icon?: string;
id: string;
@@ -168,7 +169,7 @@ export enum AppStatus {
export type AppUnmount = () => void;
// @public
-export type AppUpdatableFields = Pick;
+export type AppUpdatableFields = Pick;
// @public
export type AppUpdater = (app: AppBase) => Partial | undefined;
@@ -290,7 +291,6 @@ export interface ChromeNavLink {
readonly subUrlBase?: string;
readonly title: string;
readonly tooltip?: string;
- // @deprecated
readonly url?: string;
}
diff --git a/test/plugin_functional/test_suites/core_plugins/application_status.ts b/test/plugin_functional/test_suites/core_plugins/application_status.ts
index b6d13a5604011..c384e41851e15 100644
--- a/test/plugin_functional/test_suites/core_plugins/application_status.ts
+++ b/test/plugin_functional/test_suites/core_plugins/application_status.ts
@@ -17,6 +17,7 @@
* under the License.
*/
+import url from 'url';
import expect from '@kbn/expect';
import {
AppNavLinkStatus,
@@ -26,6 +27,15 @@ import {
import { PluginFunctionalProviderContext } from '../../services';
import '../../plugins/core_app_status/public/types';
+const getKibanaUrl = (pathname?: string, search?: string) =>
+ url.format({
+ protocol: 'http:',
+ hostname: process.env.TEST_KIBANA_HOST || 'localhost',
+ port: process.env.TEST_KIBANA_PORT || '5620',
+ pathname,
+ search,
+ });
+
// eslint-disable-next-line import/no-default-export
export default function({ getService, getPageObjects }: PluginFunctionalProviderContext) {
const PageObjects = getPageObjects(['common']);
@@ -97,6 +107,22 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider
expect(await testSubjects.exists('appStatusApp')).to.eql(true);
});
+ it('allows to change the defaultPath of an application', async () => {
+ let link = await appsMenu.getLink('App Status');
+ expect(link!.href).to.eql(getKibanaUrl('/app/app_status'));
+
+ await setAppStatus({
+ defaultPath: '/arbitrary/path',
+ });
+
+ link = await appsMenu.getLink('App Status');
+ expect(link!.href).to.eql(getKibanaUrl('/app/app_status/arbitrary/path'));
+
+ await navigateToApp('app_status');
+ expect(await testSubjects.exists('appStatusApp')).to.eql(true);
+ expect(await browser.getCurrentUrl()).to.eql(getKibanaUrl('/app/app_status/arbitrary/path'));
+ });
+
it('can change the state of the currently mounted app', async () => {
await setAppStatus({
status: AppStatus.accessible,