Skip to content

Commit

Permalink
Add application deep links to global search (elastic#83380)
Browse files Browse the repository at this point in the history
  • Loading branch information
joshdover committed Nov 30, 2020
1 parent 9a6acb4 commit 9913ad7
Show file tree
Hide file tree
Showing 21 changed files with 503 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ exactRoute?: boolean;
```ts
core.application.register({
id: 'my_app',
title: 'My App'
title: 'My App',
exactRoute: true,
mount: () => { ... },
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface App<HistoryLocationState = unknown>
| [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)<!-- -->. |
| [navLinkStatus](./kibana-plugin-core-public.app.navlinkstatus.md) | <code>AppNavLinkStatus</code> | The initial status of the application's navLink. Defaulting to <code>visible</code> if <code>status</code> is <code>accessible</code> and <code>hidden</code> if status is <code>inaccessible</code> See [AppNavLinkStatus](./kibana-plugin-core-public.appnavlinkstatus.md) |
| [order](./kibana-plugin-core-public.app.order.md) | <code>number</code> | An ordinal used to sort nav links relative to one another for display. |
| [searchDeepLinks](./kibana-plugin-core-public.app.searchdeeplinks.md) | <code>AppSearchDeepLink[]</code> | Array of links that represent secondary in-app locations for the app. |
| [status](./kibana-plugin-core-public.app.status.md) | <code>AppStatus</code> | The initial status of the application. Defaulting to <code>accessible</code> |
| [title](./kibana-plugin-core-public.app.title.md) | <code>string</code> | The title of the application. |
| [tooltip](./kibana-plugin-core-public.app.tooltip.md) | <code>string</code> | A tooltip shown when hovering over app link. |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<!-- 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; [searchDeepLinks](./kibana-plugin-core-public.app.searchdeeplinks.md)

## App.searchDeepLinks property

Array of links that represent secondary in-app locations for the app.

<b>Signature:</b>

```typescript
searchDeepLinks?: AppSearchDeepLink[];
```

## Remarks

Used to populate navigational search results (where available). Can be updated using the [App.updater$](./kibana-plugin-core-public.app.updater_.md) observable. See for more details.

## Example

The `path` property on deep links should not include the application's `appRoute`<!-- -->:

```ts
core.application.register({
id: 'my_app',
title: 'My App',
searchDeepLinks: [
{ id: 'sub1', title: 'Sub1', path: '/sub1' },
{
id: 'sub2',
title: 'Sub2',
searchDeepLinks: [
{ id: 'subsub', title: 'SubSub', path: '/sub2/sub' }
]
}
],
mount: () => { ... },
})

```
Will produce deep links on these paths: - `/app/my_app/sub1` - `/app/my_app/sub2/sub`

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!-- 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; [AppSearchDeepLink](./kibana-plugin-core-public.appsearchdeeplink.md)

## AppSearchDeepLink type

Input type for registering secondary in-app locations for an application.

Deep links must include at least one of `path` or `searchDeepLinks`<!-- -->. A deep link that does not have a `path` represents a topological level in the application's hierarchy, but does not have a destination URL that is user-accessible.

<b>Signature:</b>

```typescript
export declare type AppSearchDeepLink = {
id: string;
title: string;
} & ({
path: string;
searchDeepLinks?: AppSearchDeepLink[];
} | {
path?: string;
searchDeepLinks: AppSearchDeepLink[];
});
```
Original file line number Diff line number Diff line change
Expand Up @@ -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<App, 'status' | 'navLinkStatus' | 'tooltip' | 'defaultPath'>;
export declare type AppUpdatableFields = Pick<App, 'status' | 'navLinkStatus' | 'tooltip' | 'defaultPath' | 'searchDeepLinks'>;
```
2 changes: 2 additions & 0 deletions docs/development/core/public/kibana-plugin-core-public.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| [AppLeaveHandler](./kibana-plugin-core-public.appleavehandler.md) | A handler that will be executed before leaving the application, either when going to another application or when closing the browser tab or manually changing the url. Should return <code>confirm</code> to to prompt a message to the user before leaving the page, or <code>default</code> to keep the default behavior (doing nothing).<!-- -->See [AppMountParameters](./kibana-plugin-core-public.appmountparameters.md) for detailed usage examples. |
| [AppMount](./kibana-plugin-core-public.appmount.md) | A mount function called when the user navigates to this app's route. |
| [AppMountDeprecated](./kibana-plugin-core-public.appmountdeprecated.md) | A mount function called when the user navigates to this app's route. |
| [AppSearchDeepLink](./kibana-plugin-core-public.appsearchdeeplink.md) | Input type for registering secondary in-app locations for an application.<!-- -->Deep links must include at least one of <code>path</code> or <code>searchDeepLinks</code>. A deep link that does not have a <code>path</code> represents a topological level in the application's hierarchy, but does not have a destination URL that is user-accessible. |
| [AppUnmount](./kibana-plugin-core-public.appunmount.md) | A function called when an application should be unmounted from the page. This function should be synchronous. |
| [AppUpdatableFields](./kibana-plugin-core-public.appupdatablefields.md) | Defines the list of fields that can be updated via an [AppUpdater](./kibana-plugin-core-public.appupdater.md)<!-- -->. |
| [AppUpdater](./kibana-plugin-core-public.appupdater.md) | Updater for applications. see [ApplicationSetup](./kibana-plugin-core-public.applicationsetup.md) |
Expand All @@ -160,6 +161,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| [PluginInitializer](./kibana-plugin-core-public.plugininitializer.md) | The <code>plugin</code> export at the root of a plugin's <code>public</code> directory should conform to this interface. |
| [PluginOpaqueId](./kibana-plugin-core-public.pluginopaqueid.md) | |
| [PublicAppInfo](./kibana-plugin-core-public.publicappinfo.md) | Public information about a registered [application](./kibana-plugin-core-public.app.md) |
| [PublicAppSearchDeepLinkInfo](./kibana-plugin-core-public.publicappsearchdeeplinkinfo.md) | Public information about a registered app's [searchDeepLinks](./kibana-plugin-core-public.appsearchdeeplink.md) |
| [PublicUiSettingsParams](./kibana-plugin-core-public.publicuisettingsparams.md) | A sub-set of [UiSettingsParams](./kibana-plugin-core-public.uisettingsparams.md) exposed to the client-side. |
| [SavedObjectAttribute](./kibana-plugin-core-public.savedobjectattribute.md) | Type definition for a Saved Object attribute value |
| [SavedObjectAttributeSingle](./kibana-plugin-core-public.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-core-public.savedobjectattribute.md) |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ Public information about a registered [application](./kibana-plugin-core-public.
<b>Signature:</b>

```typescript
export declare type PublicAppInfo = Omit<App, 'mount' | 'updater$'> & {
export declare type PublicAppInfo = Omit<App, 'mount' | 'updater$' | 'searchDeepLinks'> & {
status: AppStatus;
navLinkStatus: AppNavLinkStatus;
appRoute: string;
searchDeepLinks: PublicAppSearchDeepLinkInfo[];
};
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!-- 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; [PublicAppSearchDeepLinkInfo](./kibana-plugin-core-public.publicappsearchdeeplinkinfo.md)

## PublicAppSearchDeepLinkInfo type

Public information about a registered app's [searchDeepLinks](./kibana-plugin-core-public.appsearchdeeplink.md)

<b>Signature:</b>

```typescript
export declare type PublicAppSearchDeepLinkInfo = Omit<AppSearchDeepLink, 'searchDeepLinks'> & {
searchDeepLinks: PublicAppSearchDeepLinkInfo[];
};
```
2 changes: 2 additions & 0 deletions src/core/public/application/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export {
AppNavLinkStatus,
AppUpdatableFields,
AppUpdater,
AppSearchDeepLink,
ApplicationSetup,
ApplicationStart,
AppLeaveHandler,
Expand All @@ -40,6 +41,7 @@ export {
AppLeaveConfirmAction,
NavigateToAppOptions,
PublicAppInfo,
PublicAppSearchDeepLinkInfo,
// Internal types
InternalApplicationSetup,
InternalApplicationStart,
Expand Down
80 changes: 77 additions & 3 deletions src/core/public/application/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,10 @@ export enum AppNavLinkStatus {
* Defines the list of fields that can be updated via an {@link AppUpdater}.
* @public
*/
export type AppUpdatableFields = Pick<App, 'status' | 'navLinkStatus' | 'tooltip' | 'defaultPath'>;
export type AppUpdatableFields = Pick<
App,
'status' | 'navLinkStatus' | 'tooltip' | 'defaultPath' | 'searchDeepLinks'
>;

/**
* Updater for applications.
Expand Down Expand Up @@ -222,7 +225,7 @@ export interface App<HistoryLocationState = unknown> {
* ```ts
* core.application.register({
* id: 'my_app',
* title: 'My App'
* title: 'My App',
* exactRoute: true,
* mount: () => { ... },
* })
Expand All @@ -232,18 +235,89 @@ export interface App<HistoryLocationState = unknown> {
* ```
*/
exactRoute?: boolean;

/**
* Array of links that represent secondary in-app locations for the app.
*
* @remarks
* Used to populate navigational search results (where available).
* Can be updated using the {@link App.updater$} observable. See {@link AppSubLink} for more details.
*
* @example
* The `path` property on deep links should not include the application's `appRoute`:
* ```ts
* core.application.register({
* id: 'my_app',
* title: 'My App',
* searchDeepLinks: [
* { id: 'sub1', title: 'Sub1', path: '/sub1' },
* {
* id: 'sub2',
* title: 'Sub2',
* searchDeepLinks: [
* { id: 'subsub', title: 'SubSub', path: '/sub2/sub' }
* ]
* }
* ],
* mount: () => { ... },
* })
* ```
*
* Will produce deep links on these paths:
* - `/app/my_app/sub1`
* - `/app/my_app/sub2/sub`
*/
searchDeepLinks?: AppSearchDeepLink[];
}

/**
* Input type for registering secondary in-app locations for an application.
*
* Deep links must include at least one of `path` or `searchDeepLinks`. A deep link that does not have a `path`
* represents a topological level in the application's hierarchy, but does not have a destination URL that is
* user-accessible.
* @public
*/
export type AppSearchDeepLink = {
/** Identifier to represent this sublink, should be unique for this application */
id: string;
/** Title to label represent this deep link */
title: string;
} & (
| {
/** URL path to access this link, relative to the application's appRoute. */
path: string;
/** Optional array of links that are 'underneath' this section in the hierarchy */
searchDeepLinks?: AppSearchDeepLink[];
}
| {
/** Optional path to access this section. Omit if this part of the hierarchy does not have a page URL. */
path?: string;
/** Array links that are 'underneath' this section in this hierarchy. */
searchDeepLinks: AppSearchDeepLink[];
}
);

/**
* Public information about a registered app's {@link AppSearchDeepLink | searchDeepLinks}
*
* @public
*/
export type PublicAppSearchDeepLinkInfo = Omit<AppSearchDeepLink, 'searchDeepLinks'> & {
searchDeepLinks: PublicAppSearchDeepLinkInfo[];
};

/**
* Public information about a registered {@link App | application}
*
* @public
*/
export type PublicAppInfo = Omit<App, 'mount' | 'updater$'> & {
export type PublicAppInfo = Omit<App, 'mount' | 'updater$' | 'searchDeepLinks'> & {
// remove optional on fields populated with default values
status: AppStatus;
navLinkStatus: AppNavLinkStatus;
appRoute: string;
searchDeepLinks: PublicAppSearchDeepLinkInfo[];
};

/**
Expand Down
36 changes: 36 additions & 0 deletions src/core/public/application/utils/get_app_info.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,42 @@ describe('getAppInfo', () => {
status: AppStatus.accessible,
navLinkStatus: AppNavLinkStatus.visible,
appRoute: `/app/some-id`,
searchDeepLinks: [],
});
});

it('populates default values for nested searchDeepLinks', () => {
const app = createApp({
searchDeepLinks: [
{
id: 'sub-id',
title: 'sub-title',
searchDeepLinks: [{ id: 'sub-sub-id', title: 'sub-sub-title', path: '/sub-sub' }],
},
],
});
const info = getAppInfo(app);

expect(info).toEqual({
id: 'some-id',
title: 'some-title',
status: AppStatus.accessible,
navLinkStatus: AppNavLinkStatus.visible,
appRoute: `/app/some-id`,
searchDeepLinks: [
{
id: 'sub-id',
title: 'sub-title',
searchDeepLinks: [
{
id: 'sub-sub-id',
title: 'sub-sub-title',
path: '/sub-sub',
searchDeepLinks: [], // default empty array added
},
],
},
],
});
});

Expand Down
32 changes: 30 additions & 2 deletions src/core/public/application/utils/get_app_info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,16 @@
* under the License.
*/

import { App, AppNavLinkStatus, AppStatus, PublicAppInfo } from '../types';
import {
App,
AppNavLinkStatus,
AppStatus,
AppSearchDeepLink,
PublicAppInfo,
PublicAppSearchDeepLinkInfo,
} from '../types';

export function getAppInfo(app: App<unknown>): PublicAppInfo {
export function getAppInfo(app: App): PublicAppInfo {
const navLinkStatus =
app.navLinkStatus === AppNavLinkStatus.default
? app.status === AppStatus.inaccessible
Expand All @@ -32,5 +39,26 @@ export function getAppInfo(app: App<unknown>): PublicAppInfo {
status: app.status!,
navLinkStatus,
appRoute: app.appRoute!,
searchDeepLinks: getSearchDeepLinkInfos(app, app.searchDeepLinks),
};
}

function getSearchDeepLinkInfos(
app: App,
searchDeepLinks?: AppSearchDeepLink[]
): PublicAppSearchDeepLinkInfo[] {
if (!searchDeepLinks) {
return [];
}

return searchDeepLinks.map(
(rawDeepLink): PublicAppSearchDeepLinkInfo => {
return {
id: rawDeepLink.id,
title: rawDeepLink.title,
path: rawDeepLink.path,
searchDeepLinks: getSearchDeepLinkInfos(app, rawDeepLink.searchDeepLinks),
};
}
);
}
1 change: 1 addition & 0 deletions src/core/public/chrome/nav_links/to_nav_link.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const app = (props: Partial<PublicAppInfo> = {}): PublicAppInfo => ({
status: AppStatus.accessible,
navLinkStatus: AppNavLinkStatus.default,
appRoute: `/app/some-id`,
searchDeepLinks: [],
...props,
});

Expand Down
4 changes: 3 additions & 1 deletion src/core/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ export {
ApplicationSetup,
ApplicationStart,
App,
PublicAppInfo,
AppMount,
AppMountDeprecated,
AppUnmount,
Expand All @@ -111,6 +110,9 @@ export {
AppNavLinkStatus,
AppUpdatableFields,
AppUpdater,
AppSearchDeepLink,
PublicAppInfo,
PublicAppSearchDeepLinkInfo,
ScopedHistory,
NavigateToAppOptions,
} from './application';
Expand Down
Loading

0 comments on commit 9913ad7

Please sign in to comment.