The Basics of Kibana Pluggability

May 07, 2020

Goals

The goal of this post is to help developers building plugins on top of Kibana understand key concepts and terminology. Some of this terminology is industry wide, while some is more esoteric, and unique to Elastic. When reading documentation, it’s likely you’ll run into these terms, so it will help to have an understanding of them when building plugins.

By the end of this post you should understand the differences and similarities between registries, extension points, plugins, and applications.

This is written right around the release of 7.7, so things may change in the future, as Kibana APIs are still under quite a bit of fluctuation.

Terminology

Plugin

A plugin is code a developer writes that does not have to be part of the kibana repo.

  • A plugin is an integral concept in Kibana
  • A plugin can register an application, or not.
  • A plugin makes use of core’s, or other plugin’s, extension points.
  • The primary example is registering an application.

Application

An application is something that shows up in the navigation menu. Discover, Dashboard, Visualize, Canvas, Maps - these are all applications.

kibana applications

You can see an example of registering an application here

core.application.register({
    id: 'greetingCards',
    title: 'Greeting cards',
    async mount(params: AppMountParameters) {
      const [coreStart] = await core.getStartServices();
      const { renderApp } = await import('./app');
      const services = createServiceWrapper(coreStart);
      return renderApp({ appBasePath: params.appBasePath, services }, params.element);
    },
  });

Extension points

Extension points are certain functions exposed from core, or other plugin’s setup and start contracts, to allow a developer to customize Kibana’s experience. For instance the code above uses a core extension point, core.application.register, to add custom applications.

You can access all core extension points as the first parameter passed into a plugin’s setup function (CoreSetup), and start (CoreStart) functions.

class MyPlugin extends Plugin {
  setup(core: CoreSetup) {...}
  start(core: CoreStart) {...}
}

You can access other plugin’s extension points from the second parameter that goes into these plugin lifecycle functions:

class MyPlugin extends Plugin {
  setup(core: CoreSetup, { fooPlugin, barPlugin, uiActions }: MyPluginSetupDependences) {...}
  start(core: CoreStart, { fooPlugin, barPlugin, embeddables }: MyPluginStartDependences) {...}
}

Don’t forget though, if you want access to a certain plugin’s extension points, you have to add it as a required or optional dependency in your kibana.json file:

{
  "id": "demoSearch",
  "version": "0.0.1",
  "kibanaVersion": "kibana",
  "configPath": ["demoSearch"],
  "server": true,
  "ui": true,
  "requiredPlugins": ["fooPlugin", "uiActions"],
  "optionalPlugins": ["barPlugin", "embeddables"]
}

Not every function exposed is considered an “extension point”. For example, core.http.fetch is a helper function for grabbing data with authentication injected. Since in and of itself, this wouldn’t have any direct side affects to a user’s experience of Kibana, I wouldn’t call it an “extension point”.

Some more examples of extension points:

  • core.application.register
  • core.notifications.toasts.addSuccess
  • core.overlays.showModal
  • embeddables.registerEmbeddableFactory
  • uiActions.registerAction
  • alerts.registerNavigation
  • alerting.registerType
  • core.savedObjects.registerType

Registries

Registries are stateful containers. They contain registered items that adhere to adhere to a certain generic shape, or base implementation. Registries are used primarily for two situations:

  • When a list of things is going to be displayed in the UI somewhere
  • When the state that is used to create instances of these things ends up serialized and persisted somewhere.

Many extension points support the ability for external plugin authors to add registry items.

The developer who creates a registry is called a registrar. A developer who adds a custom registry item is called a registrator.

One example of the former is how we display all registered uiActions attached to the CONTEXT_MENU_TRIGGER here

context menu panel

The latter example is often in conjunction with the former. For instance, certain registered embeddables are dynamically displayed in the UI:

embeddables listed

But the embeddable registry is also how we deserialize this data when it’s persisted.

URL generators is one example of a registry of items that is only used for serialization and deserialization.

Some more examples of registries are:

  • core.application.register
  • embeddables.registerEmbeddableFactory
  • uiActions.registerAction
  • alerts.registerNavigation
  • alerting.registerType
  • core.savedObjects.registerType

Examples of extensions points that are not considered registries:

  • core.notifications.toasts.addSuccess
  • core.overlays.showModal

A story of greeting cards

Lets start with a very simple story to explain where registries come into play.

This blog post is only going to touch on the first part of the story. I’ll have to write more later to get to the part where a registry is actually implemented.

Phase 1: Build a simple greeting card application

Lets build a plugin in Kibana that creates greeting cards.

You can explore the code here .

It’s very simple. It lets you create two kinds of greeting cards, “Get well soon”, and “Happy birthday”, and add some personalization to it:

greeting card story create

Then you can view it, in full screen mode:

greeting card story view

Pitfalls

I used some antipatterns on purpose in this example.

I hard coded the greeting card types. If I want to add another one, I’m going to have to add multiple “if” statements

This is not very maintainable.

This is a code smell.

Instead of having to add an extra conditional statement for every type of greeting card, lets create an abstraction layer - a registry of greeting card types - that we can use dynamically.

That’s all for now. Stay tuned for part 2!