From 351990068dca81fa27f1bb03ec30da84fffe6e84 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Wed, 17 Feb 2021 16:48:26 +0100 Subject: [PATCH] [Lens] Drag and drop accessibility messages (#91494) * [Lens] copy dnd * Update x-pack/plugins/lens/public/drag_drop/providers.tsx Co-authored-by: Wylie Conlon Co-authored-by: Wylie Conlon --- .../lens/public/drag_drop/announcements.tsx | 151 ++++++++++++++---- .../lens/public/drag_drop/drag_drop.test.tsx | 50 +++--- .../lens/public/drag_drop/drag_drop.tsx | 2 +- .../lens/public/drag_drop/providers.tsx | 4 +- .../plugins/lens/public/drag_drop/readme.md | 2 +- .../draggable_dimension_button.tsx | 8 +- .../config_panel/empty_dimension_button.tsx | 8 +- .../config_panel/layer_panel.test.tsx | 20 ++- .../public/editor_frame_service/mocks.tsx | 2 +- .../dimension_panel/droppable.test.ts | 30 ++-- .../dimension_panel/droppable.ts | 76 ++++++--- .../indexpattern_datasource/indexpattern.tsx | 4 +- x-pack/plugins/lens/public/types.ts | 4 +- 13 files changed, 249 insertions(+), 112 deletions(-) diff --git a/x-pack/plugins/lens/public/drag_drop/announcements.tsx b/x-pack/plugins/lens/public/drag_drop/announcements.tsx index 61785310bdcf..3c65008f8f38 100644 --- a/x-pack/plugins/lens/public/drag_drop/announcements.tsx +++ b/x-pack/plugins/lens/public/drag_drop/announcements.tsx @@ -11,6 +11,7 @@ export interface HumanData { label: string; groupLabel?: string; position?: number; + nextLabel?: string; } type AnnouncementFunction = (draggedElement: HumanData, dropElement: HumanData) => string; @@ -25,7 +26,7 @@ const selectedTargetReplace = ( { label: dropLabel, groupLabel, position }: HumanData ) => i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.replace', { - defaultMessage: `Selected {dropLabel} in {groupLabel} group at position {position}. Press space or enter to replace {dropLabel} with {label}.`, + defaultMessage: `Replace {dropLabel} in {groupLabel} group at position {position} with {label}. Press space or enter to replace`, values: { label, dropLabel, @@ -34,65 +35,103 @@ const selectedTargetReplace = ( }, }); -const droppedReplace = ({ label }: HumanData, { label: dropLabel, groupLabel }: HumanData) => +const droppedReplace = ( + { label }: HumanData, + { label: dropLabel, groupLabel, position }: HumanData +) => i18n.translate('xpack.lens.dragDrop.announce.duplicated.replace', { - defaultMessage: - 'You have dropped the item. You have replaced {dropLabel} with {label} in {groupLabel} group.', + defaultMessage: 'Replaced {dropLabel} with {label} in {groupLabel} at position {position}', values: { label, dropLabel, groupLabel, + position, }, }); export const announcements: CustomAnnouncementsType = { selectedTarget: { - reorder: ({ label, position: prevPosition }, { position }) => + reorder: ({ label, groupLabel, position: prevPosition }, { position }) => prevPosition === position ? i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.reorderedBack', { - defaultMessage: `You have moved the item {label} back to position {prevPosition}`, + defaultMessage: `{label} returned to its initial position {prevPosition}`, values: { label, prevPosition, }, }) : i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.reordered', { - defaultMessage: `You have moved the item {label} from position {prevPosition} to position {position}`, + defaultMessage: `Reorder {label} in {groupLabel} group from position {prevPosition} to position {position}. Press space or enter to reorder`, values: { + groupLabel, label, position, prevPosition, }, }), - duplicate_in_group: ({ label }, { label: dropLabel, groupLabel, position }) => + duplicate_in_group: ({ label }, { groupLabel, position }) => i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.duplicated', { - defaultMessage: `Selected {dropLabel} in {groupLabel} group at position {position}. Press space or enter to duplicate {label}.`, + defaultMessage: `Duplicate {label} to {groupLabel} group at position {position}. Press space or enter to duplicate`, values: { label, - dropLabel, groupLabel, position, }, }), field_replace: selectedTargetReplace, replace_compatible: selectedTargetReplace, - replace_incompatible: selectedTargetReplace, - }, - dropped: { - reorder: ({ label, position: prevPosition }, { position }) => - i18n.translate('xpack.lens.dragDrop.announce.dropped.reordered', { - defaultMessage: - 'You have dropped the item {label}. You have moved the item from position {prevPosition} to positon {position}', + replace_incompatible: ( + { label }: HumanData, + { label: dropLabel, groupLabel, position, nextLabel }: HumanData + ) => + i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.replaceIncompatible', { + defaultMessage: `Convert {label} to {nextLabel} and replace {dropLabel} in {groupLabel} group at position {position}. Press space or enter to replace`, values: { label, + nextLabel, + dropLabel, + groupLabel, + position, + }, + }), + move_incompatible: ( + { label }: HumanData, + { label: groupLabel, position, nextLabel }: HumanData + ) => + i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.moveIncompatible', { + defaultMessage: `Convert {label} to {nextLabel} and move to {groupLabel} group at position {position}. Press space or enter to move`, + values: { + label, + nextLabel, + groupLabel, + position, + }, + }), + move_compatible: ({ label }: HumanData, { groupLabel, position }: HumanData) => + i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.moveCompatible', { + defaultMessage: `Move {label} to {groupLabel} group at position {position}. Press space or enter to move`, + values: { + label, + groupLabel, + position, + }, + }), + }, + dropped: { + reorder: ({ label, groupLabel, position: prevPosition }, { position }) => + i18n.translate('xpack.lens.dragDrop.announce.dropped.reordered', { + defaultMessage: + 'Reordered {label} in {groupLabel} group from position {prevPosition} to positon {position}', + values: { + label, + groupLabel, position, prevPosition, }, }), duplicate_in_group: ({ label }, { groupLabel, position }) => i18n.translate('xpack.lens.dragDrop.announce.dropped.duplicated', { - defaultMessage: - 'You have dropped the item. You have duplicated {label} in {groupLabel} group at position {position}', + defaultMessage: 'Duplicated {label} in {groupLabel} group at position {position}', values: { label, groupLabel, @@ -101,7 +140,42 @@ export const announcements: CustomAnnouncementsType = { }), field_replace: droppedReplace, replace_compatible: droppedReplace, - replace_incompatible: droppedReplace, + replace_incompatible: ( + { label }: HumanData, + { label: dropLabel, groupLabel, position, nextLabel }: HumanData + ) => + i18n.translate('xpack.lens.dragDrop.announce.dropped.replaceIncompatible', { + defaultMessage: + 'Converted {label} to {nextLabel} and replaced {dropLabel} in {groupLabel} group at position {position}', + values: { + label, + nextLabel, + dropLabel, + groupLabel, + position, + }, + }), + move_incompatible: ({ label }: HumanData, { groupLabel, position, nextLabel }: HumanData) => + i18n.translate('xpack.lens.dragDrop.announce.dropped.moveIncompatible', { + defaultMessage: + 'Converted {label} to {nextLabel} and moved to {groupLabel} group at position {position}', + values: { + label, + nextLabel, + groupLabel, + position, + }, + }), + + move_compatible: ({ label }: HumanData, { groupLabel, position }: HumanData) => + i18n.translate('xpack.lens.dragDrop.announce.dropped.moveCompatible', { + defaultMessage: 'Moved {label} to {groupLabel} group at position {position}', + values: { + label, + groupLabel, + position, + }, + }), }, }; @@ -113,13 +187,29 @@ const defaultAnnouncements = { label, }, }), - cancelled: () => - i18n.translate('xpack.lens.dragDrop.announce.cancelled', { - defaultMessage: 'Movement cancelled', - }), + cancelled: ({ label, groupLabel, position }: HumanData) => { + if (!groupLabel || !position) { + return i18n.translate('xpack.lens.dragDrop.announce.cancelled', { + defaultMessage: 'Movement cancelled. {label} returned to its initial position', + values: { + label, + }, + }); + } + return i18n.translate('xpack.lens.dragDrop.announce.cancelledItem', { + defaultMessage: + 'Movement cancelled. {label} returned to {groupLabel} group at position {position}', + values: { + label, + groupLabel, + position, + }, + }); + }, + noTarget: () => { return i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.noSelected', { - defaultMessage: `No target selected. Use arrow keys to select a target.`, + defaultMessage: `No target selected. Use arrow keys to select a target`, }); }, @@ -129,17 +219,15 @@ const defaultAnnouncements = { ) => dropGroupLabel && position ? i18n.translate('xpack.lens.dragDrop.announce.droppedDefault', { - defaultMessage: - 'You have dropped {label} to {dropLabel} in {dropGroupLabel} group at position {position}', + defaultMessage: 'Added {label} in {dropGroupLabel} group at position {position}', values: { label, dropGroupLabel, position, - dropLabel, }, }) : i18n.translate('xpack.lens.dragDrop.announce.droppedNoPosition', { - defaultMessage: 'You have dropped {label} to {dropLabel}', + defaultMessage: 'Added {label} to {dropLabel}', values: { label, dropLabel, @@ -151,16 +239,15 @@ const defaultAnnouncements = { ) => { return dropGroupLabel && position ? i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.default', { - defaultMessage: `Selected {dropLabel} in {dropGroupLabel} group at position {position}. Press space or enter to drop {label}`, + defaultMessage: `Add {label} to {dropGroupLabel} group at position {position}. Press space or enter to add`, values: { - dropLabel, label, dropGroupLabel, position, }, }) : i18n.translate('xpack.lens.dragDrop.announce.selectedTarget.defaultNoPosition', { - defaultMessage: `Selected {dropLabel}. Press space or enter to drop {label}`, + defaultMessage: `Add {label} to {dropLabel}. Press space or enter to add`, values: { dropLabel, label, diff --git a/x-pack/plugins/lens/public/drag_drop/drag_drop.test.tsx b/x-pack/plugins/lens/public/drag_drop/drag_drop.test.tsx index f9bae547a563..f2a2fda73038 100644 --- a/x-pack/plugins/lens/public/drag_drop/drag_drop.test.tsx +++ b/x-pack/plugins/lens/public/drag_drop/drag_drop.test.tsx @@ -110,7 +110,7 @@ describe('DragDrop', () => { const component = mount( @@ -126,7 +126,7 @@ describe('DragDrop', () => { expect(preventDefault).toBeCalled(); expect(stopPropagation).toBeCalled(); expect(setDragging).toBeCalledWith(undefined); - expect(onDrop).toBeCalledWith({ id: '2', humanData: { label: 'label1' } }, 'field_add'); + expect(onDrop).toBeCalledWith({ id: '2', humanData: { label: 'Label1' } }, 'field_add'); }); test('drop function is not called on dropType undefined', async () => { @@ -138,7 +138,7 @@ describe('DragDrop', () => { const component = mount( @@ -195,7 +195,7 @@ describe('DragDrop', () => { }); test('additional styles are reflected in the className until drop', () => { - let dragging: { id: '1'; humanData: { label: 'label1' } } | undefined; + let dragging: { id: '1'; humanData: { label: 'Label1' } } | undefined; const getAdditionalClassesOnEnter = jest.fn().mockReturnValue('additional'); const getAdditionalClassesOnDroppable = jest.fn().mockReturnValue('droppable'); const setA11yMessage = jest.fn(); @@ -206,7 +206,7 @@ describe('DragDrop', () => { dragging={dragging} setA11yMessage={setA11yMessage} setDragging={() => { - dragging = { id: '1', humanData: { label: 'label1' } }; + dragging = { id: '1', humanData: { label: 'Label1' } }; }} > { }); test('additional enter styles are reflected in the className until dragleave', () => { - let dragging: { id: '1'; humanData: { label: 'label1' } } | undefined; + let dragging: { id: '1'; humanData: { label: 'Label1' } } | undefined; const getAdditionalClasses = jest.fn().mockReturnValue('additional'); const getAdditionalClassesOnDroppable = jest.fn().mockReturnValue('droppable'); const setActiveDropTarget = jest.fn(); @@ -252,7 +252,7 @@ describe('DragDrop', () => { setA11yMessage={jest.fn()} dragging={dragging} setDragging={() => { - dragging = { id: '1', humanData: { label: 'label1' } }; + dragging = { id: '1', humanData: { label: 'Label1' } }; }} setActiveDropTarget={setActiveDropTarget} activeDropTarget={ @@ -303,7 +303,7 @@ describe('DragDrop', () => { draggable: true, value: { id: '1', - humanData: { label: 'label1', position: 1 }, + humanData: { label: 'Label1', position: 1 }, }, children: '1', order: [2, 0, 0, 0], @@ -326,7 +326,7 @@ describe('DragDrop', () => { dragType: 'move' as 'copy' | 'move', value: { id: '3', - humanData: { label: 'label3', position: 1 }, + humanData: { label: 'label3', position: 1, groupLabel: 'Y' }, }, onDrop, dropType: 'replace_compatible' as DropType, @@ -337,7 +337,7 @@ describe('DragDrop', () => { dragType: 'move' as 'copy' | 'move', value: { id: '4', - humanData: { label: 'label4', position: 2 }, + humanData: { label: 'label4', position: 2, groupLabel: 'Y' }, }, order: [2, 0, 2, 1], }, @@ -380,11 +380,11 @@ describe('DragDrop', () => { }); keyboardHandler.simulate('keydown', { key: 'Enter' }); expect(setA11yMessage).toBeCalledWith( - 'Selected label3 in group at position 1. Press space or enter to replace label3 with label1.' + 'Replace label3 in Y group at position 1 with Label1. Press space or enter to replace' ); expect(setActiveDropTarget).toBeCalledWith(undefined); expect(onDrop).toBeCalledWith( - { humanData: { label: 'label1', position: 1 }, id: '1' }, + { humanData: { label: 'Label1', position: 1 }, id: '1' }, 'move_compatible' ); }); @@ -437,7 +437,7 @@ describe('DragDrop', () => { draggable: true, value: { id: '1', - humanData: { label: 'label1', position: 1 }, + humanData: { label: 'Label1', position: 1 }, }, children: '1', order: [2, 0, 0, 0], @@ -488,19 +488,19 @@ describe('DragDrop', () => { const items = [ { id: '1', - humanData: { label: 'label1', position: 1 }, + humanData: { label: 'Label1', position: 1, groupLabel: 'X' }, onDrop, dropType: 'reorder' as DropType, }, { id: '2', - humanData: { label: 'label2', position: 2 }, + humanData: { label: 'label2', position: 2, groupLabel: 'X' }, onDrop, dropType: 'reorder' as DropType, }, { id: '3', - humanData: { label: 'label3', position: 3 }, + humanData: { label: 'label3', position: 3, groupLabel: 'X' }, onDrop, dropType: 'reorder' as DropType, }, @@ -583,7 +583,7 @@ describe('DragDrop', () => { }); expect(setDragging).toBeCalledWith({ ...items[0] }); - expect(setA11yMessage).toBeCalledWith('Lifted label1'); + expect(setA11yMessage).toBeCalledWith('Lifted Label1'); expect( component .find('[data-test-subj="lnsDragDrop-reorderableGroup"]') @@ -652,7 +652,7 @@ describe('DragDrop', () => { jest.runAllTimers(); expect(setA11yMessage).toBeCalledWith( - 'You have dropped the item label1. You have moved the item from position 1 to positon 3' + 'Reordered Label1 in X group from position 1 to positon 3' ); expect(preventDefault).toBeCalled(); expect(stopPropagation).toBeCalled(); @@ -687,7 +687,7 @@ describe('DragDrop', () => { expect(setActiveDropTarget).toBeCalledWith(items[1]); expect(setA11yMessage).toBeCalledWith( - 'You have moved the item label1 from position 1 to position 2' + 'Reorder Label1 in X group from position 1 to position 2. Press space or enter to reorder' ); }); test(`Keyboard navigation: user can drop element to an activeDropTarget`, () => { @@ -729,13 +729,17 @@ describe('DragDrop', () => { jest.runAllTimers(); expect(onDropHandler).not.toHaveBeenCalled(); - expect(setA11yMessage).toBeCalledWith('Movement cancelled'); + expect(setA11yMessage).toBeCalledWith( + 'Movement cancelled. Label1 returned to X group at position 1' + ); keyboardHandler.simulate('keydown', { key: 'Space' }); keyboardHandler.simulate('keydown', { key: 'ArrowDown' }); keyboardHandler.simulate('blur'); expect(onDropHandler).not.toHaveBeenCalled(); - expect(setA11yMessage).toBeCalledWith('Movement cancelled'); + expect(setA11yMessage).toBeCalledWith( + 'Movement cancelled. Label1 returned to X group at position 1' + ); }); test(`Keyboard Navigation: Reordered elements get extra styles to show the reorder effect`, () => { @@ -772,7 +776,7 @@ describe('DragDrop', () => { component.find('[data-test-subj="lnsDragDrop-translatableDrop"]').at(1).prop('style') ).toEqual(undefined); expect(setA11yMessage).toBeCalledWith( - 'You have moved the item label1 from position 1 to position 2' + 'Reorder Label1 in X group from position 1 to position 2. Press space or enter to reorder' ); component @@ -837,7 +841,7 @@ describe('DragDrop', () => { keyboardHandler.simulate('keydown', { key: 'Space' }); keyboardHandler.simulate('keydown', { key: 'ArrowUp' }); expect(setActiveDropTarget).toBeCalledWith(undefined); - expect(setA11yMessage).toBeCalledWith('You have moved the item label1 back to position 1'); + expect(setA11yMessage).toBeCalledWith('Label1 returned to its initial position 1'); }); }); }); diff --git a/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx b/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx index 4b2506432032..6c6a65ab421b 100644 --- a/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx +++ b/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx @@ -267,7 +267,7 @@ const DragInner = memo(function DragInner({ setDragging(undefined); setActiveDropTarget(undefined); setKeyboardMode(false); - setA11yMessage(announce.cancelled()); + setA11yMessage(announce.cancelled(value.humanData)); if (onDragEnd) { onDragEnd(); } diff --git a/x-pack/plugins/lens/public/drag_drop/providers.tsx b/x-pack/plugins/lens/public/drag_drop/providers.tsx index 1a056902a474..deb9bf6cb17a 100644 --- a/x-pack/plugins/lens/public/drag_drop/providers.tsx +++ b/x-pack/plugins/lens/public/drag_drop/providers.tsx @@ -204,12 +204,12 @@ export function RootDragDropProvider({ children }: { children: React.ReactNode }

{i18n.translate('xpack.lens.dragDrop.keyboardInstructionsReorder', { - defaultMessage: `Press enter or space to dragging. When dragging, use the up/down arrow keys to reorder items in the group and left/right arrow keys to choose drop targets outside of the group. Press enter or space again to finish.`, + defaultMessage: `Press space or enter to start dragging. When dragging, use the up/down arrow keys to reorder items in the group and left/right arrow keys to choose drop targets outside of the group. Press space or enter again to finish.`, })}

{i18n.translate('xpack.lens.dragDrop.keyboardInstructions', { - defaultMessage: `Press enter or space to start dragging. When dragging, use the left/right arrow keys to move between drop targets. Press enter or space again to finish.`, + defaultMessage: `Press space or enter to start dragging. When dragging, use the left/right arrow keys to move between drop targets. Press space or enter again to finish.`, })}

diff --git a/x-pack/plugins/lens/public/drag_drop/readme.md b/x-pack/plugins/lens/public/drag_drop/readme.md index 55a9e3157c24..01cc4c7bc85a 100644 --- a/x-pack/plugins/lens/public/drag_drop/readme.md +++ b/x-pack/plugins/lens/public/drag_drop/readme.md @@ -56,7 +56,7 @@ const { dragging } = useContext(DragContext); return ( onChange([...items, item])} > {items.map((x) => ( diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/draggable_dimension_button.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/draggable_dimension_button.tsx index f493000aa587..1cbd41fff2a8 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/draggable_dimension_button.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/draggable_dimension_button.tsx @@ -64,13 +64,16 @@ export function DraggableDimensionButton({ columnId: string; registerNewButtonRef: (id: string, instance: HTMLDivElement | null) => void; }) { - const dropType = layerDatasource.getDropTypes({ + const dropProps = layerDatasource.getDropProps({ ...layerDatasourceDropProps, columnId, filterOperations: group.filterOperations, groupId: group.groupId, }); + const dropType = dropProps?.dropType; + const nextLabel = dropProps?.nextLabel; + const value = useMemo( () => ({ columnId, @@ -82,9 +85,10 @@ export function DraggableDimensionButton({ label, groupLabel: group.groupLabel, position: accessorIndex + 1, + nextLabel: nextLabel || '', }, }), - [columnId, group.groupId, accessorIndex, layerId, dropType, label, group.groupLabel] + [columnId, group.groupId, accessorIndex, layerId, dropType, label, group.groupLabel, nextLabel] ); // todo: simplify by id and use drop targets? diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/empty_dimension_button.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/empty_dimension_button.tsx index a83d4bde0383..c9d0a7b00287 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/empty_dimension_button.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/empty_dimension_button.tsx @@ -54,13 +54,16 @@ export function EmptyDimensionButton({ setNewColumnId(generateId()); }, [itemIndex]); - const dropType = layerDatasource.getDropTypes({ + const dropProps = layerDatasource.getDropProps({ ...layerDatasourceDropProps, columnId: newColumnId, filterOperations: group.filterOperations, groupId: group.groupId, }); + const dropType = dropProps?.dropType; + const nextLabel = dropProps?.nextLabel; + const value = useMemo( () => ({ columnId: newColumnId, @@ -72,9 +75,10 @@ export function EmptyDimensionButton({ label, groupLabel: group.groupLabel, position: itemIndex + 1, + nextLabel: nextLabel || '', }, }), - [dropType, newColumnId, group.groupId, layerId, group.groupLabel, itemIndex] + [dropType, newColumnId, group.groupId, layerId, group.groupLabel, itemIndex, nextLabel] ); return ( diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx index 1f97399fdd29..619147987cdd 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx @@ -440,7 +440,10 @@ describe('LayerPanel', () => { ], }); - mockDatasource.getDropTypes.mockReturnValue('field_add'); + mockDatasource.getDropProps.mockReturnValue({ + dropType: 'field_add', + nextLabel: '', + }); const draggingField = { field: { name: 'dragged' }, @@ -459,7 +462,7 @@ describe('LayerPanel', () => {
); - expect(mockDatasource.getDropTypes).toHaveBeenCalledWith( + expect(mockDatasource.getDropProps).toHaveBeenCalledWith( expect.objectContaining({ dragDropContext: expect.objectContaining({ dragging: draggingField, @@ -492,8 +495,8 @@ describe('LayerPanel', () => { ], }); - mockDatasource.getDropTypes.mockImplementation(({ columnId }) => - columnId !== 'a' ? 'field_replace' : undefined + mockDatasource.getDropProps.mockImplementation(({ columnId }) => + columnId !== 'a' ? { dropType: 'field_replace', nextLabel: '' } : undefined ); const draggingField = { @@ -513,7 +516,7 @@ describe('LayerPanel', () => {
); - expect(mockDatasource.getDropTypes).toHaveBeenCalledWith( + expect(mockDatasource.getDropProps).toHaveBeenCalledWith( expect.objectContaining({ columnId: 'a' }) ); @@ -554,7 +557,10 @@ describe('LayerPanel', () => { ], }); - mockDatasource.getDropTypes.mockReturnValue('replace_compatible'); + mockDatasource.getDropProps.mockReturnValue({ + dropType: 'replace_compatible', + nextLabel: '', + }); const draggingOperation = { layerId: 'first', @@ -574,7 +580,7 @@ describe('LayerPanel', () => { ); - expect(mockDatasource.getDropTypes).toHaveBeenCalledWith( + expect(mockDatasource.getDropProps).toHaveBeenCalledWith( expect.objectContaining({ dragDropContext: expect.objectContaining({ dragging: draggingOperation, diff --git a/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx b/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx index 36c2d7128460..db3b29bb74d3 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx @@ -88,7 +88,7 @@ export function createMockDatasource(id: string): DatasourceMock { uniqueLabels: jest.fn((_state) => ({})), renderDimensionTrigger: jest.fn(), renderDimensionEditor: jest.fn(), - getDropTypes: jest.fn(), + getDropProps: jest.fn(), onDrop: jest.fn(), // this is an additional property which doesn't exist on real datasources diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts index 1f0381d92ce6..17f069b8831e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.test.ts @@ -6,7 +6,7 @@ */ import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public'; import { IndexPatternDimensionEditorProps } from './dimension_panel'; -import { onDrop, getDropTypes } from './droppable'; +import { onDrop, getDropProps } from './droppable'; import { DragContextState } from '../../drag_drop'; import { createMockedDragDropContext } from '../mocks'; import { IUiSettingsClient, SavedObjectsClientContract, HttpSetup, CoreSetup } from 'kibana/public'; @@ -91,7 +91,7 @@ const draggingField = { * - Dimension trigger: Not tested here * - Dimension editor component: First half of the tests * - * - getDropTypes: Returns drop types that are possible for the current dragging field or other dimension + * - getDropProps: Returns drop types that are possible for the current dragging field or other dimension * - onDrop: Correct application of drop logic */ describe('IndexPatternDimensionEditorPanel', () => { @@ -174,14 +174,14 @@ describe('IndexPatternDimensionEditorPanel', () => { }); const groupId = 'a'; - describe('getDropTypes', () => { + describe('getDropProps', () => { it('returns undefined if no drag is happening', () => { - expect(getDropTypes({ ...defaultProps, groupId, dragDropContext })).toBe(undefined); + expect(getDropProps({ ...defaultProps, groupId, dragDropContext })).toBe(undefined); }); it('returns undefined if the dragged item has no field', () => { expect( - getDropTypes({ + getDropProps({ ...defaultProps, groupId, dragDropContext: { @@ -198,7 +198,7 @@ describe('IndexPatternDimensionEditorPanel', () => { it('returns undefined if field is not supported by filterOperations', () => { expect( - getDropTypes({ + getDropProps({ ...defaultProps, groupId, dragDropContext: { @@ -217,7 +217,7 @@ describe('IndexPatternDimensionEditorPanel', () => { it('returns remove_add if the field is supported by filterOperations and the dropTarget is an existing column', () => { expect( - getDropTypes({ + getDropProps({ ...defaultProps, groupId, dragDropContext: { @@ -226,12 +226,12 @@ describe('IndexPatternDimensionEditorPanel', () => { }, filterOperations: (op: OperationMetadata) => op.dataType === 'number', }) - ).toBe('field_replace'); + ).toEqual({ dropType: 'field_replace', nextLabel: 'Intervals' }); }); it('returns undefined if the field belongs to another index pattern', () => { expect( - getDropTypes({ + getDropProps({ ...defaultProps, groupId, dragDropContext: { @@ -250,7 +250,7 @@ describe('IndexPatternDimensionEditorPanel', () => { it('returns undefined if the dragged field is already in use by this operation', () => { expect( - getDropTypes({ + getDropProps({ ...defaultProps, groupId, dragDropContext: { @@ -275,7 +275,7 @@ describe('IndexPatternDimensionEditorPanel', () => { it('returns move if the dragged column is compatible', () => { expect( - getDropTypes({ + getDropProps({ ...defaultProps, groupId, dragDropContext: { @@ -290,7 +290,7 @@ describe('IndexPatternDimensionEditorPanel', () => { }, columnId: 'col2', }) - ).toBe('move_compatible'); + ).toEqual({ dropType: 'move_compatible' }); }); it('returns undefined if the dragged column from different group uses the same field as the dropTarget', () => { @@ -318,7 +318,7 @@ describe('IndexPatternDimensionEditorPanel', () => { }; expect( - getDropTypes({ + getDropProps({ ...defaultProps, groupId, dragDropContext: { @@ -357,7 +357,7 @@ describe('IndexPatternDimensionEditorPanel', () => { }; expect( - getDropTypes({ + getDropProps({ ...defaultProps, groupId, dragDropContext: { @@ -373,7 +373,7 @@ describe('IndexPatternDimensionEditorPanel', () => { columnId: 'col2', filterOperations: (op: OperationMetadata) => op.isBucketed === false, }) - ).toEqual('replace_incompatible'); + ).toEqual({ dropType: 'replace_incompatible', nextLabel: 'Unique count' }); }); }); describe('onDrop', () => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.ts index 69c7e8c3c2ae..be791b3c7f7c 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable.ts @@ -10,21 +10,29 @@ import { DatasourceDimensionDropHandlerProps, isDraggedOperation, DraggedOperation, + DropType, } from '../../types'; import { IndexPatternColumn } from '../indexpattern'; -import { insertOrReplaceColumn, deleteColumn, getOperationTypesForField } from '../operations'; +import { + insertOrReplaceColumn, + deleteColumn, + getOperationTypesForField, + getOperationDisplay, +} from '../operations'; import { mergeLayer } from '../state_helpers'; import { hasField, isDraggedField } from '../utils'; -import { IndexPatternPrivateState, IndexPatternField, DraggedField } from '../types'; +import { IndexPatternPrivateState, DraggedField } from '../types'; import { trackUiEvent } from '../../lens_ui_telemetry'; type DropHandlerProps = DatasourceDimensionDropHandlerProps & { droppedItem: T; }; -export function getDropTypes( +const operationLabels = getOperationDisplay(); + +export function getDropProps( props: DatasourceDimensionDropProps & { groupId: string } -) { +): { dropType: DropType; nextLabel?: string } | undefined { const { dragging } = props.dragDropContext; if (!dragging) { return; @@ -32,23 +40,27 @@ export function getDropTypes( const layerIndexPatternId = props.state.layers[props.layerId].indexPatternId; - function hasOperationForField(field: IndexPatternField) { - const operationsForNewField = getOperationTypesForField(field, props.filterOperations); - return !!operationsForNewField.length; - } - const currentColumn = props.state.layers[props.layerId].columns[props.columnId]; if (isDraggedField(dragging)) { - if ( - !!(layerIndexPatternId === dragging.indexPatternId && hasOperationForField(dragging.field)) - ) { + const operationsForNewField = getOperationTypesForField(dragging.field, props.filterOperations); + + if (!!(layerIndexPatternId === dragging.indexPatternId && operationsForNewField.length)) { + const highestPriorityOperationLabel = operationLabels[operationsForNewField[0]].displayName; if (!currentColumn) { - return 'field_add'; + return { dropType: 'field_add', nextLabel: highestPriorityOperationLabel }; } else if ( (hasField(currentColumn) && currentColumn.sourceField !== dragging.field.name) || !hasField(currentColumn) ) { - return 'field_replace'; + const persistingOperationLabel = + currentColumn && + operationsForNewField.includes(currentColumn.operationType) && + operationLabels[currentColumn.operationType].displayName; + + return { + dropType: 'field_replace', + nextLabel: persistingOperationLabel || highestPriorityOperationLabel, + }; } } return; @@ -62,9 +74,9 @@ export function getDropTypes( // same group if (props.groupId === dragging.groupId) { if (currentColumn) { - return 'reorder'; + return { dropType: 'reorder' }; } - return 'duplicate_in_group'; + return { dropType: 'duplicate_in_group' }; } // compatible group @@ -80,20 +92,34 @@ export function getDropTypes( } if (props.filterOperations(op)) { if (currentColumn) { - return 'replace_compatible'; // in the future also 'swap_compatible' and 'duplicate_compatible' + return { dropType: 'replace_compatible' }; // in the future also 'swap_compatible' and 'duplicate_compatible' } else { - return 'move_compatible'; // in the future also 'duplicate_compatible' + return { dropType: 'move_compatible' }; // in the future also 'duplicate_compatible' } } // suggest const field = hasField(op) && props.state.indexPatterns[layerIndexPatternId].getFieldByName(op.sourceField); - if (field && hasOperationForField(field)) { + const operationsForNewField = field && getOperationTypesForField(field, props.filterOperations); + + if (operationsForNewField && operationsForNewField?.length) { + const highestPriorityOperationLabel = operationLabels[operationsForNewField[0]].displayName; + if (currentColumn) { - return 'replace_incompatible'; // in the future also 'swap_incompatible', 'duplicate_incompatible' + const persistingOperationLabel = + currentColumn && + operationsForNewField.includes(currentColumn.operationType) && + operationLabels[currentColumn.operationType].displayName; + return { + dropType: 'replace_incompatible', + nextLabel: persistingOperationLabel || highestPriorityOperationLabel, + }; // in the future also 'swap_incompatible', 'duplicate_incompatible' } else { - return 'move_incompatible'; // in the future also 'duplicate_incompatible' + return { + dropType: 'move_incompatible', + nextLabel: highestPriorityOperationLabel, + }; // in the future also 'duplicate_incompatible' } } } @@ -178,6 +204,12 @@ function onMoveDropToNonCompatibleGroup(props: DropHandlerProps { renderDimensionTrigger: (domElement: Element, props: DatasourceDimensionTriggerProps) => void; renderDimensionEditor: (domElement: Element, props: DatasourceDimensionEditorProps) => void; renderLayerPanel: (domElement: Element, props: DatasourceLayerPanelProps) => void; - getDropTypes: ( + getDropProps: ( props: DatasourceDimensionDropProps & { groupId: string } - ) => DropType | undefined; + ) => { dropType: DropType; nextLabel?: string } | undefined; onDrop: (props: DatasourceDimensionDropHandlerProps) => false | true | { deleted: string }; updateStateOnCloseDimension?: (props: { layerId: string;