kibana/docs/developer/architecture/kibana-platform-plugin-api.asciidoc
Mikhail Shustov ab8a2f7427
[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>
2020-11-24 09:46:19 +01:00

347 lines
12 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[[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 its 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 cant necessarily
execute asynchronous code here reliably. For that reason,
`core` likely wouldnt 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.