From 94c02bed9fbaf7f3fdc985daf87ed256267cad5a Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Wed, 30 Jan 2019 14:36:13 -0500 Subject: [PATCH 01/13] Make Canvas use socket.io polling only so that Kibana behaves consistently across environments --- packages/kbn-interpreter/src/public/socket.js | 2 +- src/legacy/core_plugins/interpreter/server/routes/socket.js | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/kbn-interpreter/src/public/socket.js b/packages/kbn-interpreter/src/public/socket.js index 7d209e09c6887..04f8a91ed01b8 100644 --- a/packages/kbn-interpreter/src/public/socket.js +++ b/packages/kbn-interpreter/src/public/socket.js @@ -26,7 +26,7 @@ export async function createSocket(basePath, functionsRegistry) { return new Promise((resolve, reject) => { const socket = io({ path: `${basePath}/socket.io`, - transports: ['polling', 'websocket'], + transports: ['polling'], transportOptions: { polling: { extraHeaders: { diff --git a/src/legacy/core_plugins/interpreter/server/routes/socket.js b/src/legacy/core_plugins/interpreter/server/routes/socket.js index 6201eaf70ca8a..66e0fa782b449 100644 --- a/src/legacy/core_plugins/interpreter/server/routes/socket.js +++ b/src/legacy/core_plugins/interpreter/server/routes/socket.js @@ -46,7 +46,10 @@ export function socketApi(server) { handler: () => 'pong', }); - const io = socket(server.listener, { path: '/socket.io' }); + const io = socket(server.listener, { + path: '/socket.io', + transports: ['polling'], + }); io.on('connection', async socket => { // 'request' is the modified hapi request object From 76d2d12dbf1b18c5b70dec54c29751c6d96309ed Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Thu, 31 Jan 2019 20:10:04 -0500 Subject: [PATCH 02/13] Remove WebSockets from Canvas expressions interpreter --- package.json | 1 - packages/kbn-interpreter/package.json | 1 - packages/kbn-interpreter/src/public/index.js | 1 - .../kbn-interpreter/src/public/interpreter.js | 81 +++++++++------- packages/kbn-interpreter/src/public/socket.js | 63 ------------- .../interpreter/public/interpreter.js | 13 +-- .../interpreter/server/routes/index.js | 4 +- .../server/routes/server_functions.js | 59 ++++++++++++ .../interpreter/server/routes/socket.js | 93 ------------------- .../canvas/public/components/app/index.js | 4 +- .../canvas/server/lib/normalize_type.js | 2 +- yarn.lock | 4 +- 12 files changed, 118 insertions(+), 208 deletions(-) delete mode 100644 packages/kbn-interpreter/src/public/socket.js create mode 100644 src/legacy/core_plugins/interpreter/server/routes/server_functions.js delete mode 100644 src/legacy/core_plugins/interpreter/server/routes/socket.js diff --git a/package.json b/package.json index d9b0b8fbe051a..b31a7ac9dea8d 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/kbn-interpreter/package.json b/packages/kbn-interpreter/package.json index ddb7ccd2b90f8..8b4334e422e9a 100644 --- a/packages/kbn-interpreter/package.json +++ b/packages/kbn-interpreter/package.json @@ -13,7 +13,6 @@ "lodash": "npm:@elastic/lodash@3.10.1-kibana1", "lodash.clone": "^4.5.0", "scriptjs": "^2.5.8", - "socket.io-client": "^2.1.1", "uuid": "3.0.1" }, "devDependencies": { diff --git a/packages/kbn-interpreter/src/public/index.js b/packages/kbn-interpreter/src/public/index.js index 715cbe0156643..7925ca3c8ca1b 100644 --- a/packages/kbn-interpreter/src/public/index.js +++ b/packages/kbn-interpreter/src/public/index.js @@ -18,6 +18,5 @@ */ export { loadBrowserRegistries } from './browser_registries'; -export { createSocket } from './socket'; export { initializeInterpreter } from './interpreter'; export { RenderFunctionsRegistry } from './render_functions_registry'; diff --git a/packages/kbn-interpreter/src/public/interpreter.js b/packages/kbn-interpreter/src/public/interpreter.js index 45042ef4002cc..5b1c525dbdff6 100644 --- a/packages/kbn-interpreter/src/public/interpreter.js +++ b/packages/kbn-interpreter/src/public/interpreter.js @@ -21,51 +21,66 @@ import { socketInterpreterProvider } from '../common/interpreter/socket_interpre 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)); +// This should be moved to its own module, and will be where +// HTTP batching and streaming happens. +function createAjaxFunction(kbnVersion, basePath) { + return async (url, opts = {}) => { + const result = await fetch(`${basePath}${url}`, { + ...opts, + headers: { + ...(opts.headers || {}), + 'kbn-version': kbnVersion, + 'Content-Type': 'application/json', + }, + }); + + const response = await result.json(); + + if (!result.ok) { + throw response; + } - const getInitializedFunctions = async () => { - return functionList; + return response; }; +} + +export async function initializeInterpreter(kbnVersion, basePath, typesRegistry, functionsRegistry) { + const ajax = createAjaxFunction(kbnVersion, basePath); + const serverFunctionList = await ajax(`/api/canvas/fns`); + + // 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 ajax(`/api/canvas/fns/${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({ 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 }; } diff --git a/packages/kbn-interpreter/src/public/socket.js b/packages/kbn-interpreter/src/public/socket.js deleted file mode 100644 index 04f8a91ed01b8..0000000000000 --- a/packages/kbn-interpreter/src/public/socket.js +++ /dev/null @@ -1,63 +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 io from 'socket.io-client'; - -const SOCKET_CONNECTION_TIMEOUT = 5000; // timeout in ms - -export async function createSocket(basePath, functionsRegistry) { - - return new Promise((resolve, reject) => { - const socket = io({ - path: `${basePath}/socket.io`, - transports: ['polling'], - transportOptions: { - polling: { - extraHeaders: { - 'kbn-xsrf': 'professionally-crafted-string-of-text', - }, - }, - }, - timeout: SOCKET_CONNECTION_TIMEOUT, - // ensure socket.io always tries polling first, otherwise auth will fail - rememberUpgrade: false, - }); - - socket.on('getFunctionList', () => { - socket.emit('functionList', functionsRegistry.toJS()); - }); - - socket.on('connect', () => { - resolve(socket); - socket.off('connectionFailed', errorHandler); - socket.off('connect_error', errorHandler); - socket.off('connect_timeout', errorHandler); - }); - - function errorHandler(err) { - // 'connectionFailed' returns an object with a reason prop - // other error cases provide their own error - reject(err.reason ? new Error(err.reason) : err); - } - - socket.on('connectionFailed', errorHandler); - socket.on('connect_error', errorHandler); - socket.on('connect_timeout', errorHandler); - }); -} diff --git a/src/legacy/core_plugins/interpreter/public/interpreter.js b/src/legacy/core_plugins/interpreter/public/interpreter.js index 9c3303bb77828..e2551dd3cbd8e 100644 --- a/src/legacy/core_plugins/interpreter/public/interpreter.js +++ b/src/legacy/core_plugins/interpreter/public/interpreter.js @@ -18,7 +18,7 @@ */ -import { initializeInterpreter, loadBrowserRegistries, createSocket } from '@kbn/interpreter/public'; +import { initializeInterpreter, loadBrowserRegistries } from '@kbn/interpreter/public'; import chrome from 'ui/chrome'; import { functions } from './functions'; import { functionsRegistry } from './functions_registry'; @@ -27,6 +27,7 @@ import { renderFunctionsRegistry } from './render_functions_registry'; import { visualization } from './renderers/visualization'; const basePath = chrome.getInjected('serverBasePath'); +const kbnVersion = chrome.getXsrfToken(); const types = { browserFunctions: functionsRegistry, @@ -46,9 +47,8 @@ let _interpreterPromise; const initialize = async () => { await loadBrowserRegistries(types, basePath); - const socket = await createSocket(basePath, functionsRegistry); - initializeInterpreter(socket, typesRegistry, functionsRegistry).then(interpreter => { - _resolve({ interpreter, socket }); + initializeInterpreter(kbnVersion, basePath, typesRegistry, functionsRegistry).then(interpreter => { + _resolve({ interpreter }); }); }; @@ -64,8 +64,3 @@ export const interpretAst = async (...params) => { const { interpreter } = await getInterpreter(); return await interpreter.interpretAst(...params); }; - -export const updateInterpreterFunctions = async () => { - const { socket } = await getInterpreter(); - socket.emit('updateFunctionList'); -}; diff --git a/src/legacy/core_plugins/interpreter/server/routes/index.js b/src/legacy/core_plugins/interpreter/server/routes/index.js index f78baf4ad496d..924dea5b772dc 100644 --- a/src/legacy/core_plugins/interpreter/server/routes/index.js +++ b/src/legacy/core_plugins/interpreter/server/routes/index.js @@ -17,12 +17,12 @@ * under the License. */ -import { socketApi } from './socket'; import { translate } from './translate'; import { plugins } from './plugins'; +import { registerServerFunctions } from './server_functions'; export function routes(server) { plugins(server); - socketApi(server); translate(server); + registerServerFunctions(server); } diff --git a/src/legacy/core_plugins/interpreter/server/routes/server_functions.js b/src/legacy/core_plugins/interpreter/server/routes/server_functions.js new file mode 100644 index 0000000000000..cb39f5f341b78 --- /dev/null +++ b/src/legacy/core_plugins/interpreter/server/routes/server_functions.js @@ -0,0 +1,59 @@ +/* + * 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 Boom from 'boom'; +import { serializeProvider } from '@kbn/interpreter/common'; +import { API_ROUTE } from '../../common/constants'; +import { createHandlers } from '../lib/create_handlers'; + +export function registerServerFunctions(server) { + // Execute functions, kind of RPC like. + server.route({ + method: 'POST', + path: `${API_ROUTE}/fns/{functionName}`, + async handler(req) { + console.log(`POST ${API_ROUTE}/fn/${req.params.functionName} `); + + const types = server.plugins.interpreter.types.toJS(); + const { deserialize } = serializeProvider(types); + const { functionName } = req.params; + const { args, context } = req.payload; + const fnDef = server.plugins.interpreter.serverFunctions.toJS()[functionName]; + + if (!fnDef) { + throw Boom.notFound(`Function "${functionName}" could not be found.`); + } + + const handlers = await createHandlers(req, server); + const result = await fnDef.fn(deserialize(context), args, handlers); + // const result = await invokeFunction(fnDef, deserialize(context), args, types, handlers); + + return result; + }, + }); + + // Give the client the list of server-functions. + server.route({ + method: 'GET', + path: `${API_ROUTE}/fns`, + handler() { + return server.plugins.interpreter.serverFunctions.toJS(); + }, + }); +} diff --git a/src/legacy/core_plugins/interpreter/server/routes/socket.js b/src/legacy/core_plugins/interpreter/server/routes/socket.js deleted file mode 100644 index 66e0fa782b449..0000000000000 --- a/src/legacy/core_plugins/interpreter/server/routes/socket.js +++ /dev/null @@ -1,93 +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 socket from 'socket.io'; -import { serializeProvider } from '@kbn/interpreter/common'; -import { routeExpressionProvider } from '../lib/route_expression/index'; -import { browser } from '../lib/route_expression/browser'; -import { thread } from '../lib/route_expression/thread/index'; -import { server as serverEnv } from '../lib/route_expression/server'; -import { getRequest } from '../lib/get_request'; -import { API_ROUTE } from '../../common/constants'; - -async function getModifiedRequest(server, socket) { - try { - return await getRequest(server, socket.handshake); - } catch (err) { - // on errors, notify the client and close the connection - socket.emit('connectionFailed', { reason: err.message || 'Socket connection failed' }); - socket.disconnect(true); - return false; - } -} - -export function socketApi(server) { - // add a POST ping route for `getRequest` to use - // TODO: remove this once we have upstream socket support - server.route({ - method: 'POST', - path: `${API_ROUTE}/ping`, - handler: () => 'pong', - }); - - const io = socket(server.listener, { - path: '/socket.io', - transports: ['polling'], - }); - - io.on('connection', async socket => { - // 'request' is the modified hapi request object - const request = await getModifiedRequest(server, socket); - if (!request) return; // do nothing without the request object - - const types = server.plugins.interpreter.types.toJS(); - const { serialize, deserialize } = serializeProvider(types); - - // I'd love to find a way to generalize all of these, but they each need a different set of things - // Note that ORDER MATTERS here. The environments will be tried in this order. Do not reorder this array. - const routeExpression = routeExpressionProvider([ - thread({ onFunctionNotFound, serialize, deserialize }), - serverEnv({ onFunctionNotFound, request, server }), - browser({ onFunctionNotFound, socket, serialize, deserialize }), - ]); - - function onFunctionNotFound(ast, context) { - return routeExpression(ast, context); - } - - socket.on('getFunctionList', () => { - socket.emit('functionList', server.plugins.interpreter.serverFunctions.toJS()); - }); - - socket.on('run', async ({ ast, context, id }) => { - try { - const value = await routeExpression(ast, deserialize(context)); - socket.emit(`resp:${id}`, { type: 'msgSuccess', value: serialize(value) }); - } catch (err) { - // TODO: I don't think it is possible to hit this right now? Maybe ever? - socket.emit(`resp:${id}`, { type: 'msgError', value: err }); - } - }); - - socket.on('disconnect', () => { - // remove all listeners on disconnect - socket.removeAllListeners(); - }); - }); -} diff --git a/x-pack/plugins/canvas/public/components/app/index.js b/x-pack/plugins/canvas/public/components/app/index.js index 605ca490bfb39..6dd7e8fdd21af 100644 --- a/x-pack/plugins/canvas/public/components/app/index.js +++ b/x-pack/plugins/canvas/public/components/app/index.js @@ -5,7 +5,7 @@ */ import { functionsRegistry } from 'plugins/interpreter/functions_registry'; -import { getInterpreter, updateInterpreterFunctions } from 'plugins/interpreter/interpreter'; +import { getInterpreter } from 'plugins/interpreter/interpreter'; import { loadBrowserRegistries } from '@kbn/interpreter/public'; import { connect } from 'react-redux'; import { compose, withProps } from 'recompose'; @@ -55,7 +55,7 @@ const mapDispatchToProps = dispatch => ({ await getInterpreter(); // initialize the socket and interpreter loadPrivateBrowserFunctions(functionsRegistry); - await updateInterpreterFunctions(); + await loadBrowserRegistries(types, basePath); // set app state to ready diff --git a/x-pack/plugins/canvas/server/lib/normalize_type.js b/x-pack/plugins/canvas/server/lib/normalize_type.js index be2deeecc9a21..b1dd880334308 100644 --- a/x-pack/plugins/canvas/server/lib/normalize_type.js +++ b/x-pack/plugins/canvas/server/lib/normalize_type.js @@ -19,7 +19,7 @@ export function normalizeType(type) { 'token_count', '_version', ], - date: ['date'], + date: ['date', 'datetime'], boolean: ['boolean'], null: ['null'], }; diff --git a/yarn.lock b/yarn.lock index 66dc27e569585..283f071d867f0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19693,7 +19693,7 @@ socket.io-adapter@~1.1.0: resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz#2a805e8a14d6372124dd9159ad4502f8cb07f06b" integrity sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs= -socket.io-client@2.1.1, socket.io-client@^2.1.1: +socket.io-client@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.1.1.tgz#dcb38103436ab4578ddb026638ae2f21b623671f" integrity sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ== @@ -19722,7 +19722,7 @@ socket.io-parser@~3.2.0: debug "~3.1.0" isarray "2.0.1" -socket.io@2.1.1, socket.io@^2.1.1: +socket.io@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.1.1.tgz#a069c5feabee3e6b214a75b40ce0652e1cfb9980" integrity sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA== From cd919644f65ef0c5882b0104dad4be2c05cc6238 Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Thu, 31 Jan 2019 21:07:16 -0500 Subject: [PATCH 03/13] Remove console log and commented code... --- .../core_plugins/interpreter/server/routes/server_functions.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/legacy/core_plugins/interpreter/server/routes/server_functions.js b/src/legacy/core_plugins/interpreter/server/routes/server_functions.js index cb39f5f341b78..3bd6e65c96c2f 100644 --- a/src/legacy/core_plugins/interpreter/server/routes/server_functions.js +++ b/src/legacy/core_plugins/interpreter/server/routes/server_functions.js @@ -28,8 +28,6 @@ export function registerServerFunctions(server) { method: 'POST', path: `${API_ROUTE}/fns/{functionName}`, async handler(req) { - console.log(`POST ${API_ROUTE}/fn/${req.params.functionName} `); - const types = server.plugins.interpreter.types.toJS(); const { deserialize } = serializeProvider(types); const { functionName } = req.params; @@ -42,7 +40,6 @@ export function registerServerFunctions(server) { const handlers = await createHandlers(req, server); const result = await fnDef.fn(deserialize(context), args, handlers); - // const result = await invokeFunction(fnDef, deserialize(context), args, types, handlers); return result; }, From 1c5c087e42cd11f83bb2a375b93b0d7bc46639b7 Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Fri, 1 Feb 2019 08:54:40 -0500 Subject: [PATCH 04/13] Add tests for kbn interpreter --- .../kbn-interpreter/src/public/interpreter.js | 32 ++------ .../src/public/interpreter.test.js | 80 +++++++++++++++++++ .../interpreter/public/interpreter.js | 3 +- .../interpreter/public/interpreter.test.js | 78 ------------------ 4 files changed, 86 insertions(+), 107 deletions(-) create mode 100644 packages/kbn-interpreter/src/public/interpreter.test.js delete mode 100644 src/legacy/core_plugins/interpreter/public/interpreter.test.js diff --git a/packages/kbn-interpreter/src/public/interpreter.js b/packages/kbn-interpreter/src/public/interpreter.js index 5b1c525dbdff6..29ba0b49aefca 100644 --- a/packages/kbn-interpreter/src/public/interpreter.js +++ b/packages/kbn-interpreter/src/public/interpreter.js @@ -17,36 +17,13 @@ * under the License. */ +import { kfetch } from 'ui/kfetch'; import { socketInterpreterProvider } from '../common/interpreter/socket_interpret'; import { serializeProvider } from '../common/lib/serialize'; import { createHandlers } from './create_handlers'; -// This should be moved to its own module, and will be where -// HTTP batching and streaming happens. -function createAjaxFunction(kbnVersion, basePath) { - return async (url, opts = {}) => { - const result = await fetch(`${basePath}${url}`, { - ...opts, - headers: { - ...(opts.headers || {}), - 'kbn-version': kbnVersion, - 'Content-Type': 'application/json', - }, - }); - - const response = await result.json(); - - if (!result.ok) { - throw response; - } - - return response; - }; -} - -export async function initializeInterpreter(kbnVersion, basePath, typesRegistry, functionsRegistry) { - const ajax = createAjaxFunction(kbnVersion, basePath); - const serverFunctionList = await ajax(`/api/canvas/fns`); +export async function initializeInterpreter(typesRegistry, functionsRegistry) { + const serverFunctionList = await kfetch({ pathname: '/api/canvas/fns' }); // For every sever-side function, register a client-side // function that matches its definition, but which simply @@ -57,7 +34,8 @@ export async function initializeInterpreter(kbnVersion, basePath, typesRegistry, async fn(context, args) { const types = typesRegistry.toJS(); const { serialize } = serializeProvider(types); - const result = await ajax(`/api/canvas/fns/${functionName}`, { + const result = await kfetch({ + pathname: `/api/canvas/fns/${functionName}`, method: 'POST', body: JSON.stringify({ args, diff --git a/packages/kbn-interpreter/src/public/interpreter.test.js b/packages/kbn-interpreter/src/public/interpreter.test.js new file mode 100644 index 0000000000000..6d2747b8fcb1f --- /dev/null +++ b/packages/kbn-interpreter/src/public/interpreter.test.js @@ -0,0 +1,80 @@ +/* + * 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 mockFetch from 'ui/kfetch'; +import { initializeInterpreter } from './interpreter'; + +jest.mock('ui/kfetch', () => ({ + kfetch: jest.fn(() => Promise.resolve({})), +})); + +jest.mock('../common/interpreter/socket_interpret', () => ({ + socketInterpreterProvider: () => () => ({}), +})); + +jest.mock('../common/lib/serialize', () => ({ + serializeProvider: () => ({ serialize: () => ({}) }), +})); + +jest.mock('./create_handlers', () => ({ + createHandlers: () => ({}), +})); + +describe('kbn-interpreter/interpreter', () => { + it('loads server-side functions', async () => { + mockFetch.kfetch.mockResolvedValue({}); + + await initializeInterpreter({ toJS: () => ({}) }, ({ register: () => {} })); + + expect(mockFetch.kfetch).toHaveBeenCalledTimes(1); + expect(mockFetch.kfetch).toHaveBeenCalledWith({ pathname: '/api/canvas/fns' }); + }); + + it('registers client-side functions that pass through to the server', async () => { + mockFetch.kfetch.mockResolvedValue({ + hello: { name: 'hello' }, + world: { name: 'world' }, + }); + + const register = jest.fn(); + + await initializeInterpreter({ 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(mockFetch.kfetch).toHaveBeenCalledWith({ + pathname: '/api/canvas/fns/hello', + method: 'POST', + body: JSON.stringify({ args, context }), + }); + }); + +}); diff --git a/src/legacy/core_plugins/interpreter/public/interpreter.js b/src/legacy/core_plugins/interpreter/public/interpreter.js index e2551dd3cbd8e..89b8e7460cced 100644 --- a/src/legacy/core_plugins/interpreter/public/interpreter.js +++ b/src/legacy/core_plugins/interpreter/public/interpreter.js @@ -27,7 +27,6 @@ import { renderFunctionsRegistry } from './render_functions_registry'; import { visualization } from './renderers/visualization'; const basePath = chrome.getInjected('serverBasePath'); -const kbnVersion = chrome.getXsrfToken(); const types = { browserFunctions: functionsRegistry, @@ -47,7 +46,7 @@ let _interpreterPromise; const initialize = async () => { await loadBrowserRegistries(types, basePath); - initializeInterpreter(kbnVersion, basePath, typesRegistry, functionsRegistry).then(interpreter => { + initializeInterpreter(typesRegistry, functionsRegistry).then(interpreter => { _resolve({ interpreter }); }); }; diff --git a/src/legacy/core_plugins/interpreter/public/interpreter.test.js b/src/legacy/core_plugins/interpreter/public/interpreter.test.js deleted file mode 100644 index 4408894f30a58..0000000000000 --- a/src/legacy/core_plugins/interpreter/public/interpreter.test.js +++ /dev/null @@ -1,78 +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 { getInterpreter } from './interpreter'; - -import { createSocket } from '@kbn/interpreter/public'; -import { functionsRegistry } from './functions_registry'; - -jest.mock('@kbn/interpreter/public', () => ({ - createSocket: jest.fn(), - initializeInterpreter: jest.fn(() => Promise.resolve()), - loadBrowserRegistries: jest.fn(() => Promise.resolve()) -})); - -jest.mock('ui/chrome', () => ({ - getBasePath: jest.fn(() => '/abc/s/123'), - getInjected: jest.fn(config => { // eslint-disable-line no-unused-vars - return config === 'serverBasePath' ? '/abc' : '/123'; - }), -})); - -jest.mock('./functions', () => ({ - functions: [jest.fn()] -})); - -jest.mock('./render_functions_registry', () => ({ - renderFunctionsRegistry: { - register: jest.fn() - } -})); - -jest.mock('./renderers/visualization', () => ({ - visualization: jest.fn() -})); - -jest.mock('./functions_registry', () => ({ - functionsRegistry: { - register: jest.fn() - } -})); - -jest.mock('./types_registry', () => ({ - typesRegistry: jest.fn() -})); - -describe('Core Interpreter', () => { - - beforeEach(() => { - jest.resetModules(); - }); - - describe('getInterpreter', () => { - - it('calls createSocket with the correct arguments', async () => { - await getInterpreter(); - expect(createSocket).toHaveBeenCalledTimes(1); - expect(createSocket).toHaveBeenCalledWith('/abc', functionsRegistry); - }); - - }); - -}); From 374342b951e3d8464ccf9ec25051b7d1ce772fd5 Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Fri, 1 Feb 2019 10:37:44 -0500 Subject: [PATCH 05/13] Fix Canvas kfetch dependency --- .../kbn-interpreter/src/public/interpreter.js | 4 +--- .../src/public/interpreter.test.js | 21 +++++++------------ .../interpreter/public/interpreter.js | 3 ++- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/packages/kbn-interpreter/src/public/interpreter.js b/packages/kbn-interpreter/src/public/interpreter.js index 29ba0b49aefca..2bcf22bf55626 100644 --- a/packages/kbn-interpreter/src/public/interpreter.js +++ b/packages/kbn-interpreter/src/public/interpreter.js @@ -17,12 +17,11 @@ * under the License. */ -import { kfetch } from 'ui/kfetch'; import { socketInterpreterProvider } from '../common/interpreter/socket_interpret'; import { serializeProvider } from '../common/lib/serialize'; import { createHandlers } from './create_handlers'; -export async function initializeInterpreter(typesRegistry, functionsRegistry) { +export async function initializeInterpreter(kfetch, typesRegistry, functionsRegistry) { const serverFunctionList = await kfetch({ pathname: '/api/canvas/fns' }); // For every sever-side function, register a client-side @@ -49,7 +48,6 @@ export async function initializeInterpreter(typesRegistry, functionsRegistry) { }); const interpretAst = async (ast, context, handlers) => { - // Load plugins before attempting to get functions, otherwise this gets racey const interpretFn = await socketInterpreterProvider({ types: typesRegistry.toJS(), handlers: { ...handlers, ...createHandlers() }, diff --git a/packages/kbn-interpreter/src/public/interpreter.test.js b/packages/kbn-interpreter/src/public/interpreter.test.js index 6d2747b8fcb1f..959000c57996c 100644 --- a/packages/kbn-interpreter/src/public/interpreter.test.js +++ b/packages/kbn-interpreter/src/public/interpreter.test.js @@ -17,13 +17,8 @@ * under the License. */ -import mockFetch from 'ui/kfetch'; import { initializeInterpreter } from './interpreter'; -jest.mock('ui/kfetch', () => ({ - kfetch: jest.fn(() => Promise.resolve({})), -})); - jest.mock('../common/interpreter/socket_interpret', () => ({ socketInterpreterProvider: () => () => ({}), })); @@ -38,23 +33,23 @@ jest.mock('./create_handlers', () => ({ describe('kbn-interpreter/interpreter', () => { it('loads server-side functions', async () => { - mockFetch.kfetch.mockResolvedValue({}); + const kfetch = jest.fn(async () => ({})); - await initializeInterpreter({ toJS: () => ({}) }, ({ register: () => {} })); + await initializeInterpreter(kfetch, { toJS: () => ({}) }, ({ register: () => {} })); - expect(mockFetch.kfetch).toHaveBeenCalledTimes(1); - expect(mockFetch.kfetch).toHaveBeenCalledWith({ pathname: '/api/canvas/fns' }); + expect(kfetch).toHaveBeenCalledTimes(1); + expect(kfetch).toHaveBeenCalledWith({ pathname: '/api/canvas/fns' }); }); it('registers client-side functions that pass through to the server', async () => { - mockFetch.kfetch.mockResolvedValue({ + const kfetch = jest.fn(async () => ({ hello: { name: 'hello' }, world: { name: 'world' }, - }); + })); const register = jest.fn(); - await initializeInterpreter({ toJS: () => ({}) }, ({ register })); + await initializeInterpreter(kfetch, { toJS: () => ({}) }, ({ register })); expect(register).toHaveBeenCalledTimes(2); @@ -70,7 +65,7 @@ describe('kbn-interpreter/interpreter', () => { await hello.fn(context, args); - expect(mockFetch.kfetch).toHaveBeenCalledWith({ + expect(kfetch).toHaveBeenCalledWith({ pathname: '/api/canvas/fns/hello', method: 'POST', body: JSON.stringify({ args, context }), diff --git a/src/legacy/core_plugins/interpreter/public/interpreter.js b/src/legacy/core_plugins/interpreter/public/interpreter.js index 89b8e7460cced..6b0a7d24178a6 100644 --- a/src/legacy/core_plugins/interpreter/public/interpreter.js +++ b/src/legacy/core_plugins/interpreter/public/interpreter.js @@ -19,6 +19,7 @@ import { initializeInterpreter, loadBrowserRegistries } from '@kbn/interpreter/public'; +import { kfetch } from 'ui/kfetch'; import chrome from 'ui/chrome'; import { functions } from './functions'; import { functionsRegistry } from './functions_registry'; @@ -46,7 +47,7 @@ let _interpreterPromise; const initialize = async () => { await loadBrowserRegistries(types, basePath); - initializeInterpreter(typesRegistry, functionsRegistry).then(interpreter => { + initializeInterpreter(kfetch, typesRegistry, functionsRegistry).then(interpreter => { _resolve({ interpreter }); }); }; From f05202bdfab02254e5919b9e1ebbe2ae40988bcc Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Fri, 1 Feb 2019 12:08:07 -0500 Subject: [PATCH 06/13] Rename socketInterpreterProvider to interpreterProvider --- .../{socket_interpret.js => interpreter_provider.js} | 2 +- packages/kbn-interpreter/src/public/interpreter.js | 4 ++-- packages/kbn-interpreter/src/public/interpreter.test.js | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename packages/kbn-interpreter/src/common/interpreter/{socket_interpret.js => interpreter_provider.js} (98%) diff --git a/packages/kbn-interpreter/src/common/interpreter/socket_interpret.js b/packages/kbn-interpreter/src/common/interpreter/interpreter_provider.js similarity index 98% rename from packages/kbn-interpreter/src/common/interpreter/socket_interpret.js rename to packages/kbn-interpreter/src/common/interpreter/interpreter_provider.js index 1ea95e0f5f6f1..d3a21f4c743d4 100644 --- a/packages/kbn-interpreter/src/common/interpreter/socket_interpret.js +++ b/packages/kbn-interpreter/src/common/interpreter/interpreter_provider.js @@ -33,7 +33,7 @@ import { interpretProvider } from './interpret'; socket: the socket to communicate over */ -export function socketInterpreterProvider({ +export function interpreterProvider({ types, functions, handlers, diff --git a/packages/kbn-interpreter/src/public/interpreter.js b/packages/kbn-interpreter/src/public/interpreter.js index 2bcf22bf55626..311eb4e409d06 100644 --- a/packages/kbn-interpreter/src/public/interpreter.js +++ b/packages/kbn-interpreter/src/public/interpreter.js @@ -17,7 +17,7 @@ * under the License. */ -import { socketInterpreterProvider } from '../common/interpreter/socket_interpret'; +import { interpreterProvider } from '../common/interpreter/interpreter_provider'; import { serializeProvider } from '../common/lib/serialize'; import { createHandlers } from './create_handlers'; @@ -48,7 +48,7 @@ export async function initializeInterpreter(kfetch, typesRegistry, functionsRegi }); const interpretAst = async (ast, context, handlers) => { - const interpretFn = await socketInterpreterProvider({ + const interpretFn = await interpreterProvider({ types: typesRegistry.toJS(), handlers: { ...handlers, ...createHandlers() }, functions: functionsRegistry.toJS(), diff --git a/packages/kbn-interpreter/src/public/interpreter.test.js b/packages/kbn-interpreter/src/public/interpreter.test.js index 959000c57996c..ffe271c2e72de 100644 --- a/packages/kbn-interpreter/src/public/interpreter.test.js +++ b/packages/kbn-interpreter/src/public/interpreter.test.js @@ -19,8 +19,8 @@ import { initializeInterpreter } from './interpreter'; -jest.mock('../common/interpreter/socket_interpret', () => ({ - socketInterpreterProvider: () => () => ({}), +jest.mock('../common/interpreter/interpreter_provider', () => ({ + interpreterProvider: () => () => ({}), })); jest.mock('../common/lib/serialize', () => ({ From d32ccf8d553c1b534bd1f041c72f99d00d86011b Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Mon, 4 Feb 2019 10:32:47 -0500 Subject: [PATCH 07/13] Remove remaining socket references, remove unused interpreter threading --- packages/kbn-interpreter/src/common/index.js | 2 +- .../src/common/interpreter/interpret.js | 11 +- .../interpreter/interpreter_provider.js | 81 ------------ .../src/public/create_handlers.js | 2 +- .../kbn-interpreter/src/public/interpreter.js | 9 +- .../src/public/interpreter.test.js | 6 +- .../interpreter/server/lib/get_request.js | 2 +- .../server/lib/route_expression/browser.js | 67 ---------- .../server/lib/route_expression/server.js | 7 +- .../lib/route_expression/thread/babeled.js | 21 --- .../lib/route_expression/thread/index.js | 125 ------------------ .../lib/route_expression/thread/worker.js | 86 ------------ 12 files changed, 17 insertions(+), 402 deletions(-) delete mode 100644 packages/kbn-interpreter/src/common/interpreter/interpreter_provider.js delete mode 100644 src/legacy/core_plugins/interpreter/server/lib/route_expression/browser.js delete mode 100644 src/legacy/core_plugins/interpreter/server/lib/route_expression/thread/babeled.js delete mode 100644 src/legacy/core_plugins/interpreter/server/lib/route_expression/thread/index.js delete mode 100644 src/legacy/core_plugins/interpreter/server/lib/route_expression/thread/worker.js diff --git a/packages/kbn-interpreter/src/common/index.js b/packages/kbn-interpreter/src/common/index.js index 7205b76839c8c..13db04d5e1d75 100644 --- a/packages/kbn-interpreter/src/common/index.js +++ b/packages/kbn-interpreter/src/common/index.js @@ -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'; diff --git a/packages/kbn-interpreter/src/common/interpreter/interpret.js b/packages/kbn-interpreter/src/common/interpreter/interpret.js index 934f35b856d82..cc84a519c6a87 100644 --- a/packages/kbn-interpreter/src/common/interpreter/interpret.js +++ b/packages/kbn-interpreter/src/common/interpreter/interpret.js @@ -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); @@ -57,12 +57,7 @@ export function interpretProvider(config) { // 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); - } + createError({ message: `Function ${fnDef.name} could not be found.` }); } try { diff --git a/packages/kbn-interpreter/src/common/interpreter/interpreter_provider.js b/packages/kbn-interpreter/src/common/interpreter/interpreter_provider.js deleted file mode 100644 index d3a21f4c743d4..0000000000000 --- a/packages/kbn-interpreter/src/common/interpreter/interpreter_provider.js +++ /dev/null @@ -1,81 +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 uuid from 'uuid/v4'; -import { getByAlias } from '../lib/get_by_alias'; -import { serializeProvider } from '../lib/serialize'; -import { interpretProvider } from './interpret'; - -/* - Returns an interpet function that can shuttle partial ASTs and context between instances of itself over a socket - This is the interpreter that gets called during interactive sessions in the browser and communicates with the - same instance on the backend - - types: a registry of types - functions: registry of known functions - referableFunctions: An array, or a promise for an array, with a list of functions that are available to be defered to - socket: the socket to communicate over -*/ - -export function interpreterProvider({ - types, - functions, - handlers, - referableFunctions, - socket, -}) { - // Return the interpet() function - return interpretProvider({ - types, - functions, - handlers, - - onFunctionNotFound: (ast, context) => { - // Get the name of the function that wasn't found - const functionName = ast.chain[0].function; - - // Get the list of functions that are known elsewhere - return Promise.resolve(referableFunctions).then(referableFunctionMap => { - // Check if the not-found function is in the list of alternatives, if not, throw - if (!getByAlias(referableFunctionMap, functionName)) { - throw new Error(`Function not found: ${functionName}`); - } - - // set a unique message ID so the code knows what response to process - const id = uuid(); - - return new Promise(resolve => { - const { serialize, deserialize } = serializeProvider(types); - - // This will receive {type: [msgSuccess || msgError] value: foo} - // However it doesn't currently do anything with it. Which means `value`, regardless - // of failure or success, needs to be something the interpreters would logically return - // er, a primative or a {type: foo} object - const listener = resp => resolve(deserialize(resp.value)); - - socket.once(`resp:${id}`, listener); - - // Go run the remaining AST and context somewhere else, meaning either the browser or the server, depending on - // where this file was loaded - socket.emit('run', { ast, context: serialize(context), id }); - }); - }); - }, - }); -} diff --git a/packages/kbn-interpreter/src/public/create_handlers.js b/packages/kbn-interpreter/src/public/create_handlers.js index 3446a945ae76e..46e85411c5895 100644 --- a/packages/kbn-interpreter/src/public/create_handlers.js +++ b/packages/kbn-interpreter/src/public/create_handlers.js @@ -17,7 +17,7 @@ * under the License. */ -export function createHandlers(/*socket*/) { +export function createHandlers() { return { environment: 'client', }; diff --git a/packages/kbn-interpreter/src/public/interpreter.js b/packages/kbn-interpreter/src/public/interpreter.js index 311eb4e409d06..fe81567abc2a6 100644 --- a/packages/kbn-interpreter/src/public/interpreter.js +++ b/packages/kbn-interpreter/src/public/interpreter.js @@ -17,12 +17,14 @@ * under the License. */ -import { interpreterProvider } from '../common/interpreter/interpreter_provider'; +import { interpreterProvider } from '../common/interpreter/interpret'; import { serializeProvider } from '../common/lib/serialize'; import { createHandlers } from './create_handlers'; +export const FUNCTIONS_URL = '/api/canvas/fns'; + export async function initializeInterpreter(kfetch, typesRegistry, functionsRegistry) { - const serverFunctionList = await kfetch({ pathname: '/api/canvas/fns' }); + const serverFunctionList = await kfetch({ pathname: FUNCTIONS_URL }); // For every sever-side function, register a client-side // function that matches its definition, but which simply @@ -34,7 +36,7 @@ export async function initializeInterpreter(kfetch, typesRegistry, functionsRegi const types = typesRegistry.toJS(); const { serialize } = serializeProvider(types); const result = await kfetch({ - pathname: `/api/canvas/fns/${functionName}`, + pathname: `${FUNCTIONS_URL}/${functionName}`, method: 'POST', body: JSON.stringify({ args, @@ -52,7 +54,6 @@ export async function initializeInterpreter(kfetch, typesRegistry, functionsRegi types: typesRegistry.toJS(), handlers: { ...handlers, ...createHandlers() }, functions: functionsRegistry.toJS(), - referableFunctions: serverFunctionList, }); return interpretFn(ast, context); }; diff --git a/packages/kbn-interpreter/src/public/interpreter.test.js b/packages/kbn-interpreter/src/public/interpreter.test.js index ffe271c2e72de..43c98c2b52ffc 100644 --- a/packages/kbn-interpreter/src/public/interpreter.test.js +++ b/packages/kbn-interpreter/src/public/interpreter.test.js @@ -17,7 +17,7 @@ * under the License. */ -import { initializeInterpreter } from './interpreter'; +import { initializeInterpreter, FUNCTIONS_URL } from './interpreter'; jest.mock('../common/interpreter/interpreter_provider', () => ({ interpreterProvider: () => () => ({}), @@ -38,7 +38,7 @@ describe('kbn-interpreter/interpreter', () => { await initializeInterpreter(kfetch, { toJS: () => ({}) }, ({ register: () => {} })); expect(kfetch).toHaveBeenCalledTimes(1); - expect(kfetch).toHaveBeenCalledWith({ pathname: '/api/canvas/fns' }); + expect(kfetch).toHaveBeenCalledWith({ pathname: FUNCTIONS_URL }); }); it('registers client-side functions that pass through to the server', async () => { @@ -66,7 +66,7 @@ describe('kbn-interpreter/interpreter', () => { await hello.fn(context, args); expect(kfetch).toHaveBeenCalledWith({ - pathname: '/api/canvas/fns/hello', + pathname: `${FUNCTIONS_URL}/hello`, method: 'POST', body: JSON.stringify({ args, context }), }); diff --git a/src/legacy/core_plugins/interpreter/server/lib/get_request.js b/src/legacy/core_plugins/interpreter/server/lib/get_request.js index 2b29b05fd07aa..dee941e79ed66 100644 --- a/src/legacy/core_plugins/interpreter/server/lib/get_request.js +++ b/src/legacy/core_plugins/interpreter/server/lib/get_request.js @@ -36,7 +36,7 @@ export function getRequest(server, { headers }) { new Error(`Auth request failed: [${res.statusCode}] ${res.result.message}`) ); } - throw boom.unauthorized('Failed to authenticate socket connection'); + throw boom.unauthorized('Failed to authenticate connection'); } return res.request; diff --git a/src/legacy/core_plugins/interpreter/server/lib/route_expression/browser.js b/src/legacy/core_plugins/interpreter/server/lib/route_expression/browser.js deleted file mode 100644 index 4b3d43ef9acae..0000000000000 --- a/src/legacy/core_plugins/interpreter/server/lib/route_expression/browser.js +++ /dev/null @@ -1,67 +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 uuid from 'uuid/v4'; - -export const browser = ({ socket, serialize, deserialize }) => { - // Note that we need to be careful about how many times routeExpressionProvider is called, because of the socket.once below. - // It's too bad we can't get a list of browser plugins on the server - - let getFunctionsPromise; - socket.on('updateFunctionList', () => { - getFunctionsPromise = undefined; - }); - - const getFunctions = async () => { - if (!getFunctionsPromise) { - getFunctionsPromise = new Promise(resolve => { - socket.once('functionList', resolve); - socket.emit('getFunctionList'); - }); - } - - return Object.keys(await getFunctionsPromise); - }; - - return { - interpret: (ast, context) => { - return new Promise(async (resolve, reject) => { - await getFunctions(); - const id = uuid(); - const listener = resp => { - if (resp.type === 'msgError') { - const { value } = resp; - // cast error strings back into error instances - const err = value instanceof Error ? value : new Error(value); - if (value.stack) err.stack = value.stack; - // Reject's with a legit error. Check! Environments should always reject with an error when something bad happens - reject(err); - } else { - resolve(deserialize(resp.value)); - } - }; - - // {type: msgSuccess or msgError, value: foo}. Doesn't matter if it's success or error, we do the same thing for now - socket.once(`resp:${id}`, listener); - - socket.emit('run', { ast, context: serialize(context), id }); - }); - }, getFunctions - }; -}; diff --git a/src/legacy/core_plugins/interpreter/server/lib/route_expression/server.js b/src/legacy/core_plugins/interpreter/server/lib/route_expression/server.js index 663e842355784..306dc550461ae 100644 --- a/src/legacy/core_plugins/interpreter/server/lib/route_expression/server.js +++ b/src/legacy/core_plugins/interpreter/server/lib/route_expression/server.js @@ -17,19 +17,18 @@ * under the License. */ -import { interpretProvider } from '@kbn/interpreter/common'; +import { interpreterProvider } from '@kbn/interpreter/common'; import { createHandlers } from '../create_handlers'; -export const server = async ({ onFunctionNotFound, server, request }) => { +export const server = async ({ server, request }) => { const { serverFunctions, types } = server.plugins.interpreter; return { interpret: (ast, context) => { - const interpret = interpretProvider({ + const interpret = interpreterProvider({ types: types.toJS(), functions: serverFunctions.toJS(), handlers: createHandlers(request, server), - onFunctionNotFound, }); return interpret(ast, context); diff --git a/src/legacy/core_plugins/interpreter/server/lib/route_expression/thread/babeled.js b/src/legacy/core_plugins/interpreter/server/lib/route_expression/thread/babeled.js deleted file mode 100644 index 8d811e3feb7e6..0000000000000 --- a/src/legacy/core_plugins/interpreter/server/lib/route_expression/thread/babeled.js +++ /dev/null @@ -1,21 +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. - */ - -require('../../../../../../../setup_node_env'); -require('./worker'); diff --git a/src/legacy/core_plugins/interpreter/server/lib/route_expression/thread/index.js b/src/legacy/core_plugins/interpreter/server/lib/route_expression/thread/index.js deleted file mode 100644 index d16e207b6bb53..0000000000000 --- a/src/legacy/core_plugins/interpreter/server/lib/route_expression/thread/index.js +++ /dev/null @@ -1,125 +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 { fork } from 'child_process'; -import { resolve } from 'path'; -import uuid from 'uuid/v4'; - -// If the worker doesn't response in 10s, kill it. -const WORKER_TIMEOUT = 20000; -const workerPath = resolve(__dirname, 'babeled.js'); -const heap = {}; -let worker = null; - -export function getWorker() { - if (worker) return worker; - worker = fork(workerPath, {}); - - // handle run requests - worker.on('message', msg => { - const { type, value, id } = msg; - if (type === 'run') { - const { threadId } = msg; - const { ast, context } = value; - if (heap[threadId]) { - heap[threadId] - .onFunctionNotFound(ast, context) - .then(value => { - worker.send({ type: 'msgSuccess', id, value: value }); - }) - .catch(e => heap[threadId].reject(e)); - } - } - - if (type === 'msgSuccess' && heap[id]) heap[id].resolve(value); - - // TODO: I don't think it is even possible to hit this - if (type === 'msgError' && heap[id]) heap[id].reject(new Error(value)); - }); - - // handle exit event, fired when we kill the worker or it just dies - worker.on('exit', () => { - // NOTE: No need to look for 'code' or 'signal', if it isn't running, it's an issue - - // clean up any listeners on the worker before replacing it - worker.removeAllListeners(); - - // Restart immediately on exit since node takes a couple seconds to spin up - worker = null; - worker = getWorker(); - }); - - return worker; -} - -// All serialize/deserialize must occur in here. We should not return serialized stuff to the expressionRouter -export const thread = ({ onFunctionNotFound, serialize, deserialize }) => { - const getWorkerFunctions = new Promise(resolve => { - const worker = getWorker(); - - function functionListHandler(msg) { - // wait for the function list message - if (msg.type === 'functionList') { - // tear dowm the listener once the function list is provided - worker.removeListener('message', functionListHandler); - resolve(msg.value); - } - } - worker.on('message', functionListHandler); - - worker.send({ type: 'getFunctions' }); - }); - - return getWorkerFunctions.then(functions => { - return { - interpret: (ast, context) => { - const worker = getWorker(); - const id = uuid(); - worker.send({ type: 'run', id, value: { ast, context: serialize(context) } }); - - return new Promise((resolve, reject) => { - heap[id] = { - time: new Date().getTime(), - resolve: value => { - delete heap[id]; - resolve(deserialize(value)); - }, - reject: e => { - delete heap[id]; - reject(e); - }, - onFunctionNotFound: (ast, context) => - onFunctionNotFound(ast, deserialize(context)).then(serialize), - }; - - // kill the worker after the timeout is exceeded - setTimeout(() => { - if (!heap[id]) return; // Looks like this has already been cleared from the heap. - if (worker) worker.kill(); - - // The heap will be cleared because the reject on heap will delete its own id - heap[id].reject(new Error('Request timed out')); - }, WORKER_TIMEOUT); - }); - }, - - getFunctions: () => functions, - }; - }); -}; diff --git a/src/legacy/core_plugins/interpreter/server/lib/route_expression/thread/worker.js b/src/legacy/core_plugins/interpreter/server/lib/route_expression/thread/worker.js deleted file mode 100644 index cef00153e0fc2..0000000000000 --- a/src/legacy/core_plugins/interpreter/server/lib/route_expression/thread/worker.js +++ /dev/null @@ -1,86 +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 uuid from 'uuid/v4'; -import { populateServerRegistries } from '@kbn/interpreter/server'; -import { interpretProvider, serializeProvider, FunctionsRegistry, TypesRegistry } from '@kbn/interpreter/common'; - -// We actually DO need populateServerRegistries here since this is a different node process -const registries = { - commonFunctions: new FunctionsRegistry(), - types: new TypesRegistry(), -}; - -const pluginsReady = populateServerRegistries(registries); -const heap = {}; - -process.on('message', msg => { - const { type, id, value } = msg; - const threadId = id; - - pluginsReady.then(({ commonFunctions, types }) => { - types = types.toJS(); - const { serialize, deserialize } = serializeProvider(types); - const interpret = interpretProvider({ - types, - functions: commonFunctions.toJS(), - handlers: { environment: 'serverThreaded' }, - onFunctionNotFound: (ast, context) => { - const id = uuid(); - // This needs to send a message to the main thread, and receive a response. Uhg. - process.send({ - type: 'run', - threadId, - id, - value: { - ast, - context: serialize(context), - }, - }); - - // Note that there is no facility to reject here. That's because this would only occur as the result of something that happens in the main thread, and we reject there - return new Promise(resolve => { - heap[id] = { resolve }; - }); - }, - }); - - if (type === 'getFunctions') { - process.send({ type: 'functionList', value: Object.keys(commonFunctions.toJS()) }); - } - - if (type === 'msgSuccess') { - heap[id].resolve(deserialize(value)); - delete heap[id]; - } - - if (type === 'run') { - const { ast, context } = msg.value; - - interpret(ast, deserialize(context)) - .then(value => { - process.send({ type: 'msgSuccess', value: serialize(value), id }); - }) - // TODO: I don't think it is even possible to hit this - .catch(value => { - process.send({ type: 'msgError', value, id }); - }); - } - }); -}); From b43dd0416a2f8e1907fb455e26ec71d0adfb5cb5 Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Mon, 4 Feb 2019 10:54:57 -0500 Subject: [PATCH 08/13] Fix invalid fnName reference --- packages/kbn-interpreter/src/common/interpreter/interpret.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-interpreter/src/common/interpreter/interpret.js b/packages/kbn-interpreter/src/common/interpreter/interpret.js index cc84a519c6a87..cabccdc19de3f 100644 --- a/packages/kbn-interpreter/src/common/interpreter/interpret.js +++ b/packages/kbn-interpreter/src/common/interpreter/interpret.js @@ -57,7 +57,7 @@ export function interpreterProvider(config) { // 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) { - createError({ message: `Function ${fnDef.name} could not be found.` }); + createError({ message: `Function ${fnName} could not be found.` }); } try { From 1a8c06c7534306249863d05bf8c5f227243b1370 Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Mon, 4 Feb 2019 10:56:07 -0500 Subject: [PATCH 09/13] Remove unecessary comment --- packages/kbn-interpreter/src/common/interpreter/interpret.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/kbn-interpreter/src/common/interpreter/interpret.js b/packages/kbn-interpreter/src/common/interpreter/interpret.js index cabccdc19de3f..64a5719afe27a 100644 --- a/packages/kbn-interpreter/src/common/interpreter/interpret.js +++ b/packages/kbn-interpreter/src/common/interpreter/interpret.js @@ -54,8 +54,6 @@ export function interpreterProvider(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) { createError({ message: `Function ${fnName} could not be found.` }); } From fe7e2da06c2aec664833a637ff179338f79ec00d Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Mon, 4 Feb 2019 11:00:48 -0500 Subject: [PATCH 10/13] Remove Canvas' socket-based get_request --- .../interpreter/server/lib/get_request.js | 44 ------------------- 1 file changed, 44 deletions(-) delete mode 100644 src/legacy/core_plugins/interpreter/server/lib/get_request.js diff --git a/src/legacy/core_plugins/interpreter/server/lib/get_request.js b/src/legacy/core_plugins/interpreter/server/lib/get_request.js deleted file mode 100644 index dee941e79ed66..0000000000000 --- a/src/legacy/core_plugins/interpreter/server/lib/get_request.js +++ /dev/null @@ -1,44 +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 boom from 'boom'; -import { API_ROUTE } from '../../common/constants'; - -export function getRequest(server, { headers }) { - const url = `${API_ROUTE}/ping`; - - return server - .inject({ - method: 'POST', - url, - headers, - }) - .then(res => { - if (res.statusCode !== 200) { - if (process.env.NODE_ENV !== 'production') { - console.error( - new Error(`Auth request failed: [${res.statusCode}] ${res.result.message}`) - ); - } - throw boom.unauthorized('Failed to authenticate connection'); - } - - return res.request; - }); -} From d7270b49ef3d335bc538360a1b8b9a79f6172bdb Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Mon, 4 Feb 2019 11:28:50 -0500 Subject: [PATCH 11/13] Fix interpreter reference --- packages/kbn-interpreter/src/public/interpreter.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-interpreter/src/public/interpreter.test.js b/packages/kbn-interpreter/src/public/interpreter.test.js index 43c98c2b52ffc..8bf8412a360e6 100644 --- a/packages/kbn-interpreter/src/public/interpreter.test.js +++ b/packages/kbn-interpreter/src/public/interpreter.test.js @@ -19,7 +19,7 @@ import { initializeInterpreter, FUNCTIONS_URL } from './interpreter'; -jest.mock('../common/interpreter/interpreter_provider', () => ({ +jest.mock('../common/interpreter/interpret', () => ({ interpreterProvider: () => () => ({}), })); From 116e99df06e5600b13d1b6c65a2fefc88566b05e Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Mon, 4 Feb 2019 16:16:48 -0500 Subject: [PATCH 12/13] Fix a problem with interpreter error handler --- packages/kbn-interpreter/src/common/interpreter/interpret.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-interpreter/src/common/interpreter/interpret.js b/packages/kbn-interpreter/src/common/interpreter/interpret.js index 64a5719afe27a..270e0cc042a06 100644 --- a/packages/kbn-interpreter/src/common/interpreter/interpret.js +++ b/packages/kbn-interpreter/src/common/interpreter/interpret.js @@ -55,7 +55,7 @@ export function interpreterProvider(config) { const fnDef = getByAlias(functions, fnName); if (!fnDef) { - createError({ message: `Function ${fnName} could not be found.` }); + return createError({ message: `Function ${fnName} could not be found.` }); } try { From 67286a66d47b013b50e47bae67ee81a006e7e26a Mon Sep 17 00:00:00 2001 From: Christopher Davies Date: Tue, 5 Feb 2019 08:16:23 -0500 Subject: [PATCH 13/13] Re-remove scriptjs from kbn-interpreter --- packages/kbn-interpreter/package.json | 1 - yarn.lock | 5 ----- 2 files changed, 6 deletions(-) diff --git a/packages/kbn-interpreter/package.json b/packages/kbn-interpreter/package.json index 9cf8349fcd596..290e02cb2b2e7 100644 --- a/packages/kbn-interpreter/package.json +++ b/packages/kbn-interpreter/package.json @@ -12,7 +12,6 @@ "@kbn/i18n": "1.0.0", "lodash": "npm:@elastic/lodash@3.10.1-kibana1", "lodash.clone": "^4.5.0", - "scriptjs": "^2.5.8", "uuid": "3.0.1" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index d8fb50b0c28a2..102d3fd149dcb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19255,11 +19255,6 @@ script-loader@0.7.2: dependencies: raw-loader "~0.5.1" -scriptjs@^2.5.8: - version "2.5.9" - resolved "https://registry.yarnpkg.com/scriptjs/-/scriptjs-2.5.9.tgz#343915cd2ec2ed9bfdde2b9875cd28f59394b35f" - integrity sha512-qGVDoreyYiP1pkQnbnFAUIS5AjenNwwQBdl7zeos9etl+hYKWahjRTfzAZZYBv5xNHx7vNKCmaLDQZ6Fr2AEXg== - scroll-into-view@^1.3.0: version "1.9.1" resolved "https://registry.yarnpkg.com/scroll-into-view/-/scroll-into-view-1.9.1.tgz#90c3b338422f9fddaebad90e6954790940dc9c1e"