diff --git a/docs/development/core/public/kibana-plugin-public.plugin.md b/docs/development/core/public/kibana-plugin-public.plugin.md index 7a47ca7b97f6..51d44cd8cf14 100644 --- a/docs/development/core/public/kibana-plugin-public.plugin.md +++ b/docs/development/core/public/kibana-plugin-public.plugin.md @@ -7,13 +7,13 @@ The interface that should be returned by a `PluginInitializer`. Signature: ```typescript -export interface Plugin = +export interface Plugin = ``` ## Properties | Property | Type | Description | | --- | --- | --- | -| [setup](./kibana-plugin-public.plugin.setup.md) | (core: PluginSetupContext, dependencies: TDependencies) => TSetup | Promise<TSetup> | | +| [setup](./kibana-plugin-public.plugin.setup.md) | (core: PluginSetupContext, plugins: TPluginsSetup) => TSetup | Promise<TSetup> | | | [stop](./kibana-plugin-public.plugin.stop.md) | () => void | | diff --git a/docs/development/core/public/kibana-plugin-public.plugin.setup.md b/docs/development/core/public/kibana-plugin-public.plugin.setup.md index b2ebc4ca4f94..b011bc3ba010 100644 --- a/docs/development/core/public/kibana-plugin-public.plugin.setup.md +++ b/docs/development/core/public/kibana-plugin-public.plugin.setup.md @@ -5,5 +5,5 @@ Signature: ```typescript -setup: (core: PluginSetupContext, dependencies: TDependencies) => TSetup | Promise; +setup: (core: PluginSetupContext, plugins: TPluginsSetup) => TSetup | Promise; ``` diff --git a/docs/development/core/public/kibana-plugin-public.plugininitializer.md b/docs/development/core/public/kibana-plugin-public.plugininitializer.md index 3f288746ce31..6e3edf8d053e 100644 --- a/docs/development/core/public/kibana-plugin-public.plugininitializer.md +++ b/docs/development/core/public/kibana-plugin-public.plugininitializer.md @@ -7,5 +7,5 @@ The `plugin` export at the root of a plugin's `public` directory should conform Signature: ```typescript -export declare type PluginInitializer = {}> = (core: PluginInitializerContext) => Plugin; +export declare type PluginInitializer = {}> = (core: PluginInitializerContext) => Plugin; ``` diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 960ad262d163..1071b039c080 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -20,6 +20,7 @@ | [Logger](./kibana-plugin-server.logger.md) | Logger exposes all the necessary methods to log any type of information and this is the interface used by the logging consumers including plugins. | | [LoggerFactory](./kibana-plugin-server.loggerfactory.md) | The single purpose of LoggerFactory interface is to define a way to retrieve a context-based logger instance. | | [LogMeta](./kibana-plugin-server.logmeta.md) | Contextual metadata | +| [Plugin](./kibana-plugin-server.plugin.md) | The interface that should be returned by a PluginInitializer. | | [PluginInitializerContext](./kibana-plugin-server.plugininitializercontext.md) | Context that's available to plugins during initialization stage. | | [PluginSetupContext](./kibana-plugin-server.pluginsetupcontext.md) | Context passed to the plugins setup method. | @@ -30,5 +31,6 @@ | [APICaller](./kibana-plugin-server.apicaller.md) | | | [ElasticsearchClientConfig](./kibana-plugin-server.elasticsearchclientconfig.md) | | | [Headers](./kibana-plugin-server.headers.md) | | +| [PluginInitializer](./kibana-plugin-server.plugininitializer.md) | The plugin export at the root of a plugin's server directory should conform to this interface. | | [PluginName](./kibana-plugin-server.pluginname.md) | Dedicated type for plugin name/id that is supposed to make Map/Set/Arrays that use it as a key or value more obvious. | diff --git a/docs/development/core/server/kibana-plugin-server.plugin.md b/docs/development/core/server/kibana-plugin-server.plugin.md new file mode 100644 index 000000000000..b538f0e7926c --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.plugin.md @@ -0,0 +1,19 @@ +[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [Plugin](./kibana-plugin-server.plugin.md) + +## Plugin interface + +The interface that should be returned by a `PluginInitializer`. + +Signature: + +```typescript +export interface Plugin = +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [setup](./kibana-plugin-server.plugin.setup.md) | (pluginSetupContext: PluginSetupContext, plugins: TPluginsSetup) => TSetup | Promise<TSetup> | | +| [stop](./kibana-plugin-server.plugin.stop.md) | () => void | | + diff --git a/docs/development/core/server/kibana-plugin-server.plugin.setup.md b/docs/development/core/server/kibana-plugin-server.plugin.setup.md new file mode 100644 index 000000000000..b732df204afb --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.plugin.setup.md @@ -0,0 +1,9 @@ +[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [Plugin](./kibana-plugin-server.plugin.md) > [setup](./kibana-plugin-server.plugin.setup.md) + +## Plugin.setup property + +Signature: + +```typescript +setup: (pluginSetupContext: PluginSetupContext, plugins: TPluginsSetup) => TSetup | Promise; +``` diff --git a/docs/development/core/server/kibana-plugin-server.plugin.stop.md b/docs/development/core/server/kibana-plugin-server.plugin.stop.md new file mode 100644 index 000000000000..e2c51b2fc147 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.plugin.stop.md @@ -0,0 +1,9 @@ +[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [Plugin](./kibana-plugin-server.plugin.md) > [stop](./kibana-plugin-server.plugin.stop.md) + +## Plugin.stop property + +Signature: + +```typescript +stop?: () => void; +``` diff --git a/docs/development/core/server/kibana-plugin-server.plugininitializer.md b/docs/development/core/server/kibana-plugin-server.plugininitializer.md new file mode 100644 index 000000000000..78ff0dea3391 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.plugininitializer.md @@ -0,0 +1,11 @@ +[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [PluginInitializer](./kibana-plugin-server.plugininitializer.md) + +## PluginInitializer type + +The `plugin` export at the root of a plugin's `server` directory should conform to this interface. + +Signature: + +```typescript +export declare type PluginInitializer = {}> = (coreContext: PluginInitializerContext) => Plugin; +``` diff --git a/src/core/public/kibana.api.md b/src/core/public/kibana.api.md index ae9ca480ed45..9dca4df6a0c5 100644 --- a/src/core/public/kibana.api.md +++ b/src/core/public/kibana.api.md @@ -176,15 +176,15 @@ export interface OverlaySetup { } // @public -export interface Plugin = {}> { +export interface Plugin = {}> { // (undocumented) - setup: (core: PluginSetupContext, dependencies: TDependencies) => TSetup | Promise; + setup: (core: PluginSetupContext, plugins: TPluginsSetup) => TSetup | Promise; // (undocumented) stop?: () => void; } // @public -export type PluginInitializer = {}> = (core: PluginInitializerContext) => Plugin; +export type PluginInitializer = {}> = (core: PluginInitializerContext) => Plugin; // @public export interface PluginInitializerContext { diff --git a/src/core/public/plugins/plugin.ts b/src/core/public/plugins/plugin.ts index 54e54457c110..9b9a3f19dc00 100644 --- a/src/core/public/plugins/plugin.ts +++ b/src/core/public/plugins/plugin.ts @@ -26,8 +26,8 @@ import { loadPluginBundle } from './plugin_loader'; * * @public */ -export interface Plugin = {}> { - setup: (core: PluginSetupContext, dependencies: TDependencies) => TSetup | Promise; +export interface Plugin = {}> { + setup: (core: PluginSetupContext, plugins: TPluginsSetup) => TSetup | Promise; stop?: () => void; } @@ -37,9 +37,9 @@ export interface Plugin = * * @public */ -export type PluginInitializer = {}> = ( +export type PluginInitializer = {}> = ( core: PluginInitializerContext -) => Plugin; +) => Plugin; /** * Lightweight wrapper around discovered plugin that is responsible for instantiating @@ -49,14 +49,14 @@ export type PluginInitializer = Record + TPluginsSetup extends Record = Record > { public readonly name: DiscoveredPlugin['id']; public readonly configPath: DiscoveredPlugin['configPath']; - public readonly requiredDependencies: DiscoveredPlugin['requiredPlugins']; - public readonly optionalDependencies: DiscoveredPlugin['optionalPlugins']; - private initializer?: PluginInitializer; - private instance?: Plugin; + public readonly requiredPlugins: DiscoveredPlugin['requiredPlugins']; + public readonly optionalPlugins: DiscoveredPlugin['optionalPlugins']; + private initializer?: PluginInitializer; + private instance?: Plugin; constructor( readonly discoveredPlugin: DiscoveredPlugin, @@ -64,8 +64,8 @@ export class PluginWrapper< ) { this.name = discoveredPlugin.id; this.configPath = discoveredPlugin.configPath; - this.requiredDependencies = discoveredPlugin.requiredPlugins; - this.optionalDependencies = discoveredPlugin.optionalPlugins; + this.requiredPlugins = discoveredPlugin.requiredPlugins; + this.optionalPlugins = discoveredPlugin.optionalPlugins; } /** @@ -74,20 +74,20 @@ export class PluginWrapper< * @param addBasePath Function that adds the base path to a string for plugin bundle path. */ public async load(addBasePath: (path: string) => string) { - this.initializer = await loadPluginBundle(addBasePath, this.name); + this.initializer = await loadPluginBundle(addBasePath, this.name); } /** * Instantiates plugin and calls `setup` function exposed by the plugin initializer. * @param setupContext Context that consists of various core services tailored specifically * for the `setup` lifecycle event. - * @param dependencies The dictionary where the key is the dependency name and the value + * @param plugins The dictionary where the key is the dependency name and the value * is the contract returned by the dependency's `setup` function. */ - public async setup(setupContext: PluginSetupContext, dependencies: TDependenciesSetup) { + public async setup(setupContext: PluginSetupContext, plugins: TPluginsSetup) { this.instance = await this.createPluginInstance(); - return await this.instance.setup(setupContext, dependencies); + return await this.instance.setup(setupContext, plugins); } /** diff --git a/src/core/public/plugins/plugins_service.ts b/src/core/public/plugins/plugins_service.ts index 544739cff2c7..0a157067aef5 100644 --- a/src/core/public/plugins/plugins_service.ts +++ b/src/core/public/plugins/plugins_service.ts @@ -62,34 +62,35 @@ export class PluginsService implements CoreService { // Load plugin bundles await this.loadPluginBundles(deps.basePath.addToPath); - // Setup each plugin with correct dependencies + // Setup each plugin with required and optional plugin contracts const contracts = new Map(); for (const [pluginName, plugin] of this.plugins.entries()) { - const dependencies = new Set([ - ...plugin.requiredDependencies, - ...plugin.optionalDependencies.filter(optPlugin => this.plugins.get(optPlugin)), + const pluginDeps = new Set([ + ...plugin.requiredPlugins, + ...plugin.optionalPlugins.filter(optPlugin => this.plugins.get(optPlugin)), ]); - const dependencyContracts = [...dependencies.keys()].reduce( - (depContracts, dependency) => { + const pluginDepContracts = [...pluginDeps.keys()].reduce( + (depContracts, dependencyName) => { // Only set if present. Could be absent if plugin does not have client-side code or is a - // missing optional dependency. - if (contracts.get(dependency) !== undefined) { - depContracts[dependency] = contracts.get(dependency); + // missing optional plugin. + if (contracts.has(dependencyName)) { + depContracts[dependencyName] = contracts.get(dependencyName); } return depContracts; }, - {} as { [dep: string]: unknown } + {} as Record ); contracts.set( pluginName, await plugin.setup( createPluginSetupContext(this.coreContext, deps, plugin), - dependencyContracts + pluginDepContracts ) ); + this.satupPlugins.push(pluginName); } @@ -98,7 +99,7 @@ export class PluginsService implements CoreService { } public async stop() { - // Stop plugins in reverse dependency order. + // Stop plugins in reverse topological order. for (const pluginName of this.satupPlugins.reverse()) { this.plugins.get(pluginName)!.stop(); } diff --git a/src/core/server/index.ts b/src/core/server/index.ts index ef42f6abe5f0..a1beb6114e72 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -31,8 +31,11 @@ export { APICaller, } from './elasticsearch'; export { Logger, LoggerFactory, LogMeta, LogRecord, LogLevel } from './logging'; + export { DiscoveredPlugin, + Plugin, + PluginInitializer, PluginInitializerContext, PluginName, PluginSetupContext, diff --git a/src/core/server/kibana.api.md b/src/core/server/kibana.api.md index ebc1a1532391..54053c1e8436 100644 --- a/src/core/server/kibana.api.md +++ b/src/core/server/kibana.api.md @@ -187,6 +187,17 @@ export interface LogRecord { timestamp: Date; } +// @public +export interface Plugin = {}> { + // (undocumented) + setup: (pluginSetupContext: PluginSetupContext, plugins: TPluginsSetup) => TSetup | Promise; + // (undocumented) + stop?: () => void; +} + +// @public +export type PluginInitializer = {}> = (coreContext: PluginInitializerContext) => Plugin; + // @public export interface PluginInitializerContext { // (undocumented) diff --git a/src/core/server/plugins/discovery/plugin_discovery.test.ts b/src/core/server/plugins/discovery/plugin_discovery.test.ts index 2e3ba501feb1..f897fe1527aa 100644 --- a/src/core/server/plugins/discovery/plugin_discovery.test.ts +++ b/src/core/server/plugins/discovery/plugin_discovery.test.ts @@ -25,7 +25,7 @@ import { first, map, toArray } from 'rxjs/operators'; import { Config, ConfigService, Env, ObjectToConfigAdapter } from '../../config'; import { getEnvOptions } from '../../config/__mocks__/env'; import { loggingServiceMock } from '../../logging/logging_service.mock'; -import { Plugin } from '../plugin'; +import { PluginWrapper } from '../plugin'; import { PluginsConfig } from '../plugins_config'; import { discover } from './plugins_discovery'; @@ -142,10 +142,10 @@ test('properly iterates through plugin search locations', async () => { TEST_EXTRA_PLUGIN_PATH, ]) { const discoveredPlugin = plugins.find(plugin => plugin.path === path)!; - expect(discoveredPlugin).toBeInstanceOf(Plugin); + expect(discoveredPlugin).toBeInstanceOf(PluginWrapper); expect(discoveredPlugin.configPath).toEqual(['core', 'config']); - expect(discoveredPlugin.requiredDependencies).toEqual(['a', 'b']); - expect(discoveredPlugin.optionalDependencies).toEqual(['c', 'd']); + expect(discoveredPlugin.requiredPlugins).toEqual(['a', 'b']); + expect(discoveredPlugin.optionalPlugins).toEqual(['c', 'd']); } await expect( diff --git a/src/core/server/plugins/discovery/plugins_discovery.ts b/src/core/server/plugins/discovery/plugins_discovery.ts index 8bf28eeb2d50..f9ab37913bcc 100644 --- a/src/core/server/plugins/discovery/plugins_discovery.ts +++ b/src/core/server/plugins/discovery/plugins_discovery.ts @@ -23,7 +23,7 @@ import { bindNodeCallback, from, merge } from 'rxjs'; import { catchError, filter, map, mergeMap, shareReplay } from 'rxjs/operators'; import { CoreContext } from '../../core_context'; import { Logger } from '../../logging'; -import { Plugin } from '../plugin'; +import { PluginWrapper } from '../plugin'; import { createPluginInitializerContext } from '../plugin_context'; import { PluginsConfig } from '../plugins_config'; import { PluginDiscoveryError } from './plugin_discovery_error'; @@ -67,9 +67,11 @@ export function discover(config: PluginsConfig, coreContext: CoreContext) { ); return { - plugin$: discoveryResults$.pipe(filter((entry): entry is Plugin => entry instanceof Plugin)), + plugin$: discoveryResults$.pipe( + filter((entry): entry is PluginWrapper => entry instanceof PluginWrapper) + ), error$: discoveryResults$.pipe( - filter((entry): entry is PluginDiscoveryError => !(entry instanceof Plugin)) + filter((entry): entry is PluginDiscoveryError => !(entry instanceof PluginWrapper)) ), }; } @@ -115,7 +117,11 @@ function createPlugin$(path: string, log: Logger, coreContext: CoreContext) { return from(parseManifest(path, coreContext.env.packageInfo)).pipe( map(manifest => { log.debug(`Successfully discovered plugin "${manifest.id}" at "${path}"`); - return new Plugin(path, manifest, createPluginInitializerContext(coreContext, manifest)); + return new PluginWrapper( + path, + manifest, + createPluginInitializerContext(coreContext, manifest) + ); }), catchError(err => [err]) ); diff --git a/src/core/server/plugins/index.ts b/src/core/server/plugins/index.ts index 036fd20c7993..8469e21783f0 100644 --- a/src/core/server/plugins/index.ts +++ b/src/core/server/plugins/index.ts @@ -22,5 +22,11 @@ export { PluginsService, PluginsServiceSetup } from './plugins_service'; /** @internal */ export { isNewPlatformPlugin } from './discovery'; /** @internal */ -export { DiscoveredPlugin, DiscoveredPluginInternal, PluginName } from './plugin'; +export { + DiscoveredPlugin, + DiscoveredPluginInternal, + Plugin, + PluginInitializer, + PluginName, +} from './plugin'; export { PluginInitializerContext, PluginSetupContext } from './plugin_context'; diff --git a/src/core/server/plugins/plugin.test.ts b/src/core/server/plugins/plugin.test.ts index 2f2a73668a59..7acc0d2e76a4 100644 --- a/src/core/server/plugins/plugin.test.ts +++ b/src/core/server/plugins/plugin.test.ts @@ -25,7 +25,7 @@ import { CoreContext } from '../core_context'; import { elasticsearchServiceMock } from '../elasticsearch/elasticsearch_service.mock'; import { loggingServiceMock } from '../logging/logging_service.mock'; -import { Plugin, PluginManifest } from './plugin'; +import { PluginWrapper, PluginManifest } from './plugin'; import { createPluginInitializerContext, createPluginSetupContext } from './plugin_context'; const mockPluginInitializer = jest.fn(); @@ -78,7 +78,7 @@ afterEach(() => { test('`constructor` correctly initializes plugin instance', () => { const manifest = createPluginManifest(); - const plugin = new Plugin( + const plugin = new PluginWrapper( 'some-plugin-path', manifest, createPluginInitializerContext(coreContext, manifest) @@ -87,13 +87,13 @@ test('`constructor` correctly initializes plugin instance', () => { expect(plugin.name).toBe('some-plugin-id'); expect(plugin.configPath).toBe('path'); expect(plugin.path).toBe('some-plugin-path'); - expect(plugin.requiredDependencies).toEqual(['some-required-dep']); - expect(plugin.optionalDependencies).toEqual(['some-optional-dep']); + expect(plugin.requiredPlugins).toEqual(['some-required-dep']); + expect(plugin.optionalPlugins).toEqual(['some-optional-dep']); }); test('`setup` fails if `plugin` initializer is not exported', async () => { const manifest = createPluginManifest(); - const plugin = new Plugin( + const plugin = new PluginWrapper( 'plugin-without-initializer-path', manifest, createPluginInitializerContext(coreContext, manifest) @@ -108,7 +108,7 @@ test('`setup` fails if `plugin` initializer is not exported', async () => { test('`setup` fails if plugin initializer is not a function', async () => { const manifest = createPluginManifest(); - const plugin = new Plugin( + const plugin = new PluginWrapper( 'plugin-with-wrong-initializer-path', manifest, createPluginInitializerContext(coreContext, manifest) @@ -123,7 +123,7 @@ test('`setup` fails if plugin initializer is not a function', async () => { test('`setup` fails if initializer does not return object', async () => { const manifest = createPluginManifest(); - const plugin = new Plugin( + const plugin = new PluginWrapper( 'plugin-with-initializer-path', manifest, createPluginInitializerContext(coreContext, manifest) @@ -140,7 +140,7 @@ test('`setup` fails if initializer does not return object', async () => { test('`setup` fails if object returned from initializer does not define `setup` function', async () => { const manifest = createPluginManifest(); - const plugin = new Plugin( + const plugin = new PluginWrapper( 'plugin-with-initializer-path', manifest, createPluginInitializerContext(coreContext, manifest) @@ -159,7 +159,7 @@ test('`setup` fails if object returned from initializer does not define `setup` test('`setup` initializes plugin and calls appropriate lifecycle hook', async () => { const manifest = createPluginManifest(); const initializerContext = createPluginInitializerContext(coreContext, manifest); - const plugin = new Plugin('plugin-with-initializer-path', manifest, initializerContext); + const plugin = new PluginWrapper('plugin-with-initializer-path', manifest, initializerContext); const mockPluginInstance = { setup: jest.fn().mockResolvedValue({ contract: 'yes' }) }; mockPluginInitializer.mockReturnValue(mockPluginInstance); @@ -177,7 +177,7 @@ test('`setup` initializes plugin and calls appropriate lifecycle hook', async () test('`stop` fails if plugin is not set up', async () => { const manifest = createPluginManifest(); - const plugin = new Plugin( + const plugin = new PluginWrapper( 'plugin-with-initializer-path', manifest, createPluginInitializerContext(coreContext, manifest) @@ -194,7 +194,7 @@ test('`stop` fails if plugin is not set up', async () => { test('`stop` does nothing if plugin does not define `stop` function', async () => { const manifest = createPluginManifest(); - const plugin = new Plugin( + const plugin = new PluginWrapper( 'plugin-with-initializer-path', manifest, createPluginInitializerContext(coreContext, manifest) @@ -208,7 +208,7 @@ test('`stop` does nothing if plugin does not define `stop` function', async () = test('`stop` calls `stop` defined by the plugin instance', async () => { const manifest = createPluginManifest(); - const plugin = new Plugin( + const plugin = new PluginWrapper( 'plugin-with-initializer-path', manifest, createPluginInitializerContext(coreContext, manifest) diff --git a/src/core/server/plugins/plugin.ts b/src/core/server/plugins/plugin.ts index 8669f8353b71..5e23fb3b8b30 100644 --- a/src/core/server/plugins/plugin.ts +++ b/src/core/server/plugins/plugin.ts @@ -124,15 +124,28 @@ export interface DiscoveredPluginInternal extends DiscoveredPlugin { readonly path: string; } -type PluginInitializer> = ( - coreContext: PluginInitializerContext -) => { +/** + * The interface that should be returned by a `PluginInitializer`. + * + * @public + */ +export interface Plugin = {}> { setup: ( pluginSetupContext: PluginSetupContext, - dependencies: TDependenciesSetup - ) => TExposedSetup; + plugins: TPluginsSetup + ) => TSetup | Promise; stop?: () => void; -}; +} + +/** + * The `plugin` export at the root of a plugin's `server` directory should conform + * to this interface. + * + * @public + */ +export type PluginInitializer = {}> = ( + coreContext: PluginInitializerContext +) => Plugin; /** * Lightweight wrapper around discovered plugin that is responsible for instantiating @@ -140,20 +153,20 @@ type PluginInitializer = Record + TPluginsSetup extends Record = Record > { public readonly name: PluginManifest['id']; public readonly configPath: PluginManifest['configPath']; - public readonly requiredDependencies: PluginManifest['requiredPlugins']; - public readonly optionalDependencies: PluginManifest['optionalPlugins']; + public readonly requiredPlugins: PluginManifest['requiredPlugins']; + public readonly optionalPlugins: PluginManifest['optionalPlugins']; public readonly includesServerPlugin: PluginManifest['server']; public readonly includesUiPlugin: PluginManifest['ui']; private readonly log: Logger; - private instance?: ReturnType>; + private instance?: Plugin; constructor( public readonly path: string, @@ -163,8 +176,8 @@ export class Plugin< this.log = initializerContext.logger.get(); this.name = manifest.id; this.configPath = manifest.configPath; - this.requiredDependencies = manifest.requiredPlugins; - this.optionalDependencies = manifest.optionalPlugins; + this.requiredPlugins = manifest.requiredPlugins; + this.optionalPlugins = manifest.optionalPlugins; this.includesServerPlugin = manifest.server; this.includesUiPlugin = manifest.ui; } @@ -173,15 +186,15 @@ export class Plugin< * Instantiates plugin and calls `setup` function exposed by the plugin initializer. * @param setupContext Context that consists of various core services tailored specifically * for the `setup` lifecycle event. - * @param dependencies The dictionary where the key is the dependency name and the value + * @param plugins The dictionary where the key is the dependency name and the value * is the contract returned by the dependency's `setup` function. */ - public async setup(setupContext: PluginSetupContext, dependencies: TDependenciesSetup) { + public async setup(setupContext: PluginSetupContext, plugins: TPluginsSetup) { this.instance = this.createPluginInstance(); this.log.info('Setting up plugin'); - return await this.instance.setup(setupContext, dependencies); + return await this.instance.setup(setupContext, plugins); } /** @@ -211,7 +224,7 @@ export class Plugin< } const { plugin: initializer } = pluginDefinition as { - plugin: PluginInitializer; + plugin: PluginInitializer; }; if (!initializer || typeof initializer !== 'function') { throw new Error(`Definition of plugin "${this.name}" should be a function (${this.path}).`); diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 4571966af5bb..af438ff3759d 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -23,7 +23,7 @@ import { ConfigWithSchema, EnvironmentMode } from '../config'; import { CoreContext } from '../core_context'; import { ClusterClient } from '../elasticsearch'; import { LoggerFactory } from '../logging'; -import { Plugin, PluginManifest } from './plugin'; +import { PluginWrapper, PluginManifest } from './plugin'; import { PluginsServiceSetupDeps } from './plugins_service'; /** @@ -126,7 +126,7 @@ export function createPluginInitializerContext( export function createPluginSetupContext( coreContext: CoreContext, deps: PluginsServiceSetupDeps, - plugin: Plugin + plugin: PluginWrapper ): PluginSetupContext { return { elasticsearch: { diff --git a/src/core/server/plugins/plugins_service.test.ts b/src/core/server/plugins/plugins_service.test.ts index a7dc8bd5e003..305d75a5dcc7 100644 --- a/src/core/server/plugins/plugins_service.test.ts +++ b/src/core/server/plugins/plugins_service.test.ts @@ -27,7 +27,7 @@ import { getEnvOptions } from '../config/__mocks__/env'; import { elasticsearchServiceMock } from '../elasticsearch/elasticsearch_service.mock'; import { loggingServiceMock } from '../logging/logging_service.mock'; import { PluginDiscoveryError } from './discovery'; -import { Plugin } from './plugin'; +import { PluginWrapper } from './plugin'; import { PluginsService } from './plugins_service'; import { PluginsSystem } from './plugins_system'; @@ -110,7 +110,7 @@ test('`setup` throws if discovered plugins with conflicting names', async () => mockDiscover.mockReturnValue({ error$: from([]), plugin$: from([ - new Plugin( + new PluginWrapper( 'path-4', { id: 'conflicting-id', @@ -124,7 +124,7 @@ test('`setup` throws if discovered plugins with conflicting names', async () => }, { logger } as any ), - new Plugin( + new PluginWrapper( 'path-5', { id: 'conflicting-id', @@ -160,7 +160,7 @@ test('`setup` properly detects plugins that should be disabled.', async () => { mockDiscover.mockReturnValue({ error$: from([]), plugin$: from([ - new Plugin( + new PluginWrapper( 'path-1', { id: 'explicitly-disabled-plugin', @@ -174,7 +174,7 @@ test('`setup` properly detects plugins that should be disabled.', async () => { }, { logger } as any ), - new Plugin( + new PluginWrapper( 'path-2', { id: 'plugin-with-missing-required-deps', @@ -188,7 +188,7 @@ test('`setup` properly detects plugins that should be disabled.', async () => { }, { logger } as any ), - new Plugin( + new PluginWrapper( 'path-3', { id: 'plugin-with-disabled-transitive-dep', @@ -202,7 +202,7 @@ test('`setup` properly detects plugins that should be disabled.', async () => { }, { logger } as any ), - new Plugin( + new PluginWrapper( 'path-4', { id: 'another-explicitly-disabled-plugin', @@ -247,7 +247,7 @@ Array [ }); test('`setup` properly invokes `discover` and ignores non-critical errors.', async () => { - const firstPlugin = new Plugin( + const firstPlugin = new PluginWrapper( 'path-1', { id: 'some-id', @@ -262,7 +262,7 @@ test('`setup` properly invokes `discover` and ignores non-critical errors.', asy { logger } as any ); - const secondPlugin = new Plugin( + const secondPlugin = new PluginWrapper( 'path-2', { id: 'some-other-id', diff --git a/src/core/server/plugins/plugins_service.ts b/src/core/server/plugins/plugins_service.ts index 9b31ee77333c..dcea3bdf1508 100644 --- a/src/core/server/plugins/plugins_service.ts +++ b/src/core/server/plugins/plugins_service.ts @@ -24,7 +24,7 @@ import { CoreContext } from '../core_context'; import { ElasticsearchServiceSetup } from '../elasticsearch/elasticsearch_service'; import { Logger } from '../logging'; import { discover, PluginDiscoveryError, PluginDiscoveryErrorType } from './discovery'; -import { DiscoveredPlugin, DiscoveredPluginInternal, Plugin, PluginName } from './plugin'; +import { DiscoveredPlugin, DiscoveredPluginInternal, PluginWrapper, PluginName } from './plugin'; import { PluginsConfig } from './plugins_config'; import { PluginsSystem } from './plugins_system'; @@ -106,8 +106,11 @@ export class PluginsService implements CoreService { } } - private async handleDiscoveredPlugins(plugin$: Observable) { - const pluginEnableStatuses = new Map(); + private async handleDiscoveredPlugins(plugin$: Observable) { + const pluginEnableStatuses = new Map< + PluginName, + { plugin: PluginWrapper; isEnabled: boolean } + >(); await plugin$ .pipe( mergeMap(async plugin => { @@ -139,13 +142,13 @@ export class PluginsService implements CoreService { private shouldEnablePlugin( pluginName: PluginName, - pluginEnableStatuses: Map + pluginEnableStatuses: Map ): boolean { const pluginInfo = pluginEnableStatuses.get(pluginName); return ( pluginInfo !== undefined && pluginInfo.isEnabled && - pluginInfo.plugin.requiredDependencies.every(dependencyName => + pluginInfo.plugin.requiredPlugins.every(dependencyName => this.shouldEnablePlugin(dependencyName, pluginEnableStatuses) ) ); diff --git a/src/core/server/plugins/plugins_system.test.ts b/src/core/server/plugins/plugins_system.test.ts index 3202a7697bd6..3965e9a8d21b 100644 --- a/src/core/server/plugins/plugins_system.test.ts +++ b/src/core/server/plugins/plugins_system.test.ts @@ -25,7 +25,7 @@ import { getEnvOptions } from '../config/__mocks__/env'; import { CoreContext } from '../core_context'; import { elasticsearchServiceMock } from '../elasticsearch/elasticsearch_service.mock'; import { loggingServiceMock } from '../logging/logging_service.mock'; -import { Plugin, PluginName } from './plugin'; +import { PluginWrapper, PluginName } from './plugin'; import { PluginsSystem } from './plugins_system'; const logger = loggingServiceMock.create(); @@ -37,7 +37,7 @@ function createPlugin( server = true, }: { required?: string[]; optional?: string[]; server?: boolean } = {} ) { - return new Plugin( + return new PluginWrapper( 'some-path', { id, @@ -140,7 +140,7 @@ test('`setupPlugins` correctly orders plugins and returns exposed values', async createPlugin('order-3', { required: ['order-2'], optional: ['missing-dep'] }), { 'order-2': 'added-as-2' }, ], - ] as Array<[Plugin, Record]>); + ] as Array<[PluginWrapper, Record]>); const setupContextMap = new Map(); @@ -251,7 +251,7 @@ test('`uiPlugins` returns ordered Maps of all plugin manifests', async () => { createPlugin('order-3', { required: ['order-2'], optional: ['missing-dep'] }), { 'order-2': 'added-as-2' }, ], - ] as Array<[Plugin, Record]>); + ] as Array<[PluginWrapper, Record]>); [...plugins.keys()].forEach(plugin => { pluginsSystem.addPlugin(plugin); diff --git a/src/core/server/plugins/plugins_system.ts b/src/core/server/plugins/plugins_system.ts index e58497e4473a..eeb3da0a8386 100644 --- a/src/core/server/plugins/plugins_system.ts +++ b/src/core/server/plugins/plugins_system.ts @@ -21,13 +21,13 @@ import { pick } from 'lodash'; import { CoreContext } from '../core_context'; import { Logger } from '../logging'; -import { DiscoveredPlugin, DiscoveredPluginInternal, Plugin, PluginName } from './plugin'; +import { DiscoveredPlugin, DiscoveredPluginInternal, PluginWrapper, PluginName } from './plugin'; import { createPluginSetupContext } from './plugin_context'; import { PluginsServiceSetupDeps } from './plugins_service'; /** @internal */ export class PluginsSystem { - private readonly plugins = new Map(); + private readonly plugins = new Map(); private readonly log: Logger; // `satup`, the past-tense version of the noun `setup`. private readonly satupPlugins: PluginName[] = []; @@ -36,14 +36,14 @@ export class PluginsSystem { this.log = coreContext.logger.get('plugins-system'); } - public addPlugin(plugin: Plugin) { + public addPlugin(plugin: PluginWrapper) { this.plugins.set(plugin.name, plugin); } public async setupPlugins(deps: PluginsServiceSetupDeps) { - const exposedValues = new Map(); + const contracts = new Map(); if (this.plugins.size === 0) { - return exposedValues; + return contracts; } const sortedPlugins = this.getTopologicallySortedPluginNames(); @@ -57,29 +57,31 @@ export class PluginsSystem { this.log.debug(`Setting up plugin "${pluginName}"...`); - const exposedDependencyValues = [ - ...plugin.requiredDependencies, - ...plugin.optionalDependencies, - ].reduce( - (dependencies, dependencyName) => { - dependencies[dependencyName] = exposedValues.get(dependencyName); - return dependencies; + const pluginDepContracts = [...plugin.requiredPlugins, ...plugin.optionalPlugins].reduce( + (depContracts, dependencyName) => { + // Only set if present. Could be absent if plugin does not have server-side code or is a + // missing optional dependency. + if (contracts.has(dependencyName)) { + depContracts[dependencyName] = contracts.get(dependencyName); + } + + return depContracts; }, {} as Record ); - exposedValues.set( + contracts.set( pluginName, await plugin.setup( createPluginSetupContext(this.coreContext, deps, plugin), - exposedDependencyValues + pluginDepContracts ) ); this.satupPlugins.push(pluginName); } - return exposedValues; + return contracts; } public async stopPlugins() { @@ -151,8 +153,8 @@ export class PluginsSystem { return [ pluginName, new Set([ - ...plugin.requiredDependencies, - ...plugin.optionalDependencies.filter(dependency => this.plugins.has(dependency)), + ...plugin.requiredPlugins, + ...plugin.optionalPlugins.filter(dependency => this.plugins.has(dependency)), ]), ] as [PluginName, Set]; })