Allow LP plugins to access NP plugin context providers (#47639)

This commit is contained in:
Josh Dover 2019-10-16 12:13:50 -05:00 committed by GitHub
parent b267704c49
commit b03dfdf68a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 126 additions and 60 deletions

View file

@ -168,6 +168,22 @@ describe('#setup()', () => {
expect(MockContextService.setup).toHaveBeenCalledTimes(1);
});
it('injects legacy dependency to context#setup()', async () => {
const pluginA = Symbol();
const pluginB = Symbol();
const pluginDependencies = new Map<symbol, symbol[]>([[pluginA, []], [pluginB, [pluginA]]]);
MockPluginsService.getOpaqueIds.mockReturnValue(pluginDependencies);
await setupCore();
expect(MockContextService.setup).toHaveBeenCalledWith({
pluginDependencies: new Map([
[pluginA, []],
[pluginB, [pluginA]],
[MockLegacyPlatformService.legacyId, [pluginA, pluginB]],
]),
});
});
it('calls injectedMetadata#setup()', async () => {
await setupCore();
expect(MockInjectedMetadataService.setup).toHaveBeenCalledTimes(1);

View file

@ -161,9 +161,14 @@ export class CoreSystem {
const pluginDependencies = this.plugins.getOpaqueIds();
const context = this.context.setup({
// We inject a fake "legacy plugin" with no dependencies so that legacy plugins can register context providers
// that will only be available to other legacy plugins and will not leak into New Platform plugins.
pluginDependencies: new Map([...pluginDependencies, [this.legacy.legacyId, []]]),
// We inject a fake "legacy plugin" with dependencies on every plugin so that legacy plugins:
// 1) Can access context from any NP plugin
// 2) Can register context providers that will only be available to other legacy plugins and will not leak into
// New Platform plugins.
pluginDependencies: new Map([
...pluginDependencies,
[this.legacy.legacyId, [...pluginDependencies.keys()]],
]),
});
const application = this.application.setup({ context });

View file

@ -18,9 +18,11 @@
*/
import { LegacyPlatformService } from './legacy_service';
type LegacyPlatformServiceContract = PublicMethodsOf<LegacyPlatformService>;
// Use Required to get only public properties
type LegacyPlatformServiceContract = Required<LegacyPlatformService>;
const createMock = () => {
const mocked: jest.Mocked<LegacyPlatformServiceContract> = {
legacyId: Symbol(),
setup: jest.fn(),
start: jest.fn(),
stop: jest.fn(),

View file

@ -36,6 +36,7 @@ jest.doMock('./elasticsearch/elasticsearch_service', () => ({
}));
export const mockLegacyService = {
legacyId: Symbol(),
setup: jest.fn().mockReturnValue({ uiExports: {} }),
start: jest.fn(),
stop: jest.fn(),
@ -55,3 +56,9 @@ export const mockSavedObjectsService = savedObjectsServiceMock.create();
jest.doMock('./saved_objects/saved_objects_service', () => ({
SavedObjectsService: jest.fn(() => mockSavedObjectsService),
}));
import { contextServiceMock } from './context/context_service.mock';
export const mockContextService = contextServiceMock.create();
jest.doMock('./context/context_service', () => ({
ContextService: jest.fn(() => mockContextService),
}));

View file

@ -24,7 +24,8 @@ import {
mockPluginsService,
mockConfigService,
mockSavedObjectsService,
} from './index.test.mocks';
mockContextService,
} from './server.test.mocks';
import { BehaviorSubject } from 'rxjs';
import { Env, Config, ObjectToConfigAdapter } from './config';
@ -64,6 +65,25 @@ test('sets up services on "setup"', async () => {
expect(mockSavedObjectsService.setup).toHaveBeenCalledTimes(1);
});
test('injects legacy dependency to context#setup()', async () => {
const server = new Server(config$, env, logger);
const pluginA = Symbol();
const pluginB = Symbol();
const pluginDependencies = new Map<symbol, symbol[]>([[pluginA, []], [pluginB, [pluginA]]]);
mockPluginsService.discover.mockResolvedValue(pluginDependencies);
await server.setup();
expect(mockContextService.setup).toHaveBeenCalledWith({
pluginDependencies: new Map([
[pluginA, []],
[pluginB, [pluginA]],
[mockLegacyService.legacyId, [pluginA, pluginB]],
]),
});
});
test('runs services on "start"', async () => {
const server = new Server(config$, env, logger);

View file

@ -78,9 +78,14 @@ export class Server {
// Discover any plugins before continuing. This allows other systems to utilize the plugin dependency graph.
const pluginDependencies = await this.plugins.discover();
const contextServiceSetup = this.context.setup({
// We inject a fake "legacy plugin" with no dependencies so that legacy plugins can register context providers
// that will only be available to other legacy plugins and will not leak into New Platform plugins.
pluginDependencies: new Map([...pluginDependencies, [this.legacy.legacyId, []]]),
// We inject a fake "legacy plugin" with dependencies on every plugin so that legacy plugins:
// 1) Can access context from any NP plugin
// 2) Can register context providers that will only be available to other legacy plugins and will not leak into
// New Platform plugins.
pluginDependencies: new Map([
...pluginDependencies,
[this.legacy.legacyId, [...pluginDependencies.keys()]],
]),
});
const httpSetup = await this.http.setup({

View file

@ -19,6 +19,7 @@
import path from 'path';
import fs from 'fs';
import { services } from './services';
export default async function ({ readConfigFile }) {
const functionalConfig = await readConfigFile(require.resolve('../functional/config'));
@ -48,7 +49,10 @@ export default async function ({ readConfigFile }) {
require.resolve('./test_suites/core_plugins'),
],
services: functionalConfig.get('services'),
services: {
...functionalConfig.get('services'),
...services,
},
pageObjects: functionalConfig.get('pageObjects'),
servers: functionalConfig.get('servers'),
esTestCluster: functionalConfig.get('esTestCluster'),

View file

@ -1,17 +0,0 @@
{
"name": "core_legacy_compat",
"version": "1.0.0",
"main": "target/test/plugin_functional/plugins/core_legacy_compat",
"kibana": {
"version": "kibana",
"templateVersion": "1.0.0"
},
"license": "Apache-2.0",
"scripts": {
"kbn": "node ../../../../scripts/kbn.js",
"build": "rm -rf './target' && tsc"
},
"devDependencies": {
"typescript": "3.5.3"
}
}

View file

@ -1,14 +0,0 @@
{
"extends": "../../../../tsconfig.json",
"compilerOptions": {
"outDir": "./target",
"skipLibCheck": true
},
"include": [
"index.ts",
"public/**/*.ts",
"public/**/*.tsx",
"../../../../typings/**/*"
],
"exclude": []
}

View file

@ -24,6 +24,13 @@ export default function(kibana: any) {
return new kibana.Plugin({
id: 'core_plugin_legacy',
require: ['kibana'],
uiExports: {
app: {
title: 'Core Legacy Compat',
description: 'This is a sample plugin to test core to legacy compatibility',
main: 'plugins/core_plugin_legacy/index',
},
},
init(server: KbnServer) {
const { http } = server.newPlatform.setup.core;
const router = http.createRouter();
@ -32,6 +39,11 @@ export default function(kibana: any) {
const response = await context.core.elasticsearch.adminClient.callAsInternalUser('ping');
return res.ok({ body: `Pong in legacy via new platform: ${response}` });
});
router.get({ path: '/api/np-context-in-legacy', validate: false }, (context, req, res) => {
const contexts = Object.keys(context);
return res.ok({ body: { contexts } });
});
},
});
}

View file

@ -0,0 +1,24 @@
/*
* 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 { KibanaSupertestProvider } from './supertest';
export const services = {
supertest: KibanaSupertestProvider,
};

View file

@ -17,19 +17,13 @@
* under the License.
*/
import { Server } from 'hapi';
// eslint-disable-next-line import/no-default-export
export default function(kibana: any) {
return new kibana.Plugin({
uiExports: {
app: {
title: 'Core Legacy Compat',
description: 'This is a sample plugin to test core to legacy compatibility',
main: 'plugins/core_legacy_compat/index',
},
},
import { format as formatUrl } from 'url';
init(server: Server) {},
});
import supertestAsPromised from 'supertest-as-promised';
export function KibanaSupertestProvider({ getService }) {
const config = getService('config');
const kibanaServerUrl = formatUrl(config.get('servers.kibana'));
return supertestAsPromised(kibanaServerUrl);
}

View file

@ -21,21 +21,29 @@ import expect from '@kbn/expect';
export default function ({ getService, getPageObjects }) {
const PageObjects = getPageObjects(['common']);
const browser = getService('browser');
const testSubjects = getService('testSubjects');
const supertest = getService('supertest');
describe('legacy plugins', function describeIndexTests() {
it('have access to New Platform HTTP service', async () => {
const url = `${PageObjects.common.getHostPort()}/api/np-http-in-legacy`;
await browser.get(url);
describe('http', () => {
it('has access to New Platform HTTP service', async () => {
await supertest
.get('/api/np-http-in-legacy')
.expect(200)
.expect('Pong in legacy via new platform: true');
});
const pageSource = await browser.execute('return window.document.body.textContent;');
expect(pageSource).to.equal('Pong in legacy via new platform: true');
it('has access to New Platform HTTP context providers', async () => {
await supertest
.get('/api/np-context-in-legacy')
.expect(200)
.expect(JSON.stringify({ contexts: ['core', 'search', 'pluginA'] }));
});
});
describe('application service compatibility layer', function describeIndexTests() {
it('can render legacy apps', async () => {
await PageObjects.common.navigateToApp('core_legacy_compat');
await PageObjects.common.navigateToApp('core_plugin_legacy');
expect(await testSubjects.exists('coreLegacyCompatH1')).to.be(true);
});
});