15672: Adds mutually exclusive data explorer column filters
authorEric Biagiotti <ebiagiotti@veritasgenetics.com>
Wed, 4 Dec 2019 18:29:07 +0000 (13:29 -0500)
committerLucas Di Pentima <lucas@di-pentima.com.ar>
Tue, 17 Dec 2019 20:44:03 +0000 (17:44 -0300)
Arvados-DCO-1.1-Signed-off-by: Eric Biagiotti <ebiagiotti@veritasgenetics.com>

src/components/data-table-filters/data-table-filters-popover.tsx
src/components/data-table-filters/data-table-filters-tree.tsx
src/components/data-table/data-column.ts
src/components/data-table/data-table.tsx

index 3bd7991ca95cbfa2c51d2317c98af731cf7adfef..670afa95e415dc693830f0b9e5d87dc2f91e3729 100644 (file)
@@ -73,6 +73,11 @@ export interface DataTableFilterProps {
     filters: DataTableFilters;
     onChange?: (filters: DataTableFilters) => void;
 
+    /**
+     * When set to true, only one filter can be selected at a time.
+     */
+    mutuallyExclusive?: boolean;
+
     /**
      * By default `all` filters selection means that label should be grayed out.
      * Use `none` when label is supposed to be grayed out when no filter is selected.
@@ -110,7 +115,7 @@ export const DataTableFiltersPopover = withStyles(styles)(
                         onClick={this.open}
                         disableRipple>
                         {children}
-                        <IconButton component='span' classes={{root: classes.iconButton}} tabIndex={-1}>
+                        <IconButton component='span' classes={{ root: classes.iconButton }} tabIndex={-1}>
                             <i className={classnames(["fas fa-filter", classes.icon])}
                                 data-fa-transform="shrink-3"
                                 ref={this.icon} />
@@ -131,7 +136,10 @@ export const DataTableFiltersPopover = withStyles(styles)(
                         </CardContent>
                         <DataTableFiltersTree
                             filters={this.state.filters}
-                            onChange={filters => this.setState({ filters })} />
+                            mutuallyExclusive={this.props.mutuallyExclusive}
+                            onChange={filters => {
+                                this.setState({ filters });
+                            }} />
                         <CardActions>
                             <Button
                                 color="primary"
index dcc4f0e111aea7d95d0a99b01b72d11e33dd45f6..d964012dcefa348a7182660bca2e9d27985a5675 100644 (file)
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from "react";
-import { Tree, toggleNodeSelection, getNode, initTreeNode, getNodeChildrenIds } from '~/models/tree';
+import { Tree, toggleNodeSelection, getNode, initTreeNode, getNodeChildrenIds, selectNode, deselectNodes } from '~/models/tree';
 import { Tree as TreeComponent, TreeItem, TreeItemStatus } from '~/components/tree/tree';
 import { noop, map } from "lodash/fp";
 import { toggleNodeCollapse } from '~/models/tree';
@@ -18,6 +18,11 @@ export type DataTableFilters = Tree<DataTableFilterItem>;
 export interface DataTableFilterProps {
     filters: DataTableFilters;
     onChange?: (filters: DataTableFilters) => void;
+
+    /**
+     * When set to true, only one filter can be selected at a time.
+     */
+    mutuallyExclusive?: boolean;
 }
 
 export class DataTableFiltersTree extends React.Component<DataTableFilterProps> {
@@ -31,6 +36,8 @@ export class DataTableFiltersTree extends React.Component<DataTableFilterProps>
             items={filtersToTree(filters)}
             render={renderItem}
             showSelection
+            useRadioButtons={this.props.mutuallyExclusive}
+            toggleItemRadioButton={this.toggleRadioButtonFilter}
             disableRipple
             onContextMenu={noop}
             toggleItemActive={noop}
@@ -39,6 +46,22 @@ export class DataTableFiltersTree extends React.Component<DataTableFilterProps>
         />;
     }
 
+    /**
+     * Handler for when a tree item is toggled via a radio button.
+     * Ensures mutual exclusivity among filter tree items.
+     */
+    toggleRadioButtonFilter = (item: TreeItem<DataTableFilterItem>) => {
+        const { onChange = noop } = this.props;
+
+        // If the filter is already selected, do nothing.
+        if (item.selected) { return; }
+
+        // Otherwise select this node and deselect the others
+        const filters = selectNode(item.id)(this.props.filters);
+        const toDeselect = Object.keys(this.props.filters).filter((id) => (id !== item.id));
+        onChange(deselectNodes(toDeselect)(filters));
+    }
+
     toggleFilter = (_: React.MouseEvent, item: TreeItem<DataTableFilterItem>) => {
         const { onChange = noop } = this.props;
         onChange(toggleNodeSelection(item.id)(this.props.filters));
index 28e93beed0cebadc1fbaa54324cc2729a3a86ed5..aada41391f5d8c13bb157f23c0aee3317a5abd35 100644 (file)
@@ -11,6 +11,12 @@ export interface DataColumn<T> {
     name: string;
     selected: boolean;
     configurable: boolean;
+
+    /**
+     * If set to true, filters on this column will be displayed in a
+     * radio group and only one filter can be selected at a time.
+     */
+    mutuallyExclusiveFilters?: boolean;
     sortDirection?: SortDirection;
     filters: DataTableFilters;
     render: (item: T) => React.ReactElement<any>;
index 841375882e2121a318af401b1e77364d4d71279d..3fac4c4dbdc7f18bec71ef6466e7f4fa63513cfb 100644 (file)
@@ -113,6 +113,7 @@ export const DataTable = withStyles(styles)(
                     countNodes(filters) > 0
                         ? <DataTableFiltersPopover
                             name={`${name} filters`}
+                            mutuallyExclusive={column.mutuallyExclusiveFilters}
                             onChange={filters =>
                                 onFiltersChange &&
                                 onFiltersChange(filters, column)}