Skip to content

Commit

Permalink
[8.x] [Discover] Add "Shift + Select" functionality to Disc…
Browse files Browse the repository at this point in the history
…over grid (#193619) (#193955)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Discover] Add "Shift + Select" functionality to Discover
grid (#193619)](#193619)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Julia
Rechkunova","email":"julia.rechkunova@elastic.co"},"sourceCommit":{"committedDate":"2024-09-25T09:38:49Z","message":"[Discover]
Add \"Shift + Select\" functionality to Discover grid (#193619)\n\n-
Closes #192366
Summary\r\n\r\nThis PR allows to select/deselect multiple rows by
holding SHIFT key\r\nwhen toggling row checkboxes.\r\n\r\n\r\n###
Checklist\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [x] This was
checked for
[cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)","sha":"6808f826625f7f901099df6360a2f5e7edc21ab4","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:enhancement","v9.0.0","Team:DataDiscovery","backport:prev-minor","Feature:UnifiedDataTable","Project:OneDiscover"],"title":"[Discover]
Add \"Shift + Select\" functionality to Discover
grid","number":193619,"url":"#193619
Add \"Shift + Select\" functionality to Discover grid (#193619)\n\n-
Closes #192366
Summary\r\n\r\nThis PR allows to select/deselect multiple rows by
holding SHIFT key\r\nwhen toggling row checkboxes.\r\n\r\n\r\n###
Checklist\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [x] This was
checked for
[cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)","sha":"6808f826625f7f901099df6360a2f5e7edc21ab4"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/193619","number":193619,"mergeCommit":{"message":"[Discover]
Add \"Shift + Select\" functionality to Discover grid (#193619)\n\n-
Closes #192366
Summary\r\n\r\nThis PR allows to select/deselect multiple rows by
holding SHIFT key\r\nwhen toggling row checkboxes.\r\n\r\n\r\n###
Checklist\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [x] This was
checked for
[cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)","sha":"6808f826625f7f901099df6360a2f5e7edc21ab4"}}]}]
BACKPORT-->

Co-authored-by: Julia Rechkunova <julia.rechkunova@elastic.co>
  • Loading branch information
kibanamachine and jughosta committed Sep 25, 2024
1 parent 41bb84d commit 36945df
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 10 deletions.
1 change: 1 addition & 0 deletions packages/kbn-unified-data-table/__mocks__/table_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export function buildSelectedDocsState(selectedDocIds: string[]): UseSelectedDoc
selectedDocsCount: selectedDocsSet.size,
docIdsInSelectionOrder: selectedDocIds,
toggleDocSelection: jest.fn(),
toggleMultipleDocsSelection: jest.fn(),
selectAllDocs: jest.fn(),
selectMoreDocs: jest.fn(),
deselectSomeDocs: jest.fn(),
Expand Down
10 changes: 8 additions & 2 deletions packages/kbn-unified-data-table/src/components/data_table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -497,8 +497,14 @@ export const UnifiedDataTable = ({
const [isCompareActive, setIsCompareActive] = useState(false);
const displayedColumns = getDisplayedColumns(columns, dataView);
const defaultColumns = displayedColumns.includes('_source');
const docMap = useMemo(() => new Map(rows?.map((row) => [row.id, row]) ?? []), [rows]);
const getDocById = useCallback((id: string) => docMap.get(id), [docMap]);
const docMap = useMemo(
() =>
new Map<string, { doc: DataTableRecord; docIndex: number }>(
rows?.map((row, docIndex) => [row.id, { doc: row, docIndex }]) ?? []
),
[rows]
);
const getDocById = useCallback((id: string) => docMap.get(id)?.doc, [docMap]);
const selectedDocsState = useSelectedDocs(docMap);
const {
isDocSelected,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const SelectButton = (props: EuiDataGridCellValueElementProps) => {
const { record, rowIndex } = useControlColumn(props);
const { euiTheme } = useEuiTheme();
const { selectedDocsState } = useContext(UnifiedDataTableContext);
const { isDocSelected, toggleDocSelection } = selectedDocsState;
const { isDocSelected, toggleDocSelection, toggleMultipleDocsSelection } = selectedDocsState;

const toggleDocumentSelectionLabel = i18n.translate('unifiedDataTable.grid.selectDoc', {
defaultMessage: `Select document ''{rowNumber}''`,
Expand Down Expand Up @@ -66,8 +66,12 @@ export const SelectButton = (props: EuiDataGridCellValueElementProps) => {
aria-label={toggleDocumentSelectionLabel}
checked={isDocSelected(record.id)}
data-test-subj={`dscGridSelectDoc-${record.id}`}
onChange={() => {
toggleDocSelection(record.id);
onChange={(event) => {
if ((event.nativeEvent as MouseEvent)?.shiftKey) {
toggleMultipleDocsSelection(record.id);
} else {
toggleDocSelection(record.id);
}
}}
/>
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe('useSelectedDocs', () => {
const docs = generateEsHits(dataViewWithTimefieldMock, 5).map((hit) =>
buildDataTableRecord(hit, dataViewWithTimefieldMock)
);
const docsMap = new Map(docs.map((doc) => [doc.id, doc]));
const docsMap = new Map(docs.map((doc, docIndex) => [doc.id, { doc, docIndex }]));

test('should have a correct default state', () => {
const { result } = renderHook(() => useSelectedDocs(docsMap));
Expand Down Expand Up @@ -223,4 +223,30 @@ describe('useSelectedDocs', () => {
expect(result.current.getCountOfFilteredSelectedDocs([docs[0].id])).toBe(0);
expect(result.current.getCountOfFilteredSelectedDocs([docs[2].id, docs[3].id])).toBe(0);
});

test('should toggleMultipleDocsSelection correctly', () => {
const { result } = renderHook(() => useSelectedDocs(docsMap));
const docIds = docs.map((doc) => doc.id);

// select `0`
act(() => {
result.current.toggleDocSelection(docs[0].id);
});

expect(result.current.getCountOfFilteredSelectedDocs(docIds)).toBe(1);

// select from `0` to `4`
act(() => {
result.current.toggleMultipleDocsSelection(docs[4].id);
});

expect(result.current.getCountOfFilteredSelectedDocs(docIds)).toBe(5);

// deselect from `2` to `4`
act(() => {
result.current.toggleMultipleDocsSelection(docs[2].id);
});

expect(result.current.getCountOfFilteredSelectedDocs(docIds)).toBe(2);
});
});
47 changes: 45 additions & 2 deletions packages/kbn-unified-data-table/src/hooks/use_selected_docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { useCallback, useMemo, useState } from 'react';
import { useCallback, useMemo, useRef, useState } from 'react';
import type { DataTableRecord } from '@kbn/discover-utils';

export interface UseSelectedDocsState {
Expand All @@ -17,6 +17,7 @@ export interface UseSelectedDocsState {
selectedDocsCount: number;
docIdsInSelectionOrder: string[];
toggleDocSelection: (docId: string) => void;
toggleMultipleDocsSelection: (toDocId: string) => void;
selectAllDocs: () => void;
selectMoreDocs: (docIds: string[]) => void;
deselectSomeDocs: (docIds: string[]) => void;
Expand All @@ -25,8 +26,11 @@ export interface UseSelectedDocsState {
getSelectedDocsOrderedByRows: (rows: DataTableRecord[]) => DataTableRecord[];
}

export const useSelectedDocs = (docMap: Map<string, DataTableRecord>): UseSelectedDocsState => {
export const useSelectedDocs = (
docMap: Map<string, { doc: DataTableRecord; docIndex: number }>
): UseSelectedDocsState => {
const [selectedDocsSet, setSelectedDocsSet] = useState<Set<string>>(new Set());
const lastCheckboxToggledDocId = useRef<string | undefined>();

const toggleDocSelection = useCallback((docId: string) => {
setSelectedDocsSet((prevSelectedRowsSet) => {
Expand All @@ -38,6 +42,7 @@ export const useSelectedDocs = (docMap: Map<string, DataTableRecord>): UseSelect
}
return newSelectedRowsSet;
});
lastCheckboxToggledDocId.current = docId;
}, []);

const replaceSelectedDocs = useCallback((docIds: string[]) => {
Expand Down Expand Up @@ -73,6 +78,42 @@ export const useSelectedDocs = (docMap: Map<string, DataTableRecord>): UseSelect
[selectedDocsSet, docMap]
);

const toggleMultipleDocsSelection = useCallback(
(toDocId: string) => {
const shouldSelect = !isDocSelected(toDocId);

const lastToggledDocIdIndex = docMap.get(
lastCheckboxToggledDocId.current ?? toDocId
)?.docIndex;
const currentToggledDocIdIndex = docMap.get(toDocId)?.docIndex;
const docIds: string[] = [];

if (
typeof lastToggledDocIdIndex === 'number' &&
typeof currentToggledDocIdIndex === 'number' &&
lastToggledDocIdIndex !== currentToggledDocIdIndex
) {
const startIndex = Math.min(lastToggledDocIdIndex, currentToggledDocIdIndex);
const endIndex = Math.max(lastToggledDocIdIndex, currentToggledDocIdIndex);

docMap.forEach(({ doc, docIndex }) => {
if (docIndex >= startIndex && docIndex <= endIndex) {
docIds.push(doc.id);
}
});
}

if (shouldSelect) {
selectMoreDocs(docIds);
} else {
deselectSomeDocs(docIds);
}

lastCheckboxToggledDocId.current = toDocId;
},
[selectMoreDocs, deselectSomeDocs, docMap, isDocSelected]
);

const getSelectedDocsOrderedByRows = useCallback(
(rows: DataTableRecord[]) => {
return rows.filter((row) => isDocSelected(row.id));
Expand Down Expand Up @@ -101,6 +142,7 @@ export const useSelectedDocs = (docMap: Map<string, DataTableRecord>): UseSelect
docIdsInSelectionOrder: selectedDocIds,
getCountOfFilteredSelectedDocs,
toggleDocSelection,
toggleMultipleDocsSelection,
selectAllDocs,
selectMoreDocs,
deselectSomeDocs,
Expand All @@ -112,6 +154,7 @@ export const useSelectedDocs = (docMap: Map<string, DataTableRecord>): UseSelect
isDocSelected,
getCountOfFilteredSelectedDocs,
toggleDocSelection,
toggleMultipleDocsSelection,
selectAllDocs,
selectMoreDocs,
deselectSomeDocs,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,60 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
});

it('should be able to select multiple rows holding Shift key', async () => {
expect(await dataGrid.isSelectedRowsMenuVisible()).to.be(false);

// select 1 row
await dataGrid.selectRow(1);

await retry.try(async () => {
expect(await dataGrid.isSelectedRowsMenuVisible()).to.be(true);
expect(await dataGrid.getNumberOfSelectedRowsOnCurrentPage()).to.be(1);
expect(await dataGrid.getNumberOfSelectedRows()).to.be(1);
});

// select 3 more
await dataGrid.selectRow(4, { pressShiftKey: true });

await retry.try(async () => {
expect(await dataGrid.isSelectedRowsMenuVisible()).to.be(true);
expect(await dataGrid.getNumberOfSelectedRowsOnCurrentPage()).to.be(4);
expect(await dataGrid.getNumberOfSelectedRows()).to.be(4);
});

// deselect index 3 and 4
await dataGrid.selectRow(3, { pressShiftKey: true });

await retry.try(async () => {
expect(await dataGrid.isSelectedRowsMenuVisible()).to.be(true);
expect(await dataGrid.getNumberOfSelectedRowsOnCurrentPage()).to.be(2);
expect(await dataGrid.getNumberOfSelectedRows()).to.be(2);
});

// select from index 3 to 0
await dataGrid.selectRow(0, { pressShiftKey: true });

await retry.try(async () => {
expect(await dataGrid.isSelectedRowsMenuVisible()).to.be(true);
expect(await dataGrid.getNumberOfSelectedRowsOnCurrentPage()).to.be(4);
expect(await dataGrid.getNumberOfSelectedRows()).to.be(4);
});

// select from both pages
await testSubjects.click('pagination-button-1');
await retry.try(async () => {
expect(await dataGrid.getNumberOfSelectedRowsOnCurrentPage()).to.be(0);
});

await dataGrid.selectRow(2, { pressShiftKey: true });

await retry.try(async () => {
expect(await dataGrid.isSelectedRowsMenuVisible()).to.be(true);
expect(await dataGrid.getNumberOfSelectedRowsOnCurrentPage()).to.be(3);
expect(await dataGrid.getNumberOfSelectedRows()).to.be(8);
});
});

it('should be able to bulk select rows', async () => {
expect(await dataGrid.isSelectedRowsMenuVisible()).to.be(false);
expect(await testSubjects.getAttribute('selectAllDocsOnPageToggle', 'title')).to.be(
Expand Down
13 changes: 11 additions & 2 deletions test/functional/services/data_grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -689,12 +689,21 @@ export class DataGridService extends FtrService {
await this.checkCurrentRowsPerPageToBe(newValue);
}

public async selectRow(rowIndex: number) {
public async selectRow(rowIndex: number, { pressShiftKey }: { pressShiftKey?: boolean } = {}) {
const checkbox = await this.find.byCssSelector(
`.euiDataGridRow[data-grid-visible-row-index="${rowIndex}"] [data-gridcell-column-id="select"] .euiCheckbox__input`
);

await checkbox.click();
if (pressShiftKey) {
await this.browser
.getActions()
.keyDown(Key.SHIFT)
.click(checkbox._webElement)
.keyUp(Key.SHIFT)
.perform();
} else {
await checkbox.click();
}
}

public async getNumberOfSelectedRows() {
Expand Down

0 comments on commit 36945df

Please sign in to comment.