Lens editor auto refresh (#65868)
This commit is contained in:
parent
fbcb74ce28
commit
4a1b05c843
|
@ -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());
|
||||
|
|
|
@ -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';
|
||||
|
@ -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>;
|
||||
|
@ -63,6 +67,7 @@ export const ReactExpressionRenderer = ({
|
|||
renderError,
|
||||
expression,
|
||||
onEvent,
|
||||
reload$,
|
||||
...expressionLoaderOptions
|
||||
}: ReactExpressionRendererProps) => {
|
||||
const mountpoint: React.MutableRefObject<null | HTMLDivElement> = useRef(null);
|
||||
|
@ -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(
|
||||
() => {
|
||||
|
|
|
@ -305,6 +305,7 @@ export function EditorFrame(props: EditorFrameProps) {
|
|||
dispatch={dispatch}
|
||||
ExpressionRenderer={props.ExpressionRenderer}
|
||||
stagedPreview={state.stagedPreview}
|
||||
plugins={props.plugins}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
@ -85,6 +86,7 @@ describe('suggestion_panel', () => {
|
|||
dispatch: dispatchMock,
|
||||
ExpressionRenderer: expressionRendererMock,
|
||||
frame: createMockFramePublicAPI(),
|
||||
plugins: { data: dataPluginMock.createStartContract() },
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -52,6 +56,7 @@ export interface SuggestionPanelProps {
|
|||
ExpressionRenderer: ReactExpressionRendererType;
|
||||
frame: FramePublicAPI;
|
||||
stagedPreview?: PreviewState;
|
||||
plugins: { data: DataPublicPluginStart };
|
||||
}
|
||||
|
||||
const PreviewRenderer = ({
|
||||
|
@ -154,6 +159,7 @@ export function SuggestionPanel({
|
|||
frame,
|
||||
ExpressionRenderer: ExpressionRendererComponent,
|
||||
stagedPreview,
|
||||
plugins,
|
||||
}: SuggestionPanelProps) {
|
||||
const currentDatasourceStates = stagedPreview ? stagedPreview.datasourceStates : datasourceStates;
|
||||
const currentVisualizationState = stagedPreview
|
||||
|
@ -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(() => {
|
||||
|
@ -296,7 +309,7 @@ export function SuggestionPanel({
|
|||
defaultMessage: 'Current',
|
||||
}),
|
||||
}}
|
||||
ExpressionRenderer={ExpressionRendererComponent}
|
||||
ExpressionRenderer={AutoRefreshExpressionRenderer}
|
||||
onSelect={rollbackToCurrentVisualization}
|
||||
selected={lastSelectedSuggestion === -1}
|
||||
showTitleAsLabel
|
||||
|
@ -312,7 +325,7 @@ export function SuggestionPanel({
|
|||
icon: suggestion.previewIcon,
|
||||
title: suggestion.title,
|
||||
}}
|
||||
ExpressionRenderer={ExpressionRendererComponent}
|
||||
ExpressionRenderer={AutoRefreshExpressionRenderer}
|
||||
key={index}
|
||||
onSelect={() => {
|
||||
trackUiEvent('suggestion_clicked');
|
||||
|
|
|
@ -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>;
|
||||
|
@ -34,6 +40,7 @@ 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>;
|
||||
|
@ -41,6 +48,7 @@ describe('workspace_panel', () => {
|
|||
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();
|
||||
|
@ -69,7 +77,7 @@ describe('workspace_panel', () => {
|
|||
dispatch={() => {}}
|
||||
ExpressionRenderer={expressionRendererMock}
|
||||
core={coreMock.createSetup()}
|
||||
plugins={{ uiActions: uiActionsMock }}
|
||||
plugins={{ uiActions: uiActionsMock, data: dataMock }}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -92,7 +100,7 @@ describe('workspace_panel', () => {
|
|||
dispatch={() => {}}
|
||||
ExpressionRenderer={expressionRendererMock}
|
||||
core={coreMock.createSetup()}
|
||||
plugins={{ uiActions: uiActionsMock }}
|
||||
plugins={{ uiActions: uiActionsMock, data: dataMock }}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -115,7 +123,7 @@ describe('workspace_panel', () => {
|
|||
dispatch={() => {}}
|
||||
ExpressionRenderer={expressionRendererMock}
|
||||
core={coreMock.createSetup()}
|
||||
plugins={{ uiActions: uiActionsMock }}
|
||||
plugins={{ uiActions: uiActionsMock, data: dataMock }}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -152,7 +160,7 @@ describe('workspace_panel', () => {
|
|||
dispatch={() => {}}
|
||||
ExpressionRenderer={expressionRendererMock}
|
||||
core={coreMock.createSetup()}
|
||||
plugins={{ uiActions: uiActionsMock }}
|
||||
plugins={{ uiActions: uiActionsMock, data: dataMock }}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -240,7 +248,7 @@ describe('workspace_panel', () => {
|
|||
dispatch={() => {}}
|
||||
ExpressionRenderer={expressionRendererMock}
|
||||
core={coreMock.createSetup()}
|
||||
plugins={{ uiActions: uiActionsMock }}
|
||||
plugins={{ uiActions: uiActionsMock, data: dataMock }}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -292,7 +300,7 @@ describe('workspace_panel', () => {
|
|||
dispatch={() => {}}
|
||||
ExpressionRenderer={expressionRendererMock}
|
||||
core={coreMock.createSetup()}
|
||||
plugins={{ uiActions: uiActionsMock }}
|
||||
plugins={{ uiActions: uiActionsMock, data: dataMock }}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -372,7 +380,7 @@ describe('workspace_panel', () => {
|
|||
dispatch={() => {}}
|
||||
ExpressionRenderer={expressionRendererMock}
|
||||
core={coreMock.createSetup()}
|
||||
plugins={{ uiActions: uiActionsMock }}
|
||||
plugins={{ uiActions: uiActionsMock, data: dataMock }}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
@ -427,7 +435,7 @@ describe('workspace_panel', () => {
|
|||
dispatch={() => {}}
|
||||
ExpressionRenderer={expressionRendererMock}
|
||||
core={coreMock.createSetup()}
|
||||
plugins={{ uiActions: uiActionsMock }}
|
||||
plugins={{ uiActions: uiActionsMock, data: dataMock }}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
@ -482,7 +490,7 @@ describe('workspace_panel', () => {
|
|||
dispatch={() => {}}
|
||||
ExpressionRenderer={expressionRendererMock}
|
||||
core={coreMock.createSetup()}
|
||||
plugins={{ uiActions: uiActionsMock }}
|
||||
plugins={{ uiActions: uiActionsMock, data: dataMock }}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -520,7 +528,7 @@ describe('workspace_panel', () => {
|
|||
dispatch={() => {}}
|
||||
ExpressionRenderer={expressionRendererMock}
|
||||
core={coreMock.createSetup()}
|
||||
plugins={{ uiActions: uiActionsMock }}
|
||||
plugins={{ uiActions: uiActionsMock, data: dataMock }}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
@ -564,7 +572,7 @@ describe('workspace_panel', () => {
|
|||
dispatch={() => {}}
|
||||
ExpressionRenderer={expressionRendererMock}
|
||||
core={coreMock.createSetup()}
|
||||
plugins={{ uiActions: uiActionsMock }}
|
||||
plugins={{ uiActions: uiActionsMock, data: dataMock }}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
@ -620,7 +628,7 @@ describe('workspace_panel', () => {
|
|||
dispatch={mockDispatch}
|
||||
ExpressionRenderer={expressionRendererMock}
|
||||
core={coreMock.createSetup()}
|
||||
plugins={{ uiActions: uiActionsMock }}
|
||||
plugins={{ uiActions: uiActionsMock, data: dataMock }}
|
||||
/>
|
||||
</ChildDragDropProvider>
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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...
|
||||
|
|
Loading…
Reference in a new issue