Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Lens] Expose active data in some places #79851

Merged
merged 13 commits into from
Nov 6, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
<b>Signature:</b>

```typescript
ReactExpressionRenderer: ({ className, dataAttrs, padding, renderError, expression, onEvent, reload$, debounce, ...expressionLoaderOptions }: ReactExpressionRendererProps) => JSX.Element
ReactExpressionRenderer: ({ className, dataAttrs, padding, renderError, expression, onEvent, onData$, reload$, debounce, ...expressionLoaderOptions }: ReactExpressionRendererProps) => JSX.Element
```
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface ReactExpressionRendererProps extends IExpressionLoaderParams
| [dataAttrs](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.dataattrs.md) | <code>string[]</code> | |
| [debounce](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.debounce.md) | <code>number</code> | |
| [expression](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.expression.md) | <code>string &#124; ExpressionAstExpression</code> | |
| [onData$](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md) | <code>&lt;TData, TInspectorAdapters&gt;(data: TData, adapters?: TInspectorAdapters) =&gt; void</code> | |
| [onEvent](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.onevent.md) | <code>(event: ExpressionRendererEvent) =&gt; void</code> | |
| [padding](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.padding.md) | <code>'xs' &#124; 's' &#124; 'm' &#124; 'l' &#124; 'xl'</code> | |
| [reload$](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.reload_.md) | <code>Observable&lt;unknown&gt;</code> | An observable which can be used to re-run the expression without destroying the component |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) &gt; [ReactExpressionRendererProps](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md) &gt; [onData$](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md)

## ReactExpressionRendererProps.onData$ property

<b>Signature:</b>

```typescript
onData$?: <TData, TInspectorAdapters>(data: TData, adapters?: TInspectorAdapters) => void;
```
4 changes: 3 additions & 1 deletion src/plugins/expressions/public/public.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1057,7 +1057,7 @@ export interface Range {
// Warning: (ae-missing-release-tag) "ReactExpressionRenderer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export const ReactExpressionRenderer: ({ className, dataAttrs, padding, renderError, expression, onEvent, reload$, debounce, ...expressionLoaderOptions }: ReactExpressionRendererProps) => JSX.Element;
export const ReactExpressionRenderer: ({ className, dataAttrs, padding, renderError, expression, onEvent, onData$, reload$, debounce, ...expressionLoaderOptions }: ReactExpressionRendererProps) => JSX.Element;

// Warning: (ae-missing-release-tag) "ReactExpressionRendererProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
Expand All @@ -1072,6 +1072,8 @@ export interface ReactExpressionRendererProps extends IExpressionLoaderParams {
// (undocumented)
expression: string | ExpressionAstExpression;
// (undocumented)
onData$?: <TData, TInspectorAdapters>(data: TData, adapters?: TInspectorAdapters) => void;
// (undocumented)
onEvent?: (event: ExpressionRendererEvent) => void;
// (undocumented)
padding?: 'xs' | 's' | 'm' | 'l' | 'xl';
Expand Down
32 changes: 32 additions & 0 deletions src/plugins/expressions/public/react_expression_renderer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,38 @@ describe('ExpressionRenderer', () => {
expect(instance.find('[data-test-subj="custom-error"]')).toHaveLength(0);
});

it('should call onData$ prop on every data$ observable emission in loader', () => {
const dataSubject = new Subject();
const data$ = dataSubject.asObservable().pipe(share());

const newData = {};
const inspectData = {};
const onData$ = jest.fn();

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

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

expect(onData$).toHaveBeenCalledTimes(0);

act(() => {
dataSubject.next(newData);
});

expect(onData$).toHaveBeenCalledTimes(1);
expect(onData$.mock.calls[0][0]).toBe(newData);
expect(onData$.mock.calls[0][1]).toBe(inspectData);
});

it('should fire onEvent prop on every events$ observable emission in loader', () => {
const dataSubject = new Subject();
const data$ = dataSubject.asObservable().pipe(share());
Expand Down
9 changes: 9 additions & 0 deletions src/plugins/expressions/public/react_expression_renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export interface ReactExpressionRendererProps extends IExpressionLoaderParams {
) => React.ReactElement | React.ReactElement[];
padding?: 'xs' | 's' | 'm' | 'l' | 'xl';
onEvent?: (event: ExpressionRendererEvent) => void;
onData$?: <TData, TInspectorAdapters>(data: TData, adapters?: TInspectorAdapters) => void;
/**
* An observable which can be used to re-run the expression without destroying the component
*/
Expand Down Expand Up @@ -71,6 +72,7 @@ export const ReactExpressionRenderer = ({
renderError,
expression,
onEvent,
onData$,
reload$,
debounce,
...expressionLoaderOptions
Expand Down Expand Up @@ -135,6 +137,13 @@ export const ReactExpressionRenderer = ({
})
);
}
if (onData$) {
subs.push(
expressionLoaderRef.current.data$.subscribe((newData) => {
onData$(newData, expressionLoaderRef.current?.inspect());
})
);
}
subs.push(
expressionLoaderRef.current.loading$.subscribe(() => {
hasHandledErrorRef.current = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export function LayerPanel(
state: props.visualizationState,
frame: props.framePublicAPI,
dateRange: props.framePublicAPI.dateRange,
activeData: props.framePublicAPI.activeData,
};
const datasourceId = datasourcePublicAPI.datasourceId;
const layerDatasourceState = props.datasourceStates[datasourceId].state;
Expand All @@ -111,6 +112,7 @@ export function LayerPanel(
...layerDatasourceDropProps,
frame: props.framePublicAPI,
dateRange: props.framePublicAPI.dateRange,
activeData: props.framePublicAPI.activeData,
};

const { groups } = activeVisualization.getConfiguration(layerVisualizationConfigProps);
Expand Down Expand Up @@ -140,6 +142,7 @@ export function LayerPanel(
nativeProps={{
layerId,
state: layerDatasourceState,
activeData: props.framePublicAPI.activeData,
setState: (updater: unknown) => {
const newState =
typeof updater === 'function' ? updater(layerDatasourceState) : updater;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export function EditorFrame(props: EditorFrameProps) {

const framePublicAPI: FramePublicAPI = {
datasourceLayers,
activeData: state.activeData,
dateRange: props.dateRange,
query: props.query,
filters: props.filters,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import { EditorFrameProps } from './index';
import { Document } from '../../persistence/saved_object_store';
import { TableInspectorAdapter } from '../types';

export interface PreviewState {
visualization: {
Expand All @@ -21,6 +22,7 @@ export interface EditorFrameState extends PreviewState {
description?: string;
stagedPreview?: PreviewState;
activeDatasourceId: string | null;
activeData?: TableInspectorAdapter;
}

export type Action =
Expand All @@ -32,6 +34,10 @@ export type Action =
type: 'UPDATE_TITLE';
title: string;
}
| {
type: 'UPDATE_ACTIVE_DATA';
tables: TableInspectorAdapter;
}
| {
type: 'UPDATE_STATE';
// Just for diagnostics, so we can determine what action
Expand Down Expand Up @@ -139,6 +145,11 @@ export const reducer = (state: EditorFrameState, action: Action): EditorFrameSta
return { ...state, title: action.title };
case 'UPDATE_STATE':
return action.updater(state);
case 'UPDATE_ACTIVE_DATA':
return {
...state,
activeData: action.tables,
};
case 'UPDATE_LAYER':
return {
...state,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import _ from 'lodash';
import { Ast } from '@kbn/interpreter/common';
import { IconType } from '@elastic/eui/src/components/icon/icon';
import { Datatable } from 'src/plugins/expressions';
import { PaletteOutput } from 'src/plugins/charts/public';
import { VisualizeFieldContext } from '../../../../../../src/plugins/ui_actions/public';
import {
Expand Down Expand Up @@ -50,6 +51,7 @@ export function getSuggestions({
visualizationState,
field,
visualizeTriggerFieldContext,
activeData,
mainPalette,
}: {
datasourceMap: Record<string, Datasource>;
Expand All @@ -66,6 +68,7 @@ export function getSuggestions({
visualizationState: unknown;
field?: unknown;
visualizeTriggerFieldContext?: VisualizeFieldContext;
activeData?: Record<string, Datatable>;
mainPalette?: PaletteOutput;
}): Suggestion[] {
const datasources = Object.entries(datasourceMap).filter(
Expand All @@ -87,7 +90,8 @@ export function getSuggestions({
dataSourceSuggestions = datasource.getDatasourceSuggestionsForField(datasourceState, field);
} else {
dataSourceSuggestions = datasource.getDatasourceSuggestionsFromCurrentState(
datasourceState
datasourceState,
activeData
);
}
return dataSourceSuggestions.map((suggestion) => ({ ...suggestion, datasourceId }));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ export function SuggestionPanel({
visualizationMap,
activeVisualizationId: currentVisualizationId,
visualizationState: currentVisualizationState,
activeData: frame.activeData,
})
.filter((suggestion) => !suggestion.hide)
.filter(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ function getTopSuggestion(
activeVisualizationId: props.visualizationId,
visualizationState: props.visualizationState,
subVisualizationId,
activeData: props.framePublicAPI.activeData,
mainPalette,
});
const suggestions = unfilteredSuggestions.filter((suggestion) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,48 @@ describe('workspace_panel', () => {
expect(trigger.exec).toHaveBeenCalledWith({ data: eventData });
});

it('should push add current data table to state on data$ emitting value', () => {
const framePublicAPI = createMockFramePublicAPI();
framePublicAPI.datasourceLayers = {
first: mockDatasource.publicAPIMock,
};
mockDatasource.toExpression.mockReturnValue('datasource');
mockDatasource.getLayers.mockReturnValue(['first']);
const dispatch = jest.fn();

instance = mount(
<WorkspacePanel
activeDatasourceId={'mock'}
datasourceStates={{
mock: {
state: {},
isLoading: false,
},
}}
datasourceMap={{
mock: mockDatasource,
}}
framePublicAPI={framePublicAPI}
activeVisualizationId="vis"
visualizationMap={{
vis: { ...mockVisualization, toExpression: () => 'vis' },
}}
visualizationState={{}}
dispatch={dispatch}
ExpressionRenderer={expressionRendererMock}
core={coreMock.createSetup()}
plugins={{ uiActions: uiActionsMock, data: dataMock }}
/>
);

const onData = expressionRendererMock.mock.calls[0][0].onData$!;

const tableData = { table1: { columns: [], rows: [] } };
onData(undefined, { tables: tableData });

expect(dispatch).toHaveBeenCalledWith({ type: 'UPDATE_ACTIVE_DATA', tables: tableData });
});

it('should include data fetching for each layer in the expression', () => {
const mockDatasource2 = createMockDatasource('a');
const framePublicAPI = createMockFramePublicAPI();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import {
} from '../../../../../../../src/plugins/data/public';
import { WorkspacePanelWrapper } from './workspace_panel_wrapper';
import { DropIllustration } from '../../../assets/drop_illustration';
import { LensInspectorAdapters } from '../../types';
import { getOriginalRequestErrorMessage } from '../../error_helper';
import { validateDatasourceAndVisualization } from '../state_helpers';

Expand Down Expand Up @@ -296,6 +297,7 @@ export function WorkspacePanel({
expression={expression}
framePublicAPI={framePublicAPI}
timefilter={plugins.data.query.timefilter.timefilter}
dispatch={dispatch}
onEvent={onEvent}
setLocalState={setLocalState}
localState={{ ...localState, configurationValidationError }}
Expand Down Expand Up @@ -340,11 +342,13 @@ export const InnerVisualizationWrapper = ({
setLocalState,
localState,
ExpressionRendererComponent,
dispatch,
}: {
expression: Ast | null | undefined;
framePublicAPI: FramePublicAPI;
timefilter: TimefilterContract;
onEvent: (event: ExpressionRendererEvent) => void;
dispatch: (action: Action) => void;
setLocalState: (dispatch: (prevState: WorkspaceState) => WorkspaceState) => void;
localState: WorkspaceState & {
configurationValidationError?: Array<{ shortMessage: string; longMessage: string }>;
Expand All @@ -370,6 +374,18 @@ export const InnerVisualizationWrapper = ({
]
);

const onData$ = useCallback(
(data: unknown, inspectorAdapters?: LensInspectorAdapters) => {
if (inspectorAdapters && inspectorAdapters.tables) {
dispatch({
type: 'UPDATE_ACTIVE_DATA',
tables: inspectorAdapters.tables,
});
}
},
[dispatch]
);

if (localState.configurationValidationError) {
let showExtraErrors = null;
if (localState.configurationValidationError.length > 1) {
Expand Down Expand Up @@ -456,6 +472,7 @@ export const InnerVisualizationWrapper = ({
searchContext={context}
reload$={autoRefreshFetch$}
onEvent={onEvent}
onData$={onData$}
renderError={(errorMessage?: string | null, error?: ExpressionRenderError | null) => {
const visibleErrorMessage = getOriginalRequestErrorMessage(error) || errorMessage;

Expand Down
Loading