Skip to content

Commit

Permalink
Add support for plugin-scoped plugin contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
joshdover committed Jul 15, 2019
1 parent bedca06 commit 1c8b343
Show file tree
Hide file tree
Showing 28 changed files with 249 additions and 62 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,5 @@ export interface ApplicationStart

| Property | Type | Description |
| --- | --- | --- |
| [availableApps](./kibana-plugin-public.applicationstart.availableapps.md) | <code>CapabilitiesStart['availableApps']</code> | |
| [capabilities](./kibana-plugin-public.applicationstart.capabilities.md) | <code>CapabilitiesStart['capabilities']</code> | |
| [mount](./kibana-plugin-public.applicationstart.mount.md) | <code>(mountHandler: Function) =&gt; void</code> | |

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
<b>Signature:</b>

```typescript
application: Pick<ApplicationStart, 'capabilities'>;
application: ApplicationStart;
```
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface CoreStart

| Property | Type | Description |
| --- | --- | --- |
| [application](./kibana-plugin-public.corestart.application.md) | <code>Pick&lt;ApplicationStart, 'capabilities'&gt;</code> | [ApplicationStart](./kibana-plugin-public.applicationstart.md) |
| [application](./kibana-plugin-public.corestart.application.md) | <code>ApplicationStart</code> | [ApplicationStart](./kibana-plugin-public.applicationstart.md) |
| [chrome](./kibana-plugin-public.corestart.chrome.md) | <code>ChromeStart</code> | [ChromeStart](./kibana-plugin-public.chromestart.md) |
| [docLinks](./kibana-plugin-public.corestart.doclinks.md) | <code>DocLinksStart</code> | [DocLinksStart](./kibana-plugin-public.doclinksstart.md) |
| [http](./kibana-plugin-public.corestart.http.md) | <code>HttpStart</code> | [HttpStart](./kibana-plugin-public.httpstart.md) |
Expand Down
3 changes: 2 additions & 1 deletion docs/development/core/public/kibana-plugin-public.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| [NotificationsStart](./kibana-plugin-public.notificationsstart.md) | |
| [OverlayRef](./kibana-plugin-public.overlayref.md) | |
| [OverlayStart](./kibana-plugin-public.overlaystart.md) | |
| [Plugin](./kibana-plugin-public.plugin.md) | The interface that should be returned by a <code>PluginInitializer</code>. |
| [Plugin](./kibana-plugin-public.plugin.md) | The interface that should be returned by a [PluginInitializer](./kibana-plugin-public.plugininitializer.md)<!-- -->. |
| [PluginInitializerContext](./kibana-plugin-public.plugininitializercontext.md) | The available core services passed to a <code>PluginInitializer</code> |
| [UiSettingsState](./kibana-plugin-public.uisettingsstate.md) | |

Expand All @@ -61,6 +61,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| [HttpSetup](./kibana-plugin-public.httpsetup.md) | |
| [HttpStart](./kibana-plugin-public.httpstart.md) | |
| [PluginInitializer](./kibana-plugin-public.plugininitializer.md) | The <code>plugin</code> export at the root of a plugin's <code>public</code> directory should conform to this interface. |
| [PluginLifecycleContract](./kibana-plugin-public.pluginlifecyclecontract.md) | A plugin contact can either be a raw value or a function that receives a unique symbol per dependency to provide a pre-configured or scoped contract to the dependency. |
| [RecursiveReadonly](./kibana-plugin-public.recursivereadonly.md) | |
| [ToastInput](./kibana-plugin-public.toastinput.md) | |
| [UiSettingsClientContract](./kibana-plugin-public.uisettingsclientcontract.md) | [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

## Plugin interface

The interface that should be returned by a `PluginInitializer`<!-- -->.
The interface that should be returned by a [PluginInitializer](./kibana-plugin-public.plugininitializer.md)<!-- -->.

<b>Signature:</b>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<b>Signature:</b>

```typescript
setup(core: CoreSetup, plugins: TPluginsSetup): TSetup | Promise<TSetup>;
setup(core: CoreSetup, plugins: TPluginsSetup): PluginLifecycleContract<TSetup> | Promise<PluginLifecycleContract<TSetup>>;
```

## Parameters
Expand All @@ -19,5 +19,5 @@ setup(core: CoreSetup, plugins: TPluginsSetup): TSetup | Promise<TSetup>;

<b>Returns:</b>

`TSetup | Promise<TSetup>`
`PluginLifecycleContract<TSetup> | Promise<PluginLifecycleContract<TSetup>>`

Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<b>Signature:</b>

```typescript
start(core: CoreStart, plugins: TPluginsStart): TStart | Promise<TStart>;
start(core: CoreStart, plugins: TPluginsStart): PluginLifecycleContract<TStart> | Promise<PluginLifecycleContract<TStart>>;
```

## Parameters
Expand All @@ -19,5 +19,5 @@ start(core: CoreStart, plugins: TPluginsStart): TStart | Promise<TStart>;

<b>Returns:</b>

`TStart | Promise<TStart>`
`PluginLifecycleContract<TStart> | Promise<PluginLifecycleContract<TStart>>`

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [PluginLifecycleContract](./kibana-plugin-public.pluginlifecyclecontract.md)

## PluginLifecycleContract type

A plugin contact can either be a raw value or a function that receives a unique symbol per dependency to provide a pre-configured or scoped contract to the dependency.

<b>Signature:</b>

```typescript
export declare type PluginLifecycleContract<T> = T extends (dependency: Readonly<{
id: symbol;
}>) => infer U ? U : T;
```
3 changes: 2 additions & 1 deletion docs/development/core/server/kibana-plugin-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| [LogMeta](./kibana-plugin-server.logmeta.md) | Contextual metadata |
| [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md) | A tool set defining an outcome of OnPostAuth interceptor for incoming request. |
| [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. |
| [Plugin](./kibana-plugin-server.plugin.md) | The interface that should be returned by a <code>PluginInitializer</code>. |
| [Plugin](./kibana-plugin-server.plugin.md) | The interface that should be returned by a [PluginInitializer](./kibana-plugin-server.plugininitializer.md)<!-- -->. |
| [PluginInitializerContext](./kibana-plugin-server.plugininitializercontext.md) | Context that's available to plugins during initialization stage. |
| [PluginsServiceSetup](./kibana-plugin-server.pluginsservicesetup.md) | |
| [PluginsServiceStart](./kibana-plugin-server.pluginsservicestart.md) | |
Expand Down Expand Up @@ -81,6 +81,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| [OnPostAuthHandler](./kibana-plugin-server.onpostauthhandler.md) | |
| [OnPreAuthHandler](./kibana-plugin-server.onpreauthhandler.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. |
| [PluginLifecycleContract](./kibana-plugin-server.pluginlifecyclecontract.md) | A plugin contact can either be a raw value or a function that receives a unique symbol per dependency to provide a pre-configured or scoped contract to the dependency. |
| [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. |
| [RecursiveReadonly](./kibana-plugin-server.recursivereadonly.md) | |
| [RouteMethod](./kibana-plugin-server.routemethod.md) | The set of common HTTP methods supported by Kibana routing. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

## Plugin interface

The interface that should be returned by a `PluginInitializer`<!-- -->.
The interface that should be returned by a [PluginInitializer](./kibana-plugin-server.plugininitializer.md)<!-- -->.

<b>Signature:</b>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<b>Signature:</b>

```typescript
setup(core: CoreSetup, plugins: TPluginsSetup): TSetup | Promise<TSetup>;
setup(core: CoreSetup, plugins: TPluginsSetup): PluginLifecycleContract<TSetup> | Promise<PluginLifecycleContract<TSetup>>;
```

## Parameters
Expand All @@ -19,5 +19,5 @@ setup(core: CoreSetup, plugins: TPluginsSetup): TSetup | Promise<TSetup>;

<b>Returns:</b>

`TSetup | Promise<TSetup>`
`PluginLifecycleContract<TSetup> | Promise<PluginLifecycleContract<TSetup>>`

Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<b>Signature:</b>

```typescript
start(core: CoreStart, plugins: TPluginsStart): TStart | Promise<TStart>;
start(core: CoreStart, plugins: TPluginsStart): PluginLifecycleContract<TStart> | Promise<PluginLifecycleContract<TStart>>;
```

## Parameters
Expand All @@ -19,5 +19,5 @@ start(core: CoreStart, plugins: TPluginsStart): TStart | Promise<TStart>;

<b>Returns:</b>

`TStart | Promise<TStart>`
`PluginLifecycleContract<TStart> | Promise<PluginLifecycleContract<TStart>>`

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [PluginLifecycleContract](./kibana-plugin-server.pluginlifecyclecontract.md)

## PluginLifecycleContract type

A plugin contact can either be a raw value or a function that receives a unique symbol per dependency to provide a pre-configured or scoped contract to the dependency.

<b>Signature:</b>

```typescript
export declare type PluginLifecycleContract<T> = T extends (dependency: Readonly<{
id: symbol;
}>) => infer U ? U : T;
```
8 changes: 7 additions & 1 deletion src/core/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,12 @@ import {
ToastsApi,
} from './notifications';
import { OverlayRef, OverlayStart } from './overlays';
import { Plugin, PluginInitializer, PluginInitializerContext } from './plugins';
import {
Plugin,
PluginInitializer,
PluginInitializerContext,
PluginLifecycleContract,
} from './plugins';
import { UiSettingsClient, UiSettingsState, UiSettingsClientContract } from './ui_settings';
import { ApplicationSetup, Capabilities, ApplicationStart } from './application';
import { DocLinksStart } from './doc_links';
Expand Down Expand Up @@ -163,6 +168,7 @@ export {
Plugin,
PluginInitializer,
PluginInitializerContext,
PluginLifecycleContract,
Toast,
ToastInput,
ToastsApi,
Expand Down
2 changes: 1 addition & 1 deletion src/core/public/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@
*/

export * from './plugins_service';
export { Plugin, PluginInitializer } from './plugin';
export { Plugin, PluginInitializer, PluginLifecycleContract } from './plugin';
export { PluginInitializerContext } from './plugin_context';
23 changes: 20 additions & 3 deletions src/core/public/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,17 @@ import { loadPluginBundle } from './plugin_loader';
import { CoreStart, CoreSetup } from '..';

/**
* The interface that should be returned by a `PluginInitializer`.
* A plugin contact can either be a raw value or a function that receives a unique symbol per dependency to provide
* a pre-configured or scoped contract to the dependency.
*
* @public
*/
export type PluginLifecycleContract<T> = T extends (dependency: Readonly<{ id: symbol }>) => infer U
? U
: T;

/**
* The interface that should be returned by a {@link PluginInitializer}.
*
* @public
*/
Expand All @@ -33,8 +43,14 @@ export interface Plugin<
TPluginsSetup extends {} = {},
TPluginsStart extends {} = {}
> {
setup(core: CoreSetup, plugins: TPluginsSetup): TSetup | Promise<TSetup>;
start(core: CoreStart, plugins: TPluginsStart): TStart | Promise<TStart>;
setup(
core: CoreSetup,
plugins: TPluginsSetup
): PluginLifecycleContract<TSetup> | Promise<PluginLifecycleContract<TSetup>>;
start(
core: CoreStart,
plugins: TPluginsStart
): PluginLifecycleContract<TStart> | Promise<PluginLifecycleContract<TStart>>;
stop?(): void;
}

Expand Down Expand Up @@ -67,6 +83,7 @@ export class PluginWrapper<
public readonly configPath: DiscoveredPlugin['configPath'];
public readonly requiredPlugins: DiscoveredPlugin['requiredPlugins'];
public readonly optionalPlugins: DiscoveredPlugin['optionalPlugins'];
public readonly opaqueId = Symbol();
private initializer?: PluginInitializer<TSetup, TStart, TPluginsSetup, TPluginsStart>;
private instance?: Plugin<TSetup, TStart, TPluginsSetup, TPluginsStart>;

Expand Down
77 changes: 75 additions & 2 deletions src/core/public/plugins/plugins_service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ test('`PluginsService.setup` exposes dependent setup contracts to plugins', asyn

test('`PluginsService.setup` does not set missing dependent setup contracts', async () => {
mockSetupDeps.injectedMetadata.getPlugins.mockReturnValue([
{ id: 'pluginD', plugin: createManifest('pluginD', { required: ['missing'] }) },
{ id: 'pluginD', plugin: createManifest('pluginD', { optional: ['missing'] }) },
]);
mockPluginInitializers.set('pluginD', jest.fn(() => ({
setup: jest.fn(),
Expand All @@ -226,6 +226,42 @@ test('`PluginsService.setup` returns plugin setup contracts', async () => {
expect((contracts.get('pluginB')! as any).pluginAPlusB).toEqual(2);
});

test('`PluginService.setup` calls scoped plugin function for dependencies', async () => {
const setupScoped = jest.fn<string, [{ id: symbol }]>(() => 'plugin-d-setup');

mockSetupDeps.injectedMetadata.getPlugins.mockReturnValue([
{ id: 'pluginD', plugin: createManifest('pluginD') },
{ id: 'pluginE', plugin: createManifest('pluginE', { required: ['pluginD'] }) },
]);

mockPluginInitializers
.set(
'pluginD',
jest.fn(
() =>
({
setup: jest.fn(() => setupScoped),
start: jest.fn(),
} as any)
)
)
.set('pluginE', jest.fn(() => ({
setup: jest.fn(),
start: jest.fn(),
})) as any);

const pluginsService = new PluginsService(mockCoreContext);
await pluginsService.setup(mockSetupDeps);

expect(setupScoped).toHaveBeenCalled();
expect(typeof setupScoped.mock.calls[0][0].id === 'symbol').toBe(true);

const pluginEInstance = mockPluginInitializers.get('pluginE')!.mock.results[0].value;
expect(pluginEInstance.setup).toHaveBeenCalledWith(mockSetupContext, {
pluginD: 'plugin-d-setup',
});
});

test('`PluginsService.start` exposes dependent start contracts to plugins', async () => {
const pluginsService = new PluginsService(mockCoreContext);
await pluginsService.setup(mockSetupDeps);
Expand All @@ -247,7 +283,7 @@ test('`PluginsService.start` exposes dependent start contracts to plugins', asyn

test('`PluginsService.start` does not set missing dependent start contracts', async () => {
mockSetupDeps.injectedMetadata.getPlugins.mockReturnValue([
{ id: 'pluginD', plugin: createManifest('pluginD', { required: ['missing'] }) },
{ id: 'pluginD', plugin: createManifest('pluginD', { optional: ['missing'] }) },
]);
mockPluginInitializers.set('pluginD', jest.fn(() => ({
setup: jest.fn(),
Expand Down Expand Up @@ -275,6 +311,43 @@ test('`PluginsService.start` returns plugin start contracts', async () => {
expect((contracts.get('pluginB')! as any).pluginAPlusB).toEqual(3);
});

test('`PluginService.start` calls scoped plugin function for dependencies', async () => {
const startScoped = jest.fn<string, [{ id: symbol }]>(() => 'plugin-d-start');

mockSetupDeps.injectedMetadata.getPlugins.mockReturnValue([
{ id: 'pluginD', plugin: createManifest('pluginD') },
{ id: 'pluginE', plugin: createManifest('pluginE', { required: ['pluginD'] }) },
]);

mockPluginInitializers
.set(
'pluginD',
jest.fn(
() =>
({
setup: jest.fn(),
start: jest.fn(() => startScoped),
} as any)
)
)
.set('pluginE', jest.fn(() => ({
setup: jest.fn(),
start: jest.fn(),
})) as any);

const pluginsService = new PluginsService(mockCoreContext);
await pluginsService.setup(mockSetupDeps);
await pluginsService.start(mockStartDeps);

expect(startScoped).toHaveBeenCalled();
expect(typeof startScoped.mock.calls[0][0].id === 'symbol').toBe(true);

const pluginEInstance = mockPluginInitializers.get('pluginE')!.mock.results[0].value;
expect(pluginEInstance.start).toHaveBeenCalledWith(mockStartContext, {
pluginD: 'plugin-d-start',
});
});

test('`PluginService.stop` calls the stop function on each plugin', async () => {
const pluginsService = new PluginsService(mockCoreContext);
await pluginsService.setup(mockSetupDeps);
Expand Down
Loading

0 comments on commit 1c8b343

Please sign in to comment.