Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}

Copy link
Contributor

@mbondyra mbondyra Aug 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid the effect of 'x' button being slower than the rest when dragging, you could add a custom className and overwrite transition: all 250ms ease-in setting coming from EUI, eg: transition: all 250ms ease-out, visibility 0ms;
I found a better solution - can you instead animate opacity:0 and not visibility on .lnsLayerPanel__dimension-hidden? Opacity is more performant and solves the problem with the button.

Here's what I mean:

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