Skip to content

Commit

Permalink
async-import plugins in the server side (elastic#170856)
Browse files Browse the repository at this point in the history
Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
afharo and kibanamachine authored Nov 15, 2023
1 parent feee02b commit fd09c26
Show file tree
Hide file tree
Showing 246 changed files with 757 additions and 591 deletions.
13 changes: 11 additions & 2 deletions dev_docs/key_concepts/anatomy_of_a_plugin.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ If you are developing in TypeScript (which we recommend), you will need to add a
core capabilities as an argument. It should return an instance of its plugin class for Kibana to load.

```ts
import type { PluginInitializerContext } from '@kbn/core/server';
import type { PluginInitializerContext } from '@kbn/core/public';
import { DemoPlugin } from './plugin';

export function plugin(initializerContext: PluginInitializerContext) {
Expand Down Expand Up @@ -177,7 +177,16 @@ export class DemoPlugin implements Plugin {

### server/index.ts

`server/index.ts` is the entry-point into the server-side code of this plugin. It is identical in almost every way to the client-side entry-point:
`server/index.ts` is the entry-point into the server-side code of this plugin.

```ts
import type { PluginInitializerContext } from '@kbn/core/server';

export async function plugin(initializerContext: PluginInitializerContext) {
const { DemoPlugin } = await import('./plugin');
return new DemoPlugin(initializerContext);
}
```

### server/plugin.ts

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export type MyPluginConfigType = TypeOf<typeof config.schema>;

* Read config value exposed via `PluginInitializerContext`:

*my_plugin/server/index.ts*
*my_plugin/server/plugin.ts*
[source,typescript]
----
import type { PluginInitializerContext } from '@kbn/core/server';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,9 @@ entry-point:
[source,typescript]
----
import type { PluginInitializerContext } from '@kbn/core/server';
import { MyPlugin } from './plugin';
export function plugin(initializerContext: PluginInitializerContext) {
export async function plugin(initializerContext: PluginInitializerContext) {
const { MyPlugin } = await import('./plugin');
return new MyPlugin(initializerContext);
}
----
Expand Down
7 changes: 4 additions & 3 deletions examples/bfetch_explorer/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Side Public License, v 1.
*/

import { BfetchExplorerPlugin } from './plugin';

export const plugin = () => new BfetchExplorerPlugin();
export const plugin = async () => {
const { BfetchExplorerPlugin } = await import('./plugin');
return new BfetchExplorerPlugin();
};
4 changes: 2 additions & 2 deletions examples/content_management_examples/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
*/

import { PluginInitializerContext } from '@kbn/core/server';
import { ContentManagementExamplesPlugin } from './plugin';

export function plugin(initializerContext: PluginInitializerContext) {
export async function plugin(initializerContext: PluginInitializerContext) {
const { ContentManagementExamplesPlugin } = await import('./plugin');
return new ContentManagementExamplesPlugin(initializerContext);
}
7 changes: 4 additions & 3 deletions examples/embeddable_examples/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import { PluginInitializer } from '@kbn/core/server';

import { EmbeddableExamplesPlugin } from './plugin';

export const plugin: PluginInitializer<void, void> = () => new EmbeddableExamplesPlugin();
export const plugin: PluginInitializer<void, void> = async () => {
const { EmbeddableExamplesPlugin } = await import('./plugin');
return new EmbeddableExamplesPlugin();
};
6 changes: 4 additions & 2 deletions examples/feature_control_examples/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
* Side Public License, v 1.
*/
import { PluginInitializer } from '@kbn/core/server';
import { FeatureControlsPluginExample } from './plugin';

export const plugin: PluginInitializer<void, void> = () => new FeatureControlsPluginExample();
export const plugin: PluginInitializer<void, void> = async () => {
const { FeatureControlsPluginExample } = await import('./plugin');
return new FeatureControlsPluginExample();
};
5 changes: 2 additions & 3 deletions examples/field_formats_example/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
* Side Public License, v 1.
*/

import { FieldFormatsExamplePlugin } from './plugin';

export function plugin() {
export async function plugin() {
const { FieldFormatsExamplePlugin } = await import('./plugin');
return new FieldFormatsExamplePlugin();
}
4 changes: 2 additions & 2 deletions examples/files_example/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
*/

import { PluginInitializerContext } from '@kbn/core/server';
import { FilesExamplePlugin } from './plugin';

// This exports static code and TypeScript types,
// as well as, Kibana Platform `plugin()` initializer.

export function plugin(initializerContext: PluginInitializerContext) {
export async function plugin(initializerContext: PluginInitializerContext) {
const { FilesExamplePlugin } = await import('./plugin');
return new FilesExamplePlugin(initializerContext);
}
6 changes: 4 additions & 2 deletions examples/guided_onboarding_example/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
*/

import { PluginInitializerContext } from '@kbn/core/server';
import { GuidedOnboardingExamplePlugin } from './plugin';

export const plugin = (ctx: PluginInitializerContext) => new GuidedOnboardingExamplePlugin(ctx);
export const plugin = async (ctx: PluginInitializerContext) => {
const { GuidedOnboardingExamplePlugin } = await import('./plugin');
return new GuidedOnboardingExamplePlugin(ctx);
};
6 changes: 4 additions & 2 deletions examples/preboot_example/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ import type { TypeOf } from '@kbn/config-schema';
import type { PluginConfigDescriptor, PluginInitializerContext } from '@kbn/core/server';

import { ConfigSchema } from './config';
import { PrebootExamplePlugin } from './plugin';

export const config: PluginConfigDescriptor<TypeOf<typeof ConfigSchema>> = {
schema: ConfigSchema,
exposeToBrowser: { token: true },
};

export const plugin = (context: PluginInitializerContext) => new PrebootExamplePlugin(context);
export const plugin = async (context: PluginInitializerContext) => {
const { PrebootExamplePlugin } = await import('./plugin');
return new PrebootExamplePlugin(context);
};
5 changes: 2 additions & 3 deletions examples/response_stream/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@

import { PluginInitializerContext } from '@kbn/core/server';

import { ResponseStreamPlugin } from './plugin';

export function plugin(initializerContext: PluginInitializerContext) {
export async function plugin(initializerContext: PluginInitializerContext) {
const { ResponseStreamPlugin } = await import('./plugin');
return new ResponseStreamPlugin(initializerContext);
}
7 changes: 4 additions & 3 deletions examples/routing_example/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import { PluginInitializer } from '@kbn/core/server';

import { RoutingExamplePlugin } from './plugin';

export const plugin: PluginInitializer<{}, {}> = () => new RoutingExamplePlugin();
export const plugin: PluginInitializer<{}, {}> = async () => {
const { RoutingExamplePlugin } = await import('./plugin');
return new RoutingExamplePlugin();
};
6 changes: 4 additions & 2 deletions examples/screenshot_mode_example/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
*/

import { PluginInitializerContext } from '@kbn/core/server';
import { ScreenshotModeExamplePlugin } from './plugin';

export const plugin = (ctx: PluginInitializerContext) => new ScreenshotModeExamplePlugin(ctx);
export const plugin = async (ctx: PluginInitializerContext) => {
const { ScreenshotModeExamplePlugin } = await import('./plugin');
return new ScreenshotModeExamplePlugin(ctx);
};
4 changes: 2 additions & 2 deletions examples/search_examples/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
*/

import { PluginInitializerContext } from '@kbn/core/server';
import { SearchExamplesPlugin } from './plugin';

export function plugin(initializerContext: PluginInitializerContext) {
export async function plugin(initializerContext: PluginInitializerContext) {
const { SearchExamplesPlugin } = await import('./plugin');
return new SearchExamplesPlugin(initializerContext);
}

Expand Down
6 changes: 4 additions & 2 deletions examples/user_profile_examples/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { UserProfilesPlugin } from './plugin';

export const plugin = () => new UserProfilesPlugin();
export const plugin = async () => {
const { UserProfilesPlugin } = await import('./plugin');
return new UserProfilesPlugin();
};
4 changes: 2 additions & 2 deletions examples/v8_profiler_examples/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
*/

import { PluginInitializerContext } from '@kbn/core/server';
import { V8ProfilerExamplesPlugin } from './plugin';

export function plugin(initializerContext: PluginInitializerContext) {
export async function plugin(initializerContext: PluginInitializerContext) {
const { V8ProfilerExamplesPlugin } = await import('./plugin');
return new V8ProfilerExamplesPlugin(initializerContext);
}
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ describe('`constructor` correctly sets non-external source', () => {
});
});

test('`setup` fails if `plugin` initializer is not exported', () => {
test('`setup` fails if the plugin has not been initialized', () => {
const manifest = createPluginManifest();
const opaqueId = Symbol();
const plugin = new PluginWrapper({
Expand All @@ -176,11 +176,32 @@ test('`setup` fails if `plugin` initializer is not exported', () => {
expect(() =>
plugin.setup(createPluginSetupContext({ deps: setupDeps, plugin, runtimeResolver }), {})
).toThrowErrorMatchingInlineSnapshot(
`"The plugin is not initialized. Call the init method first."`
);
});

test('`init` fails if `plugin` initializer is not exported', async () => {
const manifest = createPluginManifest();
const opaqueId = Symbol();
const plugin = new PluginWrapper({
path: 'plugin-without-initializer-path',
manifest,
opaqueId,
initializerContext: createPluginInitializerContext({
coreContext,
opaqueId,
manifest,
instanceInfo,
nodeInfo,
}),
});

await expect(() => plugin.init()).rejects.toThrowErrorMatchingInlineSnapshot(
`"Plugin \\"some-plugin-id\\" does not export \\"plugin\\" definition (plugin-without-initializer-path)."`
);
});

test('`setup` fails if plugin initializer is not a function', () => {
test('`init` fails if plugin initializer is not a function', async () => {
const manifest = createPluginManifest();
const opaqueId = Symbol();
const plugin = new PluginWrapper({
Expand All @@ -196,14 +217,12 @@ test('`setup` fails if plugin initializer is not a function', () => {
}),
});

expect(() =>
plugin.setup(createPluginSetupContext({ deps: setupDeps, plugin, runtimeResolver }), {})
).toThrowErrorMatchingInlineSnapshot(
await expect(() => plugin.init()).rejects.toThrowErrorMatchingInlineSnapshot(
`"Definition of plugin \\"some-plugin-id\\" should be a function (plugin-with-wrong-initializer-path)."`
);
});

test('`setup` fails if initializer does not return object', () => {
test('`init` fails if initializer does not return object', async () => {
const manifest = createPluginManifest();
const opaqueId = Symbol();
const plugin = new PluginWrapper({
Expand All @@ -219,16 +238,14 @@ test('`setup` fails if initializer does not return object', () => {
}),
});

mockPluginInitializer.mockReturnValue(null);
mockPluginInitializer.mockResolvedValue(null);

expect(() =>
plugin.setup(createPluginSetupContext({ deps: setupDeps, plugin, runtimeResolver }), {})
).toThrowErrorMatchingInlineSnapshot(
await expect(() => plugin.init()).rejects.toThrowErrorMatchingInlineSnapshot(
`"Initializer for plugin \\"some-plugin-id\\" is expected to return plugin instance, but returned \\"null\\"."`
);
});

test('`setup` fails if object returned from initializer does not define `setup` function', () => {
test('`init` fails if object returned from initializer does not define `setup` function', async () => {
const manifest = createPluginManifest();
const opaqueId = Symbol();
const plugin = new PluginWrapper({
Expand All @@ -245,11 +262,9 @@ test('`setup` fails if object returned from initializer does not define `setup`
});

const mockPluginInstance = { run: jest.fn() };
mockPluginInitializer.mockReturnValue(mockPluginInstance);
mockPluginInitializer.mockResolvedValue(mockPluginInstance);

expect(() =>
plugin.setup(createPluginSetupContext({ deps: setupDeps, plugin, runtimeResolver }), {})
).toThrowErrorMatchingInlineSnapshot(
await expect(() => plugin.init()).rejects.toThrowErrorMatchingInlineSnapshot(
`"Instance of plugin \\"some-plugin-id\\" does not define \\"setup\\" function."`
);
});
Expand All @@ -272,7 +287,9 @@ test('`setup` initializes plugin and calls appropriate lifecycle hook', async ()
});

const mockPluginInstance = { setup: jest.fn().mockResolvedValue({ contract: 'yes' }) };
mockPluginInitializer.mockReturnValue(mockPluginInstance);
mockPluginInitializer.mockResolvedValue(mockPluginInstance);

await plugin.init();

const setupContext = createPluginSetupContext({ deps: setupDeps, plugin, runtimeResolver });
const setupDependencies = { 'some-required-dep': { contract: 'no' } };
Expand Down Expand Up @@ -323,8 +340,9 @@ test('`start` fails invoked for the `preboot` plugin', async () => {
});

const mockPluginInstance = { setup: jest.fn() };
mockPluginInitializer.mockReturnValue(mockPluginInstance);
mockPluginInitializer.mockResolvedValue(mockPluginInstance);

await plugin.init();
await plugin.setup({} as any, {} as any);

expect(() => plugin.start({} as any, {} as any)).toThrowErrorMatchingInlineSnapshot(
Expand Down Expand Up @@ -355,8 +373,9 @@ test('`start` calls plugin.start with context and dependencies', async () => {
setup: jest.fn(),
start: jest.fn().mockResolvedValue(pluginStartContract),
};
mockPluginInitializer.mockReturnValue(mockPluginInstance);
mockPluginInitializer.mockResolvedValue(mockPluginInstance);

await plugin.init();
await plugin.setup({} as any, {} as any);

const startContract = await plugin.start(context, deps);
Expand Down Expand Up @@ -399,8 +418,9 @@ test("`start` resolves `startDependencies` Promise after plugin's start", async
return pluginStartContract;
},
};
mockPluginInitializer.mockReturnValue(mockPluginInstance);
mockPluginInitializer.mockResolvedValue(mockPluginInstance);

await plugin.init();
await plugin.setup({} as any, {} as any);

const startDependenciesCheck = plugin.startDependencies.then((resolvedStartDeps) => {
Expand Down Expand Up @@ -429,7 +449,7 @@ test('`stop` fails if plugin is not set up', async () => {
});

const mockPluginInstance = { setup: jest.fn(), stop: jest.fn() };
mockPluginInitializer.mockReturnValue(mockPluginInstance);
mockPluginInitializer.mockResolvedValue(mockPluginInstance);

await expect(plugin.stop()).rejects.toMatchInlineSnapshot(
`[Error: Plugin "some-plugin-id" can't be stopped since it isn't set up.]`
Expand All @@ -453,7 +473,8 @@ test('`stop` does nothing if plugin does not define `stop` function', async () =
}),
});

mockPluginInitializer.mockReturnValue({ setup: jest.fn() });
mockPluginInitializer.mockResolvedValue({ setup: jest.fn() });
await plugin.init();
await plugin.setup(createPluginSetupContext({ deps: setupDeps, plugin, runtimeResolver }), {});

await expect(plugin.stop()).resolves.toBeUndefined();
Expand All @@ -476,7 +497,8 @@ test('`stop` calls `stop` defined by the plugin instance', async () => {
});

const mockPluginInstance = { setup: jest.fn(), stop: jest.fn() };
mockPluginInitializer.mockReturnValue(mockPluginInstance);
mockPluginInitializer.mockResolvedValue(mockPluginInstance);
await plugin.init();
await plugin.setup(createPluginSetupContext({ deps: setupDeps, plugin, runtimeResolver }), {});

await expect(plugin.stop()).resolves.toBeUndefined();
Expand Down
Loading

0 comments on commit fd09c26

Please sign in to comment.