This is an automated email from the ASF dual-hosted git repository. graceguo pushed a commit to branch scope-selector-modal-v2 in repository https://gitbox.apache.org/repos/asf/incubator-superset.git
commit 7846a8c3c568eb61c4cf6467286892f74c9d8bb1 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
