Skip to content

Commit 7edf2ce

Browse files
Wylie Conlonelasticmachine
authored andcommitted
[Lens] Drag dimension to replace (#75895)
* [Lens] Drag to replace * Add jest tests for drag and drop * Fix bug in dragging to empty * Hide dragged dimension when drag starts * Make table metrics required * Update class names * Implement styles on non-droppable items * Replace drag and drop image in docs * Remove extra specificity Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
1 parent 2213ad3 commit 7edf2ce

File tree

16 files changed

+619
-36
lines changed

16 files changed

+619
-36
lines changed

docs/images/lens_drag_drop.gif

-166 KB
Loading

x-pack/plugins/lens/public/datatable_visualization/visualization.test.tsx

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,10 @@ describe('Datatable Visualization', () => {
205205
},
206206
frame,
207207
}).groups
208-
).toHaveLength(1);
208+
).toHaveLength(2);
209209
});
210210

211-
it('allows all kinds of operations', () => {
211+
it('allows only bucket operations one category', () => {
212212
const datasource = createMockDatasource('test');
213213
const frame = mockFrame();
214214
frame.datasourceLayers = { first: datasource.publicAPIMock };
@@ -232,6 +232,40 @@ describe('Datatable Visualization', () => {
232232
expect(filterOperations({ ...baseOperation, dataType: 'boolean' })).toEqual(true);
233233
expect(filterOperations({ ...baseOperation, dataType: 'other' as DataType })).toEqual(true);
234234
expect(filterOperations({ ...baseOperation, dataType: 'date', isBucketed: false })).toEqual(
235+
false
236+
);
237+
expect(filterOperations({ ...baseOperation, dataType: 'number', isBucketed: false })).toEqual(
238+
false
239+
);
240+
});
241+
242+
it('allows only metric operations in one category', () => {
243+
const datasource = createMockDatasource('test');
244+
const frame = mockFrame();
245+
frame.datasourceLayers = { first: datasource.publicAPIMock };
246+
247+
const filterOperations = datatableVisualization.getConfiguration({
248+
layerId: 'first',
249+
state: {
250+
layers: [{ layerId: 'first', columns: [] }],
251+
},
252+
frame,
253+
}).groups[1].filterOperations;
254+
255+
const baseOperation: Operation = {
256+
dataType: 'string',
257+
isBucketed: true,
258+
label: '',
259+
};
260+
expect(filterOperations({ ...baseOperation })).toEqual(false);
261+
expect(filterOperations({ ...baseOperation, dataType: 'number' })).toEqual(false);
262+
expect(filterOperations({ ...baseOperation, dataType: 'date' })).toEqual(false);
263+
expect(filterOperations({ ...baseOperation, dataType: 'boolean' })).toEqual(false);
264+
expect(filterOperations({ ...baseOperation, dataType: 'other' as DataType })).toEqual(false);
265+
expect(filterOperations({ ...baseOperation, dataType: 'date', isBucketed: false })).toEqual(
266+
true
267+
);
268+
expect(filterOperations({ ...baseOperation, dataType: 'number', isBucketed: false })).toEqual(
235269
true
236270
);
237271
});
@@ -248,7 +282,7 @@ describe('Datatable Visualization', () => {
248282
layerId: 'a',
249283
state: { layers: [layer] },
250284
frame,
251-
}).groups[0].accessors
285+
}).groups[1].accessors
252286
).toEqual(['c', 'b']);
253287
});
254288
});

x-pack/plugins/lens/public/datatable_visualization/visualization.tsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,15 +143,29 @@ export const datatableVisualization: Visualization<DatatableVisualizationState>
143143
groups: [
144144
{
145145
groupId: 'columns',
146-
groupLabel: i18n.translate('xpack.lens.datatable.columns', {
147-
defaultMessage: 'Columns',
146+
groupLabel: i18n.translate('xpack.lens.datatable.breakdown', {
147+
defaultMessage: 'Break down by',
148148
}),
149149
layerId: state.layers[0].layerId,
150-
accessors: sortedColumns,
150+
accessors: sortedColumns.filter((c) => datasource.getOperationForColumnId(c)?.isBucketed),
151151
supportsMoreColumns: true,
152-
filterOperations: () => true,
152+
filterOperations: (op) => op.isBucketed,
153153
dataTestSubj: 'lnsDatatable_column',
154154
},
155+
{
156+
groupId: 'metrics',
157+
groupLabel: i18n.translate('xpack.lens.datatable.metrics', {
158+
defaultMessage: 'Metrics',
159+
}),
160+
layerId: state.layers[0].layerId,
161+
accessors: sortedColumns.filter(
162+
(c) => !datasource.getOperationForColumnId(c)?.isBucketed
163+
),
164+
supportsMoreColumns: true,
165+
filterOperations: (op) => !op.isBucketed,
166+
required: true,
167+
dataTestSubj: 'lnsDatatable_metrics',
168+
},
155169
],
156170
};
157171
},

x-pack/plugins/lens/public/drag_drop/__snapshots__/drag_drop.test.tsx.snap

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

x-pack/plugins/lens/public/drag_drop/_drag_drop.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
.lnsDragDrop-isNotDroppable {
2+
opacity: .5;
3+
}
4+
15
// Fix specificity by chaining classes
26

37
.lnsDragDrop.lnsDragDrop-isDropTarget {

x-pack/plugins/lens/public/drag_drop/drag_drop.test.tsx

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ describe('DragDrop', () => {
4949
const value = {};
5050

5151
const component = mount(
52-
<ChildDragDropProvider dragging={undefined} setDragging={setDragging}>
52+
<ChildDragDropProvider dragging={value} setDragging={setDragging}>
5353
<DragDrop value={value} draggable={true} label="drag label">
5454
Hello!
5555
</DragDrop>
@@ -127,4 +127,63 @@ describe('DragDrop', () => {
127127

128128
expect(component).toMatchSnapshot();
129129
});
130+
131+
test('items that have droppable=false get special styling when another item is dragged', () => {
132+
const component = mount(
133+
<ChildDragDropProvider dragging={'ignored'} setDragging={() => {}}>
134+
<DragDrop value="ignored" draggable={true} label="a">
135+
Ignored
136+
</DragDrop>
137+
<DragDrop onDrop={(x: unknown) => {}} droppable={false}>
138+
Hello!
139+
</DragDrop>
140+
</ChildDragDropProvider>
141+
);
142+
143+
expect(component.find('[data-test-subj="lnsDragDrop"]').at(1)).toMatchSnapshot();
144+
});
145+
146+
test('additional styles are reflected in the className until drop', () => {
147+
let dragging: string | undefined;
148+
const getAdditionalClasses = jest.fn().mockReturnValue('additional');
149+
const component = mount(
150+
<ChildDragDropProvider
151+
dragging={dragging}
152+
setDragging={() => {
153+
dragging = 'hello';
154+
}}
155+
>
156+
<DragDrop value="ignored" draggable={true} label="a">
157+
Ignored
158+
</DragDrop>
159+
<DragDrop
160+
onDrop={(x: unknown) => {}}
161+
droppable
162+
getAdditionalClassesOnEnter={getAdditionalClasses}
163+
>
164+
Hello!
165+
</DragDrop>
166+
</ChildDragDropProvider>
167+
);
168+
169+
const dataTransfer = {
170+
setData: jest.fn(),
171+
getData: jest.fn(),
172+
};
173+
component
174+
.find('[data-test-subj="lnsDragDrop"]')
175+
.first()
176+
.simulate('dragstart', { dataTransfer });
177+
jest.runAllTimers();
178+
179+
component.find('[data-test-subj="lnsDragDrop"]').at(1).simulate('dragover');
180+
expect(component.find('.additional')).toHaveLength(1);
181+
182+
component.find('[data-test-subj="lnsDragDrop"]').at(1).simulate('dragleave');
183+
expect(component.find('.additional')).toHaveLength(0);
184+
185+
component.find('[data-test-subj="lnsDragDrop"]').at(1).simulate('dragover');
186+
component.find('[data-test-subj="lnsDragDrop"]').at(1).simulate('drop');
187+
expect(component.find('.additional')).toHaveLength(0);
188+
});
130189
});

x-pack/plugins/lens/public/drag_drop/drag_drop.tsx

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ interface BaseProps {
4949
*/
5050
droppable?: boolean;
5151

52+
/**
53+
* Additional class names to apply when another element is over the drop target
54+
*/
55+
getAdditionalClassesOnEnter?: () => string;
56+
5257
/**
5358
* The optional test subject associated with this DOM element.
5459
*/
@@ -97,6 +102,12 @@ export const DragDrop = (props: Props) => {
97102
{...props}
98103
dragging={droppable ? dragging : undefined}
99104
isDragging={!!(draggable && value === dragging)}
105+
isNotDroppable={
106+
// If the configuration has provided a droppable flag, but this particular item is not
107+
// droppable, then it should be less prominent. Ignores items that are both
108+
// draggable and drop targets
109+
droppable === false && Boolean(dragging) && value !== dragging
110+
}
100111
setDragging={setDragging}
101112
/>
102113
);
@@ -107,9 +118,13 @@ const DragDropInner = React.memo(function DragDropInner(
107118
dragging: unknown;
108119
setDragging: (dragging: unknown) => void;
109120
isDragging: boolean;
121+
isNotDroppable: boolean;
110122
}
111123
) {
112-
const [state, setState] = useState({ isActive: false });
124+
const [state, setState] = useState({
125+
isActive: false,
126+
dragEnterClassNames: '',
127+
});
113128
const {
114129
className,
115130
onDrop,
@@ -120,13 +135,20 @@ const DragDropInner = React.memo(function DragDropInner(
120135
dragging,
121136
setDragging,
122137
isDragging,
138+
isNotDroppable,
123139
} = props;
124140

125-
const classes = classNames('lnsDragDrop', className, {
126-
'lnsDragDrop-isDropTarget': droppable,
127-
'lnsDragDrop-isActiveDropTarget': droppable && state.isActive,
128-
'lnsDragDrop-isDragging': isDragging,
129-
});
141+
const classes = classNames(
142+
'lnsDragDrop',
143+
className,
144+
{
145+
'lnsDragDrop-isDropTarget': droppable,
146+
'lnsDragDrop-isActiveDropTarget': droppable && state.isActive,
147+
'lnsDragDrop-isDragging': isDragging,
148+
'lnsDragDrop-isNotDroppable': isNotDroppable,
149+
},
150+
state.dragEnterClassNames
151+
);
130152

131153
const dragStart = (e: DroppableEvent) => {
132154
// Setting stopPropgagation causes Chrome failures, so
@@ -159,19 +181,25 @@ const DragDropInner = React.memo(function DragDropInner(
159181

160182
// An optimization to prevent a bunch of React churn.
161183
if (!state.isActive) {
162-
setState({ ...state, isActive: true });
184+
setState({
185+
...state,
186+
isActive: true,
187+
dragEnterClassNames: props.getAdditionalClassesOnEnter
188+
? props.getAdditionalClassesOnEnter()
189+
: '',
190+
});
163191
}
164192
};
165193

166194
const dragLeave = () => {
167-
setState({ ...state, isActive: false });
195+
setState({ ...state, isActive: false, dragEnterClassNames: '' });
168196
};
169197

170198
const drop = (e: DroppableEvent) => {
171199
e.preventDefault();
172200
e.stopPropagation();
173201

174-
setState({ ...state, isActive: false });
202+
setState({ ...state, isActive: false, dragEnterClassNames: '' });
175203
setDragging(undefined);
176204

177205
if (onDrop && droppable) {

x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_layer_panel.scss

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@
2727
overflow: hidden;
2828
}
2929

30+
.lnsLayerPanel__dimension-isHidden {
31+
opacity: 0;
32+
}
33+
34+
.lnsLayerPanel__dimension-isReplacing {
35+
text-decoration: line-through;
36+
}
37+
3038
.lnsLayerPanel__triggerLink {
3139
padding: $euiSizeS;
3240
width: 100%;

0 commit comments

Comments
 (0)