Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import React from 'react';
import { Ast } from '@kbn/interpreter/common';

import { ExpressionRunnerOptions, ExpressionRunner } from './expression_runner';
import { Result } from './expressions_service';

// Accept all options of the runner as props except for the
// dom element which is provided by the component itself
Expand All @@ -30,20 +31,31 @@ export type ExpressionRendererProps = Pick<
Exclude<keyof ExpressionRunnerOptions, 'element'>
> & {
expression: string | Ast;
/**
* 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 ExpressionRenderer = React.FC<ExpressionRendererProps>;

export const createRenderer = (run: ExpressionRunner): ExpressionRenderer => ({
expression,
onRenderFailure,
...options
}: ExpressionRendererProps) => {
const mountpoint: React.MutableRefObject<null | HTMLDivElement> = useRef(null);

useEffect(
() => {
if (mountpoint.current) {
run(expression, { ...options, element: mountpoint.current });
run(expression, { ...options, element: mountpoint.current }).catch(result => {
if (onRenderFailure) {
onRenderFailure(result);
}
});
}
},
[expression, mountpoint.current]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,12 @@ export const createRunFn = (
},
});

if (response.type === 'error') {
throw response;
}

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 +67,7 @@ export const createRunFn = (
},
});
} else {
// eslint-disable-next-line no-console
console.log('Unexpected result of expression', response);
throw 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,47 @@ 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 reject the promise if the response is not renderable but an element is passed', async () => {
const unexpectedResult = { type: 'datatable', value: {} };
interpretAstMock.mockReturnValue(Promise.resolve(unexpectedResult));
expect(
api.run(testAst, {
element: document.createElement('div'),
})
).rejects.toBe(unexpectedResult);
});

it('should reject the promise if the renderer is not known', async () => {
const unexpectedResult = { type: 'render', as: 'unknown_id' };
interpretAstMock.mockReturnValue(Promise.resolve(unexpectedResult));
expect(
api.run(testAst, {
element: document.createElement('div'),
})
).rejects.toBe(unexpectedResult);
});

it('should not reject the promise on unknown renderer if the runner is not rendering', async () => {
const unexpectedResult = { type: 'render', as: 'unknown_id' };
interpretAstMock.mockReturnValue(Promise.resolve(unexpectedResult));
expect(api.run(testAst, {})).resolves.toBe(unexpectedResult);
});

it('should reject the promise if the response is an error', async () => {
const errorResult = { type: 'error', error: {} };
interpretAstMock.mockReturnValue(Promise.resolve(errorResult));
expect(api.run(testAst, {})).rejects.toBe(errorResult);
});

it('should reject the promise if there are syntax errors', async () => {
expect(api.run('|||', {})).rejects.toBeInstanceOf(Error);
});

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

Expand Down Expand Up @@ -213,5 +258,19 @@ describe('expressions_service', () => {
expect(renderFunctionMock.render).toHaveBeenCalledTimes(1);
expect(interpreterMock.interpretAst).toHaveBeenCalledTimes(1);
});

it('should call onRenderFailure if the result can not be rendered', async () => {
const errorResult = { type: 'error', error: {} };
interpretAstMock.mockReturnValue(Promise.resolve(errorResult));
const renderFailureSpy = jest.fn();

const ExpressionRenderer = api.ExpressionRenderer;

mount(<ExpressionRenderer expression={testExpression} onRenderFailure={renderFailureSpy} />);

await waitForInterpreterRun();

expect(renderFailureSpy).toHaveBeenCalledWith(errorResult);
});
});
});
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