From 32cea02f7553ce81b9cd0251babda78741b4ab09 Mon Sep 17 00:00:00 2001 From: Juan Escalada <97265671+jescalada@users.noreply.github.com> Date: Wed, 17 Jul 2024 10:17:25 -0700 Subject: [PATCH] Add range selection for runs (shift+click) (#104) * Fix playwright pipeline * Upgrade playwright to 1.45.0 * Add with-deps * Add playwright envs * Change installation order * Do everything in src directory * Set working-directory * Add range start state to Table * Extend onRowSelect to allow range selection * Plug together rangeStart state and callback * Intercept and handle shift click event --- src/src/components/CustomTable/Table.tsx | 8 +++++ .../components/CustomTable/TableColumn.tsx | 21 ++++++++--- src/src/components/Table/Table.tsx | 6 ++++ .../models/explorer/metricsModelMethods.ts | 8 +++-- .../models/explorer/paramsModelMethods.ts | 15 ++++++-- .../models/explorer/runsModelMethods.ts | 15 ++++++-- .../models/explorer/scattersModelMethods.ts | 14 ++++++-- src/src/utils/app/onRowSelect.ts | 36 +++++++++++++++++-- 8 files changed, 109 insertions(+), 14 deletions(-) diff --git a/src/src/components/CustomTable/Table.tsx b/src/src/components/CustomTable/Table.tsx index b656b74d..599ca7b1 100644 --- a/src/src/components/CustomTable/Table.tsx +++ b/src/src/components/CustomTable/Table.tsx @@ -371,6 +371,8 @@ function Table(props) { isAlwaysVisible={true} onRowHover={props.onRowHover} listWindow={props.listWindow} + shiftClickRangeStart={props.shiftClickRangeStart} + setShiftClickRangeStart={props.setShiftClickRangeStart} /> @@ -445,6 +447,8 @@ function Table(props) { selectedRows={props.selectedRows} onRowSelect={props.onRowSelect} listWindow={props.listWindow} + shiftClickRangeStart={props.shiftClickRangeStart} + setShiftClickRangeStart={props.setShiftClickRangeStart} /> ))} @@ -522,6 +526,8 @@ function Table(props) { } colLeft={colLefts[index] ?? null} listWindow={props.listWindow} + shiftClickRangeStart={props.shiftClickRangeStart} + setShiftClickRangeStart={props.setShiftClickRangeStart} /> ); @@ -589,6 +595,8 @@ function Table(props) { columnOptions={col.columnOptions} selectedRows={props.selectedRows} listWindow={props.listWindow} + shiftClickRangeStart={props.shiftClickRangeStart} + setShiftClickRangeStart={props.setShiftClickRangeStart} /> ))} diff --git a/src/src/components/CustomTable/TableColumn.tsx b/src/src/components/CustomTable/TableColumn.tsx index e554ee8d..19ec682d 100644 --- a/src/src/components/CustomTable/TableColumn.tsx +++ b/src/src/components/CustomTable/TableColumn.tsx @@ -59,6 +59,8 @@ function Column({ colLeft, listWindow, noColumnActions, + shiftClickRangeStart, + setShiftClickRangeStart, }) { const [maxWidth, setMaxWidth] = React.useState(width); const [isResizing, setIsResizing] = React.useState(false); @@ -777,10 +779,21 @@ function Column({ checked={!!selectedRows[item.selectKey]} onClick={(e) => { e.stopPropagation(); - onRowSelect({ - data: item, - actionType: 'single', - }); + + if (e.shiftKey) { + onRowSelect({ + actionType: 'range', + data: data, + rangeStart: shiftClickRangeStart, + rangeEnd: item.index, + }); + } else { + setShiftClickRangeStart(item.index); + onRowSelect({ + data: item, + actionType: 'single', + }); + } }} /> diff --git a/src/src/components/Table/Table.tsx b/src/src/components/Table/Table.tsx index a10dc03f..1f7e658d 100644 --- a/src/src/components/Table/Table.tsx +++ b/src/src/components/Table/Table.tsx @@ -149,6 +149,8 @@ const Table = React.forwardRef(function Table( availableSpace: 0, }); const [isExporting, setIsExporting] = React.useState(false); + const [shiftClickRangeStart, setShiftClickRangeStart] = + React.useState(null); const handleExport = async () => { setIsExporting(true); @@ -1035,6 +1037,8 @@ const Table = React.forwardRef(function Table( columnsColorScales={columnsColorScales} onToggleColumnsColorScales={onToggleColumnsColorScales} noColumnActions={noColumnActions} + shiftClickRangeStart={shiftClickRangeStart} + setShiftClickRangeStart={setShiftClickRangeStart} {...props} /> @@ -1076,6 +1080,8 @@ const Table = React.forwardRef(function Table( onRowHover={onRowHover} onRowClick={onRowClick} disableRowClick={disableRowClick} + shiftClickRangeStart={shiftClickRangeStart} + setShiftClickRangeStart={setShiftClickRangeStart} /> ) diff --git a/src/src/services/models/explorer/metricsModelMethods.ts b/src/src/services/models/explorer/metricsModelMethods.ts index 0ec3b1a2..4bc99202 100644 --- a/src/src/services/models/explorer/metricsModelMethods.ts +++ b/src/src/services/models/explorer/metricsModelMethods.ts @@ -1916,11 +1916,15 @@ function getMetricsAppModelMethods( onRowSelect({ actionType, data, + rangeStart, + rangeEnd, }: { - actionType: 'single' | 'selectAll' | 'removeAll'; + actionType: 'single' | 'selectAll' | 'removeAll' | 'range'; data?: any; + rangeStart?: number; + rangeEnd?: number; }): void { - return onRowSelect({ actionType, data, model }); + return onRowSelect({ actionType, data, rangeStart, rangeEnd, model }); }, onRowsVisibilityChange(metricKeys: string[]): void { return onRowsVisibilityChange({ diff --git a/src/src/services/models/explorer/paramsModelMethods.ts b/src/src/services/models/explorer/paramsModelMethods.ts index d7e82065..6a84ce0f 100644 --- a/src/src/services/models/explorer/paramsModelMethods.ts +++ b/src/src/services/models/explorer/paramsModelMethods.ts @@ -1771,14 +1771,25 @@ function getParamsModelMethods( updateModelData, }); }, + onRowSelect({ actionType, data, + rangeStart, + rangeEnd, }: { - actionType: 'single' | 'selectAll' | 'removeAll'; + actionType: 'single' | 'selectAll' | 'removeAll' | 'range'; data?: any; + rangeStart?: number; + rangeEnd?: number; }): void { - return onRowSelect({ actionType, data, model }); + return onRowSelect({ + actionType, + data, + rangeStart, + rangeEnd, + model, + }); }, onRowsVisibilityChange(metricKeys: string[]): void { return onRowsVisibilityChange({ diff --git a/src/src/services/models/explorer/runsModelMethods.ts b/src/src/services/models/explorer/runsModelMethods.ts index 96c8525c..7b7b153f 100644 --- a/src/src/services/models/explorer/runsModelMethods.ts +++ b/src/src/services/models/explorer/runsModelMethods.ts @@ -1184,14 +1184,25 @@ function getRunsModelMethods( updateModelData, }); }, + onRowSelect({ actionType, data, + rangeStart, + rangeEnd, }: { - actionType: 'single' | 'selectAll' | 'removeAll'; + actionType: 'single' | 'selectAll' | 'removeAll' | 'range'; data?: any; + rangeStart?: number; + rangeEnd?: number; }): void { - return onRowSelect({ actionType, data, model }); + return onRowSelect({ + actionType, + data, + rangeStart, + rangeEnd, + model, + }); }, onToggleColumnsColorScales(colKey: string): void { onToggleColumnsColorScales({ diff --git a/src/src/services/models/explorer/scattersModelMethods.ts b/src/src/services/models/explorer/scattersModelMethods.ts index 2f5a0f48..1900e1e8 100644 --- a/src/src/services/models/explorer/scattersModelMethods.ts +++ b/src/src/services/models/explorer/scattersModelMethods.ts @@ -1682,11 +1682,21 @@ function getScattersModelMethods( onRowSelect({ actionType, data, + rangeStart, + rangeEnd, }: { - actionType: 'single' | 'selectAll' | 'removeAll'; + actionType: 'single' | 'selectAll' | 'removeAll' | 'range'; data?: any; + rangeStart?: number; + rangeEnd?: number; }): void { - return onRowSelect({ actionType, data, model }); + return onRowSelect({ + actionType, + data, + rangeStart, + rangeEnd, + model, + }); }, onRowsVisibilityChange(metricKeys: string[]): void { return onRowsVisibilityChange({ diff --git a/src/src/utils/app/onRowSelect.ts b/src/src/utils/app/onRowSelect.ts index e6f98c19..232ab182 100644 --- a/src/src/utils/app/onRowSelect.ts +++ b/src/src/utils/app/onRowSelect.ts @@ -3,22 +3,28 @@ import _ from 'lodash-es'; import { IModel, State } from 'types/services/models/model'; /** - * + * Select row in table and update selectedRows state * @param {string} key - key of table column * @param {any} data - nested data * @param {string} actionType - action type name + * @param {number} rangeStart - start range for range selection + * @param {number} rangeEnd - end range for range selection * @param {IModel} model - instance of create model */ export interface IRowSelectProps { - actionType: 'single' | 'selectAll' | 'removeAll'; + actionType: 'single' | 'selectAll' | 'removeAll' | 'range'; data?: any; + rangeStart?: number; + rangeEnd?: number; model: IModel; } export default function onRowSelect({ actionType, data, + rangeStart = 0, + rangeEnd = 0, model, }: IRowSelectProps): any { let selectedRows = model.getState()?.selectedRows || {}; @@ -86,6 +92,32 @@ export default function onRowSelect({ selectedRows = _.omit(selectedRows, hashArray); } + break; + case 'range': + if (Array.isArray(data)) { + // Compute min and max to allow reverse selection (from bottom to top) + const rangeMin = Math.min(rangeStart, rangeEnd); + const rangeMax = Math.max(rangeStart, rangeEnd); + if (selectedRows[data[rangeEnd].selectKey]) { + // Remove all rows in range if last row was already selected + const hashes = data + .slice(rangeMin, rangeMax + 1) + .map((item: any) => item.selectKey); + selectedRows = _.omit(selectedRows, hashes); + } else { + // Add all rows in range otherwise + data.slice(rangeMin, rangeMax + 1).forEach((item: any) => { + if (!selectedRows[item.selectKey]) { + selectedRows[item.selectKey] = { + selectKey: item.selectKey, + isHidden: item.isHidden, + key: item.key, + ...rawData[sliceRunHash(item.selectKey)], + }; + } + }); + } + } break; }