Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Lens] Xy expression building #37967

Merged
merged 28 commits into from
Jun 7, 2019
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
d2f3702
Use data plugin expression renderer for rendering expression
flash1293 May 31, 2019
fc85182
remove unused typings
flash1293 May 31, 2019
1333bad
start wiring up expressions and stuff which is already there
flash1293 May 31, 2019
6cd0027
register xy charts
flash1293 May 31, 2019
acd6126
remove unused import
flash1293 May 31, 2019
aff1980
Merge branch 'feature/lens' into lens/expression-rendering-2
flash1293 May 31, 2019
9a54c1e
Merge branch 'lens/expression-rendering-2' into lens/expression-regis…
flash1293 May 31, 2019
88aef13
Merge branch 'feature/lens' into lens/expression-rendering-2
flash1293 Jun 3, 2019
71f2699
Merge branch 'lens/expression-rendering-2' into lens/expression-regis…
flash1293 Jun 3, 2019
f6c7ca9
error handling for expressions
flash1293 Jun 3, 2019
fcf0993
Merge branch 'feature/lens' into lens/expression-registering
flash1293 Jun 3, 2019
3d4cf08
remove unused type definition
flash1293 Jun 3, 2019
7d7a072
clean up imports
flash1293 Jun 3, 2019
616985c
fix comment
flash1293 Jun 3, 2019
843c541
only re-render once per expression change
flash1293 Jun 3, 2019
bcc847c
remove unused import
flash1293 Jun 3, 2019
fc629ce
add typing for toExpression
flash1293 Jun 4, 2019
df15757
add types for expression parsing
flash1293 Jun 4, 2019
8e4169d
build expression out of XY vis state
flash1293 Jun 4, 2019
5a6f644
fix test
flash1293 Jun 4, 2019
9226e85
Merge branch 'lens/expression-registering' into lens/xy-expression-bu…
flash1293 Jun 4, 2019
44d9238
Merge branch 'feature/lens' into lens/expression-registering
flash1293 Jun 4, 2019
c6f3df9
Merge branch 'lens/expression-registering' into lens/xy-expression-bu…
flash1293 Jun 4, 2019
ff143b7
Merge branch 'feature/lens' into lens/xy-expression-building
flash1293 Jun 5, 2019
83e6fbe
add autoload to have working angular dependencies
flash1293 Jun 5, 2019
606da5f
add all necessary legcay dependency imports to plugin
flash1293 Jun 6, 2019
d4fb0bd
Merge branch 'feature/lens' into lens/xy-expression-building
flash1293 Jun 6, 2019
06ba998
Merge branch 'feature/lens' into lens/xy-expression-building
flash1293 Jun 6, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion packages/kbn-interpreter/src/common/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,10 @@

export { Registry } from './lib/registry';

export { fromExpression, Ast } from './lib/ast';
export {
fromExpression,
toExpression,
Ast,
ExpressionArgAST,
ExpressionFunctionAST,
} from './lib/ast';
16 changes: 15 additions & 1 deletion packages/kbn-interpreter/src/common/lib/ast.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@
* under the License.
*/

export type Ast = unknown;
export type ExpressionArgAST = string | boolean | number | Ast;

export interface ExpressionFunctionAST {
type: 'function';
function: string;
arguments: {
[key: string]: ExpressionArgAST[];
};
}

export interface Ast {
type: 'expression';
chain: ExpressionFunctionAST[];
}

export declare function fromExpression(expression: string): Ast;
export declare function toExpression(ast: Ast): string;
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ export interface ExpressionRunnerOptions {
context?: object;
getInitialContext?: () => object;
element?: Element;
/**
* If an element is specified, but the response of the expression run can't be rendered
* because it isn't a valid response or the specified renderer isn't available,
* this callback is called with the given result.
*/
onRenderFailure?: (result: Result) => void;
}

export type ExpressionRunner = (
Expand All @@ -37,7 +43,10 @@ export type ExpressionRunner = (
export const createRunFn = (
renderersRegistry: RenderFunctionsRegistry,
interpreterPromise: Promise<Interpreter>
): ExpressionRunner => async (expressionOrAst, { element, context, getInitialContext }) => {
): ExpressionRunner => async (
expressionOrAst,
{ element, context, getInitialContext, onRenderFailure }
) => {
// TODO: make interpreter initialization synchronous to avoid this
const interpreter = await interpreterPromise;
const ast =
Expand All @@ -53,7 +62,7 @@ export const createRunFn = (
});

if (element) {
if (response.type === 'render' && response.as) {
if (response.type === 'render' && response.as && renderersRegistry.get(response.as) !== null) {
renderersRegistry.get(response.as).render(element, response.value, {
onDestroy: fn => {
// TODO implement
Expand All @@ -63,8 +72,9 @@ export const createRunFn = (
},
});
} else {
// eslint-disable-next-line no-console
console.log('Unexpected result of expression', response);
if (onRenderFailure) {
onRenderFailure(response);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,26 +39,30 @@ const waitForInterpreterRun = async () => {
await new Promise(resolve => setTimeout(resolve));
};

const RENDERER_ID = 'mockId';

describe('expressions_service', () => {
let interpretAstMock: jest.Mocked<Interpreter>['interpretAst'];
let interpreterMock: jest.Mocked<Interpreter>;
let renderFunctionMock: jest.Mocked<RenderFunction>;
let setupPluginsMock: ExpressionsServiceDependencies;
const expressionResult: Result = { type: 'render', as: 'abc', value: {} };
const expressionResult: Result = { type: 'render', as: RENDERER_ID, value: {} };

let api: ExpressionsSetup;
let testExpression: string;
let testAst: Ast;

beforeEach(() => {
interpreterMock = { interpretAst: jest.fn(_ => Promise.resolve(expressionResult)) };
interpretAstMock = jest.fn(_ => Promise.resolve(expressionResult));
interpreterMock = { interpretAst: interpretAstMock };
renderFunctionMock = ({
render: jest.fn(),
} as unknown) as jest.Mocked<RenderFunction>;
setupPluginsMock = {
interpreter: {
getInterpreter: () => Promise.resolve({ interpreter: interpreterMock }),
renderersRegistry: ({
get: () => renderFunctionMock,
get: (id: string) => (id === RENDERER_ID ? renderFunctionMock : null),
} as unknown) as RenderFunctionsRegistry,
},
};
Expand Down Expand Up @@ -101,6 +105,44 @@ describe('expressions_service', () => {
);
});

it('should return the result of the interpreter run', async () => {
const response = await api.run(testAst, {});
expect(response).toBe(expressionResult);
});

it('should call on render failure if the response is not valid', async () => {
const errorResult = { type: 'error', error: {} };
interpretAstMock.mockReturnValue(Promise.resolve(errorResult));
const renderFailureSpy = jest.fn();
const response = await api.run(testAst, {
element: document.createElement('div'),
onRenderFailure: renderFailureSpy,
});
expect(renderFailureSpy).toHaveBeenCalledWith(errorResult);
expect(response).toBe(response);
});

it('should call on render failure if the renderer is not known', async () => {
const errorResult = { type: 'render', as: 'unknown_id' };
interpretAstMock.mockReturnValue(Promise.resolve(errorResult));
const renderFailureSpy = jest.fn();
const response = await api.run(testAst, {
element: document.createElement('div'),
onRenderFailure: renderFailureSpy,
});
expect(renderFailureSpy).toHaveBeenCalledWith(errorResult);
expect(response).toBe(response);
});

it('should not call on render failure if the runner does not render', async () => {
const errorResult = { type: 'error', error: {} };
interpretAstMock.mockReturnValue(Promise.resolve(errorResult));
const renderFailureSpy = jest.fn();
const response = await api.run(testAst, { onRenderFailure: renderFailureSpy });
expect(renderFailureSpy).not.toHaveBeenCalled();
expect(response).toBe(response);
});

it('should call the render function with the result and element', async () => {
const element = document.createElement('div');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export interface Result {
type: string;
as?: string;
value?: unknown;
error?: unknown;
}

interface RenderHandlers {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

// @ts-ignore - Interpreter not typed yet
import { fromExpression, toExpression } from '@kbn/interpreter/common';
import { fromExpression, toExpression, Ast } from '@kbn/interpreter/common';
import { get } from 'lodash';
import React from 'react';
import ReactDOM from 'react-dom';
Expand Down Expand Up @@ -58,7 +57,7 @@ export const dropdownFilter: RendererFactory<Config> = () => ({
if (commitValue === '%%CANVAS_MATCH_ALL%%') {
handlers.setFilter('');
} else {
const newFilterAST = {
const newFilterAST: Ast = {
type: 'expression',
chain: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,47 @@ describe('editor_frame', () => {
expect.objectContaining({ state: initialState })
);
});

it('should render the resulting expression using the expression renderer', async () => {
const instance = mount(
<EditorFrame
visualizationMap={{
testVis: { ...mockVisualization, toExpression: () => 'vis' },
}}
datasourceMap={{
testDatasource: {
...mockDatasource,
toExpression: () => 'datasource',
},
}}
initialDatasourceId="testDatasource"
initialVisualizationId="testVis"
ExpressionRenderer={expressionRendererMock}
/>
);

await waitForPromises();

instance.update();

expect(instance.find(expressionRendererMock).prop('expression')).toMatchInlineSnapshot(`
Object {
"chain": Array [
Object {
"arguments": Object {},
"function": "datasource",
"type": "function",
},
Object {
"arguments": Object {},
"function": "vis",
"type": "function",
},
],
"type": "expression",
}
`);
});
});

describe('state update', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* 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 { Ast, fromExpression } from '@kbn/interpreter/common';
import { Visualization, Datasource, DatasourcePublicAPI } from '../../types';

export function buildExpression(
visualization: Visualization | null,
visualizationState: unknown,
datasource: Datasource,
datasourceState: unknown,
datasourcePublicAPI: DatasourcePublicAPI
): Ast | null {
if (visualization === null) {
return null;
}
const datasourceExpression = datasource.toExpression(datasourceState);
const visualizationExpression = visualization.toExpression(
visualizationState,
datasourcePublicAPI
);

if (datasourceExpression === null || visualizationExpression === null) {
return null;
}

const parsedDatasourceExpression =
typeof datasourceExpression === 'string'
? fromExpression(datasourceExpression)
: datasourceExpression;
const parsedVisualizationExpression =
typeof visualizationExpression === 'string'
? fromExpression(visualizationExpression)
: visualizationExpression;
return {
type: 'expression',
chain: [...parsedDatasourceExpression.chain, ...parsedVisualizationExpression.chain],
};
}
Loading