mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
* 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> # Conflicts: # examples/locator_examples/kibana.json
This commit is contained in:
parent
6a445a657b
commit
8d1c01d2f3
28 changed files with 382 additions and 188 deletions
|
@ -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]]
|
||||
|
|
|
@ -187,7 +187,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]
|
||||
|
|
8
examples/locator_examples/README.md
Normal file
8
examples/locator_examples/README.md
Normal file
|
@ -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.
|
|
@ -1,10 +1,12 @@
|
|||
{
|
||||
"id": "urlGeneratorsExamples",
|
||||
"id": "locatorExamples",
|
||||
"version": "0.0.1",
|
||||
"kibanaVersion": "kibana",
|
||||
"server": false,
|
||||
"ui": true,
|
||||
"requiredPlugins": ["share"],
|
||||
"optionalPlugins": [],
|
||||
"extraPublicDirs": ["public/url_generator"]
|
||||
"extraPublicDirs": [
|
||||
"public/locator"
|
||||
]
|
||||
}
|
|
@ -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();
|
54
examples/locator_examples/public/locator.ts
Normal file
54
examples/locator_examples/public/locator.ts
Normal file
|
@ -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<HelloLocatorV1Params, HelloLocatorV2Params> = (
|
||||
v1: HelloLocatorV1Params
|
||||
) => {
|
||||
const v2: HelloLocatorV2Params = {
|
||||
firstName: v1.name,
|
||||
lastName: '',
|
||||
};
|
||||
|
||||
return v2;
|
||||
};
|
||||
|
||||
export type HelloLocator = LocatorPublic<HelloLocatorParams>;
|
||||
|
||||
export class HelloLocatorDefinition implements LocatorDefinition<HelloLocatorParams> {
|
||||
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,
|
||||
};
|
||||
}
|
|
@ -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<void, void, SetupDeps, StartDeps> {
|
||||
public setup(core: CoreSetup<StartDeps>, { share: { urlGenerators } }: SetupDeps) {
|
||||
urlGenerators.registerUrlGenerator(
|
||||
createHelloPageLinkGenerator(async () => ({
|
||||
appBasePath: (await core.getStartServices())[0].application.getUrlForApp(APP_ID),
|
||||
}))
|
||||
);
|
||||
|
||||
urlGenerators.registerUrlGenerator(helloPageLinkGeneratorV1);
|
||||
export class LocatorExamplesPlugin
|
||||
implements Plugin<LocatorExamplesSetup, void, SetupDeps, StartDeps> {
|
||||
public setup(core: CoreSetup<StartDeps>, 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<void, void, SetupDeps,
|
|||
);
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
locator,
|
||||
};
|
||||
}
|
||||
|
||||
public start() {}
|
|
@ -1,6 +1,7 @@
|
|||
## Access links explorer
|
||||
# Locator explorer
|
||||
|
||||
This example plugin shows how to:
|
||||
|
||||
This example app shows how to:
|
||||
- Generate links to other applications
|
||||
- Generate dynamic links, when the target application is not known
|
||||
- Handle backward compatibility of urls
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"id": "urlGeneratorsExplorer",
|
||||
"id": "locatorExplorer",
|
||||
"version": "0.0.1",
|
||||
"kibanaVersion": "kibana",
|
||||
"server": false,
|
||||
"ui": true,
|
||||
"requiredPlugins": ["share", "urlGeneratorsExamples", "developerExamples"],
|
||||
"requiredPlugins": ["share", "locatorExamples", "developerExamples"],
|
||||
"optionalPlugins": []
|
||||
}
|
|
@ -8,9 +8,7 @@
|
|||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import { EuiPage } from '@elastic/eui';
|
||||
|
||||
import { EuiButton } from '@elastic/eui';
|
||||
import { EuiPageBody } from '@elastic/eui';
|
||||
import { EuiPageContent } from '@elastic/eui';
|
||||
|
@ -21,44 +19,54 @@ import { EuiFieldText } from '@elastic/eui';
|
|||
import { EuiPageHeader } from '@elastic/eui';
|
||||
import { EuiLink } from '@elastic/eui';
|
||||
import { AppMountParameters } from '../../../src/core/public';
|
||||
import { UrlGeneratorsService } from '../../../src/plugins/share/public';
|
||||
import { SharePluginSetup } from '../../../src/plugins/share/public';
|
||||
import {
|
||||
HELLO_URL_GENERATOR,
|
||||
HELLO_URL_GENERATOR_V1,
|
||||
} from '../../url_generators_examples/public/url_generator';
|
||||
HelloLocatorV1Params,
|
||||
HelloLocatorV2Params,
|
||||
HELLO_LOCATOR,
|
||||
} from '../../locator_examples/public';
|
||||
|
||||
interface Props {
|
||||
getLinkGenerator: UrlGeneratorsService['getUrlGenerator'];
|
||||
share: SharePluginSetup;
|
||||
}
|
||||
|
||||
interface MigratedLink {
|
||||
isDeprecated: boolean;
|
||||
linkText: string;
|
||||
link: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
const ActionsExplorer = ({ getLinkGenerator }: Props) => {
|
||||
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 (
|
||||
<EuiPage>
|
||||
<EuiPageBody>
|
||||
<EuiPageHeader>Access links explorer</EuiPageHeader>
|
||||
<EuiPageHeader>Locator explorer</EuiPageHeader>
|
||||
<EuiPageContent>
|
||||
<EuiPageContentBody>
|
||||
<EuiText>
|
||||
<p>Create new links using the most recent version of a url generator.</p>
|
||||
<p>Create new links using the most recent version of a locator.</p>
|
||||
</EuiText>
|
||||
<EuiFieldText
|
||||
prepend="First name"
|
||||
|
@ -108,8 +124,9 @@ const ActionsExplorer = ({ getLinkGenerator }: Props) => {
|
|||
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) => {
|
|||
<EuiText>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</EuiText>
|
||||
{buildingLinks ? (
|
||||
|
@ -134,7 +151,7 @@ const ActionsExplorer = ({ getLinkGenerator }: Props) => {
|
|||
migratedLinks.map((link) => (
|
||||
<React.Fragment>
|
||||
<EuiLink
|
||||
color={link.isDeprecated ? 'danger' : 'primary'}
|
||||
color={link.version !== '0.0.2' ? 'danger' : 'primary'}
|
||||
data-test-subj="linkToHelloPage"
|
||||
href={link.link}
|
||||
target="_blank"
|
|
@ -6,6 +6,6 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { AccessLinksExamplesPlugin } from './plugin';
|
||||
import { LocatorExplorerPlugin } from './plugin';
|
||||
|
||||
export const plugin = () => new AccessLinksExamplesPlugin();
|
||||
export const plugin = () => new LocatorExplorerPlugin();
|
|
@ -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 SetupDeps {
|
||||
developerExamples: DeveloperExamplesSetup;
|
||||
share: SharePluginSetup;
|
||||
}
|
||||
|
||||
interface StartDeps {
|
||||
share: SharePluginStart;
|
||||
}
|
||||
|
||||
interface SetupDeps {
|
||||
developerExamples: DeveloperExamplesSetup;
|
||||
}
|
||||
|
||||
export class AccessLinksExplorerPlugin implements Plugin<void, void, SetupDeps, StartDeps> {
|
||||
public setup(core: CoreSetup<StartDeps>, { developerExamples }: SetupDeps) {
|
||||
export class LocatorExplorerPlugin implements Plugin<void, void, SetupDeps, StartDeps> {
|
||||
public setup(core: CoreSetup<StartDeps>, { 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<void, void, SetupDeps,
|
|||
});
|
||||
|
||||
developerExamples.register({
|
||||
title: 'Url generators',
|
||||
description: `Url generators offer are a backward compatible safe way to persist a URL in a saved object.
|
||||
Store the url generator id and state, instead of the href string. When the link is recreated at run time, the service
|
||||
title: 'URL locators',
|
||||
description: `Locators offer are a backward compatible safe way to persist a URL in a saved object.
|
||||
Store the locator ID and params, instead of the href string. When the link is recreated at run time, the service
|
||||
will run the state through any necessary migrations. Registrators can change their URL structure
|
||||
without breaking these links stored in saved objects.
|
||||
`,
|
||||
appId: 'urlGeneratorsExplorer',
|
||||
appId: 'locatorExplorer',
|
||||
links: [
|
||||
{
|
||||
label: 'README',
|
||||
href:
|
||||
'https://github.com/elastic/kibana/blob/master/src/plugins/share/public/url_generators/README.md',
|
||||
'https://github.com/elastic/kibana/blob/master/src/plugins/share/common/url_service/locators/README.md',
|
||||
iconType: 'logoGithub',
|
||||
size: 's',
|
||||
target: '_blank',
|
|
@ -1,7 +0,0 @@
|
|||
## Access links examples
|
||||
|
||||
This example app shows how to:
|
||||
- Register a direct access link generator.
|
||||
- Handle migration of legacy generators into a new one.
|
||||
|
||||
To run this example, use the command `yarn start --run-examples`. Navigate to the access links explorer app
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
* 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 url from 'url';
|
||||
import { UrlGeneratorState, UrlGeneratorsDefinition } from '../../../src/plugins/share/public';
|
||||
|
||||
/**
|
||||
* The name of the latest variable can always stay the same so code that
|
||||
* uses this link generator statically will switch to the latest version.
|
||||
* Typescript will warn the developer if incorrect state is being passed
|
||||
* down.
|
||||
*/
|
||||
export const HELLO_URL_GENERATOR = 'HELLO_URL_GENERATOR_V2';
|
||||
|
||||
export interface HelloLinkState {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
export type HelloLinkGeneratorState = UrlGeneratorState<HelloLinkState>;
|
||||
|
||||
export const createHelloPageLinkGenerator = (
|
||||
getStartServices: () => Promise<{ appBasePath: string }>
|
||||
): UrlGeneratorsDefinition<typeof HELLO_URL_GENERATOR> => ({
|
||||
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<typeof HELLO_URL_GENERATOR_V1> = {
|
||||
id: HELLO_URL_GENERATOR_V1,
|
||||
isDeprecated: true,
|
||||
migrate: async (state) => {
|
||||
return { id: HELLO_URL_GENERATOR, state: { firstName: state.name, lastName: '' } };
|
||||
},
|
||||
};
|
|
@ -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;
|
||||
|
|
|
@ -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: {},
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
## Sharing context menu
|
||||
|
||||
You can register an item into sharing context menu (which is displayed in
|
||||
Dahsboard, Discover, and Visuzlize apps).
|
||||
|
||||
### Example registration
|
||||
|
||||
```ts
|
||||
// For legacy plugins
|
||||
import { npSetup } from 'ui/new_platform';
|
||||
npSetup.plugins.share.register(/* same details here */);
|
||||
import { ShareContext, ShareMenuItem } from 'src/plugins/share/public';
|
||||
|
||||
// For new plugins: first add 'share' to the list of `optionalPlugins`
|
||||
// in your kibana.json file. Then access the plugin directly in `setup`:
|
||||
|
||||
class MyPlugin {
|
||||
setup(core, plugins) {
|
||||
if (plugins.share) {
|
||||
plugins.share.register(/* same details here. */);
|
||||
}
|
||||
}
|
||||
}
|
||||
plugins.share.register({
|
||||
id: 'MY_MENU',
|
||||
getShareMenuItems: (context: ShareContext): ShareMenuItem[] => {
|
||||
// ...
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
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.
|
||||
|
|
166
src/plugins/share/common/url_service/locators/README.md
Normal file
166
src/plugins/share/common/url_service/locators/README.md
Normal file
|
@ -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__.
|
|
@ -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<P>(id: string): undefined | LocatorPublic<P> {
|
||||
public get<P extends SerializableState>(id: string): undefined | LocatorPublic<P> {
|
||||
return this.locators.get(id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ export interface ILocatorClient {
|
|||
*
|
||||
* @param id Unique ID of the locator.
|
||||
*/
|
||||
get<P>(id: string): undefined | LocatorPublic<P>;
|
||||
get<P extends SerializableState>(id: string): undefined | LocatorPublic<P>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -50,7 +50,7 @@ export interface LocatorDefinition<P extends SerializableState>
|
|||
/**
|
||||
* Public interface of a registered locator.
|
||||
*/
|
||||
export interface LocatorPublic<P> {
|
||||
export interface LocatorPublic<P extends SerializableState> extends PersistableState<P> {
|
||||
/**
|
||||
* Returns a reference to a Kibana client-side location.
|
||||
*
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -52,7 +52,7 @@ export type IngestPipelinesParams =
|
|||
| IngestPipelinesCloneParams
|
||||
| IngestPipelinesCreateParams;
|
||||
|
||||
export type IngestPipelinesLocator = LocatorPublic<void>;
|
||||
export type IngestPipelinesLocator = LocatorPublic<IngestPipelinesParams>;
|
||||
|
||||
export const INGEST_PIPELINES_APP_LOCATOR = 'INGEST_PIPELINES_APP_LOCATOR';
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue