diff --git a/src/plugins/expressions/public/react_expression_renderer.test.tsx b/src/plugins/expressions/public/react_expression_renderer.test.tsx
index 702f88d785756..7c1711f056d69 100644
--- a/src/plugins/expressions/public/react_expression_renderer.test.tsx
+++ b/src/plugins/expressions/public/react_expression_renderer.test.tsx
@@ -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();
+
+ 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());
diff --git a/src/plugins/expressions/public/react_expression_renderer.tsx b/src/plugins/expressions/public/react_expression_renderer.tsx
index a83c63443906b..bf716a3b9b1e8 100644
--- a/src/plugins/expressions/public/react_expression_renderer.tsx
+++ b/src/plugins/expressions/public/react_expression_renderer.tsx
@@ -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;
}
export type ReactExpressionRendererType = React.ComponentType;
@@ -63,6 +67,7 @@ export const ReactExpressionRenderer = ({
renderError,
expression,
onEvent,
+ reload$,
...expressionLoaderOptions
}: ReactExpressionRendererProps) => {
const mountpoint: React.MutableRefObject = 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(
() => {
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx
index 90405b98afe65..07c76a81ed62d 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx
@@ -305,6 +305,7 @@ export function EditorFrame(props: EditorFrameProps) {
dispatch={dispatch}
ExpressionRenderer={props.ExpressionRenderer}
stagedPreview={state.stagedPreview}
+ plugins={props.plugins}
/>
)
}
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx
index 6b0f0338d4015..fd509c0046e13 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx
@@ -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() },
};
});
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx
index 0f0885d696ba4..b06b316ec79aa 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx
@@ -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) => (
+
+ );
+ }, [plugins.data.query.timefilter.timefilter.getAutoRefreshFetch$, ExpressionRendererComponent]);
+
const [lastSelectedSuggestion, setLastSelectedSuggestion] = useState(-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');
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.test.tsx
index 59b5f358e190f..49d12e9f41440 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.test.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.test.tsx
@@ -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;
@@ -34,6 +40,7 @@ describe('workspace_panel', () => {
let expressionRendererMock: jest.Mock;
let uiActionsMock: jest.Mocked;
+ let dataMock: jest.Mocked;
let trigger: jest.Mocked>;
let instance: ReactWrapper;
@@ -41,6 +48,7 @@ describe('workspace_panel', () => {
beforeEach(() => {
trigger = ({ exec: jest.fn() } as unknown) as jest.Mocked>;
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 }}
/>
);
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.tsx
index 44dd9f8364870..76da38ead6523 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel.tsx
@@ -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...