[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:
Josh Dover 2019-04-10 13:44:11 -05:00 committed by GitHub
parent 2a6c7b4cac
commit b006361e61
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 204 additions and 109 deletions

View file

@ -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) =&gt; TSetup &#124; Promise&lt;TSetup&gt;</code> | |
| [setup](./kibana-plugin-public.plugin.setup.md) | <code>(core: PluginSetupContext, plugins: TPluginsSetup) =&gt; TSetup &#124; Promise&lt;TSetup&gt;</code> | |
| [stop](./kibana-plugin-public.plugin.stop.md) | <code>() =&gt; void</code> | |

View file

@ -5,5 +5,5 @@
<b>Signature:</b>
```typescript
setup: (core: PluginSetupContext, dependencies: TDependencies) => TSetup | Promise<TSetup>;
setup: (core: PluginSetupContext, plugins: TPluginsSetup) => TSetup | Promise<TSetup>;
```

View file

@ -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>;
```

View file

@ -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. |

View file

@ -0,0 +1,19 @@
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [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) =&gt; TSetup &#124; Promise&lt;TSetup&gt;</code> | |
| [stop](./kibana-plugin-server.plugin.stop.md) | <code>() =&gt; void</code> | |

View file

@ -0,0 +1,9 @@
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [Plugin](./kibana-plugin-server.plugin.md) &gt; [setup](./kibana-plugin-server.plugin.setup.md)
## Plugin.setup property
<b>Signature:</b>
```typescript
setup: (pluginSetupContext: PluginSetupContext, plugins: TPluginsSetup) => TSetup | Promise<TSetup>;
```

View file

@ -0,0 +1,9 @@
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [Plugin](./kibana-plugin-server.plugin.md) &gt; [stop](./kibana-plugin-server.plugin.stop.md)
## Plugin.stop property
<b>Signature:</b>
```typescript
stop?: () => void;
```

View file

@ -0,0 +1,11 @@
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [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>;
```

View file

@ -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 {

View file

@ -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);
}
/**

View file

@ -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();
}

View file

@ -31,8 +31,11 @@ export {
APICaller,
} from './elasticsearch';
export { Logger, LoggerFactory, LogMeta, LogRecord, LogLevel } from './logging';
export {
DiscoveredPlugin,
Plugin,
PluginInitializer,
PluginInitializerContext,
PluginName,
PluginSetupContext,

View file

@ -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)

View file

@ -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(

View file

@ -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])
);

View file

@ -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';

View file

@ -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)

View file

@ -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}).`);

View file

@ -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: {

View file

@ -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',

View file

@ -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)
)
);

View file

@ -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);

View file

@ -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>];
})