Migrating legacy plugins to the Kibana Platform
editMigrating legacy plugins to the Kibana Platform
editIn Kibana 7.10, support for legacy-style Kibana plugins was completely removed. Moving forward, all plugins must be built on the new Kibana Platform Plugin API. This guide is intended to assist plugin authors in migrating their legacy plugin to the Kibana Platform Plugin API.
Make no mistake, it is going to take a lot of work to move certain plugins to the Kibana 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 Plugin API to get an overview of how plugins work in the Kibana 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
editKibana 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 Kibana Platform, depending on whether the legacy plugin code is on the server or in the browser.
Challenges on the server
editThe general architecture of legacy server-side code is similar to
the Kibana 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 Kibana 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
editThe legacy plugin system in the browser is fundamentally incompatible
with the Kibana 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 Kibana 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 Kibana Platform, and in
the fact we intend down the line to build Kibana 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
editTo 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 Kibana Platform, hapi.js, and Angular.js. Then introduce plugin definitions that more accurately reflect how plugins are defined in the Kibana Platform. Finally, replace the functionality you consume from the core and other plugins with their Kibana 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
editLegacy 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 Migration Examples.
Browser-side plan of action
editIt is generally a much greater challenge preparing legacy browser-side code for the Kibana 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 Kibana 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 Kibana Platform.
For migration examples, see Migration Examples.
Frequently asked questions
editDo plugins need to be converted to TypeScript?
editNo. 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?
editSome 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).
- redux-thunk and 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. 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?
editThere 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 Kibana Platform services?
editMost of the utilities you used to build legacy plugins are available in the Kibana Platform or Kibana Platform plugins. To help you find the new home for new services, use the tables below to find where the Kibana Platform equivalent lives.
Client-side
editCore services
editIn client code, core
can be imported in legacy plugins via the
ui/new_platform
module.
Legacy Platform | Kibana Platform |
---|---|
|
|
|
|
|
|
|
|
|
|
|
Request Data with your plugin REST HTTP API. |
|
Use application mounting via |
|
|
|
|
|
|
|
|
|
|
|
|
|
There is no global routing mechanism. Each app configures its own routing. |
|
|
|
|
|
Configuration service. Can only be used to expose configuration properties |
See also: Public’s CoreStart API Docs
Plugins for shared application services
editIn 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 Kibana plugins.
This table maps some of the most commonly used legacy items to their Kibana Platform locations. For the API provided by Kibana Plugins see the plugin list.
Legacy Platform | Kibana Platform |
---|---|
|
N/A. Replaced by triggering an APPLY_FILTER_TRIGGER trigger. Directive is deprecated. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Server-side
editCore services
editIn server code, core
can be accessed from either server.newPlatform
or kbnServer.newPlatform
:
Legacy Platform | Kibana Platform |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Handle plugin configuration deprecations and |
|
|
|
|
|
|
|
|
See also: Server’s CoreSetup API Docs
Plugin services
editLegacy Platform | Kibana Platform |
---|---|
|
|
|
UI Exports
editThe legacy platform used a set of uiExports
to inject modules from
one plugin into other plugins. This mechanism is not necessary for the
Kibana 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 Kibana Platform.
Legacy Platform | Kibana Platform |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Plugin Spec
editLegacy Platform | Kibana Platform |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
See also
editFor examples on how to migrate from specific legacy APIs, see Migration Examples.