Skip to content

Commit

Permalink
add the exactRoute property to app registration (#69772) (#69897)
Browse files Browse the repository at this point in the history
* add the `exactRoute` property

* add missing doc file

* nits on jsdoc
  • Loading branch information
pgayvallet authored Jun 25, 2020
1 parent 0b23f8f commit bc2e63f
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 38 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-core-public](./kibana-plugin-core-public.md) &gt; [App](./kibana-plugin-core-public.app.md) &gt; [exactRoute](./kibana-plugin-core-public.app.exactroute.md)

## App.exactRoute property

If set to true, the application's route will only be checked against an exact match. Defaults to `false`<!-- -->.

<b>Signature:</b>

```typescript
exactRoute?: boolean;
```

## Example


```ts
core.application.register({
id: 'my_app',
title: 'My App'
exactRoute: true,
mount: () => { ... },
})

// '[basePath]/app/my_app' will be matched
// '[basePath]/app/my_app/some/path' will not be matched

```

Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ export interface App<HistoryLocationState = unknown> extends AppBase
| --- | --- | --- |
| [appRoute](./kibana-plugin-core-public.app.approute.md) | <code>string</code> | Override the application's routing path from <code>/app/${id}</code>. Must be unique across registered applications. Should not include the base path from HTTP. |
| [chromeless](./kibana-plugin-core-public.app.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. |
| [exactRoute](./kibana-plugin-core-public.app.exactroute.md) | <code>boolean</code> | If set to true, the application's route will only be checked against an exact match. Defaults to <code>false</code>. |
| [mount](./kibana-plugin-core-public.app.mount.md) | <code>AppMount&lt;HistoryLocationState&gt; &#124; AppMountDeprecated&lt;HistoryLocationState&gt;</code> | A mount function called when the user navigates to this app's route. May have signature of [AppMount](./kibana-plugin-core-public.appmount.md) or [AppMountDeprecated](./kibana-plugin-core-public.appmountdeprecated.md)<!-- -->. |
2 changes: 2 additions & 0 deletions src/core/public/application/application_service.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ export class ApplicationService {
this.mounters.set(app.id, {
appRoute: app.appRoute!,
appBasePath: basePath.prepend(app.appRoute!),
exactRoute: app.exactRoute ?? false,
mount: wrapMount(plugin, app),
unmountBeforeMounting: false,
legacy: false,
Expand Down Expand Up @@ -236,6 +237,7 @@ export class ApplicationService {
this.mounters.set(app.id, {
appRoute,
appBasePath,
exactRoute: false,
mount,
unmountBeforeMounting: true,
legacy: true,
Expand Down
100 changes: 62 additions & 38 deletions src/core/public/application/integration_tests/router.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import { ScopedHistory } from '../scoped_history';
describe('AppRouter', () => {
let mounters: MockedMounterMap<EitherApp>;
let globalHistory: History;
let appStatuses$: BehaviorSubject<Map<string, AppStatus>>;
let update: ReturnType<typeof createRenderer>;
let scopedAppHistory: History;

Expand All @@ -53,6 +52,17 @@ describe('AppRouter', () => {
);
};

const createMountersRenderer = () =>
createRenderer(
<AppRouter
history={globalHistory}
mounters={mockMountersToMounters()}
appStatuses$={mountersToAppStatus$()}
setAppLeaveHandler={noop}
setIsMounting={noop}
/>
);

beforeEach(() => {
mounters = new Map([
createAppMounter({ appId: 'app1', html: '<span>App 1</span>' }),
Expand Down Expand Up @@ -90,16 +100,7 @@ describe('AppRouter', () => {
}),
] as Array<MockedMounterTuple<EitherApp>>);
globalHistory = createMemoryHistory();
appStatuses$ = mountersToAppStatus$();
update = createRenderer(
<AppRouter
history={globalHistory}
mounters={mockMountersToMounters()}
appStatuses$={appStatuses$}
setAppLeaveHandler={noop}
setIsMounting={noop}
/>
);
update = createMountersRenderer();
});

it('calls mount handler and returned unmount function when navigating between apps', async () => {
Expand Down Expand Up @@ -220,15 +221,7 @@ describe('AppRouter', () => {
})
);
globalHistory = createMemoryHistory();
update = createRenderer(
<AppRouter
history={globalHistory}
mounters={mockMountersToMounters()}
appStatuses$={mountersToAppStatus$()}
setAppLeaveHandler={noop}
setIsMounting={noop}
/>
);
update = createMountersRenderer();

await navigate('/fake-login');

Expand All @@ -252,22 +245,61 @@ describe('AppRouter', () => {
})
);
globalHistory = createMemoryHistory();
update = createRenderer(
<AppRouter
history={globalHistory}
mounters={mockMountersToMounters()}
appStatuses$={mountersToAppStatus$()}
setAppLeaveHandler={noop}
setIsMounting={noop}
/>
);
update = createMountersRenderer();

await navigate('/spaces/fake-login');

expect(mounters.get('spaces')!.mounter.mount).toHaveBeenCalled();
expect(mounters.get('login')!.mounter.mount).not.toHaveBeenCalled();
});

it('should mount an exact route app only when the path is an exact match', async () => {
mounters.set(
...createAppMounter({
appId: 'exactApp',
html: '<div>exact app</div>',
exactRoute: true,
appRoute: '/app/exact-app',
})
);

globalHistory = createMemoryHistory();
update = createMountersRenderer();

await navigate('/app/exact-app/some-path');

expect(mounters.get('exactApp')!.mounter.mount).not.toHaveBeenCalled();

await navigate('/app/exact-app');

expect(mounters.get('exactApp')!.mounter.mount).toHaveBeenCalledTimes(1);
});

it('should mount an an app with a route nested in an exact route app', async () => {
mounters.set(
...createAppMounter({
appId: 'exactApp',
html: '<div>exact app</div>',
exactRoute: true,
appRoute: '/app/exact-app',
})
);
mounters.set(
...createAppMounter({
appId: 'nestedApp',
html: '<div>nested app</div>',
appRoute: '/app/exact-app/another-app',
})
);
globalHistory = createMemoryHistory();
update = createMountersRenderer();

await navigate('/app/exact-app/another-app');

expect(mounters.get('exactApp')!.mounter.mount).not.toHaveBeenCalled();
expect(mounters.get('nestedApp')!.mounter.mount).toHaveBeenCalledTimes(1);
});

it('should not remount when changing pages within app', async () => {
const { mounter, unmount } = mounters.get('app1')!;
await navigate('/app/app1/page1');
Expand Down Expand Up @@ -304,15 +336,7 @@ describe('AppRouter', () => {

it('should not remount when when changing pages within app using hash history', async () => {
globalHistory = createHashHistory();
update = createRenderer(
<AppRouter
history={globalHistory}
mounters={mockMountersToMounters()}
appStatuses$={mountersToAppStatus$()}
setAppLeaveHandler={noop}
setIsMounting={noop}
/>
);
update = createMountersRenderer();

const { mounter, unmount } = mounters.get('app1')!;
await navigate('/app/app1/page1');
Expand Down
4 changes: 4 additions & 0 deletions src/core/public/application/integration_tests/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,13 @@ export const createAppMounter = ({
appId,
html = `<div>App ${appId}</div>`,
appRoute = `/app/${appId}`,
exactRoute = false,
extraMountHook,
}: {
appId: string;
html?: string;
appRoute?: string;
exactRoute?: boolean;
extraMountHook?: (params: AppMountParameters) => void;
}): MockedMounterTuple<App> => {
const unmount = jest.fn();
Expand All @@ -62,6 +64,7 @@ export const createAppMounter = ({
appRoute,
appBasePath: appRoute,
legacy: false,
exactRoute,
mount: jest.fn(async (params: AppMountParameters) => {
const { appBasePath: basename, element } = params;
Object.assign(element, {
Expand Down Expand Up @@ -90,6 +93,7 @@ export const createLegacyAppMounter = (
appBasePath: `/app/${appId.split(':')[0]}`,
unmountBeforeMounting: true,
legacy: true,
exactRoute: false,
mount: legacyMount,
},
unmount: jest.fn(),
Expand Down
19 changes: 19 additions & 0 deletions src/core/public/application/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,24 @@ export interface App<HistoryLocationState = unknown> extends AppBase {
* base path from HTTP.
*/
appRoute?: string;

/**
* If set to true, the application's route will only be checked against an exact match. Defaults to `false`.
*
* @example
* ```ts
* core.application.register({
* id: 'my_app',
* title: 'My App'
* exactRoute: true,
* mount: () => { ... },
* })
*
* // '[basePath]/app/my_app' will be matched
* // '[basePath]/app/my_app/some/path' will not be matched
* ```
*/
exactRoute?: boolean;
}

/** @public */
Expand Down Expand Up @@ -569,6 +587,7 @@ export type Mounter<T = App | LegacyApp> = SelectivePartial<
appBasePath: string;
mount: T extends LegacyApp ? LegacyAppMounter : AppMounter;
legacy: boolean;
exactRoute: boolean;
unmountBeforeMounting: T extends LegacyApp ? true : boolean;
},
T extends LegacyApp ? never : 'unmountBeforeMounting'
Expand Down
2 changes: 2 additions & 0 deletions src/core/public/application/ui/app_container.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ describe('AppContainer', () => {
appRoute: '/some-route',
unmountBeforeMounting: false,
legacy: false,
exactRoute: false,
mount: async ({ element }: AppMountParameters) => {
await promise;
const container = document.createElement('div');
Expand Down Expand Up @@ -143,6 +144,7 @@ describe('AppContainer', () => {
appRoute: '/some-route',
unmountBeforeMounting: false,
legacy: false,
exactRoute: false,
mount: async ({ element }: AppMountParameters) => {
await waitPromise;
throw new Error(`Mounting failed!`);
Expand Down
1 change: 1 addition & 0 deletions src/core/public/application/ui/app_router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export const AppRouter: FunctionComponent<Props> = ({
<Route
key={mounter.appRoute}
path={mounter.appRoute}
exact={mounter.exactRoute}
render={({ match: { url } }) => (
<AppContainer
appPath={url}
Expand Down
1 change: 1 addition & 0 deletions src/core/public/public.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ export function __kbnBootstrap__(): void;
export interface App<HistoryLocationState = unknown> extends AppBase {
appRoute?: string;
chromeless?: boolean;
exactRoute?: boolean;
mount: AppMount<HistoryLocationState> | AppMountDeprecated<HistoryLocationState>;
}

Expand Down

0 comments on commit bc2e63f

Please sign in to comment.