[Lens] Remove <NativeRenderer /> (#161521)

## Summary

The NativeRenderer component is currently used to mount another
component in a separate mounting point. As far as I recall, we
introduced <NativeRenderer/> to allow users to create visualizations in
non-React frameworks. The idea was that users could write their own Lens
visualizations or datasources code and integrate it with our system.
However, it seems that this concept hasn't gained traction and we don’t
have it prioritized. Even if users express interest in writing their
visualizations outside of React, it is still possible to do so with some
additional boilerplate code (which we could provide as an example
non-React visualization).
Pros:

1. Simplifies and shortens the code:
1.1. Testing and debugging become easier as we no longer need to check
separate React trees when integrating frame, data source, and
visualization components.
1.2. Components communicate using standard React patterns, making
maintenance and comprehension simpler.
1.3. Context providers no longer need to be passed to each separate
component since they are already within the context.
1.4. Easier propagation of events or any other form of inter-component
communication.

2. Greatly improves performance and facilitates maintenance:
2.1. Directly accessing context inside the DatasourcePanel eliminates
the need for context passing, resulting in better performance.
2.2. Removing the requirement for a separate React root also contributes
to improved performance.

3. The render method will be removed when we upgrade to React 18. While
we could replace it with the new createRoot method, it makes sense to
perform some cleanup ;)

Cons:
1. Setting up non-React visualization or data source code might become
slightly more complex.

Performance improvement for drag and drop action with these changes:

before:

<img width="1110" alt="Screenshot 2023-07-10 at 07 14 39"
src="45a1b09b-5189-46f5-af2b-7781fcf4e774">

after:

<img width="1117" alt="Screenshot 2023-07-10 at 07 16 24"
src="0e704da1-3220-4eb9-8fa0-cc3584a90090">

## Single render when dragging:

(the first image is 3 screenshots from 3 different react roots as they
have separate mounting point. The complete render time is ~380ms)
<img width="1117" alt="Screenshot 2023-07-10 at 07 16 24"
src="6d7f2d9f-a758-476e-8efb-38693ae90097">

After we have one common render tree. Because we don't have to pass
context down as a prop, we greatly reduced the number of components
rerendered. (I will be working on reducing the render time for workspace
panel as this seems to still be a bottleneck point)
<img width="732" alt="Screenshot 2023-07-10 at 14 52 41"
src="03ec97b3-8225-490e-8884-0fd4e69587e8">

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Marta Bondyra 2023-07-11 14:05:45 +02:00 committed by GitHub
parent 5d066944fc
commit 95e50875e1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 425 additions and 1197 deletions

View file

@ -16,9 +16,6 @@
"developerExamples",
"expressions",
"fieldFormats"
],
"requiredBundles": [
"kibanaReact"
]
}
}

View file

@ -7,10 +7,8 @@
import React from 'react';
import { EuiFormRow, EuiColorPicker } from '@elastic/eui';
import { render } from 'react-dom';
import { Ast } from '@kbn/interpreter';
import { ThemeServiceStart } from '@kbn/core/public';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { Visualization, OperationMetadata } from '@kbn/lens-plugin/public';
import { layerTypes } from '@kbn/lens-plugin/public';
import type { RotatingNumberState } from '../common/types';
@ -166,19 +164,16 @@ export const getRotatingNumberVisualization = ({
return { ...prevState, accessor: undefined };
},
renderDimensionEditor(domElement, props) {
render(
<KibanaThemeProvider theme$={theme.theme$}>
<EuiFormRow label="Pick a color">
<EuiColorPicker
onChange={(newColor) => {
props.setState({ ...props.state, color: newColor });
}}
color={props.state.color}
/>
</EuiFormRow>
</KibanaThemeProvider>,
domElement
DimensionEditorComponent(props) {
return (
<EuiFormRow label="Pick a color">
<EuiColorPicker
onChange={(newColor) => {
props.setState({ ...props.state, color: newColor });
}}
color={props.state.color}
/>
</EuiFormRow>
);
},
});

View file

@ -16,7 +16,6 @@
"kbn_references": [
"@kbn/core",
"@kbn/expressions-plugin",
"@kbn/kibana-react-plugin",
"@kbn/data-views-plugin",
"@kbn/field-formats-plugin",
"@kbn/lens-plugin",

View file

@ -196,6 +196,7 @@ describe('LensEditConfigurationFlyout', () => {
const { instance } = await prepareAndMountComponent(props);
expect(instance.find(VisualizationToolbar).prop('activeVisualization')).toMatchInlineSnapshot(`
Object {
"DimensionEditorComponent": [MockFunction],
"appendLayer": [MockFunction],
"clearLayer": [MockFunction],
"getConfiguration": [MockFunction] {
@ -414,7 +415,6 @@ describe('LensEditConfigurationFlyout', () => {
"initialize": [MockFunction],
"removeDimension": [MockFunction],
"removeLayer": [MockFunction],
"renderDimensionEditor": [MockFunction],
"setDimension": [MockFunction],
"switchVisualizationType": [MockFunction],
"toExpression": [MockFunction],

View file

@ -7,7 +7,6 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { createMockedDragDropContext } from '../../mocks';
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
import {
dataViewPluginMocks,
@ -408,9 +407,6 @@ describe('FormBased Data Panel', () => {
dataViews,
}),
setState: jest.fn(),
dragDropContext: createMockedDragDropContext({
dragging: { id: '1', humanData: { label: 'Label' } },
}),
dateRange: { fromDate: '2019-01-01', toDate: '2020-01-01' },
frame: getFrameAPIMock({
indexPatterns: indexPatterns as unknown as DataViewsState['indexPatterns'],

View file

@ -6,8 +6,6 @@
*/
import React from 'react';
import { render } from 'react-dom';
import { I18nProvider } from '@kbn/i18n-react';
import type { CoreStart, SavedObjectReference } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import { TimeRange } from '@kbn/es-query';
@ -16,7 +14,6 @@ import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import { flatten, isEqual } from 'lodash';
import type { DataViewsPublicPluginStart, DataView } from '@kbn/data-views-plugin/public';
import type { IndexPatternFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public';
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { DataPublicPluginStart, UI_SETTINGS } from '@kbn/data-plugin/public';
import { VisualizeFieldContext } from '@kbn/ui-actions-plugin/public';
import { ChartsPluginSetup } from '@kbn/charts-plugin/public';
@ -25,7 +22,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
import { EuiButton } from '@elastic/eui';
import type { SharePluginStart } from '@kbn/share-plugin/public';
import { ChildDragDropProvider, type DraggingIdentifier } from '@kbn/dom-drag-drop';
import { type DraggingIdentifier } from '@kbn/dom-drag-drop';
import { DimensionTrigger } from '@kbn/visualization-ui-components/public';
import memoizeOne from 'memoize-one';
import type {
@ -191,7 +188,7 @@ export function getFormBasedDatasource({
dataViewFieldEditor: IndexPatternFieldEditorStart;
uiActions: UiActionsStart;
}) {
const { uiSettings, settings } = core;
const { uiSettings } = core;
const DATASOURCE_ID = 'formBased';
const ALIAS_IDS = ['indexpattern'];
@ -452,68 +449,27 @@ export function getFormBasedDatasource({
searchSessionId
),
renderLayerSettings(domElement, props) {
render(
<KibanaThemeProvider theme$={core.theme.theme$}>
<I18nProvider>
<KibanaContextProvider
services={{
...core,
data,
dataViews,
fieldFormats,
charts,
unifiedSearch,
share,
}}
>
<LayerSettingsPanel {...props} />
</KibanaContextProvider>
</I18nProvider>
</KibanaThemeProvider>,
domElement
);
LayerSettingsComponent(props) {
return <LayerSettingsPanel {...props} />;
},
renderDataPanel(domElement: Element, props: DatasourceDataPanelProps<FormBasedPrivateState>) {
const { onChangeIndexPattern, dragDropContext, ...otherProps } = props;
DataPanelComponent(props: DatasourceDataPanelProps<FormBasedPrivateState>) {
const { onChangeIndexPattern, ...otherProps } = props;
const layerFields = formBasedDatasource?.getSelectedFields?.(props.state);
render(
<KibanaThemeProvider theme$={core.theme.theme$}>
<I18nProvider>
<KibanaContextProvider
services={{
...core,
data,
dataViews,
fieldFormats,
charts,
unifiedSearch,
share,
}}
>
<ChildDragDropProvider value={dragDropContext}>
<FormBasedDataPanel
data={data}
dataViews={dataViews}
fieldFormats={fieldFormats}
charts={charts}
indexPatternFieldEditor={dataViewFieldEditor}
{...otherProps}
core={core}
uiActions={uiActions}
onIndexPatternRefresh={onRefreshIndexPattern}
layerFields={layerFields}
/>
</ChildDragDropProvider>
</KibanaContextProvider>
</I18nProvider>
</KibanaThemeProvider>,
domElement
return (
<FormBasedDataPanel
data={data}
dataViews={dataViews}
fieldFormats={fieldFormats}
charts={charts}
indexPatternFieldEditor={dataViewFieldEditor}
{...otherProps}
core={core}
uiActions={uiActions}
onIndexPatternRefresh={onRefreshIndexPattern}
layerFields={layerFields}
/>
);
},
uniqueLabels(state: FormBasedPrivateState, indexPatternsMap: IndexPatternMap) {
const layers = state.layers;
const columnLabelMap = {} as Record<string, string>;
@ -540,115 +496,48 @@ export function getFormBasedDatasource({
return columnLabelMap;
},
renderDimensionTrigger: (
domElement: Element,
props: DatasourceDimensionTriggerProps<FormBasedPrivateState>
) => {
DimensionTriggerComponent: (props: DatasourceDimensionTriggerProps<FormBasedPrivateState>) => {
const columnLabelMap = formBasedDatasource.uniqueLabels(props.state, props.indexPatterns);
const uniqueLabel = columnLabelMap[props.columnId];
const formattedLabel = wrapOnDot(uniqueLabel);
render(
<KibanaThemeProvider theme$={core.theme.theme$}>
<I18nProvider>
<KibanaContextProvider
services={{
appName: 'lens',
storage,
uiSettings,
settings,
data,
fieldFormats,
savedObjects: core.savedObjects,
docLinks: core.docLinks,
unifiedSearch,
}}
>
<DimensionTrigger id={props.columnId} label={formattedLabel} />
</KibanaContextProvider>
</I18nProvider>
</KibanaThemeProvider>,
domElement
);
return <DimensionTrigger id={props.columnId} label={formattedLabel} />;
},
renderDimensionEditor: (
domElement: Element,
props: DatasourceDimensionEditorProps<FormBasedPrivateState>
) => {
DimensionEditorComponent: (props: DatasourceDimensionEditorProps<FormBasedPrivateState>) => {
const columnLabelMap = formBasedDatasource.uniqueLabels(props.state, props.indexPatterns);
render(
<KibanaThemeProvider theme$={core.theme.theme$}>
<I18nProvider>
<KibanaContextProvider
services={{
appName: 'lens',
storage,
uiSettings,
settings,
data,
fieldFormats,
savedObjects: core.savedObjects,
docLinks: core.docLinks,
http: core.http,
unifiedSearch,
}}
>
<FormBasedDimensionEditor
uiSettings={uiSettings}
storage={storage}
fieldFormats={fieldFormats}
http={core.http}
data={data}
unifiedSearch={unifiedSearch}
dataViews={dataViews}
uniqueLabel={columnLabelMap[props.columnId]}
notifications={core.notifications}
{...props}
/>
</KibanaContextProvider>
</I18nProvider>
</KibanaThemeProvider>,
domElement
return (
<FormBasedDimensionEditor
uiSettings={uiSettings}
storage={storage}
fieldFormats={fieldFormats}
http={core.http}
data={data}
unifiedSearch={unifiedSearch}
dataViews={dataViews}
uniqueLabel={columnLabelMap[props.columnId]}
notifications={core.notifications}
{...props}
/>
);
},
renderLayerPanel: (
domElement: Element,
props: DatasourceLayerPanelProps<FormBasedPrivateState>
) => {
LayerPanelComponent: (props: DatasourceLayerPanelProps<FormBasedPrivateState>) => {
const { onChangeIndexPattern, ...otherProps } = props;
render(
<KibanaThemeProvider theme$={core.theme.theme$}>
<I18nProvider>
<KibanaContextProvider
services={{
...core,
data,
dataViews,
fieldFormats,
charts,
unifiedSearch,
share,
}}
>
<LayerPanel
onChangeIndexPattern={(indexPatternId) => {
triggerActionOnIndexPatternChange({
indexPatternId,
state: props.state,
layerId: props.layerId,
uiActions,
});
onChangeIndexPattern(indexPatternId, DATASOURCE_ID, props.layerId);
}}
{...otherProps}
/>
</KibanaContextProvider>
</I18nProvider>
</KibanaThemeProvider>,
domElement
return (
<LayerPanel
onChangeIndexPattern={(indexPatternId) => {
triggerActionOnIndexPatternChange({
indexPatternId,
state: props.state,
layerId: props.layerId,
uiActions,
});
onChangeIndexPattern(indexPatternId, DATASOURCE_ID, props.layerId);
}}
{...otherProps}
/>
);
},

View file

@ -28,7 +28,7 @@ import { mountWithIntl } from '@kbn/test-jest-helpers';
import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks';
import { createIndexPatternServiceMock } from '../../mocks/data_views_service_mock';
import { createMockFramePublicAPI, createMockedDragDropContext } from '../../mocks';
import { createMockFramePublicAPI } from '../../mocks';
import { DataViewsState } from '../../state_management';
const fieldsFromQuery = [
@ -184,7 +184,6 @@ describe('TextBased Query Languages Data Panel', () => {
])
),
},
dragDropContext: createMockedDragDropContext(),
core,
dateRange: {
fromDate: 'now-7d',

View file

@ -6,15 +6,12 @@
*/
import React from 'react';
import { render } from 'react-dom';
import { I18nProvider } from '@kbn/i18n-react';
import { CoreStart } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public';
import type { AggregateQuery } from '@kbn/es-query';
import type { SavedObjectReference } from '@kbn/core/public';
import { EuiFormRow } from '@elastic/eui';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import type { ExpressionsStart, DatatableColumnType } from '@kbn/expressions-plugin/public';
import type { DataViewsPublicPluginStart, DataView } from '@kbn/data-views-plugin/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
@ -22,7 +19,6 @@ import { euiThemeVars } from '@kbn/ui-theme';
import { DimensionTrigger } from '@kbn/visualization-ui-components/public';
import memoizeOne from 'memoize-one';
import { isEqual } from 'lodash';
import { ChildDragDropProvider } from '@kbn/dom-drag-drop';
import {
DatasourceDimensionEditorProps,
DatasourceDataPanelProps,
@ -364,31 +360,20 @@ export function getTextBasedDatasource({
);
},
renderDataPanel(domElement: Element, props: DatasourceDataPanelProps<TextBasedPrivateState>) {
DataPanelComponent(props: DatasourceDataPanelProps<TextBasedPrivateState>) {
const layerFields = TextBasedDatasource?.getSelectedFields?.(props.state);
const { dragDropContext, ...otherProps } = props;
render(
<KibanaThemeProvider theme$={core.theme.theme$}>
<I18nProvider>
<ChildDragDropProvider value={dragDropContext}>
<TextBasedDataPanel
data={data}
dataViews={dataViews}
expressions={expressions}
layerFields={layerFields}
{...otherProps}
/>
</ChildDragDropProvider>
</I18nProvider>
</KibanaThemeProvider>,
domElement
return (
<TextBasedDataPanel
data={data}
dataViews={dataViews}
expressions={expressions}
layerFields={layerFields}
{...props}
/>
);
},
renderDimensionTrigger: (
domElement: Element,
props: DatasourceDimensionTriggerProps<TextBasedPrivateState>
) => {
DimensionTriggerComponent: (props: DatasourceDimensionTriggerProps<TextBasedPrivateState>) => {
const columnLabelMap = TextBasedDatasource.uniqueLabels(props.state, props.indexPatterns);
const layer = props.state.layers[props.layerId];
const selectedField = layer?.allColumns?.find((column) => column.columnId === props.columnId);
@ -397,23 +382,18 @@ export function getTextBasedDatasource({
customLabel = selectedField?.fieldName;
}
render(
<KibanaThemeProvider theme$={core.theme.theme$}>
<I18nProvider>
<DimensionTrigger
id={props.columnId}
color={customLabel && selectedField ? 'primary' : 'danger'}
dataTestSubj="lns-dimensionTrigger-textBased"
label={
customLabel ??
i18n.translate('xpack.lens.textBasedLanguages.missingField', {
defaultMessage: 'Missing field',
})
}
/>{' '}
</I18nProvider>
</KibanaThemeProvider>,
domElement
return (
<DimensionTrigger
id={props.columnId}
color={customLabel && selectedField ? 'primary' : 'danger'}
dataTestSubj="lns-dimensionTrigger-textBased"
label={
customLabel ??
i18n.translate('xpack.lens.textBasedLanguages.missingField', {
defaultMessage: 'Missing field',
})
}
/>
);
},
@ -421,10 +401,7 @@ export function getTextBasedDatasource({
return [];
},
renderDimensionEditor: (
domElement: Element,
props: DatasourceDimensionEditorProps<TextBasedPrivateState>
) => {
DimensionEditorComponent: (props: DatasourceDimensionEditorProps<TextBasedPrivateState>) => {
const fields = props.state.fieldList;
const selectedField = props.state.layers[props.layerId]?.allColumns?.find(
(column) => column.columnId === props.columnId
@ -442,94 +419,81 @@ export function getTextBasedDatasource({
: true,
};
});
render(
<KibanaThemeProvider theme$={core.theme.theme$}>
<I18nProvider>
<EuiFormRow
data-test-subj="text-based-languages-field-selection-row"
label={i18n.translate('xpack.lens.textBasedLanguages.chooseField', {
defaultMessage: 'Field',
})}
fullWidth
className="lnsIndexPatternDimensionEditor--padded"
return (
<>
<EuiFormRow
data-test-subj="text-based-languages-field-selection-row"
label={i18n.translate('xpack.lens.textBasedLanguages.chooseField', {
defaultMessage: 'Field',
})}
fullWidth
className="lnsIndexPatternDimensionEditor--padded"
>
<FieldSelect
existingFields={updatedFields}
selectedField={selectedField}
onChoose={(choice) => {
const meta = fields.find((f) => f.name === choice.field)?.meta;
const newColumn = {
columnId: props.columnId,
fieldName: choice.field,
meta,
};
return props.setState(
!selectedField
? {
...props.state,
layers: {
...props.state.layers,
[props.layerId]: {
...props.state.layers[props.layerId],
columns: [...props.state.layers[props.layerId].columns, newColumn],
allColumns: [
...props.state.layers[props.layerId].allColumns,
newColumn,
],
},
},
}
: {
...props.state,
layers: {
...props.state.layers,
[props.layerId]: {
...props.state.layers[props.layerId],
columns: props.state.layers[props.layerId].columns.map((col) =>
col.columnId !== props.columnId
? col
: { ...col, fieldName: choice.field, meta }
),
allColumns: props.state.layers[props.layerId].allColumns.map((col) =>
col.columnId !== props.columnId
? col
: { ...col, fieldName: choice.field, meta }
),
},
},
}
);
}}
/>
</EuiFormRow>
{props.dataSectionExtra && (
<div
style={{
paddingLeft: euiThemeVars.euiSize,
paddingRight: euiThemeVars.euiSize,
}}
>
<FieldSelect
existingFields={updatedFields}
selectedField={selectedField}
onChoose={(choice) => {
const meta = fields.find((f) => f.name === choice.field)?.meta;
const newColumn = {
columnId: props.columnId,
fieldName: choice.field,
meta,
};
return props.setState(
!selectedField
? {
...props.state,
layers: {
...props.state.layers,
[props.layerId]: {
...props.state.layers[props.layerId],
columns: [...props.state.layers[props.layerId].columns, newColumn],
allColumns: [
...props.state.layers[props.layerId].allColumns,
newColumn,
],
},
},
}
: {
...props.state,
layers: {
...props.state.layers,
[props.layerId]: {
...props.state.layers[props.layerId],
columns: props.state.layers[props.layerId].columns.map((col) =>
col.columnId !== props.columnId
? col
: { ...col, fieldName: choice.field, meta }
),
allColumns: props.state.layers[props.layerId].allColumns.map((col) =>
col.columnId !== props.columnId
? col
: { ...col, fieldName: choice.field, meta }
),
},
},
}
);
}}
/>
</EuiFormRow>
{props.dataSectionExtra && (
<div
style={{
paddingLeft: euiThemeVars.euiSize,
paddingRight: euiThemeVars.euiSize,
}}
>
{props.dataSectionExtra}
</div>
)}
</I18nProvider>
</KibanaThemeProvider>,
domElement
{props.dataSectionExtra}
</div>
)}
</>
);
},
renderLayerPanel: (
domElement: Element,
props: DatasourceLayerPanelProps<TextBasedPrivateState>
) => {
render(
<KibanaThemeProvider theme$={core.theme.theme$}>
<I18nProvider>
<LayerPanel {...props} />
</I18nProvider>
</KibanaThemeProvider>,
domElement
);
LayerPanelComponent: (props: DatasourceLayerPanelProps<TextBasedPrivateState>) => {
return <LayerPanel {...props} />;
},
uniqueLabels(state: TextBasedPrivateState) {

View file

@ -390,7 +390,7 @@ describe('LayerPanel', () => {
},
],
});
mockVisualization.renderDimensionEditor = jest.fn();
mockVisualization.DimensionEditorComponent = jest.fn().mockImplementation(() => <div />);
const { instance } = await mountWithProvider(<LayerPanel {...getDefaultProps()} />);
act(() => {
@ -434,14 +434,13 @@ describe('LayerPanel', () => {
});
instance.update();
expect(mockDatasource.renderDimensionEditor).toHaveBeenCalledWith(
expect.any(Element),
expect(mockDatasource.DimensionEditorComponent).toHaveBeenCalledWith(
expect.objectContaining({ columnId: 'newid' })
);
const stateFn =
mockDatasource.renderDimensionEditor.mock.calls[
mockDatasource.renderDimensionEditor.mock.calls.length - 1
][1].setState;
mockDatasource.DimensionEditorComponent.mock.calls[
mockDatasource.DimensionEditorComponent.mock.calls.length - 1
][0].setState;
act(() => {
stateFn(
@ -512,14 +511,13 @@ describe('LayerPanel', () => {
});
instance.update();
expect(mockDatasource.renderDimensionEditor).toHaveBeenCalledWith(
expect.any(Element),
expect(mockDatasource.DimensionEditorComponent).toHaveBeenCalledWith(
expect.objectContaining({ columnId: 'y' })
);
const stateFn =
mockDatasource.renderDimensionEditor.mock.calls[
mockDatasource.renderDimensionEditor.mock.calls.length - 1
][1].setState;
mockDatasource.DimensionEditorComponent.mock.calls[
mockDatasource.DimensionEditorComponent.mock.calls.length - 1
][0].setState;
act(() => {
stateFn(
@ -586,9 +584,9 @@ describe('LayerPanel', () => {
expect(instance.find('EuiFlyoutHeader').exists()).toBe(true);
const lastArgs =
mockDatasource.renderDimensionEditor.mock.calls[
mockDatasource.renderDimensionEditor.mock.calls.length - 1
][1];
mockDatasource.DimensionEditorComponent.mock.calls[
mockDatasource.DimensionEditorComponent.mock.calls.length - 1
][0];
// Simulate what is called by the dimension editor
act(() => {
@ -597,7 +595,7 @@ describe('LayerPanel', () => {
});
});
expect(mockVisualization.renderDimensionEditor).toHaveBeenCalled();
expect(mockVisualization.DimensionEditorComponent).toHaveBeenCalled();
});
it('should close the DimensionContainer when the active visualization changes', async () => {
@ -1331,7 +1329,7 @@ describe('LayerPanel', () => {
],
});
await mountWithProvider(<LayerPanel {...getDefaultProps()} />);
expect(mockDatasource.renderDimensionTrigger).toHaveBeenCalled();
expect(mockDatasource.DimensionTriggerComponent).toHaveBeenCalled();
});
it('should render visualization dimension trigger if there is no layer datasource', async () => {
@ -1354,14 +1352,14 @@ describe('LayerPanel', () => {
framePublicAPI: { ...props.framePublicAPI, datasourceLayers: {} },
};
mockVisualization.renderDimensionTrigger = jest.fn();
mockVisualization.DimensionTriggerComponent = jest.fn().mockImplementation(() => <div />);
mockVisualization.getUniqueLabels = jest.fn(() => ({
x: 'A',
}));
await mountWithProvider(<LayerPanel {...propsWithVisOnlyLayer} />);
expect(mockDatasource.renderDimensionTrigger).not.toHaveBeenCalled();
expect(mockVisualization.renderDimensionTrigger).toHaveBeenCalled();
expect(mockDatasource.DimensionTriggerComponent).not.toHaveBeenCalled();
expect(mockVisualization.DimensionTriggerComponent).toHaveBeenCalled();
});
// TODO - test user message display

View file

@ -24,7 +24,6 @@ import { DragDropIdentifier, ReorderProvider, DropType } from '@kbn/dom-drag-dro
import { DimensionButton, DimensionTrigger } from '@kbn/visualization-ui-components/public';
import { LayerActions } from './layer_actions';
import { IndexPatternServiceAPI } from '../../../data_views_service/service';
import { NativeRenderer } from '../../../native_renderer';
import {
StateSetter,
Visualization,
@ -374,8 +373,8 @@ export function LayerPanel(
isTextBasedLanguage,
hasLayerSettings: Boolean(
(Object.values(visualizationLayerSettings).some(Boolean) &&
activeVisualization.renderLayerSettings) ||
layerDatasource?.renderLayerSettings
activeVisualization.LayerSettingsComponent) ||
layerDatasource?.LayerSettingsComponent
),
openLayerSettings: () => setPanelSettingsOpen(true),
onCloneLayer,
@ -398,7 +397,7 @@ export function LayerPanel(
isOnlyLayer,
isTextBasedLanguage,
visualizationLayerSettings,
layerDatasource?.renderLayerSettings,
layerDatasource?.LayerSettingsComponent,
onCloneLayer,
onRemoveLayer,
]
@ -438,11 +437,12 @@ export function LayerPanel(
)}
</EuiFlexGroup>
{props.indexPatternService &&
(layerDatasource || activeVisualization.renderLayerPanel) && <EuiSpacer size="s" />}
(layerDatasource || activeVisualization.LayerPanelComponent) && (
<EuiSpacer size="s" />
)}
{layerDatasource && props.indexPatternService && (
<NativeRenderer
render={layerDatasource.renderLayerPanel}
nativeProps={{
<layerDatasource.LayerPanelComponent
{...{
layerId,
state: layerDatasourceState,
activeData: props.framePublicAPI.activeData,
@ -452,10 +452,9 @@ export function LayerPanel(
}}
/>
)}
{activeVisualization.renderLayerPanel && (
<NativeRenderer
render={activeVisualization.renderLayerPanel}
nativeProps={{
{activeVisualization.LayerPanelComponent && (
<activeVisualization.LayerPanelComponent
{...{
layerId,
state: visualizationState,
frame: framePublicAPI,
@ -612,19 +611,18 @@ export function LayerPanel(
}}
>
{layerDatasource ? (
<NativeRenderer
render={layerDatasource.renderDimensionTrigger}
nativeProps={{
<>
{layerDatasource.DimensionTriggerComponent({
...layerDatasourceConfigProps,
columnId: accessorConfig.columnId,
groupId: group.groupId,
filterOperations: group.filterOperations,
indexPatterns: dataViews.indexPatterns,
}}
/>
})}
</>
) : (
<>
{activeVisualization?.renderDimensionTrigger?.({
{activeVisualization?.DimensionTriggerComponent?.({
columnId,
label: columnLabelMap?.[columnId] ?? '',
hideTooltip,
@ -705,7 +703,7 @@ export function LayerPanel(
})}
</EuiPanel>
</section>
{(layerDatasource?.renderLayerSettings || activeVisualization?.renderLayerSettings) && (
{(layerDatasource?.LayerSettingsComponent || activeVisualization?.LayerSettingsComponent) && (
<FlyoutContainer
panelRef={(el) => (settingsPanelRef.current = el)}
isOpen={isPanelSettingsOpen}
@ -721,7 +719,7 @@ export function LayerPanel(
>
<div id={layerId}>
<div className="lnsIndexPatternDimensionEditor--padded">
{layerDatasource?.renderLayerSettings || visualizationLayerSettings.data ? (
{layerDatasource?.LayerSettingsComponent || visualizationLayerSettings.data ? (
<EuiText
size="s"
css={css`
@ -735,21 +733,17 @@ export function LayerPanel(
</h4>
</EuiText>
) : null}
{layerDatasource?.renderLayerSettings && (
{layerDatasource?.LayerSettingsComponent && (
<>
<NativeRenderer
render={layerDatasource.renderLayerSettings}
nativeProps={layerDatasourceConfigProps}
/>
<layerDatasource.LayerSettingsComponent {...layerDatasourceConfigProps} />
</>
)}
{layerDatasource?.renderLayerSettings && visualizationLayerSettings.data ? (
{layerDatasource?.LayerSettingsComponent && visualizationLayerSettings.data ? (
<EuiSpacer size="m" />
) : null}
{activeVisualization?.renderLayerSettings && visualizationLayerSettings.data ? (
<NativeRenderer
render={activeVisualization?.renderLayerSettings}
nativeProps={{
{activeVisualization?.LayerSettingsComponent && visualizationLayerSettings.data ? (
<activeVisualization.LayerSettingsComponent
{...{
...layerVisualizationConfigProps,
setState: props.updateVisualization,
panelRef: settingsPanelRef,
@ -771,10 +765,9 @@ export function LayerPanel(
</h4>
</EuiText>
) : null}
{activeVisualization?.renderLayerSettings && (
<NativeRenderer
render={activeVisualization?.renderLayerSettings}
nativeProps={{
{activeVisualization?.LayerSettingsComponent && (
<activeVisualization.LayerSettingsComponent
{...{
...layerVisualizationConfigProps,
setState: props.updateVisualization,
panelRef: settingsPanelRef,
@ -819,58 +812,54 @@ export function LayerPanel(
}}
panel={
<>
{activeGroup && activeId && layerDatasource && (
<NativeRenderer
render={layerDatasource.renderDimensionEditor}
nativeProps={{
...layerDatasourceConfigProps,
core: props.core,
columnId: activeId,
groupId: activeGroup.groupId,
hideGrouping: activeGroup.hideGrouping,
filterOperations: activeGroup.filterOperations,
isMetricDimension: activeGroup?.isMetricDimension,
dimensionGroups,
toggleFullscreen,
isFullscreen,
setState: updateDataLayerState,
supportStaticValue: Boolean(activeGroup.supportStaticValue),
paramEditorCustomProps: activeGroup.paramEditorCustomProps,
enableFormatSelector: activeGroup.enableFormatSelector !== false,
layerType: activeVisualization.getLayerType(layerId, visualizationState),
indexPatterns: dataViews.indexPatterns,
activeData: layerVisualizationConfigProps.activeData,
dataSectionExtra: !isFullscreen &&
!activeDimension.isNew &&
activeVisualization.renderDimensionEditorDataExtra && (
<NativeRenderer
render={activeVisualization.renderDimensionEditorDataExtra}
nativeProps={{
...layerVisualizationConfigProps,
groupId: activeGroup.groupId,
accessor: activeId,
datasource,
setState: props.updateVisualization,
addLayer: props.addLayer,
removeLayer: props.onRemoveLayer,
panelRef,
}}
/>
),
}}
/>
)}
{activeGroup &&
activeId &&
layerDatasource &&
layerDatasource.DimensionEditorComponent({
...layerDatasourceConfigProps,
core: props.core,
columnId: activeId,
groupId: activeGroup.groupId,
hideGrouping: activeGroup.hideGrouping,
filterOperations: activeGroup.filterOperations,
isMetricDimension: activeGroup?.isMetricDimension,
dimensionGroups,
toggleFullscreen,
isFullscreen,
setState: updateDataLayerState,
supportStaticValue: Boolean(activeGroup.supportStaticValue),
paramEditorCustomProps: activeGroup.paramEditorCustomProps,
enableFormatSelector: activeGroup.enableFormatSelector !== false,
layerType: activeVisualization.getLayerType(layerId, visualizationState),
indexPatterns: dataViews.indexPatterns,
activeData: layerVisualizationConfigProps.activeData,
dataSectionExtra: !isFullscreen &&
!activeDimension.isNew &&
activeVisualization.DimensionEditorDataExtraComponent && (
<activeVisualization.DimensionEditorDataExtraComponent
{...{
...layerVisualizationConfigProps,
groupId: activeGroup.groupId,
accessor: activeId,
datasource,
setState: props.updateVisualization,
addLayer: props.addLayer,
removeLayer: props.onRemoveLayer,
panelRef,
}}
/>
),
})}
{activeGroup &&
activeId &&
!isFullscreen &&
!activeDimension.isNew &&
activeVisualization.renderDimensionEditor &&
activeVisualization.DimensionEditorComponent &&
activeGroup?.enableDimensionEditor && (
<>
<div className="lnsLayerPanel__styleEditor">
<NativeRenderer
render={activeVisualization.renderDimensionEditor}
nativeProps={{
<activeVisualization.DimensionEditorComponent
{...{
...layerVisualizationConfigProps,
groupId: activeGroup.groupId,
accessor: activeId,
@ -882,10 +871,9 @@ export function LayerPanel(
}}
/>
</div>
{activeVisualization.renderDimensionEditorAdditionalSection && (
<NativeRenderer
render={activeVisualization.renderDimensionEditorAdditionalSection}
nativeProps={{
{activeVisualization.DimensionEditorAdditionalSectionComponent && (
<activeVisualization.DimensionEditorAdditionalSectionComponent
{...{
...layerVisualizationConfigProps,
groupId: activeGroup.groupId,
accessor: activeId,

View file

@ -65,8 +65,8 @@ describe('LayerSettings', () => {
});
it('should call the custom renderer if available', async () => {
mockVisualization.renderLayerHeader = jest.fn();
mockVisualization.LayerHeaderComponent = jest.fn().mockImplementation(() => <div />);
await mountWithProvider(<LayerSettings {...getDefaultProps()} />);
expect(mockVisualization.renderLayerHeader).toHaveBeenCalled();
expect(mockVisualization.LayerHeaderComponent).toHaveBeenCalled();
});
});

View file

@ -6,7 +6,6 @@
*/
import React from 'react';
import { NativeRenderer } from '../../../native_renderer';
import { Visualization, VisualizationLayerWidgetProps } from '../../../types';
import { StaticHeader } from '../../../shared_components';
@ -17,7 +16,7 @@ export function LayerSettings({
activeVisualization: Visualization;
layerConfigProps: VisualizationLayerWidgetProps;
}) {
if (!activeVisualization.renderLayerHeader) {
if (!activeVisualization.LayerHeaderComponent) {
const description = activeVisualization.getDescription(layerConfigProps.state);
if (!description) {
return null;
@ -25,7 +24,5 @@ export function LayerSettings({
return <StaticHeader label={description.label} icon={description.icon} />;
}
return (
<NativeRenderer render={activeVisualization.renderLayerHeader} nativeProps={layerConfigProps} />
);
return <activeVisualization.LayerHeaderComponent {...layerConfigProps} />;
}

View file

@ -6,7 +6,6 @@
*/
import React from 'react';
import { DragDropIdentifier } from '@kbn/dom-drag-drop';
import { DataPanelWrapper } from './data_panel_wrapper';
import { Datasource, DatasourceDataPanelProps, VisualizationMap } from '../../types';
import { UiActionsStart } from '@kbn/ui-actions-plugin/public';
@ -22,11 +21,11 @@ describe('Data Panel Wrapper', () => {
let datasourceDataPanelProps: DatasourceDataPanelProps;
let lensStore: Awaited<ReturnType<typeof mountWithProvider>>['lensStore'];
beforeEach(async () => {
const renderDataPanel = jest.fn();
const DataPanelComponent = jest.fn().mockImplementation(() => <div />);
const datasourceMap = {
activeDatasource: {
renderDataPanel,
DataPanelComponent,
getUsedDataViews: jest.fn(),
getLayers: jest.fn(() => []),
} as unknown as Datasource,
@ -38,8 +37,8 @@ describe('Data Panel Wrapper', () => {
visualizationMap={{} as VisualizationMap}
showNoDataPopover={() => {}}
core={{} as DatasourceDataPanelProps['core']}
dropOntoWorkspace={(field: DragDropIdentifier) => {}}
hasSuggestionForField={(field: DragDropIdentifier) => true}
dropOntoWorkspace={() => {}}
hasSuggestionForField={() => true}
plugins={{
uiActions: {} as UiActionsStart,
dataViews: {} as DataViewsPublicPluginStart,
@ -66,7 +65,7 @@ describe('Data Panel Wrapper', () => {
lensStore = mountResult.lensStore;
datasourceDataPanelProps = renderDataPanel.mock.calls[0][1] as DatasourceDataPanelProps;
datasourceDataPanelProps = DataPanelComponent.mock.calls[0][0] as DatasourceDataPanelProps;
});
describe('setState', () => {

View file

@ -12,11 +12,10 @@ import { Storage } from '@kbn/kibana-utils-plugin/public';
import { UiActionsStart } from '@kbn/ui-actions-plugin/public';
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import { EventAnnotationServiceType } from '@kbn/event-annotation-plugin/public';
import { useDragDropContext, DragDropIdentifier } from '@kbn/dom-drag-drop';
import { DragDropIdentifier } from '@kbn/dom-drag-drop';
import memoizeOne from 'memoize-one';
import { isEqual } from 'lodash';
import { Easteregg } from './easteregg';
import { NativeRenderer } from '../../native_renderer';
import {
StateSetter,
DatasourceDataPanelProps,
@ -162,7 +161,6 @@ export const DataPanelWrapper = memo((props: DataPanelWrapperProps) => {
const datasourceProps: DatasourceDataPanelProps = {
...externalContext,
dragDropContext: useDragDropContext(),
state: activeDatasourceId ? datasourceStates[activeDatasourceId].state : null,
setState: setDatasourceState,
core: props.core,
@ -187,16 +185,16 @@ export const DataPanelWrapper = memo((props: DataPanelWrapperProps) => {
[]),
]),
};
const DataPanelComponent =
activeDatasourceId && !datasourceIsLoading
? props.datasourceMap[activeDatasourceId].DataPanelComponent
: null;
return (
<>
<Easteregg query={externalContext?.query} />
{activeDatasourceId && !datasourceIsLoading && (
<NativeRenderer
className="lnsDataPanelWrapper"
render={props.datasourceMap[activeDatasourceId].renderDataPanel}
nativeProps={datasourceProps}
/>
{DataPanelComponent && (
<div className="lnsDataPanelWrapper">{DataPanelComponent(datasourceProps)}</div>
)}
</>
);

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React from 'react';
import React, { useEffect } from 'react';
import { ReactWrapper } from 'enzyme';
// Tests are executed in a jsdom environment who does not have sizing methods,
@ -41,7 +41,7 @@ import {
} from '../../mocks';
import { inspectorPluginMock } from '@kbn/inspector-plugin/public/mocks';
import { ReactExpressionRendererType } from '@kbn/expressions-plugin/public';
import { DragDrop } from '@kbn/dom-drag-drop';
import { DragDrop, useDragDropContext } from '@kbn/dom-drag-drop';
import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks';
import { chartPluginMock } from '@kbn/charts-plugin/public/mocks';
import { expressionsPluginMock } from '@kbn/expressions-plugin/public/mocks';
@ -177,7 +177,7 @@ describe('editor_frame', () => {
},
},
});
expect(mockDatasource.renderDataPanel).not.toHaveBeenCalled();
expect(mockDatasource.DataPanelComponent).not.toHaveBeenCalled();
lensStore.dispatch(
setState({
datasourceStates: {
@ -190,7 +190,7 @@ describe('editor_frame', () => {
},
})
);
expect(mockDatasource.renderDataPanel).toHaveBeenCalled();
expect(mockDatasource.DataPanelComponent).toHaveBeenCalled();
});
it('should initialize visualization state and render config panel', async () => {
@ -304,7 +304,7 @@ describe('editor_frame', () => {
},
});
const updatedState = {};
const setDatasourceState = (mockDatasource.renderDataPanel as jest.Mock).mock.calls[0][1]
const setDatasourceState = (mockDatasource.DataPanelComponent as jest.Mock).mock.calls[0][0]
.setState;
act(() => {
setDatasourceState(updatedState);
@ -334,10 +334,10 @@ describe('editor_frame', () => {
};
await mountWithProvider(<EditorFrame {...props} />);
const setDatasourceState = (mockDatasource.renderDataPanel as jest.Mock).mock.calls[0][1]
const setDatasourceState = (mockDatasource.DataPanelComponent as jest.Mock).mock.calls[0][0]
.setState;
mockDatasource.renderDataPanel.mockClear();
mockDatasource.DataPanelComponent.mockClear();
const updatedState = {
title: 'shazm',
@ -346,9 +346,8 @@ describe('editor_frame', () => {
setDatasourceState(updatedState);
});
expect(mockDatasource.renderDataPanel).toHaveBeenCalledTimes(1);
expect(mockDatasource.renderDataPanel).toHaveBeenLastCalledWith(
expect.any(Element),
expect(mockDatasource.DataPanelComponent).toHaveBeenCalledTimes(1);
expect(mockDatasource.DataPanelComponent).toHaveBeenLastCalledWith(
expect.objectContaining({
state: updatedState,
})
@ -385,7 +384,7 @@ describe('editor_frame', () => {
};
mockDatasource.getPublicAPI.mockReturnValue(updatedPublicAPI);
const setDatasourceState = (mockDatasource.renderDataPanel as jest.Mock).mock.calls[0][1]
const setDatasourceState = (mockDatasource.DataPanelComponent as jest.Mock).mock.calls[0][0]
.setState;
act(() => {
setDatasourceState({});
@ -722,8 +721,7 @@ describe('editor_frame', () => {
state: suggestionVisState,
})
);
expect(mockDatasource.renderDataPanel).toHaveBeenLastCalledWith(
expect.any(Element),
expect(mockDatasource.DataPanelComponent).toHaveBeenLastCalledWith(
expect.objectContaining({
state: newDatasourceState,
})
@ -820,19 +818,7 @@ describe('editor_frame', () => {
getDatasourceSuggestionsForField: () => [generateSuggestion()],
getDatasourceSuggestionsFromCurrentState: () => [generateSuggestion()],
getDatasourceSuggestionsForVisualizeField: () => [generateSuggestion()],
renderDataPanel: (_element, { dragDropContext: [{ dragging }, dndDispatch] }) => {
if (!dragging || dragging.id !== 'draggedField') {
dndDispatch({
type: 'startDragging',
payload: {
dragging: {
id: 'draggedField',
humanData: { label: 'draggedField' },
},
},
});
}
},
DataPanelComponent: jest.fn().mockImplementation(() => <div />),
},
},
@ -863,7 +849,7 @@ describe('editor_frame', () => {
id: '1',
humanData: { label: 'draggedField' },
},
'field_replace'
'field_add'
);
});
@ -927,8 +913,9 @@ describe('editor_frame', () => {
getDatasourceSuggestionsForField: () => [generateSuggestion()],
getDatasourceSuggestionsFromCurrentState: () => [generateSuggestion()],
getDatasourceSuggestionsForVisualizeField: () => [generateSuggestion()],
renderDataPanel: (_element, { dragDropContext: [{ dragging }, dndDispatch] }) => {
if (!dragging || dragging.id !== 'draggedField') {
DataPanelComponent: jest.fn().mockImplementation(() => {
const [, dndDispatch] = useDragDropContext();
useEffect(() => {
dndDispatch({
type: 'startDragging',
payload: {
@ -938,15 +925,16 @@ describe('editor_frame', () => {
},
},
});
}
},
}, [dndDispatch]);
return <div />;
}),
},
},
ExpressionRenderer: expressionRendererMock,
} as EditorFrameProps;
instance = (await mountWithProvider(<EditorFrame {...props} />)).instance;
instance.update();
act(() => {
@ -959,7 +947,7 @@ describe('editor_frame', () => {
label: 'label',
},
},
'field_replace'
'field_add'
);
});

View file

@ -51,7 +51,7 @@ describe('workspace_panel_wrapper', () => {
});
it('should call the toolbar renderer if provided', async () => {
const renderToolbarMock = jest.fn();
const ToolbarComponentMock = jest.fn(() => null);
const visState = { internalState: 123 };
await mountWithProvider(
<WorkspacePanelWrapper
@ -59,7 +59,9 @@ describe('workspace_panel_wrapper', () => {
visualizationState={visState}
children={<span />}
visualizationId="myVis"
visualizationMap={{ myVis: { ...mockVisualization, renderToolbar: renderToolbarMock } }}
visualizationMap={{
myVis: { ...mockVisualization, ToolbarComponent: ToolbarComponentMock },
}}
datasourceMap={{}}
datasourceStates={{}}
isFullscreen={false}
@ -74,7 +76,7 @@ describe('workspace_panel_wrapper', () => {
}
);
expect(renderToolbarMock).toHaveBeenCalledWith(expect.any(Element), {
expect(ToolbarComponentMock).toHaveBeenCalledWith({
state: visState,
frame: mockFrameAPI,
setState: expect.anything(),

View file

@ -19,7 +19,6 @@ import {
Visualization,
} from '../../../types';
import { DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS } from '../../../utils';
import { NativeRenderer } from '../../../native_renderer';
import { ChartSwitch } from './chart_switch';
import { MessageList } from './message_list';
import {
@ -59,37 +58,37 @@ export function VisualizationToolbar(props: {
const { activeDatasourceId, visualization, datasourceStates } = useLensSelector(
(state) => state.lens
);
const { activeVisualization, onUpdateStateCb } = props;
const setVisualizationState = useCallback(
(newState: unknown) => {
if (!props.activeVisualization) {
if (!activeVisualization) {
return;
}
dispatchLens(
updateVisualizationState({
visualizationId: props.activeVisualization.id,
visualizationId: activeVisualization.id,
newState,
})
);
if (activeDatasourceId && props.onUpdateStateCb) {
if (activeDatasourceId && onUpdateStateCb) {
const dsState = datasourceStates[activeDatasourceId].state;
props.onUpdateStateCb?.(dsState, newState);
onUpdateStateCb?.(dsState, newState);
}
},
[activeDatasourceId, datasourceStates, dispatchLens, props]
[activeDatasourceId, datasourceStates, dispatchLens, activeVisualization, onUpdateStateCb]
);
const ToolbarComponent = props.activeVisualization?.ToolbarComponent;
return (
<>
{props.activeVisualization && props.activeVisualization.renderToolbar && (
{ToolbarComponent && (
<EuiFlexItem grow={false}>
<NativeRenderer
render={props.activeVisualization.renderToolbar}
nativeProps={{
frame: props.framePublicAPI,
state: visualization.state,
setState: setVisualizationState,
}}
/>
{ToolbarComponent({
frame: props.framePublicAPI,
state: visualization.state,
setState: setVisualizationState,
})}
</EuiFlexItem>
)}
</>

View file

@ -4,7 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { DatasourcePublicAPI, Datasource } from '../types';
export type DatasourceMock = jest.Mocked<Datasource> & {
@ -44,8 +44,6 @@ export function createMockDatasource(
getRenderEventCounters: jest.fn((_state) => []),
getPublicAPI: jest.fn().mockReturnValue(publicAPIMock),
initialize: jest.fn((_state?) => {}),
renderDataPanel: jest.fn(),
renderLayerPanel: jest.fn(),
toExpression: jest.fn((_frame, _state, _indexPatterns, dateRange, nowInstant) => null),
insertLayer: jest.fn((_state, _newLayerId) => ({})),
removeLayer: jest.fn((state, layerId) => ({ newState: state, removedLayerIds: [layerId] })),
@ -53,8 +51,6 @@ export function createMockDatasource(
removeColumn: jest.fn((props) => {}),
getLayers: jest.fn((_state) => []),
uniqueLabels: jest.fn((_state, dataViews) => ({})),
renderDimensionTrigger: jest.fn(),
renderDimensionEditor: jest.fn(),
getDropProps: jest.fn(),
onDrop: jest.fn(),
createEmptyLayer: jest.fn(),
@ -71,6 +67,11 @@ export function createMockDatasource(
getUsedDataViews: jest.fn(),
onRefreshIndexPattern: jest.fn(),
getDatasourceInfo: jest.fn(),
DataPanelComponent: jest.fn().mockImplementation(() => <div />),
LayerPanelComponent: jest.fn().mockImplementation(() => <div />),
DimensionTriggerComponent: jest.fn().mockImplementation(() => <div />),
DimensionEditorComponent: jest.fn().mockImplementation(() => <div />),
};
}

View file

@ -100,6 +100,7 @@ export const mountWithProvider = async (
wrappingComponent?: React.FC<{
children: React.ReactNode;
}>;
wrappingComponentProps?: Record<string, unknown>;
attachTo?: HTMLElement;
}
) => {
@ -120,6 +121,7 @@ export const getMountWithProviderParams = (
wrappingComponent?: React.FC<{
children: React.ReactNode;
}>;
wrappingComponentProps?: Record<string, unknown>;
attachTo?: HTMLElement;
}
) => {
@ -133,12 +135,13 @@ export const getMountWithProviderParams = (
attachTo?: HTMLElement | undefined;
} = {};
if (options) {
const { wrappingComponent: _wrappingComponent, ...rest } = options;
const { wrappingComponent: _wrappingComponent, wrappingComponentProps, ...rest } = options;
restOptions = rest;
if (_wrappingComponent) {
wrappingComponent = ({ children }) => {
return _wrappingComponent({
...wrappingComponentProps,
children: <Provider store={lensStore}>{children}</Provider>,
});
};

View file

@ -4,7 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { LayerTypes } from '@kbn/expression-xy-plugin/public';
import { Visualization, VisualizationMap } from '../types';
@ -49,7 +49,7 @@ export function createMockVisualization(id = 'testVis'): jest.Mocked<Visualizati
setDimension: jest.fn(),
removeDimension: jest.fn(),
renderDimensionEditor: jest.fn(),
DimensionEditorComponent: jest.fn(() => <div />),
};
}

View file

@ -1,8 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './native_renderer';

View file

@ -1,252 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useEffect } from 'react';
import { render } from 'react-dom';
import { NativeRenderer } from './native_renderer';
import { act } from 'react-dom/test-utils';
function renderAndTriggerHooks(element: JSX.Element, mountpoint: Element) {
// act takes care of triggering state hooks
act(() => {
render(element, mountpoint);
});
}
describe('native_renderer', () => {
let mountpoint: Element;
beforeEach(() => {
mountpoint = document.createElement('div');
});
afterEach(() => {
mountpoint.remove();
});
it('should render element in container', () => {
const renderSpy = jest.fn();
const testProps = { a: 'abc' };
renderAndTriggerHooks(
<NativeRenderer render={renderSpy} nativeProps={testProps} />,
mountpoint
);
const containerElement = mountpoint.firstElementChild;
expect(renderSpy).toHaveBeenCalledWith(containerElement, testProps);
});
it('should render again if props change', () => {
const renderSpy = jest.fn();
const testProps = { a: 'abc' };
renderAndTriggerHooks(
<NativeRenderer render={renderSpy} nativeProps={testProps} />,
mountpoint
);
renderAndTriggerHooks(
<NativeRenderer render={renderSpy} nativeProps={{ a: 'def' }} />,
mountpoint
);
renderAndTriggerHooks(
<NativeRenderer render={renderSpy} nativeProps={{ a: 'def' }} />,
mountpoint
);
expect(renderSpy).toHaveBeenCalledTimes(3);
const containerElement = mountpoint.firstElementChild;
expect(renderSpy).lastCalledWith(containerElement, { a: 'def' });
});
it('should render again if props is just a string', () => {
const renderSpy = jest.fn();
const testProps = 'abc';
renderAndTriggerHooks(
<NativeRenderer render={renderSpy} nativeProps={testProps} />,
mountpoint
);
renderAndTriggerHooks(<NativeRenderer render={renderSpy} nativeProps="def" />, mountpoint);
renderAndTriggerHooks(<NativeRenderer render={renderSpy} nativeProps="def" />, mountpoint);
expect(renderSpy).toHaveBeenCalledTimes(3);
const containerElement = mountpoint.firstElementChild;
expect(renderSpy).lastCalledWith(containerElement, 'def');
});
it('should render again if props are extended', () => {
const renderSpy = jest.fn();
const testProps = { a: 'abc' };
renderAndTriggerHooks(
<NativeRenderer render={renderSpy} nativeProps={testProps} />,
mountpoint
);
renderAndTriggerHooks(
<NativeRenderer render={renderSpy} nativeProps={{ a: 'abc', b: 'def' }} />,
mountpoint
);
expect(renderSpy).toHaveBeenCalledTimes(2);
const containerElement = mountpoint.firstElementChild;
expect(renderSpy).lastCalledWith(containerElement, { a: 'abc', b: 'def' });
});
it('should render again if props are limited', () => {
const renderSpy = jest.fn();
const testProps = { a: 'abc', b: 'def' };
renderAndTriggerHooks(
<NativeRenderer render={renderSpy} nativeProps={testProps} />,
mountpoint
);
renderAndTriggerHooks(
<NativeRenderer render={renderSpy} nativeProps={{ a: 'abc' }} />,
mountpoint
);
expect(renderSpy).toHaveBeenCalledTimes(2);
const containerElement = mountpoint.firstElementChild;
expect(renderSpy).lastCalledWith(containerElement, { a: 'abc' });
});
it('should render a div as container', () => {
const renderSpy = jest.fn();
const testProps = { a: 'abc' };
renderAndTriggerHooks(
<NativeRenderer render={renderSpy} nativeProps={testProps} />,
mountpoint
);
const containerElement: Element = mountpoint.firstElementChild!;
expect(containerElement.nodeName).toBe('DIV');
});
it('should pass regular html attributes to the wrapping element', () => {
const renderSpy = jest.fn();
const testProps = { a: 'abc' };
renderAndTriggerHooks(
<NativeRenderer
render={renderSpy}
nativeProps={testProps}
className="testClass"
data-test-subj="container"
/>,
mountpoint
);
const containerElement: HTMLElement = mountpoint.firstElementChild! as HTMLElement;
expect(containerElement.className).toBe('testClass');
expect(containerElement.dataset.testSubj).toBe('container');
});
it('should render a specified element as container', () => {
const renderSpy = jest.fn();
const testProps = { a: 'abc' };
renderAndTriggerHooks(
<NativeRenderer render={renderSpy} tag="span" nativeProps={testProps} />,
mountpoint
);
const containerElement: Element = mountpoint.firstElementChild!;
expect(containerElement.nodeName).toBe('SPAN');
});
it('should properly unmount a react element that is mounted inside the renderer', () => {
let isUnmounted = false;
function TestComponent() {
useEffect(() => {
return () => {
isUnmounted = true;
};
}, []);
return <>Hello</>;
}
renderAndTriggerHooks(
<NativeRenderer
render={(element) => {
// This render function mimics the most common usage inside Lens
render(<TestComponent />, element);
}}
nativeProps={{}}
/>,
mountpoint
);
// Replaces the component at the mountpoint with nothing
renderAndTriggerHooks(<>Empty</>, mountpoint);
expect(isUnmounted).toBe(true);
});
it('should call the unmount function provided for non-react elements', () => {
const unmountCallback = jest.fn();
renderAndTriggerHooks(
<NativeRenderer
render={(element, props) => {
return unmountCallback;
}}
nativeProps={{}}
/>,
mountpoint
);
// Replaces the component at the mountpoint with nothing
renderAndTriggerHooks(<>Empty</>, mountpoint);
expect(unmountCallback).toHaveBeenCalled();
});
it('should handle when the mount function is asynchronous without a cleanup fn', () => {
let isUnmounted = false;
function TestComponent() {
useEffect(() => {
return () => {
isUnmounted = true;
};
}, []);
return <>Hello</>;
}
renderAndTriggerHooks(
<NativeRenderer
render={async (element, props) => {
render(<TestComponent />, element);
}}
nativeProps={{}}
/>,
mountpoint
);
// Replaces the component at the mountpoint with nothing
renderAndTriggerHooks(<>Empty</>, mountpoint);
expect(isUnmounted).toBe(true);
});
it('should handle when the mount function is asynchronous with a cleanup fn', async () => {
const unmountCallback = jest.fn();
renderAndTriggerHooks(
<NativeRenderer
render={async (element, props) => {
return unmountCallback;
}}
nativeProps={{}}
/>,
mountpoint
);
// Schedule a promise cycle to update the DOM
await new Promise((resolve) => setTimeout(resolve, 0));
// Replaces the component at the mountpoint with nothing
renderAndTriggerHooks(<>Empty</>, mountpoint);
expect(unmountCallback).toHaveBeenCalled();
});
});

View file

@ -1,66 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { HTMLAttributes, useEffect, useRef } from 'react';
import { unmountComponentAtNode } from 'react-dom';
type CleanupCallback = (el: Element) => void;
export interface NativeRendererProps<T> extends HTMLAttributes<HTMLDivElement> {
render: (
domElement: Element,
props: T
) => Promise<CleanupCallback | void> | CleanupCallback | void;
nativeProps: T;
tag?: string;
}
/**
* A component which takes care of providing a mountpoint for a generic render
* function which takes an html element and an optional props object.
* By default the mountpoint element will be a div, this can be changed with the
* `tag` prop.
*
* If the rendered component tree was using React, we need to clean it up manually,
* otherwise the unmount event never happens. A future addition is for non-React components
* to get cleaned up, which could be added in the future.
*
* @param props
*/
export function NativeRenderer<T>({ render, nativeProps, tag, ...rest }: NativeRendererProps<T>) {
const elementRef = useRef<Element>();
const cleanupRef = useRef<((cleanupElement: Element) => void) | void>();
useEffect(() => {
return () => {
if (elementRef.current) {
if (cleanupRef.current && typeof cleanupRef.current === 'function') {
cleanupRef.current(elementRef.current);
}
unmountComponentAtNode(elementRef.current);
}
};
}, []);
return React.createElement(tag || 'div', {
...rest,
ref: (el) => {
if (el) {
elementRef.current = el;
// Handles the editor frame renderer, which is async
const result = render(el, nativeProps);
if (result instanceof Promise) {
result.then((cleanup) => {
if (typeof cleanup === 'function') {
cleanupRef.current = cleanup;
}
});
} else if (typeof result === 'function') {
cleanupRef.current = result;
}
}
},
});
}

View file

@ -9,7 +9,7 @@ import type { IconType } from '@elastic/eui/src/components/icon/icon';
import type { CoreStart, SavedObjectReference, ResolvedSimpleSavedObject } from '@kbn/core/public';
import type { PaletteOutput } from '@kbn/coloring';
import type { TopNavMenuData } from '@kbn/navigation-plugin/public';
import type { MutableRefObject } from 'react';
import type { MutableRefObject, ReactElement } from 'react';
import type { Filter, TimeRange } from '@kbn/es-query';
import type {
ExpressionAstExpression,
@ -40,12 +40,7 @@ import { estypes } from '@elastic/elasticsearch';
import React from 'react';
import { CellValueContext } from '@kbn/embeddable-plugin/public';
import { EventAnnotationGroupConfig } from '@kbn/event-annotation-plugin/common';
import type {
DraggingIdentifier,
DragDropIdentifier,
DragContextValue,
DropType,
} from '@kbn/dom-drag-drop';
import type { DraggingIdentifier, DragDropIdentifier, DropType } from '@kbn/dom-drag-drop';
import type { AccessorConfig } from '@kbn/visualization-ui-components/public';
import type { DateRange, LayerType, SortingHint } from '../common/types';
import type {
@ -372,26 +367,15 @@ export interface Datasource<T = unknown, P = unknown> {
}) => T;
getSelectedFields?: (state: T) => string[];
renderLayerSettings?: (
domElement: Element,
LayerSettingsComponent?: (
props: DatasourceLayerSettingsProps<T>
) => ((cleanupElement: Element) => void) | void;
renderDataPanel: (
domElement: Element,
props: DatasourceDataPanelProps<T>
) => ((cleanupElement: Element) => void) | void;
renderDimensionTrigger: (
domElement: Element,
props: DatasourceDimensionTriggerProps<T>
) => ((cleanupElement: Element) => void) | void;
renderDimensionEditor: (
domElement: Element,
) => React.ReactElement<DatasourceLayerSettingsProps<T>> | null;
DataPanelComponent: (props: DatasourceDataPanelProps<T>) => JSX.Element | null;
DimensionTriggerComponent: (props: DatasourceDimensionTriggerProps<T>) => JSX.Element | null;
DimensionEditorComponent: (
props: DatasourceDimensionEditorProps<T>
) => ((cleanupElement: Element) => void) | void;
renderLayerPanel: (
domElement: Element,
props: DatasourceLayerPanelProps<T>
) => ((cleanupElement: Element) => void) | void;
) => ReactElement<DatasourceDimensionEditorProps<T>> | null;
LayerPanelComponent: (props: DatasourceLayerPanelProps<T>) => JSX.Element | null;
getDropProps: (
props: GetDropPropsArgs<T>
) => { dropTypes: DropType[]; nextLabel?: string } | undefined;
@ -588,7 +572,6 @@ export interface DatasourceLayerSettingsProps<T = unknown> {
export interface DatasourceDataPanelProps<T = unknown> {
state: T;
dragDropContext: DragContextValue;
setState: StateSetter<T, { applyImmediately?: boolean }>;
showNoDataPopover: () => void;
core: Pick<
@ -1032,6 +1015,13 @@ export type RegisterLibraryAnnotationGroupFunction = (groupInfo: {
id: string;
group: EventAnnotationGroupConfig;
}) => void;
interface AddLayerButtonProps {
supportedLayers: VisualizationLayerDescription[];
addLayer: AddLayerFunction;
ensureIndexPattern: (specOrId: DataViewSpec | string) => Promise<void>;
registerLibraryAnnotationGroup: RegisterLibraryAnnotationGroupFunction;
}
export interface Visualization<T = unknown, P = T, ExtraAppendLayerArg = unknown> {
/** Plugin ID, such as "lnsXY" */
id: string;
@ -1161,27 +1151,24 @@ export interface Visualization<T = unknown, P = T, ExtraAppendLayerArg = unknown
* Header rendered as layer title. This can be used for both static and dynamic content like
* for extra configurability, such as for switch chart type
*/
renderLayerHeader?: (
domElement: Element,
LayerHeaderComponent?: (
props: VisualizationLayerWidgetProps<T>
) => ((cleanupElement: Element) => void) | void;
) => null | ReactElement<VisualizationLayerWidgetProps<T>>;
/**
* Layer panel content rendered. This can be used to render a custom content below the title,
* like a custom dataview switch
*/
renderLayerPanel?: (
domElement: Element,
LayerPanelComponent?: (
props: VisualizationLayerWidgetProps<T>
) => ((cleanupElement: Element) => void) | void;
) => null | ReactElement<VisualizationLayerWidgetProps<T>>;
/**
* Toolbar rendered above the visualization. This is meant to be used to provide chart-level
* settings for the visualization.
*/
renderToolbar?: (
domElement: Element,
ToolbarComponent?: (
props: VisualizationToolbarProps<T>
) => ((cleanupElement: Element) => void) | void;
) => null | ReactElement<VisualizationToolbarProps<T>>;
/**
* The frame is telling the visualization to update or set a dimension based on user interaction
@ -1216,49 +1203,42 @@ export interface Visualization<T = unknown, P = T, ExtraAppendLayerArg = unknown
*/
hasLayerSettings?: (props: VisualizationConfigProps<T>) => Record<'data' | 'appearance', boolean>;
renderLayerSettings?: (
domElement: Element,
LayerSettingsComponent?: (
props: VisualizationLayerSettingsProps<T> & { section: 'data' | 'appearance' }
) => ((cleanupElement: Element) => void) | void;
) => null | ReactElement<VisualizationLayerSettingsProps<T>>;
/**
* Additional editor that gets rendered inside the dimension popover in the "appearance" section.
* This can be used to configure dimension-specific options
*/
renderDimensionEditor?: (
domElement: Element,
DimensionEditorComponent?: (
props: VisualizationDimensionEditorProps<T>
) => ((cleanupElement: Element) => void) | void;
) => null | ReactElement<VisualizationDimensionEditorProps<T>>;
/**
* Additional editor that gets rendered inside the dimension popover in an additional section below "appearance".
* This can be used to configure dimension-specific options
*/
renderDimensionEditorAdditionalSection?: (
domElement: Element,
DimensionEditorAdditionalSectionComponent?: (
props: VisualizationDimensionEditorProps<T>
) => ((cleanupElement: Element) => void) | void;
) => null | ReactElement<VisualizationDimensionEditorProps<T>>;
/**
* Additional editor that gets rendered inside the data section.
* This can be used to configure dimension-specific options
*/
renderDimensionEditorDataExtra?: (
domElement: Element,
DimensionEditorDataExtraComponent?: (
props: VisualizationDimensionEditorProps<T>
) => ((cleanupElement: Element) => void) | void;
) => null | ReactElement<VisualizationDimensionEditorProps<T>>;
/**
* Renders dimension trigger. Used only for noDatasource layers
*/
renderDimensionTrigger?: (props: {
DimensionTriggerComponent?: (props: {
columnId: string;
label: string;
hideTooltip?: boolean;
}) => JSX.Element | null;
getAddLayerButtonComponent?: (props: {
supportedLayers: VisualizationLayerDescription[];
addLayer: AddLayerFunction;
ensureIndexPattern: (specOrId: DataViewSpec | string) => Promise<void>;
registerLibraryAnnotationGroup: RegisterLibraryAnnotationGroupFunction;
}) => JSX.Element | null;
}) => null | ReactElement<{ columnId: string; label: string; hideTooltip?: boolean }>;
getAddLayerButtonComponent?: (
props: AddLayerButtonProps
) => null | ReactElement<AddLayerButtonProps>;
/**
* Creates map of columns ids and unique lables. Used only for noDatasource layers
*/

View file

@ -6,13 +6,10 @@
*/
import React from 'react';
import { render } from 'react-dom';
import { Ast } from '@kbn/interpreter';
import { I18nProvider } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { PaletteRegistry, CUSTOM_PALETTE } from '@kbn/coloring';
import { ThemeServiceStart } from '@kbn/core/public';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public';
import { IconChartDatatable } from '@kbn/chart-icons';
import { LayerTypes } from '@kbn/expression-xy-plugin/public';
@ -337,37 +334,16 @@ export const getDatatableVisualization = ({
sorting: prevState.sorting?.columnId === columnId ? undefined : prevState.sorting,
};
},
renderDimensionEditor(domElement, props) {
render(
<KibanaThemeProvider theme$={theme.theme$}>
<I18nProvider>
<TableDimensionEditor {...props} paletteService={paletteService} />
</I18nProvider>
</KibanaThemeProvider>,
domElement
);
DimensionEditorComponent(props) {
return <TableDimensionEditor {...props} paletteService={paletteService} />;
},
renderDimensionEditorAdditionalSection(domElement, props) {
render(
<KibanaThemeProvider theme$={theme.theme$}>
<I18nProvider>
<TableDimensionEditorAdditionalSection {...props} paletteService={paletteService} />
</I18nProvider>
</KibanaThemeProvider>,
domElement
);
DimensionEditorAdditionalSectionComponent(props) {
return <TableDimensionEditorAdditionalSection {...props} paletteService={paletteService} />;
},
renderDimensionEditorDataExtra(domElement, props) {
render(
<KibanaThemeProvider theme$={theme.theme$}>
<I18nProvider>
<TableDimensionDataExtraEditor {...props} paletteService={paletteService} />
</I18nProvider>
</KibanaThemeProvider>,
domElement
);
DimensionEditorDataExtraComponent(props) {
return <TableDimensionDataExtraEditor {...props} paletteService={paletteService} />;
},
getSupportedLayers() {
@ -522,15 +498,8 @@ export const getDatatableVisualization = ({
}, []);
},
renderToolbar(domElement, props) {
render(
<KibanaThemeProvider theme$={theme.theme$}>
<I18nProvider>
<DataTableToolbar {...props} />
</I18nProvider>
</KibanaThemeProvider>,
domElement
);
ToolbarComponent(props) {
return <DataTableToolbar {...props} />;
},
onEditAction(state, event) {

View file

@ -6,11 +6,9 @@
*/
import React from 'react';
import { render } from 'react-dom';
import { i18n } from '@kbn/i18n';
import { ThemeServiceStart } from '@kbn/core/public';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { FormattedMessage, I18nProvider } from '@kbn/i18n-react';
import { FormattedMessage } from '@kbn/i18n-react';
import { Ast } from '@kbn/interpreter';
import { buildExpressionFunction, DatatableRow } from '@kbn/expressions-plugin/common';
import { PaletteRegistry, CustomPaletteParams, CUSTOM_PALETTE } from '@kbn/coloring';
@ -400,26 +398,12 @@ export const getGaugeVisualization = ({
return update;
},
renderDimensionEditor(domElement, props) {
render(
<KibanaThemeProvider theme$={theme.theme$}>
<I18nProvider>
<GaugeDimensionEditor {...props} paletteService={paletteService} />
</I18nProvider>
</KibanaThemeProvider>,
domElement
);
DimensionEditorComponent(props) {
return <GaugeDimensionEditor {...props} paletteService={paletteService} />;
},
renderToolbar(domElement, props) {
render(
<KibanaThemeProvider theme$={theme.theme$}>
<I18nProvider>
<GaugeToolbar {...props} />
</I18nProvider>
</KibanaThemeProvider>,
domElement
);
ToolbarComponent(props) {
return <GaugeToolbar {...props} />;
},
getSupportedLayers(state, frame) {

View file

@ -6,15 +6,13 @@
*/
import React from 'react';
import { render } from 'react-dom';
import { i18n } from '@kbn/i18n';
import { FormattedMessage, I18nProvider } from '@kbn/i18n-react';
import { FormattedMessage } from '@kbn/i18n-react';
import { Ast } from '@kbn/interpreter';
import { Position } from '@elastic/charts';
import { IconChartHeatmap } from '@kbn/chart-icons';
import { CUSTOM_PALETTE, PaletteRegistry, CustomPaletteParams } from '@kbn/coloring';
import { ThemeServiceStart } from '@kbn/core/public';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public';
import { LayerTypes } from '@kbn/expression-xy-plugin/public';
import { HeatmapConfiguration } from '@kbn/visualizations-plugin/common';
@ -272,26 +270,12 @@ export const getHeatmapVisualization = ({
return update;
},
renderDimensionEditor(domElement, props) {
render(
<KibanaThemeProvider theme$={theme.theme$}>
<I18nProvider>
<HeatmapDimensionEditor {...props} paletteService={paletteService} />
</I18nProvider>
</KibanaThemeProvider>,
domElement
);
DimensionEditorComponent(props) {
return <HeatmapDimensionEditor {...props} paletteService={paletteService} />;
},
renderToolbar(domElement, props) {
render(
<KibanaThemeProvider theme$={theme.theme$}>
<I18nProvider>
<HeatmapToolbar {...props} />
</I18nProvider>
</KibanaThemeProvider>,
domElement
);
ToolbarComponent(props) {
return <HeatmapToolbar {...props} />;
},
getSupportedLayers() {

View file

@ -5,13 +5,10 @@
* 2.0.
*/ import React from 'react';
import { i18n } from '@kbn/i18n';
import { I18nProvider } from '@kbn/i18n-react';
import { euiThemeVars } from '@kbn/ui-theme';
import { render } from 'react-dom';
import { Ast } from '@kbn/interpreter';
import { PaletteOutput, PaletteRegistry, CUSTOM_PALETTE, shiftPalette } from '@kbn/coloring';
import { ThemeServiceStart } from '@kbn/core/public';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { ColorMode, CustomPaletteState } from '@kbn/charts-plugin/common';
import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public';
import { IconChartMetric } from '@kbn/chart-icons';
@ -290,26 +287,12 @@ export const getLegacyMetricVisualization = ({
return { ...prevState, accessor: undefined, colorMode: ColorMode.None, palette: undefined };
},
renderToolbar(domElement, props) {
render(
<KibanaThemeProvider theme$={theme.theme$}>
<I18nProvider>
<MetricToolbar state={props.state} setState={props.setState} frame={props.frame} />
</I18nProvider>
</KibanaThemeProvider>,
domElement
);
ToolbarComponent(props) {
return <MetricToolbar state={props.state} setState={props.setState} frame={props.frame} />;
},
renderDimensionEditor(domElement, props) {
render(
<KibanaThemeProvider theme$={theme.theme$}>
<I18nProvider>
<MetricDimensionEditor {...props} paletteService={paletteService} />
</I18nProvider>
</KibanaThemeProvider>,
domElement
);
DimensionEditorComponent(props) {
return <MetricDimensionEditor {...props} paletteService={paletteService} />;
},
getVisualizationInfo(state: LegacyMetricState) {

View file

@ -7,14 +7,11 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import { I18nProvider } from '@kbn/i18n-react';
import { render } from 'react-dom';
import { PaletteOutput, PaletteRegistry, CustomPaletteParams } from '@kbn/coloring';
import { ThemeServiceStart } from '@kbn/core/public';
import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public';
import { LayoutDirection } from '@elastic/charts';
import { euiLightVars, euiThemeVars } from '@kbn/ui-theme';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { IconChartMetric } from '@kbn/chart-icons';
import { AccessorConfig } from '@kbn/visualization-ui-components/public';
import { CollapseFunction } from '../../../common/expressions';
@ -592,37 +589,16 @@ export const getMetricVisualization = ({
return updated;
},
renderToolbar(domElement, props) {
render(
<KibanaThemeProvider theme$={theme.theme$}>
<I18nProvider>
<Toolbar {...props} />
</I18nProvider>
</KibanaThemeProvider>,
domElement
);
ToolbarComponent(props) {
return <Toolbar {...props} />;
},
renderDimensionEditor(domElement, props) {
render(
<KibanaThemeProvider theme$={theme.theme$}>
<I18nProvider>
<DimensionEditor {...props} paletteService={paletteService} />
</I18nProvider>
</KibanaThemeProvider>,
domElement
);
DimensionEditorComponent(props) {
return <DimensionEditor {...props} paletteService={paletteService} />;
},
renderDimensionEditorAdditionalSection(domElement, props) {
render(
<KibanaThemeProvider theme$={theme.theme$}>
<I18nProvider>
<DimensionEditorAdditionalSection {...props} />
</I18nProvider>
</KibanaThemeProvider>,
domElement
);
DimensionEditorAdditionalSectionComponent(props) {
return <DimensionEditorAdditionalSection {...props} />;
},
getDisplayOptions() {

View file

@ -6,12 +6,10 @@
*/
import React from 'react';
import { render } from 'react-dom';
import { i18n } from '@kbn/i18n';
import { FormattedMessage, I18nProvider } from '@kbn/i18n-react';
import { FormattedMessage } from '@kbn/i18n-react';
import type { PaletteRegistry } from '@kbn/coloring';
import { ThemeServiceStart } from '@kbn/core/public';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public';
import { EuiSpacer } from '@elastic/eui';
import { PartitionVisConfiguration } from '@kbn/visualizations-plugin/common/convert_to_lens';
@ -459,25 +457,11 @@ export const getPieVisualization = ({
layers: newState.layers.map((l) => (l.layerId === layerId ? newLayer : l)),
};
},
renderDimensionEditor(domElement, props) {
render(
<KibanaThemeProvider theme$={kibanaTheme.theme$}>
<I18nProvider>
<DimensionEditor {...props} paletteService={paletteService} />
</I18nProvider>
</KibanaThemeProvider>,
domElement
);
DimensionEditorComponent(props) {
return <DimensionEditor {...props} paletteService={paletteService} />;
},
renderDimensionEditorDataExtra(domElement, props) {
render(
<KibanaThemeProvider theme$={kibanaTheme.theme$}>
<I18nProvider>
<DimensionDataExtraEditor {...props} paletteService={paletteService} />
</I18nProvider>
</KibanaThemeProvider>,
domElement
);
DimensionEditorDataExtraComponent(props) {
return <DimensionDataExtraEditor {...props} paletteService={paletteService} />;
},
getSupportedLayers() {
@ -501,30 +485,16 @@ export const getPieVisualization = ({
toPreviewExpression: (state, layers, datasourceExpressionsByLayers) =>
toPreviewExpression(state, layers, paletteService, datasourceExpressionsByLayers),
renderToolbar(domElement, props) {
render(
<KibanaThemeProvider theme$={kibanaTheme.theme$}>
<I18nProvider>
<PieToolbar {...props} />
</I18nProvider>
</KibanaThemeProvider>,
domElement
);
ToolbarComponent(props) {
return <PieToolbar {...props} />;
},
hasLayerSettings(props) {
return { data: props.state.shape !== PieChartTypes.MOSAIC, appearance: false };
},
renderLayerSettings(domElement, props) {
render(
<KibanaThemeProvider theme$={kibanaTheme.theme$}>
<I18nProvider>
<LayerSettings {...props} />
</I18nProvider>
</KibanaThemeProvider>,
domElement
);
LayerSettingsComponent(props) {
return <LayerSettings {...props} />;
},
getSuggestionFromConvertToLensContext(props) {

View file

@ -7,10 +7,7 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import { I18nProvider } from '@kbn/i18n-react';
import { render } from 'react-dom';
import { ThemeServiceStart } from '@kbn/core/public';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public';
import type { ExpressionTagcloudFunctionDefinition } from '@kbn/expression-tagcloud-plugin/common';
import { LayerTypes } from '@kbn/expression-xy-plugin/public';
@ -268,31 +265,20 @@ export const getTagcloudVisualization = ({
return update;
},
renderDimensionEditor(domElement, props) {
DimensionEditorComponent(props) {
if (props.groupId === TAG_GROUP_ID) {
render(
<KibanaThemeProvider theme$={theme.theme$}>
<I18nProvider>
<TagsDimensionEditor
paletteService={paletteService}
state={props.state}
setState={props.setState}
/>
</I18nProvider>
</KibanaThemeProvider>,
domElement
return (
<TagsDimensionEditor
paletteService={paletteService}
state={props.state}
setState={props.setState}
/>
);
}
return null;
},
renderToolbar(domElement, props) {
render(
<KibanaThemeProvider theme$={theme.theme$}>
<I18nProvider>
<TagcloudToolbar {...props} />
</I18nProvider>
</KibanaThemeProvider>,
domElement
);
ToolbarComponent(props) {
return <TagcloudToolbar {...props} />;
},
});

View file

@ -6,9 +6,8 @@
*/
import React from 'react';
import { render } from 'react-dom';
import { Position } from '@elastic/charts';
import { FormattedMessage, I18nProvider } from '@kbn/i18n-react';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import type { PaletteRegistry } from '@kbn/coloring';
import { IconChartBarReferenceLine, IconChartBarAnnotations } from '@kbn/chart-icons';
@ -18,7 +17,6 @@ import {
EventAnnotationServiceType,
getAnnotationAccessor,
} from '@kbn/event-annotation-plugin/public';
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public';
@ -340,15 +338,8 @@ export const getXyVisualization = ({
return { data: Boolean(layer && isAnnotationsLayer(layer)), appearance: false };
},
renderLayerSettings(domElement, props) {
render(
<KibanaThemeProvider theme$={kibanaTheme.theme$}>
<I18nProvider>
<LayerSettings {...props} />
</I18nProvider>
</KibanaThemeProvider>,
domElement
);
LayerSettingsComponent(props) {
return <LayerSettings {...props} />;
},
onIndexPatternChange(state, indexPatternId, layerId) {
const layerIndex = state.layers.findIndex((l) => l.layerId === layerId);
@ -622,61 +613,29 @@ export const getXyVisualization = ({
};
},
renderLayerPanel(domElement, props) {
LayerPanelComponent(props) {
const { onChangeIndexPattern, ...otherProps } = props;
render(
<KibanaThemeProvider theme$={kibanaTheme.theme$}>
<I18nProvider>
<KibanaContextProvider
services={{
appName: 'lens',
storage,
uiSettings: core.uiSettings,
data,
fieldFormats,
savedObjects: core.savedObjects,
docLinks: core.docLinks,
http: core.http,
unifiedSearch,
}}
>
<LayerHeaderContent
{...otherProps}
onChangeIndexPattern={(indexPatternId) => {
// TODO: should it trigger an action as in the datasource?
onChangeIndexPattern(indexPatternId);
}}
/>
</KibanaContextProvider>
</I18nProvider>
</KibanaThemeProvider>,
domElement
return (
<LayerHeaderContent
{...otherProps}
onChangeIndexPattern={(indexPatternId) => {
// TODO: should it trigger an action as in the datasource?
onChangeIndexPattern(indexPatternId);
}}
/>
);
},
renderLayerHeader(domElement, props) {
render(
<KibanaThemeProvider theme$={kibanaTheme.theme$}>
<I18nProvider>
<LayerHeader {...props} />
</I18nProvider>
</KibanaThemeProvider>,
domElement
);
LayerHeaderComponent(props) {
return <LayerHeader {...props} />;
},
renderToolbar(domElement, props) {
render(
<KibanaThemeProvider theme$={kibanaTheme.theme$}>
<I18nProvider>
<XyToolbar {...props} useLegacyTimeAxis={useLegacyTimeAxis} />
</I18nProvider>
</KibanaThemeProvider>,
domElement
);
ToolbarComponent(props) {
return <XyToolbar {...props} useLegacyTimeAxis={useLegacyTimeAxis} />;
},
renderDimensionEditor(domElement, props) {
DimensionEditorComponent(props) {
const allProps = {
...props,
datatableUtilities: data.datatableUtilities,
@ -692,31 +651,10 @@ export const getXyVisualization = ({
<DataDimensionEditor {...allProps} />
);
render(
<KibanaThemeProvider theme$={kibanaTheme.theme$}>
<I18nProvider>
<KibanaContextProvider
services={{
appName: 'lens',
storage,
uiSettings: core.uiSettings,
data,
fieldFormats,
savedObjects: core.savedObjects,
docLinks: core.docLinks,
http: core.http,
unifiedSearch,
}}
>
{dimensionEditor}
</KibanaContextProvider>
</I18nProvider>
</KibanaThemeProvider>,
domElement
);
return dimensionEditor;
},
renderDimensionEditorDataExtra(domElement, props) {
DimensionEditorDataExtraComponent(props) {
const allProps = {
...props,
datatableUtilities: data.datatableUtilities,
@ -725,34 +663,13 @@ export const getXyVisualization = ({
};
const layer = props.state.layers.find((l) => l.layerId === props.layerId)!;
if (isReferenceLayer(layer)) {
return;
return null;
}
if (isAnnotationsLayer(layer)) {
return;
return null;
}
render(
<KibanaThemeProvider theme$={kibanaTheme.theme$}>
<I18nProvider>
<KibanaContextProvider
services={{
appName: 'lens',
storage,
uiSettings: core.uiSettings,
data,
fieldFormats,
savedObjects: core.savedObjects,
docLinks: core.docLinks,
http: core.http,
unifiedSearch,
}}
>
<DataDimensionEditorDataSectionExtra {...allProps} />
</KibanaContextProvider>
</I18nProvider>
</KibanaThemeProvider>,
domElement
);
return <DataDimensionEditorDataSectionExtra {...allProps} />;
},
getAddLayerButtonComponent: (props) => {
return (
@ -986,7 +903,7 @@ export const getXyVisualization = ({
state?.layers.filter(isAnnotationsLayer).map(({ indexPatternId }) => indexPatternId) ?? []
);
},
renderDimensionTrigger({ columnId, label }) {
DimensionTriggerComponent({ columnId, label }) {
if (label) {
return <DimensionTrigger id={columnId} label={label || defaultAnnotationLabel} />;
}

View file

@ -7,12 +7,9 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import { I18nProvider } from '@kbn/i18n-react';
import { render } from 'react-dom';
import type { FileLayer } from '@elastic/ems-client';
import type { PaletteRegistry } from '@kbn/coloring';
import { ThemeServiceStart } from '@kbn/core/public';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { layerTypes } from '@kbn/lens-plugin/public';
import type { OperationMetadata, SuggestionRequest, Visualization } from '@kbn/lens-plugin/public';
import { IconRegionMap } from '@kbn/chart-icons';
@ -195,20 +192,16 @@ export const getVisualization = ({
return update;
},
renderDimensionEditor(domElement, props) {
DimensionEditorComponent(props) {
if (props.groupId === REGION_KEY_GROUP_ID) {
render(
<KibanaThemeProvider theme$={theme.theme$}>
<I18nProvider>
<RegionKeyEditor
emsFileLayers={emsFileLayers}
state={props.state}
setState={props.setState}
/>
</I18nProvider>
</KibanaThemeProvider>,
domElement
return (
<RegionKeyEditor
emsFileLayers={emsFileLayers}
state={props.state}
setState={props.setState}
/>
);
}
return null;
},
});