Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

async-import plugins in the server side #170856

Merged
merged 16 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading