This commit is contained in:
parent
ab1a480b4e
commit
9e1c76feb5
7 changed files with 653 additions and 423 deletions
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import {
|
||||
createMockVisualization,
|
||||
createMockFramePublicAPI,
|
||||
createMockDatasource,
|
||||
DatasourceMock,
|
||||
} from '../../mocks';
|
||||
import { Visualization } from '../../../types';
|
||||
import { mountWithIntl } from '@kbn/test/jest';
|
||||
import { LayerPanels } from './config_panel';
|
||||
import { LayerPanel } from './layer_panel';
|
||||
import { coreMock } from 'src/core/public/mocks';
|
||||
import { generateId } from '../../../id_generator';
|
||||
|
||||
jest.mock('../../../id_generator');
|
||||
|
||||
describe('ConfigPanel', () => {
|
||||
let mockVisualization: jest.Mocked<Visualization>;
|
||||
let mockVisualization2: jest.Mocked<Visualization>;
|
||||
let mockDatasource: DatasourceMock;
|
||||
const frame = createMockFramePublicAPI();
|
||||
|
||||
function getDefaultProps() {
|
||||
frame.datasourceLayers = {
|
||||
first: mockDatasource.publicAPIMock,
|
||||
};
|
||||
return {
|
||||
activeVisualizationId: 'vis1',
|
||||
visualizationMap: {
|
||||
vis1: mockVisualization,
|
||||
vis2: mockVisualization2,
|
||||
},
|
||||
activeDatasourceId: 'ds1',
|
||||
datasourceMap: {
|
||||
ds1: mockDatasource,
|
||||
},
|
||||
activeVisualization: ({
|
||||
...mockVisualization,
|
||||
getLayerIds: () => Object.keys(frame.datasourceLayers),
|
||||
appendLayer: true,
|
||||
} as unknown) as Visualization,
|
||||
datasourceStates: {
|
||||
ds1: {
|
||||
isLoading: false,
|
||||
state: 'state',
|
||||
},
|
||||
},
|
||||
visualizationState: 'state',
|
||||
updateVisualization: jest.fn(),
|
||||
updateDatasource: jest.fn(),
|
||||
updateAll: jest.fn(),
|
||||
framePublicAPI: frame,
|
||||
dispatch: jest.fn(),
|
||||
core: coreMock.createStart(),
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mockVisualization = {
|
||||
...createMockVisualization(),
|
||||
id: 'testVis',
|
||||
visualizationTypes: [
|
||||
{
|
||||
icon: 'empty',
|
||||
id: 'testVis',
|
||||
label: 'TEST1',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
mockVisualization2 = {
|
||||
...createMockVisualization(),
|
||||
|
||||
id: 'testVis2',
|
||||
visualizationTypes: [
|
||||
{
|
||||
icon: 'empty',
|
||||
id: 'testVis2',
|
||||
label: 'TEST2',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
mockVisualization.getLayerIds.mockReturnValue(Object.keys(frame.datasourceLayers));
|
||||
mockDatasource = createMockDatasource('ds1');
|
||||
});
|
||||
|
||||
describe('focus behavior when adding or removing layers', () => {
|
||||
it('should focus the only layer when resetting the layer', () => {
|
||||
const component = mountWithIntl(<LayerPanels {...getDefaultProps()} />);
|
||||
const firstLayerFocusable = component
|
||||
.find(LayerPanel)
|
||||
.first()
|
||||
.find('section')
|
||||
.first()
|
||||
.instance();
|
||||
act(() => {
|
||||
component.find('[data-test-subj="lnsLayerRemove"]').first().simulate('click');
|
||||
});
|
||||
const focusedEl = document.activeElement;
|
||||
expect(focusedEl).toEqual(firstLayerFocusable);
|
||||
});
|
||||
|
||||
it('should focus the second layer when removing the first layer', () => {
|
||||
const defaultProps = getDefaultProps();
|
||||
// overwriting datasourceLayers to test two layers
|
||||
frame.datasourceLayers = {
|
||||
first: mockDatasource.publicAPIMock,
|
||||
second: mockDatasource.publicAPIMock,
|
||||
};
|
||||
const component = mountWithIntl(<LayerPanels {...defaultProps} />);
|
||||
const secondLayerFocusable = component
|
||||
.find(LayerPanel)
|
||||
.at(1)
|
||||
.find('section')
|
||||
.first()
|
||||
.instance();
|
||||
act(() => {
|
||||
component.find('[data-test-subj="lnsLayerRemove"]').at(0).simulate('click');
|
||||
});
|
||||
const focusedEl = document.activeElement;
|
||||
expect(focusedEl).toEqual(secondLayerFocusable);
|
||||
});
|
||||
|
||||
it('should focus the first layer when removing the second layer', () => {
|
||||
const defaultProps = getDefaultProps();
|
||||
// overwriting datasourceLayers to test two layers
|
||||
frame.datasourceLayers = {
|
||||
first: mockDatasource.publicAPIMock,
|
||||
second: mockDatasource.publicAPIMock,
|
||||
};
|
||||
const component = mountWithIntl(<LayerPanels {...defaultProps} />);
|
||||
const firstLayerFocusable = component
|
||||
.find(LayerPanel)
|
||||
.first()
|
||||
.find('section')
|
||||
.first()
|
||||
.instance();
|
||||
act(() => {
|
||||
component.find('[data-test-subj="lnsLayerRemove"]').at(2).simulate('click');
|
||||
});
|
||||
const focusedEl = document.activeElement;
|
||||
expect(focusedEl).toEqual(firstLayerFocusable);
|
||||
});
|
||||
|
||||
it('should focus the added layer', () => {
|
||||
(generateId as jest.Mock).mockReturnValue(`second`);
|
||||
const dispatch = jest.fn((x) => {
|
||||
if (x.subType === 'ADD_LAYER') {
|
||||
frame.datasourceLayers.second = mockDatasource.publicAPIMock;
|
||||
}
|
||||
});
|
||||
|
||||
const component = mountWithIntl(<LayerPanels {...getDefaultProps()} dispatch={dispatch} />);
|
||||
act(() => {
|
||||
component.find('[data-test-subj="lnsLayerAddButton"]').first().simulate('click');
|
||||
});
|
||||
const focusedEl = document.activeElement;
|
||||
expect(focusedEl?.children[0].getAttribute('data-test-subj')).toEqual('lns-layerPanel-1');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
import './config_panel.scss';
|
||||
|
||||
import React, { useMemo, memo } from 'react';
|
||||
import React, { useMemo, memo, useEffect, useState, useCallback } from 'react';
|
||||
import { EuiFlexItem, EuiToolTip, EuiButton, EuiForm } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Visualization } from '../../../types';
|
||||
|
@ -24,7 +24,51 @@ export const ConfigPanelWrapper = memo(function ConfigPanelWrapper(props: Config
|
|||
) : null;
|
||||
});
|
||||
|
||||
function LayerPanels(
|
||||
function useFocusUpdate(layerIds: string[]) {
|
||||
const [nextFocusedLayerId, setNextFocusedLayerId] = useState<string | null>(null);
|
||||
const [layerRefs, setLayersRefs] = useState<Record<string, HTMLElement | null>>({});
|
||||
|
||||
useEffect(() => {
|
||||
const focusable = nextFocusedLayerId && layerRefs[nextFocusedLayerId];
|
||||
if (focusable) {
|
||||
focusable.focus();
|
||||
setNextFocusedLayerId(null);
|
||||
}
|
||||
}, [layerIds, layerRefs, nextFocusedLayerId]);
|
||||
|
||||
const setLayerRef = useCallback((layerId, el) => {
|
||||
if (el) {
|
||||
setLayersRefs((refs) => ({
|
||||
...refs,
|
||||
[layerId]: el,
|
||||
}));
|
||||
}
|
||||
}, []);
|
||||
|
||||
const removeLayerRef = useCallback(
|
||||
(layerId) => {
|
||||
if (layerIds.length <= 1) {
|
||||
return setNextFocusedLayerId(layerId);
|
||||
}
|
||||
|
||||
const removedLayerIndex = layerIds.findIndex((l) => l === layerId);
|
||||
const nextFocusedLayerIdId =
|
||||
removedLayerIndex === 0 ? layerIds[1] : layerIds[removedLayerIndex - 1];
|
||||
|
||||
setLayersRefs((refs) => {
|
||||
const newLayerRefs = { ...refs };
|
||||
delete newLayerRefs[layerId];
|
||||
return newLayerRefs;
|
||||
});
|
||||
return setNextFocusedLayerId(nextFocusedLayerIdId);
|
||||
},
|
||||
[layerIds]
|
||||
);
|
||||
|
||||
return { setNextFocusedLayerId, removeLayerRef, setLayerRef };
|
||||
}
|
||||
|
||||
export function LayerPanels(
|
||||
props: ConfigPanelWrapperProps & {
|
||||
activeDatasourceId: string;
|
||||
activeVisualization: Visualization;
|
||||
|
@ -37,6 +81,10 @@ function LayerPanels(
|
|||
activeDatasourceId,
|
||||
datasourceMap,
|
||||
} = props;
|
||||
|
||||
const layerIds = activeVisualization.getLayerIds(visualizationState);
|
||||
const { setNextFocusedLayerId, removeLayerRef, setLayerRef } = useFocusUpdate(layerIds);
|
||||
|
||||
const setVisualizationState = useMemo(
|
||||
() => (newState: unknown) => {
|
||||
dispatch({
|
||||
|
@ -85,13 +133,13 @@ function LayerPanels(
|
|||
},
|
||||
[dispatch]
|
||||
);
|
||||
const layerIds = activeVisualization.getLayerIds(visualizationState);
|
||||
|
||||
return (
|
||||
<EuiForm className="lnsConfigPanel">
|
||||
{layerIds.map((layerId, index) => (
|
||||
<LayerPanel
|
||||
{...props}
|
||||
setLayerRef={setLayerRef}
|
||||
key={layerId}
|
||||
layerId={layerId}
|
||||
index={index}
|
||||
|
@ -113,6 +161,7 @@ function LayerPanels(
|
|||
state,
|
||||
}),
|
||||
});
|
||||
removeLayerRef(layerId);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
@ -138,18 +187,20 @@ function LayerPanels(
|
|||
defaultMessage: 'Add layer',
|
||||
})}
|
||||
onClick={() => {
|
||||
const id = generateId();
|
||||
dispatch({
|
||||
type: 'UPDATE_STATE',
|
||||
subType: 'ADD_LAYER',
|
||||
updater: (state) =>
|
||||
appendLayer({
|
||||
activeVisualization,
|
||||
generateId,
|
||||
generateId: () => id,
|
||||
trackUiEvent,
|
||||
activeDatasource: datasourceMap[activeDatasourceId],
|
||||
state,
|
||||
}),
|
||||
});
|
||||
setNextFocusedLayerId(id);
|
||||
}}
|
||||
iconType="plusInCircleFilled"
|
||||
/>
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
.lnsLayerPanel {
|
||||
margin-bottom: $euiSizeS;
|
||||
|
||||
// disable focus ring for mouse clicks, leave it for keyboard users
|
||||
&:focus:not(:focus-visible) {
|
||||
animation: none !important; // sass-lint:disable-line no-important
|
||||
}
|
||||
}
|
||||
|
||||
.lnsLayerPanel__sourceFlexItem {
|
||||
|
|
|
@ -59,6 +59,7 @@ describe('LayerPanel', () => {
|
|||
dispatch: jest.fn(),
|
||||
core: coreMock.createStart(),
|
||||
index: 0,
|
||||
setLayerRef: jest.fn(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -74,6 +74,7 @@ export function LayerPanel(
|
|||
newVisualizationState: unknown
|
||||
) => void;
|
||||
onRemoveLayer: () => void;
|
||||
setLayerRef: (layerId: string, instance: HTMLDivElement | null) => void;
|
||||
}
|
||||
) {
|
||||
const dragDropContext = useContext(DragContext);
|
||||
|
@ -81,13 +82,18 @@ export function LayerPanel(
|
|||
initialActiveDimensionState
|
||||
);
|
||||
|
||||
const { framePublicAPI, layerId, isOnlyLayer, onRemoveLayer, index } = props;
|
||||
const { framePublicAPI, layerId, isOnlyLayer, onRemoveLayer, setLayerRef, index } = props;
|
||||
const datasourcePublicAPI = framePublicAPI.datasourceLayers[layerId];
|
||||
|
||||
useEffect(() => {
|
||||
setActiveDimension(initialActiveDimensionState);
|
||||
}, [props.activeVisualizationId]);
|
||||
|
||||
const setLayerRefMemoized = React.useCallback((el) => setLayerRef(layerId, el), [
|
||||
layerId,
|
||||
setLayerRef,
|
||||
]);
|
||||
|
||||
if (
|
||||
!datasourcePublicAPI ||
|
||||
!props.activeVisualizationId ||
|
||||
|
@ -131,467 +137,465 @@ export function LayerPanel(
|
|||
const columnLabelMap = layerDatasource.uniqueLabels(layerDatasourceConfigProps.state);
|
||||
return (
|
||||
<ChildDragDropProvider {...dragDropContext}>
|
||||
<EuiPanel
|
||||
data-test-subj={`lns-layerPanel-${index}`}
|
||||
className="lnsLayerPanel"
|
||||
paddingSize="s"
|
||||
>
|
||||
<EuiFlexGroup gutterSize="s" alignItems="flexStart" responsive={false}>
|
||||
<EuiFlexItem grow={false} className="lnsLayerPanel__settingsFlexItem">
|
||||
<LayerSettings
|
||||
layerId={layerId}
|
||||
layerConfigProps={{
|
||||
...layerVisualizationConfigProps,
|
||||
setState: props.updateVisualization,
|
||||
}}
|
||||
activeVisualization={activeVisualization}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
{layerDatasource && (
|
||||
<EuiFlexItem className="lnsLayerPanel__sourceFlexItem">
|
||||
<NativeRenderer
|
||||
render={layerDatasource.renderLayerPanel}
|
||||
nativeProps={{
|
||||
layerId,
|
||||
state: layerDatasourceState,
|
||||
activeData: props.framePublicAPI.activeData,
|
||||
setState: (updater: unknown) => {
|
||||
const newState =
|
||||
typeof updater === 'function' ? updater(layerDatasourceState) : updater;
|
||||
// Look for removed columns
|
||||
const nextPublicAPI = layerDatasource.getPublicAPI({
|
||||
state: newState,
|
||||
layerId,
|
||||
});
|
||||
const nextTable = new Set(
|
||||
nextPublicAPI.getTableSpec().map(({ columnId }) => columnId)
|
||||
);
|
||||
const removed = datasourcePublicAPI
|
||||
.getTableSpec()
|
||||
.map(({ columnId }) => columnId)
|
||||
.filter((columnId) => !nextTable.has(columnId));
|
||||
let nextVisState = props.visualizationState;
|
||||
removed.forEach((columnId) => {
|
||||
nextVisState = activeVisualization.removeDimension({
|
||||
layerId,
|
||||
columnId,
|
||||
prevState: nextVisState,
|
||||
});
|
||||
});
|
||||
|
||||
props.updateAll(datasourceId, newState, nextVisState);
|
||||
},
|
||||
<section tabIndex={-1} ref={setLayerRefMemoized} className="lnsLayerPanel">
|
||||
<EuiPanel data-test-subj={`lns-layerPanel-${index}`} paddingSize="s">
|
||||
<EuiFlexGroup gutterSize="s" alignItems="flexStart" responsive={false}>
|
||||
<EuiFlexItem grow={false} className="lnsLayerPanel__settingsFlexItem">
|
||||
<LayerSettings
|
||||
layerId={layerId}
|
||||
layerConfigProps={{
|
||||
...layerVisualizationConfigProps,
|
||||
setState: props.updateVisualization,
|
||||
}}
|
||||
activeVisualization={activeVisualization}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
{groups.map((group, groupIndex) => {
|
||||
const newId = generateId();
|
||||
const isMissing = !isEmptyLayer && group.required && group.accessors.length === 0;
|
||||
|
||||
return (
|
||||
<EuiFormRow
|
||||
className={
|
||||
group.supportsMoreColumns
|
||||
? 'lnsLayerPanel__row'
|
||||
: 'lnsLayerPanel__row lnsLayerPanel__row--notSupportsMoreColumns'
|
||||
}
|
||||
fullWidth
|
||||
label={<div className="lnsLayerPanel__groupLabel">{group.groupLabel}</div>}
|
||||
labelType="legend"
|
||||
key={groupIndex}
|
||||
isInvalid={isMissing}
|
||||
error={
|
||||
isMissing ? (
|
||||
<div className="lnsLayerPanel__error">
|
||||
{i18n.translate('xpack.lens.editorFrame.requiredDimensionWarningLabel', {
|
||||
defaultMessage: 'Required dimension',
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
[]
|
||||
)
|
||||
}
|
||||
>
|
||||
<>
|
||||
<ReorderProvider id={group.groupId} className={'lnsLayerPanel__group'}>
|
||||
{group.accessors.map((accessorConfig) => {
|
||||
const accessor = accessorConfig.columnId;
|
||||
const { dragging } = dragDropContext;
|
||||
const dragType =
|
||||
isDraggedOperation(dragging) && accessor === dragging.columnId
|
||||
? 'move'
|
||||
: isDraggedOperation(dragging) && group.groupId === dragging.groupId
|
||||
? 'reorder'
|
||||
: 'copy';
|
||||
|
||||
const dropType = isDraggedOperation(dragging)
|
||||
? group.groupId !== dragging.groupId
|
||||
? 'replace'
|
||||
: 'reorder'
|
||||
: 'add';
|
||||
|
||||
const isFromCompatibleGroup =
|
||||
dragging?.groupId !== group.groupId &&
|
||||
layerDatasource.canHandleDrop({
|
||||
...layerDatasourceDropProps,
|
||||
columnId: accessor,
|
||||
filterOperations: group.filterOperations,
|
||||
{layerDatasource && (
|
||||
<EuiFlexItem className="lnsLayerPanel__sourceFlexItem">
|
||||
<NativeRenderer
|
||||
render={layerDatasource.renderLayerPanel}
|
||||
nativeProps={{
|
||||
layerId,
|
||||
state: layerDatasourceState,
|
||||
activeData: props.framePublicAPI.activeData,
|
||||
setState: (updater: unknown) => {
|
||||
const newState =
|
||||
typeof updater === 'function' ? updater(layerDatasourceState) : updater;
|
||||
// Look for removed columns
|
||||
const nextPublicAPI = layerDatasource.getPublicAPI({
|
||||
state: newState,
|
||||
layerId,
|
||||
});
|
||||
const nextTable = new Set(
|
||||
nextPublicAPI.getTableSpec().map(({ columnId }) => columnId)
|
||||
);
|
||||
const removed = datasourcePublicAPI
|
||||
.getTableSpec()
|
||||
.map(({ columnId }) => columnId)
|
||||
.filter((columnId) => !nextTable.has(columnId));
|
||||
let nextVisState = props.visualizationState;
|
||||
removed.forEach((columnId) => {
|
||||
nextVisState = activeVisualization.removeDimension({
|
||||
layerId,
|
||||
columnId,
|
||||
prevState: nextVisState,
|
||||
});
|
||||
});
|
||||
|
||||
const isFromTheSameGroup =
|
||||
isDraggedOperation(dragging) &&
|
||||
dragging.groupId === group.groupId &&
|
||||
dragging.columnId !== accessor;
|
||||
props.updateAll(datasourceId, newState, nextVisState);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
|
||||
const isDroppable = isDraggedOperation(dragging)
|
||||
? dragType === 'reorder'
|
||||
? isFromTheSameGroup
|
||||
: isFromCompatibleGroup
|
||||
: layerDatasource.canHandleDrop({
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
{groups.map((group, groupIndex) => {
|
||||
const newId = generateId();
|
||||
const isMissing = !isEmptyLayer && group.required && group.accessors.length === 0;
|
||||
|
||||
return (
|
||||
<EuiFormRow
|
||||
className={
|
||||
group.supportsMoreColumns
|
||||
? 'lnsLayerPanel__row'
|
||||
: 'lnsLayerPanel__row lnsLayerPanel__row--notSupportsMoreColumns'
|
||||
}
|
||||
fullWidth
|
||||
label={<div className="lnsLayerPanel__groupLabel">{group.groupLabel}</div>}
|
||||
labelType="legend"
|
||||
key={groupIndex}
|
||||
isInvalid={isMissing}
|
||||
error={
|
||||
isMissing ? (
|
||||
<div className="lnsLayerPanel__error">
|
||||
{i18n.translate('xpack.lens.editorFrame.requiredDimensionWarningLabel', {
|
||||
defaultMessage: 'Required dimension',
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
[]
|
||||
)
|
||||
}
|
||||
>
|
||||
<>
|
||||
<ReorderProvider id={group.groupId} className={'lnsLayerPanel__group'}>
|
||||
{group.accessors.map((accessorConfig) => {
|
||||
const accessor = accessorConfig.columnId;
|
||||
const { dragging } = dragDropContext;
|
||||
const dragType =
|
||||
isDraggedOperation(dragging) && accessor === dragging.columnId
|
||||
? 'move'
|
||||
: isDraggedOperation(dragging) && group.groupId === dragging.groupId
|
||||
? 'reorder'
|
||||
: 'copy';
|
||||
|
||||
const dropType = isDraggedOperation(dragging)
|
||||
? group.groupId !== dragging.groupId
|
||||
? 'replace'
|
||||
: 'reorder'
|
||||
: 'add';
|
||||
|
||||
const isFromCompatibleGroup =
|
||||
dragging?.groupId !== group.groupId &&
|
||||
layerDatasource.canHandleDrop({
|
||||
...layerDatasourceDropProps,
|
||||
columnId: accessor,
|
||||
filterOperations: group.filterOperations,
|
||||
});
|
||||
|
||||
return (
|
||||
<DragDrop
|
||||
key={accessor}
|
||||
draggable={!activeId}
|
||||
dragType={dragType}
|
||||
dropType={dropType}
|
||||
data-test-subj={group.dataTestSubj}
|
||||
itemsInGroup={group.accessors.map((a) =>
|
||||
typeof a === 'string' ? a : a.columnId
|
||||
)}
|
||||
className={'lnsLayerPanel__dimensionContainer'}
|
||||
value={{
|
||||
columnId: accessor,
|
||||
groupId: group.groupId,
|
||||
layerId,
|
||||
id: accessor,
|
||||
}}
|
||||
isValueEqual={isSameConfiguration}
|
||||
label={columnLabelMap[accessor]}
|
||||
droppable={dragging && isDroppable}
|
||||
dropTo={(dropTargetId: string) => {
|
||||
layerDatasource.onDrop({
|
||||
isReorder: true,
|
||||
...layerDatasourceDropProps,
|
||||
droppedItem: {
|
||||
columnId: accessor,
|
||||
groupId: group.groupId,
|
||||
layerId,
|
||||
id: accessor,
|
||||
},
|
||||
columnId: dropTargetId,
|
||||
filterOperations: group.filterOperations,
|
||||
});
|
||||
}}
|
||||
onDrop={(droppedItem) => {
|
||||
const isReorder =
|
||||
isDraggedOperation(droppedItem) &&
|
||||
droppedItem.groupId === group.groupId &&
|
||||
droppedItem.columnId !== accessor;
|
||||
const isFromTheSameGroup =
|
||||
isDraggedOperation(dragging) &&
|
||||
dragging.groupId === group.groupId &&
|
||||
dragging.columnId !== accessor;
|
||||
|
||||
const dropResult = layerDatasource.onDrop({
|
||||
isReorder,
|
||||
const isDroppable = isDraggedOperation(dragging)
|
||||
? dragType === 'reorder'
|
||||
? isFromTheSameGroup
|
||||
: isFromCompatibleGroup
|
||||
: layerDatasource.canHandleDrop({
|
||||
...layerDatasourceDropProps,
|
||||
droppedItem,
|
||||
columnId: accessor,
|
||||
filterOperations: group.filterOperations,
|
||||
});
|
||||
if (typeof dropResult === 'object') {
|
||||
// When a column is moved, we delete the reference to the old
|
||||
props.updateVisualization(
|
||||
activeVisualization.removeDimension({
|
||||
|
||||
return (
|
||||
<DragDrop
|
||||
key={accessor}
|
||||
draggable={!activeId}
|
||||
dragType={dragType}
|
||||
dropType={dropType}
|
||||
data-test-subj={group.dataTestSubj}
|
||||
itemsInGroup={group.accessors.map((a) =>
|
||||
typeof a === 'string' ? a : a.columnId
|
||||
)}
|
||||
className={'lnsLayerPanel__dimensionContainer'}
|
||||
value={{
|
||||
columnId: accessor,
|
||||
groupId: group.groupId,
|
||||
layerId,
|
||||
id: accessor,
|
||||
}}
|
||||
isValueEqual={isSameConfiguration}
|
||||
label={columnLabelMap[accessor]}
|
||||
droppable={dragging && isDroppable}
|
||||
dropTo={(dropTargetId: string) => {
|
||||
layerDatasource.onDrop({
|
||||
isReorder: true,
|
||||
...layerDatasourceDropProps,
|
||||
droppedItem: {
|
||||
columnId: accessor,
|
||||
groupId: group.groupId,
|
||||
layerId,
|
||||
columnId: dropResult.deleted,
|
||||
id: accessor,
|
||||
},
|
||||
columnId: dropTargetId,
|
||||
filterOperations: group.filterOperations,
|
||||
});
|
||||
}}
|
||||
onDrop={(droppedItem) => {
|
||||
const isReorder =
|
||||
isDraggedOperation(droppedItem) &&
|
||||
droppedItem.groupId === group.groupId &&
|
||||
droppedItem.columnId !== accessor;
|
||||
|
||||
const dropResult = layerDatasource.onDrop({
|
||||
isReorder,
|
||||
...layerDatasourceDropProps,
|
||||
droppedItem,
|
||||
columnId: accessor,
|
||||
filterOperations: group.filterOperations,
|
||||
});
|
||||
if (typeof dropResult === 'object') {
|
||||
// When a column is moved, we delete the reference to the old
|
||||
props.updateVisualization(
|
||||
activeVisualization.removeDimension({
|
||||
layerId,
|
||||
columnId: dropResult.deleted,
|
||||
prevState: props.visualizationState,
|
||||
})
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="lnsLayerPanel__dimension">
|
||||
<EuiLink
|
||||
className="lnsLayerPanel__dimensionLink"
|
||||
data-test-subj="lnsLayerPanel-dimensionLink"
|
||||
onClick={() => {
|
||||
if (activeId) {
|
||||
setActiveDimension(initialActiveDimensionState);
|
||||
} else {
|
||||
setActiveDimension({
|
||||
isNew: false,
|
||||
activeGroup: group,
|
||||
activeId: accessor,
|
||||
});
|
||||
}
|
||||
}}
|
||||
aria-label={triggerLinkA11yText(columnLabelMap[accessor])}
|
||||
title={triggerLinkA11yText(columnLabelMap[accessor])}
|
||||
>
|
||||
<ColorIndicator accessorConfig={accessorConfig}>
|
||||
<NativeRenderer
|
||||
render={layerDatasource.renderDimensionTrigger}
|
||||
nativeProps={{
|
||||
...layerDatasourceConfigProps,
|
||||
columnId: accessor,
|
||||
filterOperations: group.filterOperations,
|
||||
}}
|
||||
/>
|
||||
</ColorIndicator>
|
||||
</EuiLink>
|
||||
<EuiButtonIcon
|
||||
className="lnsLayerPanel__dimensionRemove"
|
||||
data-test-subj="indexPattern-dimension-remove"
|
||||
iconType="cross"
|
||||
iconSize="s"
|
||||
size="s"
|
||||
color="danger"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.lens.indexPattern.removeColumnLabel',
|
||||
{
|
||||
defaultMessage: 'Remove configuration from "{groupLabel}"',
|
||||
values: { groupLabel: group.groupLabel },
|
||||
}
|
||||
)}
|
||||
title={i18n.translate('xpack.lens.indexPattern.removeColumnLabel', {
|
||||
defaultMessage: 'Remove configuration from "{groupLabel}"',
|
||||
values: { groupLabel: group.groupLabel },
|
||||
})}
|
||||
onClick={() => {
|
||||
trackUiEvent('indexpattern_dimension_removed');
|
||||
props.updateAll(
|
||||
datasourceId,
|
||||
layerDatasource.removeColumn({
|
||||
layerId,
|
||||
columnId: accessor,
|
||||
prevState: layerDatasourceState,
|
||||
}),
|
||||
activeVisualization.removeDimension({
|
||||
layerId,
|
||||
columnId: accessor,
|
||||
prevState: props.visualizationState,
|
||||
})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<PaletteIndicator accessorConfig={accessorConfig} />
|
||||
</div>
|
||||
</DragDrop>
|
||||
);
|
||||
})}
|
||||
</ReorderProvider>
|
||||
{group.supportsMoreColumns ? (
|
||||
<div className={'lnsLayerPanel__dimensionContainer'}>
|
||||
<DragDrop
|
||||
data-test-subj={group.dataTestSubj}
|
||||
droppable={
|
||||
Boolean(dragDropContext.dragging) &&
|
||||
// Verify that the dragged item is not coming from the same group
|
||||
// since this would be a reorder
|
||||
(!isDraggedOperation(dragDropContext.dragging) ||
|
||||
dragDropContext.dragging.groupId !== group.groupId) &&
|
||||
layerDatasource.canHandleDrop({
|
||||
...layerDatasourceDropProps,
|
||||
columnId: newId,
|
||||
filterOperations: group.filterOperations,
|
||||
})
|
||||
}
|
||||
onDrop={(droppedItem) => {
|
||||
const dropResult = layerDatasource.onDrop({
|
||||
...layerDatasourceDropProps,
|
||||
droppedItem,
|
||||
columnId: newId,
|
||||
filterOperations: group.filterOperations,
|
||||
});
|
||||
if (dropResult) {
|
||||
props.updateVisualization(
|
||||
activeVisualization.setDimension({
|
||||
layerId,
|
||||
groupId: group.groupId,
|
||||
columnId: newId,
|
||||
prevState: props.visualizationState,
|
||||
})
|
||||
);
|
||||
|
||||
if (typeof dropResult === 'object') {
|
||||
// When a column is moved, we delete the reference to the old
|
||||
props.updateVisualization(
|
||||
activeVisualization.removeDimension({
|
||||
layerId,
|
||||
columnId: dropResult.deleted,
|
||||
prevState: props.visualizationState,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="lnsLayerPanel__dimension">
|
||||
<EuiLink
|
||||
className="lnsLayerPanel__dimensionLink"
|
||||
data-test-subj="lnsLayerPanel-dimensionLink"
|
||||
<div className="lnsLayerPanel__dimension lnsLayerPanel__dimension--empty">
|
||||
<EuiButtonEmpty
|
||||
className="lnsLayerPanel__triggerText"
|
||||
color="text"
|
||||
size="xs"
|
||||
iconType="plusInCircleFilled"
|
||||
contentProps={{
|
||||
className: 'lnsLayerPanel__triggerTextContent',
|
||||
}}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.lens.indexPattern.removeColumnAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Drop a field or click to add to {groupLabel}',
|
||||
values: { groupLabel: group.groupLabel },
|
||||
}
|
||||
)}
|
||||
data-test-subj="lns-empty-dimension"
|
||||
onClick={() => {
|
||||
if (activeId) {
|
||||
setActiveDimension(initialActiveDimensionState);
|
||||
} else {
|
||||
setActiveDimension({
|
||||
isNew: false,
|
||||
isNew: true,
|
||||
activeGroup: group,
|
||||
activeId: accessor,
|
||||
activeId: newId,
|
||||
});
|
||||
}
|
||||
}}
|
||||
aria-label={triggerLinkA11yText(columnLabelMap[accessor])}
|
||||
title={triggerLinkA11yText(columnLabelMap[accessor])}
|
||||
>
|
||||
<ColorIndicator accessorConfig={accessorConfig}>
|
||||
<NativeRenderer
|
||||
render={layerDatasource.renderDimensionTrigger}
|
||||
nativeProps={{
|
||||
...layerDatasourceConfigProps,
|
||||
columnId: accessor,
|
||||
filterOperations: group.filterOperations,
|
||||
}}
|
||||
/>
|
||||
</ColorIndicator>
|
||||
</EuiLink>
|
||||
<EuiButtonIcon
|
||||
className="lnsLayerPanel__dimensionRemove"
|
||||
data-test-subj="indexPattern-dimension-remove"
|
||||
iconType="cross"
|
||||
iconSize="s"
|
||||
size="s"
|
||||
color="danger"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.lens.indexPattern.removeColumnLabel',
|
||||
{
|
||||
defaultMessage: 'Remove configuration from "{groupLabel}"',
|
||||
values: { groupLabel: group.groupLabel },
|
||||
}
|
||||
)}
|
||||
title={i18n.translate('xpack.lens.indexPattern.removeColumnLabel', {
|
||||
defaultMessage: 'Remove configuration from "{groupLabel}"',
|
||||
values: { groupLabel: group.groupLabel },
|
||||
})}
|
||||
onClick={() => {
|
||||
trackUiEvent('indexpattern_dimension_removed');
|
||||
props.updateAll(
|
||||
datasourceId,
|
||||
layerDatasource.removeColumn({
|
||||
layerId,
|
||||
columnId: accessor,
|
||||
prevState: layerDatasourceState,
|
||||
}),
|
||||
activeVisualization.removeDimension({
|
||||
layerId,
|
||||
columnId: accessor,
|
||||
prevState: props.visualizationState,
|
||||
})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<PaletteIndicator accessorConfig={accessorConfig} />
|
||||
<FormattedMessage
|
||||
id="xpack.lens.configure.emptyConfig"
|
||||
defaultMessage="Drop a field or click to add"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</div>
|
||||
</DragDrop>
|
||||
);
|
||||
})}
|
||||
</ReorderProvider>
|
||||
{group.supportsMoreColumns ? (
|
||||
<div className={'lnsLayerPanel__dimensionContainer'}>
|
||||
<DragDrop
|
||||
data-test-subj={group.dataTestSubj}
|
||||
droppable={
|
||||
Boolean(dragDropContext.dragging) &&
|
||||
// Verify that the dragged item is not coming from the same group
|
||||
// since this would be a reorder
|
||||
(!isDraggedOperation(dragDropContext.dragging) ||
|
||||
dragDropContext.dragging.groupId !== group.groupId) &&
|
||||
layerDatasource.canHandleDrop({
|
||||
...layerDatasourceDropProps,
|
||||
columnId: newId,
|
||||
filterOperations: group.filterOperations,
|
||||
})
|
||||
}
|
||||
onDrop={(droppedItem) => {
|
||||
const dropResult = layerDatasource.onDrop({
|
||||
...layerDatasourceDropProps,
|
||||
droppedItem,
|
||||
columnId: newId,
|
||||
filterOperations: group.filterOperations,
|
||||
});
|
||||
if (dropResult) {
|
||||
props.updateVisualization(
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
</EuiFormRow>
|
||||
);
|
||||
})}
|
||||
<DimensionContainer
|
||||
isOpen={!!activeId}
|
||||
groupLabel={activeGroup?.groupLabel || ''}
|
||||
handleClose={() => {
|
||||
if (layerDatasource.updateStateOnCloseDimension) {
|
||||
const newState = layerDatasource.updateStateOnCloseDimension({
|
||||
state: layerDatasourceState,
|
||||
layerId,
|
||||
columnId: activeId!,
|
||||
});
|
||||
if (newState) {
|
||||
props.updateDatasource(datasourceId, newState);
|
||||
}
|
||||
}
|
||||
setActiveDimension(initialActiveDimensionState);
|
||||
}}
|
||||
panel={
|
||||
<>
|
||||
{activeGroup && activeId && (
|
||||
<NativeRenderer
|
||||
render={layerDatasource.renderDimensionEditor}
|
||||
nativeProps={{
|
||||
...layerDatasourceConfigProps,
|
||||
core: props.core,
|
||||
columnId: activeId,
|
||||
filterOperations: activeGroup.filterOperations,
|
||||
dimensionGroups: groups,
|
||||
setState: (newState: unknown, shouldUpdateVisualization?: boolean) => {
|
||||
if (shouldUpdateVisualization) {
|
||||
props.updateAll(
|
||||
datasourceId,
|
||||
newState,
|
||||
activeVisualization.setDimension({
|
||||
layerId,
|
||||
groupId: group.groupId,
|
||||
columnId: newId,
|
||||
groupId: activeGroup.groupId,
|
||||
columnId: activeId,
|
||||
prevState: props.visualizationState,
|
||||
})
|
||||
);
|
||||
|
||||
if (typeof dropResult === 'object') {
|
||||
// When a column is moved, we delete the reference to the old
|
||||
props.updateVisualization(
|
||||
activeVisualization.removeDimension({
|
||||
layerId,
|
||||
columnId: dropResult.deleted,
|
||||
prevState: props.visualizationState,
|
||||
})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
props.updateDatasource(datasourceId, newState);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="lnsLayerPanel__dimension lnsLayerPanel__dimension--empty">
|
||||
<EuiButtonEmpty
|
||||
className="lnsLayerPanel__triggerText"
|
||||
color="text"
|
||||
size="xs"
|
||||
iconType="plusInCircleFilled"
|
||||
contentProps={{
|
||||
className: 'lnsLayerPanel__triggerTextContent',
|
||||
}}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.lens.indexPattern.removeColumnAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Drop a field or click to add to {groupLabel}',
|
||||
values: { groupLabel: group.groupLabel },
|
||||
}
|
||||
)}
|
||||
data-test-subj="lns-empty-dimension"
|
||||
onClick={() => {
|
||||
if (activeId) {
|
||||
setActiveDimension(initialActiveDimensionState);
|
||||
} else {
|
||||
setActiveDimension({
|
||||
isNew: true,
|
||||
activeGroup: group,
|
||||
activeId: newId,
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.lens.configure.emptyConfig"
|
||||
defaultMessage="Drop a field or click to add"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</div>
|
||||
</DragDrop>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
</EuiFormRow>
|
||||
);
|
||||
})}
|
||||
<DimensionContainer
|
||||
isOpen={!!activeId}
|
||||
groupLabel={activeGroup?.groupLabel || ''}
|
||||
handleClose={() => {
|
||||
if (layerDatasource.updateStateOnCloseDimension) {
|
||||
const newState = layerDatasource.updateStateOnCloseDimension({
|
||||
state: layerDatasourceState,
|
||||
layerId,
|
||||
columnId: activeId!,
|
||||
});
|
||||
if (newState) {
|
||||
props.updateDatasource(datasourceId, newState);
|
||||
}
|
||||
}
|
||||
setActiveDimension(initialActiveDimensionState);
|
||||
}}
|
||||
panel={
|
||||
<>
|
||||
{activeGroup && activeId && (
|
||||
<NativeRenderer
|
||||
render={layerDatasource.renderDimensionEditor}
|
||||
nativeProps={{
|
||||
...layerDatasourceConfigProps,
|
||||
core: props.core,
|
||||
columnId: activeId,
|
||||
filterOperations: activeGroup.filterOperations,
|
||||
dimensionGroups: groups,
|
||||
setState: (newState: unknown, shouldUpdateVisualization?: boolean) => {
|
||||
if (shouldUpdateVisualization) {
|
||||
props.updateAll(
|
||||
datasourceId,
|
||||
newState,
|
||||
activeVisualization.setDimension({
|
||||
layerId,
|
||||
groupId: activeGroup.groupId,
|
||||
columnId: activeId,
|
||||
prevState: props.visualizationState,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
props.updateDatasource(datasourceId, newState);
|
||||
}
|
||||
setActiveDimension({
|
||||
...activeDimension,
|
||||
isNew: false,
|
||||
});
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{activeGroup &&
|
||||
activeId &&
|
||||
!activeDimension.isNew &&
|
||||
activeVisualization.renderDimensionEditor &&
|
||||
activeGroup?.enableDimensionEditor && (
|
||||
<div className="lnsLayerPanel__styleEditor">
|
||||
<NativeRenderer
|
||||
render={activeVisualization.renderDimensionEditor}
|
||||
nativeProps={{
|
||||
...layerVisualizationConfigProps,
|
||||
groupId: activeGroup.groupId,
|
||||
accessor: activeId,
|
||||
setState: props.updateVisualization,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
setActiveDimension({
|
||||
...activeDimension,
|
||||
isNew: false,
|
||||
});
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
{activeGroup &&
|
||||
activeId &&
|
||||
!activeDimension.isNew &&
|
||||
activeVisualization.renderDimensionEditor &&
|
||||
activeGroup?.enableDimensionEditor && (
|
||||
<div className="lnsLayerPanel__styleEditor">
|
||||
<NativeRenderer
|
||||
render={activeVisualization.renderDimensionEditor}
|
||||
nativeProps={{
|
||||
...layerVisualizationConfigProps,
|
||||
groupId: activeGroup.groupId,
|
||||
accessor: activeId,
|
||||
setState: props.updateVisualization,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiFlexGroup justifyContent="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
iconType="trash"
|
||||
color="danger"
|
||||
data-test-subj="lnsLayerRemove"
|
||||
aria-label={
|
||||
isOnlyLayer
|
||||
? i18n.translate('xpack.lens.resetLayerAriaLabel', {
|
||||
defaultMessage: 'Reset layer {index}',
|
||||
values: { index: index + 1 },
|
||||
})
|
||||
: i18n.translate('xpack.lens.deleteLayerAriaLabel', {
|
||||
defaultMessage: `Delete layer {index}`,
|
||||
values: { index: index + 1 },
|
||||
})
|
||||
}
|
||||
onClick={() => {
|
||||
// If we don't blur the remove / clear button, it remains focused
|
||||
// which is a strange UX in this case. e.target.blur doesn't work
|
||||
// due to who knows what, but probably event re-writing. Additionally,
|
||||
// activeElement does not have blur so, we need to do some casting + safeguards.
|
||||
const el = (document.activeElement as unknown) as { blur: () => void };
|
||||
|
||||
if (el?.blur) {
|
||||
el.blur();
|
||||
<EuiFlexGroup justifyContent="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
iconType="trash"
|
||||
color="danger"
|
||||
data-test-subj="lnsLayerRemove"
|
||||
aria-label={
|
||||
isOnlyLayer
|
||||
? i18n.translate('xpack.lens.resetLayerAriaLabel', {
|
||||
defaultMessage: 'Reset layer {index}',
|
||||
values: { index: index + 1 },
|
||||
})
|
||||
: i18n.translate('xpack.lens.deleteLayerAriaLabel', {
|
||||
defaultMessage: `Delete layer {index}`,
|
||||
values: { index: index + 1 },
|
||||
})
|
||||
}
|
||||
onClick={() => {
|
||||
// If we don't blur the remove / clear button, it remains focused
|
||||
// which is a strange UX in this case. e.target.blur doesn't work
|
||||
// due to who knows what, but probably event re-writing. Additionally,
|
||||
// activeElement does not have blur so, we need to do some casting + safeguards.
|
||||
const el = (document.activeElement as unknown) as { blur: () => void };
|
||||
|
||||
onRemoveLayer();
|
||||
}}
|
||||
>
|
||||
{isOnlyLayer
|
||||
? i18n.translate('xpack.lens.resetLayer', {
|
||||
defaultMessage: 'Reset layer',
|
||||
})
|
||||
: i18n.translate('xpack.lens.deleteLayer', {
|
||||
defaultMessage: `Delete layer`,
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
if (el?.blur) {
|
||||
el.blur();
|
||||
}
|
||||
|
||||
onRemoveLayer();
|
||||
}}
|
||||
>
|
||||
{isOnlyLayer
|
||||
? i18n.translate('xpack.lens.resetLayer', {
|
||||
defaultMessage: 'Reset layer',
|
||||
})
|
||||
: i18n.translate('xpack.lens.deleteLayer', {
|
||||
defaultMessage: `Delete layer`,
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</section>
|
||||
</ChildDragDropProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -602,7 +602,7 @@ describe('editor_frame', () => {
|
|||
});
|
||||
|
||||
// validation requires to calls this getConfiguration API
|
||||
expect(mockVisualization.getConfiguration).toHaveBeenCalledTimes(6);
|
||||
expect(mockVisualization.getConfiguration).toHaveBeenCalledTimes(7);
|
||||
expect(mockVisualization.getConfiguration).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
state: updatedState,
|
||||
|
@ -682,7 +682,7 @@ describe('editor_frame', () => {
|
|||
});
|
||||
|
||||
// validation requires to calls this getConfiguration API
|
||||
expect(mockVisualization.getConfiguration).toHaveBeenCalledTimes(6);
|
||||
expect(mockVisualization.getConfiguration).toHaveBeenCalledTimes(7);
|
||||
expect(mockVisualization.getConfiguration).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
frame: expect.objectContaining({
|
||||
|
@ -1196,7 +1196,7 @@ describe('editor_frame', () => {
|
|||
});
|
||||
|
||||
// validation requires to calls this getConfiguration API
|
||||
expect(mockVisualization.getConfiguration).toHaveBeenCalledTimes(4);
|
||||
expect(mockVisualization.getConfiguration).toHaveBeenCalledTimes(5);
|
||||
expect(mockVisualization.getConfiguration).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
state: suggestionVisState,
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
|
||||
.lnsConfigPanel {
|
||||
@include euiScrollBar;
|
||||
padding: $euiSize 0 $euiSize $euiSize;
|
||||
padding: $euiSize $euiSizeXS $euiSize $euiSize;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue