Kibana 插件 API编辑

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

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

插件剖析编辑

插件被定义为类,并通过一个简单的包装函数呈现给 Kibana。插件可以包含浏览器端代码、服务器端代码或两者兼而有之。浏览器中的插件和服务器上的插件之间没有架构差异。在这两个地方,您都以类似的方式描述您的插件,并且您以相同的方式与核心和其他插件交互。

名为 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 的函数,该函数将接收 一组标准的核心功能 作为参数。它应该返回其插件类的实例以供 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 集成有关,这些因素可能会极大地影响它们的构建方式。

生命周期和核心服务编辑

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

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 函数提供任何异步函数。

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

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

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

构造函数

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';
      },
    };
  }
}

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

与核心不同,插件公开的功能 *不会* 自动注入到所有插件中。相反,如果插件希望使用另一个插件提供的公共接口,则它必须首先在其 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。

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

请注意,设置和启动生命周期的类型不同。插件生命周期函数只能访问 *在* 该生命周期 *期间* 公开的 API。