Skip to content

Commit

Permalink
Lens editor auto refresh (#65868)
Browse files Browse the repository at this point in the history
  • Loading branch information
flash1293 authored Jun 3, 2020
1 parent fbcb74c commit 4a1b05c
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 18 deletions.
25 changes: 25 additions & 0 deletions src/plugins/expressions/public/react_expression_renderer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,31 @@ describe('ExpressionRenderer', () => {
expect(instance.find(EuiProgress)).toHaveLength(0);
});

it('updates the expression loader when refresh subject emits', () => {
const refreshSubject = new Subject();
const loaderUpdate = jest.fn();

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

const instance = mount(<ReactExpressionRenderer reload$={refreshSubject} expression="" />);

act(() => {
refreshSubject.next();
});

expect(loaderUpdate).toHaveBeenCalled();

instance.unmount();
});

it('should display a custom error message if the user provides one and then remove it after successful render', () => {
const dataSubject = new Subject();
const data$ = dataSubject.asObservable().pipe(share());
Expand Down
16 changes: 15 additions & 1 deletion src/plugins/expressions/public/react_expression_renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

import React, { useRef, useEffect, useState, useLayoutEffect } from 'react';
import classNames from 'classnames';
import { Subscription } from 'rxjs';
import { Observable, Subscription } from 'rxjs';
import { filter } from 'rxjs/operators';
import useShallowCompareEffect from 'react-use/lib/useShallowCompareEffect';
import { EuiLoadingChart, EuiProgress } from '@elastic/eui';
Expand All @@ -38,6 +38,10 @@ export interface ReactExpressionRendererProps extends IExpressionLoaderParams {
renderError?: (error?: string | null) => React.ReactElement | React.ReactElement[];
padding?: 'xs' | 's' | 'm' | 'l' | 'xl';
onEvent?: (event: ExpressionRendererEvent) => void;
/**
* An observable which can be used to re-run the expression without destroying the component
*/
reload$?: Observable<unknown>;
}

export type ReactExpressionRendererType = React.ComponentType<ReactExpressionRendererProps>;
Expand All @@ -63,6 +67,7 @@ export const ReactExpressionRenderer = ({
renderError,
expression,
onEvent,
reload$,
...expressionLoaderOptions
}: ReactExpressionRendererProps) => {
const mountpoint: React.MutableRefObject<null | HTMLDivElement> = useRef(null);
Expand Down Expand Up @@ -135,6 +140,15 @@ export const ReactExpressionRenderer = ({
};
}, [hasCustomRenderErrorHandler, onEvent]);

useEffect(() => {
const subscription = reload$?.subscribe(() => {
if (expressionLoaderRef.current) {
expressionLoaderRef.current.update(expression, expressionLoaderOptions);
}
});
return () => subscription?.unsubscribe();
}, [reload$, expression, ...Object.values(expressionLoaderOptions)]);

// Re-fetch data automatically when the inputs change
useShallowCompareEffect(
() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ export function EditorFrame(props: EditorFrameProps) {
dispatch={dispatch}
ExpressionRenderer={props.ExpressionRenderer}
stagedPreview={state.stagedPreview}
plugins={props.plugins}
/>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { SuggestionPanel, SuggestionPanelProps } from './suggestion_panel';
import { getSuggestions, Suggestion } from './suggestion_helpers';
import { EuiIcon, EuiPanel, EuiToolTip } from '@elastic/eui';
import chartTableSVG from '../../..assets/chart_datatable.svg';
import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks';

jest.mock('./suggestion_helpers');

Expand Down Expand Up @@ -85,6 +86,7 @@ describe('suggestion_panel', () => {
dispatch: dispatchMock,
ExpressionRenderer: expressionRendererMock,
frame: createMockFramePublicAPI(),
plugins: { data: dataPluginMock.createStartContract() },
};
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,14 @@ import classNames from 'classnames';
import { Action, PreviewState } from './state_management';
import { Datasource, Visualization, FramePublicAPI, DatasourcePublicAPI } from '../../types';
import { getSuggestions, switchToSuggestion } from './suggestion_helpers';
import { ReactExpressionRendererType } from '../../../../../../src/plugins/expressions/public';
import {
ReactExpressionRendererProps,
ReactExpressionRendererType,
} from '../../../../../../src/plugins/expressions/public';
import { prependDatasourceExpression, prependKibanaContext } from './expression_helpers';
import { debouncedComponent } from '../../debounced_component';
import { trackUiEvent, trackSuggestionEvent } from '../../lens_ui_telemetry';
import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public';

const MAX_SUGGESTIONS_DISPLAYED = 5;

Expand All @@ -52,6 +56,7 @@ export interface SuggestionPanelProps {
ExpressionRenderer: ReactExpressionRendererType;
frame: FramePublicAPI;
stagedPreview?: PreviewState;
plugins: { data: DataPublicPluginStart };
}

const PreviewRenderer = ({
Expand Down Expand Up @@ -154,6 +159,7 @@ export function SuggestionPanel({
frame,
ExpressionRenderer: ExpressionRendererComponent,
stagedPreview,
plugins,
}: SuggestionPanelProps) {
const currentDatasourceStates = stagedPreview ? stagedPreview.datasourceStates : datasourceStates;
const currentVisualizationState = stagedPreview
Expand Down Expand Up @@ -204,6 +210,13 @@ export function SuggestionPanel({
visualizationMap,
]);

const AutoRefreshExpressionRenderer = useMemo(() => {
const autoRefreshFetch$ = plugins.data.query.timefilter.timefilter.getAutoRefreshFetch$();
return (props: ReactExpressionRendererProps) => (
<ExpressionRendererComponent {...props} reload$={autoRefreshFetch$} />
);
}, [plugins.data.query.timefilter.timefilter.getAutoRefreshFetch$, ExpressionRendererComponent]);

const [lastSelectedSuggestion, setLastSelectedSuggestion] = useState<number>(-1);

useEffect(() => {
Expand Down Expand Up @@ -296,7 +309,7 @@ export function SuggestionPanel({
defaultMessage: 'Current',
}),
}}
ExpressionRenderer={ExpressionRendererComponent}
ExpressionRenderer={AutoRefreshExpressionRenderer}
onSelect={rollbackToCurrentVisualization}
selected={lastSelectedSuggestion === -1}
showTitleAsLabel
Expand All @@ -312,7 +325,7 @@ export function SuggestionPanel({
icon: suggestion.previewIcon,
title: suggestion.title,
}}
ExpressionRenderer={ExpressionRendererComponent}
ExpressionRenderer={AutoRefreshExpressionRenderer}
key={index}
onSelect={() => {
trackUiEvent('suggestion_clicked');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,17 @@ import { ReactWrapper } from 'enzyme';
import { DragDrop, ChildDragDropProvider } from '../../drag_drop';
import { Ast } from '@kbn/interpreter/common';
import { coreMock } from 'src/core/public/mocks';
import { esFilters, IFieldType, IIndexPattern } from '../../../../../../src/plugins/data/public';
import {
DataPublicPluginStart,
esFilters,
IFieldType,
IIndexPattern,
} from '../../../../../../src/plugins/data/public';
import { TriggerId, UiActionsStart } from '../../../../../../src/plugins/ui_actions/public';
import { uiActionsPluginMock } from '../../../../../../src/plugins/ui_actions/public/mocks';
import { TriggerContract } from '../../../../../../src/plugins/ui_actions/public/triggers';
import { VIS_EVENT_TO_TRIGGER } from '../../../../../../src/plugins/visualizations/public/embeddable';
import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks';

describe('workspace_panel', () => {
let mockVisualization: jest.Mocked<Visualization>;
Expand All @@ -34,13 +40,15 @@ describe('workspace_panel', () => {

let expressionRendererMock: jest.Mock<React.ReactElement, [ReactExpressionRendererProps]>;
let uiActionsMock: jest.Mocked<UiActionsStart>;
let dataMock: jest.Mocked<DataPublicPluginStart>;
let trigger: jest.Mocked<TriggerContract<TriggerId>>;

let instance: ReactWrapper<WorkspacePanelProps>;

beforeEach(() => {
trigger = ({ exec: jest.fn() } as unknown) as jest.Mocked<TriggerContract<TriggerId>>;
uiActionsMock = uiActionsPluginMock.createStartContract();
dataMock = dataPluginMock.createStartContract();
uiActionsMock.getTrigger.mockReturnValue(trigger);
mockVisualization = createMockVisualization();
mockVisualization2 = createMockVisualization();
Expand Down Expand Up @@ -69,7 +77,7 @@ describe('workspace_panel', () => {
dispatch={() => {}}
ExpressionRenderer={expressionRendererMock}
core={coreMock.createSetup()}
plugins={{ uiActions: uiActionsMock }}
plugins={{ uiActions: uiActionsMock, data: dataMock }}
/>
);

Expand All @@ -92,7 +100,7 @@ describe('workspace_panel', () => {
dispatch={() => {}}
ExpressionRenderer={expressionRendererMock}
core={coreMock.createSetup()}
plugins={{ uiActions: uiActionsMock }}
plugins={{ uiActions: uiActionsMock, data: dataMock }}
/>
);

Expand All @@ -115,7 +123,7 @@ describe('workspace_panel', () => {
dispatch={() => {}}
ExpressionRenderer={expressionRendererMock}
core={coreMock.createSetup()}
plugins={{ uiActions: uiActionsMock }}
plugins={{ uiActions: uiActionsMock, data: dataMock }}
/>
);

Expand Down Expand Up @@ -152,7 +160,7 @@ describe('workspace_panel', () => {
dispatch={() => {}}
ExpressionRenderer={expressionRendererMock}
core={coreMock.createSetup()}
plugins={{ uiActions: uiActionsMock }}
plugins={{ uiActions: uiActionsMock, data: dataMock }}
/>
);

Expand Down Expand Up @@ -240,7 +248,7 @@ describe('workspace_panel', () => {
dispatch={() => {}}
ExpressionRenderer={expressionRendererMock}
core={coreMock.createSetup()}
plugins={{ uiActions: uiActionsMock }}
plugins={{ uiActions: uiActionsMock, data: dataMock }}
/>
);

Expand Down Expand Up @@ -292,7 +300,7 @@ describe('workspace_panel', () => {
dispatch={() => {}}
ExpressionRenderer={expressionRendererMock}
core={coreMock.createSetup()}
plugins={{ uiActions: uiActionsMock }}
plugins={{ uiActions: uiActionsMock, data: dataMock }}
/>
);

Expand Down Expand Up @@ -372,7 +380,7 @@ describe('workspace_panel', () => {
dispatch={() => {}}
ExpressionRenderer={expressionRendererMock}
core={coreMock.createSetup()}
plugins={{ uiActions: uiActionsMock }}
plugins={{ uiActions: uiActionsMock, data: dataMock }}
/>
);
});
Expand Down Expand Up @@ -427,7 +435,7 @@ describe('workspace_panel', () => {
dispatch={() => {}}
ExpressionRenderer={expressionRendererMock}
core={coreMock.createSetup()}
plugins={{ uiActions: uiActionsMock }}
plugins={{ uiActions: uiActionsMock, data: dataMock }}
/>
);
});
Expand Down Expand Up @@ -482,7 +490,7 @@ describe('workspace_panel', () => {
dispatch={() => {}}
ExpressionRenderer={expressionRendererMock}
core={coreMock.createSetup()}
plugins={{ uiActions: uiActionsMock }}
plugins={{ uiActions: uiActionsMock, data: dataMock }}
/>
);

Expand Down Expand Up @@ -520,7 +528,7 @@ describe('workspace_panel', () => {
dispatch={() => {}}
ExpressionRenderer={expressionRendererMock}
core={coreMock.createSetup()}
plugins={{ uiActions: uiActionsMock }}
plugins={{ uiActions: uiActionsMock, data: dataMock }}
/>
);
});
Expand Down Expand Up @@ -564,7 +572,7 @@ describe('workspace_panel', () => {
dispatch={() => {}}
ExpressionRenderer={expressionRendererMock}
core={coreMock.createSetup()}
plugins={{ uiActions: uiActionsMock }}
plugins={{ uiActions: uiActionsMock, data: dataMock }}
/>
);
});
Expand Down Expand Up @@ -620,7 +628,7 @@ describe('workspace_panel', () => {
dispatch={mockDispatch}
ExpressionRenderer={expressionRendererMock}
core={coreMock.createSetup()}
plugins={{ uiActions: uiActionsMock }}
plugins={{ uiActions: uiActionsMock, data: dataMock }}
/>
</ChildDragDropProvider>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { debouncedComponent } from '../../debounced_component';
import { trackUiEvent } from '../../lens_ui_telemetry';
import { UiActionsStart } from '../../../../../../src/plugins/ui_actions/public';
import { VIS_EVENT_TO_TRIGGER } from '../../../../../../src/plugins/visualizations/public';
import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public';

export interface WorkspacePanelProps {
activeVisualizationId: string | null;
Expand All @@ -54,7 +55,7 @@ export interface WorkspacePanelProps {
dispatch: (action: Action) => void;
ExpressionRenderer: ReactExpressionRendererType;
core: CoreStart | CoreSetup;
plugins: { uiActions?: UiActionsStart };
plugins: { uiActions?: UiActionsStart; data: DataPublicPluginStart };
}

export const WorkspacePanel = debouncedComponent(InnerWorkspacePanel);
Expand Down Expand Up @@ -135,6 +136,11 @@ export function InnerWorkspacePanel({
framePublicAPI.filters,
]);

const autoRefreshFetch$ = useMemo(
() => plugins.data.query.timefilter.timefilter.getAutoRefreshFetch$(),
[plugins.data.query.timefilter.timefilter.getAutoRefreshFetch$]
);

useEffect(() => {
// reset expression error if component attempts to run it again
if (expression && localState.expressionBuildError) {
Expand Down Expand Up @@ -224,6 +230,7 @@ export function InnerWorkspacePanel({
className="lnsExpressionRenderer__component"
padding="m"
expression={expression!}
reload$={autoRefreshFetch$}
onEvent={(event: ExpressionRendererEvent) => {
if (!plugins.uiActions) {
// ui actions not available, not handling event...
Expand Down

0 comments on commit 4a1b05c

Please sign in to comment.