X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/525e81bdbe18ea369f5a61a713e743608bab2e42..8169b80dda901b6a628af000e2f2bd12b3489f50:/services/workbench2/src/components/data-table/data-table.tsx?ds=sidebyside

diff --git a/services/workbench2/src/components/data-table/data-table.tsx b/services/workbench2/src/components/data-table/data-table.tsx
index 155d772f85..7629034f9d 100644
--- a/services/workbench2/src/components/data-table/data-table.tsx
+++ b/services/workbench2/src/components/data-table/data-table.tsx
@@ -24,11 +24,13 @@ import { DataTableFilters } from "../data-table-filters/data-table-filters-tree"
 import { DataTableMultiselectPopover } from "../data-table-multiselect-popover/data-table-multiselect-popover";
 import { DataTableFiltersPopover } from "../data-table-filters/data-table-filters-popover";
 import { countNodes, getTreeDirty } from "models/tree";
-import { IconType, PendingIcon } from "components/icon/icon";
+import { IconType } from "components/icon/icon";
 import { SvgIconProps } from "@material-ui/core/SvgIcon";
 import ArrowDownwardIcon from "@material-ui/icons/ArrowDownward";
 import { createTree } from "models/tree";
 import { DataTableMultiselectOption } from "../data-table-multiselect-popover/data-table-multiselect-popover";
+import { isExactlyOneSelected } from "store/multiselect/multiselect-actions";
+import { PendingIcon } from "components/icon/icon";
 
 export type DataColumns<I, R> = Array<DataColumn<I, R>>;
 
@@ -49,11 +51,14 @@ export interface DataTableDataProps<I> {
     working?: boolean;
     defaultViewIcon?: IconType;
     defaultViewMessages?: string[];
-    currentItemUuid?: string;
-    currentRoute?: string;
     toggleMSToolbar: (isVisible: boolean) => void;
     setCheckedListOnStore: (checkedList: TCheckedList) => void;
+    currentRoute?: string;
+    currentRouteUuid: string;
     checkedList: TCheckedList;
+    selectedResourceUuid: string;
+    setSelectedUuid: (uuid: string | null) => void;
+    isNotFound?: boolean;
 }
 
 type CssRules =
@@ -63,13 +68,13 @@ type CssRules =
     | "noItemsInfo"
     | "checkBoxHead"
     | "checkBoxCell"
+    | "clickBox"
     | "checkBox"
     | "firstTableCell"
     | "tableCell"
     | "arrow"
     | "arrowButton"
-    | "tableCellWorkflows"
-    | "loader";
+    | "tableCellWorkflows";
 
 const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
     root: {
@@ -82,11 +87,6 @@ const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
     tableBody: {
         background: theme.palette.background.paper,
     },
-    loader: {
-        left: "50%",
-        marginLeft: "-84px",
-        position: "absolute",
-    },
     noItemsInfo: {
         textAlign: "center",
         padding: theme.spacing.unit,
@@ -94,10 +94,22 @@ const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
     checkBoxHead: {
         padding: "0",
         display: "flex",
+        width: '2rem',
+        height: "1.5rem",
+        paddingLeft: '0.9rem',
+        marginRight: '0.5rem'
     },
     checkBoxCell: {
         padding: "0",
-        paddingLeft: "10px",
+    },
+    clickBox: {
+        display: 'flex',
+        width: '1.6rem',
+        height: "1.5rem",
+        paddingLeft: '0.35rem',
+        paddingTop: '0.1rem',
+        marginLeft: '0.5rem',
+        cursor: "pointer",
     },
     checkBox: {
         cursor: "pointer",
@@ -133,6 +145,7 @@ export type TCheckedList = Record<string, boolean>;
 
 type DataTableState = {
     isSelected: boolean;
+    isLoaded: boolean;
 };
 
 type DataTableProps<T> = DataTableDataProps<T> & WithStyles<CssRules>;
@@ -141,20 +154,50 @@ export const DataTable = withStyles(styles)(
     class Component<T> extends React.Component<DataTableProps<T>> {
         state: DataTableState = {
             isSelected: false,
+            isLoaded: false,
         };
 
         componentDidMount(): void {
-            this.initializeCheckedList(this.props.items);
+            this.initializeCheckedList([]);
+            // If table is initialized loaded but empty
+            // isLoaded won't be set true by componentDidUpdate later
+            // So we set it to true here
+            if (!this.props.working) {
+                this.setState({ isLoaded: true });
+            }
         }
 
         componentDidUpdate(prevProps: Readonly<DataTableProps<T>>, prevState: DataTableState) {
-            const { items, setCheckedListOnStore } = this.props;
+            const { items, currentRouteUuid, setCheckedListOnStore } = this.props;
             const { isSelected } = this.state;
+            const singleSelected = isExactlyOneSelected(this.props.checkedList);
             if (prevProps.items !== items) {
                 if (isSelected === true) this.setState({ isSelected: false });
                 if (items.length) this.initializeCheckedList(items);
                 else setCheckedListOnStore({});
             }
+            if (prevProps.currentRoute !== this.props.currentRoute) {
+                this.initializeCheckedList([]);
+            }
+            if (singleSelected && singleSelected !== isExactlyOneSelected(prevProps.checkedList)) {
+                this.props.setSelectedUuid(singleSelected);
+            }
+            if (!singleSelected && !!currentRouteUuid && !this.isAnySelected()) {
+                this.props.setSelectedUuid(currentRouteUuid);
+            }
+            if (!singleSelected && this.isAnySelected()) {
+                this.props.setSelectedUuid(null);
+            }
+            if(prevProps.working === true && this.props.working === false) {
+                this.setState({ isLoaded: true });
+            }
+            if((this.props.items.length > 0) && !this.state.isLoaded) {
+                this.setState({ isLoaded: true });
+            }
+        }
+
+        componentWillUnmount(): void {
+            this.initializeCheckedList([]);
         }
 
         checkBoxColumn: DataColumn<any, any> = {
@@ -165,13 +208,24 @@ export const DataTable = withStyles(styles)(
             render: uuid => {
                 const { classes, checkedList } = this.props;
                 return (
-                    <input
-                        type="checkbox"
-                        name={uuid}
-                        className={classes.checkBox}
-                        checked={checkedList && checkedList[uuid] ? checkedList[uuid] : false}
-                        onChange={() => this.handleSelectOne(uuid)}
-                        onDoubleClick={ev => ev.stopPropagation()}></input>
+                    <div
+                        className={classes.clickBox}
+                        onClick={(ev) => {
+                            ev.stopPropagation()
+                            this.handleSelectOne(uuid)
+                        }}
+                        onDoubleClick={(ev) => ev.stopPropagation()}
+                    >
+                        <input
+                            data-cy={`multiselect-checkbox-${uuid}`}
+                            type='checkbox'
+                            name={uuid}
+                            className={classes.checkBox}
+                            checked={checkedList && checkedList[uuid] ? checkedList[uuid] : false}
+                            onChange={() => this.handleSelectOne(uuid)}
+                            onDoubleClick={(ev) => ev.stopPropagation()}
+                        ></input>
+                    </div>
                 );
             },
         };
@@ -185,11 +239,12 @@ export const DataTable = withStyles(styles)(
         initializeCheckedList = (uuids: any[]): void => {
             const newCheckedList = { ...this.props.checkedList };
 
-            uuids.forEach(uuid => {
-                if (!newCheckedList.hasOwnProperty(uuid)) {
-                    newCheckedList[uuid] = false;
+            if(Object.keys(newCheckedList).length === 0){
+                for(const uuid of uuids){
+                    newCheckedList[uuid] = false
                 }
-            });
+            }
+
             for (const key in newCheckedList) {
                 if (!uuids.includes(key)) {
                     delete newCheckedList[key];
@@ -260,7 +315,8 @@ export const DataTable = withStyles(styles)(
         };
 
         render() {
-            const { items, classes, working, columns } = this.props;
+            const { items, classes, columns, isNotFound } = this.props;
+            const { isLoaded } = this.state;
             if (columns[0].name === this.checkBoxColumn.name) columns.shift();
             columns.unshift(this.checkBoxColumn);
             return (
@@ -270,31 +326,43 @@ export const DataTable = withStyles(styles)(
                             <TableHead>
                                 <TableRow>{this.mapVisibleColumns(this.renderHeadCell)}</TableRow>
                             </TableHead>
-                            <TableBody className={classes.tableBody}>{!working && items.map(this.renderBodyRow)}</TableBody>
+                            <TableBody className={classes.tableBody}>{(isLoaded && !isNotFound) && items.map(this.renderBodyRow)}</TableBody>
                         </Table>
-                        {!!working && (
-                            <div className={classes.loader}>
-                                <DataTableDefaultView
-                                    icon={PendingIcon}
-                                    messages={["Loading data, please wait."]}
-                                />
-                            </div>
-                        )}
-                        {items.length === 0 && !working && this.renderNoItemsPlaceholder(this.props.columns)}
+                        {(!isLoaded || isNotFound || items.length === 0) && this.renderNoItemsPlaceholder(this.props.columns)}
                     </div>
                 </div>
             );
         }
 
         renderNoItemsPlaceholder = (columns: DataColumns<T, any>) => {
+            const { isLoaded } = this.state;
+            const { working, isNotFound } = this.props;
             const dirty = columns.some(column => getTreeDirty("")(column.filters));
-            return (
-                <DataTableDefaultView
-                    icon={this.props.defaultViewIcon}
-                    messages={this.props.defaultViewMessages}
-                    filtersApplied={dirty}
-                />
-            );
+            if (isNotFound && isLoaded) {
+                return (
+                    <DataTableDefaultView
+                        icon={this.props.defaultViewIcon}
+                        messages={["No items found"]}
+                    />
+                );
+            } else
+            if (isLoaded === false || working === true) {
+                return (
+                    <DataTableDefaultView
+                        icon={PendingIcon}
+                        messages={["Loading data, please wait"]}
+                    />
+                );
+            } else {
+                // isLoaded && !working && !isNotFound
+                return (
+                    <DataTableDefaultView
+                        icon={this.props.defaultViewIcon}
+                        messages={this.props.defaultViewMessages}
+                        filtersApplied={dirty}
+                    />
+                );
+            }
         };
 
         renderHeadCell = (column: DataColumn<T, any>, index: number) => {
@@ -306,7 +374,7 @@ export const DataTable = withStyles(styles)(
                     key={key || index}
                     className={classes.checkBoxCell}>
                     <div className={classes.checkBoxHead}>
-                        <Tooltip title={this.state.isSelected ? "Deselect All" : "Select All"}>
+                        <Tooltip title={this.state.isSelected ? "Deselect all" : "Select all"}>
                             <input
                                 type="checkbox"
                                 className={classes.checkBox}
@@ -364,15 +432,16 @@ export const DataTable = withStyles(styles)(
         );
 
         renderBodyRow = (item: any, index: number) => {
-            const { onRowClick, onRowDoubleClick, extractKey, classes, currentItemUuid, currentRoute } = this.props;
+            const { onRowClick, onRowDoubleClick, extractKey, classes, selectedResourceUuid, currentRoute } = this.props;
             return (
                 <TableRow
+                    data-cy={'data-table-row'}
                     hover
                     key={extractKey ? extractKey(item) : index}
                     onClick={event => onRowClick && onRowClick(event, item)}
                     onContextMenu={this.handleRowContextMenu(item)}
                     onDoubleClick={event => onRowDoubleClick && onRowDoubleClick(event, item)}
-                    selected={item === currentItemUuid}>
+                    selected={item === selectedResourceUuid}>
                     {this.mapVisibleColumns((column, index) => (
                         <TableCell
                             key={column.key || index}
