From a62ceb9d3da1aae8bed776a32310d96f777111cb Mon Sep 17 00:00:00 2001 From: Josh Dover Date: Tue, 23 Jul 2019 11:58:16 -0500 Subject: [PATCH] Use pluginId explicitly --- ...n-public.contextcontainer.createhandler.md | 7 +- .../kibana-plugin-public.contextcontainer.md | 50 ++--- ...public.contextcontainer.registercontext.md | 5 +- .../kibana-plugin-public.contextsetup.md | 62 +++--- .../kibana-plugin-public.coresetup.context.md | 2 +- .../public/kibana-plugin-public.coresetup.md | 2 +- .../core/public/kibana-plugin-public.md | 4 +- src/core/public/context/context.mock.ts | 1 - src/core/public/context/context.test.ts | 103 ++++++---- src/core/public/context/context.ts | 183 +++++++++--------- .../public/context/context_service.mock.ts | 13 +- .../public/context/context_service.test.ts | 69 +------ src/core/public/context/context_service.ts | 59 ++---- src/core/public/context/index.ts | 2 +- src/core/public/core_system.test.ts | 10 +- src/core/public/core_system.ts | 14 +- src/core/public/index.ts | 6 +- src/core/public/legacy/legacy_service.test.ts | 2 - .../public/plugins/plugins_service.test.ts | 13 +- src/core/public/plugins/plugins_service.ts | 14 +- src/core/public/public.api.md | 17 +- 21 files changed, 254 insertions(+), 384 deletions(-) diff --git a/docs/development/core/public/kibana-plugin-public.contextcontainer.createhandler.md b/docs/development/core/public/kibana-plugin-public.contextcontainer.createhandler.md index 899b35c07e6ec69..70cc72825dd098e 100644 --- a/docs/development/core/public/kibana-plugin-public.contextcontainer.createhandler.md +++ b/docs/development/core/public/kibana-plugin-public.contextcontainer.createhandler.md @@ -9,20 +9,19 @@ Create a new handler function pre-wired to context for the plugin. Signature: ```typescript -createHandler(handler: Handler): (...rest: THandlerParameters) => Promisify; +createHandler(plugin: string | CoreId, handler: Handler): (...rest: THandlerParameters) => Promisify; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | +| plugin | string | CoreId | The plugin ID for the plugin that registers this context. | | handler | Handler<TContext, THandlerReturn, THandlerParameters> | | Returns: `(...rest: THandlerParameters) => Promisify` -## Remarks - -This must be called when the handler is registered by the consuming plugin. If this is called later in the lifecycle it will throw an exception. +A function that takes `THandlerParameters`, calls `handler` with a new context, and returns a Promise of the `handler` return value. diff --git a/docs/development/core/public/kibana-plugin-public.contextcontainer.md b/docs/development/core/public/kibana-plugin-public.contextcontainer.md index 621683bbb608f94..2a861c60bf9817b 100644 --- a/docs/development/core/public/kibana-plugin-public.contextcontainer.md +++ b/docs/development/core/public/kibana-plugin-public.contextcontainer.md @@ -4,7 +4,7 @@ ## ContextContainer interface -An object that handles registration of context providers and building of new context objects. +An object that handles registration of context providers and configuring handlers with context. Signature: @@ -16,16 +16,18 @@ export interface ContextContainercontextName. | ## Remarks -A [ContextContainer](./kibana-plugin-public.contextcontainer.md) can be used by any Core service or plugin (known as the "service owner") which wishes to expose APIs in a handler function. The container object will manage registering context providers and building a context object for a handler with all of the contexts that should be exposed to the handler's plugin. This is dependent on the dependencies that the handler's plugin declares. +A [ContextContainer](./kibana-plugin-public.contextcontainer.md) can be used by any Core service or plugin (known as the "service owner") which wishes to expose APIs in a handler function. The container object will manage registering context providers and configuring a handler with all of the contexts that should be exposed to the handler's plugin. This is dependent on the dependencies that the handler's plugin declares. Contexts providers are executed in the order they were registered. Each provider gets access to context values provided by any plugins that it depends on. -In order to configure a handler with context, you must call the [ContextContainer.createHandler()](./kibana-plugin-public.contextcontainer.createhandler.md) function. This function must be called \_while the calling plugin's lifecycle method is still running\_ or else you risk configuring the handler for the wrong plugin, or not plugin at all (the latter will throw an error). +In order to configure a handler with context, you must call the [ContextContainer.createHandler()](./kibana-plugin-public.contextcontainer.createhandler.md) function and use the returned handler which will automatically build a context object when called. + +When registering context or creating handlers, the \_calling plugin's id\_ must be provided. Note this should NOT be the context service owner, but the plugin that is actually registering the context or handler. ```ts // GOOD @@ -35,10 +37,13 @@ class MyPlugin { setup(core) { this.contextContainer = core.context.createContextContainer(); return { - registerRoute(path, handler) { + registerContext(plugin, contextName, provider) { + this.contextContainer.registerContext(plugin, contextName, provider); + }, + registerRoute(plugin, path, handler) { this.handlers.set( path, - this.contextContainer.createHandler(handler) + this.contextContainer.createHandler(plugin, handler) ); } } @@ -52,32 +57,15 @@ class MyPlugin { setup(core) { this.contextContainer = core.context.createContextContainer(); return { - // When the promise isn't returned, it's possible `createHandler` won't be called until after the lifecycle - // hook is completed. - registerRoute(path, handler) { - doAsyncThing().then(() => this.handlers.set( - path, - this.contextContainer.createHandler(handler) - )); - } - } - } -} - -// ALSO GOOD -class MyPlugin { - private readonly handlers = new Map(); - - setup(core) { - this.contextContainer = core.context.createContextContainer(); - return { - // Returning a Promise also works, but only if calling plugins wait for it to resolve before returning from - // their lifecycle hooks. - async registerRoute(path, handler) { - await doAsyncThing(); + registerContext(plugin, contextName, provider) { + // This would leak this context to all handlers rather tha only plugins that depend on the calling plugin. + this.contextContainer.registerContext('my_plugin', contextName, provider); + }, + registerRoute(plugin, path, handler) { this.handlers.set( path, - this.contextContainer.createHandler(handler) + // the handler will not receive any contexts provided by other dependencies of the calling plugin. + this.contextContainer.createHandler('my_plugin', handler) ); } } diff --git a/docs/development/core/public/kibana-plugin-public.contextcontainer.registercontext.md b/docs/development/core/public/kibana-plugin-public.contextcontainer.registercontext.md index 22047b66d02151a..18d3d52b821cf5e 100644 --- a/docs/development/core/public/kibana-plugin-public.contextcontainer.registercontext.md +++ b/docs/development/core/public/kibana-plugin-public.contextcontainer.registercontext.md @@ -4,18 +4,19 @@ ## ContextContainer.registerContext() method -Register a new context provider. Throws an exception if more than one provider is registered for the same context key. +Register a new context provider. Throws an exception if more than one provider is registered for the same `contextName`. Signature: ```typescript -registerContext(contextName: TContextName, provider: ContextProvider): this; +registerContext(plugin: string | CoreId, contextName: TContextName, provider: ContextProvider): this; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | +| plugin | string | CoreId | The plugin ID for the plugin that registers this context. | | contextName | TContextName | The key of the TContext object this provider supplies the value for. | | provider | ContextProvider<TContext, TContextName, THandlerParameters> | A [ContextProvider](./kibana-plugin-public.contextprovider.md) to be called each time a new context is created. | diff --git a/docs/development/core/public/kibana-plugin-public.contextsetup.md b/docs/development/core/public/kibana-plugin-public.contextsetup.md index bb8309634d6d272..e1de024d0f2e5b9 100644 --- a/docs/development/core/public/kibana-plugin-public.contextsetup.md +++ b/docs/development/core/public/kibana-plugin-public.contextsetup.md @@ -4,7 +4,7 @@ ## ContextSetup interface -An object that handles registration of context providers and building of new context objects. +An object that handles registration of context providers and configuring handlers with context. Signature: @@ -20,11 +20,13 @@ export interface ContextSetup ## Remarks -A [ContextContainer](./kibana-plugin-public.contextcontainer.md) can be used by any Core service or plugin (known as the "service owner") which wishes to expose APIs in a handler function. The container object will manage registering context providers and building a context object for a handler with all of the contexts that should be exposed to the handler's plugin. This is dependent on the dependencies that the handler's plugin declares. +A [ContextContainer](./kibana-plugin-public.contextcontainer.md) can be used by any Core service or plugin (known as the "service owner") which wishes to expose APIs in a handler function. The container object will manage registering context providers and configuring a handler with all of the contexts that should be exposed to the handler's plugin. This is dependent on the dependencies that the handler's plugin declares. Contexts providers are executed in the order they were registered. Each provider gets access to context values provided by any plugins that it depends on. -In order to configure a handler with context, you must call the [ContextContainer.createHandler()](./kibana-plugin-public.contextcontainer.createhandler.md) function. This function must be called \_while the calling plugin's lifecycle method is still running\_ or else you risk configuring the handler for the wrong plugin, or not plugin at all (the latter will throw an error). +In order to configure a handler with context, you must call the [ContextContainer.createHandler()](./kibana-plugin-public.contextcontainer.createhandler.md) function and use the returned handler which will automatically build a context object when called. + +When registering context or creating handlers, the \_calling plugin's id\_ must be provided. Note this should NOT be the context service owner, but the plugin that is actually registering the context or handler. ```ts // GOOD @@ -34,10 +36,13 @@ class MyPlugin { setup(core) { this.contextContainer = core.context.createContextContainer(); return { - registerRoute(path, handler) { + registerContext(plugin, contextName, provider) { + this.contextContainer.registerContext(plugin, contextName, provider); + }, + registerRoute(plugin, path, handler) { this.handlers.set( path, - this.contextContainer.createHandler(handler) + this.contextContainer.createHandler(plugin, handler) ); } } @@ -51,32 +56,15 @@ class MyPlugin { setup(core) { this.contextContainer = core.context.createContextContainer(); return { - // When the promise isn't returned, it's possible `createHandler` won't be called until after the lifecycle - // hook is completed. - registerRoute(path, handler) { - doAsyncThing().then(() => this.handlers.set( - path, - this.contextContainer.createHandler(handler) - )); - } - } - } -} - -// ALSO GOOD -class MyPlugin { - private readonly handlers = new Map(); - - setup(core) { - this.contextContainer = core.context.createContextContainer(); - return { - // Returning a Promise also works, but only if calling plugins wait for it to resolve before returning from - // their lifecycle hooks. - async registerRoute(path, handler) { - await doAsyncThing(); + registerContext(plugin, contextName, provider) { + // This would leak this context to all handlers rather tha only plugins that depend on the calling plugin. + this.contextContainer.registerContext('my_plugin', contextName, provider); + }, + registerRoute(plugin, path, handler) { this.handlers.set( path, - this.contextContainer.createHandler(handler) + // the handler will not receive any contexts provided by other dependencies of the calling plugin. + this.contextContainer.createHandler('my_plugin', handler) ); } } @@ -111,27 +99,27 @@ class VizRenderingPlugin { >(); return { - registerContext: this.contextContainer.register, - registerVizRenderer: (renderMethod: string, renderer: VizTypeRenderer) => - // `createHandler` must be called immediately during the calling plugin's lifecycle method. - this.vizRenderers.set(renderMethod, this.contextContainer.createHandler(renderer)), + registerContext: this.contextContainer.registerContext, + registerVizRenderer: (plugin: string, renderMethod: string, renderer: VizTypeRenderer) => + this.vizRenderers.set(renderMethod, this.contextContainer.createHandler(plugin, renderer)), }; } start(core) { - // Register the core context available to all renderers - this.contextContainer.register('core', () => ({ + // Register the core context available to all renderers. Use the VizRendererContext's pluginId as the first arg. + this.contextContainer.registerContext('viz_rendering', 'core', () => ({ i18n: core.i18n, uiSettings: core.uiSettings })); return { - registerContext: this.contextContainer.register, + registerContext: this.contextContainer.registerContext, + // The handler can now be called directly with only an `HTMLElement` and will automaticallly // have the `context` argument supplied. renderVizualization: (renderMethod: string, domElement: HTMLElement) => { if (!this.vizRenderer.has(renderMethod)) { - throw new Error(`Render method ${renderMethod} has not be registered`); + throw new Error(`Render method '${renderMethod}' has not been registered`); } return this.vizRenderers.get(renderMethod)(domElement); diff --git a/docs/development/core/public/kibana-plugin-public.coresetup.context.md b/docs/development/core/public/kibana-plugin-public.coresetup.context.md index dbb1c6d127a505b..e56ecb92074c480 100644 --- a/docs/development/core/public/kibana-plugin-public.coresetup.context.md +++ b/docs/development/core/public/kibana-plugin-public.coresetup.context.md @@ -9,5 +9,5 @@ Signature: ```typescript -context: Pick; +context: ContextSetup; ``` diff --git a/docs/development/core/public/kibana-plugin-public.coresetup.md b/docs/development/core/public/kibana-plugin-public.coresetup.md index 529a36c6e73683f..a4b5b88df36dc48 100644 --- a/docs/development/core/public/kibana-plugin-public.coresetup.md +++ b/docs/development/core/public/kibana-plugin-public.coresetup.md @@ -16,7 +16,7 @@ export interface CoreSetup | Property | Type | Description | | --- | --- | --- | -| [context](./kibana-plugin-public.coresetup.context.md) | Pick<ContextSetup, 'createContextContainer'> | [ContextSetup](./kibana-plugin-public.contextsetup.md) | +| [context](./kibana-plugin-public.coresetup.context.md) | ContextSetup | [ContextSetup](./kibana-plugin-public.contextsetup.md) | | [fatalErrors](./kibana-plugin-public.coresetup.fatalerrors.md) | FatalErrorsSetup | [FatalErrorsSetup](./kibana-plugin-public.fatalerrorssetup.md) | | [http](./kibana-plugin-public.coresetup.http.md) | HttpSetup | [HttpSetup](./kibana-plugin-public.httpsetup.md) | | [notifications](./kibana-plugin-public.coresetup.notifications.md) | NotificationsSetup | [NotificationsSetup](./kibana-plugin-public.notificationssetup.md) | diff --git a/docs/development/core/public/kibana-plugin-public.md b/docs/development/core/public/kibana-plugin-public.md index 15e300e689d2d6b..9b00a697f24241e 100644 --- a/docs/development/core/public/kibana-plugin-public.md +++ b/docs/development/core/public/kibana-plugin-public.md @@ -34,8 +34,8 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ChromeRecentlyAccessed](./kibana-plugin-public.chromerecentlyaccessed.md) | [APIs](./kibana-plugin-public.chromerecentlyaccessed.md) for recently accessed history. | | [ChromeRecentlyAccessedHistoryItem](./kibana-plugin-public.chromerecentlyaccessedhistoryitem.md) | | | [ChromeStart](./kibana-plugin-public.chromestart.md) | ChromeStart allows plugins to customize the global chrome header UI and enrich the UX with additional information about the current location of the browser. | -| [ContextContainer](./kibana-plugin-public.contextcontainer.md) | An object that handles registration of context providers and building of new context objects. | -| [ContextSetup](./kibana-plugin-public.contextsetup.md) | An object that handles registration of context providers and building of new context objects. | +| [ContextContainer](./kibana-plugin-public.contextcontainer.md) | An object that handles registration of context providers and configuring handlers with context. | +| [ContextSetup](./kibana-plugin-public.contextsetup.md) | An object that handles registration of context providers and configuring handlers with context. | | [CoreSetup](./kibana-plugin-public.coresetup.md) | Core services exposed to the Plugin setup lifecycle | | [CoreStart](./kibana-plugin-public.corestart.md) | Core services exposed to the Plugin start lifecycle | | [DocLinksStart](./kibana-plugin-public.doclinksstart.md) | | diff --git a/src/core/public/context/context.mock.ts b/src/core/public/context/context.mock.ts index 3ff6ea280276345..2cbb39c67421b31 100644 --- a/src/core/public/context/context.mock.ts +++ b/src/core/public/context/context.mock.ts @@ -27,7 +27,6 @@ const createContextMock = () => { const contextMock: ContextContainerMock = { registerContext: jest.fn(), createHandler: jest.fn(), - setCurrentPlugin: jest.fn(), }; return contextMock; }; diff --git a/src/core/public/context/context.test.ts b/src/core/public/context/context.test.ts index 8027acd7aa19608..03f11b24d6fd3ef 100644 --- a/src/core/public/context/context.test.ts +++ b/src/core/public/context/context.test.ts @@ -37,13 +37,15 @@ interface MyContext { baseCtx: number; } +const coreId = Symbol(); + describe('ContextContainer', () => { it('does not allow the same context to be registered twice', () => { - const contextContainer = new ContextContainerImplementation(plugins); - contextContainer.registerContext('ctxFromA', () => 'aString'); + const contextContainer = new ContextContainerImplementation(plugins, coreId); + contextContainer.registerContext(coreId, 'ctxFromA', () => 'aString'); expect(() => - contextContainer.registerContext('ctxFromA', () => 'aString') + contextContainer.registerContext(coreId, 'ctxFromA', () => 'aString') ).toThrowErrorMatchingInlineSnapshot( `"Context provider for ctxFromA has already been registered."` ); @@ -51,43 +53,38 @@ describe('ContextContainer', () => { describe('context building', () => { it('resolves dependencies', async () => { - const contextContainer = new ContextContainerImplementation(plugins); + const contextContainer = new ContextContainerImplementation( + plugins, + coreId + ); expect.assertions(8); - contextContainer.registerContext('core1', context => { + contextContainer.registerContext(coreId, 'core1', context => { expect(context).toEqual({}); return 'core'; }); - contextContainer.setCurrentPlugin('pluginA'); - contextContainer.registerContext('ctxFromA', context => { + contextContainer.registerContext('pluginA', 'ctxFromA', context => { expect(context).toEqual({ core1: 'core' }); return 'aString'; }); - contextContainer.setCurrentPlugin('pluginB'); - contextContainer.registerContext('ctxFromB', context => { + contextContainer.registerContext('pluginB', 'ctxFromB', context => { expect(context).toEqual({ core1: 'core', ctxFromA: 'aString' }); return 299; }); - contextContainer.setCurrentPlugin('pluginC'); - contextContainer.registerContext('ctxFromC', context => { + contextContainer.registerContext('pluginC', 'ctxFromC', context => { expect(context).toEqual({ core1: 'core', ctxFromA: 'aString', ctxFromB: 299 }); return false; }); - contextContainer.setCurrentPlugin('pluginD'); - contextContainer.registerContext('ctxFromD', context => { + contextContainer.registerContext('pluginD', 'ctxFromD', context => { expect(context).toEqual({ core1: 'core' }); return {}; }); - contextContainer.setCurrentPlugin('pluginC'); const rawHandler1 = jest.fn(() => 'handler1'); - const handler1 = contextContainer.createHandler(rawHandler1); + const handler1 = contextContainer.createHandler('pluginC', rawHandler1); - contextContainer.setCurrentPlugin('pluginD'); const rawHandler2 = jest.fn(() => 'handler2'); - const handler2 = contextContainer.createHandler(rawHandler2); - - contextContainer.setCurrentPlugin(undefined); + const handler2 = contextContainer.createHandler('pluginD', rawHandler2); await handler1(); await handler2(); @@ -107,23 +104,25 @@ describe('ContextContainer', () => { }); }); - it('exposes all previously registered context to Core providers', async () => { + it('exposes all core context to core providers', async () => { expect.assertions(4); - const contextContainer = new ContextContainerImplementation(plugins); + const contextContainer = new ContextContainerImplementation( + plugins, + coreId + ); contextContainer - .registerContext('core1', context => { + .registerContext(coreId, 'core1', context => { expect(context).toEqual({}); return 'core'; }) - .registerContext('core2', context => { + .registerContext(coreId, 'core2', context => { expect(context).toEqual({ core1: 'core' }); return 101; }); - contextContainer.setCurrentPlugin('pluginA'); const rawHandler1 = jest.fn(() => 'handler1'); - const handler1 = contextContainer.createHandler(rawHandler1); + const handler1 = contextContainer.createHandler('pluginA', rawHandler1); expect(await handler1()).toEqual('handler1'); @@ -134,22 +133,41 @@ describe('ContextContainer', () => { }); }); + it('does not expose plugin contexts to core handler', async () => { + const contextContainer = new ContextContainerImplementation( + plugins, + coreId + ); + + contextContainer + .registerContext(coreId, 'core1', context => 'core') + .registerContext('pluginA', 'ctxFromA', context => 'aString'); + + const rawHandler1 = jest.fn(() => 'handler1'); + const handler1 = contextContainer.createHandler(coreId, rawHandler1); + + expect(await handler1()).toEqual('handler1'); + // pluginA context should not be present in a core handler + expect(rawHandler1).toHaveBeenCalledWith({ + core1: 'core', + }); + }); + it('passes additional arguments to providers', async () => { expect.assertions(6); const contextContainer = new ContextContainerImplementation< MyContext, string, [string, number] - >(plugins); + >(plugins, coreId); - contextContainer.registerContext('core1', (context, str, num) => { + contextContainer.registerContext(coreId, 'core1', (context, str, num) => { expect(str).toEqual('passed string'); expect(num).toEqual(77); return `core ${str}`; }); - contextContainer.setCurrentPlugin('pluginD'); - contextContainer.registerContext('ctxFromD', (context, str, num) => { + contextContainer.registerContext('pluginD', 'ctxFromD', (context, str, num) => { expect(str).toEqual('passed string'); expect(num).toEqual(77); return { @@ -158,7 +176,7 @@ describe('ContextContainer', () => { }); const rawHandler1 = jest.fn(() => 'handler1'); - const handler1 = contextContainer.createHandler(rawHandler1); + const handler1 = contextContainer.createHandler('pluginD', rawHandler1); expect(await handler1('passed string', 77)).toEqual('handler1'); @@ -176,20 +194,24 @@ describe('ContextContainer', () => { }); describe('createHandler', () => { - it('throws error if registered outside plugin', async () => { - const contextContainer = new ContextContainerImplementation(plugins); - contextContainer.setCurrentPlugin(undefined); + it('throws error if called with a different symbol', async () => { + const contextContainer = new ContextContainerImplementation( + plugins, + coreId + ); await expect(() => - contextContainer.createHandler(jest.fn()) - ).toThrowErrorMatchingInlineSnapshot(`"Cannot create handlers outside a plugin!"`); + contextContainer.createHandler(Symbol(), jest.fn()) + ).toThrowErrorMatchingInlineSnapshot(`"Symbol only allowed for core services"`); }); it('returns value from original handler', async () => { - const contextContainer = new ContextContainerImplementation(plugins); + const contextContainer = new ContextContainerImplementation( + plugins, + coreId + ); - contextContainer.setCurrentPlugin('pluginA'); const rawHandler1 = jest.fn(() => 'handler1'); - const handler1 = contextContainer.createHandler(rawHandler1); + const handler1 = contextContainer.createHandler('pluginA', rawHandler1); expect(await handler1()).toEqual('handler1'); }); @@ -199,11 +221,10 @@ describe('ContextContainer', () => { MyContext, string, [string, number] - >(plugins); + >(plugins, coreId); - contextContainer.setCurrentPlugin('pluginA'); const rawHandler1 = jest.fn(() => 'handler1'); - const handler1 = contextContainer.createHandler(rawHandler1); + const handler1 = contextContainer.createHandler('pluginA', rawHandler1); await handler1('passed string', 77); expect(rawHandler1).toHaveBeenCalledWith({}, 'passed string', 77); diff --git a/src/core/public/context/context.ts b/src/core/public/context/context.ts index 73f8de89f429cc8..584142a99cd5ae1 100644 --- a/src/core/public/context/context.ts +++ b/src/core/public/context/context.ts @@ -20,6 +20,7 @@ import { flatten } from 'lodash'; import { PluginName } from '../../server'; import { pick } from '../../utils'; +import { CoreId } from '../core_system'; /** * A function that returns a context value for a specific key of given context type. @@ -58,20 +59,22 @@ export type Handler = T extends Promise ? Promise : Promise; /** - * An object that handles registration of context providers and building of new context objects. + * An object that handles registration of context providers and configuring handlers with context. * * @remarks - * A {@link ContextContainer} can be used by any Core service or plugin (known as the "service owner") which wishes to expose - * APIs in a handler function. The container object will manage registering context providers and building a context - * object for a handler with all of the contexts that should be exposed to the handler's plugin. This is dependent on - * the dependencies that the handler's plugin declares. + * A {@link ContextContainer} can be used by any Core service or plugin (known as the "service owner") which wishes to + * expose APIs in a handler function. The container object will manage registering context providers and configuring a + * handler with all of the contexts that should be exposed to the handler's plugin. This is dependent on the + * dependencies that the handler's plugin declares. * * Contexts providers are executed in the order they were registered. Each provider gets access to context values * provided by any plugins that it depends on. * - * In order to configure a handler with context, you must call the {@link ContextContainer.createHandler} function. This - * function must be called _while the calling plugin's lifecycle method is still running_ or else you risk configuring - * the handler for the wrong plugin, or not plugin at all (the latter will throw an error). + * In order to configure a handler with context, you must call the {@link ContextContainer.createHandler} function and + * use the returned handler which will automatically build a context object when called. + * + * When registering context or creating handlers, the _calling plugin's id_ must be provided. Note this should NOT be + * the context service owner, but the plugin that is actually registering the context or handler. * * ```ts * // GOOD @@ -81,10 +84,13 @@ type Promisify = T extends Promise ? Promise : Promise; * setup(core) { * this.contextContainer = core.context.createContextContainer(); * return { - * registerRoute(path, handler) { + * registerContext(plugin, contextName, provider) { + * this.contextContainer.registerContext(plugin, contextName, provider); + * }, + * registerRoute(plugin, path, handler) { * this.handlers.set( * path, - * this.contextContainer.createHandler(handler) + * this.contextContainer.createHandler(plugin, handler) * ); * } * } @@ -98,32 +104,15 @@ type Promisify = T extends Promise ? Promise : Promise; * setup(core) { * this.contextContainer = core.context.createContextContainer(); * return { - * // When the promise isn't returned, it's possible `createHandler` won't be called until after the lifecycle - * // hook is completed. - * registerRoute(path, handler) { - * doAsyncThing().then(() => this.handlers.set( - * path, - * this.contextContainer.createHandler(handler) - * )); - * } - * } - * } - * } - * - * // ALSO GOOD - * class MyPlugin { - * private readonly handlers = new Map(); - * - * setup(core) { - * this.contextContainer = core.context.createContextContainer(); - * return { - * // Returning a Promise also works, but only if calling plugins wait for it to resolve before returning from - * // their lifecycle hooks. - * async registerRoute(path, handler) { - * await doAsyncThing(); + * registerContext(plugin, contextName, provider) { + * // This would leak this context to all handlers rather tha only plugins that depend on the calling plugin. + * this.contextContainer.registerContext('my_plugin', contextName, provider); + * }, + * registerRoute(plugin, path, handler) { * this.handlers.set( * path, - * this.contextContainer.createHandler(handler) + * // the handler will not receive any contexts provided by other dependencies of the calling plugin. + * this.contextContainer.createHandler('my_plugin', handler) * ); * } * } @@ -131,7 +120,6 @@ type Promisify = T extends Promise ? Promise : Promise; * } * ``` * - * * @public */ export interface ContextContainer< @@ -140,14 +128,16 @@ export interface ContextContainer< THandlerParameters extends any[] = [] > { /** - * Register a new context provider. Throws an exception if more than one provider is registered for the same context - * key. + * Register a new context provider. Throws an exception if more than one provider is registered for the same + * `contextName`. * + * @param plugin - The plugin ID for the plugin that registers this context. * @param contextName - The key of the `TContext` object this provider supplies the value for. * @param provider - A {@link ContextProvider} to be called each time a new context is created. * @returns The `ContextContainer` for method chaining. */ registerContext( + plugin: string | CoreId, contextName: TContextName, provider: ContextProvider ): this; @@ -155,25 +145,25 @@ export interface ContextContainer< /** * Create a new handler function pre-wired to context for the plugin. * - * @remarks - * This must be called when the handler is registered by the consuming plugin. If this is called later in the - * lifecycle it will throw an exception. - * + * @param plugin - The plugin ID for the plugin that registers this context. * @param handler + * @returns A function that takes `THandlerParameters`, calls `handler` with a new context, and returns a Promise of + * the `handler` return value. */ createHandler( + plugin: string | CoreId, handler: Handler ): (...rest: THandlerParameters) => Promisify; } +type ContextSource = PluginName | CoreId; + /** @internal */ export class ContextContainerImplementation< TContext extends {}, THandlerReturn, THandlerParameters extends any[] = [] > implements ContextContainer { - private currentPlugin?: string; - /** * Used to map contexts to their providers and associated plugin. In registration order which is tightly coupled to * plugin load order. @@ -182,18 +172,24 @@ export class ContextContainerImplementation< keyof TContext, { provider: ContextProvider; - plugin?: PluginName; + source: ContextSource; } >(); /** Used to keep track of which plugins registered which contexts for dependency resolution. */ - private readonly contextNamesByPlugin = new Map>(); + private readonly contextNamesBySource: Map>; /** * @param pluginDependencies - A map of plugins to an array of their dependencies. */ - constructor(private readonly pluginDependencies: ReadonlyMap) {} + constructor( + private readonly pluginDependencies: ReadonlyMap, + private readonly coreId: CoreId + ) { + this.contextNamesBySource = new Map>([[coreId, []]]); + } public registerContext = ( + source: ContextSource, contextName: TContextName, provider: ContextProvider ): this => { @@ -201,72 +197,50 @@ export class ContextContainerImplementation< throw new Error(`Context provider for ${contextName} has already been registered.`); } - const plugin = this.currentPlugin; - this.contextProviders.set(contextName, { provider, plugin }); - - if (plugin) { - this.contextNamesByPlugin.set(plugin, [ - ...(this.contextNamesByPlugin.get(plugin) || []), - contextName, - ]); + if (typeof source === 'symbol' && source !== this.coreId) { + throw new Error(`Symbol only allowed for core services`); } + this.contextProviders.set(contextName, { provider, source }); + this.contextNamesBySource.set(source, [ + ...(this.contextNamesBySource.get(source) || []), + contextName, + ]); + return this; }; - public createHandler = (handler: Handler) => { - const plugin = this.currentPlugin; - if (!plugin) { - throw new Error(`Cannot create handlers outside a plugin!`); - } else if (!this.pluginDependencies.has(plugin)) { - throw new Error(`Cannot create handler for unknown plugin: ${plugin}`); + public createHandler = ( + source: ContextSource, + handler: Handler + ) => { + if (typeof source === 'symbol' && source !== this.coreId) { + throw new Error(`Symbol only allowed for core services`); + } else if (typeof source === 'string' && !this.pluginDependencies.has(source)) { + throw new Error(`Cannot create handler for unknown plugin: ${source}`); } return (async (...args: THandlerParameters) => { - const context = await this.buildContext(plugin, {}, ...args); + const context = await this.buildContext(source, {}, ...args); return handler(context, ...args); }) as (...args: THandlerParameters) => Promisify; }; private async buildContext( - pluginName: PluginName, + source: ContextSource, baseContext: Partial = {}, ...contextArgs: THandlerParameters ): Promise { - const ownerContextNames = [...this.contextProviders] - .filter(([name, { plugin }]) => plugin === undefined) - .map(([name]) => name); - const contextNamesForPlugin = (plug: PluginName): Set => { - const pluginDeps = this.pluginDependencies.get(plug); - if (!pluginDeps) { - // This should be impossible, but just in case. - throw new Error(`Cannot create context for unknown plugin: ${pluginName}`); - } - - return new Set([ - // Owner contexts - ...ownerContextNames, - // Contexts calling plugin created - ...(this.contextNamesByPlugin.get(pluginName) || []), - // Contexts calling plugin's dependencies created - ...flatten(pluginDeps.map(p => this.contextNamesByPlugin.get(p) || [])), - ]); - }; - - const contextsToBuild = contextNamesForPlugin(pluginName); + const contextsToBuild = new Set(this.contextNamesForSource(source)); return [...this.contextProviders] .filter(([contextName]) => contextsToBuild.has(contextName)) .reduce( - async (contextPromise, [contextName, { provider, plugin }]) => { + async (contextPromise, [contextName, { provider, source: providerSource }]) => { const resolvedContext = await contextPromise; - // If the provider is not from a plugin, give access to the entire - // context built so far (this is only possible for providers registered - // by the service owner). - const exposedContext = plugin - ? pick(resolvedContext, [...contextNamesForPlugin(plugin)]) - : resolvedContext; + // For the next provider, only expose the context available based on the dependencies. + const exposedContext = pick(resolvedContext, this.contextNamesForSource(providerSource)); return { ...resolvedContext, @@ -277,8 +251,31 @@ export class ContextContainerImplementation< ); } - /** @internal */ - public setCurrentPlugin(plugin?: string) { - this.currentPlugin = plugin; + private contextNamesForSource(source: ContextSource): Array { + // If the source is core... + if (typeof source === 'symbol') { + if (source !== this.coreId) { + // This case should never be hit. + throw new Error(`Cannot build context for symbol`); + } + + return this.contextNamesBySource.get(this.coreId)!; + } + + // If the source is a plugin... + const pluginDeps = this.pluginDependencies.get(source); + if (!pluginDeps) { + // This case should never be hit. + throw new Error(`Cannot create context for unknown plugin: ${source}`); + } + + return [ + // Core contexts + ...this.contextNamesBySource.get(this.coreId)!, + // Contexts source created + ...(this.contextNamesBySource.get(source) || []), + // Contexts sources's dependencies created + ...flatten(pluginDeps.map(p => this.contextNamesBySource.get(p) || [])), + ]; } } diff --git a/src/core/public/context/context_service.mock.ts b/src/core/public/context/context_service.mock.ts index fae14cd1ccae6c3..289732247b37995 100644 --- a/src/core/public/context/context_service.mock.ts +++ b/src/core/public/context/context_service.mock.ts @@ -17,37 +17,26 @@ * under the License. */ -import { ContextStart, ContextService, ContextSetup } from './context_service'; +import { ContextService, ContextSetup } from './context_service'; import { contextMock } from './context.mock'; const createSetupContractMock = () => { const setupContract: jest.Mocked = { createContextContainer: jest.fn().mockImplementation(() => contextMock.create()), - setCurrentPlugin: jest.fn(), }; return setupContract; }; -const createStartContractMock = () => { - const startContract: jest.Mocked = { - setCurrentPlugin: jest.fn(), - }; - return startContract; -}; - type ContextServiceContract = PublicMethodsOf; const createMock = () => { const mocked: jest.Mocked = { setup: jest.fn(), - start: jest.fn(), }; mocked.setup.mockReturnValue(createSetupContractMock()); - mocked.start.mockReturnValue(createStartContractMock()); return mocked; }; export const contextServiceMock = { create: createMock, createSetupContract: createSetupContractMock, - createStartContract: createStartContractMock, }; diff --git a/src/core/public/context/context_service.test.ts b/src/core/public/context/context_service.test.ts index 467341083279a21..98fb3848eefcd75 100644 --- a/src/core/public/context/context_service.test.ts +++ b/src/core/public/context/context_service.test.ts @@ -21,7 +21,6 @@ import { MockContextConstructor } from './context_service.test.mocks'; import { ContextService } from './context_service'; -import { ContextContainerMock } from './context.mock'; const pluginDependencies = new Map([ ['pluginA', []], @@ -32,73 +31,11 @@ const pluginDependencies = new Map([ describe('ContextService', () => { describe('#setup()', () => { test('createContextContainer returns a new container configured with pluginDependencies', () => { - const service = new ContextService(); + const coreId = Symbol(); + const service = new ContextService({ coreId }); const setup = service.setup({ pluginDependencies }); expect(setup.createContextContainer()).toBeDefined(); - expect(MockContextConstructor).toHaveBeenCalledWith(pluginDependencies); - }); - - test('setCurrentPlugin does not fail if there are no contianers', () => { - const service = new ContextService(); - const setup = service.setup({ pluginDependencies }); - expect(() => setup.setCurrentPlugin('pluginA')).not.toThrow(); - }); - - test('setCurrentPlugin calls on all context containers', () => { - const service = new ContextService(); - const setup = service.setup({ pluginDependencies }); - const container1 = setup.createContextContainer() as ContextContainerMock; - const container2 = setup.createContextContainer() as ContextContainerMock; - const container3 = setup.createContextContainer() as ContextContainerMock; - - setup.setCurrentPlugin('pluginA'); - expect(container1.setCurrentPlugin).toHaveBeenCalledWith('pluginA'); - expect(container2.setCurrentPlugin).toHaveBeenCalledWith('pluginA'); - expect(container3.setCurrentPlugin).toHaveBeenCalledWith('pluginA'); - - setup.setCurrentPlugin('pluginB'); - expect(container1.setCurrentPlugin).toHaveBeenCalledWith('pluginB'); - expect(container2.setCurrentPlugin).toHaveBeenCalledWith('pluginB'); - expect(container3.setCurrentPlugin).toHaveBeenCalledWith('pluginB'); - - setup.setCurrentPlugin(undefined); - expect(container1.setCurrentPlugin).toHaveBeenCalledWith(undefined); - expect(container2.setCurrentPlugin).toHaveBeenCalledWith(undefined); - expect(container3.setCurrentPlugin).toHaveBeenCalledWith(undefined); - }); - }); - - describe('#start()', () => { - test('setCurrentPlugin does not fail if there are no contianers', () => { - const service = new ContextService(); - service.setup({ pluginDependencies }); - const start = service.start(); - expect(() => start.setCurrentPlugin('pluginA')).not.toThrow(); - }); - - test('setCurrentPlugin calls on all context containers', () => { - const service = new ContextService(); - const setup = service.setup({ pluginDependencies }); - const container1 = setup.createContextContainer() as ContextContainerMock; - const container2 = setup.createContextContainer() as ContextContainerMock; - const container3 = setup.createContextContainer() as ContextContainerMock; - - const start = service.start(); - - start.setCurrentPlugin('pluginA'); - expect(container1.setCurrentPlugin).toHaveBeenCalledWith('pluginA'); - expect(container2.setCurrentPlugin).toHaveBeenCalledWith('pluginA'); - expect(container3.setCurrentPlugin).toHaveBeenCalledWith('pluginA'); - - start.setCurrentPlugin('pluginB'); - expect(container1.setCurrentPlugin).toHaveBeenCalledWith('pluginB'); - expect(container2.setCurrentPlugin).toHaveBeenCalledWith('pluginB'); - expect(container3.setCurrentPlugin).toHaveBeenCalledWith('pluginB'); - - start.setCurrentPlugin(undefined); - expect(container1.setCurrentPlugin).toHaveBeenCalledWith(undefined); - expect(container2.setCurrentPlugin).toHaveBeenCalledWith(undefined); - expect(container3.setCurrentPlugin).toHaveBeenCalledWith(undefined); + expect(MockContextConstructor).toHaveBeenCalledWith(pluginDependencies, coreId); }); }); }); diff --git a/src/core/public/context/context_service.ts b/src/core/public/context/context_service.ts index ac75a1593d90244..940e95c67527914 100644 --- a/src/core/public/context/context_service.ts +++ b/src/core/public/context/context_service.ts @@ -18,6 +18,7 @@ */ import { ContextContainer, ContextContainerImplementation } from './context'; +import { CoreContext } from '../core_system'; interface StartDeps { pluginDependencies: ReadonlyMap; @@ -25,37 +26,21 @@ interface StartDeps { /** @internal */ export class ContextService { - private readonly containers = new Set>(); + constructor(private readonly core: CoreContext) {} public setup({ pluginDependencies }: StartDeps): ContextSetup { return { - setCurrentPlugin: this.setCurrentPlugin.bind(this), createContextContainer: < TContext extends {}, THandlerReturn, THandlerParameters extends any[] = [] - >() => { - const newContainer = new ContextContainerImplementation< - TContext, - THandlerReturn, - THandlerParameters - >(pluginDependencies); - - this.containers.add(newContainer); - return newContainer; - }, - }; - } - - public start(): ContextStart { - return { - setCurrentPlugin: this.setCurrentPlugin.bind(this), + >() => + new ContextContainerImplementation( + pluginDependencies, + this.core.coreId + ), }; } - - private setCurrentPlugin(plugin?: string) { - [...this.containers].forEach(container => container.setCurrentPlugin(plugin)); - } } /** @@ -86,27 +71,27 @@ export class ContextService { * >(); * * return { - * registerContext: this.contextContainer.register, - * registerVizRenderer: (renderMethod: string, renderer: VizTypeRenderer) => - * // `createHandler` must be called immediately during the calling plugin's lifecycle method. - * this.vizRenderers.set(renderMethod, this.contextContainer.createHandler(renderer)), + * registerContext: this.contextContainer.registerContext, + * registerVizRenderer: (plugin: string, renderMethod: string, renderer: VizTypeRenderer) => + * this.vizRenderers.set(renderMethod, this.contextContainer.createHandler(plugin, renderer)), * }; * } * * start(core) { - * // Register the core context available to all renderers - * this.contextContainer.register('core', () => ({ + * // Register the core context available to all renderers. Use the VizRendererContext's pluginId as the first arg. + * this.contextContainer.registerContext('viz_rendering', 'core', () => ({ * i18n: core.i18n, * uiSettings: core.uiSettings * })); * * return { - * registerContext: this.contextContainer.register, + * registerContext: this.contextContainer.registerContext, + * * // The handler can now be called directly with only an `HTMLElement` and will automaticallly * // have the `context` argument supplied. * renderVizualization: (renderMethod: string, domElement: HTMLElement) => { * if (!this.vizRenderer.has(renderMethod)) { - * throw new Error(`Render method ${renderMethod} has not be registered`); + * throw new Error(`Render method '${renderMethod}' has not been registered`); * } * * return this.vizRenderers.get(renderMethod)(domElement); @@ -119,12 +104,6 @@ export class ContextService { * @public */ export interface ContextSetup { - /** - * Must be called by the PluginsService during each plugin's lifecycle methods. - * @internal - */ - setCurrentPlugin(plugin?: string): void; - /** * Creates a new {@link ContextContainer} for a service owner. */ @@ -134,11 +113,3 @@ export interface ContextSetup { THandlerParmaters extends any[] = [] >(): ContextContainer; } - -/** @internal */ -export interface ContextStart { - /** - * @internal - */ - setCurrentPlugin(plugin?: string): void; -} diff --git a/src/core/public/context/index.ts b/src/core/public/context/index.ts index 270d8563cad8e08..81dcf1956f4fb3e 100644 --- a/src/core/public/context/index.ts +++ b/src/core/public/context/index.ts @@ -17,5 +17,5 @@ * under the License. */ -export { ContextService, ContextSetup, ContextStart } from './context_service'; +export { ContextService, ContextSetup } from './context_service'; export { ContextContainer, ContextProvider, Handler } from './context'; diff --git a/src/core/public/core_system.test.ts b/src/core/public/core_system.test.ts index ca26b6fe45dbadf..76c1df4e2f5800b 100644 --- a/src/core/public/core_system.test.ts +++ b/src/core/public/core_system.test.ts @@ -161,6 +161,11 @@ describe('#setup()', () => { expect(MockApplicationService.setup).toHaveBeenCalledTimes(1); }); + it('calls context#setup()', async () => { + await setupCore(); + expect(MockContextService.setup).toHaveBeenCalledTimes(1); + }); + it('calls injectedMetadata#setup()', async () => { await setupCore(); expect(MockInjectedMetadataService.setup).toHaveBeenCalledTimes(1); @@ -217,11 +222,6 @@ describe('#start()', () => { expect(MockApplicationService.start).toHaveBeenCalledTimes(1); }); - it('calls context#start()', async () => { - await startCore(); - expect(MockContextService.start).toHaveBeenCalledTimes(1); - }); - it('calls docLinks#start()', async () => { await startCore(); expect(MockDocLinksService.start).toHaveBeenCalledTimes(1); diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index d8de15d73b1cc43..7a4c2868041bc6a 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -45,8 +45,12 @@ interface Params { } /** @internal */ -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface CoreContext {} +export type CoreId = symbol; + +/** @internal */ +export interface CoreContext { + coreId: CoreId; +} /** * The CoreSystem is the root of the new platform, and setups all parts @@ -105,9 +109,9 @@ export class CoreSystem { this.chrome = new ChromeService({ browserSupportsCsp }); this.docLinks = new DocLinksService(); this.rendering = new RenderingService(); - this.context = new ContextService(); - const core: CoreContext = {}; + const core: CoreContext = { coreId: Symbol('core') }; + this.context = new ContextService(core); this.plugins = new PluginsService(core); this.legacyPlatform = new LegacyPlatformService({ @@ -166,7 +170,6 @@ export class CoreSystem { const http = await this.http.start({ injectedMetadata, fatalErrors: this.fatalErrorsSetup }); const i18n = await this.i18n.start(); const application = await this.application.start({ injectedMetadata }); - const context = await this.context.start(); const coreUiTargetDomElement = document.createElement('div'); coreUiTargetDomElement.id = 'kibana-body'; @@ -198,7 +201,6 @@ export class CoreSystem { const core: InternalCoreStart = { application, chrome, - context, docLinks, http, i18n, diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 66b69bbc289778e..dc0ff3981bc36dd 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -66,7 +66,7 @@ import { Plugin, PluginInitializer, PluginInitializerContext } from './plugins'; import { UiSettingsClient, UiSettingsState, UiSettingsClientContract } from './ui_settings'; import { ApplicationSetup, Capabilities, ApplicationStart } from './application'; import { DocLinksStart } from './doc_links'; -import { ContextContainer, ContextProvider, ContextSetup, ContextStart, Handler } from './context'; +import { ContextContainer, ContextProvider, ContextSetup, Handler } from './context'; export { CoreContext, CoreSystem } from './core_system'; export { RecursiveReadonly } from '../utils'; @@ -82,7 +82,7 @@ export { RecursiveReadonly } from '../utils'; */ export interface CoreSetup { /** {@link ContextSetup} */ - context: Pick; + context: ContextSetup; /** {@link FatalErrorsSetup} */ fatalErrors: FatalErrorsSetup; /** {@link HttpSetup} */ @@ -124,14 +124,12 @@ export interface CoreStart { /** @internal */ export interface InternalCoreSetup extends CoreSetup { application: ApplicationSetup; - context: ContextSetup; injectedMetadata: InjectedMetadataSetup; } /** @internal */ export interface InternalCoreStart extends CoreStart { application: ApplicationStart; - context: ContextStart; injectedMetadata: InjectedMetadataStart; } diff --git a/src/core/public/legacy/legacy_service.test.ts b/src/core/public/legacy/legacy_service.test.ts index 76a05f8d03ae712..5f1c4e1cf6bf99e 100644 --- a/src/core/public/legacy/legacy_service.test.ts +++ b/src/core/public/legacy/legacy_service.test.ts @@ -88,7 +88,6 @@ const defaultSetupDeps = { }; const applicationStart = applicationServiceMock.createStartContract(); -const contextStart = contextServiceMock.createStartContract(); const docLinksStart = docLinksServiceMock.createStartContract(); const httpStart = httpServiceMock.createStartContract(); const chromeStart = chromeServiceMock.createStartContract(); @@ -101,7 +100,6 @@ const uiSettingsStart = uiSettingsServiceMock.createStartContract(); const defaultStartDeps = { core: { application: applicationStart, - context: contextStart, docLinks: docLinksStart, http: httpStart, chrome: chromeStart, diff --git a/src/core/public/plugins/plugins_service.test.ts b/src/core/public/plugins/plugins_service.test.ts index ef8ecd9348ca819..bea23c51db4e5e4 100644 --- a/src/core/public/plugins/plugins_service.test.ts +++ b/src/core/public/plugins/plugins_service.test.ts @@ -53,7 +53,7 @@ mockPluginInitializerProvider.mockImplementation( type DeeplyMocked = { [P in keyof T]: jest.Mocked }; -const mockCoreContext: CoreContext = {}; +const mockCoreContext: CoreContext = { coreId: Symbol() }; let mockSetupDeps: DeeplyMocked; let mockSetupContext: DeeplyMocked; let mockStartDeps: DeeplyMocked; @@ -86,7 +86,6 @@ beforeEach(() => { }; mockStartDeps = { application: applicationServiceMock.createStartContract(), - context: contextServiceMock.createStartContract(), docLinks: docLinksServiceMock.createStartContract(), http: httpServiceMock.createStartContract(), chrome: chromeServiceMock.createStartContract(), @@ -97,7 +96,7 @@ beforeEach(() => { uiSettings: uiSettingsServiceMock.createStartContract(), }; mockStartContext = { - ...omit(mockStartDeps, 'context', 'injectedMetadata'), + ...omit(mockStartDeps, 'injectedMetadata'), application: { capabilities: mockStartDeps.application.capabilities, }, @@ -181,13 +180,13 @@ test('`PluginsService.setup` calls loadPluginBundles with http and plugins', asy expect(mockLoadPluginBundle).toHaveBeenCalledWith(mockSetupDeps.http.basePath.prepend, 'pluginC'); }); -test('`PluginsService.setup` initalizes plugins with CoreContext', async () => { +test('`PluginsService.setup` initalizes plugins with PluginIntitializerContext', async () => { const pluginsService = new PluginsService(mockCoreContext); await pluginsService.setup(mockSetupDeps); - expect(mockPluginInitializers.get('pluginA')).toHaveBeenCalledWith(mockCoreContext); - expect(mockPluginInitializers.get('pluginB')).toHaveBeenCalledWith(mockCoreContext); - expect(mockPluginInitializers.get('pluginC')).toHaveBeenCalledWith(mockCoreContext); + expect(mockPluginInitializers.get('pluginA')).toHaveBeenCalledWith({}); + expect(mockPluginInitializers.get('pluginB')).toHaveBeenCalledWith({}); + expect(mockPluginInitializers.get('pluginC')).toHaveBeenCalledWith({}); }); test('`PluginsService.setup` exposes dependent setup contracts to plugins', async () => { diff --git a/src/core/public/plugins/plugins_service.ts b/src/core/public/plugins/plugins_service.ts index 94ba4ce9bc9e858..b192d3300175c87 100644 --- a/src/core/public/plugins/plugins_service.ts +++ b/src/core/public/plugins/plugins_service.ts @@ -78,7 +78,7 @@ export class PluginsService implements CoreService this.plugins.set( id, - new PluginWrapper(plugin, createPluginInitializerContext(deps, plugin)) + new PluginWrapper(plugin, createPluginInitializerContext(this.coreContext, plugin)) ) ); @@ -88,9 +88,6 @@ export class PluginsService implements CoreService(); for (const [pluginName, plugin] of this.plugins.entries()) { - // Set global context variable for current plugin setting up - deps.context.setCurrentPlugin(pluginName); - const pluginDepContracts = [...this.pluginDependencies.get(pluginName)!].reduce( (depContracts, dependencyName) => { // Only set if present. Could be absent if plugin does not have client-side code or is a @@ -115,9 +112,6 @@ export class PluginsService implements CoreService(); for (const [pluginName, plugin] of this.plugins.entries()) { - // Set global context variable for current plugin setting up - deps.context.setCurrentPlugin(pluginName); - const pluginDepContracts = [...this.pluginDependencies.get(pluginName)!].reduce( (depContracts, dependencyName) => { // Only set if present. Could be absent if plugin does not have client-side code or is a @@ -151,9 +142,6 @@ export class PluginsService implements CoreService { // Warning: (ae-forgotten-export) The symbol "Promisify" needs to be exported by the entry point index.d.ts - createHandler(handler: Handler): (...rest: THandlerParameters) => Promisify; - registerContext(contextName: TContextName, provider: ContextProvider): this; + createHandler(plugin: string | CoreId, handler: Handler): (...rest: THandlerParameters) => Promisify; + // Warning: (ae-forgotten-export) The symbol "CoreId" needs to be exported by the entry point index.d.ts + registerContext(plugin: string | CoreId, contextName: TContextName, provider: ContextProvider): this; } // @public @@ -179,18 +180,18 @@ export type ContextProvider(): ContextContainer; - // @internal - setCurrentPlugin(plugin?: string): void; } // @internal (undocumented) export interface CoreContext { + // (undocumented) + coreId: CoreId; } // @public export interface CoreSetup { // (undocumented) - context: Pick; + context: ContextSetup; // (undocumented) fatalErrors: FatalErrorsSetup; // (undocumented) @@ -426,8 +427,6 @@ export interface I18nStart { export interface InternalCoreSetup extends CoreSetup { // (undocumented) application: ApplicationSetup; - // (undocumented) - context: ContextSetup; // Warning: (ae-forgotten-export) The symbol "InjectedMetadataSetup" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -438,10 +437,6 @@ export interface InternalCoreSetup extends CoreSetup { export interface InternalCoreStart extends CoreStart { // (undocumented) application: ApplicationStart; - // Warning: (ae-forgotten-export) The symbol "ContextStart" needs to be exported by the entry point index.d.ts - // - // (undocumented) - context: ContextStart; // Warning: (ae-forgotten-export) The symbol "InjectedMetadataStart" needs to be exported by the entry point index.d.ts // // (undocumented)