Move canvas to use NP Expressions service (#58387)

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Corey Robertson 2020-03-10 09:11:58 -04:00 committed by GitHub
parent 914df7996d
commit e982bed7c6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 596 additions and 245 deletions

View file

@ -20,3 +20,4 @@
export { Registry } from './lib/registry';
export { fromExpression, toExpression, Ast, ExpressionFunctionAST } from './lib/ast';
export { getType } from './lib/get_type';

View file

@ -0,0 +1,20 @@
/*
* 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.
*/
export declare function getType(node: any): string;

View file

@ -49,7 +49,7 @@ module.exports = async ({ config }) => {
// Parse props data for .tsx files
// This is notoriously slow, and is making Storybook unusable. Disabling for now.
// See: https://github.com/storybookjs/storybook/issues/7998
//
//
// config.module.rules.push({
// test: /\.tsx$/,
// // Exclude example files, as we don't display props info for them
@ -177,14 +177,6 @@ module.exports = async ({ config }) => {
config.resolve.alias['ui/chrome'] = path.resolve(__dirname, '../tasks/mocks/uiChrome');
config.resolve.alias.ui = path.resolve(KIBANA_ROOT, 'src/legacy/ui/public');
config.resolve.alias.ng_mock$ = path.resolve(KIBANA_ROOT, 'src/test_utils/public/ng_mock');
config.resolve.alias['plugins/interpreter/interpreter'] = path.resolve(
KIBANA_ROOT,
'packages/kbn-interpreter/target/common'
);
config.resolve.alias['plugins/interpreter/registries'] = path.resolve(
KIBANA_ROOT,
'packages/kbn-interpreter/target/common/registries'
);
return config;
};

View file

@ -6,7 +6,11 @@
// @ts-ignore untyped Elastic library
import { getType } from '@kbn/interpreter/common';
import { ExpressionFunctionDefinition, Datatable } from 'src/plugins/expressions/common';
import {
ExpressionFunctionDefinition,
Datatable,
DatatableColumnType,
} from 'src/plugins/expressions/common';
import { getFunctionHelp } from '../../../i18n';
interface Arguments {
@ -42,7 +46,7 @@ export function staticColumn(): ExpressionFunctionDefinition<
},
fn: (input, args) => {
const rows = input.rows.map(row => ({ ...row, [args.name]: args.value }));
const type = getType(args.value);
const type = getType(args.value) as DatatableColumnType;
const columns = [...input.columns];
const existingColumnIndex = columns.findIndex(({ name }) => name === args.name);
const newColumn = { name: args.name, type };

View file

@ -0,0 +1,55 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { CoreSetup, CoreStart, Plugin } from 'src/core/public';
import { CanvasSetup } from '../public';
import { functions } from './functions/browser';
import { typeFunctions } from './expression_types';
// @ts-ignore: untyped local
import { renderFunctions } from './renderers';
import { elementSpecs } from './elements';
// @ts-ignore Untyped Local
import { transformSpecs } from './uis/transforms';
// @ts-ignore Untyped Local
import { datasourceSpecs } from './uis/datasources';
// @ts-ignore Untyped Local
import { modelSpecs } from './uis/models';
// @ts-ignore Untyped Local
import { viewSpecs } from './uis/views';
// @ts-ignore Untyped Local
import { args as argSpecs } from './uis/arguments';
import { tagSpecs } from './uis/tags';
import { templateSpecs } from './templates';
interface SetupDeps {
canvas: CanvasSetup;
}
/** @internal */
export class CanvasSrcPlugin implements Plugin<{}, {}, SetupDeps, {}> {
public setup(core: CoreSetup, plugins: SetupDeps) {
plugins.canvas.addFunctions(functions);
plugins.canvas.addTypes(typeFunctions);
plugins.canvas.addRenderers(renderFunctions);
plugins.canvas.addElements(elementSpecs);
plugins.canvas.addDatasourceUIs(datasourceSpecs);
plugins.canvas.addModelUIs(modelSpecs);
plugins.canvas.addViewUIs(viewSpecs);
plugins.canvas.addArgumentUIs(argSpecs);
plugins.canvas.addTagUIs(tagSpecs);
plugins.canvas.addTemplates(templateSpecs);
plugins.canvas.addTransformUIs(transformSpecs);
return {};
}
public start(core: CoreStart, plugins: {}) {
return {};
}
}

View file

@ -3,13 +3,12 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';
import { filters } from '../../../public/functions/filters';
import { filtersFunctionFactory } from '../../../public/functions/filters';
import { FunctionHelp } from '../function_help';
import { FunctionFactory } from '../../../types';
export const help: FunctionHelp<FunctionFactory<typeof filters>> = {
export const help: FunctionHelp<FunctionFactory<ReturnType<typeof filtersFunctionFactory>>> = {
help: i18n.translate('xpack.canvas.functions.filtersHelpText', {
defaultMessage:
'Aggregates element filters from the workpad for use elsewhere, usually a data source.',

View file

@ -5,12 +5,12 @@
*/
import { i18n } from '@kbn/i18n';
import { to } from '../../../public/functions/to';
import { toFunctionFactory } from '../../../public/functions/to';
import { FunctionHelp } from '../function_help';
import { FunctionFactory } from '../../../types';
import { CONTEXT } from '../../constants';
export const help: FunctionHelp<FunctionFactory<typeof to>> = {
export const help: FunctionHelp<FunctionFactory<ReturnType<typeof toFunctionFactory>>> = {
help: i18n.translate('xpack.canvas.functions.toHelpText', {
defaultMessage: 'Explicitly casts the type of the {CONTEXT} to the specified type.',
values: {

View file

@ -26,11 +26,7 @@ export function canvas(kibana) {
main: 'plugins/canvas/legacy_start',
category: DEFAULT_APP_CATEGORIES.analyze,
},
interpreter: [
'plugins/canvas/browser_functions',
'plugins/canvas/renderers',
'plugins/canvas/interpreter_expression_types',
],
interpreter: ['plugins/canvas/legacy_register_interpreter'],
styleSheetPaths: resolve(__dirname, 'public/style/index.scss'),
hacks: [
// window.onerror override

View file

@ -12,13 +12,14 @@ import { Provider } from 'react-redux';
import { AppMountParameters, CoreStart } from 'kibana/public';
import { CanvasStartDeps } from './plugin';
// @ts-ignore Untyped local
import { App } from './components/app';
import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public';
export const renderApp = (
coreStart: CoreStart,
plugins: object,
plugins: CanvasStartDeps,
{ element }: AppMountParameters,
canvasStore: Store
) => {

View file

@ -4,35 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { register, addRegistries } from '@kbn/interpreter/common';
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';
import { registerLanguage } from '../../lib/monaco_language_def';
import { templatesRegistry } from '../../lib/templates_registry';
import { tagsRegistry } from '../../lib/tags_registry';
import { elementSpecs } from '../../../canvas_plugin_src/elements';
import { transformSpecs } from '../../../canvas_plugin_src/uis/transforms';
import { modelSpecs } from '../../../canvas_plugin_src/uis/models';
import { viewSpecs } from '../../../canvas_plugin_src/uis/views';
import { datasourceSpecs } from '../../../canvas_plugin_src/uis/datasources';
import { args as argSpecs } from '../../../canvas_plugin_src/uis/arguments';
import { tagSpecs } from '../../../canvas_plugin_src/uis/tags';
import { templateSpecs } from '../../../canvas_plugin_src/templates';
import { clientFunctions } from '../../functions';
import {
argTypeRegistry,
datasourceRegistry,
modelRegistry,
transformRegistry,
viewRegistry,
} from '../../expression_types';
import { App as Component } from './app';
import { trackRouteChange } from './track_route_change';
@ -46,38 +22,9 @@ const mapStateToProps = state => {
};
};
addRegistries(registries, {
elements: elementsRegistry,
transformUIs: transformRegistry,
datasourceUIs: datasourceRegistry,
modelUIs: modelRegistry,
viewUIs: viewRegistry,
argumentUIs: argTypeRegistry,
templates: templatesRegistry,
tagUIs: tagsRegistry,
});
register(registries, {
elements: elementSpecs,
transformUIs: transformSpecs,
modelUIs: modelSpecs,
viewUIs: viewSpecs,
datasourceUIs: datasourceSpecs,
argumentUIs: argSpecs,
browserFunctions: clientFunctions,
templates: templateSpecs,
tagUIs: tagSpecs,
});
const mapDispatchToProps = dispatch => ({
setAppReady: () => async () => {
try {
await loadLegacyServerFunctionWrappers();
await getInterpreter();
// Register the expression language with the Monaco Editor
registerLanguage();
// set app state to ready
dispatch(appReady());
} catch (e) {

View file

@ -6,7 +6,7 @@
import { pure, compose, lifecycle, withState, branch, renderComponent } from 'recompose';
import { PropTypes } from 'prop-types';
import { interpretAst } from 'plugins/interpreter/interpreter';
import { interpretAst } from '../../../lib/run_interpreter';
import { Loading } from '../../loading';
import { DatasourcePreview as Component } from './datasource_preview';

View file

@ -8,8 +8,8 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { compose, withProps } from 'recompose';
import { get } from 'lodash';
import { npStart } from 'ui/new_platform';
import { getSelectedPage, getPageById } from '../../state/selectors/workpad';
import { withKibana } from '../../../../../../../src/plugins/kibana_react/public';
import { ElementContent as Component } from './element_content';
const mapStateToProps = state => ({
@ -18,8 +18,9 @@ const mapStateToProps = state => ({
export const ElementContent = compose(
connect(mapStateToProps),
withProps(({ renderable }) => ({
renderFunction: npStart.plugins.expressions.getRenderer(get(renderable, 'as')),
withKibana,
withProps(({ renderable, kibana }) => ({
renderFunction: kibana.services.expressions.getRenderer(get(renderable, 'as')),
}))
)(Component);

View file

@ -15,16 +15,15 @@ import {
renderComponent,
} from 'recompose';
import { fromExpression } from '@kbn/interpreter/common';
import { withKibana } from '../../../../../../../src/plugins/kibana_react/public';
import { getSelectedPage, getSelectedElement } from '../../state/selectors/workpad';
import { setExpression, flushContext } from '../../state/actions/elements';
import { getFunctionDefinitions } from '../../lib/function_definitions';
import { ElementNotSelected } from './element_not_selected';
import { Expression as Component } from './expression';
const mapStateToProps = state => ({
pageId: getSelectedPage(state),
element: getSelectedElement(state),
functionDefinitionsPromise: getFunctionDefinitions(state),
});
const mapDispatchToProps = dispatch => ({
@ -47,9 +46,12 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
const { expression } = element;
const functions = Object.values(allProps.kibana.services.expressions.getFunctions());
return {
...allProps,
expression,
functionDefinitions: functions,
setExpression: dispatchProps.setExpression(element.id, pageId),
};
};
@ -66,15 +68,11 @@ const expressionLifecycle = lifecycle({
});
}
},
componentDidMount() {
const { functionDefinitionsPromise, setFunctionDefinitions } = this.props;
functionDefinitionsPromise.then(defs => setFunctionDefinitions(defs));
},
});
export const Expression = compose(
withKibana,
connect(mapStateToProps, mapDispatchToProps, mergeProps),
withState('functionDefinitions', 'setFunctionDefinitions', []),
withState('formState', 'setFormState', ({ expression }) => ({
expression,
dirty: false,

View file

@ -7,7 +7,7 @@
import { compose, withProps } from 'recompose';
import { get } from 'lodash';
import { toExpression } from '@kbn/interpreter/common';
import { interpretAst } from 'plugins/interpreter/interpreter';
import { interpretAst } from '../../lib/run_interpreter';
import { modelRegistry, viewRegistry, transformRegistry } from '../../expression_types';
import { FunctionFormList as Component } from './function_form_list';

View file

@ -6,16 +6,14 @@
import { fromExpression } from '@kbn/interpreter/common';
import { get } from 'lodash';
// @ts-ignore untyped Elastic lib
import { interpretAst } from 'plugins/interpreter/interpreter';
// @ts-ignore untyped Elastic lib
import { registries } from 'plugins/interpreter/registries';
import { ExpressionFunctionDefinition } from 'src/plugins/expressions/public';
import { interpretAst } from '../lib/run_interpreter';
// @ts-ignore untyped local
import { getState } from '../state/store';
import { getGlobalFilters } from '../state/selectors/workpad';
import { Filter } from '../../types';
import { getFunctionHelp } from '../../i18n';
import { InitializeArguments } from '.';
interface Arguments {
group: string[];
@ -43,39 +41,45 @@ function getFiltersByGroup(allFilters: string[], groups?: string[], ungrouped =
});
}
export function filters(): ExpressionFunctionDefinition<'filters', null, Arguments, Filter> {
const { help, args: argHelp } = getFunctionHelp().filters;
type FiltersFunction = ExpressionFunctionDefinition<'filters', null, Arguments, Filter>;
return {
name: 'filters',
type: 'filter',
help,
inputTypes: ['null'],
args: {
group: {
aliases: ['_'],
types: ['string'],
help: argHelp.group,
multi: true,
},
ungrouped: {
aliases: ['nogroup', 'nogroups'],
types: ['boolean'],
help: argHelp.ungrouped,
default: false,
},
},
fn: (input, { group, ungrouped }) => {
const filterList = getFiltersByGroup(getGlobalFilters(getState()), group, ungrouped);
export function filtersFunctionFactory(initialize: InitializeArguments): () => FiltersFunction {
return function filters(): FiltersFunction {
const { help, args: argHelp } = getFunctionHelp().filters;
if (filterList && filterList.length) {
const filterExpression = filterList.join(' | ');
const filterAST = fromExpression(filterExpression);
return interpretAst(filterAST);
} else {
const filterType = registries.types.get('filter');
return filterType.from(null);
}
},
return {
name: 'filters',
type: 'filter',
help,
context: {
types: ['null'],
},
args: {
group: {
aliases: ['_'],
types: ['string'],
help: argHelp.group,
multi: true,
},
ungrouped: {
aliases: ['nogroup', 'nogroups'],
types: ['boolean'],
help: argHelp.ungrouped,
default: false,
},
},
fn: (input, { group, ungrouped }) => {
const filterList = getFiltersByGroup(getGlobalFilters(getState()), group, ungrouped);
if (filterList && filterList.length) {
const filterExpression = filterList.join(' | ');
const filterAST = fromExpression(filterExpression);
return interpretAst(filterAST);
} else {
const filterType = initialize.typesRegistry.get('filter');
return filterType?.from(null, {});
}
},
};
};
}

View file

@ -4,9 +4,16 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { ExpressionsSetup } from 'src/plugins/expressions/public';
import { asset } from './asset';
import { filters } from './filters';
import { filtersFunctionFactory } from './filters';
import { timelion } from './timelion';
import { to } from './to';
import { toFunctionFactory } from './to';
export const clientFunctions = [asset, filters, timelion, to];
export interface InitializeArguments {
typesRegistry: ExpressionsSetup['__LEGACY']['types'];
}
export function initFunctions(initialize: InitializeArguments) {
return [asset, filtersFunctionFactory(initialize), timelion, toFunctionFactory(initialize)];
}

View file

@ -7,35 +7,39 @@
// @ts-ignore untyped Elastic library
import { castProvider } from '@kbn/interpreter/common';
import { ExpressionFunctionDefinition } from 'src/plugins/expressions/public';
import { npStart } from 'ui/new_platform';
import { getFunctionHelp, getFunctionErrors } from '../../i18n';
import { InitializeArguments } from '.';
interface Arguments {
type: string[];
}
export function to(): ExpressionFunctionDefinition<'to', any, Arguments, any> {
const { help, args: argHelp } = getFunctionHelp().to;
const errors = getFunctionErrors().to;
type ToFunction = ExpressionFunctionDefinition<'to', any, Arguments, any>;
return {
name: 'to',
aliases: [],
help,
args: {
type: {
types: ['string'],
help: argHelp.type,
aliases: ['_'],
multi: true,
export function toFunctionFactory(initialize: InitializeArguments): () => ToFunction {
return function to(): ToFunction {
const { help, args: argHelp } = getFunctionHelp().to;
const errors = getFunctionErrors().to;
return {
name: 'to',
aliases: [],
help,
args: {
type: {
types: ['string'],
help: argHelp.type,
aliases: ['_'],
multi: true,
},
},
},
fn: (input, args) => {
if (!args.type) {
throw errors.missingType();
}
fn: (input, args) => {
if (!args.type) {
throw errors.missingType();
}
return castProvider(npStart.plugins.expressions.getTypes())(input, args.type);
},
return castProvider(initialize.typesRegistry.toJS())(input, args.type);
},
};
};
}

View file

@ -1,12 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { typesRegistry } from '../../../../../src/legacy/core_plugins/interpreter/public/registries';
import { typeFunctions } from '../canvas_plugin_src/expression_types';
typeFunctions.forEach(r => {
typesRegistry.register(r);
});

View file

@ -5,7 +5,7 @@
*/
import { npSetup, npStart } from 'ui/new_platform';
import { CanvasStartDeps } from './plugin'; // eslint-disable-line import/order
import { CanvasStartDeps, CanvasSetupDeps } from './plugin'; // eslint-disable-line import/order
// @ts-ignore Untyped Kibana Lib
import chrome, { loadingCount } from 'ui/chrome'; // eslint-disable-line import/order
@ -19,12 +19,14 @@ const shimCoreSetup = {
const shimCoreStart = {
...npStart.core,
};
const shimSetupPlugins = {
const shimSetupPlugins: CanvasSetupDeps = {
expressions: npSetup.plugins.expressions,
home: npSetup.plugins.home,
};
const shimStartPlugins: CanvasStartDeps = {
...npStart.plugins,
expressions: npStart.plugins.expressions,
__LEGACY: {
// ToDo: Copy directly into canvas
absoluteToParsedUrl,

View file

@ -0,0 +1,47 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
// @ts-ignore
import { Registry, registryFactory } from '@kbn/interpreter/common';
type specFn = (...args: any[]) => { name: string };
const fnWrapper = (fn: specFn) => {
const obj = fn();
return () => ({
name: obj.name,
fn,
});
};
class LegacyRegistry extends Registry<any, any> {
register(fn: specFn) {
super.register(fnWrapper(fn));
}
getOriginalFns() {
return this.toArray().map(entry => entry.fn);
}
}
export const legacyRegistries = {
browserFunctions: new LegacyRegistry(),
renderers: new LegacyRegistry(),
types: new LegacyRegistry(),
elements: new LegacyRegistry(),
transformUIs: new LegacyRegistry(),
datasourceUIs: new LegacyRegistry(),
modelUIs: new LegacyRegistry(),
viewUIs: new LegacyRegistry(),
argumentUIs: new LegacyRegistry(),
templates: new LegacyRegistry(),
tagUIs: new LegacyRegistry(),
};
(global as any).kbnInterpreter = Object.assign(
(global as any).kbnInterpreter || {},
registryFactory(legacyRegistries)
);

View file

@ -6,8 +6,13 @@
import { npSetup } from 'ui/new_platform';
import { functions } from '../canvas_plugin_src/functions/browser';
import { typeFunctions } from '../canvas_plugin_src/expression_types';
// @ts-ignore untyped local
import { renderFunctions } from '../canvas_plugin_src/renderers';
functions.forEach(npSetup.plugins.expressions.registerFunction);
typeFunctions.forEach(npSetup.plugins.expressions.registerType);
renderFunctions.forEach(npSetup.plugins.expressions.registerRenderer);
// eslint-disable-next-line import/no-default-export
export default functions;

View file

@ -13,12 +13,7 @@ import 'uiExports/spyModes';
import 'uiExports/embeddableFactories';
import 'uiExports/interpreter';
// TODO: These dependencies should be moved into plugin startup methods
// Load the interpreter so that the kbnInterpreter global will be available when plugins load
import 'plugins/interpreter/interpreter';
// Load our app component to initialize registries
import './components/app';
import './legacy_plugin_support';
// load application code
import 'uiExports/canvas';

View file

@ -5,7 +5,7 @@
*/
import { monaco } from '@kbn/ui-shared-deps/monaco';
import { npSetup } from 'ui/new_platform';
import { ExpressionFunction } from '../../types';
export const LANGUAGE_ID = 'canvas-expression';
@ -94,9 +94,8 @@ export const language: Language = {
},
};
export function registerLanguage() {
const functions = Object.values(npSetup.plugins.expressions.getFunctions());
language.keywords = functions.map(({ name }) => name);
export function registerLanguage(functions: ExpressionFunction[]) {
language.keywords = functions.map(fn => fn.name);
monaco.languages.register({ id: LANGUAGE_ID });
monaco.languages.setMonarchTokensProvider(LANGUAGE_ID, language);

View file

@ -1,42 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
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';
/**
* Runs interpreter, usually in the browser
*
* @param {object} ast - Executable AST
* @param {any} context - Initial context for AST execution
* @param {object} options
* @param {boolean} options.castToRender - try to cast to a type: render object?
* @param {boolean} options.retryRenderCasting -
* @returns {promise}
*/
export function runInterpreter(ast, context = null, options = {}) {
return loadLegacyServerFunctionWrappers()
.then(() => interpretAst(ast, context))
.then(renderable => {
if (getType(renderable) === 'render') {
return renderable;
}
if (options.castToRender) {
return runInterpreter(fromExpression('render'), renderable, {
castToRender: false,
});
}
return new Error(`Ack! I don't know how to render a '${getType(renderable)}'`);
})
.catch(err => {
notify.error(err);
throw err;
});
}

View file

@ -0,0 +1,87 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { fromExpression, getType } from '@kbn/interpreter/common';
import { ExpressionValue, ExpressionAstExpression } from 'src/plugins/expressions/public';
// @ts-ignore Untyped Local
import { notify } from './notify';
import { CanvasStartDeps, CanvasSetupDeps } from '../plugin';
let expressionsStarting: Promise<CanvasStartDeps['expressions']>;
export const initInterpreter = function(
expressionsStart: CanvasStartDeps['expressions'],
expressionsSetup: CanvasSetupDeps['expressions']
) {
expressionsStarting = startExpressions(expressionsStart, expressionsSetup);
return expressionsStarting;
};
async function startExpressions(
expressionsStart: CanvasStartDeps['expressions'],
expressionsSetup: CanvasSetupDeps['expressions']
) {
await expressionsSetup.__LEGACY.loadLegacyServerFunctionWrappers();
return expressionsStart;
}
interface Options {
castToRender?: boolean;
}
/**
* Meant to be a replacement for plugins/interpreter/interpretAST
*/
export async function interpretAst(ast: ExpressionAstExpression): Promise<ExpressionValue> {
if (!expressionsStarting) {
throw new Error('Interpreter has not been initialized');
}
const expressions = await expressionsStarting;
return await expressions.execute(ast).getData();
}
/**
* Runs interpreter, usually in the browser
*
* @param {object} ast - Executable AST
* @param {any} input - Initial input for AST execution
* @param {object} options
* @param {boolean} options.castToRender - try to cast to a type: render object?
* @returns {promise}
*/
export async function runInterpreter(
ast: ExpressionAstExpression,
input: ExpressionValue,
options: Options = {}
): Promise<ExpressionValue> {
if (!expressionsStarting) {
throw new Error('Interpreter has not been initialized');
}
const expressions = await expressionsStarting;
try {
const renderable = await expressions.execute(ast, input).getData();
if (getType(renderable) === 'render') {
return renderable;
}
if (options.castToRender) {
return runInterpreter(fromExpression('render'), renderable, {
castToRender: false,
});
}
throw new Error(`Ack! I don't know how to render a '${getType(renderable)}'`);
} catch (err) {
notify.error(err);
throw err;
}
}

View file

@ -28,6 +28,33 @@ import { getDocumentationLinks } from './lib/documentation_links';
// @ts-ignore: untyped local
import { initClipboard } from './lib/clipboard';
import { featureCatalogueEntry } from './feature_catalogue_entry';
import { ExpressionsSetup, ExpressionsStart } from '../../../../../src/plugins/expressions/public';
// @ts-ignore untyped local
import { datasourceSpecs } from './expression_types/datasources';
// @ts-ignore untyped local
import { argTypeSpecs } from './expression_types/arg_types';
import { transitions } from './transitions';
import { registerLanguage } from './lib/monaco_language_def';
import { initInterpreter } from './lib/run_interpreter';
import { legacyRegistries } from './legacy_plugin_support';
import { getPluginApi, CanvasApi, SetupRegistries } from './plugin_api';
import {
initRegistries,
addElements,
addTransformUIs,
addDatasourceUIs,
addModelUIs,
addViewUIs,
addArgumentUIs,
addTagUIs,
addTemplates,
addTransitions,
} from './registries';
import { initFunctions } from './functions';
import { CanvasSrcPlugin } from '../canvas_plugin_src/plugin';
export { CoreStart };
/**
@ -36,9 +63,12 @@ export { CoreStart };
*/
// This interface will be built out as we require other plugins for setup
export interface CanvasSetupDeps {
expressions: ExpressionsSetup;
home: HomePublicPluginSetup;
}
export interface CanvasStartDeps {
expressions: ExpressionsStart;
__LEGACY: {
absoluteToParsedUrl: (url: string, basePath: string) => any;
formatMsg: any;
@ -53,16 +83,18 @@ export interface CanvasStartDeps {
*/
// These interfaces are empty for now but will be populate as we need to export
// things for other plugins to use at startup or runtime
export interface CanvasSetup {} // eslint-disable-line @typescript-eslint/no-empty-interface
export type CanvasSetup = CanvasApi;
export interface CanvasStart {} // eslint-disable-line @typescript-eslint/no-empty-interface
/** @internal */
export class CanvasPlugin
implements Plugin<CanvasSetup, CanvasStart, CanvasSetupDeps, CanvasStartDeps> {
public setup(core: CoreSetup, plugins: CanvasSetupDeps) {
// This is where any setup actions need to occur.
// Things like registering functions to the interpreter that need
// to be available everywhere, not just in Canvas
private expressionSetup: CanvasSetupDeps['expressions'] | undefined;
private registries: SetupRegistries | undefined;
public setup(core: CoreSetup<CanvasStartDeps>, plugins: CanvasSetupDeps) {
const { api: canvasApi, registries } = getPluginApi(plugins.expressions);
this.registries = registries;
core.application.register({
id: 'canvas',
@ -82,15 +114,51 @@ export class CanvasPlugin
});
plugins.home.featureCatalogue.register(featureCatalogueEntry);
this.expressionSetup = plugins.expressions;
return {};
// Register Legacy plugin stuff
canvasApi.addFunctions(legacyRegistries.browserFunctions.getOriginalFns());
canvasApi.addElements(legacyRegistries.elements.getOriginalFns());
// TODO: Do we want to completely move canvas_plugin_src into it's own plugin?
const srcPlugin = new CanvasSrcPlugin();
srcPlugin.setup(core, { canvas: canvasApi });
// Register core canvas stuff
canvasApi.addFunctions(initFunctions({ typesRegistry: plugins.expressions.__LEGACY.types }));
canvasApi.addDatasourceUIs(datasourceSpecs);
canvasApi.addArgumentUIs(argTypeSpecs);
canvasApi.addTransitions(transitions);
return {
...canvasApi,
};
}
public start(core: CoreStart, plugins: CanvasStartDeps) {
loadExpressionTypes();
loadTransitions();
initLoadingIndicator(core.http.addLoadingCountSource);
initRegistries();
if (this.expressionSetup) {
const expressionSetup = this.expressionSetup;
initInterpreter(plugins.expressions, expressionSetup).then(() => {
registerLanguage(Object.values(plugins.expressions.getFunctions()));
});
}
if (this.registries) {
addElements(this.registries.elements);
addTransformUIs(this.registries.transformUIs);
addDatasourceUIs(this.registries.datasourceUIs);
addModelUIs(this.registries.modelUIs);
addViewUIs(this.registries.viewUIs);
addArgumentUIs(this.registries.argumentUIs);
addTemplates(this.registries.templates);
addTagUIs(this.registries.tagUIs);
addTransitions(this.registries.transitions);
} else {
throw new Error('Unable to initialize Canvas registries');
}
core.chrome.setBadge(
core.application.capabilities.canvas && core.application.capabilities.canvas.save

View file

@ -0,0 +1,89 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import {
AnyExpressionFunctionDefinition,
AnyExpressionTypeDefinition,
RendererFactory,
} from '../types';
import { ElementFactory } from '../types';
import { ExpressionsSetup } from '../../../../../src/plugins/expressions/public';
type AddToRegistry<T extends any> = (add: T[]) => void;
export interface CanvasApi {
addArgumentUIs: AddToRegistry<any>;
addDatasourceUIs: AddToRegistry<any>;
addElements: AddToRegistry<ElementFactory>;
addFunctions: AddToRegistry<() => AnyExpressionFunctionDefinition>;
addModelUIs: AddToRegistry<any>;
addRenderers: AddToRegistry<RendererFactory>;
addTagUIs: AddToRegistry<any>;
addTemplates: AddToRegistry<any>;
addTransformUIs: AddToRegistry<any>;
addTransitions: AddToRegistry<any>;
addTypes: AddToRegistry<() => AnyExpressionTypeDefinition>;
addViewUIs: AddToRegistry<any>;
}
export interface SetupRegistries {
elements: ElementFactory[];
transformUIs: any[];
datasourceUIs: any[];
modelUIs: any[];
viewUIs: any[];
argumentUIs: any[];
templates: any[];
tagUIs: any[];
transitions: any[];
}
export function getPluginApi(
expressionsPluginSetup: ExpressionsSetup
): { api: CanvasApi; registries: SetupRegistries } {
const registries: SetupRegistries = {
elements: [],
transformUIs: [],
datasourceUIs: [],
modelUIs: [],
viewUIs: [],
argumentUIs: [],
templates: [],
tagUIs: [],
transitions: [],
};
const api: CanvasApi = {
// Functions, types and renderers are registered directly to expression plugin
addFunctions: fns => {
fns.forEach(fn => {
expressionsPluginSetup.registerFunction(fn);
});
},
addTypes: types => {
types.forEach(type => {
expressionsPluginSetup.registerType(type as any);
});
},
addRenderers: renderers => {
renderers.forEach((r: any) => {
expressionsPluginSetup.registerRenderer(r);
});
},
// All these others are local to canvas, and they will only register on start
addElements: elements => registries.elements.push(...elements),
addTransformUIs: transforms => registries.transformUIs.push(...transforms),
addDatasourceUIs: datasources => registries.datasourceUIs.push(...datasources),
addModelUIs: models => registries.modelUIs.push(...models),
addViewUIs: views => registries.viewUIs.push(...views),
addArgumentUIs: args => registries.argumentUIs.push(...args),
addTemplates: templates => registries.templates.push(...templates),
addTagUIs: tags => registries.tagUIs.push(...tags),
addTransitions: transitions => registries.transitions.push(...transitions),
};
return { api, registries };
}

View file

@ -0,0 +1,81 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
// @ts-ignore untyped module
import { addRegistries, register } from '@kbn/interpreter/common';
// @ts-ignore untyped local
import { elementsRegistry } from './lib/elements_registry';
// @ts-ignore untyped local
import { templatesRegistry } from './lib/templates_registry';
import { tagsRegistry } from './lib/tags_registry';
import { ElementFactory } from '../types';
// @ts-ignore untyped local
import { transitionsRegistry } from './lib/transitions_registry';
import {
argTypeRegistry,
datasourceRegistry,
modelRegistry,
transformRegistry,
viewRegistry,
// @ts-ignore untyped local
} from './expression_types';
export const registries = {};
export function initRegistries() {
addRegistries(registries, {
elements: elementsRegistry,
transformUIs: transformRegistry,
datasourceUIs: datasourceRegistry,
modelUIs: modelRegistry,
viewUIs: viewRegistry,
argumentUIs: argTypeRegistry,
templates: templatesRegistry,
tagUIs: tagsRegistry,
transitions: transitionsRegistry,
});
}
export function addElements(elements: ElementFactory[]) {
register(registries, { elements });
}
export function addTransformUIs(transformUIs: any[]) {
register(registries, { transformUIs });
}
export function addDatasourceUIs(datasourceUIs: any[]) {
register(registries, { datasourceUIs });
}
export function addModelUIs(modelUIs: any[]) {
register(registries, { modelUIs });
}
export function addViewUIs(viewUIs: any[]) {
register(registries, { viewUIs });
}
export function addArgumentUIs(argumentUIs: any[]) {
register(registries, { argumentUIs });
}
export function addTemplates(templates: any[]) {
register(registries, { templates });
}
export function addTagUIs(tagUIs: any[]) {
register(registries, { tagUIs });
}
export function addTransitions(transitions: any[]) {
register(registries, { transitions });
}
export function addBrowserFunctions(browserFunctions: any[]) {
register(registries, { browserFunctions });
}

View file

@ -1,12 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { npSetup } from 'ui/new_platform';
import { renderFunctions } from '../canvas_plugin_src/renderers';
renderFunctions.forEach(npSetup.plugins.expressions.registerRenderer);
export default renderFunctions;

View file

@ -9,13 +9,12 @@ import { createThunk } from 'redux-thunks';
import immutable from 'object-path-immutable';
import { get, pick, cloneDeep, without } from 'lodash';
import { toExpression, safeElementFromExpression } from '@kbn/interpreter/common';
import { interpretAst } from 'plugins/interpreter/interpreter';
import { getPages, getNodeById, getNodes, getSelectedPageIndex } from '../selectors/workpad';
import { getValue as getResolvedArgsValue } from '../selectors/resolved_args';
import { getDefaultElement } from '../defaults';
import { ErrorStrings } from '../../../i18n';
import { notify } from '../../lib/notify';
import { runInterpreter } from '../../lib/run_interpreter';
import { runInterpreter, interpretAst } from '../../lib/run_interpreter';
import { subMultitree } from '../../lib/aeroelastic/functional';
import { selectToplevelNodes } from './transient';
import * as args from './resolved_args';

View file

@ -5,10 +5,7 @@
*/
import { CoreSetup, PluginsSetup } from './shim';
import { functions } from '../canvas_plugin_src/functions/server';
export class Plugin {
public setup(core: CoreSetup, plugins: PluginsSetup) {
plugins.interpreter.register({ serverFunctions: functions });
}
public setup(core: CoreSetup, plugins: PluginsSetup) {}
}

View file

@ -8,7 +8,7 @@ import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common';
import { functions as commonFunctions } from '../canvas_plugin_src/functions/common';
import { functions as browserFunctions } from '../canvas_plugin_src/functions/browser';
import { functions as serverFunctions } from '../canvas_plugin_src/functions/server';
import { clientFunctions } from '../public/functions';
import { initFunctions } from '../public/functions';
/**
* A `ExpressionFunctionFactory` is a powerful type used for any function that produces
@ -87,7 +87,9 @@ export type FunctionFactory<FnFactory> =
type CommonFunction = FunctionFactory<typeof commonFunctions[number]>;
type BrowserFunction = FunctionFactory<typeof browserFunctions[number]>;
type ServerFunction = FunctionFactory<typeof serverFunctions[number]>;
type ClientFunctions = FunctionFactory<typeof clientFunctions[number]>;
type ClientFunctions = FunctionFactory<
ReturnType<typeof initFunctions> extends Array<infer U> ? U : never
>;
/**
* A collection of all Canvas Functions.

View file

@ -5,6 +5,6 @@
"configPath": ["xpack", "canvas"],
"server": true,
"ui": false,
"requiredPlugins": ["features", "home"],
"requiredPlugins": ["expressions", "features", "home"],
"optionalPlugins": ["usageCollection"]
}

View file

@ -6,17 +6,20 @@
import { first } from 'rxjs/operators';
import { CoreSetup, PluginInitializerContext, Plugin, Logger } from 'src/core/server';
import { ExpressionsServerSetup } from 'src/plugins/expressions/server';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { HomeServerPluginSetup } from 'src/plugins/home/server';
import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server';
import { initRoutes } from './routes';
import { registerCanvasUsageCollector } from './collectors';
import { loadSampleData } from './sample_data';
import { setupInterpreter } from './setup_interpreter';
interface PluginsSetup {
usageCollection?: UsageCollectionSetup;
expressions: ExpressionsServerSetup;
features: FeaturesPluginSetup;
home: HomeServerPluginSetup;
usageCollection?: UsageCollectionSetup;
}
export class CanvasPlugin implements Plugin {
@ -65,6 +68,8 @@ export class CanvasPlugin implements Plugin {
.pipe(first())
.toPromise();
registerCanvasUsageCollector(plugins.usageCollection, globalConfig.kibana.index);
setupInterpreter(plugins.expressions);
}
public start() {}

View file

@ -0,0 +1,12 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { ExpressionsServerSetup } from 'src/plugins/expressions/server';
import { functions } from '../../../legacy/plugins/canvas/canvas_plugin_src/functions/server';
export function setupInterpreter(expressions: ExpressionsServerSetup) {
expressions.__LEGACY.register({ types: [], serverFunctions: functions });
}