Skip to content

Commit

Permalink
Remove WebSockets from Canvas expressions interpreter (#29792)
Browse files Browse the repository at this point in the history
This modifies the interpreter to use REST instead of WebSockets.
  • Loading branch information
chrisdavies authored Feb 5, 2019
1 parent 52e12c7 commit ebd3a82
Show file tree
Hide file tree
Showing 23 changed files with 180 additions and 731 deletions.
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,6 @@
"rxjs": "^6.2.1",
"script-loader": "0.7.2",
"semver": "^5.5.0",
"socket.io": "^2.1.1",
"stream-stream": "^1.2.6",
"style-loader": "0.23.1",
"tar": "2.2.0",
Expand Down
1 change: 0 additions & 1 deletion packages/kbn-interpreter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
"@kbn/i18n": "1.0.0",
"lodash": "npm:@elastic/lodash@3.10.1-kibana1",
"lodash.clone": "^4.5.0",
"socket.io-client": "^2.1.1",
"uuid": "3.0.1"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/kbn-interpreter/src/common/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
export { FunctionsRegistry } from './lib/functions_registry';
export { TypesRegistry } from './lib/types_registry';
export { createError } from './interpreter/create_error';
export { interpretProvider } from './interpreter/interpret';
export { interpreterProvider } from './interpreter/interpret';
export { serializeProvider } from './lib/serialize';
export { fromExpression, toExpression, safeElementFromExpression } from './lib/ast';
export { Fn } from './lib/fn';
Expand Down
13 changes: 3 additions & 10 deletions packages/kbn-interpreter/src/common/interpreter/interpret.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ import { getByAlias } from '../lib/get_by_alias';
import { castProvider } from './cast';
import { createError } from './create_error';

export function interpretProvider(config) {
const { functions, onFunctionNotFound, types } = config;
export function interpreterProvider(config) {
const { functions, types } = config;
const handlers = { ...config.handlers, types };
const cast = castProvider(types);

Expand Down Expand Up @@ -54,15 +54,8 @@ export function interpretProvider(config) {
const { function: fnName, arguments: fnArgs } = link;
const fnDef = getByAlias(functions, fnName);

// if the function is not found, pass the expression chain to the not found handler
// in this case, it will try to execute the function in another context
if (!fnDef) {
chain.unshift(link);
try {
return await onFunctionNotFound({ type: 'expression', chain: chain }, context);
} catch (e) {
return createError(e);
}
return createError({ message: `Function ${fnName} could not be found.` });
}

try {
Expand Down

This file was deleted.

2 changes: 1 addition & 1 deletion packages/kbn-interpreter/src/public/create_handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

export function createHandlers(/*socket*/) {
export function createHandlers() {
return {
environment: 'client',
};
Expand Down
1 change: 0 additions & 1 deletion packages/kbn-interpreter/src/public/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,5 @@
*/

export { loadBrowserRegistries } from './browser_registries';
export { createSocket } from './socket';
export { initializeInterpreter } from './interpreter';
export { RenderFunctionsRegistry } from './render_functions_registry';
70 changes: 31 additions & 39 deletions packages/kbn-interpreter/src/public/interpreter.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,55 +17,47 @@
* under the License.
*/

import { socketInterpreterProvider } from '../common/interpreter/socket_interpret';
import { interpreterProvider } from '../common/interpreter/interpret';
import { serializeProvider } from '../common/lib/serialize';
import { createHandlers } from './create_handlers';

export async function initializeInterpreter(socket, typesRegistry, functionsRegistry) {
let resolve;
const functionList = new Promise(_resolve => (resolve = _resolve));

const getInitializedFunctions = async () => {
return functionList;
};
export const FUNCTIONS_URL = '/api/canvas/fns';

export async function initializeInterpreter(kfetch, typesRegistry, functionsRegistry) {
const serverFunctionList = await kfetch({ pathname: FUNCTIONS_URL });

// 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],
async fn(context, args) {
const types = typesRegistry.toJS();
const { serialize } = serializeProvider(types);
const result = await kfetch({
pathname: `${FUNCTIONS_URL}/${functionName}`,
method: 'POST',
body: JSON.stringify({
args,
context: serialize(context),
}),
});

return result;
},
}));
});

const interpretAst = async (ast, context, handlers) => {
// Load plugins before attempting to get functions, otherwise this gets racey
const serverFunctionList = await functionList;
const interpretFn = await socketInterpreterProvider({
const interpretFn = await interpreterProvider({
types: typesRegistry.toJS(),
handlers: { ...handlers, ...createHandlers(socket) },
handlers: { ...handlers, ...createHandlers() },
functions: functionsRegistry.toJS(),
referableFunctions: serverFunctionList,
socket: socket,
});
return interpretFn(ast, context);
};

// Listen for interpreter runs
socket.on('run', ({ ast, context, id }) => {
const types = typesRegistry.toJS();
const { serialize, deserialize } = serializeProvider(types);
interpretAst(ast, deserialize(context)).then(value => {
socket.emit(`resp:${id}`, { value: serialize(value) });
});
});

// Create the function list
let gotFunctionList = false;
socket.once('functionList', (fl) => {
gotFunctionList = true;
resolve(fl);
});

const interval = setInterval(() => {
if (gotFunctionList) {
clearInterval(interval);
return;
}
socket.emit('getFunctionList');
}, 1000);

return { getInitializedFunctions, interpretAst };
return { interpretAst };
}

75 changes: 75 additions & 0 deletions packages/kbn-interpreter/src/public/interpreter.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* 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 { initializeInterpreter, FUNCTIONS_URL } from './interpreter';

jest.mock('../common/interpreter/interpret', () => ({
interpreterProvider: () => () => ({}),
}));

jest.mock('../common/lib/serialize', () => ({
serializeProvider: () => ({ serialize: () => ({}) }),
}));

jest.mock('./create_handlers', () => ({
createHandlers: () => ({}),
}));

describe('kbn-interpreter/interpreter', () => {
it('loads server-side functions', async () => {
const kfetch = jest.fn(async () => ({}));

await initializeInterpreter(kfetch, { toJS: () => ({}) }, ({ register: () => {} }));

expect(kfetch).toHaveBeenCalledTimes(1);
expect(kfetch).toHaveBeenCalledWith({ pathname: FUNCTIONS_URL });
});

it('registers client-side functions that pass through to the server', async () => {
const kfetch = jest.fn(async () => ({
hello: { name: 'hello' },
world: { name: 'world' },
}));

const register = jest.fn();

await initializeInterpreter(kfetch, { toJS: () => ({}) }, ({ 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.' };

await hello.fn(context, args);

expect(kfetch).toHaveBeenCalledWith({
pathname: `${FUNCTIONS_URL}/hello`,
method: 'POST',
body: JSON.stringify({ args, context }),
});
});

});
63 changes: 0 additions & 63 deletions packages/kbn-interpreter/src/public/socket.js

This file was deleted.

Loading

0 comments on commit ebd3a82

Please sign in to comment.