Skip to content

Commit

Permalink
Chore: Only load on canvas (elastic#24247)
Browse files Browse the repository at this point in the history
Closes elastic#23379

Defer loading Canvas code until the application is used.

- Wait to load the window error override
- Only create socket when the app is ready

Since we need to support deferring loading the application until some async things finish (mostly fetching functions), I opted to handle this in the App component, since it was a convenient async way to hold until ready. I figure we can refactor that later.
  • Loading branch information
w33ble authored and njd5475 committed Oct 24, 2018
1 parent aeb2d5a commit c59fdbf
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 56 deletions.
4 changes: 0 additions & 4 deletions x-pack/plugins/canvas/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ export function canvas(kibana) {
hacks: [
// window.onerror override
'plugins/canvas/lib/window_error_handler.js',

// Client side plugins go here
'plugins/canvas/lib/load_expression_types.js',
'plugins/canvas/lib/load_transitions.js',
],
home: ['plugins/canvas/register_feature'],
mappings,
Expand Down
4 changes: 4 additions & 0 deletions x-pack/plugins/canvas/public/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,9 @@ import 'uiExports/savedObjectTypes';
import 'uiExports/spyModes';
import 'uiExports/fieldFormats';

// load application code
import './lib/load_expression_types';
import './lib/load_transitions';

// load the application
chrome.setRootController('canvas', CanvasRootController);
11 changes: 11 additions & 0 deletions x-pack/plugins/canvas/public/components/app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { routes } from '../../apps';
import { shortcutManager } from '../../lib/shortcut_manager';
import { getWindow } from '../../lib/get_window';
import { Router } from '../router';

export class App extends React.PureComponent {
Expand All @@ -26,6 +27,16 @@ export class App extends React.PureComponent {
return { shortcuts: shortcutManager };
}

componentDidMount() {
const win = getWindow();
win.canvasInitErrorHandler && win.canvasInitErrorHandler();
}

componentWillUnmount() {
const win = getWindow();
win.canvasRestoreErrorHandler && win.canvasRestoreErrorHandler();
}

renderError = () => {
console.error(this.props.appState);

Expand Down
17 changes: 13 additions & 4 deletions x-pack/plugins/canvas/public/components/app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

import { connect } from 'react-redux';
import { compose, withProps } from 'recompose';
import { createSocket } from '../../socket';
import { initialize as initializeInterpreter } from '../../lib/interpreter';
import { getAppReady } from '../../state/selectors/app';
import { appReady, appError } from '../../state/actions/app';
import { trackRouteChange } from './track_route_change';
Expand All @@ -20,10 +22,17 @@ const mapStateToProps = state => {
};
};

const mapDispatchToProps = {
setAppReady: appReady,
setAppError: appError,
};
const mapDispatchToProps = dispatch => ({
setAppReady: async () => {
// initialize the socket and interpreter
createSocket();
await initializeInterpreter();

// set app state to ready
dispatch(appReady());
},
setAppError: payload => dispatch(appError(payload)),
});

export const App = compose(
connect(
Expand Down
37 changes: 23 additions & 14 deletions x-pack/plugins/canvas/public/lib/interpreter.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,37 @@

import { socketInterpreterProvider } from '../../common/interpreter/socket_interpret';
import { serializeProvider } from '../../common/lib/serialize';
import { socket } from '../socket';
import { getSocket } from '../socket';
import { typesRegistry } from '../../common/lib/types_registry';
import { createHandlers } from './create_handlers';
import { functionsRegistry } from './functions_registry';
import { loadBrowserPlugins } from './load_browser_plugins';

// Create the function list
socket.emit('getFunctionList');
export const getServerFunctions = new Promise(resolve => socket.once('functionList', resolve));
let socket;
let functionList;

export async function initialize() {
socket = getSocket();

// 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
socket.emit('getFunctionList');
functionList = new Promise(resolve => socket.once('functionList', resolve));
return functionList;
}

// Use the above promise to seed the interpreter with the functions it can defer to
export function interpretAst(ast, context) {
export async function interpretAst(ast, context) {
// Load plugins before attempting to get functions, otherwise this gets racey
return Promise.all([getServerFunctions, loadBrowserPlugins()])
return Promise.all([functionList, loadBrowserPlugins()])
.then(([serverFunctionList]) => {
return socketInterpreterProvider({
types: typesRegistry.toJS(),
Expand All @@ -31,11 +48,3 @@ export function interpretAst(ast, context) {
})
.then(interpretFn => interpretFn(ast, context));
}

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) });
});
});
62 changes: 34 additions & 28 deletions x-pack/plugins/canvas/public/lib/window_error_handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,36 +41,42 @@ function showError(err) {
body.appendChild(notice);
}

// React will delegate to window.onerror, even when errors are caught with componentWillCatch,
// so check for a known custom error type and skip the default error handling when we find one
window.onerror = (...args) => {
const [message, , , , err] = args;
window.canvasInitErrorHandler = () => {
// React will delegate to window.onerror, even when errors are caught with componentWillCatch,
// so check for a known custom error type and skip the default error handling when we find one
window.onerror = (...args) => {
const [message, , , , err] = args;

const isKnownError = Object.keys(knownErrors).find(errorName => {
return err.constructor.name === errorName || message.indexOf(errorName) >= 0;
});
if (isKnownError) return;
const isKnownError = Object.keys(knownErrors).find(errorName => {
return err.constructor.name === errorName || message.indexOf(errorName) >= 0;
});
if (isKnownError) return;

// uncaught errors are silenced in dev mode
// NOTE: react provides no way I can tell to distingish that an error came from react, it just
// throws generic Errors. In development mode, it throws those errors even if you catch them in
// an error boundary. This uses in the stack trace to try to detect it, but that stack changes
// between development and production modes. Fortunately, beginWork exists in both, so it uses
// a mix of the runtime mode and checking for another react method (renderRoot) for development
// TODO: this is *super* fragile. If the React method names ever change, which seems kind of likely,
// this check will break.
const isProduction = process.env.NODE_ENV === 'production';
if (!isProduction) {
// TODO: we should do something here to let the user know something failed,
// but we don't currently have an error logging service
console.error(err);
console.warn(`*** Uncaught error swallowed in dev mode ***
// uncaught errors are silenced in dev mode
// NOTE: react provides no way I can tell to distingish that an error came from react, it just
// throws generic Errors. In development mode, it throws those errors even if you catch them in
// an error boundary. This uses in the stack trace to try to detect it, but that stack changes
// between development and production modes. Fortunately, beginWork exists in both, so it uses
// a mix of the runtime mode and checking for another react method (renderRoot) for development
// TODO: this is *super* fragile. If the React method names ever change, which seems kind of likely,
// this check will break.
const isProduction = process.env.NODE_ENV === 'production';
if (!isProduction) {
// TODO: we should do something here to let the user know something failed,
// but we don't currently have an error logging service
console.error(err);
console.warn(`*** Uncaught error swallowed in dev mode ***
Check and fix the above error. This will blow up Kibana when run in production mode!`);
showError(err);
return;
}
Check and fix the above error. This will blow up Kibana when run in production mode!`);
showError(err);
return;
}

// fall back to the default kibana uncaught error handler
oldHandler(...args);
};
};

// fall back to the default kibana uncaught error handler
oldHandler(...args);
window.canvasRestoreErrorHandler = () => {
window.onerror = oldHandler;
};
21 changes: 15 additions & 6 deletions x-pack/plugins/canvas/public/socket.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,20 @@ import io from 'socket.io-client';
import { functionsRegistry } from '../common/lib/functions_registry';
import { loadBrowserPlugins } from './lib/load_browser_plugins';

const basePath = chrome.getBasePath();
export const socket = io(undefined, { path: `${basePath}/socket.io` });
let socket;

socket.on('getFunctionList', () => {
const pluginsLoaded = loadBrowserPlugins();
export function createSocket() {
const basePath = chrome.getBasePath();
socket = io(undefined, { path: `${basePath}/socket.io` });

pluginsLoaded.then(() => socket.emit('functionList', functionsRegistry.toJS()));
});
socket.on('getFunctionList', () => {
const pluginsLoaded = loadBrowserPlugins();

pluginsLoaded.then(() => socket.emit('functionList', functionsRegistry.toJS()));
});
}

export function getSocket() {
if (!socket) throw new Error('getSocket failed, socket has not been created');
return socket;
}

0 comments on commit c59fdbf

Please sign in to comment.