[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 <wylieconlon@gmail.com> Co-authored-by: Wylie Conlon <wylieconlon@gmail.com>
This commit is contained in:
parent
31cbfbb8d5
commit
351990068d
|
@ -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,
|
||||
|
|
|
@ -110,7 +110,7 @@ describe('DragDrop', () => {
|
|||
const component = mount(
|
||||
<ChildDragDropProvider
|
||||
{...defaultContext}
|
||||
dragging={{ id: '2', humanData: { label: 'label1' } }}
|
||||
dragging={{ id: '2', humanData: { label: 'Label1' } }}
|
||||
setDragging={setDragging}
|
||||
>
|
||||
<DragDrop onDrop={onDrop} dropType="field_add" value={value} order={[2, 0, 1, 0]}>
|
||||
|
@ -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(
|
||||
<ChildDragDropProvider
|
||||
{...defaultContext}
|
||||
dragging={{ id: 'hi', humanData: { label: 'label1' } }}
|
||||
dragging={{ id: 'hi', humanData: { label: 'Label1' } }}
|
||||
setDragging={setDragging}
|
||||
>
|
||||
<DragDrop onDrop={onDrop} dropType={undefined} value={value} order={[2, 0, 1, 0]}>
|
||||
|
@ -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' } };
|
||||
}}
|
||||
>
|
||||
<DragDrop
|
||||
|
@ -242,7 +242,7 @@ describe('DragDrop', () => {
|
|||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -204,12 +204,12 @@ export function RootDragDropProvider({ children }: { children: React.ReactNode }
|
|||
</p>
|
||||
<p id={`lnsDragDrop-keyboardInstructionsWithReorder`}>
|
||||
{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.`,
|
||||
})}
|
||||
</p>
|
||||
<p id={`lnsDragDrop-keyboardInstructions`}>
|
||||
{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.`,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -56,7 +56,7 @@ const { dragging } = useContext(DragContext);
|
|||
return (
|
||||
<DragDrop
|
||||
className="axis"
|
||||
dropType={getDropTypes(dragging)}
|
||||
dropType={getDropProps(dragging)}
|
||||
onDrop={(item) => onChange([...items, item])}
|
||||
>
|
||||
{items.map((x) => (
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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', () => {
|
|||
</ChildDragDropProvider>
|
||||
);
|
||||
|
||||
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', () => {
|
|||
</ChildDragDropProvider>
|
||||
);
|
||||
|
||||
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', () => {
|
|||
</ChildDragDropProvider>
|
||||
);
|
||||
|
||||
expect(mockDatasource.getDropTypes).toHaveBeenCalledWith(
|
||||
expect(mockDatasource.getDropProps).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
dragDropContext: expect.objectContaining({
|
||||
dragging: draggingOperation,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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<T> = DatasourceDimensionDropHandlerProps<IndexPatternPrivateState> & {
|
||||
droppedItem: T;
|
||||
};
|
||||
|
||||
export function getDropTypes(
|
||||
const operationLabels = getOperationDisplay();
|
||||
|
||||
export function getDropProps(
|
||||
props: DatasourceDimensionDropProps<IndexPatternPrivateState> & { 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<DraggedOperation
|
|||
}
|
||||
|
||||
const currentIndexPattern = state.indexPatterns[layer.indexPatternId];
|
||||
// Detects if we can change the field only, otherwise change field + operation
|
||||
|
||||
const selectedColumn: IndexPatternColumn | null = layer.columns[columnId] || null;
|
||||
|
||||
const fieldIsCompatibleWithCurrent =
|
||||
selectedColumn && operationsForNewField.includes(selectedColumn.operationType);
|
||||
|
||||
const newLayer = insertOrReplaceColumn({
|
||||
layer: deleteColumn({
|
||||
|
@ -187,7 +219,7 @@ function onMoveDropToNonCompatibleGroup(props: DropHandlerProps<DraggedOperation
|
|||
}),
|
||||
columnId,
|
||||
indexPattern: currentIndexPattern,
|
||||
op: operationsForNewField[0],
|
||||
op: fieldIsCompatibleWithCurrent ? selectedColumn.operationType : operationsForNewField[0],
|
||||
field,
|
||||
});
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ import { toExpression } from './to_expression';
|
|||
import {
|
||||
IndexPatternDimensionTrigger,
|
||||
IndexPatternDimensionEditor,
|
||||
getDropTypes,
|
||||
getDropProps,
|
||||
onDrop,
|
||||
} from './dimension_panel';
|
||||
import { IndexPatternDataPanel } from './datapanel';
|
||||
|
@ -308,7 +308,7 @@ export function getIndexPatternDatasource({
|
|||
domElement
|
||||
);
|
||||
},
|
||||
getDropTypes,
|
||||
getDropProps,
|
||||
onDrop,
|
||||
|
||||
// Reset the temporary invalid state when closing the editor, but don't
|
||||
|
|
|
@ -189,9 +189,9 @@ export interface Datasource<T = unknown, P = unknown> {
|
|||
renderDimensionTrigger: (domElement: Element, props: DatasourceDimensionTriggerProps<T>) => void;
|
||||
renderDimensionEditor: (domElement: Element, props: DatasourceDimensionEditorProps<T>) => void;
|
||||
renderLayerPanel: (domElement: Element, props: DatasourceLayerPanelProps<T>) => void;
|
||||
getDropTypes: (
|
||||
getDropProps: (
|
||||
props: DatasourceDimensionDropProps<T> & { groupId: string }
|
||||
) => DropType | undefined;
|
||||
) => { dropType: DropType; nextLabel?: string } | undefined;
|
||||
onDrop: (props: DatasourceDimensionDropHandlerProps<T>) => false | true | { deleted: string };
|
||||
updateStateOnCloseDimension?: (props: {
|
||||
layerId: string;
|
||||
|
|
Loading…
Reference in a new issue