Kibana 插件 API

编辑

此功能处于技术预览阶段,可能会在未来的版本中更改或删除。Elastic 将努力修复任何问题,但技术预览中的功能不受官方 GA 功能的支持 SLA 约束。

Kibana 平台插件是朝着为所有开发人员稳定 Kibana 架构迈出的重要一步。我们确保插件可以继续使用它们今天使用的大部分技术,至少从技术角度来看是这样。

插件的结构

编辑

插件被定义为类,并通过简单的包装函数呈现给 Kibana。插件可以有浏览器端代码、服务器端代码或两者都有。浏览器中的插件和服务器上的插件之间没有架构差异。在这两种情况下,你都以类似的方式描述你的插件,并且你以相同的方式与 Core 和其他插件进行交互。

一个名为 demo 且同时具有客户端和服务器端代码的 Kibana 插件的基本文件结构如下

plugins/
  demo
    kibana.json [1]
    public
      index.ts  [2]
      plugin.ts [3]
    server
      index.ts  [4]
      plugin.ts [5]

[1] kibana.json 是一个静态清单文件,用于标识插件并指定此插件是否具有服务器端代码、浏览器端代码或两者都有

{
  "id": "demo",
  "version": "kibana",
  "server": true,
  "ui": true
}

了解清单文件格式

package.json 文件与 Kibana 发现和加载插件无关,并且会被 Kibana 忽略。

[2] public/index.ts 是此插件客户端代码的入口点。它必须导出一个名为 plugin 的函数,该函数将接收一组标准的 core 功能作为参数。它应该返回其插件类的实例,供 Kibana 加载。

import type { PluginInitializerContext } from '@kbn/core/server';
import { MyPlugin } from './plugin';

export function plugin(initializerContext: PluginInitializerContext) {
  return new MyPlugin(initializerContext);
}

[3] public/plugin.ts 是客户端插件定义本身。从技术上讲,它不需要是一个类,甚至不需要与入口点分开的文件,但所有 Elastic 的插件都应该以这种方式保持一致。请参阅所有Elastic 第一方插件的约定

import type { Plugin, PluginInitializerContext, CoreSetup, CoreStart } from '@kbn/core/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 是此插件服务器端代码的入口点。它几乎在各个方面都与客户端入口点相同

import type { PluginInitializerContext } from '@kbn/core/server';

export async function plugin(initializerContext: PluginInitializerContext) {
  const { MyPlugin } = await import('./plugin');
  return new MyPlugin(initializerContext);
}

[5] server/plugin.ts 是服务器端插件定义。此插件的形状与其客户端对应部分相同。

import type { Plugin, PluginInitializerContext, CoreSetup, CoreStart } from '@kbn/core/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
  }
}

Kibana 不对插件的内部架构施加任何技术限制,但有一些与插件如何与核心 API 和其他插件公开的 API 集成相关的考虑因素,这些因素可能会极大地影响它们的构建方式。

生命周期 & 核心服务

编辑

构成 core 的各个独立域由一系列服务表示,其中许多服务公开了提供给所有插件的公共接口。服务在其生命周期的不同部分公开不同的功能。我们使用服务定义中专门命名的函数来描述核心服务和插件的生命周期。

Kibana 有三个生命周期:setupstartstop。当 Kibana 在服务器上设置或在浏览器中加载时,会依次调用每个插件的 setup 函数。在所有插件的 setup 完成后,会依次调用 start 函数。当 Kibana 优雅地关闭服务器或关闭浏览器选项卡或窗口时,会依次调用 stop 函数。

下表解释了每个生命周期如何与 Kibana 的状态相关。

生命周期 目的 服务器 浏览器

setup

执行“注册”工作,为运行时设置环境

配置 REST API 端点,注册已保存的对象类型等。

在 SPA 中配置应用程序路由,在扩展点注册自定义 UI 元素等。

start

引导运行时逻辑

响应传入的请求,请求 Elasticsearch 服务器等。

开始轮询 Kibana 服务器,响应用户交互更新 DOM 树等。

stop

清理运行时

在服务器关闭之前处理活动句柄。

当用户离开 Kibana 时,将会话数据存储在 LocalStorage 中等。

相反,Kibana 平台插件中没有等效于 uiExports 的东西。作为一般经验法则,通过 uiExports 注册的功能现在在 setup 阶段注册。其他大部分内容应该移动到 start 阶段。

核心服务公开的特定于生命周期的约定始终作为插件中等效生命周期函数的第一个参数传递。例如,核心 http 服务向所有插件的 setup 函数公开一个函数 createRouter。要使用此函数注册 HTTP 路由处理程序,插件只需从第一个参数中访问它即可

import type { CoreSetup } from '@kbn/core/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' }));
  }
}

不同的服务接口可以并且将会传递给 setupstartstop,因为某些功能在运行的插件的上下文中才有意义,而其他类型的功能可能有限制,或者可能仅在插件停止的上下文中才有意义。

例如,浏览器中的 stop 函数是作为 window.onbeforeunload 事件的一部分调用的,这意味着你不能在这里可靠地执行异步代码。因此,core 可能不会向浏览器中的插件 stop 函数提供任何异步函数。

所有插件的当前生命周期函数将在下一个生命周期开始之前执行。也就是说,在执行任何 start 函数之前,将执行所有 setup 函数。

这些是核心服务为每个生命周期公开的约定

生命周期 服务器约定 浏览器约定

构造函数

PluginInitializerContext

PluginInitializerContext

setup

CoreSetup

CoreSetup

start

CoreStart

CoreStart

与其他插件集成

编辑

插件可以公开公共接口,供其他插件使用。与 core 一样,这些接口绑定到生命周期函数 setup 和/或 start

setupstart 返回的任何内容都将充当接口,虽然这不是技术要求,但所有 Elastic 第一方插件也将公开该接口的类型。强烈建议希望允许其他插件与其集成的第三方插件也公开其插件接口的类型。

foobar plugin.ts

import type { Plugin } from '@kbn/core/server';
export interface FoobarPluginSetup { 
  getFoo(): string;
}

export interface FoobarPluginStart { 
  getBar(): string;
}

export class MyPlugin implements Plugin<FoobarPluginSetup, FoobarPluginStart> {
  public setup(): FoobarPluginSetup {
    return {
      getFoo() {
        return 'foo';
      },
    };
  }

  public start(): FoobarPluginStart {
    return {
      getBar() {
        return 'bar';
      },
    };
  }
}

我们强烈建议插件作者为他们的插件显式声明公共接口。

与 core 不同,插件公开的功能不会自动注入到所有插件中。相反,如果插件希望使用另一个插件提供的公共接口,它必须首先在其kibana.json 清单文件中将该插件声明为依赖项。

demo kibana.json

{
  "id": "demo",
  "requiredPlugins": ["foobar"],
  "server": true,
  "ui": true
}

在插件清单中指定后,相应的接口可以通过 setup 和/或 start 的第二个参数获得。

demo plugin.ts

import type { CoreSetup, CoreStart } from '@kbn/core/server';
import type { FoobarPluginSetup, FoobarPluginStart } from '../../foobar/server';

interface DemoSetupPlugins { 
  foobar: FoobarPluginSetup;
}

interface DemoStartPlugins {
  foobar: FoobarPluginStart;
}

export class AnotherPlugin {
  public setup(core: CoreSetup, plugins: DemoSetupPlugins) { 
    const { foobar } = plugins;
    foobar.getFoo(); // 'foo'
    foobar.getBar(); // throws because getBar does not exist
  }

  public start(core: CoreStart, plugins: DemoStartPlugins) { 
    const { foobar } = plugins;
    foobar.getFoo(); // throws because getFoo does not exist
    foobar.getBar(); // 'bar'
  }

  public stop() {}
}

插件依赖项的接口必须手动组合。你可以通过从插件导入相应的类型并构造一个接口(其中属性名称是插件的 ID)来完成此操作。

然后,应使用这些手动构造的类型来指定插件第二个参数的类型。

请注意,setup 和 start 生命周期对应的类型是不同的。插件生命周期函数只能访问该生命周期期间公开的 API。