* implements server-side getStartServices * add unit test * add integration test * update generated doc * improve test
This commit is contained in:
parent
c4d088f58e
commit
8fba124795
|
@ -0,0 +1,17 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreSetup](./kibana-plugin-server.coresetup.md) > [getStartServices](./kibana-plugin-server.coresetup.getstartservices.md)
|
||||
|
||||
## CoreSetup.getStartServices() method
|
||||
|
||||
Allows plugins to get access to APIs available in start inside async handlers. Promise will not resolve until Core and plugin dependencies have completed `start`<!-- -->. This should only be used inside handlers registered during `setup` that will only be executed after `start` lifecycle.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
getStartServices(): Promise<[CoreStart, TPluginsStart]>;
|
||||
```
|
||||
<b>Returns:</b>
|
||||
|
||||
`Promise<[CoreStart, TPluginsStart]>`
|
||||
|
|
@ -1,26 +1,32 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreSetup](./kibana-plugin-server.coresetup.md)
|
||||
|
||||
## CoreSetup interface
|
||||
|
||||
Context passed to the plugins `setup` method.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface CoreSetup
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [capabilities](./kibana-plugin-server.coresetup.capabilities.md) | <code>CapabilitiesSetup</code> | [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) |
|
||||
| [context](./kibana-plugin-server.coresetup.context.md) | <code>ContextSetup</code> | [ContextSetup](./kibana-plugin-server.contextsetup.md) |
|
||||
| [elasticsearch](./kibana-plugin-server.coresetup.elasticsearch.md) | <code>ElasticsearchServiceSetup</code> | [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) |
|
||||
| [http](./kibana-plugin-server.coresetup.http.md) | <code>HttpServiceSetup</code> | [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) |
|
||||
| [savedObjects](./kibana-plugin-server.coresetup.savedobjects.md) | <code>SavedObjectsServiceSetup</code> | [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) |
|
||||
| [uiSettings](./kibana-plugin-server.coresetup.uisettings.md) | <code>UiSettingsServiceSetup</code> | [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) |
|
||||
| [uuid](./kibana-plugin-server.coresetup.uuid.md) | <code>UuidServiceSetup</code> | [UuidServiceSetup](./kibana-plugin-server.uuidservicesetup.md) |
|
||||
|
||||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreSetup](./kibana-plugin-server.coresetup.md)
|
||||
|
||||
## CoreSetup interface
|
||||
|
||||
Context passed to the plugins `setup` method.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface CoreSetup<TPluginsStart extends object = object>
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [capabilities](./kibana-plugin-server.coresetup.capabilities.md) | <code>CapabilitiesSetup</code> | [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) |
|
||||
| [context](./kibana-plugin-server.coresetup.context.md) | <code>ContextSetup</code> | [ContextSetup](./kibana-plugin-server.contextsetup.md) |
|
||||
| [elasticsearch](./kibana-plugin-server.coresetup.elasticsearch.md) | <code>ElasticsearchServiceSetup</code> | [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) |
|
||||
| [http](./kibana-plugin-server.coresetup.http.md) | <code>HttpServiceSetup</code> | [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) |
|
||||
| [savedObjects](./kibana-plugin-server.coresetup.savedobjects.md) | <code>SavedObjectsServiceSetup</code> | [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) |
|
||||
| [uiSettings](./kibana-plugin-server.coresetup.uisettings.md) | <code>UiSettingsServiceSetup</code> | [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) |
|
||||
| [uuid](./kibana-plugin-server.coresetup.uuid.md) | <code>UuidServiceSetup</code> | [UuidServiceSetup](./kibana-plugin-server.uuidservicesetup.md) |
|
||||
|
||||
## Methods
|
||||
|
||||
| Method | Description |
|
||||
| --- | --- |
|
||||
| [getStartServices()](./kibana-plugin-server.coresetup.getstartservices.md) | Allows plugins to get access to APIs available in start inside async handlers. Promise will not resolve until Core and plugin dependencies have completed <code>start</code>. This should only be used inside handlers registered during <code>setup</code> that will only be executed after <code>start</code> lifecycle. |
|
||||
|
||||
|
|
|
@ -283,7 +283,7 @@ export interface RequestHandlerContext {
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
export interface CoreSetup {
|
||||
export interface CoreSetup<TPluginsStart extends object = object> {
|
||||
/** {@link CapabilitiesSetup} */
|
||||
capabilities: CapabilitiesSetup;
|
||||
/** {@link ContextSetup} */
|
||||
|
@ -298,6 +298,13 @@ export interface CoreSetup {
|
|||
uiSettings: UiSettingsServiceSetup;
|
||||
/** {@link UuidServiceSetup} */
|
||||
uuid: UuidServiceSetup;
|
||||
/**
|
||||
* Allows plugins to get access to APIs available in start inside async handlers.
|
||||
* Promise will not resolve until Core and plugin dependencies have completed `start`.
|
||||
* This should only be used inside handlers registered during `setup` that will only be executed
|
||||
* after `start` lifecycle.
|
||||
*/
|
||||
getStartServices(): Promise<[CoreStart, TPluginsStart]>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -256,6 +256,12 @@ export class LegacyService implements CoreService {
|
|||
startDeps: LegacyServiceStartDeps,
|
||||
legacyPlugins: LegacyPlugins
|
||||
) {
|
||||
const coreStart: CoreStart = {
|
||||
capabilities: startDeps.core.capabilities,
|
||||
savedObjects: { getScopedClient: startDeps.core.savedObjects.getScopedClient },
|
||||
uiSettings: { asScopedToClient: startDeps.core.uiSettings.asScopedToClient },
|
||||
};
|
||||
|
||||
const coreSetup: CoreSetup = {
|
||||
capabilities: setupDeps.core.capabilities,
|
||||
context: setupDeps.core.context,
|
||||
|
@ -291,11 +297,7 @@ export class LegacyService implements CoreService {
|
|||
uuid: {
|
||||
getInstanceUuid: setupDeps.core.uuid.getInstanceUuid,
|
||||
},
|
||||
};
|
||||
const coreStart: CoreStart = {
|
||||
capabilities: startDeps.core.capabilities,
|
||||
savedObjects: { getScopedClient: startDeps.core.savedObjects.getScopedClient },
|
||||
uiSettings: { asScopedToClient: startDeps.core.uiSettings.asScopedToClient },
|
||||
getStartServices: () => Promise.resolve([coreStart, startDeps.plugins]),
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
|
|
|
@ -86,6 +86,8 @@ function pluginInitializerContextMock<T>(config: T = {} as T) {
|
|||
return mock;
|
||||
}
|
||||
|
||||
type CoreSetupMockType = MockedKeys<CoreSetup> & jest.Mocked<Pick<CoreSetup, 'getStartServices'>>;
|
||||
|
||||
function createCoreSetupMock() {
|
||||
const httpService = httpServiceMock.createSetupContract();
|
||||
const httpMock: jest.Mocked<CoreSetup['http']> = {
|
||||
|
@ -105,7 +107,7 @@ function createCoreSetupMock() {
|
|||
const uiSettingsMock = {
|
||||
register: uiSettingsServiceMock.createSetupContract().register,
|
||||
};
|
||||
const mock: MockedKeys<CoreSetup> = {
|
||||
const mock: CoreSetupMockType = {
|
||||
capabilities: capabilitiesServiceMock.createSetupContract(),
|
||||
context: contextServiceMock.createSetupContract(),
|
||||
elasticsearch: elasticsearchServiceMock.createSetup(),
|
||||
|
@ -113,6 +115,9 @@ function createCoreSetupMock() {
|
|||
savedObjects: savedObjectsServiceMock.createSetupContract(),
|
||||
uiSettings: uiSettingsMock,
|
||||
uuid: uuidServiceMock.createSetupContract(),
|
||||
getStartServices: jest
|
||||
.fn<Promise<[ReturnType<typeof createCoreStartMock>, object]>, []>()
|
||||
.mockResolvedValue([createCoreStartMock(), {}]),
|
||||
};
|
||||
|
||||
return mock;
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export const mockPackage = new Proxy(
|
||||
{ raw: { __dirname: '/tmp' } as any },
|
||||
{ get: (obj, prop) => obj.raw[prop] }
|
||||
);
|
||||
jest.mock('../../../../core/server/utils/package_json', () => ({ pkg: mockPackage }));
|
||||
|
||||
export const mockDiscover = jest.fn();
|
||||
jest.mock('../discovery/plugins_discovery', () => ({ discover: mockDiscover }));
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { mockPackage, mockDiscover } from './plugins_service.test.mocks';
|
||||
|
||||
import { join } from 'path';
|
||||
|
||||
import { PluginsService } from '../plugins_service';
|
||||
import { ConfigPath, ConfigService, Env } from '../../config';
|
||||
import { getEnvOptions } from '../../config/__mocks__/env';
|
||||
import { BehaviorSubject, from } from 'rxjs';
|
||||
import { rawConfigServiceMock } from '../../config/raw_config_service.mock';
|
||||
import { config } from '../plugins_config';
|
||||
import { loggingServiceMock } from '../../logging/logging_service.mock';
|
||||
import { coreMock } from '../../mocks';
|
||||
import { Plugin } from '../types';
|
||||
import { PluginWrapper } from '../plugin';
|
||||
|
||||
describe('PluginsService', () => {
|
||||
const logger = loggingServiceMock.create();
|
||||
let pluginsService: PluginsService;
|
||||
|
||||
const createPlugin = (
|
||||
id: string,
|
||||
{
|
||||
path = id,
|
||||
disabled = false,
|
||||
version = 'some-version',
|
||||
requiredPlugins = [],
|
||||
optionalPlugins = [],
|
||||
kibanaVersion = '7.0.0',
|
||||
configPath = [path],
|
||||
server = true,
|
||||
ui = true,
|
||||
}: {
|
||||
path?: string;
|
||||
disabled?: boolean;
|
||||
version?: string;
|
||||
requiredPlugins?: string[];
|
||||
optionalPlugins?: string[];
|
||||
kibanaVersion?: string;
|
||||
configPath?: ConfigPath;
|
||||
server?: boolean;
|
||||
ui?: boolean;
|
||||
}
|
||||
): PluginWrapper => {
|
||||
return new PluginWrapper({
|
||||
path,
|
||||
manifest: {
|
||||
id,
|
||||
version,
|
||||
configPath: `${configPath}${disabled ? '-disabled' : ''}`,
|
||||
kibanaVersion,
|
||||
requiredPlugins,
|
||||
optionalPlugins,
|
||||
server,
|
||||
ui,
|
||||
},
|
||||
opaqueId: Symbol(id),
|
||||
initializerContext: { logger } as any,
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
mockPackage.raw = {
|
||||
branch: 'feature-v1',
|
||||
version: 'v1',
|
||||
build: {
|
||||
distributable: true,
|
||||
number: 100,
|
||||
sha: 'feature-v1-build-sha',
|
||||
},
|
||||
};
|
||||
|
||||
const env = Env.createDefault(getEnvOptions());
|
||||
const config$ = new BehaviorSubject<Record<string, any>>({
|
||||
plugins: {
|
||||
initialize: true,
|
||||
},
|
||||
});
|
||||
const rawConfigService = rawConfigServiceMock.create({ rawConfig$: config$ });
|
||||
const configService = new ConfigService(rawConfigService, env, logger);
|
||||
await configService.setSchema(config.path, config.schema);
|
||||
|
||||
pluginsService = new PluginsService({
|
||||
coreId: Symbol('core'),
|
||||
env,
|
||||
logger,
|
||||
configService,
|
||||
});
|
||||
});
|
||||
|
||||
it("properly resolves `getStartServices` in plugin's lifecycle", async () => {
|
||||
expect.assertions(5);
|
||||
|
||||
const pluginPath = 'plugin-path';
|
||||
|
||||
mockDiscover.mockReturnValue({
|
||||
error$: from([]),
|
||||
plugin$: from([
|
||||
createPlugin('plugin-id', {
|
||||
path: pluginPath,
|
||||
configPath: 'path',
|
||||
}),
|
||||
]),
|
||||
});
|
||||
|
||||
let startDependenciesResolved = false;
|
||||
let contextFromStart: any = null;
|
||||
let contextFromStartService: any = null;
|
||||
|
||||
const pluginInitializer = () =>
|
||||
({
|
||||
setup: async (coreSetup, deps) => {
|
||||
coreSetup.getStartServices().then(([core, plugins]) => {
|
||||
startDependenciesResolved = true;
|
||||
contextFromStartService = { core, plugins };
|
||||
});
|
||||
},
|
||||
start: async (core, plugins) => {
|
||||
contextFromStart = { core, plugins };
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
expect(startDependenciesResolved).toBe(false);
|
||||
},
|
||||
} as Plugin);
|
||||
|
||||
jest.doMock(
|
||||
join(pluginPath, 'server'),
|
||||
() => ({
|
||||
plugin: pluginInitializer,
|
||||
}),
|
||||
{
|
||||
virtual: true,
|
||||
}
|
||||
);
|
||||
|
||||
await pluginsService.discover();
|
||||
|
||||
const setupDeps = coreMock.createInternalSetup();
|
||||
await pluginsService.setup(setupDeps);
|
||||
|
||||
expect(startDependenciesResolved).toBe(false);
|
||||
|
||||
const startDeps = coreMock.createInternalStart();
|
||||
await pluginsService.start(startDeps);
|
||||
|
||||
expect(startDependenciesResolved).toBe(true);
|
||||
expect(contextFromStart!.core).toEqual(contextFromStartService!.core);
|
||||
expect(contextFromStart!.plugins).toEqual(contextFromStartService!.plugins);
|
||||
});
|
||||
});
|
|
@ -237,6 +237,43 @@ test('`start` calls plugin.start with context and dependencies', async () => {
|
|||
expect(mockPluginInstance.start).toHaveBeenCalledWith(context, deps);
|
||||
});
|
||||
|
||||
test("`start` resolves `startDependencies` Promise after plugin's start", async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const manifest = createPluginManifest();
|
||||
const opaqueId = Symbol();
|
||||
const plugin = new PluginWrapper({
|
||||
path: 'plugin-with-initializer-path',
|
||||
manifest,
|
||||
opaqueId,
|
||||
initializerContext: createPluginInitializerContext(coreContext, opaqueId, manifest),
|
||||
});
|
||||
const startContext = { any: 'thing' } as any;
|
||||
const pluginDeps = { someDep: 'value' };
|
||||
|
||||
let startDependenciesResolved = false;
|
||||
|
||||
const mockPluginInstance = {
|
||||
setup: jest.fn(),
|
||||
start: async () => {
|
||||
// delay to ensure startDependencies is not resolved until after the plugin instance's start resolves.
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
expect(startDependenciesResolved).toBe(false);
|
||||
},
|
||||
};
|
||||
mockPluginInitializer.mockReturnValue(mockPluginInstance);
|
||||
|
||||
await plugin.setup({} as any, {} as any);
|
||||
|
||||
const startDependenciesCheck = plugin.startDependencies.then(resolvedStartDeps => {
|
||||
startDependenciesResolved = true;
|
||||
expect(resolvedStartDeps).toEqual([startContext, pluginDeps]);
|
||||
});
|
||||
|
||||
await plugin.start(startContext, pluginDeps);
|
||||
await startDependenciesCheck;
|
||||
});
|
||||
|
||||
test('`stop` fails if plugin is not set up', async () => {
|
||||
const manifest = createPluginManifest();
|
||||
const opaqueId = Symbol();
|
||||
|
|
|
@ -19,7 +19,8 @@
|
|||
|
||||
import { join } from 'path';
|
||||
import typeDetect from 'type-detect';
|
||||
|
||||
import { Subject } from 'rxjs';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { Type } from '@kbn/config-schema';
|
||||
|
||||
import { Logger } from '../logging';
|
||||
|
@ -60,6 +61,9 @@ export class PluginWrapper<
|
|||
|
||||
private instance?: Plugin<TSetup, TStart, TPluginsSetup, TPluginsStart>;
|
||||
|
||||
private readonly startDependencies$ = new Subject<[CoreStart, TPluginsStart]>();
|
||||
public readonly startDependencies = this.startDependencies$.pipe(first()).toPromise();
|
||||
|
||||
constructor(
|
||||
public readonly params: {
|
||||
readonly path: string;
|
||||
|
@ -88,12 +92,12 @@ export class PluginWrapper<
|
|||
* @param plugins The dictionary where the key is the dependency name and the value
|
||||
* is the contract returned by the dependency's `setup` function.
|
||||
*/
|
||||
public async setup(setupContext: CoreSetup, plugins: TPluginsSetup) {
|
||||
public async setup(setupContext: CoreSetup<TPluginsStart>, plugins: TPluginsSetup) {
|
||||
this.instance = this.createPluginInstance();
|
||||
|
||||
this.log.info('Setting up plugin');
|
||||
|
||||
return await this.instance.setup(setupContext, plugins);
|
||||
return this.instance.setup(setupContext, plugins);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -108,7 +112,9 @@ export class PluginWrapper<
|
|||
throw new Error(`Plugin "${this.name}" can't be started since it isn't set up.`);
|
||||
}
|
||||
|
||||
return await this.instance.start(startContext, plugins);
|
||||
const startContract = await this.instance.start(startContext, plugins);
|
||||
this.startDependencies$.next([startContext, plugins]);
|
||||
return startContract;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -176,6 +176,7 @@ export function createPluginSetupContext<TPlugin, TPluginDependencies>(
|
|||
uuid: {
|
||||
getInstanceUuid: deps.uuid.getInstanceUuid,
|
||||
},
|
||||
getStartServices: () => plugin.startDependencies,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -552,13 +552,14 @@ export interface ContextSetup {
|
|||
export type CoreId = symbol;
|
||||
|
||||
// @public
|
||||
export interface CoreSetup {
|
||||
export interface CoreSetup<TPluginsStart extends object = object> {
|
||||
// (undocumented)
|
||||
capabilities: CapabilitiesSetup;
|
||||
// (undocumented)
|
||||
context: ContextSetup;
|
||||
// (undocumented)
|
||||
elasticsearch: ElasticsearchServiceSetup;
|
||||
getStartServices(): Promise<[CoreStart, TPluginsStart]>;
|
||||
// (undocumented)
|
||||
http: HttpServiceSetup;
|
||||
// (undocumented)
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { of } from 'rxjs';
|
||||
import { ByteSizeValue } from '@kbn/config-schema';
|
||||
import { ICustomClusterClient, CoreSetup } from '../../../../src/core/server';
|
||||
import { ICustomClusterClient } from '../../../../src/core/server';
|
||||
import { elasticsearchClientPlugin } from './elasticsearch_client_plugin';
|
||||
import { Plugin, PluginSetupDependencies } from './plugin';
|
||||
|
||||
|
@ -14,7 +14,7 @@ import { coreMock, elasticsearchServiceMock } from '../../../../src/core/server/
|
|||
|
||||
describe('Security Plugin', () => {
|
||||
let plugin: Plugin;
|
||||
let mockCoreSetup: MockedKeys<CoreSetup>;
|
||||
let mockCoreSetup: ReturnType<typeof coreMock.createSetup>;
|
||||
let mockClusterClient: jest.Mocked<ICustomClusterClient>;
|
||||
let mockDependencies: PluginSetupDependencies;
|
||||
beforeEach(() => {
|
||||
|
|
Loading…
Reference in a new issue