mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Allow to define and update a defaultPath for applications (#64498)
* add defaultPath to `AppBase` and use it in `navigateToApp` * add removeSlashes util * adapt `toNavLink` to handle defaultPath * update generated doc * codestyle * add FTR test * address comments * add tests
This commit is contained in:
parent
f85b3898f6
commit
aa8cb620bb
16 changed files with 320 additions and 36 deletions
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
defaultPath?: string;
|
||||
```
|
|
@ -18,6 +18,7 @@ export interface AppBase
|
|||
| [capabilities](./kibana-plugin-core-public.appbase.capabilities.md) | <code>Partial<Capabilities></code> | Custom capabilities defined by the app. |
|
||||
| [category](./kibana-plugin-core-public.appbase.category.md) | <code>AppCategory</code> | 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) | <code>boolean</code> | Hide the UI chrome when the application is mounted. Defaults to <code>false</code>. Takes precedence over chrome service visibility settings. |
|
||||
| [defaultPath](./kibana-plugin-core-public.appbase.defaultpath.md) | <code>string</code> | 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 <code>path</code> 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) | <code>string</code> | A EUI iconType that will be used for the app's icon. This icon takes precendence over the <code>icon</code> property. |
|
||||
| [icon](./kibana-plugin-core-public.appbase.icon.md) | <code>string</code> | A URL to an image file used as an icon. Used as a fallback if <code>euiIconType</code> is not provided. |
|
||||
| [id](./kibana-plugin-core-public.appbase.id.md) | <code>string</code> | The unique identifier of the application |
|
||||
|
|
|
@ -9,5 +9,5 @@ Defines the list of fields that can be updated via an [AppUpdater](./kibana-plug
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare type AppUpdatableFields = Pick<AppBase, 'status' | 'navLinkStatus' | 'tooltip'>;
|
||||
export declare type AppUpdatableFields = Pick<AppBase, 'status' | 'navLinkStatus' | 'tooltip' | 'defaultPath'>;
|
||||
```
|
||||
|
|
|
@ -29,5 +29,5 @@ export interface ChromeNavLink
|
|||
| [subUrlBase](./kibana-plugin-core-public.chromenavlink.suburlbase.md) | <code>string</code> | A url base that legacy apps can set to match deep URLs to an application. |
|
||||
| [title](./kibana-plugin-core-public.chromenavlink.title.md) | <code>string</code> | The title of the application. |
|
||||
| [tooltip](./kibana-plugin-core-public.chromenavlink.tooltip.md) | <code>string</code> | A tooltip shown when hovering over an app link. |
|
||||
| [url](./kibana-plugin-core-public.chromenavlink.url.md) | <code>string</code> | A url that legacy apps can set to deep link into their applications. |
|
||||
| [url](./kibana-plugin-core-public.chromenavlink.url.md) | <code>string</code> | The route used to open the [default path](./kibana-plugin-core-public.appbase.defaultpath.md) of an application. If unset, <code>baseUrl</code> will be used instead. |
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
|
|
|
@ -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<AppUpdater>(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);
|
||||
|
||||
|
|
|
@ -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<string, Mounter>, 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);
|
||||
|
|
|
@ -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<AppBase, 'status' | 'navLinkStatus' | 'tooltip'>;
|
||||
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<void>;
|
||||
|
|
71
src/core/public/application/utils.test.ts
Normal file
71
src/core/public/application/utils.test.ts
Normal file
|
@ -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');
|
||||
});
|
||||
});
|
54
src/core/public/application/utils.ts
Normal file
54
src/core/public/application/utils.ts
Normal file
|
@ -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,
|
||||
});
|
||||
};
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -36,6 +36,7 @@ export interface AppBase {
|
|||
capabilities?: Partial<Capabilities>;
|
||||
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<AppBase, 'status' | 'navLinkStatus' | 'tooltip'>;
|
||||
export type AppUpdatableFields = Pick<AppBase, 'status' | 'navLinkStatus' | 'tooltip' | 'defaultPath'>;
|
||||
|
||||
// @public
|
||||
export type AppUpdater = (app: AppBase) => Partial<AppUpdatableFields> | undefined;
|
||||
|
@ -290,7 +291,6 @@ export interface ChromeNavLink {
|
|||
readonly subUrlBase?: string;
|
||||
readonly title: string;
|
||||
readonly tooltip?: string;
|
||||
// @deprecated
|
||||
readonly url?: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue