mirror of
https://github.com/elastic/kibana.git
synced 2025-06-28 03:01:21 -04:00
[docs] Convert migration guide to asciidoc (#82600)
* Initial conversion to asciidoc * Update and split migration guide * Convert MIGRATION_EXAMPLES to asciidoc * build with --focus flag * convert migration guide to asciidoc * cleanup migration_examples * fix wrong Heading size * update links in docs * Apply suggestions from code review Co-authored-by: Rudolf Meijering <skaapgif@gmail.com> * Apply suggestions from code review Co-authored-by: Rudolf Meijering <skaapgif@gmail.com> * add tooling section * explain purpose of each lifecycle method * cleanup docs * cleanup p2 * fix wrong link * resturcture core docs * fix wrong link * update missing links * Apply suggestions from code review Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * address comments * add a commenta about plugin-helpers preconfigured * improve density of tables * fix lik * remove links to the migration guide * address comments * Apply suggestions from code review Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * address @gchaps comments * Apply suggestions from code review Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * change format of ES client change list Co-authored-by: Josh Dover <me@joshdover.com> Co-authored-by: Rudolf Meijering <skaapgif@gmail.com> Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com>
This commit is contained in:
parent
b11f7830cb
commit
ab8a2f7427
17 changed files with 2841 additions and 3107 deletions
|
@ -28,11 +28,11 @@ Then register the tutorial object by calling `home.tutorials.registerTutorial(tu
|
|||
String values can contain variables that are substituted when rendered. Variables are specified by `{}`.
|
||||
For example: `{config.docs.version}` is rendered as `6.2` when running the tutorial in {kib} 6.2.
|
||||
|
||||
link:{kib-repo}tree/{branch}/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/replace_template_strings.js#L23[Provided variables]
|
||||
link:{kib-repo}tree/{branch}/src/plugins/home/public/application/components/tutorial/replace_template_strings.js[Provided variables]
|
||||
|
||||
[discrete]
|
||||
==== Markdown
|
||||
String values can contain limited Markdown syntax.
|
||||
|
||||
link:{kib-repo}tree/{branch}/src/legacy/core_plugins/kibana/public/home/components/tutorial/content.js#L8[Enabled Markdown grammars]
|
||||
link:{kib-repo}tree/{branch}/src/legacy/core_plugins/kibana/public/home/components/tutorial/content.js[Enabled Markdown grammars]
|
||||
|
||||
|
|
451
docs/developer/architecture/core/index.asciidoc
Normal file
451
docs/developer/architecture/core/index.asciidoc
Normal file
|
@ -0,0 +1,451 @@
|
|||
[[kibana-platform-api]]
|
||||
== {kib} Core API
|
||||
|
||||
experimental[]
|
||||
|
||||
{kib} Core provides a set of low-level API's required to run all {kib} plugins.
|
||||
These API's are injected into your plugin's lifecycle methods and may be invoked during that lifecycle only:
|
||||
|
||||
[source,typescript]
|
||||
----
|
||||
import type { PluginInitializerContext, CoreSetup, CoreStart } from 'kibana/server';
|
||||
|
||||
export class MyPlugin {
|
||||
constructor(initializerContext: PluginInitializerContext) {}
|
||||
|
||||
public setup(core: CoreSetup) {
|
||||
// called when plugin is setting up during Kibana's startup sequence
|
||||
}
|
||||
|
||||
public start(core: CoreStart) {
|
||||
// called after all plugins are set up
|
||||
}
|
||||
|
||||
public stop() {
|
||||
// called when plugin is torn down during Kibana's shutdown sequence
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
=== Server-side
|
||||
[[configuration-service]]
|
||||
==== Configuration service
|
||||
{kib} provides `ConfigService` if a plugin developer may want to support
|
||||
adjustable runtime behavior for their plugins.
|
||||
Plugins can only read their own configuration values, it is not possible to access the configuration values from {kib} Core or other plugins directly.
|
||||
|
||||
[source,js]
|
||||
----
|
||||
// in Legacy platform
|
||||
const basePath = config.get('server.basePath');
|
||||
// in Kibana Platform 'basePath' belongs to the http service
|
||||
const basePath = core.http.basePath.get(request);
|
||||
----
|
||||
|
||||
To have access to your plugin config, you _should_:
|
||||
|
||||
* Declare plugin-specific `configPath` (will fallback to plugin `id`
|
||||
if not specified) in {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md[`kibana.json`] manifest file.
|
||||
* Export schema validation for the config from plugin's main file. Schema is
|
||||
mandatory. If a plugin reads from the config without schema declaration,
|
||||
`ConfigService` will throw an error.
|
||||
|
||||
*my_plugin/server/index.ts*
|
||||
[source,typescript]
|
||||
----
|
||||
import { schema, TypeOf } from '@kbn/config-schema';
|
||||
export const plugin = …
|
||||
export const config = {
|
||||
schema: schema.object(…),
|
||||
};
|
||||
export type MyPluginConfigType = TypeOf<typeof config.schema>;
|
||||
----
|
||||
|
||||
* Read config value exposed via `PluginInitializerContext`.
|
||||
*my_plugin/server/index.ts*
|
||||
[source,typescript]
|
||||
----
|
||||
import type { PluginInitializerContext } from 'kibana/server';
|
||||
export class MyPlugin {
|
||||
constructor(initializerContext: PluginInitializerContext) {
|
||||
this.config$ = initializerContext.config.create<MyPluginConfigType>();
|
||||
// or if config is optional:
|
||||
this.config$ = initializerContext.config.createIfExists<MyPluginConfigType>();
|
||||
}
|
||||
----
|
||||
|
||||
If your plugin also has a client-side part, you can also expose
|
||||
configuration properties to it using the configuration `exposeToBrowser`
|
||||
allow-list property.
|
||||
|
||||
*my_plugin/server/index.ts*
|
||||
[source,typescript]
|
||||
----
|
||||
import { schema, TypeOf } from '@kbn/config-schema';
|
||||
import type { PluginConfigDescriptor } from 'kibana/server';
|
||||
|
||||
const configSchema = schema.object({
|
||||
secret: schema.string({ defaultValue: 'Only on server' }),
|
||||
uiProp: schema.string({ defaultValue: 'Accessible from client' }),
|
||||
});
|
||||
|
||||
type ConfigType = TypeOf<typeof configSchema>;
|
||||
|
||||
export const config: PluginConfigDescriptor<ConfigType> = {
|
||||
exposeToBrowser: {
|
||||
uiProp: true,
|
||||
},
|
||||
schema: configSchema,
|
||||
};
|
||||
----
|
||||
|
||||
Configuration containing only the exposed properties will be then
|
||||
available on the client-side using the plugin's `initializerContext`:
|
||||
|
||||
*my_plugin/public/index.ts*
|
||||
[source,typescript]
|
||||
----
|
||||
interface ClientConfigType {
|
||||
uiProp: string;
|
||||
}
|
||||
|
||||
export class MyPlugin implements Plugin<PluginSetup, PluginStart> {
|
||||
constructor(private readonly initializerContext: PluginInitializerContext) {}
|
||||
|
||||
public async setup(core: CoreSetup, deps: {}) {
|
||||
const config = this.initializerContext.config.get<ClientConfigType>();
|
||||
}
|
||||
----
|
||||
|
||||
All plugins are considered enabled by default. If you want to disable
|
||||
your plugin, you could declare the `enabled` flag in the plugin
|
||||
config. This is a special {kib} Platform key. {kib} reads its
|
||||
value and won’t create a plugin instance if `enabled: false`.
|
||||
|
||||
[source,js]
|
||||
----
|
||||
export const config = {
|
||||
schema: schema.object({ enabled: schema.boolean({ defaultValue: false }) }),
|
||||
};
|
||||
----
|
||||
[[handle-plugin-configuration-deprecations]]
|
||||
===== Handle plugin configuration deprecations
|
||||
If your plugin has deprecated configuration keys, you can describe them using
|
||||
the `deprecations` config descriptor field.
|
||||
Deprecations are managed on a per-plugin basis, meaning you don’t need to specify
|
||||
the whole property path, but use the relative path from your plugin’s
|
||||
configuration root.
|
||||
|
||||
*my_plugin/server/index.ts*
|
||||
[source,typescript]
|
||||
----
|
||||
import { schema, TypeOf } from '@kbn/config-schema';
|
||||
import type { PluginConfigDescriptor } from 'kibana/server';
|
||||
|
||||
const configSchema = schema.object({
|
||||
newProperty: schema.string({ defaultValue: 'Some string' }),
|
||||
});
|
||||
|
||||
type ConfigType = TypeOf<typeof configSchema>;
|
||||
|
||||
export const config: PluginConfigDescriptor<ConfigType> = {
|
||||
schema: configSchema,
|
||||
deprecations: ({ rename, unused }) => [
|
||||
rename('oldProperty', 'newProperty'),
|
||||
unused('someUnusedProperty'),
|
||||
],
|
||||
};
|
||||
----
|
||||
|
||||
In some cases, accessing the whole configuration for deprecations is
|
||||
necessary. For these edge cases, `renameFromRoot` and `unusedFromRoot`
|
||||
are also accessible when declaring deprecations.
|
||||
|
||||
*my_plugin/server/index.ts*
|
||||
[source,typescript]
|
||||
----
|
||||
export const config: PluginConfigDescriptor<ConfigType> = {
|
||||
schema: configSchema,
|
||||
deprecations: ({ renameFromRoot, unusedFromRoot }) => [
|
||||
renameFromRoot('oldplugin.property', 'myplugin.property'),
|
||||
unusedFromRoot('oldplugin.deprecated'),
|
||||
],
|
||||
};
|
||||
----
|
||||
==== Logging service
|
||||
Allows a plugin to provide status and diagnostic information.
|
||||
For detailed instructions see the {kib-repo}blob/{branch}/src/core/server/logging/README.md[logging service documentation].
|
||||
|
||||
[source,typescript]
|
||||
----
|
||||
import type { PluginInitializerContext, CoreSetup, Plugin, Logger } from 'kibana/server';
|
||||
|
||||
export class MyPlugin implements Plugin {
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(initializerContext: PluginInitializerContext) {
|
||||
this.logger = initializerContext.logger.get();
|
||||
}
|
||||
|
||||
public setup(core: CoreSetup) {
|
||||
try {
|
||||
this.logger.debug('doing something...');
|
||||
// …
|
||||
} catch (e) {
|
||||
this.logger.error('failed doing something...');
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
==== Elasticsearch service
|
||||
`Elasticsearch service` provides `elasticsearch.client` program API to communicate with Elasticsearch server REST API.
|
||||
`elasticsearch.client` interacts with Elasticsearch service on behalf of:
|
||||
|
||||
- `kibana_system` user via `elasticsearch.client.asInternalUser.*` methods.
|
||||
- a current end-user via `elasticsearch.client.asCurrentUser.*` methods. In this case Elasticsearch client should be given the current user credentials.
|
||||
See <<scoped-services>> and <<development-security>>.
|
||||
|
||||
{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.md[Elasticsearch service API docs]
|
||||
|
||||
[source,typescript]
|
||||
----
|
||||
import { CoreStart, Plugin } from 'kibana/public';
|
||||
|
||||
export class MyPlugin implements Plugin {
|
||||
public start(core: CoreStart) {
|
||||
async function asyncTask() {
|
||||
const result = await core.elasticsearch.client.asInternalUser.ping(…);
|
||||
}
|
||||
asyncTask();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
For advanced use-cases, such as a search, use {kib-repo}blob/{branch}/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md[Data plugin]
|
||||
|
||||
include::saved-objects-service.asciidoc[leveloffset=+1]
|
||||
|
||||
==== HTTP service
|
||||
Allows plugins:
|
||||
|
||||
* to extend the {kib} server with custom REST API.
|
||||
* to execute custom logic on an incoming request or server response.
|
||||
* implement custom authentication and authorization strategy.
|
||||
|
||||
See {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.md[HTTP service API docs]
|
||||
|
||||
[source,typescript]
|
||||
----
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import type { CoreSetup, Plugin } from 'kibana/server';
|
||||
|
||||
export class MyPlugin implements Plugin {
|
||||
public setup(core: CoreSetup) {
|
||||
const router = core.http.createRouter();
|
||||
|
||||
const validate = {
|
||||
params: schema.object({
|
||||
id: schema.string(),
|
||||
}),
|
||||
};
|
||||
|
||||
router.get({
|
||||
path: 'my_plugin/{id}',
|
||||
validate
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const data = await findObject(request.params.id);
|
||||
if (!data) return response.notFound();
|
||||
return response.ok({
|
||||
body: data,
|
||||
headers: {
|
||||
'content-type': 'application/json'
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
==== UI settings service
|
||||
The program interface to <<advanced-options, UI settings>>.
|
||||
It makes it possible for Kibana plugins to extend Kibana UI Settings Management with custom settings.
|
||||
|
||||
See:
|
||||
|
||||
- {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.uisettingsservicesetup.register.md[UI settings service Setup API docs]
|
||||
- {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.uisettingsservicestart.register.md[UI settings service Start API docs]
|
||||
|
||||
[source,typescript]
|
||||
----
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import type { CoreSetup,Plugin } from 'kibana/server';
|
||||
|
||||
export class MyPlugin implements Plugin {
|
||||
public setup(core: CoreSetup) {
|
||||
core.uiSettings.register({
|
||||
custom: {
|
||||
value: '42',
|
||||
schema: schema.string(),
|
||||
},
|
||||
});
|
||||
const router = core.http.createRouter();
|
||||
router.get({
|
||||
path: 'my_plugin/{id}',
|
||||
validate: …,
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const customSetting = await context.uiSettings.client.get('custom');
|
||||
…
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
----
|
||||
|
||||
=== Client-side
|
||||
==== Application service
|
||||
Kibana has migrated to be a Single Page Application. Plugins should use `Application service` API to instruct Kibana what an application should be loaded & rendered in the UI in response to user interactions.
|
||||
[source,typescript]
|
||||
----
|
||||
import { AppMountParameters, CoreSetup, Plugin, DEFAULT_APP_CATEGORIES } from 'kibana/public';
|
||||
|
||||
export class MyPlugin implements Plugin {
|
||||
public setup(core: CoreSetup) {
|
||||
core.application.register({ // <1>
|
||||
category: DEFAULT_APP_CATEGORIES.kibana,
|
||||
id: 'my-plugin',
|
||||
title: 'my plugin title',
|
||||
euiIconType: '/path/to/some.svg',
|
||||
order: 100,
|
||||
appRoute: '/app/my_plugin', // <2>
|
||||
async mount(params: AppMountParameters) { // <3>
|
||||
// Load application bundle
|
||||
const { renderApp } = await import('./application');
|
||||
// Get start services
|
||||
const [coreStart, depsStart] = await core.getStartServices(); // <4>
|
||||
// Render the application
|
||||
return renderApp(coreStart, depsStart, params); // <5>
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> See {kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.applicationsetup.register.md[application.register interface]
|
||||
<2> Application specific URL.
|
||||
<3> `mount` callback is invoked when a user navigates to the application-specific URL.
|
||||
<4> `core.getStartServices` method provides API available during `start` lifecycle.
|
||||
<5> `mount` method must return a function that will be called to unmount the application.
|
||||
|
||||
NOTE:: you are free to use any UI library to render a plugin application in DOM.
|
||||
However, we recommend using React and https://elastic.github.io/eui[EUI] for all your basic UI
|
||||
components to create a consistent UI experience.
|
||||
|
||||
==== HTTP service
|
||||
Provides API to communicate with the {kib} server. Feel free to use another HTTP client library to request 3rd party services.
|
||||
|
||||
[source,typescript]
|
||||
----
|
||||
import { CoreStart } from 'kibana/public';
|
||||
interface ResponseType {…};
|
||||
async function fetchData<ResponseType>(core: CoreStart) {
|
||||
return await core.http.get<>(
|
||||
'/api/my_plugin/',
|
||||
{ query: … },
|
||||
);
|
||||
}
|
||||
----
|
||||
See {kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.httpsetup.md[for all available API].
|
||||
|
||||
==== Elasticsearch service
|
||||
Not available in the browser. Use {kib-repo}blob/{branch}/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md[Data plugin] instead.
|
||||
|
||||
== Patterns
|
||||
[[scoped-services]]
|
||||
=== Scoped services
|
||||
Whenever Kibana needs to get access to data saved in elasticsearch, it
|
||||
should perform a check whether an end-user has access to the data. In
|
||||
the legacy platform, Kibana requires binding elasticsearch related API
|
||||
with an incoming request to access elasticsearch service on behalf of a
|
||||
user.
|
||||
|
||||
[source,js]
|
||||
----
|
||||
async function handler(req, res) {
|
||||
const dataCluster = server.plugins.elasticsearch.getCluster('data');
|
||||
const data = await dataCluster.callWithRequest(req, 'ping');
|
||||
}
|
||||
----
|
||||
|
||||
The Kibana Platform introduced a handler interface on the server-side to perform that association
|
||||
internally. Core services, that require impersonation with an incoming
|
||||
request, are exposed via `context` argument of
|
||||
{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.requesthandler.md[the
|
||||
request handler interface.] The above example looks in the Kibana Platform
|
||||
as
|
||||
|
||||
[source,js]
|
||||
----
|
||||
async function handler(context, req, res) {
|
||||
const data = await context.core.elasticsearch.client.asCurrentUser('ping');
|
||||
}
|
||||
----
|
||||
|
||||
The
|
||||
{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md[request
|
||||
handler context] exposed the next scoped *core* services:
|
||||
|
||||
[width="100%",cols="30%,70%",options="header",]
|
||||
|===
|
||||
|Legacy Platform |Kibana Platform
|
||||
|`request.getSavedObjectsClient`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.md[`context.savedObjects.client`]
|
||||
|
||||
|`server.plugins.elasticsearch.getCluster('admin')`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.iscopedclusterclient.md[`context.elasticsearch.client`]
|
||||
|
||||
|`server.plugins.elasticsearch.getCluster('data')`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.iscopedclusterclient.md[`context.elasticsearch.client`]
|
||||
|
||||
|`request.getUiSettingsService`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.iuisettingsclient.md[`context.uiSettings.client`]
|
||||
|===
|
||||
|
||||
==== Declare a custom scoped service
|
||||
|
||||
Plugins can extend the handler context with a custom API that will be
|
||||
available to the plugin itself and all dependent plugins. For example,
|
||||
the plugin creates a custom elasticsearch client and wants to use it via
|
||||
the request handler context:
|
||||
|
||||
[source,typescript]
|
||||
----
|
||||
import type { CoreSetup, IScopedClusterClient } from 'kibana/server';
|
||||
|
||||
export interface MyPluginContext {
|
||||
client: IScopedClusterClient;
|
||||
}
|
||||
|
||||
// extend RequestHandlerContext when a dependent plugin imports MyPluginContext from the file
|
||||
declare module 'kibana/server' {
|
||||
interface RequestHandlerContext {
|
||||
myPlugin?: MyPluginContext;
|
||||
}
|
||||
}
|
||||
|
||||
class MyPlugin {
|
||||
setup(core: CoreSetup) {
|
||||
const client = core.elasticsearch.createClient('myClient');
|
||||
core.http.registerRouteHandlerContext('myPlugin', (context, req, res) => {
|
||||
return { client: client.asScoped(req) };
|
||||
});
|
||||
const router = core.http.createRouter();
|
||||
router.get(
|
||||
{ path: '/api/my-plugin/', validate: … },
|
||||
async (context, req, res) => {
|
||||
const data = await context.myPlugin.client.asCurrentUser('endpoint');
|
||||
}
|
||||
);
|
||||
}
|
||||
----
|
|
@ -1,7 +1,7 @@
|
|||
[[development-plugin-saved-objects]]
|
||||
== Using Saved Objects
|
||||
[[saved-objects-service]]
|
||||
== Saved Objects service
|
||||
|
||||
Saved Objects allow {kib} plugins to use {es} like a primary
|
||||
`Saved Objects service` allows {kib} plugins to use {es} like a primary
|
||||
database. Think of it as an Object Document Mapper for {es}. Once a
|
||||
plugin has registered one or more Saved Object types, the Saved Objects client
|
||||
can be used to query or perform create, read, update and delete operations on
|
|
@ -3,9 +3,15 @@
|
|||
|
||||
[IMPORTANT]
|
||||
==============================================
|
||||
{kib} developer services and apis are in a state of constant development. We cannot provide backwards compatibility at this time due to the high rate of change.
|
||||
The {kib} Plugin APIs are in a state of
|
||||
constant development. We cannot provide backwards compatibility at this time due
|
||||
to the high rate of change.
|
||||
==============================================
|
||||
|
||||
To begin plugin development, we recommend reading our overview of how plugins work:
|
||||
|
||||
* <<kibana-platform-plugin-api>>
|
||||
|
||||
Our developer services are changing all the time. One of the best ways to discover and learn about them is to read the available
|
||||
READMEs from all the plugins inside our {kib-repo}tree/{branch}/src/plugins[open source plugins folder] and our
|
||||
{kib-repo}/tree/{branch}/x-pack/plugins[commercial plugins folder].
|
||||
|
@ -14,17 +20,17 @@ A few services also automatically generate api documentation which can be browse
|
|||
|
||||
A few notable services are called out below.
|
||||
|
||||
* <<kibana-platform-api>>
|
||||
* <<development-security>>
|
||||
* <<development-plugin-saved-objects>>
|
||||
* <<add-data-tutorials>>
|
||||
* <<development-visualize-index>>
|
||||
|
||||
include::security/index.asciidoc[leveloffset=+1]
|
||||
include::kibana-platform-plugin-api.asciidoc[leveloffset=+1]
|
||||
|
||||
include::development-plugin-saved-objects.asciidoc[leveloffset=+1]
|
||||
include::core/index.asciidoc[leveloffset=+1]
|
||||
|
||||
include::security/index.asciidoc[leveloffset=+1]
|
||||
|
||||
include::add-data-tutorials.asciidoc[leveloffset=+1]
|
||||
|
||||
include::development-visualize-index.asciidoc[leveloffset=+1]
|
||||
|
||||
|
||||
|
|
347
docs/developer/architecture/kibana-platform-plugin-api.asciidoc
Normal file
347
docs/developer/architecture/kibana-platform-plugin-api.asciidoc
Normal file
|
@ -0,0 +1,347 @@
|
|||
[[kibana-platform-plugin-api]]
|
||||
== {kib} Plugin API
|
||||
|
||||
experimental[]
|
||||
|
||||
{kib} platform plugins are a significant step toward stabilizing {kib} architecture for all the developers.
|
||||
We made sure plugins could continue to use most of the same technologies they use today, at least from a technical perspective.
|
||||
|
||||
=== Anatomy of a plugin
|
||||
|
||||
Plugins are defined as classes and present themselves to {kib}
|
||||
through a simple wrapper function. A plugin can have browser-side code,
|
||||
server-side code, or both. There is no architectural difference between
|
||||
a plugin in the browser and a plugin on the server.
|
||||
In both places, you describe your plugin similarly, and you interact with
|
||||
Core and other plugins in the same way.
|
||||
|
||||
The basic file structure of a {kib} plugin named `demo` that
|
||||
has both client-side and server-side code would be:
|
||||
|
||||
[source,tree]
|
||||
----
|
||||
plugins/
|
||||
demo
|
||||
kibana.json [1]
|
||||
public
|
||||
index.ts [2]
|
||||
plugin.ts [3]
|
||||
server
|
||||
index.ts [4]
|
||||
plugin.ts [5]
|
||||
----
|
||||
|
||||
*[1] `kibana.json`* is a static manifest file that is used to identify the
|
||||
plugin and to specify if this plugin has server-side code, browser-side code, or both:
|
||||
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
"id": "demo",
|
||||
"version": "kibana",
|
||||
"server": true,
|
||||
"ui": true
|
||||
}
|
||||
----
|
||||
|
||||
Learn about the {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md[manifest
|
||||
file format].
|
||||
|
||||
NOTE: `package.json` files are irrelevant to and ignored by {kib} for discovering and loading plugins.
|
||||
|
||||
*[2] `public/index.ts`* is the entry point into the client-side code of
|
||||
this plugin. It must export a function named `plugin`, which will
|
||||
receive {kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.plugininitializercontext.md[a standard set of core capabilities] as an argument.
|
||||
It should return an instance of its plugin class for
|
||||
{kib} to load.
|
||||
|
||||
[source,typescript]
|
||||
----
|
||||
import type { PluginInitializerContext } from 'kibana/server';
|
||||
import { MyPlugin } from './plugin';
|
||||
|
||||
export function plugin(initializerContext: PluginInitializerContext) {
|
||||
return new MyPlugin(initializerContext);
|
||||
}
|
||||
----
|
||||
|
||||
*[3] `public/plugin.ts`* is the client-side plugin definition itself.
|
||||
Technically speaking, it does not need to be a class or even a separate
|
||||
file from the entry point, but _all plugins at Elastic_ should be
|
||||
consistent in this way. See all {kib-repo}blob/{branch}/src/core/CONVENTIONS.md[conventions
|
||||
for first-party Elastic plugins].
|
||||
|
||||
[source,typescript]
|
||||
----
|
||||
import type { Plugin, PluginInitializerContext, CoreSetup, CoreStart } from 'kibana/server';
|
||||
|
||||
export class MyPlugin implements Plugin {
|
||||
constructor(initializerContext: PluginInitializerContext) {}
|
||||
|
||||
public setup(core: CoreSetup) {
|
||||
// called when plugin is setting up during Kibana's startup sequence
|
||||
}
|
||||
|
||||
public start(core: CoreStart) {
|
||||
// called after all plugins are set up
|
||||
}
|
||||
|
||||
public stop() {
|
||||
// called when plugin is torn down during Kibana's shutdown sequence
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
*[4] `server/index.ts`* is the entry-point into the server-side code of
|
||||
this plugin. {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.plugininitializercontext.md[It is identical] in almost every way to the client-side
|
||||
entry-point:
|
||||
|
||||
|
||||
[source,typescript]
|
||||
----
|
||||
import type { PluginInitializerContext } from 'kibana/server';
|
||||
import { MyPlugin } from './plugin';
|
||||
|
||||
export function plugin(initializerContext: PluginInitializerContext) {
|
||||
return new MyPlugin(initializerContext);
|
||||
}
|
||||
----
|
||||
|
||||
*[5] `server/plugin.ts`* is the server-side plugin definition. The
|
||||
shape of this plugin is the same as it’s client-side counter-part:
|
||||
|
||||
[source,typescript]
|
||||
----
|
||||
import type { Plugin, PluginInitializerContext, CoreSetup, CoreStart } from 'kibana/server';
|
||||
|
||||
export class MyPlugin implements Plugin {
|
||||
constructor(initializerContext: PluginInitializerContext) {}
|
||||
|
||||
public setup(core: CoreSetup) {
|
||||
// called when plugin is setting up during Kibana's startup sequence
|
||||
}
|
||||
|
||||
public start(core: CoreStart) {
|
||||
// called after all plugins are set up
|
||||
}
|
||||
|
||||
public stop() {
|
||||
// called when plugin is torn down during Kibana's shutdown sequence
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
{kib} does not impose any technical restrictions on how the
|
||||
the internals of a plugin are architected, though there are certain
|
||||
considerations related to how plugins integrate with core APIs
|
||||
and APIs exposed by other plugins that may greatly impact how
|
||||
they are built.
|
||||
[[plugin-lifecycles]]
|
||||
=== Lifecycles & Core Services
|
||||
|
||||
The various independent domains that makeup `core` are represented by a
|
||||
series of services and many of those services expose public interfaces
|
||||
that are provided to all plugins. Services expose different features
|
||||
at different parts of their lifecycle. We describe the lifecycle of
|
||||
core services and plugins with specifically-named functions on the
|
||||
service definition.
|
||||
|
||||
{kib} has three lifecycles: `setup`,
|
||||
`start`, and `stop`. Each plugin's `setup` functions is called sequentially
|
||||
while Kibana is setting up on the server or when it is being loaded in
|
||||
the browser. The `start` functions are called sequentially after `setup`
|
||||
has been completed for all plugins. The `stop` functions are called
|
||||
sequentially while Kibana is gracefully shutting down the server or
|
||||
when the browser tab or window is being closed.
|
||||
|
||||
The table below explains how each lifecycle relates to the state
|
||||
of Kibana.
|
||||
|
||||
[width="100%",cols="10%, 15%, 37%, 38%",options="header",]
|
||||
|===
|
||||
|lifecycle | purpose| server |browser
|
||||
|_setup_
|
||||
|perform "registration" work to setup environment for runtime
|
||||
|configure REST API endpoint, register saved object types, etc.
|
||||
|configure application routes in SPA, register custom UI elements in extension points, etc.
|
||||
|
||||
|_start_
|
||||
|bootstrap runtime logic
|
||||
|respond to an incoming request, request Elasticsearch server, etc.
|
||||
|start polling Kibana server, update DOM tree in response to user interactions, etc.
|
||||
|
||||
|_stop_
|
||||
|cleanup runtime
|
||||
|dispose of active handles before the server shutdown.
|
||||
|store session data in the LocalStorage when the user navigates away from {kib}, etc.
|
||||
|===
|
||||
|
||||
There is no equivalent behavior to `start` or `stop` in legacy plugins.
|
||||
Conversely, there is no equivalent to `uiExports` in Kibana Platform plugins.
|
||||
As a general rule of thumb, features that were registered via `uiExports` are
|
||||
now registered during the `setup` phase. Most of everything else should move
|
||||
to the `start` phase.
|
||||
|
||||
The lifecycle-specific contracts exposed by core services are always
|
||||
passed as the first argument to the equivalent lifecycle function in a
|
||||
plugin. For example, the core `http` service exposes a function
|
||||
`createRouter` to all plugin `setup` functions. To use this function to register
|
||||
an HTTP route handler, a plugin just accesses it off of the first argument:
|
||||
|
||||
[source, typescript]
|
||||
----
|
||||
import type { CoreSetup } from 'kibana/server';
|
||||
|
||||
export class MyPlugin {
|
||||
public setup(core: CoreSetup) {
|
||||
const router = core.http.createRouter();
|
||||
// handler is called when '/path' resource is requested with `GET` method
|
||||
router.get({ path: '/path', validate: false }, (context, req, res) => res.ok({ content: 'ok' }));
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Different service interfaces can and will be passed to `setup`, `start`, and
|
||||
`stop` because certain functionality makes sense in the context of a
|
||||
running plugin while other types of functionality may have restrictions
|
||||
or may only make sense in the context of a plugin that is stopping.
|
||||
|
||||
For example, the `stop` function in the browser gets invoked as part of
|
||||
the `window.onbeforeunload` event, which means you can’t necessarily
|
||||
execute asynchronous code here reliably. For that reason,
|
||||
`core` likely wouldn’t provide any asynchronous functions to plugin
|
||||
`stop` functions in the browser.
|
||||
|
||||
The current lifecycle function for all plugins will be executed before the next
|
||||
lifecycle starts. That is to say that all `setup` functions are executed before
|
||||
any `start` functions are executed.
|
||||
|
||||
These are the contracts exposed by the core services for each lifecycle:
|
||||
|
||||
[cols=",,",options="header",]
|
||||
|===
|
||||
|lifecycle |server contract|browser contract
|
||||
|_contructor_
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.plugininitializercontext.md[PluginInitializerContext]
|
||||
|{kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.plugininitializercontext.md[PluginInitializerContext]
|
||||
|
||||
|_setup_
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.coresetup.md[CoreSetup]
|
||||
|{kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.coresetup.md[CoreSetup]
|
||||
|
||||
|_start_
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.corestart.md[CoreStart]
|
||||
|{kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.corestart.md[CoreStart]
|
||||
|
||||
|_stop_ |
|
||||
|===
|
||||
|
||||
=== Integrating with other plugins
|
||||
|
||||
Plugins can expose public interfaces for other plugins to consume. Like
|
||||
`core`, those interfaces are bound to the lifecycle functions `setup`
|
||||
and/or `start`.
|
||||
|
||||
Anything returned from `setup` or `start` will act as the interface, and
|
||||
while not a technical requirement, all first-party Elastic plugins
|
||||
will expose types for that interface as well. Third party plugins
|
||||
wishing to allow other plugins to integrate with it are also highly
|
||||
encouraged to expose types for their plugin interfaces.
|
||||
|
||||
*foobar plugin.ts:*
|
||||
|
||||
[source, typescript]
|
||||
----
|
||||
import type { Plugin } from 'kibana/server';
|
||||
export interface FoobarPluginSetup { <1>
|
||||
getFoo(): string;
|
||||
}
|
||||
|
||||
export interface FoobarPluginStart { <1>
|
||||
getBar(): string;
|
||||
}
|
||||
|
||||
export class MyPlugin implements Plugin<FoobarPluginSetup, FoobarPluginStart> {
|
||||
public setup(): FoobarPluginSetup {
|
||||
return {
|
||||
getFoo() {
|
||||
return 'foo';
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public start(): FoobarPluginStart {
|
||||
return {
|
||||
getBar() {
|
||||
return 'bar';
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> We highly encourage plugin authors to explicitly declare public interfaces for their plugins.
|
||||
|
||||
Unlike core, capabilities exposed by plugins are _not_ automatically
|
||||
injected into all plugins. Instead, if a plugin wishes to use the public
|
||||
interface provided by another plugin, it must first declare that
|
||||
plugin as a dependency in it's {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md[`kibana.json`] manifest file.
|
||||
|
||||
*demo kibana.json:*
|
||||
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
"id": "demo",
|
||||
"requiredPlugins": ["foobar"],
|
||||
"server": true,
|
||||
"ui": true
|
||||
}
|
||||
----
|
||||
|
||||
With that specified in the plugin manifest, the appropriate interfaces
|
||||
are then available via the second argument of `setup` and/or `start`:
|
||||
|
||||
*demo plugin.ts:*
|
||||
|
||||
[source,typescript]
|
||||
----
|
||||
import type { CoreSetup, CoreStart } from 'kibana/server';
|
||||
import type { FoobarPluginSetup, FoobarPluginStart } from '../../foobar/server';
|
||||
|
||||
interface DemoSetupPlugins { <1>
|
||||
foobar: FoobarPluginSetup;
|
||||
}
|
||||
|
||||
interface DemoStartPlugins {
|
||||
foobar: FoobarPluginStart;
|
||||
}
|
||||
|
||||
export class AnotherPlugin {
|
||||
public setup(core: CoreSetup, plugins: DemoSetupPlugins) { <2>
|
||||
const { foobar } = plugins;
|
||||
foobar.getFoo(); // 'foo'
|
||||
foobar.getBar(); // throws because getBar does not exist
|
||||
}
|
||||
|
||||
public start(core: CoreStart, plugins: DemoStartPlugins) { <3>
|
||||
const { foobar } = plugins;
|
||||
foobar.getFoo(); // throws because getFoo does not exist
|
||||
foobar.getBar(); // 'bar'
|
||||
}
|
||||
|
||||
public stop() {}
|
||||
}
|
||||
----
|
||||
<1> The interface for plugin's dependencies must be manually composed. You can
|
||||
do this by importing the appropriate type from the plugin and constructing an
|
||||
interface where the property name is the plugin's ID.
|
||||
<2> These manually constructed types should then be used to specify the type of
|
||||
the second argument to the plugin.
|
||||
<3> Notice that the type for the setup and start lifecycles are different. Plugin lifecycle
|
||||
functions can only access the APIs that are exposed _during_ that lifecycle.
|
||||
|
||||
=== Migrating legacy plugins
|
||||
|
||||
In Kibana 7.10, support for legacy plugins was removed. See
|
||||
<<migrating-legacy-plugins>> for detailed information on how to convert existing
|
||||
legacy plugins to this new API.
|
Loading…
Add table
Add a link
Reference in a new issue