diff --git a/docs/development/core/public/kibana-plugin-public.chromenavlink.active.md b/docs/development/core/public/kibana-plugin-public.chromenavlink.active.md new file mode 100644 index 000000000000..510053231398 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.chromenavlink.active.md @@ -0,0 +1,15 @@ + + +[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) > [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. + +Signature: + +```typescript +readonly active?: boolean; +``` diff --git a/docs/development/core/public/kibana-plugin-public.chromenavlink.baseurl.md b/docs/development/core/public/kibana-plugin-public.chromenavlink.baseurl.md new file mode 100644 index 000000000000..5d50e45c9fe5 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.chromenavlink.baseurl.md @@ -0,0 +1,13 @@ + + +[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) > [baseUrl](./kibana-plugin-public.chromenavlink.baseurl.md) + +## ChromeNavLink.baseUrl property + +The base route used to open the root of an application. + +Signature: + +```typescript +readonly baseUrl: string; +``` diff --git a/docs/development/core/public/kibana-plugin-public.chromenavlink.disabled.md b/docs/development/core/public/kibana-plugin-public.chromenavlink.disabled.md new file mode 100644 index 000000000000..87f290573b49 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.chromenavlink.disabled.md @@ -0,0 +1,15 @@ + + +[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) > [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. + +Signature: + +```typescript +readonly disabled?: boolean; +``` diff --git a/docs/development/core/public/kibana-plugin-public.chromenavlink.euiicontype.md b/docs/development/core/public/kibana-plugin-public.chromenavlink.euiicontype.md new file mode 100644 index 000000000000..37d196ae4558 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.chromenavlink.euiicontype.md @@ -0,0 +1,13 @@ + + +[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) > [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. + +Signature: + +```typescript +readonly euiIconType?: string; +``` diff --git a/docs/development/core/public/kibana-plugin-public.chromenavlink.hidden.md b/docs/development/core/public/kibana-plugin-public.chromenavlink.hidden.md new file mode 100644 index 000000000000..cde90415a2df --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.chromenavlink.hidden.md @@ -0,0 +1,15 @@ + + +[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) > [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. + +Signature: + +```typescript +readonly hidden?: boolean; +``` diff --git a/docs/development/core/public/kibana-plugin-public.chromenavlink.icon.md b/docs/development/core/public/kibana-plugin-public.chromenavlink.icon.md new file mode 100644 index 000000000000..05e182e756d7 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.chromenavlink.icon.md @@ -0,0 +1,13 @@ + + +[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) > [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. + +Signature: + +```typescript +readonly icon?: string; +``` diff --git a/docs/development/core/public/kibana-plugin-public.chromenavlink.id.md b/docs/development/core/public/kibana-plugin-public.chromenavlink.id.md new file mode 100644 index 000000000000..179ca9200178 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.chromenavlink.id.md @@ -0,0 +1,13 @@ + + +[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) > [id](./kibana-plugin-public.chromenavlink.id.md) + +## ChromeNavLink.id property + +A unique identifier for looking up links. + +Signature: + +```typescript +readonly id: string; +``` diff --git a/docs/development/core/public/kibana-plugin-public.chromenavlink.linktolastsuburl.md b/docs/development/core/public/kibana-plugin-public.chromenavlink.linktolastsuburl.md new file mode 100644 index 000000000000..9a7f438f289a --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.chromenavlink.linktolastsuburl.md @@ -0,0 +1,15 @@ + + +[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) > [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. + +Signature: + +```typescript +readonly linkToLastSubUrl?: boolean; +``` diff --git a/docs/development/core/public/kibana-plugin-public.chromenavlink.md b/docs/development/core/public/kibana-plugin-public.chromenavlink.md new file mode 100644 index 000000000000..e13efce19c09 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.chromenavlink.md @@ -0,0 +1,31 @@ + + +[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) + +## ChromeNavLink interface + + +Signature: + +```typescript +export interface ChromeNavLink +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [active](./kibana-plugin-public.chromenavlink.active.md) | boolean | 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) | string | The base route used to open the root of an application. | +| [disabled](./kibana-plugin-public.chromenavlink.disabled.md) | 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. | +| [euiIconType](./kibana-plugin-public.chromenavlink.euiicontype.md) | string | A EUI iconType that will be used for the app's icon. This icon takes precendence over the icon property. | +| [hidden](./kibana-plugin-public.chromenavlink.hidden.md) | boolean | 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) | string | A URL to an image file used as an icon. Used as a fallback if euiIconType is not provided. | +| [id](./kibana-plugin-public.chromenavlink.id.md) | string | A unique identifier for looking up links. | +| [linkToLastSubUrl](./kibana-plugin-public.chromenavlink.linktolastsuburl.md) | boolean | Whether or not the subUrl feature should be enabled.NOTE: only read by legacy platform. | +| [order](./kibana-plugin-public.chromenavlink.order.md) | number | An ordinal used to sort nav links relative to one another for display. | +| [subUrlBase](./kibana-plugin-public.chromenavlink.suburlbase.md) | string | 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) | string | The title of the application. | +| [tooltip](./kibana-plugin-public.chromenavlink.tooltip.md) | string | A tooltip shown when hovering over an app link. | +| [url](./kibana-plugin-public.chromenavlink.url.md) | string | 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. | + diff --git a/docs/development/core/public/kibana-plugin-public.chromenavlink.order.md b/docs/development/core/public/kibana-plugin-public.chromenavlink.order.md new file mode 100644 index 000000000000..19c86371e334 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.chromenavlink.order.md @@ -0,0 +1,13 @@ + + +[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) > [order](./kibana-plugin-public.chromenavlink.order.md) + +## ChromeNavLink.order property + +An ordinal used to sort nav links relative to one another for display. + +Signature: + +```typescript +readonly order: number; +``` diff --git a/docs/development/core/public/kibana-plugin-public.chromenavlink.suburlbase.md b/docs/development/core/public/kibana-plugin-public.chromenavlink.suburlbase.md new file mode 100644 index 000000000000..a00984396cc0 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.chromenavlink.suburlbase.md @@ -0,0 +1,15 @@ + + +[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) > [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. + +Signature: + +```typescript +readonly subUrlBase?: string; +``` diff --git a/docs/development/core/public/kibana-plugin-public.chromenavlink.title.md b/docs/development/core/public/kibana-plugin-public.chromenavlink.title.md new file mode 100644 index 000000000000..7c4ff8612f23 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.chromenavlink.title.md @@ -0,0 +1,13 @@ + + +[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) > [title](./kibana-plugin-public.chromenavlink.title.md) + +## ChromeNavLink.title property + +The title of the application. + +Signature: + +```typescript +readonly title: string; +``` diff --git a/docs/development/core/public/kibana-plugin-public.chromenavlink.tooltip.md b/docs/development/core/public/kibana-plugin-public.chromenavlink.tooltip.md new file mode 100644 index 000000000000..c33ca742fae2 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.chromenavlink.tooltip.md @@ -0,0 +1,13 @@ + + +[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) > [tooltip](./kibana-plugin-public.chromenavlink.tooltip.md) + +## ChromeNavLink.tooltip property + +A tooltip shown when hovering over an app link. + +Signature: + +```typescript +readonly tooltip?: string; +``` diff --git a/docs/development/core/public/kibana-plugin-public.chromenavlink.url.md b/docs/development/core/public/kibana-plugin-public.chromenavlink.url.md new file mode 100644 index 000000000000..bba9d83ab434 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.chromenavlink.url.md @@ -0,0 +1,15 @@ + + +[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) > [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. + +Signature: + +```typescript +readonly url?: string; +``` diff --git a/docs/development/core/public/kibana-plugin-public.chromestart.md b/docs/development/core/public/kibana-plugin-public.chromestart.md new file mode 100644 index 000000000000..28ea29dab9b5 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.chromestart.md @@ -0,0 +1,12 @@ + + +[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeStart](./kibana-plugin-public.chromestart.md) + +## ChromeStart type + + +Signature: + +```typescript +export declare type ChromeStart = ReturnType; +``` diff --git a/docs/development/core/public/kibana-plugin-public.corestart.chrome.md b/docs/development/core/public/kibana-plugin-public.corestart.chrome.md new file mode 100644 index 000000000000..9a574edf6b3e --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.corestart.chrome.md @@ -0,0 +1,13 @@ + + +[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [CoreStart](./kibana-plugin-public.corestart.md) > [chrome](./kibana-plugin-public.corestart.chrome.md) + +## CoreStart.chrome property + +[ChromeStart](./kibana-plugin-public.chromestart.md) + +Signature: + +```typescript +chrome: ChromeStart; +``` diff --git a/docs/development/core/public/kibana-plugin-public.corestart.md b/docs/development/core/public/kibana-plugin-public.corestart.md index b9124a185976..031c694b3e50 100644 --- a/docs/development/core/public/kibana-plugin-public.corestart.md +++ b/docs/development/core/public/kibana-plugin-public.corestart.md @@ -18,6 +18,7 @@ export interface CoreStart | --- | --- | --- | | [application](./kibana-plugin-public.corestart.application.md) | ApplicationStart | [ApplicationStart](./kibana-plugin-public.applicationstart.md) | | [basePath](./kibana-plugin-public.corestart.basepath.md) | BasePathStart | [BasePathStart](./kibana-plugin-public.basepathstart.md) | +| [chrome](./kibana-plugin-public.corestart.chrome.md) | ChromeStart | [ChromeStart](./kibana-plugin-public.chromestart.md) | | [http](./kibana-plugin-public.corestart.http.md) | HttpStart | [HttpStart](./kibana-plugin-public.httpstart.md) | | [i18n](./kibana-plugin-public.corestart.i18n.md) | I18nStart | [I18nStart](./kibana-plugin-public.i18nstart.md) | | [injectedMetadata](./kibana-plugin-public.corestart.injectedmetadata.md) | InjectedMetadataStart | [InjectedMetadataStart](./kibana-plugin-public.injectedmetadatastart.md) | diff --git a/docs/development/core/public/kibana-plugin-public.md b/docs/development/core/public/kibana-plugin-public.md index 0e496c982fd7..435b7fa76374 100644 --- a/docs/development/core/public/kibana-plugin-public.md +++ b/docs/development/core/public/kibana-plugin-public.md @@ -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 message and stack 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) | | diff --git a/docs/development/core/public/kibana-plugin-public.pluginstartcontext.chrome.md b/docs/development/core/public/kibana-plugin-public.pluginstartcontext.chrome.md new file mode 100644 index 000000000000..fc458dcef622 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.pluginstartcontext.chrome.md @@ -0,0 +1,11 @@ + + +[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [PluginStartContext](./kibana-plugin-public.pluginstartcontext.md) > [chrome](./kibana-plugin-public.pluginstartcontext.chrome.md) + +## PluginStartContext.chrome property + +Signature: + +```typescript +chrome: ChromeStart; +``` diff --git a/docs/development/core/public/kibana-plugin-public.pluginstartcontext.md b/docs/development/core/public/kibana-plugin-public.pluginstartcontext.md index 6b0fa1be7ea8..05dd4024cc05 100644 --- a/docs/development/core/public/kibana-plugin-public.pluginstartcontext.md +++ b/docs/development/core/public/kibana-plugin-public.pluginstartcontext.md @@ -18,6 +18,7 @@ export interface PluginStartContext | --- | --- | --- | | [application](./kibana-plugin-public.pluginstartcontext.application.md) | Pick<ApplicationStart, 'capabilities'> | | | [basePath](./kibana-plugin-public.pluginstartcontext.basepath.md) | BasePathStart | | +| [chrome](./kibana-plugin-public.pluginstartcontext.chrome.md) | ChromeStart | | | [http](./kibana-plugin-public.pluginstartcontext.http.md) | HttpStart | | | [i18n](./kibana-plugin-public.pluginstartcontext.i18n.md) | I18nStart | | | [notifications](./kibana-plugin-public.pluginstartcontext.notifications.md) | NotificationsStart | | diff --git a/docs/development/core/server/kibana-plugin-server.configservice.atpath.md b/docs/development/core/server/kibana-plugin-server.configservice.atpath.md new file mode 100644 index 000000000000..5ae66deb7485 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.configservice.atpath.md @@ -0,0 +1,25 @@ + + +[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [ConfigService](./kibana-plugin-server.configservice.md) > [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`. + +Signature: + +```typescript +atPath, TConfig>(path: ConfigPath, ConfigClass: ConfigWithSchema): Observable; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| path | ConfigPath | The path to the desired subset of the config. | +| ConfigClass | ConfigWithSchema<TSchema, TConfig> | A class (not an instance of a class) that contains a static schema that we validate the config at the given path against. | + +Returns: + +`Observable` + diff --git a/docs/development/core/server/kibana-plugin-server.configservice.getconfig$.md b/docs/development/core/server/kibana-plugin-server.configservice.getconfig$.md new file mode 100644 index 000000000000..6a9e288a8160 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.configservice.getconfig$.md @@ -0,0 +1,17 @@ + + +[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [ConfigService](./kibana-plugin-server.configservice.md) > [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. + +Signature: + +```typescript +getConfig$(): Observable; +``` +Returns: + +`Observable` + diff --git a/docs/development/core/server/kibana-plugin-server.configservice.getunusedpaths.md b/docs/development/core/server/kibana-plugin-server.configservice.getunusedpaths.md new file mode 100644 index 000000000000..9026672abb78 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.configservice.getunusedpaths.md @@ -0,0 +1,15 @@ + + +[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [ConfigService](./kibana-plugin-server.configservice.md) > [getUnusedPaths](./kibana-plugin-server.configservice.getunusedpaths.md) + +## ConfigService.getUnusedPaths() method + +Signature: + +```typescript +getUnusedPaths(): Promise; +``` +Returns: + +`Promise` + diff --git a/docs/development/core/server/kibana-plugin-server.configservice.getusedpaths.md b/docs/development/core/server/kibana-plugin-server.configservice.getusedpaths.md new file mode 100644 index 000000000000..a29b075a8b3e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.configservice.getusedpaths.md @@ -0,0 +1,15 @@ + + +[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [ConfigService](./kibana-plugin-server.configservice.md) > [getUsedPaths](./kibana-plugin-server.configservice.getusedpaths.md) + +## ConfigService.getUsedPaths() method + +Signature: + +```typescript +getUsedPaths(): Promise; +``` +Returns: + +`Promise` + diff --git a/docs/development/core/server/kibana-plugin-server.configservice.isenabledatpath.md b/docs/development/core/server/kibana-plugin-server.configservice.isenabledatpath.md new file mode 100644 index 000000000000..08c9985145f3 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.configservice.isenabledatpath.md @@ -0,0 +1,22 @@ + + +[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [ConfigService](./kibana-plugin-server.configservice.md) > [isEnabledAtPath](./kibana-plugin-server.configservice.isenabledatpath.md) + +## ConfigService.isEnabledAtPath() method + +Signature: + +```typescript +isEnabledAtPath(path: ConfigPath): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| path | ConfigPath | | + +Returns: + +`Promise` + diff --git a/docs/development/core/server/kibana-plugin-server.configservice.md b/docs/development/core/server/kibana-plugin-server.configservice.md new file mode 100644 index 000000000000..34a2bd9cecaa --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.configservice.md @@ -0,0 +1,24 @@ + + +[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [ConfigService](./kibana-plugin-server.configservice.md) + +## ConfigService class + + +Signature: + +```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 path and validates it against the static schema on the given ConfigClass. | +| [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 atPath, but returns undefined if there is no config at the specified path.[ConfigService.atPath()](./kibana-plugin-server.configservice.atpath.md) | + diff --git a/docs/development/core/server/kibana-plugin-server.configservice.optionalatpath.md b/docs/development/core/server/kibana-plugin-server.configservice.optionalatpath.md new file mode 100644 index 000000000000..8efc64568f83 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.configservice.optionalatpath.md @@ -0,0 +1,27 @@ + + +[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [ConfigService](./kibana-plugin-server.configservice.md) > [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) + +Signature: + +```typescript +optionalAtPath, TConfig>(path: ConfigPath, ConfigClass: ConfigWithSchema): Observable; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| path | ConfigPath | | +| ConfigClass | ConfigWithSchema<TSchema, TConfig> | | + +Returns: + +`Observable` + diff --git a/docs/development/core/server/kibana-plugin-server.corestart.md b/docs/development/core/server/kibana-plugin-server.corestart.md index 90cc4549eb3b..119036da61ee 100644 --- a/docs/development/core/server/kibana-plugin-server.corestart.md +++ b/docs/development/core/server/kibana-plugin-server.corestart.md @@ -4,7 +4,6 @@ ## CoreStart interface - Signature: ```typescript diff --git a/docs/development/core/server/kibana-plugin-server.discoveredplugin.configpath.md b/docs/development/core/server/kibana-plugin-server.discoveredplugin.configpath.md deleted file mode 100644 index a870cd7d7a61..000000000000 --- a/docs/development/core/server/kibana-plugin-server.discoveredplugin.configpath.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [DiscoveredPlugin](./kibana-plugin-server.discoveredplugin.md) > [configPath](./kibana-plugin-server.discoveredplugin.configpath.md) - -## DiscoveredPlugin.configPath property - -Root configuration path used by the plugin, defaults to "id". - -Signature: - -```typescript -readonly configPath: ConfigPath; -``` diff --git a/docs/development/core/server/kibana-plugin-server.discoveredplugin.id.md b/docs/development/core/server/kibana-plugin-server.discoveredplugin.id.md deleted file mode 100644 index 6f75b399ace8..000000000000 --- a/docs/development/core/server/kibana-plugin-server.discoveredplugin.id.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [DiscoveredPlugin](./kibana-plugin-server.discoveredplugin.md) > [id](./kibana-plugin-server.discoveredplugin.id.md) - -## DiscoveredPlugin.id property - -Identifier of the plugin. - -Signature: - -```typescript -readonly id: PluginName; -``` diff --git a/docs/development/core/server/kibana-plugin-server.discoveredplugin.md b/docs/development/core/server/kibana-plugin-server.discoveredplugin.md deleted file mode 100644 index d03d8a8f4a17..000000000000 --- a/docs/development/core/server/kibana-plugin-server.discoveredplugin.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [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. - -Signature: - -```typescript -export interface DiscoveredPlugin -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [configPath](./kibana-plugin-server.discoveredplugin.configpath.md) | ConfigPath | Root configuration path used by the plugin, defaults to "id". | -| [id](./kibana-plugin-server.discoveredplugin.id.md) | PluginName | Identifier of the plugin. | -| [optionalPlugins](./kibana-plugin-server.discoveredplugin.optionalplugins.md) | ReadonlyArray<PluginName> | 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) | ReadonlyArray<PluginName> | An optional list of the other plugins that \*\*must be\*\* installed and enabled for this plugin to function properly. | - diff --git a/docs/development/core/server/kibana-plugin-server.discoveredplugin.optionalplugins.md b/docs/development/core/server/kibana-plugin-server.discoveredplugin.optionalplugins.md deleted file mode 100644 index 1ebc425f44ce..000000000000 --- a/docs/development/core/server/kibana-plugin-server.discoveredplugin.optionalplugins.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [DiscoveredPlugin](./kibana-plugin-server.discoveredplugin.md) > [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. - -Signature: - -```typescript -readonly optionalPlugins: ReadonlyArray; -``` diff --git a/docs/development/core/server/kibana-plugin-server.discoveredplugin.requiredplugins.md b/docs/development/core/server/kibana-plugin-server.discoveredplugin.requiredplugins.md deleted file mode 100644 index 4ca74797289f..000000000000 --- a/docs/development/core/server/kibana-plugin-server.discoveredplugin.requiredplugins.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [DiscoveredPlugin](./kibana-plugin-server.discoveredplugin.md) > [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. - -Signature: - -```typescript -readonly requiredPlugins: ReadonlyArray; -``` diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 027aef60265a..f3e11fca6b28 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -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 asScoped(...)). | +| [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" ClusterClient but exposes additional callAsCurrentUser method that doesn't use credentials of the Kibana internal user (as callAsInternalUser 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 PluginInitializer. | | [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 setup method. | -| [PluginsServiceSetup](./kibana-plugin-server.pluginsservicesetup.md) | | -| [PluginsServiceStart](./kibana-plugin-server.pluginsservicestart.md) | | | [PluginStartContext](./kibana-plugin-server.pluginstartcontext.md) | Context passed to the plugins start method. | ## Type Aliases diff --git a/docs/development/core/server/kibana-plugin-server.pluginsservicesetup.contracts.md b/docs/development/core/server/kibana-plugin-server.pluginsservicesetup.contracts.md deleted file mode 100644 index 090693a814c1..000000000000 --- a/docs/development/core/server/kibana-plugin-server.pluginsservicesetup.contracts.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [PluginsServiceSetup](./kibana-plugin-server.pluginsservicesetup.md) > [contracts](./kibana-plugin-server.pluginsservicesetup.contracts.md) - -## PluginsServiceSetup.contracts property - -Signature: - -```typescript -contracts: Map; -``` diff --git a/docs/development/core/server/kibana-plugin-server.pluginsservicesetup.md b/docs/development/core/server/kibana-plugin-server.pluginsservicesetup.md deleted file mode 100644 index c1b3c739d80f..000000000000 --- a/docs/development/core/server/kibana-plugin-server.pluginsservicesetup.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [PluginsServiceSetup](./kibana-plugin-server.pluginsservicesetup.md) - -## PluginsServiceSetup interface - - -Signature: - -```typescript -export interface PluginsServiceSetup -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [contracts](./kibana-plugin-server.pluginsservicesetup.contracts.md) | Map<PluginName, unknown> | | -| [uiPlugins](./kibana-plugin-server.pluginsservicesetup.uiplugins.md) | {`

` public: Map<PluginName, DiscoveredPlugin>;`

` internal: Map<PluginName, DiscoveredPluginInternal>;`

` } | | - diff --git a/docs/development/core/server/kibana-plugin-server.pluginsservicesetup.uiplugins.md b/docs/development/core/server/kibana-plugin-server.pluginsservicesetup.uiplugins.md deleted file mode 100644 index ff63944fdf64..000000000000 --- a/docs/development/core/server/kibana-plugin-server.pluginsservicesetup.uiplugins.md +++ /dev/null @@ -1,14 +0,0 @@ - - -[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [PluginsServiceSetup](./kibana-plugin-server.pluginsservicesetup.md) > [uiPlugins](./kibana-plugin-server.pluginsservicesetup.uiplugins.md) - -## PluginsServiceSetup.uiPlugins property - -Signature: - -```typescript -uiPlugins: { - public: Map; - internal: Map; - }; -``` diff --git a/docs/development/core/server/kibana-plugin-server.pluginsservicestart.contracts.md b/docs/development/core/server/kibana-plugin-server.pluginsservicestart.contracts.md deleted file mode 100644 index f6316e777264..000000000000 --- a/docs/development/core/server/kibana-plugin-server.pluginsservicestart.contracts.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [PluginsServiceStart](./kibana-plugin-server.pluginsservicestart.md) > [contracts](./kibana-plugin-server.pluginsservicestart.contracts.md) - -## PluginsServiceStart.contracts property - -Signature: - -```typescript -contracts: Map; -``` diff --git a/docs/development/core/server/kibana-plugin-server.pluginsservicestart.md b/docs/development/core/server/kibana-plugin-server.pluginsservicestart.md deleted file mode 100644 index 6e7354d3ea40..000000000000 --- a/docs/development/core/server/kibana-plugin-server.pluginsservicestart.md +++ /dev/null @@ -1,19 +0,0 @@ - - -[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [PluginsServiceStart](./kibana-plugin-server.pluginsservicestart.md) - -## PluginsServiceStart interface - - -Signature: - -```typescript -export interface PluginsServiceStart -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [contracts](./kibana-plugin-server.pluginsservicestart.contracts.md) | Map<PluginName, unknown> | | - diff --git a/src/core/public/application/application_service.tsx b/src/core/public/application/application_service.tsx index 7a8fa20e1385..d195984552e6 100644 --- a/src/core/public/application/application_service.tsx +++ b/src/core/public/application/application_service.tsx @@ -76,8 +76,8 @@ export interface App extends BaseApp { /** @internal */ export interface LegacyApp extends BaseApp { appUrl: string; - - url?: string; + subUrlBase?: string; + linkToLastSubUrl?: boolean; } /** @internal */ diff --git a/src/core/public/chrome/chrome_service.mock.ts b/src/core/public/chrome/chrome_service.mock.ts index bef316968555..a56c2b63ca49 100644 --- a/src/core/public/chrome/chrome_service.mock.ts +++ b/src/core/public/chrome/chrome_service.mock.ts @@ -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 => ({ + 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; const createMock = () => { const mocked: jest.Mocked = { 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, }; diff --git a/src/core/public/chrome/chrome_service.ts b/src/core/public/chrome/chrome_service.ts index 1bd8a8b1ef36..37146b05f920 100644 --- a/src/core/public/chrome/chrome_service.ts +++ b/src/core/public/chrome/chrome_service.ts @@ -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; + +/** @public */ +export type ChromeStart = ReturnType; diff --git a/src/core/public/chrome/index.ts b/src/core/public/chrome/index.ts index fc816b8dd136..77008f5bc534 100644 --- a/src/core/public/chrome/index.ts +++ b/src/core/public/chrome/index.ts @@ -22,6 +22,8 @@ export { ChromeBreadcrumb, ChromeService, ChromeSetup, + ChromeStart, ChromeBrand, ChromeHelpExtension, } from './chrome_service'; +export { ChromeNavLink } from './nav_links'; diff --git a/src/legacy/core_plugins/timelion/public/hacks/toggle_app_link_in_nav.js b/src/core/public/chrome/nav_links/index.ts similarity index 78% rename from src/legacy/core_plugins/timelion/public/hacks/toggle_app_link_in_nav.js rename to src/core/public/chrome/nav_links/index.ts index e3b68ddf0228..8060d5cab23e 100644 --- a/src/legacy/core_plugins/timelion/public/hacks/toggle_app_link_in_nav.js +++ b/src/core/public/chrome/nav_links/index.ts @@ -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'; diff --git a/src/core/public/chrome/nav_links/nav_link.ts b/src/core/public/chrome/nav_links/nav_link.ts new file mode 100644 index 000000000000..a2f75b220b31 --- /dev/null +++ b/src/core/public/chrome/nav_links/nav_link.ts @@ -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 +>; + +export class NavLinkWrapper { + public readonly id: string; + public readonly properties: Readonly; + + 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 }); + } +} diff --git a/src/core/public/chrome/nav_links/nav_links_service.test.ts b/src/core/public/chrome/nav_links/nav_links_service.test.ts new file mode 100644 index 000000000000..9acc1bc132da --- /dev/null +++ b/src/core/public/chrome/nav_links/nav_links_service.test.ts @@ -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; + + 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); + }); + }); +}); diff --git a/src/core/public/chrome/nav_links/nav_links_service.ts b/src/core/public/chrome/nav_links/nav_links_service.ts new file mode 100644 index 000000000000..156d8e2d0157 --- /dev/null +++ b/src/core/public/chrome/nav_links/nav_links_service.ts @@ -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>( + 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) { + 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; +} diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index b0101c2ad6d5..4002eafba7b0 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -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, diff --git a/src/core/public/index.ts b/src/core/public/index.ts index a87f03bfd972..c99ff82fa560 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -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, diff --git a/src/core/public/legacy/legacy_service.test.ts b/src/core/public/legacy/legacy_service.test.ts index 4007a8128f65..e2cdcfe71b09 100644 --- a/src/core/public/legacy/legacy_service.test.ts +++ b/src/core/public/legacy/legacy_service.test.ts @@ -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, diff --git a/src/core/public/legacy/legacy_service.ts b/src/core/public/legacy/legacy_service.ts index 6e6f25649742..c9e90376e976 100644 --- a/src/core/public/legacy/legacy_service.ts +++ b/src/core/public/legacy/legacy_service.ts @@ -91,6 +91,8 @@ export class LegacyPlatformService { euiIconType: navLink.euiIconType, icon: navLink.icon, appUrl: navLink.url, + subUrlBase: navLink.subUrlBase, + linkToLastSubUrl: navLink.linkToLastSubUrl, }) ); diff --git a/src/core/public/plugins/plugin_context.ts b/src/core/public/plugins/plugin_context.ts index aae002a082bf..dd74dadb36a1 100644 --- a/src/core/public/plugins/plugin_context.ts +++ b/src/core/public/plugins/plugin_context.ts @@ -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; + chrome: ChromeStart; basePath: BasePathStart; http: HttpStart; i18n: I18nStart; @@ -128,6 +129,7 @@ export function createPluginStartContext { mockStartDeps = { application: applicationServiceMock.createStartContract(), basePath: basePathServiceMock.createStartContract(), + chrome: chromeServiceMock.createStartContract(), http: httpServiceMock.createStartContract(), i18n: i18nServiceMock.createStartContract(), injectedMetadata: injectedMetadataServiceMock.createStartContract(), diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index da2d365fc593..d1d88a64558b 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -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; +// @public (undocumented) +export type ChromeStart = ReturnType; + // @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; diff --git a/src/legacy/core_plugins/kibana/public/context/index.js b/src/legacy/core_plugins/kibana/public/context/index.js index d1908be4bea7..8cce2c4e73e3 100644 --- a/src/legacy/core_plugins/kibana/public/context/index.js +++ b/src/legacy/core_plugins/kibana/public/context/index.js @@ -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()); } diff --git a/src/legacy/core_plugins/kibana/public/dev_tools/hacks/__tests__/hide_empty_tools.js b/src/legacy/core_plugins/kibana/public/dev_tools/hacks/__tests__/hide_empty_tools.js index bf69fa09823f..03c86767067b 100644 --- a/src/legacy/core_plugins/kibana/public/dev_tools/hacks/__tests__/hide_empty_tools.js +++ b/src/legacy/core_plugins/kibana/public/dev_tools/hacks/__tests__/hide_empty_tools.js @@ -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(); }); }); diff --git a/src/legacy/core_plugins/kibana/public/dev_tools/hacks/hide_empty_tools.js b/src/legacy/core_plugins/kibana/public/dev_tools/hacks/hide_empty_tools.js index 3426932ea5e7..c4b07f95f26e 100644 --- a/src/legacy/core_plugins/kibana/public/dev_tools/hacks/hide_empty_tools.js +++ b/src/legacy/core_plugins/kibana/public/dev_tools/hacks/hide_empty_tools.js @@ -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 + }); } } diff --git a/src/legacy/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.js b/src/legacy/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.js index ec531600bd21..4d188030b731 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.js +++ b/src/legacy/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.js @@ -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 = ( diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index af7f9c8d34a6..fe5df3a8de54 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -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); diff --git a/src/legacy/core_plugins/status_page/public/status_page.js b/src/legacy/core_plugins/status_page/public/status_page.js index 7aa770b076cf..140550256303 100644 --- a/src/legacy/core_plugins/status_page/public/status_page.js +++ b/src/legacy/core_plugins/status_page/public/status_page.js @@ -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(() => { diff --git a/src/legacy/core_plugins/tests_bundle/tests_entry_template.js b/src/legacy/core_plugins/tests_bundle/tests_entry_template.js index 79ed7c4e98b5..73dc777b317d 100644 --- a/src/legacy/core_plugins/tests_bundle/tests_entry_template.js +++ b/src/legacy/core_plugins/tests_bundle/tests_entry_template.js @@ -94,7 +94,8 @@ const coreSystem = new CoreSystem({ uiSettings: { defaults: ${JSON.stringify(defaultUiSettings, null, 2).split('\n').join('\n ')}, user: {} - } + }, + nav: [] }, csp: { warnLegacyBrowsers: false, diff --git a/src/legacy/core_plugins/timelion/public/hacks/toggle_app_link_in_nav.ts b/src/legacy/core_plugins/timelion/public/hacks/toggle_app_link_in_nav.ts new file mode 100644 index 000000000000..8aa8940c5bbd --- /dev/null +++ b/src/legacy/core_plugins/timelion/public/hacks/toggle_app_link_in_nav.ts @@ -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 }); + } +}); diff --git a/src/legacy/ui/public/chrome/api/__tests__/nav.js b/src/legacy/ui/public/chrome/api/__tests__/nav.js index faf43058259e..ff20f6958618 100644 --- a/src/legacy/ui/public/chrome/api/__tests__/nav.js +++ b/src/legacy/ui/public/chrome/api/__tests__/nav.js @@ -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); }); }); }); diff --git a/src/legacy/ui/public/chrome/api/angular.js b/src/legacy/ui/public/chrome/api/angular.js index d01ed1a35561..e6457fec9363 100644 --- a/src/legacy/ui/public/chrome/api/angular.js +++ b/src/legacy/ui/public/chrome/api/angular.js @@ -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); diff --git a/src/legacy/ui/public/chrome/api/nav.d.ts b/src/legacy/ui/public/chrome/api/nav.d.ts deleted file mode 100644 index f7c639bc5733..000000000000 --- a/src/legacy/ui/public/chrome/api/nav.d.ts +++ /dev/null @@ -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; - 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; -} diff --git a/src/legacy/ui/public/chrome/api/nav.js b/src/legacy/ui/public/chrome/api/nav.js deleted file mode 100644 index 1feb0a8f22e0..000000000000 --- a/src/legacy/ui/public/chrome/api/nav.js +++ /dev/null @@ -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); -} diff --git a/src/legacy/ui/public/chrome/api/nav.ts b/src/legacy/ui/public/chrome/api/nav.ts new file mode 100644 index 000000000000..8a4504125d82 --- /dev/null +++ b/src/legacy/ui/public/chrome/api/nav.ts @@ -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); + }); +} diff --git a/src/legacy/ui/public/chrome/api/sub_url_hooks.js b/src/legacy/ui/public/chrome/api/sub_url_hooks.js index beb762a1235e..5ed3af9b59ff 100644 --- a/src/legacy/ui/public/chrome/api/sub_url_hooks.js +++ b/src/legacy/ui/public/chrome/api/sub_url_hooks.js @@ -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 }); } diff --git a/src/legacy/ui/public/chrome/directives/header_global_nav/components/header.tsx b/src/legacy/ui/public/chrome/directives/header_global_nav/components/header.tsx index af593384ae16..4c9c4001a9a4 100644 --- a/src/legacy/ui/public/chrome/directives/header_global_nav/components/header.tsx +++ b/src/legacy/ui/public/chrome/directives/header_global_nav/components/header.tsx @@ -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; homeHref: string; isVisible: boolean; - navLinks$: Rx.Observable; + navLinks$: Rx.Observable; recentlyAccessed$: Rx.Observable; forceAppSwitcherNavigation$: Rx.Observable; helpExtension$: Rx.Observable; 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 { navControls, helpExtension$, intl, - uiCapabilities, } = this.props; const { navLinks, recentlyAccessed } = this.state; @@ -235,31 +232,28 @@ class HeaderUI extends Component { 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 ? ( - - ) : ( - 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 ? ( + + ) : ( + undefined + ), + 'data-test-subj': 'navDrawerAppsMenuLink', + })); const recentLinksArray = [ { diff --git a/src/legacy/ui/public/chrome/directives/header_global_nav/header_global_nav.js b/src/legacy/ui/public/chrome/directives/header_global_nav/header_global_nav.js index f4b5022089b4..d2e16cf7dad4 100644 --- a/src/legacy/ui/public/chrome/directives/header_global_nav/header_global_nav.js +++ b/src/legacy/ui/public/chrome/directives/header_global_nav/header_global_nav.js @@ -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, diff --git a/src/legacy/ui/public/chrome/index.d.ts b/src/legacy/ui/public/chrome/index.d.ts index 71532fbbebcc..7f61c7a2ca8c 100644 --- a/src/legacy/ui/public/chrome/index.d.ts +++ b/src/legacy/ui/public/chrome/index.d.ts @@ -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'; diff --git a/src/legacy/ui/public/url/kibana_parsed_url.ts b/src/legacy/ui/public/url/kibana_parsed_url.ts index 7f1653a9f8d8..d431c775d1d2 100644 --- a/src/legacy/ui/public/url/kibana_parsed_url.ts +++ b/src/legacy/ui/public/url/kibana_parsed_url.ts @@ -106,7 +106,7 @@ export class KibanaParsedUrl { return query._g || ''; } - public setGlobalState(newGlobalState: string) { + public setGlobalState(newGlobalState: string | string[]) { if (!this.appPath) { return; } diff --git a/x-pack/plugins/apm/public/hacks/toggle_app_link_in_nav.js b/x-pack/plugins/apm/public/hacks/toggle_app_link_in_nav.ts similarity index 50% rename from x-pack/plugins/apm/public/hacks/toggle_app_link_in_nav.js rename to x-pack/plugins/apm/public/hacks/toggle_app_link_in_nav.ts index 90a8f31364f5..a4304876c7ec 100644 --- a/x-pack/plugins/apm/public/hacks/toggle_app_link_in_nav.js +++ b/x-pack/plugins/apm/public/hacks/toggle_app_link_in_nav.ts @@ -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 }); + } +}); diff --git a/x-pack/plugins/dashboard_mode/public/dashboard_viewer.js b/x-pack/plugins/dashboard_mode/public/dashboard_viewer.js index a653804d634b..8b04e8ee82c8 100644 --- a/x-pack/plugins/dashboard_mode/public/dashboard_viewer.js +++ b/x-pack/plugins/dashboard_mode/public/dashboard_viewer.js @@ -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); diff --git a/x-pack/plugins/graph/public/app.js b/x-pack/plugins/graph/public/app.js index 837e40815fb8..17c8760ab2e2 100644 --- a/x-pack/plugins/graph/public/app.js +++ b/x-pack/plugins/graph/public/app.js @@ -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) { diff --git a/x-pack/plugins/graph/public/hacks/toggle_app_link_in_nav.js b/x-pack/plugins/graph/public/hacks/toggle_app_link_in_nav.js index 70162e321f71..5d6e406e9e85 100644 --- a/x-pack/plugins/graph/public/hacks/toggle_app_link_in_nav.js +++ b/x-pack/plugins/graph/public/hacks/toggle_app_link_in_nav.js @@ -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)); + }); diff --git a/x-pack/plugins/ml/public/hacks/toggle_app_link_in_nav.js b/x-pack/plugins/ml/public/hacks/toggle_app_link_in_nav.js index bed0fe46c323..27c5e28b3300 100644 --- a/x-pack/plugins/ml/public/hacks/toggle_app_link_in_nav.js +++ b/x-pack/plugins/ml/public/hacks/toggle_app_link_in_nav.js @@ -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)); }); diff --git a/x-pack/plugins/monitoring/public/hacks/toggle_app_link_in_nav.js b/x-pack/plugins/monitoring/public/hacks/toggle_app_link_in_nav.js index 451793b83dd6..c68d0d37b77c 100644 --- a/x-pack/plugins/monitoring/public/hacks/toggle_app_link_in_nav.js +++ b/x-pack/plugins/monitoring/public/hacks/toggle_app_link_in_nav.js @@ -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 })); }); diff --git a/x-pack/plugins/reporting/public/hacks/job_completion_notifier.js b/x-pack/plugins/reporting/public/hacks/job_completion_notifier.js index 505ef204f4d0..42b63b2d4206 100644 --- a/x-pack/plugins/reporting/public/hacks/job_completion_notifier.js +++ b/x-pack/plugins/reporting/public/hacks/job_completion_notifier.js @@ -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 = (