This is an automated email from the ASF dual-hosted git repository.

graceguo pushed a commit to branch gg-Test-Scoped-Filters
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git

commit a2d5a9683861f43a1e7dfc6d778ed7a2c18f7cb3
Author: Grace <[email protected]>
AuthorDate: Fri Oct 25 10:47:26 2019 -0700

    add single-field-edit in multi-edit mode switch
---
 .../components/filterscope/FilterFieldTree.jsx     |  3 +
 .../components/filterscope/FilterScopeSelector.jsx | 47 +++++++++++++--
 .../components/filterscope/FilterScopeTree.jsx     |  4 +-
 .../filterscope/renderFilterScopeTreeNodes.jsx     | 12 +++-
 .../stylesheets/filter-scope-selector.less         | 67 +++++++++++++---------
 5 files changed, 99 insertions(+), 34 deletions(-)

diff --git 
a/superset/assets/src/dashboard/components/filterscope/FilterFieldTree.jsx 
b/superset/assets/src/dashboard/components/filterscope/FilterFieldTree.jsx
index d240849..dd76e96 100644
--- a/superset/assets/src/dashboard/components/filterscope/FilterFieldTree.jsx
+++ b/superset/assets/src/dashboard/components/filterscope/FilterFieldTree.jsx
@@ -47,6 +47,7 @@ export default function FilterFieldTree({
 }) {
   return (
     <CheckboxTree
+      showExpandAll
       showNodeIcon={false}
       expandOnClick
       nodes={renderFilterFieldTreeNodes({ nodes, activeKey })}
@@ -61,6 +62,8 @@ export default function FilterFieldTree({
         halfCheck: <CheckboxHalfchecked />,
         expandClose: <span className="rct-icon rct-icon-expand-close" />,
         expandOpen: <span className="rct-icon rct-icon-expand-open" />,
+        expandAll: <span className="rct-icon rct-icon-expand-all" />,
+        collapseAll: <span className="rct-icon rct-icon-collapse-all" />,
         parentClose: <span className="rct-icon rct-icon-parent-close" />,
         parentOpen: <span className="rct-icon rct-icon-parent-open" />,
         leaf: <span className="rct-icon rct-icon-leaf" />,
diff --git 
a/superset/assets/src/dashboard/components/filterscope/FilterScopeSelector.jsx 
b/superset/assets/src/dashboard/components/filterscope/FilterScopeSelector.jsx
index 33e31ba..79256ac 100644
--- 
a/superset/assets/src/dashboard/components/filterscope/FilterScopeSelector.jsx
+++ 
b/superset/assets/src/dashboard/components/filterscope/FilterScopeSelector.jsx
@@ -21,6 +21,7 @@ import PropTypes from 'prop-types';
 import cx from 'classnames';
 import { Button } from 'react-bootstrap';
 import { t } from '@superset-ui/translation';
+import { safeStringify } from '../../../utils/safeStringify';
 
 import getFilterScopeNodesTree from '../../util/getFilterScopeNodesTree';
 import getFilterFieldNodesTree from '../../util/getFilterFieldNodesTree';
@@ -216,7 +217,7 @@ export default class FilterScopeSelector extends 
React.PureComponent {
       isSingleEditMode,
       checkedFilterFields,
     });
-    const activeKey = `[${checkedFilterFields.join(',')}]`;
+    const activeKey = safeStringify(checkedFilterFields);
     const checkedChartIdSet = new Set();
     checkedFilterFields.forEach(filterField => {
       (filterScopeMap[filterField].checked || []).forEach(chartId => {
@@ -249,9 +250,21 @@ export default class FilterScopeSelector extends 
React.PureComponent {
     this.setState({ searchText: e.target.value }, this.filterTree);
   }
 
-  onChangeFilterField(activeKey) {
-    if (this.allfilterFields.includes(activeKey)) {
-      this.setState({ activeKey });
+  onChangeFilterField(nextActiveKey) {
+    const {
+      isSingleEditMode,
+      activeKey: currentActiveKey,
+      checkedFilterFields,
+    } = this.state;
+
+    // multi-edit mode: if user click on the single filter field,
+    // will show filter scope for the single field.
+    // if user click on the same filter filed again,
+    // will toggle activeKey back to group selected fields
+    if (!isSingleEditMode && nextActiveKey === currentActiveKey) {
+      this.onCheckFilterField(checkedFilterFields);
+    } else if (this.allfilterFields.includes(nextActiveKey)) {
+      this.setState({ activeKey: nextActiveKey });
     }
   }
 
@@ -395,6 +408,10 @@ export default class FilterScopeSelector extends 
React.PureComponent {
       isSingleEditMode,
       searchText,
     } = this.state;
+
+    const [chartId] = isSingleEditMode
+      ? getDashboardFilterByKey(activeKey)
+      : [0];
     return (
       <React.Fragment>
         <input
@@ -412,6 +429,8 @@ export default class FilterScopeSelector extends 
React.PureComponent {
           expanded={filterScopeMap[activeKey].expanded}
           onCheck={this.onCheckFilterScope}
           onExpand={this.onExpandFilterScope}
+          // hide checkbox for selected filter field itself
+          selectedFilterId={chartId}
         />
       </React.Fragment>
     );
@@ -434,13 +453,31 @@ export default class FilterScopeSelector extends 
React.PureComponent {
   }
 
   render() {
-    const { showSelector, isSingleEditMode } = this.state;
+    const { dashboardFilters } = this.props;
+    const { showSelector, isSingleEditMode, activeKey } = this.state;
+    const isSingleValue = activeKey.indexOf('[') === -1;
+    const currentFilterLabels = []
+      .concat(isSingleValue ? activeKey : JSON.parse(activeKey))
+      .map(key => {
+        const [chartId, column] = getDashboardFilterByKey(key);
+        return dashboardFilters[chartId].labels[column] || column;
+      });
 
     return (
       <React.Fragment>
         <div className="filter-scope-container">
           <div className="filter-scope-header">
             <h4>{t('Configure filter scopes')}</h4>
+            <div
+              className={cx('selected-fields', {
+                'multi-edit-mode': !isSingleEditMode,
+              })}
+            >
+              {`Batch editing ${currentFilterLabels.length} filters: `}
+              <span className="selected-scopes">
+                {currentFilterLabels.join(', ')}
+              </span>
+            </div>
           </div>
 
           {!showSelector && <div>There is no filter in this dashboard</div>}
diff --git 
a/superset/assets/src/dashboard/components/filterscope/FilterScopeTree.jsx 
b/superset/assets/src/dashboard/components/filterscope/FilterScopeTree.jsx
index bc05551..9e869a5 100644
--- a/superset/assets/src/dashboard/components/filterscope/FilterScopeTree.jsx
+++ b/superset/assets/src/dashboard/components/filterscope/FilterScopeTree.jsx
@@ -32,6 +32,7 @@ const propTypes = {
   expanded: PropTypes.arrayOf(PropTypes.string).isRequired,
   onCheck: PropTypes.func.isRequired,
   onExpand: PropTypes.func.isRequired,
+  selectedFilterId: PropTypes.number.isRequired,
 };
 
 export default function FilterScopeTree({
@@ -40,13 +41,14 @@ export default function FilterScopeTree({
   expanded,
   onCheck,
   onExpand,
+  selectedFilterId,
 }) {
   return (
     <CheckboxTree
       showExpandAll
       expandOnClick
       showNodeIcon={false}
-      nodes={renderFilterScopeTreeNodes(nodes)}
+      nodes={renderFilterScopeTreeNodes(nodes, selectedFilterId)}
       checked={checked}
       expanded={expanded}
       onCheck={onCheck}
diff --git 
a/superset/assets/src/dashboard/components/filterscope/renderFilterScopeTreeNodes.jsx
 
b/superset/assets/src/dashboard/components/filterscope/renderFilterScopeTreeNodes.jsx
index 29c448d..5d0fbf1 100644
--- 
a/superset/assets/src/dashboard/components/filterscope/renderFilterScopeTreeNodes.jsx
+++ 
b/superset/assets/src/dashboard/components/filterscope/renderFilterScopeTreeNodes.jsx
@@ -17,8 +17,9 @@
  * under the License.
  */
 import React from 'react';
+import cx from 'classnames';
 
-export default function renderFilterScopeTreeNodes(nodes) {
+export default function renderFilterScopeTreeNodes(nodes, selectedFilterId) {
   if (nodes.length === 0) {
     return [];
   }
@@ -40,10 +41,17 @@ export default function renderFilterScopeTreeNodes(nodes) {
       };
     }
 
+    const { value } = currentNode;
     return {
       ...currentNode,
       label: (
-        <a className={`filter-scope-type ${type.toLowerCase()}`}>{label}</a>
+        <a
+          className={cx(`filter-scope-type ${type.toLowerCase()}`, {
+            'selected-filter': selectedFilterId === value,
+          })}
+        >
+          {label}
+        </a>
       ),
     };
   }
diff --git 
a/superset/assets/src/dashboard/stylesheets/filter-scope-selector.less 
b/superset/assets/src/dashboard/stylesheets/filter-scope-selector.less
index 1adfa14..d57d6de 100644
--- a/superset/assets/src/dashboard/stylesheets/filter-scope-selector.less
+++ b/superset/assets/src/dashboard/stylesheets/filter-scope-selector.less
@@ -22,23 +22,28 @@
   font-family: @font-family-sans-serif;
   font-size: 14px;
 
-  .filter-scope-header {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
+  .nav.nav-tabs {
+    border: none;
+  }
+}
 
-    input {
-      flex: 0 0 200px;
-    }
+.filter-scope-header {
+  h4 {
+    margin-top: 0;
   }
 
-  .nav.nav-tabs {
-    border: none;
+  .selected-fields {
+    margin: 12px 0 16px;
+    visibility: hidden;
+
+    &.multi-edit-mode {
+      visibility: visible;
+    }
   }
 }
 
 .filters-scope-selector {
-  margin: 20px -24px;
+  margin: 10px -24px 20px -24px;
   display: flex;
   flex-direction: row;
   position: relative;
@@ -52,8 +57,8 @@
   }
 
   .filter-field-pane .edit-mode-toggle,
-  .filter-scope-pane .react-checkbox-tree .rct-icon.rct-icon-expand-all,
-  .filter-scope-pane .react-checkbox-tree .rct-icon.rct-icon-collapse-all {
+  .react-checkbox-tree .rct-icon.rct-icon-expand-all,
+  .react-checkbox-tree .rct-icon.rct-icon-collapse-all {
     font-size: 13px;
     font-family: @font-family-sans-serif;
     color: @brand-primary;
@@ -61,22 +66,27 @@
     &:hover {
       text-decoration: underline;
     }
+
+    &:focus {
+      outline: none;
+    }
   }
 
   .filter-field-pane {
+    position: relative;
     width: 40%;
     padding: 16px 16px 16px 24px;
     border-right: 1px solid #ccc;
 
-    .filter-container {
-      svg {
-        position: relative;
-        top: 2px;
-      }
+    .edit-mode-toggle {
+      position: absolute;
+      right: 24px;
+    }
 
+    .filter-container {
       label {
         font-weight: normal;
-        margin: 0 0 0 8px;
+        margin: 0 0 0 16px;
       }
     }
 
@@ -84,14 +94,14 @@
       height: 40px;
       display: flex;
       align-items: center;
-      padding: 0 30px;
-      margin-left: -30px;
+      padding: 0 24px;
+      margin-left: -24px;
 
       &.is-selected {
         border: 1px solid #aaa;
         border-radius: 4px;
         background-color: #eee;
-        margin-left: -31px;
+        margin-left: -25px;
       }
     }
 
@@ -111,15 +121,13 @@
   }
 
   .filter-scope-pane {
+    position: relative;
     flex: 1;
     padding: 16px 24px 16px 16px;
-
-    .react-checkbox-tree {
-      flex-direction: column;
-    }
   }
 
   .react-checkbox-tree {
+    flex-direction: column;
     color: @almost-black;
     font-size: 14px;
 
@@ -132,16 +140,21 @@
         border-radius: 4px;
         padding: 2px 4px;
         font-size: 10px;
-        margin-right: 4px;
+        margin-right: 8px;
         font-weight: 400;
       }
 
       &.chart {
+        font-weight: normal;
         &::before {
           content: 'Chart';
         }
       }
 
+      &.selected-filter {
+        padding-left: 28px;
+      }
+
       &.root {
         font-weight: 700;
       }
@@ -187,6 +200,7 @@
 
     .rct-options {
       text-align: left;
+      margin-left: 0;
     }
 
     .rct-text {
@@ -196,6 +210,7 @@
 
     .rct-title {
       display: block;
+      font-weight: bold;
     }
 
     // disable style from react-checkbox-tress.css

Reply via email to