Skip to content

Commit

Permalink
[Lens] Drag dimension to replace (#75895)
Browse files Browse the repository at this point in the history
* [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>
  • Loading branch information
Wylie Conlon and elasticmachine authored Sep 4, 2020
1 parent 2eb8dac commit 9ca8551
Show file tree
Hide file tree
Showing 16 changed files with 619 additions and 36 deletions.
Binary file modified docs/visualize/images/lens_drag_drop.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -205,10 +205,10 @@ describe('Datatable Visualization', () => {
},
frame,
}).groups
).toHaveLength(1);
).toHaveLength(2);
});

it('allows all kinds of operations', () => {
it('allows only bucket operations one category', () => {
const datasource = createMockDatasource('test');
const frame = mockFrame();
frame.datasourceLayers = { first: datasource.publicAPIMock };
Expand All @@ -232,6 +232,40 @@ describe('Datatable Visualization', () => {
expect(filterOperations({ ...baseOperation, dataType: 'boolean' })).toEqual(true);
expect(filterOperations({ ...baseOperation, dataType: 'other' as DataType })).toEqual(true);
expect(filterOperations({ ...baseOperation, dataType: 'date', isBucketed: false })).toEqual(
false
);
expect(filterOperations({ ...baseOperation, dataType: 'number', isBucketed: false })).toEqual(
false
);
});

it('allows only metric operations in one category', () => {
const datasource = createMockDatasource('test');
const frame = mockFrame();
frame.datasourceLayers = { first: datasource.publicAPIMock };

const filterOperations = datatableVisualization.getConfiguration({
layerId: 'first',
state: {
layers: [{ layerId: 'first', columns: [] }],
},
frame,
}).groups[1].filterOperations;

const baseOperation: Operation = {
dataType: 'string',
isBucketed: true,
label: '',
};
expect(filterOperations({ ...baseOperation })).toEqual(false);
expect(filterOperations({ ...baseOperation, dataType: 'number' })).toEqual(false);
expect(filterOperations({ ...baseOperation, dataType: 'date' })).toEqual(false);
expect(filterOperations({ ...baseOperation, dataType: 'boolean' })).toEqual(false);
expect(filterOperations({ ...baseOperation, dataType: 'other' as DataType })).toEqual(false);
expect(filterOperations({ ...baseOperation, dataType: 'date', isBucketed: false })).toEqual(
true
);
expect(filterOperations({ ...baseOperation, dataType: 'number', isBucketed: false })).toEqual(
true
);
});
Expand All @@ -248,7 +282,7 @@ describe('Datatable Visualization', () => {
layerId: 'a',
state: { layers: [layer] },
frame,
}).groups[0].accessors
}).groups[1].accessors
).toEqual(['c', 'b']);
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,15 +143,29 @@ export const datatableVisualization: Visualization<DatatableVisualizationState>
groups: [
{
groupId: 'columns',
groupLabel: i18n.translate('xpack.lens.datatable.columns', {
defaultMessage: 'Columns',
groupLabel: i18n.translate('xpack.lens.datatable.breakdown', {
defaultMessage: 'Break down by',
}),
layerId: state.layers[0].layerId,
accessors: sortedColumns,
accessors: sortedColumns.filter((c) => datasource.getOperationForColumnId(c)?.isBucketed),
supportsMoreColumns: true,
filterOperations: () => true,
filterOperations: (op) => op.isBucketed,
dataTestSubj: 'lnsDatatable_column',
},
{
groupId: 'metrics',
groupLabel: i18n.translate('xpack.lens.datatable.metrics', {
defaultMessage: 'Metrics',
}),
layerId: state.layers[0].layerId,
accessors: sortedColumns.filter(
(c) => !datasource.getOperationForColumnId(c)?.isBucketed
),
supportsMoreColumns: true,
filterOperations: (op) => !op.isBucketed,
required: true,
dataTestSubj: 'lnsDatatable_metrics',
},
],
};
},
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions x-pack/plugins/lens/public/drag_drop/_drag_drop.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
.lnsDragDrop-isNotDroppable {
opacity: .5;
}

// Fix specificity by chaining classes

.lnsDragDrop.lnsDragDrop-isDropTarget {
Expand Down
61 changes: 60 additions & 1 deletion x-pack/plugins/lens/public/drag_drop/drag_drop.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ describe('DragDrop', () => {
const value = {};

const component = mount(
<ChildDragDropProvider dragging={undefined} setDragging={setDragging}>
<ChildDragDropProvider dragging={value} setDragging={setDragging}>
<DragDrop value={value} draggable={true} label="drag label">
Hello!
</DragDrop>
Expand Down Expand Up @@ -127,4 +127,63 @@ describe('DragDrop', () => {

expect(component).toMatchSnapshot();
});

test('items that have droppable=false get special styling when another item is dragged', () => {
const component = mount(
<ChildDragDropProvider dragging={'ignored'} setDragging={() => {}}>
<DragDrop value="ignored" draggable={true} label="a">
Ignored
</DragDrop>
<DragDrop onDrop={(x: unknown) => {}} droppable={false}>
Hello!
</DragDrop>
</ChildDragDropProvider>
);

expect(component.find('[data-test-subj="lnsDragDrop"]').at(1)).toMatchSnapshot();
});

test('additional styles are reflected in the className until drop', () => {
let dragging: string | undefined;
const getAdditionalClasses = jest.fn().mockReturnValue('additional');
const component = mount(
<ChildDragDropProvider
dragging={dragging}
setDragging={() => {
dragging = 'hello';
}}
>
<DragDrop value="ignored" draggable={true} label="a">
Ignored
</DragDrop>
<DragDrop
onDrop={(x: unknown) => {}}
droppable
getAdditionalClassesOnEnter={getAdditionalClasses}
>
Hello!
</DragDrop>
</ChildDragDropProvider>
);

const dataTransfer = {
setData: jest.fn(),
getData: jest.fn(),
};
component
.find('[data-test-subj="lnsDragDrop"]')
.first()
.simulate('dragstart', { dataTransfer });
jest.runAllTimers();

component.find('[data-test-subj="lnsDragDrop"]').at(1).simulate('dragover');
expect(component.find('.additional')).toHaveLength(1);

component.find('[data-test-subj="lnsDragDrop"]').at(1).simulate('dragleave');
expect(component.find('.additional')).toHaveLength(0);

component.find('[data-test-subj="lnsDragDrop"]').at(1).simulate('dragover');
component.find('[data-test-subj="lnsDragDrop"]').at(1).simulate('drop');
expect(component.find('.additional')).toHaveLength(0);
});
});
46 changes: 37 additions & 9 deletions x-pack/plugins/lens/public/drag_drop/drag_drop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ interface BaseProps {
*/
droppable?: boolean;

/**
* Additional class names to apply when another element is over the drop target
*/
getAdditionalClassesOnEnter?: () => string;

/**
* The optional test subject associated with this DOM element.
*/
Expand Down Expand Up @@ -97,6 +102,12 @@ export const DragDrop = (props: Props) => {
{...props}
dragging={droppable ? dragging : undefined}
isDragging={!!(draggable && value === dragging)}
isNotDroppable={
// If the configuration has provided a droppable flag, but this particular item is not
// droppable, then it should be less prominent. Ignores items that are both
// draggable and drop targets
droppable === false && Boolean(dragging) && value !== dragging
}
setDragging={setDragging}
/>
);
Expand All @@ -107,9 +118,13 @@ const DragDropInner = React.memo(function DragDropInner(
dragging: unknown;
setDragging: (dragging: unknown) => void;
isDragging: boolean;
isNotDroppable: boolean;
}
) {
const [state, setState] = useState({ isActive: false });
const [state, setState] = useState({
isActive: false,
dragEnterClassNames: '',
});
const {
className,
onDrop,
Expand All @@ -120,13 +135,20 @@ const DragDropInner = React.memo(function DragDropInner(
dragging,
setDragging,
isDragging,
isNotDroppable,
} = props;

const classes = classNames('lnsDragDrop', className, {
'lnsDragDrop-isDropTarget': droppable,
'lnsDragDrop-isActiveDropTarget': droppable && state.isActive,
'lnsDragDrop-isDragging': isDragging,
});
const classes = classNames(
'lnsDragDrop',
className,
{
'lnsDragDrop-isDropTarget': droppable,
'lnsDragDrop-isActiveDropTarget': droppable && state.isActive,
'lnsDragDrop-isDragging': isDragging,
'lnsDragDrop-isNotDroppable': isNotDroppable,
},
state.dragEnterClassNames
);

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

// An optimization to prevent a bunch of React churn.
if (!state.isActive) {
setState({ ...state, isActive: true });
setState({
...state,
isActive: true,
dragEnterClassNames: props.getAdditionalClassesOnEnter
? props.getAdditionalClassesOnEnter()
: '',
});
}
};

const dragLeave = () => {
setState({ ...state, isActive: false });
setState({ ...state, isActive: false, dragEnterClassNames: '' });
};

const drop = (e: DroppableEvent) => {
e.preventDefault();
e.stopPropagation();

setState({ ...state, isActive: false });
setState({ ...state, isActive: false, dragEnterClassNames: '' });
setDragging(undefined);

if (onDrop && droppable) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@
overflow: hidden;
}

.lnsLayerPanel__dimension-isHidden {
opacity: 0;
}

.lnsLayerPanel__dimension-isReplacing {
text-decoration: line-through;
}

.lnsLayerPanel__triggerLink {
padding: $euiSizeS;
width: 100%;
Expand Down
Loading

0 comments on commit 9ca8551

Please sign in to comment.