[new-platform] Improve naming and consistency in Plugin types (#34725)
* Improve consistency in Plugin types * Use #has() instead of undefined check * Rename parameter * Update core docs * Internal updates
This commit is contained in:
parent
2a6c7b4cac
commit
b006361e61
|
@ -7,13 +7,13 @@ The interface that should be returned by a `PluginInitializer`<!-- -->.
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface Plugin<TSetup, TDependencies extends Record<string, unknown> =
|
||||
export interface Plugin<TSetup, TPluginsSetup extends Record<string, unknown> =
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [setup](./kibana-plugin-public.plugin.setup.md) | <code>(core: PluginSetupContext, dependencies: TDependencies) => TSetup | Promise<TSetup></code> | |
|
||||
| [setup](./kibana-plugin-public.plugin.setup.md) | <code>(core: PluginSetupContext, plugins: TPluginsSetup) => TSetup | Promise<TSetup></code> | |
|
||||
| [stop](./kibana-plugin-public.plugin.stop.md) | <code>() => void</code> | |
|
||||
|
||||
|
|
|
@ -5,5 +5,5 @@
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
setup: (core: PluginSetupContext, dependencies: TDependencies) => TSetup | Promise<TSetup>;
|
||||
setup: (core: PluginSetupContext, plugins: TPluginsSetup) => TSetup | Promise<TSetup>;
|
||||
```
|
||||
|
|
|
@ -7,5 +7,5 @@ The `plugin` export at the root of a plugin's `public` directory should conform
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare type PluginInitializer<TSetup, TDependencies extends Record<string, unknown> = {}> = (core: PluginInitializerContext) => Plugin<TSetup, TDependencies>;
|
||||
export declare type PluginInitializer<TSetup, TPluginsSetup extends Record<string, unknown> = {}> = (core: PluginInitializerContext) => Plugin<TSetup, TPluginsSetup>;
|
||||
```
|
||||
|
|
|
@ -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 <code>LoggerFactory</code> 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 <code>PluginInitializer</code>. |
|
||||
| [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 <code>setup</code> 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 <code>plugin</code> export at the root of a plugin's <code>server</code> 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. |
|
||||
|
||||
|
|
19
docs/development/core/server/kibana-plugin-server.plugin.md
Normal file
19
docs/development/core/server/kibana-plugin-server.plugin.md
Normal file
|
@ -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`<!-- -->.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface Plugin<TSetup, TPluginsSetup extends Record<PluginName, unknown> =
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [setup](./kibana-plugin-server.plugin.setup.md) | <code>(pluginSetupContext: PluginSetupContext, plugins: TPluginsSetup) => TSetup | Promise<TSetup></code> | |
|
||||
| [stop](./kibana-plugin-server.plugin.stop.md) | <code>() => void</code> | |
|
||||
|
|
@ -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
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
setup: (pluginSetupContext: PluginSetupContext, plugins: TPluginsSetup) => TSetup | Promise<TSetup>;
|
||||
```
|
|
@ -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
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
stop?: () => void;
|
||||
```
|
|
@ -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.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare type PluginInitializer<TSetup, TPluginsSetup extends Record<PluginName, unknown> = {}> = (coreContext: PluginInitializerContext) => Plugin<TSetup, TPluginsSetup>;
|
||||
```
|
|
@ -176,15 +176,15 @@ export interface OverlaySetup {
|
|||
}
|
||||
|
||||
// @public
|
||||
export interface Plugin<TSetup, TDependencies extends Record<string, unknown> = {}> {
|
||||
export interface Plugin<TSetup, TPluginsSetup extends Record<string, unknown> = {}> {
|
||||
// (undocumented)
|
||||
setup: (core: PluginSetupContext, dependencies: TDependencies) => TSetup | Promise<TSetup>;
|
||||
setup: (core: PluginSetupContext, plugins: TPluginsSetup) => TSetup | Promise<TSetup>;
|
||||
// (undocumented)
|
||||
stop?: () => void;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type PluginInitializer<TSetup, TDependencies extends Record<string, unknown> = {}> = (core: PluginInitializerContext) => Plugin<TSetup, TDependencies>;
|
||||
export type PluginInitializer<TSetup, TPluginsSetup extends Record<string, unknown> = {}> = (core: PluginInitializerContext) => Plugin<TSetup, TPluginsSetup>;
|
||||
|
||||
// @public
|
||||
export interface PluginInitializerContext {
|
||||
|
|
|
@ -26,8 +26,8 @@ import { loadPluginBundle } from './plugin_loader';
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
export interface Plugin<TSetup, TDependencies extends Record<string, unknown> = {}> {
|
||||
setup: (core: PluginSetupContext, dependencies: TDependencies) => TSetup | Promise<TSetup>;
|
||||
export interface Plugin<TSetup, TPluginsSetup extends Record<string, unknown> = {}> {
|
||||
setup: (core: PluginSetupContext, plugins: TPluginsSetup) => TSetup | Promise<TSetup>;
|
||||
stop?: () => void;
|
||||
}
|
||||
|
||||
|
@ -37,9 +37,9 @@ export interface Plugin<TSetup, TDependencies extends Record<string, unknown> =
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
export type PluginInitializer<TSetup, TDependencies extends Record<string, unknown> = {}> = (
|
||||
export type PluginInitializer<TSetup, TPluginsSetup extends Record<string, unknown> = {}> = (
|
||||
core: PluginInitializerContext
|
||||
) => Plugin<TSetup, TDependencies>;
|
||||
) => Plugin<TSetup, TPluginsSetup>;
|
||||
|
||||
/**
|
||||
* Lightweight wrapper around discovered plugin that is responsible for instantiating
|
||||
|
@ -49,14 +49,14 @@ export type PluginInitializer<TSetup, TDependencies extends Record<string, unkno
|
|||
*/
|
||||
export class PluginWrapper<
|
||||
TSetup = unknown,
|
||||
TDependenciesSetup extends Record<PluginName, unknown> = Record<PluginName, unknown>
|
||||
TPluginsSetup extends Record<PluginName, unknown> = Record<PluginName, unknown>
|
||||
> {
|
||||
public readonly name: DiscoveredPlugin['id'];
|
||||
public readonly configPath: DiscoveredPlugin['configPath'];
|
||||
public readonly requiredDependencies: DiscoveredPlugin['requiredPlugins'];
|
||||
public readonly optionalDependencies: DiscoveredPlugin['optionalPlugins'];
|
||||
private initializer?: PluginInitializer<TSetup, TDependenciesSetup>;
|
||||
private instance?: Plugin<TSetup, TDependenciesSetup>;
|
||||
public readonly requiredPlugins: DiscoveredPlugin['requiredPlugins'];
|
||||
public readonly optionalPlugins: DiscoveredPlugin['optionalPlugins'];
|
||||
private initializer?: PluginInitializer<TSetup, TPluginsSetup>;
|
||||
private instance?: Plugin<TSetup, TPluginsSetup>;
|
||||
|
||||
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<TSetup, TDependenciesSetup>(addBasePath, this.name);
|
||||
this.initializer = await loadPluginBundle<TSetup, TPluginsSetup>(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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -62,34 +62,35 @@ export class PluginsService implements CoreService<PluginsServiceSetup> {
|
|||
// 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<string, unknown>();
|
||||
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<PluginName, unknown>
|
||||
);
|
||||
|
||||
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<PluginsServiceSetup> {
|
|||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -31,8 +31,11 @@ export {
|
|||
APICaller,
|
||||
} from './elasticsearch';
|
||||
export { Logger, LoggerFactory, LogMeta, LogRecord, LogLevel } from './logging';
|
||||
|
||||
export {
|
||||
DiscoveredPlugin,
|
||||
Plugin,
|
||||
PluginInitializer,
|
||||
PluginInitializerContext,
|
||||
PluginName,
|
||||
PluginSetupContext,
|
||||
|
|
|
@ -187,6 +187,17 @@ export interface LogRecord {
|
|||
timestamp: Date;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface Plugin<TSetup, TPluginsSetup extends Record<PluginName, unknown> = {}> {
|
||||
// (undocumented)
|
||||
setup: (pluginSetupContext: PluginSetupContext, plugins: TPluginsSetup) => TSetup | Promise<TSetup>;
|
||||
// (undocumented)
|
||||
stop?: () => void;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type PluginInitializer<TSetup, TPluginsSetup extends Record<PluginName, unknown> = {}> = (coreContext: PluginInitializerContext) => Plugin<TSetup, TPluginsSetup>;
|
||||
|
||||
// @public
|
||||
export interface PluginInitializerContext {
|
||||
// (undocumented)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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])
|
||||
);
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -124,15 +124,28 @@ export interface DiscoveredPluginInternal extends DiscoveredPlugin {
|
|||
readonly path: string;
|
||||
}
|
||||
|
||||
type PluginInitializer<TExposedSetup, TDependenciesSetup extends Record<PluginName, unknown>> = (
|
||||
coreContext: PluginInitializerContext
|
||||
) => {
|
||||
/**
|
||||
* The interface that should be returned by a `PluginInitializer`.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface Plugin<TSetup, TPluginsSetup extends Record<PluginName, unknown> = {}> {
|
||||
setup: (
|
||||
pluginSetupContext: PluginSetupContext,
|
||||
dependencies: TDependenciesSetup
|
||||
) => TExposedSetup;
|
||||
plugins: TPluginsSetup
|
||||
) => TSetup | Promise<TSetup>;
|
||||
stop?: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The `plugin` export at the root of a plugin's `server` directory should conform
|
||||
* to this interface.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type PluginInitializer<TSetup, TPluginsSetup extends Record<PluginName, unknown> = {}> = (
|
||||
coreContext: PluginInitializerContext
|
||||
) => Plugin<TSetup, TPluginsSetup>;
|
||||
|
||||
/**
|
||||
* Lightweight wrapper around discovered plugin that is responsible for instantiating
|
||||
|
@ -140,20 +153,20 @@ type PluginInitializer<TExposedSetup, TDependenciesSetup extends Record<PluginNa
|
|||
*
|
||||
* @internal
|
||||
*/
|
||||
export class Plugin<
|
||||
export class PluginWrapper<
|
||||
TSetup = unknown,
|
||||
TDependenciesSetup extends Record<PluginName, unknown> = Record<PluginName, unknown>
|
||||
TPluginsSetup extends Record<PluginName, unknown> = Record<PluginName, unknown>
|
||||
> {
|
||||
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<PluginInitializer<TSetup, TDependenciesSetup>>;
|
||||
private instance?: Plugin<TSetup, TPluginsSetup>;
|
||||
|
||||
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<TSetup, TDependenciesSetup>;
|
||||
plugin: PluginInitializer<TSetup, TPluginsSetup>;
|
||||
};
|
||||
if (!initializer || typeof initializer !== 'function') {
|
||||
throw new Error(`Definition of plugin "${this.name}" should be a function (${this.path}).`);
|
||||
|
|
|
@ -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<TPlugin, TPluginDependencies>(
|
||||
coreContext: CoreContext,
|
||||
deps: PluginsServiceSetupDeps,
|
||||
plugin: Plugin<TPlugin, TPluginDependencies>
|
||||
plugin: PluginWrapper<TPlugin, TPluginDependencies>
|
||||
): PluginSetupContext {
|
||||
return {
|
||||
elasticsearch: {
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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<PluginsServiceSetup> {
|
|||
}
|
||||
}
|
||||
|
||||
private async handleDiscoveredPlugins(plugin$: Observable<Plugin>) {
|
||||
const pluginEnableStatuses = new Map<PluginName, { plugin: Plugin; isEnabled: boolean }>();
|
||||
private async handleDiscoveredPlugins(plugin$: Observable<PluginWrapper>) {
|
||||
const pluginEnableStatuses = new Map<
|
||||
PluginName,
|
||||
{ plugin: PluginWrapper; isEnabled: boolean }
|
||||
>();
|
||||
await plugin$
|
||||
.pipe(
|
||||
mergeMap(async plugin => {
|
||||
|
@ -139,13 +142,13 @@ export class PluginsService implements CoreService<PluginsServiceSetup> {
|
|||
|
||||
private shouldEnablePlugin(
|
||||
pluginName: PluginName,
|
||||
pluginEnableStatuses: Map<PluginName, { plugin: Plugin; isEnabled: boolean }>
|
||||
pluginEnableStatuses: Map<PluginName, { plugin: PluginWrapper; isEnabled: boolean }>
|
||||
): 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)
|
||||
)
|
||||
);
|
||||
|
|
|
@ -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<PluginName, unknown>]>);
|
||||
] as Array<[PluginWrapper, Record<PluginName, unknown>]>);
|
||||
|
||||
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<PluginName, unknown>]>);
|
||||
] as Array<[PluginWrapper, Record<PluginName, unknown>]>);
|
||||
|
||||
[...plugins.keys()].forEach(plugin => {
|
||||
pluginsSystem.addPlugin(plugin);
|
||||
|
|
|
@ -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<PluginName, Plugin>();
|
||||
private readonly plugins = new Map<PluginName, PluginWrapper>();
|
||||
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<PluginName, unknown>();
|
||||
const contracts = new Map<PluginName, unknown>();
|
||||
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<PluginName, unknown>
|
||||
);
|
||||
|
||||
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<PluginName>];
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue