Skip to content

Commit

Permalink
onEvent prop for expression component (#64995)
Browse files Browse the repository at this point in the history
* feat: 🎸 add onEvent prop to expression component

* feat: 🎸 add type safety to onEvent prop in expression component

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
streamich and elasticmachine authored May 4, 2020
1 parent c995a33 commit 4d19323
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 5 deletions.
2 changes: 1 addition & 1 deletion src/plugins/expressions/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export {
ReactExpressionRendererProps,
ReactExpressionRendererType,
} from './react_expression_renderer';
export { ExpressionRenderHandler } from './render';
export { ExpressionRenderHandler, ExpressionRendererEvent } from './render';
export {
AnyExpressionFunctionDefinition,
AnyExpressionTypeDefinition,
Expand Down
41 changes: 41 additions & 0 deletions src/plugins/expressions/public/react_expression_renderer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { ExpressionLoader } from './loader';
import { mount } from 'enzyme';
import { EuiProgress } from '@elastic/eui';
import { RenderErrorHandlerFnType } from './types';
import { ExpressionRendererEvent } from './render';

jest.mock('./loader', () => {
return {
Expand Down Expand Up @@ -135,4 +136,44 @@ describe('ExpressionRenderer', () => {
expect(instance.find(EuiProgress)).toHaveLength(0);
expect(instance.find('[data-test-subj="custom-error"]')).toHaveLength(0);
});

it('should fire onEvent prop on every events$ observable emission in loader', () => {
const dataSubject = new Subject();
const data$ = dataSubject.asObservable().pipe(share());
const renderSubject = new Subject();
const render$ = renderSubject.asObservable().pipe(share());
const loadingSubject = new Subject();
const loading$ = loadingSubject.asObservable().pipe(share());
const eventsSubject = new Subject<ExpressionRendererEvent>();
const events$ = eventsSubject.asObservable().pipe(share());

const onEvent = jest.fn();
const event: ExpressionRendererEvent = {
name: 'foo',
data: {
bar: 'baz',
},
};

(ExpressionLoader as jest.Mock).mockImplementation(() => {
return {
render$,
data$,
loading$,
events$,
update: jest.fn(),
};
});

mount(<ReactExpressionRenderer expression="" onEvent={onEvent} />);

expect(onEvent).toHaveBeenCalledTimes(0);

act(() => {
eventsSubject.next(event);
});

expect(onEvent).toHaveBeenCalledTimes(1);
expect(onEvent.mock.calls[0][0]).toBe(event);
});
});
12 changes: 11 additions & 1 deletion src/plugins/expressions/public/react_expression_renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import theme from '@elastic/eui/dist/eui_theme_light.json';
import { IExpressionLoaderParams, RenderError } from './types';
import { ExpressionAstExpression, IInterpreterRenderHandlers } from '../common';
import { ExpressionLoader } from './loader';
import { ExpressionRendererEvent } from './render';

// Accept all options of the runner as props except for the
// dom element which is provided by the component itself
Expand All @@ -36,6 +37,7 @@ export interface ReactExpressionRendererProps extends IExpressionLoaderParams {
expression: string | ExpressionAstExpression;
renderError?: (error?: string | null) => React.ReactElement | React.ReactElement[];
padding?: 'xs' | 's' | 'm' | 'l' | 'xl';
onEvent?: (event: ExpressionRendererEvent) => void;
}

export type ReactExpressionRendererType = React.ComponentType<ReactExpressionRendererProps>;
Expand All @@ -60,6 +62,7 @@ export const ReactExpressionRenderer = ({
padding,
renderError,
expression,
onEvent,
...expressionLoaderOptions
}: ReactExpressionRendererProps) => {
const mountpoint: React.MutableRefObject<null | HTMLDivElement> = useRef(null);
Expand Down Expand Up @@ -99,6 +102,13 @@ export const ReactExpressionRenderer = ({
}
: expressionLoaderOptions.onRenderError,
});
if (onEvent) {
subs.push(
expressionLoaderRef.current.events$.subscribe(event => {
onEvent(event);
})
);
}
subs.push(
expressionLoaderRef.current.loading$.subscribe(() => {
hasHandledErrorRef.current = false;
Expand All @@ -123,7 +133,7 @@ export const ReactExpressionRenderer = ({

errorRenderHandlerRef.current = null;
};
}, [hasCustomRenderErrorHandler]);
}, [hasCustomRenderErrorHandler, onEvent]);

// Re-fetch data automatically when the inputs change
useShallowCompareEffect(
Expand Down
6 changes: 3 additions & 3 deletions src/plugins/expressions/public/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export interface ExpressionRenderHandlerParams {
onRenderError: RenderErrorHandlerFnType;
}

interface Event {
export interface ExpressionRendererEvent {
name: string;
data: any;
}
Expand All @@ -45,7 +45,7 @@ interface UpdateValue {
export class ExpressionRenderHandler {
render$: Observable<number>;
update$: Observable<UpdateValue | null>;
events$: Observable<Event>;
events$: Observable<ExpressionRendererEvent>;

private element: HTMLElement;
private destroyFn?: any;
Expand All @@ -63,7 +63,7 @@ export class ExpressionRenderHandler {
this.element = element;

this.eventsSubject = new Rx.Subject();
this.events$ = this.eventsSubject.asObservable() as Observable<Event>;
this.events$ = this.eventsSubject.asObservable() as Observable<ExpressionRendererEvent>;

this.onRenderError = onRenderError || defaultRenderErrorHandler;

Expand Down

0 comments on commit 4d19323

Please sign in to comment.