From 82e32faf1a1ee3818ed028946e67f5d34d44e509 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Mon, 28 Jun 2021 21:44:11 +0200 Subject: [PATCH] Locator docs (#103129) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 add locator_examples plugin * feat: 🎸 add example app in locator_examples * feat: 🎸 add locator_explorer plugin * chore: 🤖 remove url_generaotrs_* example plugins * docs: ✏️ update share plugin readme * docs: ✏️ add locators readme * docs: ✏️ update docs link in example plugin * docs: ✏️ update navigation docs * fix: 🐛 make P extend SerializableState * test: 💍 update test mocks * fix: 🐛 use correct type in ingest pipeline locator * test: 💍 add missing methods in mock * test: 💍 update test mocks * chore: 🤖 update plugin list Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../best-practices/navigation.asciidoc | 18 +- docs/developer/plugin-list.asciidoc | 3 +- examples/locator_examples/README.md | 8 + .../kibana.json | 4 +- .../public/app.tsx | 0 .../public/index.ts | 12 +- examples/locator_examples/public/locator.ts | 54 ++++++ .../public/plugin.tsx | 41 ++--- .../tsconfig.json | 0 .../README.md | 5 +- .../kibana.json | 4 +- .../public/app.tsx | 77 ++++---- .../public/index.ts | 4 +- .../public/page.tsx | 0 .../public/plugin.tsx | 32 ++-- .../tsconfig.json | 0 examples/url_generators_examples/README.md | 7 - .../public/url_generator.ts | 68 ------- src/plugins/discover/public/mocks.ts | 8 + src/plugins/management/public/mocks/index.ts | 4 + src/plugins/share/README.md | 33 ++-- .../common/url_service/locators/README.md | 166 ++++++++++++++++++ .../url_service/locators/locator_client.ts | 2 +- .../common/url_service/locators/types.ts | 4 +- .../explore_data_chart_action.test.ts | 4 + .../explore_data_context_menu_action.test.ts | 4 + .../ingest_pipelines/public/locator.test.ts | 4 + .../ingest_pipelines/public/locator.ts | 2 +- 28 files changed, 380 insertions(+), 188 deletions(-) create mode 100644 examples/locator_examples/README.md rename examples/{url_generators_examples => locator_examples}/kibana.json (74%) rename examples/{url_generators_examples => locator_examples}/public/app.tsx (100%) rename examples/{url_generators_explorer => locator_examples}/public/index.ts (59%) create mode 100644 examples/locator_examples/public/locator.ts rename examples/{url_generators_examples => locator_examples}/public/plugin.tsx (53%) rename examples/{url_generators_examples => locator_examples}/tsconfig.json (100%) rename examples/{url_generators_explorer => locator_explorer}/README.md (79%) rename examples/{url_generators_explorer => locator_explorer}/kibana.json (50%) rename examples/{url_generators_explorer => locator_explorer}/public/app.tsx (68%) rename examples/{url_generators_examples => locator_explorer}/public/index.ts (75%) rename examples/{url_generators_explorer => locator_explorer}/public/page.tsx (100%) rename examples/{url_generators_explorer => locator_explorer}/public/plugin.tsx (63%) rename examples/{url_generators_explorer => locator_explorer}/tsconfig.json (100%) delete mode 100644 examples/url_generators_examples/README.md delete mode 100644 examples/url_generators_examples/public/url_generator.ts create mode 100644 src/plugins/share/common/url_service/locators/README.md diff --git a/docs/developer/best-practices/navigation.asciidoc b/docs/developer/best-practices/navigation.asciidoc index d01f2c2aa0f95..32946a2f74bd9 100644 --- a/docs/developer/best-practices/navigation.asciidoc +++ b/docs/developer/best-practices/navigation.asciidoc @@ -47,24 +47,26 @@ console.log(discoverUrl); // http://localhost:5601/bpr/s/space/app/discover const discoverUrlWithSomeState = core.http.basePath.prepend(`/discover#/?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:'2020-09-10T11:39:50.203Z',to:'2020-09-10T11:40:20.249Z'))&_a=(columns:!(_source),filters:!(),index:'90943e30-9a47-11e8-b64d-95841ca0b247',interval:auto,query:(language:kuery,query:''),sort:!())`); ---- -Instead, each app should expose {kib-repo}tree/{branch}/src/plugins/share/public/url_generators/README.md[a URL generator]. -Other apps should use those URL generators for creating URLs. +Instead, each app should expose {kib-repo}tree/{branch}/src/plugins/share/common/url_service/locators/README.md[a locator]. +Other apps should use those locators for navigation or URL creation. [source,typescript jsx] ---- -// Properly generated URL to *Discover* app. Generator code is owned by *Discover* app and available on *Discover*'s plugin contract. -const discoverUrl = discoverUrlGenerator.createUrl({filters, timeRange}); +// Properly generated URL to *Discover* app. Locator code is owned by *Discover* app and available on *Discover*'s plugin contract. +const discoverUrl = await plugins.discover.locator.getUrl({filters, timeRange}); +// or directly execute navigation +await plugins.discover.locator.navigate({filters, timeRange}); ---- -To get a better idea, take a look at *Discover* URL generator {kib-repo}tree/{branch}/src/plugins/discover/public/url_generator.ts[implementation]. +To get a better idea, take a look at *Discover* locator {kib-repo}tree/{branch}/src/plugins/discover/public/locator.ts[implementation]. It allows specifying various **Discover** app state pieces like: index pattern, filters, query, time range and more. -There are two ways to access other's app URL generator in your code: +There are two ways to access locators of other apps: 1. From a plugin contract of a destination app *(preferred)*. -2. Using URL generator service instance on `share` plugin contract (in case an explicit plugin dependency is not possible). +2. Using locator client in `share` plugin (case an explicit plugin dependency is not possible). -In case you want other apps to link to your app, then you should create a URL generator and expose it on your plugin's contract. +In case you want other apps to link to your app, then you should create a locator and expose it on your plugin's contract. [[navigating-between-kibana-apps]] diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 96326b739422f..231e089950a28 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -191,7 +191,8 @@ so they can properly protect the data within their clusters. |{kib-repo}blob/{branch}/src/plugins/share/README.md[share] -|Replaces the legacy ui/share module for registering share context menus. +|The share plugin contains various utilities for displaying sharing context menu, +generating deep links to other apps, and creating short URLs. |{kib-repo}blob/{branch}/src/plugins/spaces_oss/README.md[spacesOss] diff --git a/examples/locator_examples/README.md b/examples/locator_examples/README.md new file mode 100644 index 0000000000000..bcc1b19f689d1 --- /dev/null +++ b/examples/locator_examples/README.md @@ -0,0 +1,8 @@ +# Locator examples + +This example plugin shows how to: + + - Register a URL locator. + - Return locator from plugin contract. + +To run this example, use the command `yarn start --run-examples`. Navigate to the locator app. diff --git a/examples/url_generators_examples/kibana.json b/examples/locator_examples/kibana.json similarity index 74% rename from examples/url_generators_examples/kibana.json rename to examples/locator_examples/kibana.json index 9658f5c7300aa..df336b2ab3613 100644 --- a/examples/url_generators_examples/kibana.json +++ b/examples/locator_examples/kibana.json @@ -1,5 +1,5 @@ { - "id": "urlGeneratorsExamples", + "id": "locatorExamples", "version": "0.0.1", "kibanaVersion": "kibana", "server": false, @@ -7,6 +7,6 @@ "requiredPlugins": ["share"], "optionalPlugins": [], "extraPublicDirs": [ - "public/url_generator" + "public/locator" ] } diff --git a/examples/url_generators_examples/public/app.tsx b/examples/locator_examples/public/app.tsx similarity index 100% rename from examples/url_generators_examples/public/app.tsx rename to examples/locator_examples/public/app.tsx diff --git a/examples/url_generators_explorer/public/index.ts b/examples/locator_examples/public/index.ts similarity index 59% rename from examples/url_generators_explorer/public/index.ts rename to examples/locator_examples/public/index.ts index 8a78f3214453d..50da3501805fa 100644 --- a/examples/url_generators_explorer/public/index.ts +++ b/examples/locator_examples/public/index.ts @@ -6,6 +6,14 @@ * Side Public License, v 1. */ -import { AccessLinksExplorerPlugin } from './plugin'; +import { LocatorExamplesPlugin } from './plugin'; -export const plugin = () => new AccessLinksExplorerPlugin(); +export { + HelloLocator, + HelloLocatorV1Params, + HelloLocatorV2Params, + HelloLocatorParams, + HELLO_LOCATOR, +} from './locator'; + +export const plugin = () => new LocatorExamplesPlugin(); diff --git a/examples/locator_examples/public/locator.ts b/examples/locator_examples/public/locator.ts new file mode 100644 index 0000000000000..18caeca08564e --- /dev/null +++ b/examples/locator_examples/public/locator.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { SerializableState, MigrateFunction } from 'src/plugins/kibana_utils/common'; +import { LocatorDefinition, LocatorPublic } from '../../../src/plugins/share/public'; + +export const HELLO_LOCATOR = 'HELLO_LOCATOR'; + +export interface HelloLocatorV1Params extends SerializableState { + name: string; +} + +export interface HelloLocatorV2Params extends SerializableState { + firstName: string; + lastName: string; +} + +export type HelloLocatorParams = HelloLocatorV2Params; + +const migrateV1ToV2: MigrateFunction = ( + v1: HelloLocatorV1Params +) => { + const v2: HelloLocatorV2Params = { + firstName: v1.name, + lastName: '', + }; + + return v2; +}; + +export type HelloLocator = LocatorPublic; + +export class HelloLocatorDefinition implements LocatorDefinition { + public readonly id = HELLO_LOCATOR; + + public readonly getLocation = async ({ firstName, lastName }: HelloLocatorParams) => { + return { + app: 'locatorExamples', + path: `/hello?firstName=${encodeURIComponent(firstName)}&lastName=${encodeURIComponent( + lastName + )}`, + state: {}, + }; + }; + + public readonly migrations = { + '0.0.2': (migrateV1ToV2 as unknown) as MigrateFunction, + }; +} diff --git a/examples/url_generators_examples/public/plugin.tsx b/examples/locator_examples/public/plugin.tsx similarity index 53% rename from examples/url_generators_examples/public/plugin.tsx rename to examples/locator_examples/public/plugin.tsx index f797c92d4c902..4364c46e6138c 100644 --- a/examples/url_generators_examples/public/plugin.tsx +++ b/examples/locator_examples/public/plugin.tsx @@ -8,44 +8,27 @@ import { SharePluginStart, SharePluginSetup } from '../../../src/plugins/share/public'; import { Plugin, CoreSetup, AppMountParameters, AppNavLinkStatus } from '../../../src/core/public'; -import { - HelloLinkGeneratorState, - createHelloPageLinkGenerator, - LegacyHelloLinkGeneratorState, - HELLO_URL_GENERATOR_V1, - HELLO_URL_GENERATOR, - helloPageLinkGeneratorV1, -} from './url_generator'; +import { HelloLocator, HelloLocatorDefinition } from './locator'; -declare module '../../../src/plugins/share/public' { - export interface UrlGeneratorStateMapping { - [HELLO_URL_GENERATOR_V1]: LegacyHelloLinkGeneratorState; - [HELLO_URL_GENERATOR]: HelloLinkGeneratorState; - } +interface SetupDeps { + share: SharePluginSetup; } interface StartDeps { share: SharePluginStart; } -interface SetupDeps { - share: SharePluginSetup; +export interface LocatorExamplesSetup { + locator: HelloLocator; } -const APP_ID = 'urlGeneratorsExamples'; - -export class AccessLinksExamplesPlugin implements Plugin { - public setup(core: CoreSetup, { share: { urlGenerators } }: SetupDeps) { - urlGenerators.registerUrlGenerator( - createHelloPageLinkGenerator(async () => ({ - appBasePath: (await core.getStartServices())[0].application.getUrlForApp(APP_ID), - })) - ); - - urlGenerators.registerUrlGenerator(helloPageLinkGeneratorV1); +export class LocatorExamplesPlugin + implements Plugin { + public setup(core: CoreSetup, plugins: SetupDeps) { + const locator = plugins.share.url.locators.create(new HelloLocatorDefinition()); core.application.register({ - id: APP_ID, + id: 'locatorExamples', title: 'Access links examples', navLinkStatus: AppNavLinkStatus.hidden, async mount(params: AppMountParameters) { @@ -58,6 +41,10 @@ export class AccessLinksExamplesPlugin implements Plugin { +const ActionsExplorer = ({ share }: Props) => { const [migratedLinks, setMigratedLinks] = useState([] as MigratedLink[]); const [buildingLinks, setBuildingLinks] = useState(false); const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); + /** * Lets pretend we grabbed these links from a persistent store, like a saved object. - * Some of these links were created with older versions of the hello link generator. - * They use deprecated generator ids. + * Some of these links were created with older versions of the hello locator. */ - const [persistedLinks, setPersistedLinks] = useState([ + const [persistedLinks, setPersistedLinks] = useState< + Array<{ + id: string; + version: string; + linkText: string; + params: HelloLocatorV1Params | HelloLocatorV2Params; + }> + >([ { - id: HELLO_URL_GENERATOR_V1, + id: HELLO_LOCATOR, + version: '0.0.1', linkText: 'Say hello to Mary', - state: { + params: { name: 'Mary', }, }, { - id: HELLO_URL_GENERATOR, + id: HELLO_LOCATOR, + version: '0.0.2', linkText: 'Say hello to George', - state: { + params: { firstName: 'George', lastName: 'Washington', }, @@ -71,30 +79,38 @@ const ActionsExplorer = ({ getLinkGenerator }: Props) => { const updateLinks = async () => { const updatedLinks = await Promise.all( persistedLinks.map(async (savedLink) => { - const generator = getLinkGenerator(savedLink.id); - const link = await generator.createUrl(savedLink.state); + const locator = share.url.locators.get(savedLink.id); + if (!locator) return; + let params: HelloLocatorV1Params | HelloLocatorV2Params = savedLink.params; + if (savedLink.version === '0.0.1') { + const migration = locator.migrations['0.0.2']; + if (migration) { + params = migration(params) as HelloLocatorV2Params; + } + } + const link = await locator.getUrl(params, { absolute: false }); return { - isDeprecated: generator.isDeprecated, linkText: savedLink.linkText, link, - }; + version: savedLink.version, + } as MigratedLink; }) ); - setMigratedLinks(updatedLinks); + setMigratedLinks(updatedLinks as MigratedLink[]); setBuildingLinks(false); }; updateLinks(); - }, [getLinkGenerator, persistedLinks]); + }, [share, persistedLinks]); return ( - Access links explorer + Locator explorer -

Create new links using the most recent version of a url generator.

+

Create new links using the most recent version of a locator.

{ setPersistedLinks([ ...persistedLinks, { - id: HELLO_URL_GENERATOR, - state: { firstName, lastName }, + id: HELLO_LOCATOR, + version: '0.0.2', + params: { firstName, lastName }, linkText: `Say hello to ${firstName} ${lastName}`, }, ]) @@ -122,10 +139,10 @@ const ActionsExplorer = ({ getLinkGenerator }: Props) => {

Existing links retrieved from storage. The links that were generated from legacy - generators are in red. This can be useful for developers to know they will have to + locators are in red. This can be useful for developers to know they will have to migrate persisted state or in a future version of Kibana, these links may no longer - work. They still work now because legacy url generators must provide a state - migration function. + work. They still work now because legacy locators must provide state migration + functions.

{buildingLinks ? ( @@ -134,7 +151,7 @@ const ActionsExplorer = ({ getLinkGenerator }: Props) => { migratedLinks.map((link) => ( new AccessLinksExamplesPlugin(); +export const plugin = () => new LocatorExplorerPlugin(); diff --git a/examples/url_generators_explorer/public/page.tsx b/examples/locator_explorer/public/page.tsx similarity index 100% rename from examples/url_generators_explorer/public/page.tsx rename to examples/locator_explorer/public/page.tsx diff --git a/examples/url_generators_explorer/public/plugin.tsx b/examples/locator_explorer/public/plugin.tsx similarity index 63% rename from examples/url_generators_explorer/public/plugin.tsx rename to examples/locator_explorer/public/plugin.tsx index f5f12df669d6c..3e8382f2a606f 100644 --- a/examples/url_generators_explorer/public/plugin.tsx +++ b/examples/locator_explorer/public/plugin.tsx @@ -6,30 +6,30 @@ * Side Public License, v 1. */ -import { SharePluginStart } from '../../../src/plugins/share/public'; +import { SharePluginSetup, SharePluginStart } from '../../../src/plugins/share/public'; import { Plugin, CoreSetup, AppMountParameters, AppNavLinkStatus } from '../../../src/core/public'; import { DeveloperExamplesSetup } from '../../developer_examples/public'; -interface StartDeps { - share: SharePluginStart; -} - interface SetupDeps { developerExamples: DeveloperExamplesSetup; + share: SharePluginSetup; +} + +interface StartDeps { + share: SharePluginStart; } -export class AccessLinksExplorerPlugin implements Plugin { - public setup(core: CoreSetup, { developerExamples }: SetupDeps) { +export class LocatorExplorerPlugin implements Plugin { + public setup(core: CoreSetup, { developerExamples, share }: SetupDeps) { core.application.register({ - id: 'urlGeneratorsExplorer', - title: 'Access links explorer', + id: 'locatorExplorer', + title: 'Locator explorer', navLinkStatus: AppNavLinkStatus.hidden, async mount(params: AppMountParameters) { - const depsStart = (await core.getStartServices())[1]; const { renderApp } = await import('./app'); return renderApp( { - getLinkGenerator: depsStart.share.urlGenerators.getUrlGenerator, + share, }, params ); @@ -37,18 +37,18 @@ export class AccessLinksExplorerPlugin implements Plugin; - -export const createHelloPageLinkGenerator = ( - getStartServices: () => Promise<{ appBasePath: string }> -): UrlGeneratorsDefinition => ({ - id: HELLO_URL_GENERATOR, - createUrl: async (state) => { - const startServices = await getStartServices(); - const appBasePath = startServices.appBasePath; - const parsedUrl = url.parse(window.location.href); - - return url.format({ - protocol: parsedUrl.protocol, - host: parsedUrl.host, - pathname: `${appBasePath}/hello`, - query: { - ...state, - }, - }); - }, -}); - -/** - * The name of this legacy generator id changes, but the *value* stays the same. - */ -export const HELLO_URL_GENERATOR_V1 = 'HELLO_URL_GENERATOR'; - -export interface HelloLinkStateV1 { - name: string; -} - -export type LegacyHelloLinkGeneratorState = UrlGeneratorState< - HelloLinkStateV1, - typeof HELLO_URL_GENERATOR, - HelloLinkState ->; - -export const helloPageLinkGeneratorV1: UrlGeneratorsDefinition = { - id: HELLO_URL_GENERATOR_V1, - isDeprecated: true, - migrate: async (state) => { - return { id: HELLO_URL_GENERATOR, state: { firstName: state.name, lastName: '' } }; - }, -}; diff --git a/src/plugins/discover/public/mocks.ts b/src/plugins/discover/public/mocks.ts index 53160df472a3c..e2000e422f227 100644 --- a/src/plugins/discover/public/mocks.ts +++ b/src/plugins/discover/public/mocks.ts @@ -21,6 +21,10 @@ const createSetupContract = (): Setup => { getUrl: jest.fn(), useUrl: jest.fn(), navigate: jest.fn(), + extract: jest.fn(), + inject: jest.fn(), + telemetry: jest.fn(), + migrations: {}, }, }; return setupContract; @@ -37,6 +41,10 @@ const createStartContract = (): Start => { getUrl: jest.fn(), useUrl: jest.fn(), navigate: jest.fn(), + extract: jest.fn(), + inject: jest.fn(), + telemetry: jest.fn(), + migrations: {}, }, }; return startContract; diff --git a/src/plugins/management/public/mocks/index.ts b/src/plugins/management/public/mocks/index.ts index b06e41502e9df..ce7bc94c8f9cf 100644 --- a/src/plugins/management/public/mocks/index.ts +++ b/src/plugins/management/public/mocks/index.ts @@ -39,6 +39,10 @@ const createSetupContract = (): ManagementSetup => ({ getUrl: jest.fn(), useUrl: jest.fn(), navigate: jest.fn(), + extract: jest.fn(), + inject: jest.fn(), + telemetry: jest.fn(), + migrations: {}, }, }); diff --git a/src/plugins/share/README.md b/src/plugins/share/README.md index 7ecf23134cf24..b3ce5e8832244 100644 --- a/src/plugins/share/README.md +++ b/src/plugins/share/README.md @@ -1,24 +1,23 @@ # Share plugin -Replaces the legacy `ui/share` module for registering share context menus. +The `share` plugin contains various utilities for displaying sharing context menu, +generating deep links to other apps, and creating short URLs. -## Example registration -```ts -// For legacy plugins -import { npSetup } from 'ui/new_platform'; -npSetup.plugins.share.register(/* same details here */); +## Sharing context menu -// For new plugins: first add 'share' to the list of `optionalPlugins` -// in your kibana.json file. Then access the plugin directly in `setup`: +You can register an item into sharing context menu (which is displayed in +Dahsboard, Discover, and Visuzlize apps). -class MyPlugin { - setup(core, plugins) { - if (plugins.share) { - plugins.share.register(/* same details here. */); - } - } -} -``` +### Example registration -Note that the old module supported providing a Angular DI function to receive Angular dependencies. This is no longer supported as we migrate away from Angular and will be removed in 8.0. +```ts +import { ShareContext, ShareMenuItem } from 'src/plugins/share/public'; + +plugins.share.register({ + id: 'MY_MENU', + getShareMenuItems: (context: ShareContext): ShareMenuItem[] => { + // ... + }, +}; +``` diff --git a/src/plugins/share/common/url_service/locators/README.md b/src/plugins/share/common/url_service/locators/README.md new file mode 100644 index 0000000000000..1ca990f92d7b8 --- /dev/null +++ b/src/plugins/share/common/url_service/locators/README.md @@ -0,0 +1,166 @@ +# Locators + +## Locators service + +Developers who maintain pages in Kibana that other developers may want to link to +can register a *locator*. Locators provide backward compatibility support +so the developer of the app/page has a way to change their url structure without +breaking users of this system. If users were to generate the URLs on their own, +using string concatenation, those links may break often. + +Owners: __Kibana App Services team__. + + +## Producer Usage + +Here is how you create a *locator*, which deeply link into your Kibana app: + +```ts +plugins.share.url.locators.create({ + id: 'MY_APP_LOCATOR', + getLocation: async (params: { productId: string }) => { + return { + app: 'myApp', + path: `/products/${productId}`, + state: {}, + }; + }, +}); +``` + +When navigation in Kibana is done without a page reload a serializable *location state* +object is passed to the destination app, which the app can use to change its +appearance. The *location state* object does not appear in the URL, but apps +can still use that, similar to how URL parameters are used. + +```ts +plugins.share.url.locators.create({ + id: 'MY_APP_LOCATOR', + getLocation: async (params: { productId: string, tab?: 'pics' | 'attributes' }) => { + return { + app: 'myApp', + path: `/products/${productId}`, + state: { + tab: params.tab || 'pics', + }, + }; + }, +}); +``` + +When you want to change the shape of the parameters that the locator receives, you can +provide a migration function, which can transform the shape of the parameters from +one Kibana version to another. For example, below we replace `productId` param by `id`. + +```ts +plugins.share.url.locators.create({ + id: 'MY_APP_LOCATOR', + getLocation: async (params: { id: string, tab?: 'pics' | 'attributes' }) => { + return { + app: 'myApp', + path: `/products/${id}`, + state: { + tab: params.tab || 'pics', + }, + }; + }, + + migrations = { + '7.20.0': ({productId, ...rest}) => { + return { + id: productId, + ...rest, + }; + }, + }, +}); +``` + +The migration version should correspond to Kibana relase when the chagne was +introduced. It is the responsibility of the *consumer* to make sure they +migrate their stored parameters using the provided migration function to the +latest version. Migrations versions are ordered by semver. As a consumer, +if persist somewhere a locator parameters object, you also want to store +the version of that object, so later you know from starting from which +version you need to execute migrations. + + +## Consumer Usage + +Consumers of the Locators service can use the locators to generate deeps links +into Kibana apps, or navigate to the apps while passing to the destination +app the *location state*. + +First you will need to get hold of the *locator* for the app you want to +navigate to. + +Usually, that app will return it from its plugin contract from the "setup" +life-cycle: + +```ts +class MyPlugin { + setup(core, plugins) { + const locator = plugins.destinationApp.locator; + } +} +``` + +Or, you can get hold of any locator from the central registry: + +```ts +class MyPlugin { + setup(core, plugins) { + const locator = plugins.share.url.locators.get('DESTINATION_APP_LOCATOR'); + } +} +``` + +Once you have the locator, you can use it to navigate to some kibana app: + +```ts +await locator.navigate({ + productId: '123', +}); +``` + +You can also use it to generate a URL string of the destination: + +```ts +const url = await locator.getUrl({ + productId: '123', +}); +``` + +#### Migrations + +**As a consumer, you should not persist the resulting URL string!** + +As soon as you do, you have lost your migration options. Instead you should +store the ID, version and params of your locator. This will let you +re-create the migrated URL later. + +If, as a consumer, you store the ID, version and params of the locator, you +should use the migration functions provided by the locator when migrating +between Kibana versions. + +```ts +const migration = locator.migrations[version]; + +if (migration) { + params = migration(params); +} +``` + + +### Examples + +You can view the provided example plugins to learn more how to work with locators. +There are two plugins (`locator_examples` and `locator_explorer`) provided in the +`/examples` folder. You can run the example plugins using the following command: + +``` +yarn start --run-examples +``` + +To view the `locator_explorer` example plugin in Kibana navigate to: __Analytics__ 👉 +__Developer examples__ 👉 __URL locators__. diff --git a/src/plugins/share/common/url_service/locators/locator_client.ts b/src/plugins/share/common/url_service/locators/locator_client.ts index 168cc02d03ff1..fc6b23f94a386 100644 --- a/src/plugins/share/common/url_service/locators/locator_client.ts +++ b/src/plugins/share/common/url_service/locators/locator_client.ts @@ -41,7 +41,7 @@ export class LocatorClient implements ILocatorClient { * @param id ID of a URL locator. * @returns A public interface of a registered URL locator. */ - public get

(id: string): undefined | LocatorPublic

{ + public get

(id: string): undefined | LocatorPublic

{ return this.locators.get(id); } } diff --git a/src/plugins/share/common/url_service/locators/types.ts b/src/plugins/share/common/url_service/locators/types.ts index 870eaa3718d3f..0429d52a8f52d 100644 --- a/src/plugins/share/common/url_service/locators/types.ts +++ b/src/plugins/share/common/url_service/locators/types.ts @@ -25,7 +25,7 @@ export interface ILocatorClient { * * @param id Unique ID of the locator. */ - get

(id: string): undefined | LocatorPublic

; + get

(id: string): undefined | LocatorPublic

; } /** @@ -50,7 +50,7 @@ export interface LocatorDefinition

/** * Public interface of a registered locator. */ -export interface LocatorPublic

{ +export interface LocatorPublic

extends PersistableState

{ /** * Returns a reference to a Kibana client-side location. * diff --git a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_chart_action.test.ts b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_chart_action.test.ts index 23ac882e4ecf7..84c81246781bb 100644 --- a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_chart_action.test.ts +++ b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_chart_action.test.ts @@ -55,6 +55,10 @@ const setup = ( navigate: jest.fn(async () => {}), getUrl: jest.fn(), useUrl: jest.fn(), + extract: jest.fn(), + inject: jest.fn(), + telemetry: jest.fn(), + migrations: {}, }; const plugins: PluginDeps = { diff --git a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.test.ts b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.test.ts index 5bdac602ec271..e0a8cf20ee943 100644 --- a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.test.ts +++ b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.test.ts @@ -41,6 +41,10 @@ const setup = ({ dashboardOnlyMode = false }: { dashboardOnlyMode?: boolean } = navigate: jest.fn(async () => {}), getUrl: jest.fn(), useUrl: jest.fn(), + extract: jest.fn(), + inject: jest.fn(), + telemetry: jest.fn(), + migrations: {}, }; const plugins: PluginDeps = { diff --git a/x-pack/plugins/ingest_pipelines/public/locator.test.ts b/x-pack/plugins/ingest_pipelines/public/locator.test.ts index 0b1246b2bed59..47c7b13eb07ea 100644 --- a/x-pack/plugins/ingest_pipelines/public/locator.test.ts +++ b/x-pack/plugins/ingest_pipelines/public/locator.test.ts @@ -21,6 +21,10 @@ describe('Ingest pipeline locator', () => { throw new Error('not implemented'); }, useUrl: () => '', + telemetry: jest.fn(), + extract: jest.fn(), + inject: jest.fn(), + migrations: {}, }, }); return { definition }; diff --git a/x-pack/plugins/ingest_pipelines/public/locator.ts b/x-pack/plugins/ingest_pipelines/public/locator.ts index d819011f14f47..bfcc2f0fc9ce2 100644 --- a/x-pack/plugins/ingest_pipelines/public/locator.ts +++ b/x-pack/plugins/ingest_pipelines/public/locator.ts @@ -52,7 +52,7 @@ export type IngestPipelinesParams = | IngestPipelinesCloneParams | IngestPipelinesCreateParams; -export type IngestPipelinesLocator = LocatorPublic; +export type IngestPipelinesLocator = LocatorPublic; export const INGEST_PIPELINES_APP_LOCATOR = 'INGEST_PIPELINES_APP_LOCATOR';