mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -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.
|
|
@ -12,6 +12,8 @@ Are you planning with scalability in mind?
|
|||
* Consider data with many fields
|
||||
* Consider data with high cardinality fields
|
||||
* Consider large data sets, that span a long time range
|
||||
* Are you loading a minimal amount of JS code in the browser?
|
||||
** See <<plugin-performance>> for more guidance.
|
||||
* Do you make lots of requests to the server?
|
||||
** If so, have you considered using the streaming {kib-repo}tree/{branch}/src/plugins/bfetch[bfetch service]?
|
||||
|
||||
|
@ -140,6 +142,8 @@ Review:
|
|||
* <<security-best-practices>>
|
||||
* <<typescript>>
|
||||
|
||||
include::performance.asciidoc[leveloffset=+1]
|
||||
|
||||
include::navigation.asciidoc[leveloffset=+1]
|
||||
|
||||
include::stability.asciidoc[leveloffset=+1]
|
||||
|
|
101
docs/developer/best-practices/performance.asciidoc
Normal file
101
docs/developer/best-practices/performance.asciidoc
Normal file
|
@ -0,0 +1,101 @@
|
|||
[[plugin-performance]]
|
||||
== Keep {kib} fast
|
||||
|
||||
*tl;dr*: Load as much code lazily as possible. Everyone loves snappy
|
||||
applications with a responsive UI and hates spinners. Users deserve the
|
||||
best experience whether they run {kib} locally or
|
||||
in the cloud, regardless of their hardware and environment.
|
||||
|
||||
There are 2 main aspects of the perceived speed of an application: loading time
|
||||
and responsiveness to user actions. {kib} loads and bootstraps *all*
|
||||
the plugins whenever a user lands on any page. It means that
|
||||
every new application affects the overall _loading performance_, as plugin code is
|
||||
loaded _eagerly_ to initialize the plugin and provide plugin API to dependent
|
||||
plugins.
|
||||
|
||||
However, it’s usually not necessary that the whole plugin code should be loaded
|
||||
and initialized at once. The plugin could keep on loading code covering API functionality
|
||||
on {kib} bootstrap, but load UI related code lazily on-demand, when an
|
||||
application page or management section is mounted.
|
||||
Always prefer to import UI root components lazily when possible (such as in `mount`
|
||||
handlers). Even if their size may seem negligible, they are likely using
|
||||
some heavy-weight libraries that will also be removed from the initial
|
||||
plugin bundle, therefore, reducing its size by a significant amount.
|
||||
|
||||
[source,typescript]
|
||||
----
|
||||
import type { Plugin, CoreSetup, AppMountParameters } from 'kibana/public';
|
||||
export class MyPlugin implements Plugin<MyPluginSetup> {
|
||||
setup(core: CoreSetup, plugins: SetupDeps) {
|
||||
core.application.register({
|
||||
id: 'app',
|
||||
title: 'My app',
|
||||
async mount(params: AppMountParameters) {
|
||||
const { mountApp } = await import('./app/mount_app');
|
||||
return mountApp(await core.getStartServices(), params);
|
||||
},
|
||||
});
|
||||
plugins.management.sections.section.kibana.registerApp({
|
||||
id: 'app',
|
||||
title: 'My app',
|
||||
order: 1,
|
||||
async mount(params) {
|
||||
const { mountManagementSection } = await import('./app/mount_management_section');
|
||||
return mountManagementSection(coreSetup, params);
|
||||
},
|
||||
});
|
||||
return {
|
||||
doSomething() {},
|
||||
};
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
=== Understanding plugin bundle size
|
||||
|
||||
{kib} Platform plugins are pre-built with `@kbn/optimizer`
|
||||
and distributed as package artifacts. This means that it is no
|
||||
longer necessary for us to include the `optimizer` in the
|
||||
distributable version of {kib}. Every plugin artifact contains all
|
||||
plugin dependencies required to run the plugin, except some
|
||||
stateful dependencies shared across plugin bundles via
|
||||
`@kbn/ui-shared-deps`. This means that plugin artifacts _tend to
|
||||
be larger_ than they were in the legacy platform. To understand the
|
||||
current size of your plugin artifact, run `@kbn/optimizer` with:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
node scripts/build_kibana_platform_plugins.js --dist --profile --focus=my_plugin
|
||||
----
|
||||
|
||||
and check the output in the `target` sub-folder of your plugin folder:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
ls -lh plugins/my_plugin/target/public/
|
||||
# output
|
||||
# an async chunk loaded on demand
|
||||
... 262K 0.plugin.js
|
||||
# eagerly loaded chunk
|
||||
... 50K my_plugin.plugin.js
|
||||
----
|
||||
|
||||
You might see at least one js bundle - `my_plugin.plugin.js`. This is
|
||||
the _only_ artifact loaded by {kib} during bootstrap in the
|
||||
browser. The rule of thumb is to keep its size as small as possible.
|
||||
Other lazily loaded parts of your plugin will be present in the same folder as
|
||||
separate chunks under `{number}.myplugin.js` names. If you want to
|
||||
investigate what your plugin bundle consists of, you need to run
|
||||
`@kbn/optimizer` with `--profile` flag to generate a
|
||||
https://webpack.js.org/api/stats/[webpack stats file].
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
node scripts/build_kibana_platform_plugins.js --dist --no-examples --profile
|
||||
----
|
||||
|
||||
Many OSS tools allow you to analyze the generated stats file:
|
||||
|
||||
* http://webpack.github.io/analyse/#modules[An official tool] from
|
||||
Webpack authors
|
||||
* https://chrisbateman.github.io/webpack-visualizer/[webpack-visualizer]
|
|
@ -75,7 +75,7 @@ In order to prevent the page load bundles from growing unexpectedly large we lim
|
|||
|
||||
In most cases the limit should be high enough that PRs shouldn't trigger overages, but when they do make sure it's clear what is cuasing the overage by trying the following:
|
||||
|
||||
1. Run the optimizer locally with the `--profile` flag to produce webpack `stats.json` files for bundles which can be inspected using a number of different online tools. Focus on the chunk named `{pluginId}.plugin.js`; the `*.chunk.js` chunks make up the `async chunks size` metric which is currently unlimited and is the main way that we {kib-repo}blob/{branch}/src/core/MIGRATION.md#keep-kibana-fast[reduce the size of page load chunks].
|
||||
1. Run the optimizer locally with the `--profile` flag to produce webpack `stats.json` files for bundles which can be inspected using a number of different online tools. Focus on the chunk named `{pluginId}.plugin.js`; the `*.chunk.js` chunks make up the `async chunks size` metric which is currently unlimited and is the main way that we <<plugin-performance, reduce the size of page load chunks>>.
|
||||
+
|
||||
[source,shell]
|
||||
-----------
|
||||
|
@ -111,7 +111,7 @@ prettier -w {pluginDir}/target/public/{pluginId}.plugin.js
|
|||
|
||||
6. If all else fails reach out to Operations for help.
|
||||
|
||||
Once you've identified the files which were added to the build you likely just need to stick them behind an async import as described in {kib-repo}blob/{branch}/src/core/MIGRATION.md#keep-kibana-fast[the MIGRATION.md docs].
|
||||
Once you've identified the files which were added to the build you likely just need to stick them behind an async import as described in <<plugin-performance, Plugin performance>>.
|
||||
|
||||
In the case that the bundle size is not being bloated by anything obvious, but it's still larger than the limit, you can raise the limit in your PR. Do this either by editting the {kib-repo}blob/{branch}/packages/kbn-optimizer/limits.yml[`limits.yml` file] manually or by running the following to have the limit updated to the current size + 15kb
|
||||
|
||||
|
|
|
@ -51,8 +51,9 @@ but not in the distributable version of {kib}. If you use the
|
|||
[discrete]
|
||||
=== {kib} platform migration guide
|
||||
|
||||
{kib-repo}blob/{branch}/src/core/MIGRATION.md#migrating-legacy-plugins-to-the-new-platform[This guide]
|
||||
provides an action plan for moving a legacy plugin to the new platform.
|
||||
<<migrating-legacy-plugins, This guide>>
|
||||
provides an action plan for moving a legacy plugin to the new platform.
|
||||
<<migrating-legacy-plugins-examples, Provides>> migration examples for the legacy core services.
|
||||
|
||||
[discrete]
|
||||
=== Externally developed plugins
|
||||
|
|
|
@ -9,34 +9,16 @@ The {kib} plugin interfaces are in a state of constant development. We cannot p
|
|||
Most developers who contribute code directly to the {kib} repo are writing code inside plugins, so our <<contributing>> docs are the best place to
|
||||
start. However, there are a few differences when developing plugins outside the {kib} repo. These differences are covered here.
|
||||
|
||||
[discrete]
|
||||
[[automatic-plugin-generator]]
|
||||
=== Automatic plugin generator
|
||||
|
||||
We recommend that you kick-start your plugin by generating it with the {kib-repo}tree/{branch}/packages/kbn-plugin-generator[Kibana Plugin Generator]. Run the following in the {kib} repo, and you will be asked a couple questions, see some progress bars, and have a freshly generated plugin ready for you to play with in {kib}'s `plugins` folder.
|
||||
|
||||
["source","shell"]
|
||||
-----------
|
||||
node scripts/generate_plugin my_plugin_name # replace "my_plugin_name" with your desired plugin name
|
||||
-----------
|
||||
|
||||
[discrete]
|
||||
=== Plugin location
|
||||
|
||||
The {kib} directory must be named `kibana`, and your plugin directory should be located in the root of `kibana` in a `plugins` directory, for example:
|
||||
|
||||
["source","shell"]
|
||||
----
|
||||
.
|
||||
└── kibana
|
||||
└── plugins
|
||||
├── foo-plugin
|
||||
└── bar-plugin
|
||||
----
|
||||
|
||||
* <<plugin-tooling>>
|
||||
* <<migrating-legacy-plugins>>
|
||||
* <<migrating-legacy-plugins-examples>>
|
||||
* <<external-plugin-functional-tests>>
|
||||
* <<external-plugin-localization>>
|
||||
* <<testing-kibana-plugin>>
|
||||
|
||||
include::plugin-tooling.asciidoc[leveloffset=+1]
|
||||
include::migrating-legacy-plugins.asciidoc[leveloffset=+1]
|
||||
include::migrating-legacy-plugins-examples.asciidoc[leveloffset=+1]
|
||||
include::external-plugin-functional-tests.asciidoc[leveloffset=+1]
|
||||
|
||||
include::external-plugin-localization.asciidoc[leveloffset=+1]
|
||||
include::testing-kibana-plugin.asciidoc[leveloffset=+1]
|
||||
|
|
1186
docs/developer/plugin/migrating-legacy-plugins-examples.asciidoc
Normal file
1186
docs/developer/plugin/migrating-legacy-plugins-examples.asciidoc
Normal file
File diff suppressed because it is too large
Load diff
608
docs/developer/plugin/migrating-legacy-plugins.asciidoc
Normal file
608
docs/developer/plugin/migrating-legacy-plugins.asciidoc
Normal file
|
@ -0,0 +1,608 @@
|
|||
[[migrating-legacy-plugins]]
|
||||
== Migrating legacy plugins to the {kib} Platform
|
||||
|
||||
[IMPORTANT]
|
||||
==============================================
|
||||
In {kib} 7.10, support for legacy-style {kib} plugins was completely removed.
|
||||
Moving forward, all plugins must be built on the new {kib} Platform Plugin API.
|
||||
This guide is intended to assist plugin authors in migrating their legacy plugin
|
||||
to the {kib} Platform Plugin API.
|
||||
==============================================
|
||||
|
||||
Make no mistake, it is going to take a lot of work to move certain
|
||||
plugins to the {kib} Platform.
|
||||
|
||||
The goal of this document is to guide developers through the recommended
|
||||
process of migrating at a high level. Every plugin is different, so
|
||||
developers should tweak this plan based on their unique requirements.
|
||||
|
||||
First, we recommend you read <<kibana-platform-plugin-api>> to get an overview
|
||||
of how plugins work in the {kib} Platform. Then continue here to follow our
|
||||
generic plan of action that can be applied to any legacy plugin.
|
||||
|
||||
=== Challenges to overcome with legacy plugins
|
||||
|
||||
{kib} Platform plugins have an identical architecture in the browser and on
|
||||
the server. Legacy plugins have one architecture that they use in the
|
||||
browser and an entirely different architecture that they use on the
|
||||
server.
|
||||
|
||||
This means that there are unique sets of challenges for migrating to the
|
||||
{kib} Platform, depending on whether the legacy plugin code is on the
|
||||
server or in the browser.
|
||||
|
||||
==== Challenges on the server
|
||||
|
||||
The general architecture of legacy server-side code is similar to
|
||||
the {kib} Platform architecture in one important way: most legacy
|
||||
server-side plugins define an `init` function where the bulk of their
|
||||
business logic begins, and they access both `core` and
|
||||
`plugin-provided` functionality through the arguments given to `init`.
|
||||
Rarely does legacy server-side code share stateful services via import
|
||||
statements.
|
||||
|
||||
Although not exactly the same, legacy plugin `init` functions behave
|
||||
similarly today as {kib} Platform `setup` functions. `KbnServer` also
|
||||
exposes an `afterPluginsInit` method, which behaves similarly to `start`.
|
||||
There is no corresponding legacy concept of `stop`.
|
||||
|
||||
Despite their similarities, server-side plugins pose a formidable
|
||||
challenge: legacy core and plugin functionality is retrieved from either
|
||||
the hapi.js `server` or `request` god objects. Worse, these objects are
|
||||
often passed deeply throughout entire plugins, which directly couples
|
||||
business logic with hapi. And the worst of it all is, these objects are
|
||||
mutable at any time.
|
||||
|
||||
The key challenge to overcome with legacy server-side plugins will
|
||||
decoupling from hapi.
|
||||
|
||||
==== Challenges in the browser
|
||||
|
||||
The legacy plugin system in the browser is fundamentally incompatible
|
||||
with the {kib} Platform. There is no client-side plugin definition. There
|
||||
are no services that get passed to plugins at runtime. There really
|
||||
isn’t even a concrete notion of `core`.
|
||||
|
||||
When a legacy browser plugin needs to access functionality from another
|
||||
plugin, say to register a UI section to render within another plugin, it
|
||||
imports a stateful (global singleton) JavaScript module and performs
|
||||
some sort of state mutation. Sometimes this module exists inside the
|
||||
plugin itself, and it gets imported via the `plugin/` webpack alias.
|
||||
Sometimes this module exists outside the context of plugins entirely and
|
||||
gets imported via the `ui/` webpack alias. Neither of these concepts
|
||||
exists in the {kib} Platform.
|
||||
|
||||
Legacy browser plugins rely on the feature known as `uiExports/`, which
|
||||
integrates directly with our build system to ensure that plugin code is
|
||||
bundled together in such a way to enable that global singleton module
|
||||
state. There is no corresponding feature in the {kib} Platform, and in
|
||||
the fact we intend down the line to build {kib} Platform plugins as immutable
|
||||
bundles that can not share state in this way.
|
||||
|
||||
The key challenge to overcome with legacy browser-side plugins will be
|
||||
converting all imports from `plugin/`, `ui/`, `uiExports`, and relative
|
||||
imports from other plugins into a set of services that originate at
|
||||
runtime during plugin initialization and get passed around throughout
|
||||
the business logic of the plugin as function arguments.
|
||||
|
||||
==== Plan of action
|
||||
|
||||
To move a legacy plugin to the new plugin system, the
|
||||
challenges on the server and in the browser must be addressed.
|
||||
|
||||
The approach and level of effort varies significantly between server and
|
||||
browser plugins, but at a high level, the approach is the same.
|
||||
|
||||
First, decouple your plugin’s business logic from the dependencies that
|
||||
are not exposed through the {kib} Platform, hapi.js, and Angular.js. Then
|
||||
introduce plugin definitions that more accurately reflect how plugins
|
||||
are defined in the {kib} Platform. Finally, replace the functionality you
|
||||
consume from the core and other plugins with their {kib} Platform equivalents.
|
||||
|
||||
Once those things are finished for any given plugin, it can officially
|
||||
be switched to the new plugin system.
|
||||
|
||||
=== Server-side plan of action
|
||||
|
||||
Legacy server-side plugins access functionality from the core and other
|
||||
plugins at runtime via function arguments, which is similar to how they
|
||||
must be architected to use the new plugin system. This greatly
|
||||
simplifies the plan of action for migrating server-side plugins.
|
||||
The main challenge here is to de-couple plugin logic from hapi.js server and request objects.
|
||||
|
||||
For migration examples, see <<migrating-legacy-plugins-examples>>.
|
||||
|
||||
=== Browser-side plan of action
|
||||
|
||||
It is generally a much greater challenge preparing legacy browser-side
|
||||
code for the {kib} Platform than it is server-side, and as such there are
|
||||
a few more steps. The level of effort here is proportional to the extent
|
||||
to which a plugin is dependent on Angular.js.
|
||||
|
||||
To complicate matters further, a significant amount of the business
|
||||
logic in {kib} client-side code exists inside the `ui/public`
|
||||
directory (aka ui modules), and all of that must be migrated as well.
|
||||
|
||||
Because the usage of Angular and `ui/public` modules varies widely between
|
||||
legacy plugins, there is no `one size fits all` solution to migrating
|
||||
your browser-side code to the {kib} Platform.
|
||||
|
||||
For migration examples, see <<migrating-legacy-plugins-examples>>.
|
||||
|
||||
=== Frequently asked questions
|
||||
|
||||
==== Do plugins need to be converted to TypeScript?
|
||||
|
||||
No. That said, the migration process will require a lot of refactoring,
|
||||
and TypeScript will make this dramatically easier and less risky.
|
||||
|
||||
Although it's not strictly necessary, we encourage any plugin that exposes an extension point to do so
|
||||
with first-class type support so downstream plugins that _are_ using
|
||||
TypeScript can depend on those types.
|
||||
|
||||
==== How can I avoid passing core services deeply within my UI component tree?
|
||||
|
||||
Some core services are purely presentational, for example
|
||||
`core.overlays.openModal()`, where UI
|
||||
code does need access to these deeply within your application. However,
|
||||
passing these services down as props throughout your application leads
|
||||
to lots of boilerplate. To avoid this, you have three options:
|
||||
|
||||
* Use an abstraction layer, like Redux, to decouple your UI code from
|
||||
core (*this is the highly preferred option*).
|
||||
* https://github.com/reduxjs/redux-thunk#injecting-a-custom-argument[redux-thunk]
|
||||
and
|
||||
https://redux-saga.js.org/docs/api/#createsagamiddlewareoptions[redux-saga]
|
||||
already have ways to do this.
|
||||
* Use React Context to provide these services to large parts of your
|
||||
React tree.
|
||||
* Create a high-order-component that injects core into a React
|
||||
component.
|
||||
* This would be a stateful module that holds a reference to core, but
|
||||
provides it as props to components with a `withCore(MyComponent)`
|
||||
interface. This can make testing components simpler. (Note: this module
|
||||
cannot be shared across plugin boundaries, see above).
|
||||
* Create a global singleton module that gets imported into each module
|
||||
that needs it. This module cannot be shared across plugin
|
||||
boundaries.
|
||||
https://gist.github.com/epixa/06c8eeabd99da3c7545ab295e49acdc3[Example].
|
||||
|
||||
If you find that you need many different core services throughout your
|
||||
application, this might indicate a problem in your code and could lead to pain down the
|
||||
road. For instance, if you need access to an HTTP Client or
|
||||
SavedObjectsClient in many places in your React tree, it’s likely that a
|
||||
data layer abstraction (like Redux) could make developing your plugin
|
||||
much simpler.
|
||||
|
||||
Without such an abstraction, you will need to mock out core services
|
||||
throughout your test suite and will couple your UI code very tightly to
|
||||
core. However, if you can contain all of your integration points with
|
||||
core to Redux middleware and reducers, you only need to mock core
|
||||
services once and benefit from being able to change those integrations
|
||||
with core in one place rather than many. This will become incredibly
|
||||
handy when core APIs have breaking changes.
|
||||
|
||||
==== How is the 'common' code shared on both the client and the server?
|
||||
|
||||
There is no formal notion of `common` code that can safely be imported
|
||||
from either client-side or server-side code. However, if a plugin author
|
||||
wishes to maintain a set of code in their plugin in a single place and
|
||||
then expose it to both server-side and client-side code, they can do so
|
||||
by exporting the index files for both the `server` and `public`
|
||||
directories.
|
||||
|
||||
Plugins _should not_ ever import code from deeply inside another plugin
|
||||
(e.g. `my_plugin/public/components`) or from other top-level directories
|
||||
(e.g. `my_plugin/common/constants`) as these are not checked for breaking
|
||||
changes and are considered unstable and subject to change at any time.
|
||||
You can have other top-level directories like `my_plugin/common`, but
|
||||
our tooling will not treat these as a stable API, and linter rules will
|
||||
prevent importing from these directories _from outside the plugin_.
|
||||
|
||||
The benefit of this approach is that the details of where code lives and
|
||||
whether it is accessible in multiple runtimes is an implementation
|
||||
detail of the plugin itself. A plugin consumer that is writing
|
||||
client-side code only ever needs to concern themselves with the
|
||||
client-side contracts being exposed, and the same can be said for
|
||||
server-side contracts on the server.
|
||||
|
||||
A plugin author, who decides some set of code should diverge from having
|
||||
a single `common` definition, can now safely change the implementation
|
||||
details without impacting downstream consumers.
|
||||
|
||||
==== How do I find {kib} Platform services?
|
||||
|
||||
Most of the utilities you used to build legacy plugins are available
|
||||
in the {kib} Platform or {kib} Platform plugins. To help you find the new
|
||||
home for new services, use the tables below to find where the {kib}
|
||||
Platform equivalent lives.
|
||||
|
||||
===== Client-side
|
||||
====== Core services
|
||||
|
||||
In client code, `core` can be imported in legacy plugins via the
|
||||
`ui/new_platform` module.
|
||||
|
||||
[[client-side-core-migration-table]]
|
||||
[width="100%",cols="15%,85%",options="header",]
|
||||
|===
|
||||
|Legacy Platform |{kib} Platform
|
||||
|`chrome.addBasePath`
|
||||
|{kib-repo}/tree/{branch}/docs/development/core/public/kibana-plugin-core-public.ibasepath.md[`core.http.basePath.prepend`]
|
||||
|
||||
|`chrome.breadcrumbs.set`
|
||||
|{kib-repo}/tree/{branch}/docs/development/core/public/kibana-plugin-core-public.chromestart.setbreadcrumbs.md[`core.chrome.setBreadcrumbs`]
|
||||
|
||||
|`chrome.getUiSettingsClient`
|
||||
|{kib-repo}/tree/{branch}/docs/development/core/public/kibana-plugin-core-public.corestart.uisettings.md[`core.uiSettings`]
|
||||
|
||||
|`chrome.helpExtension.set`
|
||||
|{kib-repo}/tree/{branch}/docs/development/core/public/kibana-plugin-core-public.chromestart.sethelpextension.md[`core.chrome.setHelpExtension`]
|
||||
|
||||
|`chrome.setVisible`
|
||||
|{kib-repo}/tree/{branch}/docs/development/core/public/kibana-plugin-core-public.chromestart.setisvisible.md[`core.chrome.setIsVisible`]
|
||||
|
||||
|`chrome.getInjected`
|
||||
| Request Data with your plugin REST HTTP API.
|
||||
|
||||
|`chrome.setRootTemplate` / `chrome.setRootController`
|
||||
|Use application mounting via {kib-repo}/tree/{branch}/docs/development/core/public/kibana-plugin-core-public.applicationsetup.register.md[`core.application.register`]
|
||||
|
||||
|`chrome.navLinks.update`
|
||||
|{kib-repo}/tree/{branch}/docs/development/core/public/kibana-plugin-core-public.app.updater_.md[`core.appbase.updater`]. Use the `updater$` property when registering your application via
|
||||
`core.application.register`
|
||||
|
||||
|`import { recentlyAccessed } from 'ui/persisted_log'`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.chromerecentlyaccessed.md[`core.chrome.recentlyAccessed`]
|
||||
|
||||
|`ui/capabilities`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.capabilities.md[`core.application.capabilities`]
|
||||
|
||||
|`ui/documentation_links`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md[`core.docLinks`]
|
||||
|
||||
|`ui/kfetch`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.httpsetup.md[`core.http`]
|
||||
|
||||
|`ui/notify`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.notificationsstart.md[`core.notifications`]
|
||||
and
|
||||
{kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.overlaystart.md[`core.overlays`]. Toast messages are in `notifications`, banners are in `overlays`.
|
||||
|
||||
|`ui/routes`
|
||||
|There is no global routing mechanism. Each app
|
||||
{kib-repo}blob/{branch}/rfcs/text/0004_application_service_mounting.md#complete-example[configures
|
||||
its own routing].
|
||||
|
||||
|`ui/saved_objects`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.savedobjectsstart.md[`core.savedObjects`]
|
||||
|
||||
|`ui/doc_title`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.chromedoctitle.md[`core.chrome.docTitle`]
|
||||
|
||||
|`uiExports/injectedVars` / `chrome.getInjected`
|
||||
|<<configuration-service, Configuration service>>. Can only be used to expose configuration properties
|
||||
|===
|
||||
|
||||
_See also:
|
||||
{kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.corestart.md[Public’s
|
||||
CoreStart API Docs]_
|
||||
|
||||
====== Plugins for shared application services
|
||||
|
||||
In client code, we have a series of plugins that house shared
|
||||
application services, which are not technically part of `core`, but are
|
||||
often used in {kib} plugins.
|
||||
|
||||
This table maps some of the most commonly used legacy items to their {kib}
|
||||
Platform locations. For the API provided by {kib} Plugins see <<plugin-list, the plugin list>>.
|
||||
|
||||
[width="100%",cols="15,85",options="header"]
|
||||
|===
|
||||
|Legacy Platform |{kib} Platform
|
||||
|`import 'ui/apply_filters'` |N/A. Replaced by triggering an
|
||||
{kib-repo}blob/{branch}/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.action_global_apply_filter.md[APPLY_FILTER_TRIGGER trigger]. Directive is deprecated.
|
||||
|
||||
|`import 'ui/filter_bar'`
|
||||
|`import { FilterBar } from 'plugins/data/public'`. Directive is deprecated.
|
||||
|
||||
|`import 'ui/query_bar'`
|
||||
|`import { QueryStringInput } from 'plugins/data/public'` {kib-repo}blob/{branch}/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md[QueryStringInput]. Directives are deprecated.
|
||||
|
||||
|`import 'ui/search_bar'`
|
||||
|`import { SearchBar } from 'plugins/data/public'` {kib-repo}blob/{branch}/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstartui.searchbar.md[SearchBar]. Directive is deprecated.
|
||||
|
||||
|`import 'ui/kbn_top_nav'`
|
||||
|`import { TopNavMenu } from 'plugins/navigation/public'`. Directive was removed.
|
||||
|
||||
|`ui/saved_objects/saved_object_finder`
|
||||
|`import { SavedObjectFinder } from 'plugins/saved_objects/public'`
|
||||
|
||||
|`core_plugins/interpreter`
|
||||
|{kib-repo}blob/{branch}/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.md[`plugins.data.expressions`]
|
||||
|
||||
|`ui/courier`
|
||||
|{kib-repo}blob/{branch}/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginsetup.search.md[`plugins.data.search`]
|
||||
|
||||
|`ui/agg_types`
|
||||
|{kib-repo}blob/{branch}/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.aggs.md[`plugins.data.search.aggs`]. Most code is available for
|
||||
static import. Stateful code is part of the `search` service.
|
||||
|
||||
|`ui/embeddable`
|
||||
|{kib-repo}blob/{branch}/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablesetup.md[`plugins.embeddables`]
|
||||
|
||||
|`ui/filter_manager`
|
||||
|`import { FilterManager } from 'plugins/data/public'` {kib-repo}blob/{branch}/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.filtermanager.md[`FilterManager`]
|
||||
|
||||
|`ui/index_patterns`
|
||||
|`import { IndexPatternsService } from 'plugins/data/public'` {kib-repo}blob/{branch}/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.md[IndexPatternsService]
|
||||
|
||||
|`import 'ui/management'`
|
||||
|`plugins.management.sections`. Management plugin `setup` contract.
|
||||
|
||||
|`import 'ui/registry/field_format_editors'`
|
||||
|`plugins.indexPatternManagement.fieldFormatEditors` indexPatternManagement plugin `setup` contract.
|
||||
|
||||
|`ui/registry/field_formats`
|
||||
|{kib-repo}blob/{branch}/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldformats.md[`plugins.data.fieldFormats`]
|
||||
|
||||
|`ui/registry/feature_catalogue`
|
||||
|`plugins.home.featureCatalogue.register` home plugin `setup` contract
|
||||
|
||||
|`ui/registry/vis_types`
|
||||
|`plugins.visualizations`
|
||||
|
||||
|`ui/vis`
|
||||
|`plugins.visualizations`
|
||||
|
||||
|`ui/share`
|
||||
|`plugins.share`. share plugin `start` contract. `showShareContextMenu` is now called
|
||||
`toggleShareContextMenu`, `ShareContextMenuExtensionsRegistryProvider`
|
||||
is now called `register`
|
||||
|
||||
|`ui/vis/vis_factory`
|
||||
|`plugins.visualizations`
|
||||
|
||||
|`ui/vis/vis_filters`
|
||||
|`plugins.visualizations.filters`
|
||||
|
||||
|`ui/utils/parse_es_interval`
|
||||
|`import { search: { aggs: { parseEsInterval } } } from 'plugins/data/public'`. `parseEsInterval`, `ParsedInterval`, `InvalidEsCalendarIntervalError`,
|
||||
`InvalidEsIntervalFormatError` items were moved to the `Data Plugin` as
|
||||
a static code
|
||||
|===
|
||||
|
||||
===== Server-side
|
||||
|
||||
====== Core services
|
||||
|
||||
In server code, `core` can be accessed from either `server.newPlatform`
|
||||
or `kbnServer.newPlatform`:
|
||||
|
||||
[width="100%",cols="17, 83",options="header"]
|
||||
|===
|
||||
|Legacy Platform |{kib} Platform
|
||||
|`server.config()`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.plugininitializercontext.config.md[`initializerContext.config.create()`]. Must also define schema. See <<config-migration>>
|
||||
|
||||
|`server.route`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.createrouter.md[`core.http.createRouter`]. See <<http-routes-migration, HTTP routes migration>>.
|
||||
|
||||
|`server.renderApp()`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.httpresourcesservicetoolkit.rendercoreapp.md[`response.renderCoreApp()`]. See <<render-html-migration, Render HTML migration>>.
|
||||
|
||||
|`server.renderAppWithDefaultConfig()`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.httpresourcesservicetoolkit.renderanonymouscoreapp.md[`response.renderAnonymousCoreApp()`]. See <<render-html-migration, Render HTML migration>>.
|
||||
|
||||
|`request.getBasePath()`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.basepath.md[`core.http.basePath.get`]
|
||||
|
||||
|`server.plugins.elasticsearch.getCluster('data')`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.iscopedclusterclient.md[`context.core.elasticsearch.client`]
|
||||
|
||||
|`server.plugins.elasticsearch.getCluster('admin')`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.iscopedclusterclient.md[`context.core.elasticsearch.client`]
|
||||
|
||||
|`server.plugins.elasticsearch.createCluster(...)`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.createclient.md[`core.elasticsearch.createClient`]
|
||||
|
||||
|`server.savedObjects.setScopedSavedObjectsClientFactory`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.setclientfactoryprovider.md[`core.savedObjects.setClientFactoryProvider`]
|
||||
|
||||
|`server.savedObjects.addScopedSavedObjectsClientWrapperFactory`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.addclientwrapper.md[`core.savedObjects.addClientWrapper`]
|
||||
|
||||
|`server.savedObjects.getSavedObjectsRepository`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicestart.createinternalrepository.md[`core.savedObjects.createInternalRepository`]
|
||||
{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicestart.createscopedrepository.md[`core.savedObjects.createScopedRepository`]
|
||||
|
||||
|`server.savedObjects.getScopedSavedObjectsClient`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicestart.getscopedclient.md[`core.savedObjects.getScopedClient`]
|
||||
|
||||
|`request.getSavedObjectsClient`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.core.md[`context.core.savedObjects.client`]
|
||||
|
||||
|`request.getUiSettingsService`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.iuisettingsclient.md[`context.core.uiSettings.client`]
|
||||
|
||||
|`kibana.Plugin.deprecations`
|
||||
|<<handle-plugin-configuration-deprecations, Handle plugin configuration deprecations>> and {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.pluginconfigdescriptor.md[`PluginConfigDescriptor.deprecations`]. Deprecations from {kib} Platform are not applied to legacy configuration
|
||||
|
||||
|`kibana.Plugin.savedObjectSchemas`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.registertype.md[`core.savedObjects.registerType`]
|
||||
|
||||
|`kibana.Plugin.mappings`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.registertype.md[`core.savedObjects.registerType`]. Learn more in <<saved-objects-migration, SavedObjects migration>>.
|
||||
|
||||
|`kibana.Plugin.migrations`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.registertype.md[`core.savedObjects.registerType`]. Learn more in <<saved-objects-migration, SavedObjects migration>>.
|
||||
|
||||
|`kibana.Plugin.savedObjectsManagement`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.registertype.md[`core.savedObjects.registerType`]. Learn more in <<saved-objects-migration, SavedObjects migration>>.
|
||||
|===
|
||||
|
||||
_See also:
|
||||
{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.coresetup.md[Server’s
|
||||
CoreSetup API Docs]_
|
||||
|
||||
====== Plugin services
|
||||
|
||||
[width="100%",cols="50%,50%",options="header",]
|
||||
|===
|
||||
|Legacy Platform |{kib} Platform
|
||||
|`xpack_main.registerFeature`
|
||||
|{kib-repo}blob/{branch}/x-pack/plugins/features/server/plugin.ts[`plugins.features.registerKibanaFeature`]
|
||||
|
||||
|`xpack_main.feature(pluginID).registerLicenseCheckResultsGenerator`
|
||||
|{kib-repo}blob/{branch}/x-pack/plugins/licensing/README.md[`x-pack licensing plugin`]
|
||||
|===
|
||||
|
||||
===== UI Exports
|
||||
|
||||
The legacy platform used a set of `uiExports` to inject modules from
|
||||
one plugin into other plugins. This mechanism is not necessary for the
|
||||
{kib} Platform because _all plugins are executed on the page at once_,
|
||||
though only one application is rendered at a time.
|
||||
|
||||
This table shows where these uiExports have moved to in the {kib}
|
||||
Platform.
|
||||
|
||||
[width="100%",cols="15%,85%",options="header"]
|
||||
|===
|
||||
|Legacy Platform |{kib} Platform
|
||||
|`aliases`
|
||||
|`N/A`.
|
||||
|
||||
|`app`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.applicationsetup.register.md[`core.application.register`]
|
||||
|
||||
|`canvas`
|
||||
|{kib-repo}blob/{branch}/x-pack/plugins/canvas/README.md[Canvas plugin API]
|
||||
|
||||
|`chromeNavControls`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.chromenavcontrols.md[`core.chrome.navControls.register{Left,Right}`]
|
||||
|
||||
|`docViews`
|
||||
|{kib-repo}blob/{branch}/src/plugins/discover/public/[`discover.docViews.addDocView`]
|
||||
|
||||
|`embeddableActions`
|
||||
|{kib-repo}blob/{branch}/src/plugins/embeddable/README.asciidoc[`embeddable plugin`]
|
||||
|
||||
|`embeddableFactories`
|
||||
|{kib-repo}blob/{branch}/src/plugins/embeddable/README.asciidoc[`embeddable plugin`], {kib-repo}blob/{branch}/docs/development/plugins/embeddable/server/kibana-plugin-plugins-embeddable-server.embeddablesetup.registerembeddablefactory.md[`embeddable.registerEmbeddableFactory`]
|
||||
|
||||
|`fieldFormatEditors`, `fieldFormats`
|
||||
|{kib-repo}blob/{branch}/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldformats.md[`data.fieldFormats`]
|
||||
|
||||
|`hacks`
|
||||
|`N/A`. Just run the code in your plugin’s `start` method.
|
||||
|
||||
|`home`
|
||||
|{kib-repo}blob/{branch}/src/plugins/embeddable/README.asciidoc[`home plugin`] {kib-repo}blob/{branch}/src/plugins/home/public/services/feature_catalogue[`home.featureCatalogue.register`]
|
||||
|
||||
|`indexManagement`
|
||||
|{kib-repo}blob/{branch}/x-pack/plugins/index_management/README.md[`index management plugin`]
|
||||
|
||||
|`injectDefaultVars`
|
||||
|`N/A`. Plugins will only be able to allow config values for the frontend. See<<configuration-service>>
|
||||
|
||||
|`inspectorViews`
|
||||
|{kib-repo}blob/{branch}/src/plugins/inspector/README.md[`inspector plugin`]
|
||||
|
||||
|`interpreter`
|
||||
|{kib-repo}blob/{branch}/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.md[`plugins.data.expressions`]
|
||||
|
||||
|`links`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.applicationsetup.register.md[`core.application.register`]
|
||||
|
||||
|`managementSections`
|
||||
|{kib-repo}blob/{branch}/src/plugins/management/README.md[`plugins.management.sections.register`]
|
||||
|
||||
|`mappings`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.registertype.md[`core.savedObjects.registerType`]
|
||||
|
||||
|`migrations`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.registertype.md[`core.savedObjects.registerType`]
|
||||
|
||||
|`navbarExtensions`
|
||||
|`N/A`. Deprecated.
|
||||
|
||||
|`savedObjectSchemas`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.registertype.md[`core.savedObjects.registerType`]
|
||||
|
||||
|`savedObjectsManagement`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.registertype.md[`core.savedObjects.registerType`]
|
||||
|
||||
|`savedObjectTypes`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.registertype.md[`core.savedObjects.registerType`]
|
||||
|
||||
|`search`
|
||||
|{kib-repo}blob/{branch}/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md[`data.search`]
|
||||
|
||||
|`shareContextMenuExtensions`
|
||||
|{kib-repo}blob/{branch}/src/plugins/share/README.md[`plugins.share`]
|
||||
|
||||
|`taskDefinitions`
|
||||
|{kib-repo}blob/{branch}/x-pack/plugins/task_manager/README.md[`taskManager plugin`]
|
||||
|
||||
|`uiCapabilities`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.applicationsetup.register.md[`core.application.register`]
|
||||
|
||||
|`uiSettingDefaults`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.uisettingsservicesetup.md[`core.uiSettings.register`]
|
||||
|
||||
|`validations`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicesetup.registertype.md[`core.savedObjects.registerType`]
|
||||
|
||||
|`visEditorTypes`
|
||||
|{kib-repo}blob/{branch}/src/plugins/visualizations[`visualizations plugin`]
|
||||
|
||||
|`visTypeEnhancers`
|
||||
|{kib-repo}blob/{branch}/src/plugins/visualizations[`visualizations plugin`]
|
||||
|
||||
|`visTypes`
|
||||
|{kib-repo}blob/{branch}/src/plugins/visualizations[`visualizations plugin`]
|
||||
|
||||
|`visualize`
|
||||
|{kib-repo}blob/{branch}/src/plugins/visualize/README.md[`visualize plugin`]
|
||||
|===
|
||||
|
||||
===== Plugin Spec
|
||||
|
||||
[width="100%",cols="22%,78%",options="header",]
|
||||
|===
|
||||
|Legacy Platform |{kib} Platform
|
||||
|`id`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md[`manifest.id`]
|
||||
|
||||
|`require`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md[`manifest.requiredPlugins`]
|
||||
|
||||
|`version`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md[`manifest.version`]
|
||||
|
||||
|`kibanaVersion`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md[`manifest.kibanaVersion`]
|
||||
|
||||
|`configPrefix`
|
||||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md[`manifest.configPath`]
|
||||
|
||||
|`config`
|
||||
|<<configuration-service>>
|
||||
|
||||
|`deprecations`
|
||||
|<<configuration-service>>
|
||||
|
||||
|`uiExports`
|
||||
|`N/A`. Use platform & plugin public contracts
|
||||
|
||||
|`publicDir`
|
||||
|`N/A`. {kib} Platform serves static assets from `/public/assets` folder under `/plugins/{id}/assets/{path*}` URL.
|
||||
|
||||
|`preInit`, `init`, `postInit`
|
||||
|`N/A`. Use {kib} Platform <<plugin-lifecycles,plugin-lifecycles>>
|
||||
|===
|
||||
|
||||
=== See also
|
||||
|
||||
For examples on how to migrate from specific legacy APIs, see <<migrating-legacy-plugins-examples>>.
|
50
docs/developer/plugin/plugin-tooling.asciidoc
Normal file
50
docs/developer/plugin/plugin-tooling.asciidoc
Normal file
|
@ -0,0 +1,50 @@
|
|||
[[plugin-tooling]]
|
||||
== Plugin tooling
|
||||
|
||||
[discrete]
|
||||
[[automatic-plugin-generator]]
|
||||
=== Automatic plugin generator
|
||||
|
||||
We recommend that you kick-start your plugin by generating it with the {kib-repo}tree/{branch}/packages/kbn-plugin-generator[{kib} Plugin Generator]. Run the following in the {kib} repo, and you will be asked a couple of questions, see some progress bars, and have a freshly generated plugin ready for you to play with in {kib}'s `plugins` folder.
|
||||
|
||||
["source","shell"]
|
||||
-----------
|
||||
node scripts/generate_plugin my_plugin_name # replace "my_plugin_name" with your desired plugin name
|
||||
-----------
|
||||
|
||||
[discrete]
|
||||
=== Plugin location
|
||||
|
||||
The {kib} directory must be named `kibana`, and your plugin directory should be located in the root of `kibana` in a `plugins` directory, for example:
|
||||
|
||||
["source","shell"]
|
||||
----
|
||||
.
|
||||
└── kibana
|
||||
└── plugins
|
||||
├── foo-plugin
|
||||
└── bar-plugin
|
||||
----
|
||||
|
||||
=== Build plugin distributable
|
||||
WARNING: {kib} distributable is not shipped with `@kbn/optimizer` anymore. You need to pre-build your plugin for use in production.
|
||||
|
||||
You can leverage {kib-repo}blob/{branch}/packages/kbn-plugin-helpers[@kbn/plugin-helpers] to build a distributable archive for your plugin.
|
||||
The package transpiles the plugin code, adds polyfills, and links necessary js modules in the runtime.
|
||||
You don't need to install the `plugin-helpers`: the `package.json` is already pre-configured if you created your plugin with `node scripts/generate_plugin` script.
|
||||
To build your plugin run within your plugin folder:
|
||||
["source","shell"]
|
||||
-----------
|
||||
yarn build
|
||||
-----------
|
||||
It will output a`zip` archive in `kibana/plugins/my_plugin_name/build/` folder.
|
||||
|
||||
=== Install a plugin from archive
|
||||
See <<install-plugin, How to install a plugin>>.
|
||||
|
||||
=== Run {kib} with your plugin in dev mode
|
||||
Run `yarn start` in the {kib} root folder. Make sure {kib} found and bootstrapped your plugin:
|
||||
["source","shell"]
|
||||
-----------
|
||||
[info][plugins-system] Setting up […] plugins: […, myPluginName, …]
|
||||
-----------
|
63
docs/developer/plugin/testing-kibana-plugin.asciidoc
Normal file
63
docs/developer/plugin/testing-kibana-plugin.asciidoc
Normal file
|
@ -0,0 +1,63 @@
|
|||
[[testing-kibana-plugin]]
|
||||
== Testing {kib} Plugins
|
||||
=== Writing tests
|
||||
Learn about <<development-tests, recommended testing approaches >>.
|
||||
|
||||
=== Mock {kib} Core services in tests
|
||||
|
||||
Core services already provide mocks to simplify testing and make sure
|
||||
plugins always rely on valid public contracts:
|
||||
|
||||
*my_plugin/server/plugin.test.ts*
|
||||
[source,typescript]
|
||||
----
|
||||
import { configServiceMock } from 'kibana/server/mocks';
|
||||
|
||||
const configService = configServiceMock.create();
|
||||
configService.atPath.mockReturnValue(config$);
|
||||
…
|
||||
const plugin = new MyPlugin({ configService }, …);
|
||||
----
|
||||
|
||||
Or if you need to get the whole core `setup` or `start` contracts:
|
||||
|
||||
*my_plugin/server/plugin.test.ts*
|
||||
[source,typescript]
|
||||
----
|
||||
import { coreMock } from 'kibana/public/mocks';
|
||||
|
||||
const coreSetup = coreMock.createSetup();
|
||||
coreSetup.uiSettings.get.mockImplementation((key: string) => {
|
||||
…
|
||||
});
|
||||
…
|
||||
const plugin = new MyPlugin(coreSetup, ...);
|
||||
----
|
||||
|
||||
=== Writing mocks for your plugin
|
||||
Although it isn’t mandatory, we strongly recommended you export your
|
||||
plugin mocks as well, in order for dependent plugins to use them in
|
||||
tests. Your plugin mocks should be exported from the root `/server` and
|
||||
`/public` directories in your plugin:
|
||||
|
||||
*my_plugin/(server|public)/mocks.ts*
|
||||
[source,typescript]
|
||||
----
|
||||
const createSetupContractMock = () => {
|
||||
const startContract: jest.Mocked<MyPluginStartContract>= {
|
||||
isValid: jest.fn(),
|
||||
}
|
||||
// here we already type check as TS infers to the correct type declared above
|
||||
startContract.isValid.mockReturnValue(true);
|
||||
return startContract;
|
||||
}
|
||||
|
||||
export const myPluginMocks = {
|
||||
createSetup: createSetupContractMock,
|
||||
createStart: …
|
||||
}
|
||||
----
|
||||
|
||||
Plugin mocks should consist of mocks for _public APIs only_:
|
||||
`setup`, `start` & `stop` contracts. Mocks aren’t necessary for pure functions as
|
||||
other plugins can call the original implementation in tests.
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -8,7 +8,7 @@ Core Plugin API Documentation:
|
|||
- [Core Server API](/docs/development/core/server/kibana-plugin-core-server.md)
|
||||
- [Conventions for Plugins](./CONVENTIONS.md)
|
||||
- [Testing Kibana Plugins](./TESTING.md)
|
||||
- [Migration guide for porting existing plugins](./MIGRATION.md)
|
||||
- [Kibana Platform Plugin API](./docs/developer/architecture/kibana-platform-plugin-api.asciidoc )
|
||||
|
||||
Internal Documentation:
|
||||
- [Saved Objects Migrations](./server/saved_objects/migrations/README.md)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue