Move Nav APIs to new platform (#34490)

This moves the core Nav APIs from `ui/chrome` into the `ChromeService` in the new platform.

- `ChromeStart` now exposes a sub-service for reading and making limited updates to navlinks. These are powered by apps registered with the `ApplicationService` and filtered by UI Capabilities before being exposed by the `ChromeService`.
- The `header-global-nav` directive now consumes navlinks from the new platform.
- The `lastSubUrl` feature utilized by legacy apps has been refactored and will remain in `ui/chrome`. This feature utilizes the limited fields that `ChromeService` exposes to updates by outside code.

This change is the main blocker to moving the Chrome UI to the new platform. This will be necessary to enable the new platform to control top-level routing.
This commit is contained in:
Josh Dover 2019-05-10 09:03:10 -05:00 committed by GitHub
parent 5c03eeef0b
commit 3dacef2901
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
79 changed files with 1323 additions and 580 deletions

View file

@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) &gt; [active](./kibana-plugin-public.chromenavlink.active.md)
## ChromeNavLink.active property
Indicates whether or not this app is currently on the screen.
NOTE: remove this when ApplicationService is implemented and managing apps.
<b>Signature:</b>
```typescript
readonly active?: boolean;
```

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) &gt; [baseUrl](./kibana-plugin-public.chromenavlink.baseurl.md)
## ChromeNavLink.baseUrl property
The base route used to open the root of an application.
<b>Signature:</b>
```typescript
readonly baseUrl: string;
```

View file

@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) &gt; [disabled](./kibana-plugin-public.chromenavlink.disabled.md)
## ChromeNavLink.disabled property
Disables a link from being clickable.
NOTE: this is only used by the ML and Graph plugins currently. They use this field to disable the nav link when the license is expired.
<b>Signature:</b>
```typescript
readonly disabled?: boolean;
```

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) &gt; [euiIconType](./kibana-plugin-public.chromenavlink.euiicontype.md)
## ChromeNavLink.euiIconType property
A EUI iconType that will be used for the app's icon. This icon takes precendence over the `icon` property.
<b>Signature:</b>
```typescript
readonly euiIconType?: string;
```

View file

@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) &gt; [hidden](./kibana-plugin-public.chromenavlink.hidden.md)
## ChromeNavLink.hidden property
Hides a link from the navigation.
NOTE: remove this when ApplicationService is implemented. Instead, plugins should only register an Application if needed.
<b>Signature:</b>
```typescript
readonly hidden?: boolean;
```

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) &gt; [icon](./kibana-plugin-public.chromenavlink.icon.md)
## ChromeNavLink.icon property
A URL to an image file used as an icon. Used as a fallback if `euiIconType` is not provided.
<b>Signature:</b>
```typescript
readonly icon?: string;
```

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) &gt; [id](./kibana-plugin-public.chromenavlink.id.md)
## ChromeNavLink.id property
A unique identifier for looking up links.
<b>Signature:</b>
```typescript
readonly id: string;
```

View file

@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) &gt; [linkToLastSubUrl](./kibana-plugin-public.chromenavlink.linktolastsuburl.md)
## ChromeNavLink.linkToLastSubUrl property
Whether or not the subUrl feature should be enabled.
NOTE: only read by legacy platform.
<b>Signature:</b>
```typescript
readonly linkToLastSubUrl?: boolean;
```

View file

@ -0,0 +1,31 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [ChromeNavLink](./kibana-plugin-public.chromenavlink.md)
## ChromeNavLink interface
<b>Signature:</b>
```typescript
export interface ChromeNavLink
```
## Properties
| Property | Type | Description |
| --- | --- | --- |
| [active](./kibana-plugin-public.chromenavlink.active.md) | <code>boolean</code> | Indicates whether or not this app is currently on the screen.<!-- -->NOTE: remove this when ApplicationService is implemented and managing apps. |
| [baseUrl](./kibana-plugin-public.chromenavlink.baseurl.md) | <code>string</code> | The base route used to open the root of an application. |
| [disabled](./kibana-plugin-public.chromenavlink.disabled.md) | <code>boolean</code> | Disables a link from being clickable.<!-- -->NOTE: this is only used by the ML and Graph plugins currently. They use this field to disable the nav link when the license is expired. |
| [euiIconType](./kibana-plugin-public.chromenavlink.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. |
| [hidden](./kibana-plugin-public.chromenavlink.hidden.md) | <code>boolean</code> | Hides a link from the navigation.<!-- -->NOTE: remove this when ApplicationService is implemented. Instead, plugins should only register an Application if needed. |
| [icon](./kibana-plugin-public.chromenavlink.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-public.chromenavlink.id.md) | <code>string</code> | A unique identifier for looking up links. |
| [linkToLastSubUrl](./kibana-plugin-public.chromenavlink.linktolastsuburl.md) | <code>boolean</code> | Whether or not the subUrl feature should be enabled.<!-- -->NOTE: only read by legacy platform. |
| [order](./kibana-plugin-public.chromenavlink.order.md) | <code>number</code> | An ordinal used to sort nav links relative to one another for display. |
| [subUrlBase](./kibana-plugin-public.chromenavlink.suburlbase.md) | <code>string</code> | A url base that legacy apps can set to match deep URLs to an applcation.<!-- -->NOTE: this should be removed once legacy apps are gone. |
| [title](./kibana-plugin-public.chromenavlink.title.md) | <code>string</code> | The title of the application. |
| [tooltip](./kibana-plugin-public.chromenavlink.tooltip.md) | <code>string</code> | A tooltip shown when hovering over an app link. |
| [url](./kibana-plugin-public.chromenavlink.url.md) | <code>string</code> | A url that legacy apps can set to deep link into their applications.<!-- -->NOTE: 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. |

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) &gt; [order](./kibana-plugin-public.chromenavlink.order.md)
## ChromeNavLink.order property
An ordinal used to sort nav links relative to one another for display.
<b>Signature:</b>
```typescript
readonly order: number;
```

View file

@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) &gt; [subUrlBase](./kibana-plugin-public.chromenavlink.suburlbase.md)
## ChromeNavLink.subUrlBase property
A url base that legacy apps can set to match deep URLs to an applcation.
NOTE: this should be removed once legacy apps are gone.
<b>Signature:</b>
```typescript
readonly subUrlBase?: string;
```

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) &gt; [title](./kibana-plugin-public.chromenavlink.title.md)
## ChromeNavLink.title property
The title of the application.
<b>Signature:</b>
```typescript
readonly title: string;
```

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) &gt; [tooltip](./kibana-plugin-public.chromenavlink.tooltip.md)
## ChromeNavLink.tooltip property
A tooltip shown when hovering over an app link.
<b>Signature:</b>
```typescript
readonly tooltip?: string;
```

View file

@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) &gt; [url](./kibana-plugin-public.chromenavlink.url.md)
## ChromeNavLink.url property
A url that legacy apps can set to deep link into their applications.
NOTE: 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.
<b>Signature:</b>
```typescript
readonly url?: string;
```

View file

@ -0,0 +1,12 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [ChromeStart](./kibana-plugin-public.chromestart.md)
## ChromeStart type
<b>Signature:</b>
```typescript
export declare type ChromeStart = ReturnType<ChromeService['start']>;
```

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [CoreStart](./kibana-plugin-public.corestart.md) &gt; [chrome](./kibana-plugin-public.corestart.chrome.md)
## CoreStart.chrome property
[ChromeStart](./kibana-plugin-public.chromestart.md)
<b>Signature:</b>
```typescript
chrome: ChromeStart;
```

View file

@ -18,6 +18,7 @@ export interface CoreStart
| --- | --- | --- |
| [application](./kibana-plugin-public.corestart.application.md) | <code>ApplicationStart</code> | [ApplicationStart](./kibana-plugin-public.applicationstart.md) |
| [basePath](./kibana-plugin-public.corestart.basepath.md) | <code>BasePathStart</code> | [BasePathStart](./kibana-plugin-public.basepathstart.md) |
| [chrome](./kibana-plugin-public.corestart.chrome.md) | <code>ChromeStart</code> | [ChromeStart](./kibana-plugin-public.chromestart.md) |
| [http](./kibana-plugin-public.corestart.http.md) | <code>HttpStart</code> | [HttpStart](./kibana-plugin-public.httpstart.md) |
| [i18n](./kibana-plugin-public.corestart.i18n.md) | <code>I18nStart</code> | [I18nStart](./kibana-plugin-public.i18nstart.md) |
| [injectedMetadata](./kibana-plugin-public.corestart.injectedmetadata.md) | <code>InjectedMetadataStart</code> | [InjectedMetadataStart](./kibana-plugin-public.injectedmetadatastart.md) |

View file

@ -22,6 +22,7 @@
| [ChromeBadge](./kibana-plugin-public.chromebadge.md) | |
| [ChromeBrand](./kibana-plugin-public.chromebrand.md) | |
| [ChromeBreadcrumb](./kibana-plugin-public.chromebreadcrumb.md) | |
| [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) | |
| [CoreSetup](./kibana-plugin-public.coresetup.md) | Core services exposed to the setup lifecycle |
| [CoreStart](./kibana-plugin-public.corestart.md) | Core services exposed to the start lifecycle |
| [FatalErrorInfo](./kibana-plugin-public.fatalerrorinfo.md) | Represents the <code>message</code> and <code>stack</code> of a fatal Error |
@ -45,6 +46,7 @@
| [BasePathStart](./kibana-plugin-public.basepathstart.md) | Provides access to the 'server.basePath' configuration option in kibana.yml |
| [ChromeHelpExtension](./kibana-plugin-public.chromehelpextension.md) | |
| [ChromeSetup](./kibana-plugin-public.chromesetup.md) | |
| [ChromeStart](./kibana-plugin-public.chromestart.md) | |
| [HttpSetup](./kibana-plugin-public.httpsetup.md) | |
| [HttpStart](./kibana-plugin-public.httpstart.md) | |
| [I18nStart](./kibana-plugin-public.i18nstart.md) | |

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [PluginStartContext](./kibana-plugin-public.pluginstartcontext.md) &gt; [chrome](./kibana-plugin-public.pluginstartcontext.chrome.md)
## PluginStartContext.chrome property
<b>Signature:</b>
```typescript
chrome: ChromeStart;
```

View file

@ -18,6 +18,7 @@ export interface PluginStartContext
| --- | --- | --- |
| [application](./kibana-plugin-public.pluginstartcontext.application.md) | <code>Pick&lt;ApplicationStart, 'capabilities'&gt;</code> | |
| [basePath](./kibana-plugin-public.pluginstartcontext.basepath.md) | <code>BasePathStart</code> | |
| [chrome](./kibana-plugin-public.pluginstartcontext.chrome.md) | <code>ChromeStart</code> | |
| [http](./kibana-plugin-public.pluginstartcontext.http.md) | <code>HttpStart</code> | |
| [i18n](./kibana-plugin-public.pluginstartcontext.i18n.md) | <code>I18nStart</code> | |
| [notifications](./kibana-plugin-public.pluginstartcontext.notifications.md) | <code>NotificationsStart</code> | |

View file

@ -0,0 +1,25 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [ConfigService](./kibana-plugin-server.configservice.md) &gt; [atPath](./kibana-plugin-server.configservice.atpath.md)
## ConfigService.atPath() method
Reads the subset of the config at the specified `path` and validates it against the static `schema` on the given `ConfigClass`<!-- -->.
<b>Signature:</b>
```typescript
atPath<TSchema extends Type<any>, TConfig>(path: ConfigPath, ConfigClass: ConfigWithSchema<TSchema, TConfig>): Observable<TConfig>;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| path | <code>ConfigPath</code> | The path to the desired subset of the config. |
| ConfigClass | <code>ConfigWithSchema&lt;TSchema, TConfig&gt;</code> | A class (not an instance of a class) that contains a static <code>schema</code> that we validate the config at the given <code>path</code> against. |
<b>Returns:</b>
`Observable<TConfig>`

View file

@ -0,0 +1,17 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [ConfigService](./kibana-plugin-server.configservice.md) &gt; [getConfig$](./kibana-plugin-server.configservice.getconfig$.md)
## ConfigService.getConfig$() method
Returns the full config object observable. This is not intended for "normal use", but for features that \_need\_ access to the full object.
<b>Signature:</b>
```typescript
getConfig$(): Observable<Config>;
```
<b>Returns:</b>
`Observable<Config>`

View file

@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [ConfigService](./kibana-plugin-server.configservice.md) &gt; [getUnusedPaths](./kibana-plugin-server.configservice.getunusedpaths.md)
## ConfigService.getUnusedPaths() method
<b>Signature:</b>
```typescript
getUnusedPaths(): Promise<string[]>;
```
<b>Returns:</b>
`Promise<string[]>`

View file

@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [ConfigService](./kibana-plugin-server.configservice.md) &gt; [getUsedPaths](./kibana-plugin-server.configservice.getusedpaths.md)
## ConfigService.getUsedPaths() method
<b>Signature:</b>
```typescript
getUsedPaths(): Promise<string[]>;
```
<b>Returns:</b>
`Promise<string[]>`

View file

@ -0,0 +1,22 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [ConfigService](./kibana-plugin-server.configservice.md) &gt; [isEnabledAtPath](./kibana-plugin-server.configservice.isenabledatpath.md)
## ConfigService.isEnabledAtPath() method
<b>Signature:</b>
```typescript
isEnabledAtPath(path: ConfigPath): Promise<boolean>;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| path | <code>ConfigPath</code> | |
<b>Returns:</b>
`Promise<boolean>`

View file

@ -0,0 +1,24 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [ConfigService](./kibana-plugin-server.configservice.md)
## ConfigService class
<b>Signature:</b>
```typescript
export declare class ConfigService
```
## Methods
| Method | Modifiers | Description |
| --- | --- | --- |
| [atPath(path, ConfigClass)](./kibana-plugin-server.configservice.atpath.md) | | Reads the subset of the config at the specified <code>path</code> and validates it against the static <code>schema</code> on the given <code>ConfigClass</code>. |
| [getConfig$()](./kibana-plugin-server.configservice.getconfig$.md) | | Returns the full config object observable. This is not intended for "normal use", but for features that \_need\_ access to the full object. |
| [getUnusedPaths()](./kibana-plugin-server.configservice.getunusedpaths.md) | | |
| [getUsedPaths()](./kibana-plugin-server.configservice.getusedpaths.md) | | |
| [isEnabledAtPath(path)](./kibana-plugin-server.configservice.isenabledatpath.md) | | |
| [optionalAtPath(path, ConfigClass)](./kibana-plugin-server.configservice.optionalatpath.md) | | Same as <code>atPath</code>, but returns <code>undefined</code> if there is no config at the specified path.[ConfigService.atPath()](./kibana-plugin-server.configservice.atpath.md) |

View file

@ -0,0 +1,27 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [ConfigService](./kibana-plugin-server.configservice.md) &gt; [optionalAtPath](./kibana-plugin-server.configservice.optionalatpath.md)
## ConfigService.optionalAtPath() method
Same as `atPath`<!-- -->, but returns `undefined` if there is no config at the specified path.
[ConfigService.atPath()](./kibana-plugin-server.configservice.atpath.md)
<b>Signature:</b>
```typescript
optionalAtPath<TSchema extends Type<any>, TConfig>(path: ConfigPath, ConfigClass: ConfigWithSchema<TSchema, TConfig>): Observable<TConfig | undefined>;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| path | <code>ConfigPath</code> | |
| ConfigClass | <code>ConfigWithSchema&lt;TSchema, TConfig&gt;</code> | |
<b>Returns:</b>
`Observable<TConfig | undefined>`

View file

@ -4,7 +4,6 @@
## CoreStart interface
<b>Signature:</b>
```typescript

View file

@ -1,13 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [DiscoveredPlugin](./kibana-plugin-server.discoveredplugin.md) &gt; [configPath](./kibana-plugin-server.discoveredplugin.configpath.md)
## DiscoveredPlugin.configPath property
Root configuration path used by the plugin, defaults to "id".
<b>Signature:</b>
```typescript
readonly configPath: ConfigPath;
```

View file

@ -1,13 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [DiscoveredPlugin](./kibana-plugin-server.discoveredplugin.md) &gt; [id](./kibana-plugin-server.discoveredplugin.id.md)
## DiscoveredPlugin.id property
Identifier of the plugin.
<b>Signature:</b>
```typescript
readonly id: PluginName;
```

View file

@ -1,23 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [DiscoveredPlugin](./kibana-plugin-server.discoveredplugin.md)
## DiscoveredPlugin interface
Small container object used to expose information about discovered plugins that may or may not have been started.
<b>Signature:</b>
```typescript
export interface DiscoveredPlugin
```
## Properties
| Property | Type | Description |
| --- | --- | --- |
| [configPath](./kibana-plugin-server.discoveredplugin.configpath.md) | <code>ConfigPath</code> | Root configuration path used by the plugin, defaults to "id". |
| [id](./kibana-plugin-server.discoveredplugin.id.md) | <code>PluginName</code> | Identifier of the plugin. |
| [optionalPlugins](./kibana-plugin-server.discoveredplugin.optionalplugins.md) | <code>ReadonlyArray&lt;PluginName&gt;</code> | An optional list of the other plugins that if installed and enabled \*\*may be\*\* leveraged by this plugin for some additional functionality but otherwise are not required for this plugin to work properly. |
| [requiredPlugins](./kibana-plugin-server.discoveredplugin.requiredplugins.md) | <code>ReadonlyArray&lt;PluginName&gt;</code> | An optional list of the other plugins that \*\*must be\*\* installed and enabled for this plugin to function properly. |

View file

@ -1,13 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [DiscoveredPlugin](./kibana-plugin-server.discoveredplugin.md) &gt; [optionalPlugins](./kibana-plugin-server.discoveredplugin.optionalplugins.md)
## DiscoveredPlugin.optionalPlugins property
An optional list of the other plugins that if installed and enabled \*\*may be\*\* leveraged by this plugin for some additional functionality but otherwise are not required for this plugin to work properly.
<b>Signature:</b>
```typescript
readonly optionalPlugins: ReadonlyArray<PluginName>;
```

View file

@ -1,13 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [DiscoveredPlugin](./kibana-plugin-server.discoveredplugin.md) &gt; [requiredPlugins](./kibana-plugin-server.discoveredplugin.requiredplugins.md)
## DiscoveredPlugin.requiredPlugins property
An optional list of the other plugins that \*\*must be\*\* installed and enabled for this plugin to function properly.
<b>Signature:</b>
```typescript
readonly requiredPlugins: ReadonlyArray<PluginName>;
```

View file

@ -9,6 +9,7 @@
| Class | Description |
| --- | --- |
| [ClusterClient](./kibana-plugin-server.clusterclient.md) | Represents an Elasticsearch cluster API client and allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via <code>asScoped(...)</code>). |
| [ConfigService](./kibana-plugin-server.configservice.md) | |
| [KibanaRequest](./kibana-plugin-server.kibanarequest.md) | |
| [Router](./kibana-plugin-server.router.md) | |
| [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md) | Serves the same purpose as "normal" <code>ClusterClient</code> but exposes additional <code>callAsCurrentUser</code> method that doesn't use credentials of the Kibana internal user (as <code>callAsInternalUser</code> does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API |
@ -21,7 +22,6 @@
| [CallAPIOptions](./kibana-plugin-server.callapioptions.md) | The set of options that defines how API call should be made and result be processed. |
| [CoreSetup](./kibana-plugin-server.coresetup.md) | |
| [CoreStart](./kibana-plugin-server.corestart.md) | |
| [DiscoveredPlugin](./kibana-plugin-server.discoveredplugin.md) | Small container object used to expose information about discovered plugins that may or may not have been started. |
| [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) | |
| [HttpServiceStart](./kibana-plugin-server.httpservicestart.md) | |
| [Logger](./kibana-plugin-server.logger.md) | Logger exposes all the necessary methods to log any type of information and this is the interface used by the logging consumers including plugins. |
@ -31,8 +31,6 @@
| [Plugin](./kibana-plugin-server.plugin.md) | The interface that should be returned by a <code>PluginInitializer</code>. |
| [PluginInitializerContext](./kibana-plugin-server.plugininitializercontext.md) | Context that's available to plugins during initialization stage. |
| [PluginSetupContext](./kibana-plugin-server.pluginsetupcontext.md) | Context passed to the plugins <code>setup</code> method. |
| [PluginsServiceSetup](./kibana-plugin-server.pluginsservicesetup.md) | |
| [PluginsServiceStart](./kibana-plugin-server.pluginsservicestart.md) | |
| [PluginStartContext](./kibana-plugin-server.pluginstartcontext.md) | Context passed to the plugins <code>start</code> method. |
## Type Aliases

View file

@ -1,11 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [PluginsServiceSetup](./kibana-plugin-server.pluginsservicesetup.md) &gt; [contracts](./kibana-plugin-server.pluginsservicesetup.contracts.md)
## PluginsServiceSetup.contracts property
<b>Signature:</b>
```typescript
contracts: Map<PluginName, unknown>;
```

View file

@ -1,20 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [PluginsServiceSetup](./kibana-plugin-server.pluginsservicesetup.md)
## PluginsServiceSetup interface
<b>Signature:</b>
```typescript
export interface PluginsServiceSetup
```
## Properties
| Property | Type | Description |
| --- | --- | --- |
| [contracts](./kibana-plugin-server.pluginsservicesetup.contracts.md) | <code>Map&lt;PluginName, unknown&gt;</code> | |
| [uiPlugins](./kibana-plugin-server.pluginsservicesetup.uiplugins.md) | <code>{`<p/>` public: Map&lt;PluginName, DiscoveredPlugin&gt;;`<p/>` internal: Map&lt;PluginName, DiscoveredPluginInternal&gt;;`<p/>` }</code> | |

View file

@ -1,14 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [PluginsServiceSetup](./kibana-plugin-server.pluginsservicesetup.md) &gt; [uiPlugins](./kibana-plugin-server.pluginsservicesetup.uiplugins.md)
## PluginsServiceSetup.uiPlugins property
<b>Signature:</b>
```typescript
uiPlugins: {
public: Map<PluginName, DiscoveredPlugin>;
internal: Map<PluginName, DiscoveredPluginInternal>;
};
```

View file

@ -1,11 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [PluginsServiceStart](./kibana-plugin-server.pluginsservicestart.md) &gt; [contracts](./kibana-plugin-server.pluginsservicestart.contracts.md)
## PluginsServiceStart.contracts property
<b>Signature:</b>
```typescript
contracts: Map<PluginName, unknown>;
```

View file

@ -1,19 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [PluginsServiceStart](./kibana-plugin-server.pluginsservicestart.md)
## PluginsServiceStart interface
<b>Signature:</b>
```typescript
export interface PluginsServiceStart
```
## Properties
| Property | Type | Description |
| --- | --- | --- |
| [contracts](./kibana-plugin-server.pluginsservicestart.contracts.md) | <code>Map&lt;PluginName, unknown&gt;</code> | |

View file

@ -76,8 +76,8 @@ export interface App extends BaseApp {
/** @internal */
export interface LegacyApp extends BaseApp {
appUrl: string;
url?: string;
subUrlBase?: string;
linkToLastSubUrl?: boolean;
}
/** @internal */

View file

@ -23,6 +23,7 @@ import {
ChromeBreadcrumb,
ChromeService,
ChromeSetup,
ChromeStart,
} from './chrome_service';
const createSetupContractMock = () => {
@ -53,17 +54,34 @@ const createSetupContractMock = () => {
return setupContract;
};
const createStartContractMock = (): jest.Mocked<ChromeStart> => ({
navLinks: {
getNavLinks$: jest.fn(),
clear: jest.fn(),
has: jest.fn(),
get: jest.fn(),
getAll: jest.fn(),
showOnly: jest.fn(),
update: jest.fn(),
enableForcedAppSwitcherNavigation: jest.fn(),
getForceAppSwitcherNavigation$: jest.fn(),
},
});
type ChromeServiceContract = PublicMethodsOf<ChromeService>;
const createMock = () => {
const mocked: jest.Mocked<ChromeServiceContract> = {
setup: jest.fn(),
start: jest.fn(),
stop: jest.fn(),
};
mocked.setup.mockReturnValue(createSetupContractMock());
mocked.start.mockReturnValue(createStartContractMock());
return mocked;
};
export const chromeServiceMock = {
create: createMock,
createSetupContract: createSetupContractMock,
createStartContract: createStartContractMock,
};

View file

@ -25,6 +25,9 @@ import { map, takeUntil } from 'rxjs/operators';
import { IconType } from '@elastic/eui';
import { InjectedMetadataSetup } from '../injected_metadata';
import { NotificationsSetup } from '../notifications';
import { NavLinksService } from './nav_links/nav_links_service';
import { ApplicationStart } from '../application';
import { BasePathStart } from '../base_path';
const IS_COLLAPSED_KEY = 'core.chrome.isCollapsed';
@ -65,10 +68,16 @@ interface SetupDeps {
notifications: NotificationsSetup;
}
interface StartDeps {
application: ApplicationStart;
basePath: BasePathStart;
}
/** @internal */
export class ChromeService {
private readonly stop$ = new Rx.ReplaySubject(1);
private readonly browserSupportsCsp: boolean;
private readonly navLinks = new NavLinksService();
public constructor({ browserSupportsCsp }: ConstructorParams) {
this.browserSupportsCsp = browserSupportsCsp;
@ -183,7 +192,6 @@ export class ChromeService {
map(set => [...set]),
takeUntil(this.stop$)
),
/**
* Get an observable of the current badge
*/
@ -222,10 +230,20 @@ export class ChromeService {
};
}
public start({ application, basePath }: StartDeps) {
return {
navLinks: this.navLinks.start({ application, basePath }),
};
}
public stop() {
this.navLinks.stop();
this.stop$.next();
}
}
/** @public */
export type ChromeSetup = ReturnType<ChromeService['setup']>;
/** @public */
export type ChromeStart = ReturnType<ChromeService['start']>;

View file

@ -22,6 +22,8 @@ export {
ChromeBreadcrumb,
ChromeService,
ChromeSetup,
ChromeStart,
ChromeBrand,
ChromeHelpExtension,
} from './chrome_service';
export { ChromeNavLink } from './nav_links';

View file

@ -17,9 +17,5 @@
* under the License.
*/
import chrome from 'ui/chrome';
const timelionUiEnabled = chrome.getInjected('timelionUiEnabled');
if (timelionUiEnabled === false && chrome.navLinkExists('timelion')) {
chrome.getNavLinkById('timelion').hidden = true;
}
export { ChromeNavLink } from './nav_link';
export { NavLinksService } from './nav_links_service';

View file

@ -0,0 +1,134 @@
/*
* 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 { pick } from '../../../utils';
/**
* @public
*/
export interface ChromeNavLink {
/**
* A unique identifier for looking up links.
*/
readonly id: string;
/**
* Indicates whether or not this app is currently on the screen.
*
* NOTE: remove this when ApplicationService is implemented and managing apps.
*/
readonly active?: boolean;
/**
* Disables a link from being clickable.
*
* NOTE: this is only used by the ML and Graph plugins currently. They use this field
* to disable the nav link when the license is expired.
*/
readonly disabled?: boolean;
/**
* Hides a link from the navigation.
*
* NOTE: remove this when ApplicationService is implemented. Instead, plugins should only
* register an Application if needed.
*/
readonly hidden?: boolean;
/**
* An ordinal used to sort nav links relative to one another for display.
*/
readonly order: number;
/**
* The title of the application.
*/
readonly title: string;
/**
* A tooltip shown when hovering over an app link.
*/
readonly tooltip?: string;
/**
* The base route used to open the root of an application.
*/
readonly baseUrl: string;
/**
* A EUI iconType that will be used for the app's icon. This icon
* takes precendence over the `icon` property.
*/
readonly euiIconType?: string;
/**
* A URL to an image file used as an icon. Used as a fallback
* if `euiIconType` is not provided.
*/
readonly icon?: string;
/** LEGACY FIELDS */
/**
* A url base that legacy apps can set to match deep URLs to an applcation.
*
* NOTE: this should be removed once legacy apps are gone.
*/
readonly subUrlBase?: string;
/**
* Whether or not the subUrl feature should be enabled.
*
* NOTE: only read by legacy platform.
*/
readonly linkToLastSubUrl?: boolean;
/**
* A url that legacy apps can set to deep link into their applications.
*
* NOTE: 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.
*/
readonly url?: string;
}
export type NavLinkUpdateableFields = Partial<
Pick<ChromeNavLink, 'active' | 'disabled' | 'hidden' | 'url' | 'subUrlBase'>
>;
export class NavLinkWrapper {
public readonly id: string;
public readonly properties: Readonly<ChromeNavLink>;
constructor(properties: ChromeNavLink) {
if (!properties || !properties.id) {
throw new Error('`id` is required.');
}
this.id = properties.id;
this.properties = Object.freeze(properties);
}
public update(newProps: NavLinkUpdateableFields) {
// Enforce limited properties at runtime for JS code
newProps = pick(newProps, ['active', 'disabled', 'hidden', 'url', 'subUrlBase']);
return new NavLinkWrapper({ ...this.properties, ...newProps });
}
}

View file

@ -0,0 +1,178 @@
/*
* 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 { NavLinksService } from './nav_links_service';
import { take, map, takeLast } from 'rxjs/operators';
const mockAppService = {
availableApps: [
{ id: 'app1', order: 0, title: 'App 1', icon: 'app1', rootRoute: '/app1' },
{ id: 'app2', order: -10, title: 'App 2', euiIconType: 'canvasApp', rootRoute: '/app2' },
{ id: 'legacyApp', order: 20, title: 'Legacy App', appUrl: '/legacy-app' },
],
} as any;
const mockBasePath = {
addToPath: (url: string) => `wow${url}`,
} as any;
describe('NavLinksService', () => {
let service: NavLinksService;
let start: ReturnType<NavLinksService['start']>;
beforeEach(() => {
service = new NavLinksService();
start = service.start({ application: mockAppService, basePath: mockBasePath });
});
describe('#getNavLinks$()', () => {
it('sorts navlinks by `order` property', async () => {
expect(
await start
.getNavLinks$()
.pipe(
take(1),
map(links => links.map(l => l.id))
)
.toPromise()
).toEqual(['app2', 'app1', 'legacyApp']);
});
it('emits multiple values', async () => {
const navLinkIds$ = start.getNavLinks$().pipe(map(links => links.map(l => l.id)));
const emittedLinks: string[][] = [];
navLinkIds$.subscribe(r => emittedLinks.push(r));
start.update('app1', { active: true });
service.stop();
expect(emittedLinks).toEqual([['app2', 'app1', 'legacyApp'], ['app2', 'app1', 'legacyApp']]);
});
it('completes when service is stopped', async () => {
const last$ = start
.getNavLinks$()
.pipe(takeLast(1))
.toPromise();
service.stop();
await expect(last$).resolves.toBeInstanceOf(Array);
});
});
describe('#get()', () => {
it('returns link if exists', () => {
expect(start.get('app1')!.title).toEqual('App 1');
});
it('returns undefined if it does not exist', () => {
expect(start.get('phony')).toBeUndefined();
});
});
describe('#getAll()', () => {
it('returns a sorted array of navlinks', () => {
expect(start.getAll().map(l => l.id)).toEqual(['app2', 'app1', 'legacyApp']);
});
});
describe('#has()', () => {
it('returns true if exists', () => {
expect(start.has('app1')).toBe(true);
});
it('returns false if it does not exist', () => {
expect(start.has('phony')).toBe(false);
});
});
describe('#showOnly()', () => {
it('does nothing if link does not exist', async () => {
start.showOnly('fake');
expect(
await start
.getNavLinks$()
.pipe(
take(1),
map(links => links.map(l => l.id))
)
.toPromise()
).toEqual(['app2', 'app1', 'legacyApp']);
});
it('removes all other links', async () => {
start.showOnly('app1');
expect(
await start
.getNavLinks$()
.pipe(
take(1),
map(links => links.map(l => l.id))
)
.toPromise()
).toEqual(['app1']);
});
});
describe('#update()', () => {
it('updates the navlinks and returns the updated link', async () => {
expect(start.update('app1', { hidden: true })).toMatchInlineSnapshot(`
Object {
"baseUrl": "http://localhost/wow/app1",
"hidden": true,
"icon": "app1",
"id": "app1",
"order": 0,
"rootRoute": "/app1",
"title": "App 1",
}
`);
const hiddenLinkIds = await start
.getNavLinks$()
.pipe(
take(1),
map(links => links.filter(l => l.hidden).map(l => l.id))
)
.toPromise();
expect(hiddenLinkIds).toEqual(['app1']);
});
it('returns undefined if link does not exist', () => {
expect(start.update('fake', { hidden: true })).toBeUndefined();
});
});
describe('#enableForcedAppSwitcherNavigation()', () => {
it('flips #getForceAppSwitcherNavigation$()', async () => {
await expect(
start
.getForceAppSwitcherNavigation$()
.pipe(take(1))
.toPromise()
).resolves.toBe(false);
start.enableForcedAppSwitcherNavigation();
await expect(
start
.getForceAppSwitcherNavigation$()
.pipe(take(1))
.toPromise()
).resolves.toBe(true);
});
});
});

View file

@ -0,0 +1,163 @@
/*
* 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 { sortBy } from 'lodash';
import { BehaviorSubject, ReplaySubject } from 'rxjs';
import { map, takeUntil } from 'rxjs/operators';
import { NavLinkWrapper, NavLinkUpdateableFields } from './nav_link';
import { ApplicationStart } from '../../application';
import { BasePathStart } from '../../base_path';
interface StartDeps {
application: ApplicationStart;
basePath: BasePathStart;
}
export class NavLinksService {
private readonly stop$ = new ReplaySubject(1);
public start({ application, basePath }: StartDeps) {
const navLinks$ = new BehaviorSubject<ReadonlyMap<string, NavLinkWrapper>>(
new Map(
application.availableApps.map(
app =>
[
app.id,
new NavLinkWrapper({
...app,
// Either rootRoute or appUrl must be defined.
baseUrl: relativeToAbsolute(basePath.addToPath((app.rootRoute || app.appUrl)!)),
}),
] as [string, NavLinkWrapper]
)
)
);
const forceAppSwitcherNavigation$ = new BehaviorSubject(false);
return {
/**
* Get an observable for a sorted list of navlinks.
*/
getNavLinks$: () => {
return navLinks$.pipe(
map(sortNavLinks),
takeUntil(this.stop$)
);
},
/**
* Get the state of a navlink at this point in time.
* @param id
*/
get(id: string) {
const link = navLinks$.value.get(id);
return link && link.properties;
},
/**
* Get the current state of all navlinks.
*/
getAll() {
return sortNavLinks(navLinks$.value);
},
/**
* Check whether or not a navlink exists.
* @param id
*/
has(id: string) {
return navLinks$.value.has(id);
},
/**
* Remove all navlinks except the one matching the given id.
* NOTE: this is not reversible.
* @param id
*/
showOnly(id: string) {
if (!this.has(id)) {
return;
}
navLinks$.next(new Map([...navLinks$.value.entries()].filter(([linkId]) => linkId === id)));
},
/**
* Update the navlink for the given id with the updated attributes.
* Returns the updated navlink or `undefined` if it does not exist.
* @param id
* @param values
*/
update(id: string, values: NavLinkUpdateableFields) {
if (!this.has(id)) {
return;
}
navLinks$.next(
new Map(
[...navLinks$.value.entries()].map(([linkId, link]) => {
return [linkId, link.id === id ? link.update(values) : link] as [
string,
NavLinkWrapper
];
})
)
);
return this.get(id);
},
/**
* Enable forced navigation mode, which will trigger a page refresh
* when a nav link is clicked and only the hash is updated. This is only
* necessary when rendering the status page in place of another app, as
* links to that app will set the current URL and change the hash, but
* the routes for the correct are not loaded so nothing will happen.
* https://github.com/elastic/kibana/pull/29770
*
* Used only by status_page plugin
*/
enableForcedAppSwitcherNavigation() {
forceAppSwitcherNavigation$.next(true);
},
/**
* An observable of the forced app switcher state.
*/
getForceAppSwitcherNavigation$() {
return forceAppSwitcherNavigation$.asObservable();
},
};
}
public stop() {
this.stop$.next();
}
}
function sortNavLinks(navLinks: ReadonlyMap<string, NavLinkWrapper>) {
return sortBy([...navLinks.values()].map(link => link.properties), 'order');
}
function relativeToAbsolute(url: string) {
// convert all link urls to absolute urls
const a = document.createElement('a');
a.setAttribute('href', url);
return a.href;
}

View file

@ -169,6 +169,7 @@ export class CoreSystem {
const http = await this.http.start();
const i18n = await this.i18n.start();
const application = await this.application.start({ basePath, injectedMetadata });
const chrome = await this.chrome.start({ application, basePath });
const notificationsTargetDomElement = document.createElement('div');
const overlayTargetDomElement = document.createElement('div');
@ -190,6 +191,7 @@ export class CoreSystem {
const core: CoreStart = {
application,
basePath,
chrome,
http,
i18n,
injectedMetadata,

View file

@ -23,7 +23,9 @@ import {
ChromeBrand,
ChromeBreadcrumb,
ChromeHelpExtension,
ChromeNavLink,
ChromeSetup,
ChromeStart,
} from './chrome';
import { FatalErrorsSetup, FatalErrorInfo } from './fatal_errors';
import { HttpSetup, HttpStart } from './http';
@ -98,6 +100,8 @@ export interface CoreStart {
application: ApplicationStart;
/** {@link BasePathStart} */
basePath: BasePathStart;
/** {@link ChromeStart} */
chrome: ChromeStart;
/** {@link HttpStart} */
http: HttpStart;
/** {@link I18nStart} */
@ -121,10 +125,12 @@ export {
FatalErrorInfo,
Capabilities,
ChromeSetup,
ChromeStart,
ChromeBadge,
ChromeBreadcrumb,
ChromeBrand,
ChromeHelpExtension,
ChromeNavLink,
I18nSetup,
I18nStart,
InjectedMetadataSetup,

View file

@ -193,6 +193,7 @@ const defaultSetupDeps = {
const applicationStart = applicationServiceMock.createStartContract();
const basePathStart = basePathServiceMock.createStartContract();
const chromeStart = chromeServiceMock.createStartContract();
const i18nStart = i18nServiceMock.createStartContract();
const httpStart = httpServiceMock.createStartContract();
const injectedMetadataStart = injectedMetadataServiceMock.createStartContract();
@ -203,6 +204,7 @@ const defaultStartDeps = {
core: {
application: applicationStart,
basePath: basePathStart,
chrome: chromeStart,
i18n: i18nStart,
http: httpStart,
injectedMetadata: injectedMetadataStart,

View file

@ -91,6 +91,8 @@ export class LegacyPlatformService {
euiIconType: navLink.euiIconType,
icon: navLink.icon,
appUrl: navLink.url,
subUrlBase: navLink.subUrlBase,
linkToLastSubUrl: navLink.linkToLastSubUrl,
})
);

View file

@ -19,7 +19,7 @@
import { DiscoveredPlugin } from '../../server';
import { BasePathSetup, BasePathStart } from '../base_path';
import { ChromeSetup } from '../chrome';
import { ChromeSetup, ChromeStart } from '../chrome';
import { CoreContext } from '../core_system';
import { FatalErrorsSetup } from '../fatal_errors';
import { I18nSetup, I18nStart } from '../i18n';
@ -61,6 +61,7 @@ export interface PluginSetupContext {
*/
export interface PluginStartContext {
application: Pick<ApplicationStart, 'capabilities'>;
chrome: ChromeStart;
basePath: BasePathStart;
http: HttpStart;
i18n: I18nStart;
@ -128,6 +129,7 @@ export function createPluginStartContext<TSetup, TStart, TPluginsSetup, TPlugins
application: {
capabilities: deps.application.capabilities,
},
chrome: deps.chrome,
basePath: deps.basePath,
http: deps.http,
i18n: deps.i18n,

View file

@ -90,6 +90,7 @@ beforeEach(() => {
mockStartDeps = {
application: applicationServiceMock.createStartContract(),
basePath: basePathServiceMock.createStartContract(),
chrome: chromeServiceMock.createStartContract(),
http: httpServiceMock.createStartContract(),
i18n: i18nServiceMock.createStartContract(),
injectedMetadata: injectedMetadataServiceMock.createStartContract(),

View file

@ -87,11 +87,31 @@ export interface ChromeBreadcrumb {
// @public (undocumented)
export type ChromeHelpExtension = (element: HTMLDivElement) => (() => void);
// @public (undocumented)
export interface ChromeNavLink {
readonly active?: boolean;
readonly baseUrl: string;
readonly disabled?: boolean;
readonly euiIconType?: string;
readonly hidden?: boolean;
readonly icon?: string;
readonly id: string;
readonly linkToLastSubUrl?: boolean;
readonly order: number;
readonly subUrlBase?: string;
readonly title: string;
readonly tooltip?: string;
readonly url?: string;
}
// Warning: (ae-forgotten-export) The symbol "ChromeService" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
export type ChromeSetup = ReturnType<ChromeService['setup']>;
// @public (undocumented)
export type ChromeStart = ReturnType<ChromeService['start']>;
// @internal (undocumented)
export interface CoreContext {
}
@ -125,6 +145,8 @@ export interface CoreStart {
// (undocumented)
basePath: BasePathStart;
// (undocumented)
chrome: ChromeStart;
// (undocumented)
http: HttpStart;
// (undocumented)
i18n: I18nStart;
@ -355,6 +377,8 @@ export interface PluginStartContext {
// (undocumented)
basePath: BasePathStart;
// (undocumented)
chrome: ChromeStart;
// (undocumented)
http: HttpStart;
// (undocumented)
i18n: I18nStart;

View file

@ -27,6 +27,7 @@ import { i18n } from '@kbn/i18n';
import './app';
import contextAppRouteTemplate from './index.html';
import { getRootBreadcrumbs } from '../discover/breadcrumbs';
import { getNewPlatform } from 'ui/new_platform';
uiRoutes
.when('/context/:indexPatternId/:type/:id*', {
@ -85,7 +86,7 @@ function ContextAppRouteController(
this.anchorType = $routeParams.type;
this.anchorId = $routeParams.id;
this.indexPattern = indexPattern;
this.discoverUrl = chrome.getNavLinkById('kibana:discover').lastSubUrl;
this.discoverUrl = getNewPlatform().start.core.chrome.navLinks.get('kibana:discover').url;
this.filters = _.cloneDeep(queryFilter.getFilters());
}

View file

@ -20,11 +20,11 @@
import expect from '@kbn/expect';
import sinon from 'sinon';
import chrome from 'ui/chrome';
import { hideEmptyDevTools } from '../hide_empty_tools';
import { getNewPlatform } from 'ui/new_platform';
describe('hide dev tools', function () {
let navlinks;
let updateNavLink;
function PrivateWithoutTools() {
return [];
@ -35,12 +35,12 @@ describe('hide dev tools', function () {
}
function isHidden() {
return !!chrome.getNavLinkById('kibana:dev_tools').hidden;
return updateNavLink.calledWith('kibana:dev_tools', { hidden: true });
}
beforeEach(function () {
navlinks = {};
sinon.stub(chrome, 'getNavLinkById').returns(navlinks);
const coreNavLinks = getNewPlatform().start.core.chrome.navLinks;
updateNavLink = sinon.spy(coreNavLinks, 'update');
});
it('should hide the app if there are no dev tools', function () {
@ -54,6 +54,6 @@ describe('hide dev tools', function () {
});
afterEach(function () {
chrome.getNavLinkById.restore();
updateNavLink.restore();
});
});

View file

@ -18,14 +18,15 @@
*/
import { uiModules } from 'ui/modules';
import chrome from 'ui/chrome';
import { DevToolsRegistryProvider } from 'ui/registry/dev_tools';
import { getNewPlatform } from 'ui/new_platform';
export function hideEmptyDevTools(Private) {
const hasTools = !!Private(DevToolsRegistryProvider).length;
if (!hasTools) {
const navLink = chrome.getNavLinkById('kibana:dev_tools');
navLink.hidden = true;
getNewPlatform().start.core.chrome.navLinks.update('kibana:dev_tools', {
hidden: true
});
}
}

View file

@ -20,9 +20,9 @@
import 'ngreact';
import React, { Fragment } from 'react';
import { uiModules } from 'ui/modules';
import chrome from 'ui/chrome';
import { wrapInI18nContext } from 'ui/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { getNewPlatform } from 'ui/new_platform';
import {
EuiFlexGroup,
@ -40,7 +40,7 @@ const DiscoverFetchError = ({ fetchError }) => {
let body;
if (fetchError.lang === 'painless') {
const managementUrl = chrome.getNavLinkById('kibana:management').url;
const managementUrl = getNewPlatform().start.core.chrome.navLinks.get('kibana:management').url;
const url = `${managementUrl}/kibana/index_patterns`;
body = (

View file

@ -53,6 +53,7 @@ import { getUnhashableStatesProvider } from 'ui/state_management/state_hashing';
import { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal';
import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal';
import { getEditBreadcrumbs, getCreateBreadcrumbs } from '../breadcrumbs';
import { getNewPlatform } from 'ui/new_platform';
import { data } from 'plugins/data';
data.search.loadLegacyDirectives();
@ -505,7 +506,7 @@ function VisEditor(
// url, not the unsaved one.
chrome.trackSubUrlForApp('kibana:visualize', savedVisualizationParsedUrl);
const lastDashboardAbsoluteUrl = chrome.getNavLinkById('kibana:dashboard').lastSubUrl;
const lastDashboardAbsoluteUrl = getNewPlatform().start.core.chrome.navLinks.get('kibana:dashboard').url;
const dashboardParsedUrl = absoluteToParsedUrl(lastDashboardAbsoluteUrl, chrome.getBasePath());
dashboardParsedUrl.addQueryParameter(DashboardConstants.NEW_VISUALIZATION_ID_PARAM, savedVis.id);
kbnUrl.change(dashboardParsedUrl.appPath);

View file

@ -20,10 +20,14 @@
import 'ui/autoload/styles';
import 'ui/i18n';
import chrome from 'ui/chrome';
import { onStart } from 'ui/new_platform';
import { destroyStatusPage, renderStatusPage } from './components/render';
onStart(({ core }) => {
core.chrome.navLinks.enableForcedAppSwitcherNavigation();
});
chrome
.enableForcedAppSwitcherNavigation()
.setRootTemplate(require('plugins/status_page/status_page.html'))
.setRootController('ui', function ($scope, buildNum, buildSha) {
$scope.$$postDigest(() => {

View file

@ -94,7 +94,8 @@ const coreSystem = new CoreSystem({
uiSettings: {
defaults: ${JSON.stringify(defaultUiSettings, null, 2).split('\n').join('\n ')},
user: {}
}
},
nav: []
},
csp: {
warnLegacyBrowsers: false,

View file

@ -0,0 +1,27 @@
/*
* 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 { onStart } from 'ui/new_platform';
onStart(({ core }) => {
const timelionUiEnabled = core.injectedMetadata.getInjectedVar('timelionUiEnabled');
if (timelionUiEnabled === false) {
core.chrome.navLinks.update('timelion', { hidden: true });
}
});

View file

@ -18,10 +18,12 @@
*/
import expect from '@kbn/expect';
import sinon from 'sinon';
import { initChromeNavApi } from '../nav';
import { StubBrowserStorage } from 'test_utils/stub_browser_storage';
import { KibanaParsedUrl } from '../../../url/kibana_parsed_url';
import { getNewPlatform } from 'ui/new_platform';
import { absoluteToParsedUrl } from '../../../url/absolute_to_parsed_url';
const basePath = '/someBasePath';
@ -38,146 +40,124 @@ function init(customInternals = { basePath }) {
return { chrome, internals };
}
describe('chrome nav apis', function () {
describe('#getNavLinkById', () => {
it('retrieves the correct nav link, given its ID', () => {
const appUrlStore = new StubBrowserStorage();
const nav = [
{ id: 'kibana:discover', title: 'Discover' }
];
const {
chrome
} = init({ appUrlStore, nav });
let coreNavLinks;
let fakedLinks = [];
const navLink = chrome.getNavLinkById('kibana:discover');
expect(navLink).to.eql(nav[0]);
});
const baseUrl = (function () {
const a = document.createElement('a');
a.setAttribute('href', '/');
return a.href.slice(0, a.href.length - 1);
}());
it('throws an error if the nav link with the given ID is not found', () => {
const appUrlStore = new StubBrowserStorage();
const nav = [
{ id: 'kibana:discover', title: 'Discover' }
];
const {
chrome
} = init({ appUrlStore, nav });
let errorThrown = false;
try {
chrome.getNavLinkById('nonexistent');
} catch (e) {
errorThrown = true;
beforeEach(() => {
coreNavLinks = getNewPlatform().start.core.chrome.navLinks;
sinon.stub(coreNavLinks, 'update').callsFake((linkId, updateAttrs) => {
const link = fakedLinks.find(({ id }) => id === linkId);
for (const key of Object.keys(updateAttrs)) {
link[key] = updateAttrs[key];
}
expect(errorThrown).to.be(true);
return link;
});
sinon.stub(coreNavLinks, 'getAll').callsFake(() => fakedLinks);
sinon.stub(coreNavLinks, 'get').callsFake((linkId) => fakedLinks.find(({ id }) => id === linkId));
});
afterEach(() => {
coreNavLinks.update.restore();
coreNavLinks.getAll.restore();
coreNavLinks.get.restore();
});
describe('#untrackNavLinksForDeletedSavedObjects', function () {
const appId = 'appId';
const appUrl = 'https://localhost:9200/app/kibana#test';
const appUrl = `${baseUrl}/app/kibana#test`;
const deletedId = 'IAMDELETED';
it('should clear last url when last url contains link to deleted saved object', function () {
const appUrlStore = new StubBrowserStorage();
const nav = [
{
id: appId,
title: 'Discover',
linkToLastSubUrl: true,
lastSubUrl: `${appUrl}?id=${deletedId}`,
url: appUrl
}
];
const {
chrome
} = init({ appUrlStore, nav });
fakedLinks = [{
id: appId,
title: 'Discover',
url: `${appUrl}?id=${deletedId}`,
baseUrl: appUrl,
linkToLastSubUrl: true,
}];
const { chrome } = init({ appUrlStore });
chrome.untrackNavLinksForDeletedSavedObjects([deletedId]);
expect(chrome.getNavLinkById('appId').lastSubUrl).to.be(appUrl);
expect(coreNavLinks.update.calledWith(appId, { url: appUrl })).to.be(true);
});
it('should not clear last url when last url does not contains link to deleted saved object', function () {
const lastUrl = `${appUrl}?id=anotherSavedObjectId`;
const appUrlStore = new StubBrowserStorage();
const nav = [
{
id: appId,
title: 'Discover',
linkToLastSubUrl: true,
lastSubUrl: lastUrl,
url: appUrl
}
];
const {
chrome
} = init({ appUrlStore, nav });
fakedLinks = [{
id: appId,
title: 'Discover',
url: lastUrl,
baseUrl: appUrl,
linkToLastSubUrl: true
}];
const { chrome } = init({ appUrlStore });
chrome.untrackNavLinksForDeletedSavedObjects([deletedId]);
expect(chrome.getNavLinkById(appId).lastSubUrl).to.be(lastUrl);
expect(coreNavLinks.update.calledWith(appId, { url: appUrl })).to.be(false);
});
});
describe('internals.trackPossibleSubUrl()', function () {
it('injects the globalState of the current url to all links for the same app', function () {
const appUrlStore = new StubBrowserStorage();
const nav = [
fakedLinks = [
{
url: 'https://localhost:9200/app/kibana#discover',
subUrlBase: 'https://localhost:9200/app/kibana#discover'
id: 'kibana:discover',
baseUrl: `${baseUrl}/app/kibana#discover`,
subUrlBase: '/app/kibana#discover'
},
{
url: 'https://localhost:9200/app/kibana#visualize',
subUrlBase: 'https://localhost:9200/app/kibana#visualize'
id: 'kibana:visualize',
baseUrl: `${baseUrl}/app/kibana#visualize`,
subUrlBase: '/app/kibana#visualize'
},
{
url: 'https://localhost:9200/app/kibana#dashboards',
subUrlBase: 'https://localhost:9200/app/kibana#dashboard'
id: 'kibana:dashboard',
baseUrl: `${baseUrl}/app/kibana#dashboards`,
subUrlBase: '/app/kibana#dashboard'
},
].map(l => {
l.lastSubUrl = l.url;
return l;
});
];
const {
internals
} = init({ appUrlStore, nav });
const { internals } = init({ appUrlStore });
internals.trackPossibleSubUrl(`${baseUrl}/app/kibana#dashboard?_g=globalstate`);
internals.trackPossibleSubUrl('https://localhost:9200/app/kibana#dashboard?_g=globalstate');
expect(internals.nav[0].lastSubUrl).to.be('https://localhost:9200/app/kibana#discover?_g=globalstate');
expect(internals.nav[0].active).to.be(false);
expect(fakedLinks[0].url).to.be(`${baseUrl}/app/kibana#discover?_g=globalstate`);
expect(fakedLinks[0].active).to.be(false);
expect(internals.nav[1].lastSubUrl).to.be('https://localhost:9200/app/kibana#visualize?_g=globalstate');
expect(internals.nav[1].active).to.be(false);
expect(fakedLinks[1].url).to.be(`${baseUrl}/app/kibana#visualize?_g=globalstate`);
expect(fakedLinks[1].active).to.be(false);
expect(internals.nav[2].lastSubUrl).to.be('https://localhost:9200/app/kibana#dashboard?_g=globalstate');
expect(internals.nav[2].active).to.be(true);
expect(fakedLinks[2].url).to.be(`${baseUrl}/app/kibana#dashboard?_g=globalstate`);
expect(fakedLinks[2].active).to.be(true);
});
});
describe('internals.trackSubUrlForApp()', function () {
describe('chrome.trackSubUrlForApp()', function () {
it('injects a manual app url', function () {
const appUrlStore = new StubBrowserStorage();
const nav = [
{
id: 'kibana:visualize',
url: 'https://localhost:9200/app/kibana#visualize',
lastSubUrl: 'https://localhost:9200/app/kibana#visualize',
subUrlBase: 'https://localhost:9200/app/kibana#visualize'
}
];
fakedLinks = [{
id: 'kibana:visualize',
baseUrl: `${baseUrl}/app/kibana#visualize`,
url: `${baseUrl}/app/kibana#visualize`,
subUrlBase: '/app/kibana#visualize',
}];
const { chrome, internals } = init({ appUrlStore, nav });
const basePath = '/xyz';
const appId = 'kibana';
const appPath = 'visualize/1234?_g=globalstate';
const hostname = 'localhost';
const port = '9200';
const protocol = 'https';
const kibanaParsedUrl = new KibanaParsedUrl({ basePath, appId, appPath, hostname, port, protocol });
const { chrome } = init({ appUrlStore });
const kibanaParsedUrl = absoluteToParsedUrl(`${baseUrl}/xyz/app/kibana#visualize/1234?_g=globalstate`, '/xyz');
chrome.trackSubUrlForApp('kibana:visualize', kibanaParsedUrl);
expect(internals.nav[0].lastSubUrl).to.be('https://localhost:9200/xyz/app/kibana#visualize/1234?_g=globalstate');
expect(
coreNavLinks.update.calledWith('kibana:visualize', { url: `${baseUrl}/xyz/app/kibana#visualize/1234?_g=globalstate` })
).to.be(true);
});
});
});

View file

@ -29,9 +29,7 @@ export function initAngularApi(chrome, internals) {
configureAppAngularModule(kibana);
kibana
.value('chrome', chrome)
.run(internals.$initNavLinksDeepWatch);
kibana.value('chrome', chrome);
registerSubUrlHooks(kibana, internals);
directivesProvider(chrome, internals);

View file

@ -1,48 +0,0 @@
/*
* 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 { IconType } from '@elastic/eui';
import * as Rx from 'rxjs';
import { KibanaParsedUrl } from 'ui/url/kibana_parsed_url';
export interface NavLink {
title: string;
url: string;
subUrlBase: string;
id: string;
euiIconType: IconType;
icon?: string;
active: boolean;
lastSubUrl?: string;
hidden?: boolean;
disabled?: boolean;
}
export interface ChromeNavLinks {
getNavLinks$(): Rx.Observable<NavLink>;
getNavLinks(): NavLink[];
navLinkExists(id: string): boolean;
getNavLinkById(id: string): NavLink;
showOnlyById(id: string): void;
untrackNavLinksForDeletedSavedObjects(deletedIds: string[]): void;
trackSubUrlForApp(linkId: string, parsedKibanaUrl: KibanaParsedUrl): void;
enableForcedAppSwitcherNavigation(): this;
getForcedAppSwitcherNavigation$(): Rx.Observable<boolean>;
}

View file

@ -1,177 +0,0 @@
/*
* 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 * as Rx from 'rxjs';
import { mapTo } from 'rxjs/operators';
import { remove } from 'lodash';
import { relativeToAbsolute } from '../../url/relative_to_absolute';
import { absoluteToParsedUrl } from '../../url/absolute_to_parsed_url';
export function initChromeNavApi(chrome, internals) {
const navUpdate$ = new Rx.BehaviorSubject(undefined);
chrome.getNavLinks = function () {
return internals.nav;
};
chrome.getNavLinks$ = function () {
return navUpdate$.pipe(mapTo(internals.nav));
};
// track navLinks with $rootScope.$watch like the old nav used to, necessary
// as long as random parts of the app are directly mutating the navLinks
internals.$initNavLinksDeepWatch = function ($rootScope) {
$rootScope.$watch(
() => internals.nav,
() => navUpdate$.next(),
true
);
};
const forceAppSwitcherNavigation$ = new Rx.BehaviorSubject(false);
/**
* Enable forced navigation mode, which will trigger a page refresh
* when a nav link is clicked and only the hash is updated. This is only
* necessary when rendering the status page in place of another app, as
* links to that app will set the current URL and change the hash, but
* the routes for the correct are not loaded so nothing will happen.
* https://github.com/elastic/kibana/pull/29770
*/
chrome.enableForcedAppSwitcherNavigation = () => {
forceAppSwitcherNavigation$.next(true);
return chrome;
};
chrome.getForceAppSwitcherNavigation$ = () => {
return forceAppSwitcherNavigation$.asObservable();
};
chrome.navLinkExists = (id) => {
return !!internals.nav.find(link => link.id === id);
};
chrome.getNavLinkById = (id) => {
const navLink = internals.nav.find(link => link.id === id);
if (!navLink) {
throw new Error(`Nav link for id = ${id} not found`);
}
return navLink;
};
chrome.showOnlyById = (id) => {
remove(internals.nav, app => app.id !== id);
};
function lastSubUrlKey(link) {
return `lastSubUrl:${link.url}`;
}
function setLastUrl(link, url) {
if (link.linkToLastSubUrl === false) {
return;
}
link.lastSubUrl = url;
internals.appUrlStore.setItem(lastSubUrlKey(link), url);
}
function refreshLastUrl(link) {
link.lastSubUrl = internals.appUrlStore.getItem(lastSubUrlKey(link)) || link.lastSubUrl || link.url;
}
function injectNewGlobalState(link, fromAppId, newGlobalState) {
const kibanaParsedUrl = absoluteToParsedUrl(link.lastSubUrl, chrome.getBasePath());
// don't copy global state if links are for different apps
if (fromAppId !== kibanaParsedUrl.appId) return;
kibanaParsedUrl.setGlobalState(newGlobalState);
link.lastSubUrl = kibanaParsedUrl.getAbsoluteUrl();
}
/**
* Clear last url for deleted saved objects to avoid loading pages with "Could not locate.."
*/
chrome.untrackNavLinksForDeletedSavedObjects = (deletedIds) => {
function urlContainsDeletedId(url) {
const includedId = deletedIds.find(deletedId => {
return url.includes(deletedId);
});
if (includedId === undefined) {
return false;
}
return true;
}
internals.nav.forEach(link => {
if (link.linkToLastSubUrl && urlContainsDeletedId(link.lastSubUrl)) {
setLastUrl(link, link.url);
}
});
};
/**
* Manually sets the last url for the given app. The last url for a given app is updated automatically during
* normal page navigation, so this should only need to be called to insert a last url that was not actually
* navigated to. For instance, when saving an object and redirecting to another page, the last url of the app
* should be the saved instance, but because of the redirect to a different page (e.g. `Save and Add to Dashboard`
* on visualize tab), it won't be tracked automatically and will need to be inserted manually. See
* https://github.com/elastic/kibana/pull/11932 for more background on why this was added.
* @param linkId {String} - an id that represents the navigation link.
* @param kibanaParsedUrl {KibanaParsedUrl} the url to track
*/
chrome.trackSubUrlForApp = (linkId, kibanaParsedUrl) => {
for (const link of internals.nav) {
if (link.id === linkId) {
const absoluteUrl = kibanaParsedUrl.getAbsoluteUrl();
setLastUrl(link, absoluteUrl);
return;
}
}
};
internals.trackPossibleSubUrl = function (url) {
const kibanaParsedUrl = absoluteToParsedUrl(url, chrome.getBasePath());
for (const link of internals.nav) {
link.active = url.startsWith(link.subUrlBase);
if (link.active) {
setLastUrl(link, url);
continue;
}
refreshLastUrl(link);
const newGlobalState = kibanaParsedUrl.getGlobalState();
if (newGlobalState) {
injectNewGlobalState(link, kibanaParsedUrl.appId, newGlobalState);
}
}
};
internals.nav.forEach(link => {
link.url = relativeToAbsolute(chrome.addBasePath(link.url));
link.subUrlBase = relativeToAbsolute(chrome.addBasePath(link.subUrlBase));
});
// simulate a possible change in url to initialize the
// link.active and link.lastUrl properties
internals.trackPossibleSubUrl(document.location.href);
}

View file

@ -0,0 +1,159 @@
/*
* 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 { KibanaParsedUrl } from 'ui/url/kibana_parsed_url';
import { absoluteToParsedUrl } from '../../url/absolute_to_parsed_url';
import { onStart } from '../../new_platform';
import { ChromeStart, ChromeNavLink } from '../../../../../core/public';
import { relativeToAbsolute } from '../../url/relative_to_absolute';
export interface ChromeNavLinks {
untrackNavLinksForDeletedSavedObjects(deletedIds: string[]): void;
trackSubUrlForApp(linkId: string, parsedKibanaUrl: KibanaParsedUrl): void;
}
interface NavInternals {
appUrlStore: Storage;
trackPossibleSubUrl(url: string): void;
}
export function initChromeNavApi(chrome: any, internals: NavInternals) {
let coreNavLinks: ChromeStart['navLinks'];
onStart(({ core }) => (coreNavLinks = core.chrome.navLinks));
/**
* Clear last url for deleted saved objects to avoid loading pages with "Could not locate..."
*/
chrome.untrackNavLinksForDeletedSavedObjects = (deletedIds: string[]) => {
function urlContainsDeletedId(url: string) {
const includedId = deletedIds.find(deletedId => {
return url.includes(deletedId);
});
return includedId !== undefined;
}
coreNavLinks.getAll().forEach(link => {
if (link.linkToLastSubUrl && urlContainsDeletedId(link.url!)) {
setLastUrl(link, link.baseUrl);
}
});
};
/**
* Manually sets the last url for the given app. The last url for a given app is updated automatically during
* normal page navigation, so this should only need to be called to insert a last url that was not actually
* navigated to. For instance, when saving an object and redirecting to another page, the last url of the app
* should be the saved instance, but because of the redirect to a different page (e.g. `Save and Add to Dashboard`
* on visualize tab), it won't be tracked automatically and will need to be inserted manually. See
* https://github.com/elastic/kibana/pull/11932 for more background on why this was added.
*
* @param id {String} - an id that represents the navigation link.
* @param kibanaParsedUrl {KibanaParsedUrl} the url to track
*/
chrome.trackSubUrlForApp = (id: string, kibanaParsedUrl: KibanaParsedUrl) => {
const navLink = coreNavLinks.get(id);
if (navLink) {
setLastUrl(navLink, kibanaParsedUrl.getAbsoluteUrl());
}
};
internals.trackPossibleSubUrl = async function(url: string) {
const kibanaParsedUrl = absoluteToParsedUrl(url, chrome.getBasePath());
coreNavLinks
.getAll()
// Filter only legacy links
.filter(link => link.subUrlBase)
.forEach(link => {
const active = url.startsWith(link.subUrlBase!);
link = coreNavLinks.update(link.id, { active })!;
if (active) {
setLastUrl(link, url);
return;
}
link = refreshLastUrl(link);
const newGlobalState = kibanaParsedUrl.getGlobalState();
if (newGlobalState) {
injectNewGlobalState(link, kibanaParsedUrl.appId, newGlobalState);
}
});
};
function lastSubUrlKey(link: ChromeNavLink) {
return `lastSubUrl:${link.baseUrl}`;
}
function getLastUrl(link: ChromeNavLink) {
return internals.appUrlStore.getItem(lastSubUrlKey(link));
}
function setLastUrl(link: ChromeNavLink, url: string) {
if (link.linkToLastSubUrl === false) {
return;
}
internals.appUrlStore.setItem(lastSubUrlKey(link), url);
refreshLastUrl(link);
}
function refreshLastUrl(link: ChromeNavLink) {
const lastSubUrl = getLastUrl(link);
return coreNavLinks.update(link.id, {
url: lastSubUrl || link.url || link.baseUrl,
})!;
}
function injectNewGlobalState(
link: ChromeNavLink,
fromAppId: string,
newGlobalState: string | string[]
) {
const kibanaParsedUrl = absoluteToParsedUrl(
getLastUrl(link) || link.url || link.baseUrl,
chrome.getBasePath()
);
// don't copy global state if links are for different apps
if (fromAppId !== kibanaParsedUrl.appId) return;
kibanaParsedUrl.setGlobalState(newGlobalState);
coreNavLinks.update(link.id, {
url: kibanaParsedUrl.getAbsoluteUrl(),
});
}
// simulate a possible change in url to initialize the
// link.active and link.lastUrl properties
onStart(({ core }) => {
core.chrome.navLinks
.getAll()
.filter(link => link.subUrlBase)
.forEach(link => {
core.chrome.navLinks.update(link.id, {
subUrlBase: relativeToAbsolute(chrome.addBasePath(link.subUrlBase)),
});
});
internals.trackPossibleSubUrl(document.location.href);
});
}

View file

@ -23,6 +23,7 @@ import {
getUnhashableStatesProvider,
unhashUrl,
} from '../../state_management/state_hashing';
import { onStart } from '../../new_platform';
export function registerSubUrlHooks(angularModule, internals) {
angularModule.run(($rootScope, Private, $location) => {
@ -60,7 +61,7 @@ export function registerSubUrlHooks(angularModule, internals) {
$rootScope.$on('$routeChangeSuccess', onRouteChange);
$rootScope.$on('$routeUpdate', onRouteChange);
updateSubUrls(); // initialize sub urls
onStart(updateSubUrls); // initialize sub urls
});
}

View file

@ -52,8 +52,7 @@ import {
import { i18n } from '@kbn/i18n';
import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
import { UICapabilities } from 'ui/capabilities';
import chrome, { NavLink } from 'ui/chrome';
import chrome from 'ui/chrome';
import { HelpExtension } from 'ui/chrome';
import { RecentlyAccessedHistoryItem } from 'ui/persisted_log';
import { ChromeHeaderNavControlsRegistry } from 'ui/registry/chrome_header_nav_controls';
@ -65,7 +64,7 @@ import { HeaderHelpMenu } from './header_help_menu';
import { HeaderNavControls } from './header_nav_controls';
import { NavControlSide } from '../';
import { ChromeBadge, ChromeBreadcrumb } from '../../../../../../../core/public';
import { ChromeBadge, ChromeBreadcrumb, ChromeNavLink } from '../../../../../../../core/public';
interface Props {
appTitle?: string;
@ -73,13 +72,12 @@ interface Props {
breadcrumbs$: Rx.Observable<ChromeBreadcrumb[]>;
homeHref: string;
isVisible: boolean;
navLinks$: Rx.Observable<NavLink[]>;
navLinks$: Rx.Observable<ChromeNavLink[]>;
recentlyAccessed$: Rx.Observable<RecentlyAccessedHistoryItem[]>;
forceAppSwitcherNavigation$: Rx.Observable<boolean>;
helpExtension$: Rx.Observable<HelpExtension>;
navControls: ChromeHeaderNavControlsRegistry;
intl: InjectedIntl;
uiCapabilities: UICapabilities;
}
// Providing a buffer between the limit and the cut off index
@ -88,11 +86,11 @@ const TRUNCATE_LIMIT: number = 64;
const TRUNCATE_AT: number = 58;
function extendRecentlyAccessedHistoryItem(
navLinks: NavLink[],
navLinks: ChromeNavLink[],
recentlyAccessed: RecentlyAccessedHistoryItem
) {
const href = relativeToAbsolute(chrome.addBasePath(recentlyAccessed.link));
const navLink = navLinks.find(nl => href.startsWith(nl.subUrlBase));
const navLink = navLinks.find(nl => href.startsWith(nl.subUrlBase || nl.baseUrl));
let titleAndAriaLabel = recentlyAccessed.label;
if (navLink) {
@ -114,10 +112,10 @@ function extendRecentlyAccessedHistoryItem(
};
}
function extendNavLink(navLink: NavLink) {
function extendNavLink(navLink: ChromeNavLink) {
return {
...navLink,
href: navLink.lastSubUrl && !navLink.active ? navLink.lastSubUrl : navLink.url,
href: navLink.url && !navLink.active ? navLink.url : navLink.baseUrl,
};
}
@ -224,7 +222,6 @@ class HeaderUI extends Component<Props, State> {
navControls,
helpExtension$,
intl,
uiCapabilities,
} = this.props;
const { navLinks, recentlyAccessed } = this.state;
@ -235,31 +232,28 @@ class HeaderUI extends Component<Props, State> {
const leftNavControls = navControls.bySide[NavControlSide.Left];
const rightNavControls = navControls.bySide[NavControlSide.Right];
let navLinksArray = navLinks.map(navLink =>
navLink.hidden || !uiCapabilities.navLinks[navLink.id]
? null
: {
key: navLink.id,
label: navLink.title,
href: navLink.href,
iconType: navLink.euiIconType,
icon:
!navLink.euiIconType && navLink.icon ? (
<EuiImage
size="s"
alt=""
aria-hidden={true}
url={chrome.addBasePath(`/${navLink.icon}`)}
/>
) : (
undefined
),
isActive: navLink.active,
'data-test-subj': 'navDrawerAppsMenuLink',
}
);
// filter out the null items
navLinksArray = navLinksArray.filter(item => item !== null);
const navLinksArray = navLinks
.filter(navLink => !navLink.hidden)
.map(navLink => ({
key: navLink.id,
label: navLink.title,
href: navLink.href,
isDisabled: navLink.disabled,
isActive: navLink.active,
iconType: navLink.euiIconType,
icon:
!navLink.euiIconType && navLink.icon ? (
<EuiImage
size="s"
alt=""
aria-hidden={true}
url={chrome.addBasePath(`/${navLink.icon}`)}
/>
) : (
undefined
),
'data-test-subj': 'navDrawerAppsMenuLink',
}));
const recentLinksArray = [
{

View file

@ -22,6 +22,7 @@ import { uiModules } from '../../../modules';
import { Header } from './components/header';
import { wrapInI18nContext } from 'ui/i18n';
import { chromeHeaderNavControlsRegistry } from 'ui/registry/chrome_header_nav_controls';
import { getNewPlatform } from '../../../new_platform';
const module = uiModules.get('kibana');
@ -29,6 +30,8 @@ module.directive('headerGlobalNav', (reactDirective, chrome, Private, uiCapabili
const { recentlyAccessed } = require('ui/persisted_log');
const navControls = Private(chromeHeaderNavControlsRegistry);
const homeHref = chrome.addBasePath('/app/kibana#/home');
const newPlatform = getNewPlatform();
const newPlatformStart = newPlatform.start.core;
return reactDirective(wrapInI18nContext(Header), [
// scope accepted by directive, passed in as React props
@ -41,9 +44,9 @@ module.directive('headerGlobalNav', (reactDirective, chrome, Private, uiCapabili
badge$: chrome.badge.get$(),
breadcrumbs$: chrome.breadcrumbs.get$(),
helpExtension$: chrome.helpExtension.get$(),
navLinks$: chrome.getNavLinks$(),
navLinks$: newPlatformStart.chrome.navLinks.getNavLinks$(),
recentlyAccessed$: recentlyAccessed.get$(),
forceAppSwitcherNavigation$: chrome.getForceAppSwitcherNavigation$(),
forceAppSwitcherNavigation$: newPlatformStart.chrome.navLinks.getForceAppSwitcherNavigation$(),
navControls,
homeHref,
uiCapabilities,

View file

@ -55,5 +55,4 @@ declare const chrome: Chrome;
export default chrome;
export { Chrome };
export { Breadcrumb } from './api/breadcrumbs';
export { NavLink } from './api/nav';
export { HelpExtension } from './api/help_extension';

View file

@ -106,7 +106,7 @@ export class KibanaParsedUrl {
return query._g || '';
}
public setGlobalState(newGlobalState: string) {
public setGlobalState(newGlobalState: string | string[]) {
if (!this.appPath) {
return;
}

View file

@ -4,9 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
import chrome from 'ui/chrome';
import { onStart } from 'ui/new_platform';
const apmUiEnabled = chrome.getInjected('apmUiEnabled');
if (apmUiEnabled === false && chrome.navLinkExists('apm')) {
chrome.getNavLinkById('apm').hidden = true;
}
onStart(({ core }) => {
const apmUiEnabled = core.injectedMetadata.getInjectedVar('apmUiEnabled');
if (apmUiEnabled === false) {
core.chrome.navLinks.update('apm', { hidden: true });
}
});

View file

@ -37,6 +37,7 @@ import 'ui/agg_response';
import 'ui/agg_types';
import 'ui/timepicker';
import 'leaflet';
import { getNewPlatform } from 'ui/new_platform';
import { showAppRedirectNotification } from 'ui/notify';
import { DashboardConstants, createDashboardEditUrl } from 'plugins/kibana/dashboard/dashboard_constants';
@ -49,7 +50,7 @@ routes.otherwise({ redirectTo: defaultUrl() });
chrome
.setRootController('kibana', function () {
chrome.showOnlyById('kibana:dashboard');
getNewPlatform().start.core.chrome.navLinks.showOnly('kibana:dashboard');
});
uiModules.get('kibana').run(showAppRedirectNotification);

View file

@ -26,6 +26,7 @@ import { notify, addAppRedirectMessageToUrl, fatalError, toastNotifications } fr
import { IndexPatternsProvider } from 'ui/index_patterns/index_patterns';
import { SavedObjectsClientProvider } from 'ui/saved_objects';
import { KibanaParsedUrl } from 'ui/url/kibana_parsed_url';
import { getNewPlatform } from 'ui/new_platform';
import { XPackInfoProvider } from 'plugins/xpack_main/services/xpack_info';
@ -758,7 +759,7 @@ app.controller('graphuiPlugin', function ($scope, $route, $http, kbnUrl, Private
.on('zoom', redraw));
const managementUrl = chrome.getNavLinkById('kibana:management').url;
const managementUrl = getNewPlatform().start.core.chrome.navLinks.get('kibana:management').url;
const url = `${managementUrl}/kibana/index_patterns`;
if ($scope.indices.length === 0) {

View file

@ -4,23 +4,23 @@
* you may not use this file except in compliance with the Elastic License.
*/
import chrome from 'ui/chrome';
import { uiModules } from 'ui/modules';
import { onStart } from 'ui/new_platform';
import { XPackInfoProvider } from 'plugins/xpack_main/services/xpack_info';
uiModules.get('xpack/graph').run((Private) => {
const xpackInfo = Private(XPackInfoProvider);
if (!chrome.navLinkExists('graph')) {
return;
}
uiModules.get('xpack/graph')
.run(Private => {
const xpackInfo = Private(XPackInfoProvider);
const navLink = chrome.getNavLinkById('graph');
navLink.hidden = true;
const showAppLink = xpackInfo.get('features.graph.showAppLink', false);
navLink.hidden = !showAppLink;
if (showAppLink) {
navLink.disabled = !xpackInfo.get('features.graph.enableAppLink', false);
navLink.tooltip = xpackInfo.get('features.graph.message');
}
});
const navLinkUpdates = {};
navLinkUpdates.hidden = true;
const showAppLink = xpackInfo.get('features.graph.showAppLink', false);
navLinkUpdates.hidden = !showAppLink;
if (showAppLink) {
navLinkUpdates.disabled = !xpackInfo.get('features.graph.enableAppLink', false);
navLinkUpdates.tooltip = xpackInfo.get('features.graph.message');
}
onStart(({ core }) => core.chrome.navLinks.update('graph', navLinkUpdates));
});

View file

@ -7,19 +7,19 @@
import { XPackInfoProvider } from 'plugins/xpack_main/services/xpack_info';
import chrome from 'ui/chrome';
import { uiModules } from 'ui/modules';
import { onStart } from 'ui/new_platform';
uiModules.get('xpack/ml').run((Private) => {
const xpackInfo = Private(XPackInfoProvider);
if (!chrome.navLinkExists('ml')) return;
const navLink = chrome.getNavLinkById('ml');
// hide by default, only show once the xpackInfo is initialized
navLink.hidden = true;
const showAppLink = xpackInfo.get('features.ml.showLinks', false);
navLink.hidden = !showAppLink;
if (showAppLink) {
navLink.disabled = !xpackInfo.get('features.ml.isAvailable', false);
}
const navLinkUpdates = {
// hide by default, only show once the xpackInfo is initialized
hidden: !showAppLink,
disabled: !showAppLink || (showAppLink && !xpackInfo.get('features.ml.isAvailable', false))
};
onStart(({ core }) => core.chrome.navLinks.update('ml', navLinkUpdates));
});

View file

@ -4,13 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
import chrome from 'ui/chrome';
import { uiModules } from 'ui/modules';
import { onStart } from 'ui/new_platform';
uiModules.get('monitoring/hacks').run((monitoringUiEnabled) => {
if (monitoringUiEnabled || !chrome.navLinkExists('monitoring')) {
if (monitoringUiEnabled) {
return;
}
chrome.getNavLinkById('monitoring').hidden = true;
onStart(({ core }) => core.chrome.navLinks.update('monitoring', { hidden: true }));
});

View file

@ -7,7 +7,6 @@
import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { toastNotifications } from 'ui/notify';
import chrome from 'ui/chrome';
import { uiModules } from 'ui/modules';
import { get } from 'lodash';
import { jobQueueClient } from 'plugins/reporting/lib/job_queue_client';
@ -20,6 +19,7 @@ import {
EuiButton,
} from '@elastic/eui';
import { downloadReport } from '../lib/download_report';
import { getNewPlatform } from 'ui/new_platform';
/**
* Poll for changes to reports. Inform the user of changes when the license is active.
@ -59,10 +59,13 @@ uiModules.get('kibana')
let seeReportLink;
const core = getNewPlatform().start.core;
// In-case the license expired/changed between the time they queued the job and the time that
// the job completes, that way we don't give the user a toast to download their report if they can't.
if (chrome.navLinkExists('kibana:management')) {
const managementUrl = chrome.getNavLinkById('kibana:management').url;
// NOTE: this should be looking at configuration rather than the existence of a navLink
if (core.chrome.navLinks.has('kibana:management')) {
const managementUrl = core.chrome.navLinks.get('kibana:management').url;
const reportingSectionUrl = `${managementUrl}/kibana/reporting`;
seeReportLink = (
<p>