mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
* refactor: 💡 separate server-side function reg from executor * refactor: 💡 separate Canvas functionality of Interpreter * refactor: 💡 simplify, move server fn init into Canavas * fix: 🐛 adjust Expressions service usage after master merge
This commit is contained in:
parent
e7e0723f05
commit
4bda107558
13 changed files with 114 additions and 171 deletions
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This file needs to be deleted by 8.0 release. It is here to load available
|
||||
* server side functions and create a wrappers around them on client side, to
|
||||
* execute them from client side. This functionality is used only by Canvas
|
||||
* and all server side functions are in Canvas plugin.
|
||||
*
|
||||
* In 8.0 there will be no server-side functions, plugins will register only
|
||||
* client side functions and if they need those to execute something on the
|
||||
* server side, it should be respective function's internal implementation detail.
|
||||
*/
|
||||
|
||||
import { get, identity } from 'lodash';
|
||||
// @ts-ignore
|
||||
import { npSetup } from 'ui/new_platform';
|
||||
import { FUNCTIONS_URL } from './consts';
|
||||
import { ajaxStream } from './ajax_stream';
|
||||
import { batchedFetch } from './batched_fetch';
|
||||
|
||||
export function getType(node: any) {
|
||||
if (node == null) return 'null';
|
||||
if (typeof node === 'object') {
|
||||
if (!node.type) throw new Error('Objects must have a type property');
|
||||
return node.type;
|
||||
}
|
||||
return typeof node;
|
||||
}
|
||||
|
||||
export function serializeProvider(types: any) {
|
||||
return {
|
||||
serialize: provider('serialize'),
|
||||
deserialize: provider('deserialize'),
|
||||
};
|
||||
|
||||
function provider(key: any) {
|
||||
return (context: any) => {
|
||||
const type = getType(context);
|
||||
const typeDef = types[type];
|
||||
const fn: any = get(typeDef, key) || identity;
|
||||
return fn(context);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let cached: Promise<void> | null = null;
|
||||
|
||||
export const loadLegacyServerFunctionWrappers = async () => {
|
||||
if (!cached) {
|
||||
cached = (async () => {
|
||||
const serverFunctionList = await npSetup.core.http.get(FUNCTIONS_URL);
|
||||
const types = npSetup.plugins.expressions.__LEGACY.types.toJS();
|
||||
const { serialize } = serializeProvider(types);
|
||||
const batch = batchedFetch({
|
||||
ajaxStream: ajaxStream(
|
||||
npSetup.core.injectedMetadata.getKibanaVersion(),
|
||||
npSetup.core.injectedMetadata.getBasePath()
|
||||
),
|
||||
serialize,
|
||||
});
|
||||
|
||||
// For every sever-side function, register a client-side
|
||||
// function that matches its definition, but which simply
|
||||
// calls the server-side function endpoint.
|
||||
Object.keys(serverFunctionList).forEach(functionName => {
|
||||
const fn = () => ({
|
||||
...serverFunctionList[functionName],
|
||||
fn: (context: any, args: any) => batch({ functionName, args, context }),
|
||||
});
|
||||
npSetup.plugins.expressions.registerFunction(fn);
|
||||
});
|
||||
})();
|
||||
}
|
||||
|
||||
return cached;
|
||||
};
|
|
@ -19,19 +19,12 @@
|
|||
|
||||
import 'uiExports/interpreter';
|
||||
import { register, registryFactory } from '@kbn/interpreter/common';
|
||||
import { npSetup } from 'ui/new_platform';
|
||||
import { initializeInterpreter } from './lib/interpreter';
|
||||
import { registries } from './registries';
|
||||
|
||||
import { ajaxStream } from './lib/ajax_stream';
|
||||
import { functions } from './functions';
|
||||
import { visualization } from './renderers/visualization';
|
||||
import { typeSpecs } from '../../../../plugins/expressions/common';
|
||||
|
||||
const { http } = npSetup.core;
|
||||
const KIBANA_VERSION = npSetup.core.injectedMetadata.getKibanaVersion();
|
||||
const KIBANA_BASE_PATH = npSetup.core.injectedMetadata.getBasePath();
|
||||
|
||||
// Expose kbnInterpreter.register(specs) and kbnInterpreter.registries() globally so that plugins
|
||||
// can register without a transpile step.
|
||||
global.kbnInterpreter = Object.assign(global.kbnInterpreter || {}, registryFactory(registries));
|
||||
|
@ -42,26 +35,13 @@ register(registries, {
|
|||
renderers: [visualization],
|
||||
});
|
||||
|
||||
let _resolve;
|
||||
let _interpreterPromise;
|
||||
|
||||
const initialize = async () => {
|
||||
initializeInterpreter({
|
||||
http,
|
||||
ajaxStream: ajaxStream(KIBANA_VERSION, KIBANA_BASE_PATH),
|
||||
typesRegistry: registries.types,
|
||||
functionsRegistry: registries.browserFunctions,
|
||||
}).then(interpreter => {
|
||||
_resolve({ interpreter });
|
||||
});
|
||||
};
|
||||
let interpreterPromise;
|
||||
|
||||
export const getInterpreter = async () => {
|
||||
if (!_interpreterPromise) {
|
||||
_interpreterPromise = new Promise(resolve => _resolve = resolve);
|
||||
initialize();
|
||||
if (!interpreterPromise) {
|
||||
interpreterPromise = initializeInterpreter();
|
||||
}
|
||||
return await _interpreterPromise;
|
||||
return await interpreterPromise;
|
||||
};
|
||||
|
||||
export const interpretAst = async (...params) => {
|
||||
|
|
|
@ -36,7 +36,9 @@ jest.mock('@kbn/interpreter/common', () => ({
|
|||
}));
|
||||
|
||||
const mockInterpreter = {
|
||||
interpretAst: jest.fn(),
|
||||
interpreter: {
|
||||
interpretAst: jest.fn(),
|
||||
}
|
||||
};
|
||||
jest.mock('./lib/interpreter', () => ({
|
||||
initializeInterpreter: jest.fn().mockReturnValue(Promise.resolve(mockInterpreter)),
|
||||
|
@ -71,12 +73,6 @@ describe('interpreter/interpreter', () => {
|
|||
it('initializes interpreter', async () => {
|
||||
await getInterpreter();
|
||||
expect(initializeInterpreter).toHaveBeenCalledTimes(1);
|
||||
expect(initializeInterpreter.mock.calls[0][0]).toMatchObject({
|
||||
ajaxStream: expect.any(Function),
|
||||
http: expect.any(Object),
|
||||
typesRegistry: expect.any(Function),
|
||||
functionsRegistry: expect.any(Function),
|
||||
});
|
||||
});
|
||||
|
||||
it('only initializes interpreter once', async () => {
|
||||
|
@ -110,15 +106,15 @@ describe('interpreter/interpreter', () => {
|
|||
it('calls interpreter.interpretAst with the provided params', async () => {
|
||||
const params = [{}];
|
||||
await interpretAst(...params);
|
||||
expect(mockInterpreter.interpretAst).toHaveBeenCalledTimes(1);
|
||||
expect(mockInterpreter.interpretAst).toHaveBeenCalledWith(...params);
|
||||
expect(mockInterpreter.interpreter.interpretAst).toHaveBeenCalledTimes(1);
|
||||
expect(mockInterpreter.interpreter.interpretAst).toHaveBeenCalledWith(...params);
|
||||
});
|
||||
|
||||
it('calls interpreter.interpretAst each time', async () => {
|
||||
const params = [{}];
|
||||
await interpretAst(...params);
|
||||
await interpretAst(...params);
|
||||
expect(mockInterpreter.interpretAst).toHaveBeenCalledTimes(2);
|
||||
expect(mockInterpreter.interpreter.interpretAst).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,105 +0,0 @@
|
|||
/*
|
||||
* 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 { FUNCTIONS_URL } from './consts';
|
||||
import { initializeInterpreter } from './interpreter';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { httpServiceMock } from '../../../../../core/public/http/http_service.mock';
|
||||
|
||||
jest.mock('../../common', () => ({
|
||||
serializeProvider: () => ({ serialize: () => ({}) }),
|
||||
}));
|
||||
|
||||
jest.mock('./create_handlers', () => ({
|
||||
createHandlers: () => ({}),
|
||||
}));
|
||||
|
||||
it('loads server-side functions', async () => {
|
||||
const http = httpServiceMock.createStartContract();
|
||||
http.get.mockImplementation(async () => {
|
||||
return {
|
||||
hello: { name: 'hello' },
|
||||
world: { name: 'world' },
|
||||
};
|
||||
});
|
||||
const ajaxStream = jest.fn(async () => ({}));
|
||||
|
||||
await initializeInterpreter({
|
||||
http,
|
||||
ajaxStream,
|
||||
typesRegistry: { toJS: () => ({}) },
|
||||
functionsRegistry: { register: () => {} },
|
||||
});
|
||||
|
||||
expect(http.get).toHaveBeenCalledTimes(1);
|
||||
expect(http.get).toHaveBeenCalledWith(FUNCTIONS_URL);
|
||||
});
|
||||
|
||||
it('registers client-side functions that pass through to the server', async () => {
|
||||
const http = httpServiceMock.createStartContract();
|
||||
http.get.mockImplementation(async () => {
|
||||
return {
|
||||
hello: { name: 'hello' },
|
||||
world: { name: 'world' },
|
||||
};
|
||||
});
|
||||
|
||||
const register = jest.fn();
|
||||
const ajaxStream = jest.fn(async ({ onResponse }) => {
|
||||
onResponse({ id: 1, result: { hello: 'world' } });
|
||||
});
|
||||
|
||||
await initializeInterpreter({
|
||||
http,
|
||||
ajaxStream,
|
||||
typesRegistry: { toJS: () => ({}) },
|
||||
functionsRegistry: { register },
|
||||
});
|
||||
|
||||
expect(register).toHaveBeenCalledTimes(2);
|
||||
|
||||
const [hello, world] = register.mock.calls.map(([fn]) => fn());
|
||||
|
||||
expect(hello.name).toEqual('hello');
|
||||
expect(typeof hello.fn).toEqual('function');
|
||||
expect(world.name).toEqual('world');
|
||||
expect(typeof world.fn).toEqual('function');
|
||||
|
||||
const context = {};
|
||||
const args = { quote: 'All we have to decide is what to do with the time that is given us.' };
|
||||
|
||||
const result = await hello.fn(context, args);
|
||||
|
||||
expect(result).toEqual({ hello: 'world' });
|
||||
|
||||
expect(ajaxStream).toHaveBeenCalledWith({
|
||||
url: FUNCTIONS_URL,
|
||||
onResponse: expect.any(Function),
|
||||
body: JSON.stringify({
|
||||
functions: [
|
||||
{
|
||||
functionName: 'hello',
|
||||
args,
|
||||
context,
|
||||
id: 1,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
});
|
|
@ -17,44 +17,19 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { interpreterProvider, serializeProvider } from '../../common';
|
||||
import { interpreterProvider } from '../../common';
|
||||
import { createHandlers } from './create_handlers';
|
||||
import { batchedFetch } from './batched_fetch';
|
||||
import { FUNCTIONS_URL } from './consts';
|
||||
import { CoreStart } from '../../../../../core/public';
|
||||
|
||||
interface Config {
|
||||
http: CoreStart['http'];
|
||||
ajaxStream: any; // TODO: Import this from kibana_utils/ajax_stream
|
||||
typesRegistry: any;
|
||||
functionsRegistry: any;
|
||||
}
|
||||
|
||||
export async function initializeInterpreter(config: Config) {
|
||||
const { http, ajaxStream, typesRegistry, functionsRegistry } = config;
|
||||
const serverFunctionList = await http.get(FUNCTIONS_URL);
|
||||
const types = typesRegistry.toJS();
|
||||
const { serialize } = serializeProvider(types);
|
||||
const batch = batchedFetch({ ajaxStream, serialize });
|
||||
|
||||
// For every sever-side function, register a client-side
|
||||
// function that matches its definition, but which simply
|
||||
// calls the server-side function endpoint.
|
||||
Object.keys(serverFunctionList).forEach(functionName => {
|
||||
functionsRegistry.register(() => ({
|
||||
...serverFunctionList[functionName],
|
||||
fn: (context: any, args: any) => batch({ functionName, args, context }),
|
||||
}));
|
||||
});
|
||||
import { registries } from '../registries';
|
||||
|
||||
export async function initializeInterpreter() {
|
||||
const interpretAst = async (ast: any, context: any, handlers: any) => {
|
||||
const interpretFn = await interpreterProvider({
|
||||
types: typesRegistry.toJS(),
|
||||
types: registries.types.toJS(),
|
||||
handlers: { ...handlers, ...createHandlers() },
|
||||
functions: functionsRegistry.toJS(),
|
||||
functions: registries.browserFunctions.toJS(),
|
||||
});
|
||||
return interpretFn(ast, context);
|
||||
};
|
||||
|
||||
return { interpretAst };
|
||||
return { interpreter: { interpretAst } };
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import { connect } from 'react-redux';
|
|||
import { compose, withProps } from 'recompose';
|
||||
import { registries } from 'plugins/interpreter/registries';
|
||||
import { getInterpreter } from 'plugins/interpreter/interpreter';
|
||||
import { loadLegacyServerFunctionWrappers } from 'plugins/interpreter/canvas/load_legacy_server_function_wrappers';
|
||||
import { getAppReady, getBasePath } from '../../state/selectors/app';
|
||||
import { appReady, appError } from '../../state/actions/app';
|
||||
import { elementsRegistry } from '../../lib/elements_registry';
|
||||
|
@ -71,6 +72,7 @@ register(registries, {
|
|||
const mapDispatchToProps = dispatch => ({
|
||||
setAppReady: () => async () => {
|
||||
try {
|
||||
await loadLegacyServerFunctionWrappers();
|
||||
await getInterpreter();
|
||||
|
||||
// Register the expression language with the Monaco Editor
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import { fromExpression, getType } from '@kbn/interpreter/common';
|
||||
import { interpretAst } from 'plugins/interpreter/interpreter';
|
||||
import { loadLegacyServerFunctionWrappers } from 'plugins/interpreter/canvas/load_legacy_server_function_wrappers';
|
||||
import { notify } from './notify';
|
||||
|
||||
/**
|
||||
|
@ -19,7 +20,8 @@ import { notify } from './notify';
|
|||
* @returns {promise}
|
||||
*/
|
||||
export function runInterpreter(ast, context = null, options = {}) {
|
||||
return interpretAst(ast, context)
|
||||
return loadLegacyServerFunctionWrappers()
|
||||
.then(() => interpretAst(ast, context))
|
||||
.then(renderable => {
|
||||
if (getType(renderable) === 'render') {
|
||||
return renderable;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue