This is an automated email from the ASF dual-hosted git repository. graceguo pushed a commit to branch update-dashboard-redux-state in repository https://gitbox.apache.org/repos/asf/incubator-superset.git
commit cc6bc04682848a646f4ba68ac8d8aca169827106 Author: Grace <[email protected]> AuthorDate: Mon Oct 14 17:57:34 2019 -0700 another PR --- .../util/getFilterScopeFromNodesTree_spec.js | 67 +++++++++++++ .../src/dashboard/actions/dashboardFilters.js | 16 +++ .../src/dashboard/actions/dashboardLayout.js | 4 + .../assets/src/dashboard/components/Dashboard.jsx | 68 +++++-------- .../components/FilterIndicatorsContainer.jsx | 31 +++--- .../dashboard/components/HeaderActionsDropdown.jsx | 1 + .../src/dashboard/components/SliceHeader.jsx | 2 - .../dashboard/components/SliceHeaderControls.jsx | 5 +- .../components/filterscope/FilterScopeSelector.jsx | 65 ++++++------ .../dashboard/components/gridComponents/Chart.jsx | 16 ++- superset/assets/src/dashboard/containers/Chart.jsx | 11 ++- .../assets/src/dashboard/containers/Dashboard.jsx | 7 +- .../src/dashboard/containers/FilterIndicators.jsx | 6 +- .../src/dashboard/containers/FilterScope.jsx | 12 +-- .../src/dashboard/reducers/dashboardFilters.js | 57 ++++++++++- .../src/dashboard/reducers/getInitialState.js | 55 +++++++++-- .../src/dashboard/util/activeDashboardFilters.js | 110 +++++++++++++++++---- .../util/charts/getEffectiveExtraFilters.js | 43 ++------ .../util/charts/getFormDataWithExtraFilters.js | 10 +- .../src/dashboard/util/getCurrentScopeChartIds.js | 60 ----------- .../src/dashboard/util/getDashboardFilterKey.js | 4 +- .../assets/src/dashboard/util/getDashboardUrl.js | 20 +++- .../dashboard/util/getFilterScopeFromNodesTree.js | 77 +++++++++++++++ ...rdFilterKey.js => getFilterValuesByFilterId.js} | 25 +++-- superset/assets/src/dashboard/util/propShapes.jsx | 34 +++---- 25 files changed, 515 insertions(+), 291 deletions(-) diff --git a/superset/assets/spec/javascripts/dashboard/util/getFilterScopeFromNodesTree_spec.js b/superset/assets/spec/javascripts/dashboard/util/getFilterScopeFromNodesTree_spec.js new file mode 100644 index 0000000..85c9b74 --- /dev/null +++ b/superset/assets/spec/javascripts/dashboard/util/getFilterScopeFromNodesTree_spec.js @@ -0,0 +1,67 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import getFilterScopeFromNodesTree from '../../../../src/dashboard/util/getFilterScopeFromNodesTree'; + +describe('getFilterScopeFromNodesTree', () => { + it('should return empty dashboard', () => { + const nodes = []; + expect( + getFilterScopeFromNodesTree({ + nodes, + checkedChartIds: [], + }), + ).toEqual({ + scope: [], + immune: [], + }); + }); + + it('should return scope for simple grid', () => { + const nodes = [ + { + label: 'All dashboard', + type: 'ROOT', + value: 'ROOT_ID', + children: [ + { + value: 104, + label: 'Life Expectancy VS Rural %', + type: 'CHART', + }, + { value: 105, label: 'Rural Breakdown', type: 'CHART' }, + { + value: 106, + label: "World's Pop Growth", + type: 'CHART', + }, + ], + }, + ]; + const checkedChartIds = [104, 106]; + expect( + getFilterScopeFromNodesTree({ + nodes, + checkedChartIds, + }), + ).toEqual({ + scope: ['ROOT_ID'], + immune: [105], + }); + }); +}); diff --git a/superset/assets/src/dashboard/actions/dashboardFilters.js b/superset/assets/src/dashboard/actions/dashboardFilters.js index c2b8b9c..b8f92b8 100644 --- a/superset/assets/src/dashboard/actions/dashboardFilters.js +++ b/superset/assets/src/dashboard/actions/dashboardFilters.js @@ -46,11 +46,13 @@ export const CHANGE_FILTER = 'CHANGE_FILTER'; export function changeFilter(chartId, newSelectedValues, merge) { return (dispatch, getState) => { if (isValidFilter(getState, chartId)) { + const components = getState().dashboardLayout.present; return dispatch({ type: CHANGE_FILTER, chartId, newSelectedValues, merge, + components, }); } return getState().dashboardFilters; @@ -66,3 +68,17 @@ export function updateDirectPathToFilter(chartId, path) { return getState().dashboardFilters; }; } + +export const UPDATE_LAYOUT_COMPONENTS = 'UPDATE_LAYOUT_COMPONENTS'; +export function updateLayoutComponents(components) { + return dispatch => { + dispatch({ type: UPDATE_LAYOUT_COMPONENTS, components }); + }; +} + +export const UPDATE_DASHBOARD_FILTERS_SCOPE = 'UPDATE_DASHBOARD_FILTERS_SCOPE'; +export function updateDashboardFiltersScope(scopes) { + return dispatch => { + dispatch({ type: UPDATE_DASHBOARD_FILTERS_SCOPE, scopes }); + }; +} diff --git a/superset/assets/src/dashboard/actions/dashboardLayout.js b/superset/assets/src/dashboard/actions/dashboardLayout.js index b276e37..8e517d5 100644 --- a/superset/assets/src/dashboard/actions/dashboardLayout.js +++ b/superset/assets/src/dashboard/actions/dashboardLayout.js @@ -20,6 +20,7 @@ import { ActionCreators as UndoActionCreators } from 'redux-undo'; import { t } from '@superset-ui/translation'; import { addWarningToast } from '../../messageToasts/actions'; +import { updateLayoutComponents } from './dashboardFilters'; import { setUnsavedChanges } from './dashboardState'; import { TABS_TYPE, ROW_TYPE } from '../util/componentTypes'; import { @@ -42,6 +43,9 @@ function setUnsavedChangesAfterAction(action) { dispatch(result); } + const components = getState().dashboardLayout.present; + dispatch(updateLayoutComponents(components)); + if (!getState().dashboardState.hasUnsavedChanges) { dispatch(setUnsavedChanges(true)); } diff --git a/superset/assets/src/dashboard/components/Dashboard.jsx b/superset/assets/src/dashboard/components/Dashboard.jsx index 334704e..0e789ff 100644 --- a/superset/assets/src/dashboard/components/Dashboard.jsx +++ b/superset/assets/src/dashboard/components/Dashboard.jsx @@ -28,14 +28,13 @@ import { slicePropShape, dashboardInfoPropShape, dashboardStatePropShape, - loadStatsPropShape, } from '../util/propShapes'; -import { areObjectsEqual } from '../../reduxUtils'; import { LOG_ACTIONS_MOUNT_DASHBOARD } from '../../logger/LogUtils'; import OmniContainer from '../../components/OmniContainer'; import { safeStringify } from '../../utils/safeStringify'; import '../stylesheets/index.less'; +import { getDashboardFilterByKey } from '../util/getDashboardFilterKey'; const propTypes = { actions: PropTypes.shape({ @@ -50,7 +49,6 @@ const propTypes = { slices: PropTypes.objectOf(slicePropShape).isRequired, filters: PropTypes.object.isRequired, datasources: PropTypes.object.isRequired, - loadStats: loadStatsPropShape.isRequired, layout: PropTypes.object.isRequired, impressionId: PropTypes.string.isRequired, initMessages: PropTypes.array, @@ -122,27 +120,31 @@ class Dashboard extends React.PureComponent { // do not apply filter when dashboard in edit mode if (!editMode && safeStringify(appliedFilters) !== safeStringify(filters)) { // refresh charts if a filter was removed, added, or changed - let changedFilterKey = null; const currFilterKeys = Object.keys(filters); const appliedFilterKeys = Object.keys(appliedFilters); - currFilterKeys.forEach(key => { - if ( - // filter was added or changed - typeof appliedFilters[key] === 'undefined' || - !areObjectsEqual(appliedFilters[key], filters[key]) + const allKeys = new Set(currFilterKeys.concat(appliedFilterKeys)); + [...allKeys].forEach(filterKey => { + const affectedChartIds = []; + if (!currFilterKeys.includes(filterKey)) { + // removed filter? + [].push.apply(affectedChartIds, appliedFilters[filterKey].scope); + } else if (!appliedFilterKeys.includes(filterKey)) { + // added filter? + [].push.apply(affectedChartIds, filters[filterKey].scope); + } else if ( + safeStringify(filters[filterKey].values) !== + safeStringify(appliedFilters[filterKey].values) ) { - changedFilterKey = key; + // changed filter field value? + [].push.apply(affectedChartIds, filters[filterKey].scope); } + + const idSet = new Set(affectedChartIds); + this.refreshExcept([...idSet]); }); - if ( - !!changedFilterKey || - currFilterKeys.length !== appliedFilterKeys.length // remove 1 or more filters - ) { - this.refreshExcept(changedFilterKey); - this.appliedFilters = filters; - } + this.appliedFilters = filters; } if (hasUnsavedChanges) { @@ -157,35 +159,9 @@ class Dashboard extends React.PureComponent { return Object.values(this.props.charts); } - refreshExcept(filterKey) { - const { filters } = this.props; - const currentFilteredNames = - filterKey && filters[filterKey] ? Object.keys(filters[filterKey]) : []; - const filterImmuneSlices = this.props.dashboardInfo.metadata - .filterImmuneSlices; - const filterImmuneSliceFields = this.props.dashboardInfo.metadata - .filterImmuneSliceFields; - - this.getAllCharts().forEach(chart => { - // filterKey is a string, filter_immune_slices array contains numbers - if ( - String(chart.id) === filterKey || - filterImmuneSlices.includes(chart.id) - ) { - return; - } - - const filterImmuneSliceFieldsNames = - filterImmuneSliceFields[chart.id] || []; - // has filter-able field names - if ( - currentFilteredNames.length === 0 || - currentFilteredNames.some( - name => !filterImmuneSliceFieldsNames.includes(name), - ) - ) { - this.props.actions.triggerQuery(true, chart.id); - } + refreshExcept(ids) { + ids.forEach(id => { + this.props.actions.triggerQuery(true, id); }); } diff --git a/superset/assets/src/dashboard/components/FilterIndicatorsContainer.jsx b/superset/assets/src/dashboard/components/FilterIndicatorsContainer.jsx index 6c8b6cf..f4aa83c 100644 --- a/superset/assets/src/dashboard/components/FilterIndicatorsContainer.jsx +++ b/superset/assets/src/dashboard/components/FilterIndicatorsContainer.jsx @@ -23,6 +23,7 @@ import { isEmpty } from 'lodash'; import FilterIndicator from './FilterIndicator'; import FilterIndicatorGroup from './FilterIndicatorGroup'; import { FILTER_INDICATORS_DISPLAY_LENGTH } from '../util/constants'; +import { getChartIdsInFilterScope } from '../util/activeDashboardFilters'; import { getDashboardFilterKey } from '../util/getDashboardFilterKey'; import { getFilterColorMap } from '../util/dashboardFiltersColorMap'; @@ -33,8 +34,6 @@ const propTypes = { chartStatus: PropTypes.string, // from redux - filterImmuneSlices: PropTypes.arrayOf(PropTypes.number).isRequired, - filterImmuneSliceFields: PropTypes.object.isRequired, setDirectPathToChild: PropTypes.func.isRequired, filterFieldOnFocus: PropTypes.object.isRequired, }; @@ -59,8 +58,6 @@ export default class FilterIndicatorsContainer extends React.PureComponent { const { dashboardFilters, chartId: currentChartId, - filterImmuneSlices, - filterImmuneSliceFields, filterFieldOnFocus, } = this.props; @@ -76,20 +73,23 @@ export default class FilterIndicatorsContainer extends React.PureComponent { chartId, componentId, directPathToFilter, - scope, isDateFilter, isInstantFilter, columns, labels, + scopes, } = dashboardFilter; - // do not apply filter on filter_box itself - // do not apply filter on filterImmuneSlices list - if ( - currentChartId !== chartId && - !filterImmuneSlices.includes(currentChartId) - ) { + if (currentChartId !== chartId) { Object.keys(columns).forEach(name => { + const chartIdsInFilterScope = getChartIdsInFilterScope({ + filterScope: scopes[name], + }); + + if (!chartIdsInFilterScope.includes(currentChartId)) { + return; + } + const colorMapKey = getDashboardFilterKey(chartId, name); const directPathToLabel = directPathToFilter.slice(); directPathToLabel.push(`LABEL-${name}`); @@ -98,7 +98,6 @@ export default class FilterIndicatorsContainer extends React.PureComponent { colorCode: dashboardFiltersColorMap[colorMapKey], componentId, directPathToFilter: directPathToLabel, - scope, isDateFilter, isInstantFilter, name, @@ -113,14 +112,6 @@ export default class FilterIndicatorsContainer extends React.PureComponent { name === filterFieldOnFocus.column, }; - // do not apply filter on fields in the filterImmuneSliceFields map - if ( - filterImmuneSliceFields[currentChartId] && - filterImmuneSliceFields[currentChartId].includes(name) - ) { - return; - } - if (isEmpty(indicator.values)) { indicators[1].push(indicator); } else { diff --git a/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx b/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx index 6fc0182..c407443 100644 --- a/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx +++ b/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx @@ -51,6 +51,7 @@ const propTypes = { isLoading: PropTypes.bool.isRequired, layout: PropTypes.object.isRequired, filters: PropTypes.object.isRequired, + filterScopes: PropTypes.object.isRequired, expandedSlices: PropTypes.object.isRequired, onSave: PropTypes.func.isRequired, }; diff --git a/superset/assets/src/dashboard/components/SliceHeader.jsx b/superset/assets/src/dashboard/components/SliceHeader.jsx index 100a070..3d36a51 100644 --- a/superset/assets/src/dashboard/components/SliceHeader.jsx +++ b/superset/assets/src/dashboard/components/SliceHeader.jsx @@ -94,7 +94,6 @@ class SliceHeader extends React.PureComponent { annotationQuery, annotationError, componentId, - filters, addDangerToast, } = this.props; @@ -146,7 +145,6 @@ class SliceHeader extends React.PureComponent { supersetCanCSV={supersetCanCSV} sliceCanEdit={sliceCanEdit} componentId={componentId} - filters={filters} addDangerToast={addDangerToast} /> )} diff --git a/superset/assets/src/dashboard/components/SliceHeaderControls.jsx b/superset/assets/src/dashboard/components/SliceHeaderControls.jsx index 65565f0..779609d 100644 --- a/superset/assets/src/dashboard/components/SliceHeaderControls.jsx +++ b/superset/assets/src/dashboard/components/SliceHeaderControls.jsx @@ -23,11 +23,11 @@ import { Dropdown, MenuItem } from 'react-bootstrap'; import { t } from '@superset-ui/translation'; import URLShortLinkModal from '../../components/URLShortLinkModal'; import getDashboardUrl from '../util/getDashboardUrl'; +import { getActiveFilters } from '../util/activeDashboardFilters'; const propTypes = { slice: PropTypes.object.isRequired, componentId: PropTypes.string.isRequired, - filters: PropTypes.object.isRequired, addDangerToast: PropTypes.func.isRequired, isCached: PropTypes.bool, isExpanded: PropTypes.bool, @@ -107,7 +107,6 @@ class SliceHeaderControls extends React.PureComponent { isCached, cachedDttm, updatedDttm, - filters, componentId, addDangerToast, } = this.props; @@ -162,7 +161,7 @@ class SliceHeaderControls extends React.PureComponent { <URLShortLinkModal url={getDashboardUrl( window.location.pathname, - filters, + getActiveFilters(), componentId, )} addDangerToast={addDangerToast} diff --git a/superset/assets/src/dashboard/components/filterscope/FilterScopeSelector.jsx b/superset/assets/src/dashboard/components/filterscope/FilterScopeSelector.jsx index 8028271..126fa39 100644 --- a/superset/assets/src/dashboard/components/filterscope/FilterScopeSelector.jsx +++ b/superset/assets/src/dashboard/components/filterscope/FilterScopeSelector.jsx @@ -25,19 +25,20 @@ import { t } from '@superset-ui/translation'; import getFilterScopeNodesTree from '../../util/getFilterScopeNodesTree'; import getFilterFieldNodesTree from '../../util/getFilterFieldNodesTree'; import getFilterScopeParentNodes from '../../util/getFilterScopeParentNodes'; -import getCurrentScopeChartIds from '../../util/getCurrentScopeChartIds'; +import getFilterScopeFromNodesTree from '../../util/getFilterScopeFromNodesTree'; import getRevertedFilterScope from '../../util/getRevertedFilterScope'; import FilterScopeTree from './FilterScopeTree'; import FilterFieldTree from './FilterFieldTree'; +import { getChartIdsInFilterScope } from '../../util/activeDashboardFilters'; import { getDashboardFilterKey } from '../../util/getDashboardFilterKey'; +import { dashboardFilterPropShape } from '../../util/propShapes'; const propTypes = { - dashboardFilters: PropTypes.object.isRequired, + dashboardFilters: dashboardFilterPropShape.isRequired, layout: PropTypes.object.isRequired, - filterImmuneSlices: PropTypes.arrayOf(PropTypes.number).isRequired, - filterImmuneSliceFields: PropTypes.object.isRequired, - setDirectPathToChild: PropTypes.func.isRequired, + updateDashboardFiltersScope: PropTypes.func.isRequired, + setUnsavedChanges: PropTypes.func.isRequired, onCloseModal: PropTypes.func.isRequired, }; @@ -45,12 +46,7 @@ export default class FilterScopeSelector extends React.PureComponent { constructor(props) { super(props); - const { - dashboardFilters, - filterImmuneSlices, - filterImmuneSliceFields, - layout, - } = props; + const { dashboardFilters, layout } = props; if (Object.keys(dashboardFilters).length > 0) { // display filter fields in tree structure @@ -72,7 +68,7 @@ export default class FilterScopeSelector extends React.PureComponent { // do not expand filter box const expandedFilterIds = []; - // display checkbox tree of whole dashboard layout + // display whole dashboard layout in tree structure const nodes = getFilterScopeNodesTree({ components: layout, isSingleEditMode: true, @@ -84,6 +80,14 @@ export default class FilterScopeSelector extends React.PureComponent { const filterScopeByChartId = Object.keys(columns).reduce( (mapByChartId, columnName) => { const filterKey = getDashboardFilterKey(chartId, columnName); + const chartIdsInFilterScope = getChartIdsInFilterScope({ + filterScope: dashboardFilters[chartId].scopes[columnName], + }); + // remove filter's id from its scope + chartIdsInFilterScope.splice( + chartIdsInFilterScope.indexOf(chartId), + 1, + ); return { ...mapByChartId, [filterKey]: { @@ -91,13 +95,7 @@ export default class FilterScopeSelector extends React.PureComponent { nodes, // filtered nodes in display if searchText is not empty nodesFiltered: nodes.slice(), - checked: getCurrentScopeChartIds({ - scopeComponentIds: ['ROOT_ID'], //dashboardFilters[chartId].scopes[columnName], - filterField: columnName, - filterImmuneSlices, - filterImmuneSliceFields, - components: layout, - }), + checked: chartIdsInFilterScope.slice(), expanded, }, }; @@ -251,11 +249,7 @@ export default class FilterScopeSelector extends React.PureComponent { } onToggleEditMode() { - const { - activeKey, - isSingleEditMode, - checkedFilterFields, - } = this.state; + const { activeKey, isSingleEditMode, checkedFilterFields } = this.state; const { dashboardFilters } = this.props; if (isSingleEditMode) { // single edit => multi edit @@ -296,16 +290,21 @@ export default class FilterScopeSelector extends React.PureComponent { onSave() { const { filterScopeMap } = this.state; - console.log( - 'i am current state', - this.allfilterFields.reduce( - (map, key) => ({ - ...map, - [key]: filterScopeMap[key].checked, - }), - {}, - ), + const currentFiltersState = this.allfilterFields.reduce( + (map, key) => ({ + ...map, + [key]: { + nodes: filterScopeMap[key].nodes, + checked: filterScopeMap[key].checked, + }, + }), + {}, + ); + console.log('i am current state', currentFiltersState); + this.props.updateDashboardFiltersScope( + getFilterScopeFromNodesTree(currentFiltersState), ); + this.props.setUnsavedChanges(true); this.props.onCloseModal(); } diff --git a/superset/assets/src/dashboard/components/gridComponents/Chart.jsx b/superset/assets/src/dashboard/components/gridComponents/Chart.jsx index 4d34cc6..d50c24c 100644 --- a/superset/assets/src/dashboard/components/gridComponents/Chart.jsx +++ b/superset/assets/src/dashboard/components/gridComponents/Chart.jsx @@ -30,6 +30,9 @@ import { LOG_ACTIONS_EXPORT_CSV_DASHBOARD_CHART, LOG_ACTIONS_FORCE_REFRESH_CHART, } from '../../../logger/LogUtils'; +import { safeStringify } from '../../../utils/safeStringify'; +import { isFilterBox } from '../../util/activeDashboardFilters'; +import getFilterValuesByFilterId from '../../util/getFilterValuesByFilterId'; const propTypes = { id: PropTypes.number.isRequired, @@ -46,6 +49,7 @@ const propTypes = { slice: slicePropShape.isRequired, sliceName: PropTypes.string.isRequired, timeout: PropTypes.number.isRequired, + // all active filter fields in dashboard filters: PropTypes.object.isRequired, refreshChart: PropTypes.func.isRequired, logEvent: PropTypes.func.isRequired, @@ -115,7 +119,9 @@ class Chart extends React.Component { for (let i = 0; i < SHOULD_UPDATE_ON_PROP_CHANGES.length; i += 1) { const prop = SHOULD_UPDATE_ON_PROP_CHANGES[i]; - if (nextProps[prop] !== this.props[prop]) { + if ( + safeStringify(nextProps[prop]) !== safeStringify(this.props[prop]) + ) { return true; } } @@ -238,6 +244,12 @@ class Chart extends React.Component { const isCached = queryResponse && queryResponse.is_cached; const cachedDttm = queryResponse && queryResponse.cached_dttm; const isOverflowable = OVERFLOWABLE_VIZ_TYPES.has(slice.viz_type); + const initialValues = isFilterBox(id) + ? getFilterValuesByFilterId({ + activeFilters: filters, + filterId: id, + }) + : {}; return ( <div> @@ -297,7 +309,7 @@ class Chart extends React.Component { chartId={id} chartStatus={chart.chartStatus} datasource={datasource} - initialValues={filters[id]} + initialValues={initialValues} formData={formData} queryResponse={chart.queryResponse} timeout={timeout} diff --git a/superset/assets/src/dashboard/containers/Chart.jsx b/superset/assets/src/dashboard/containers/Chart.jsx index 5f0b107..361fe47 100644 --- a/superset/assets/src/dashboard/containers/Chart.jsx +++ b/superset/assets/src/dashboard/containers/Chart.jsx @@ -29,7 +29,10 @@ import { changeFilter } from '../actions/dashboardFilters'; import { addDangerToast } from '../../messageToasts/actions'; import { refreshChart } from '../../chart/chartAction'; import { logEvent } from '../../logger/actions'; -import { getActiveFilters } from '../util/activeDashboardFilters'; +import { + getActiveFilters, + getAppliedFilterValues, +} from '../util/activeDashboardFilters'; import getFormDataWithExtraFilters from '../util/charts/getFormDataWithExtraFilters'; import Chart from '../components/gridComponents/Chart'; @@ -48,7 +51,6 @@ function mapStateToProps( const { id } = ownProps; const chart = chartQueries[id] || {}; const { colorScheme, colorNamespace } = dashboardState; - const filters = getActiveFilters(); return { chart, @@ -57,12 +59,11 @@ function mapStateToProps( {}, slice: sliceEntities.slices[id], timeout: dashboardInfo.common.conf.SUPERSET_WEBSERVER_TIMEOUT, - filters: filters || EMPTY_FILTERS, + filters: getActiveFilters() || EMPTY_FILTERS, // note: this method caches filters if possible to prevent render cascades formData: getFormDataWithExtraFilters({ chart, - dashboardMetadata: dashboardInfo.metadata, - filters, + filters: getAppliedFilterValues(id), colorScheme, colorNamespace, sliceId: id, diff --git a/superset/assets/src/dashboard/containers/Dashboard.jsx b/superset/assets/src/dashboard/containers/Dashboard.jsx index c602cd3..02472ac 100644 --- a/superset/assets/src/dashboard/containers/Dashboard.jsx +++ b/superset/assets/src/dashboard/containers/Dashboard.jsx @@ -27,7 +27,6 @@ import { } from '../actions/dashboardState'; import { triggerQuery } from '../../chart/chartAction'; import { logEvent } from '../../logger/actions'; -import getLoadStatsPerTopLevelComponent from '../util/logging/getLoadStatsPerTopLevelComponent'; import { getActiveFilters } from '../util/activeDashboardFilters'; function mapStateToProps(state) { @@ -49,7 +48,7 @@ function mapStateToProps(state) { dashboardState, charts, datasources, - // filters prop: All the filter_box's state in this dashboard + // filters prop: All the active filter_box's values and scope in this dashboard, // When dashboard is first loaded into browser, // its value is from preselect_filters that dashboard owner saved in dashboard's meta data // When user start interacting with dashboard, it will be user picked values from all filter_box @@ -57,10 +56,6 @@ function mapStateToProps(state) { slices: sliceEntities.slices, layout: dashboardLayout.present, impressionId, - loadStats: getLoadStatsPerTopLevelComponent({ - layout: dashboardLayout.present, - chartQueries: charts, - }), }; } diff --git a/superset/assets/src/dashboard/containers/FilterIndicators.jsx b/superset/assets/src/dashboard/containers/FilterIndicators.jsx index 9ac8d62..80e3f77 100644 --- a/superset/assets/src/dashboard/containers/FilterIndicators.jsx +++ b/superset/assets/src/dashboard/containers/FilterIndicators.jsx @@ -23,7 +23,7 @@ import FilterIndicatorsContainer from '../components/FilterIndicatorsContainer'; import { setDirectPathToChild } from '../actions/dashboardState'; function mapStateToProps( - { dashboardState, dashboardFilters, dashboardInfo, charts }, + { dashboardState, dashboardFilters, dashboardLayout, charts }, ownProps, ) { const chartId = ownProps.chartId; @@ -33,9 +33,7 @@ function mapStateToProps( dashboardFilters, chartId, chartStatus, - filterImmuneSlices: dashboardInfo.metadata.filterImmuneSlices || [], - filterImmuneSliceFields: - dashboardInfo.metadata.filterImmuneSliceFields || {}, + layout: dashboardLayout.present, filterFieldOnFocus: dashboardState.focusedFilterField.length === 0 ? {} diff --git a/superset/assets/src/dashboard/containers/FilterScope.jsx b/superset/assets/src/dashboard/containers/FilterScope.jsx index 38a5a08..d816df9 100644 --- a/superset/assets/src/dashboard/containers/FilterScope.jsx +++ b/superset/assets/src/dashboard/containers/FilterScope.jsx @@ -19,24 +19,22 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; -import { setDirectPathToChild } from '../actions/dashboardState'; +import { updateDashboardFiltersScope } from '../actions/dashboardFilters'; +import { setUnsavedChanges } from '../actions/dashboardState'; import FilterScopeSelector from '../components/filterscope/FilterScopeSelector'; -function mapStateToProps({ dashboardLayout, dashboardFilters, dashboardInfo }, ownProps) { +function mapStateToProps({ dashboardLayout, dashboardFilters }) { return { dashboardFilters, - filterImmuneSlices: dashboardInfo.metadata.filterImmuneSlices || [], - filterImmuneSliceFields: - dashboardInfo.metadata.filterImmuneSliceFields || {}, layout: dashboardLayout.present, - // closeModal: ownProps.onCloseModal, }; } function mapDispatchToProps(dispatch) { return bindActionCreators( { - setDirectPathToChild, + updateDashboardFiltersScope, + setUnsavedChanges, }, dispatch, ); diff --git a/superset/assets/src/dashboard/reducers/dashboardFilters.js b/superset/assets/src/dashboard/reducers/dashboardFilters.js index 1525462..74033b1 100644 --- a/superset/assets/src/dashboard/reducers/dashboardFilters.js +++ b/superset/assets/src/dashboard/reducers/dashboardFilters.js @@ -22,11 +22,20 @@ import { REMOVE_FILTER, CHANGE_FILTER, UPDATE_DIRECT_PATH_TO_FILTER, + UPDATE_LAYOUT_COMPONENTS, + UPDATE_DASHBOARD_FILTERS_SCOPE, } from '../actions/dashboardFilters'; import { TIME_RANGE } from '../../visualizations/FilterBox/FilterBox'; +import { DASHBOARD_ROOT_ID } from '../util/constants'; import getFilterConfigsFromFormdata from '../util/getFilterConfigsFromFormdata'; import { buildFilterColorMap } from '../util/dashboardFiltersColorMap'; import { buildActiveFilters } from '../util/activeDashboardFilters'; +import {getDashboardFilterByKey} from "../util/getDashboardFilterKey"; + +export const DASHBOARD_FILTER_SCOPE_GLOBAL = { + scope: [DASHBOARD_ROOT_ID], + immune: [], +}; export const dashboardFilter = { chartId: 0, @@ -40,6 +49,8 @@ export const dashboardFilter = { scopes: {}, }; +let dashboardLayoutComponents = {}; + export default function dashboardFiltersReducer(dashboardFilters = {}, action) { const actionHandlers = { [ADD_FILTER]() { @@ -57,6 +68,13 @@ export default function dashboardFiltersReducer(dashboardFilters = {}, action) { directPathToFilter, columns, labels, + scopes: Object.keys(columns).reduce( + (map, column) => ({ + ...map, + [column]: DASHBOARD_FILTER_SCOPE_GLOBAL, + }), + {}, + ), isInstantFilter: !!form_data.instant_filtering, isDateFilter: Object.keys(columns).includes(TIME_RANGE), }; @@ -99,11 +117,40 @@ export default function dashboardFiltersReducer(dashboardFilters = {}, action) { }, }; - if (action.type === REMOVE_FILTER) { + if (action.type === UPDATE_LAYOUT_COMPONENTS) { + dashboardLayoutComponents = action.components; + buildActiveFilters(dashboardFilters, dashboardLayoutComponents); + return dashboardFilters; + } else if (action.type === UPDATE_DASHBOARD_FILTERS_SCOPE) { + const allDashboardFiltersScope = action.scopes; + // fulfill filter scope by each filter field + const updatedDashboardFilters = Object.entries( + allDashboardFiltersScope, + ).reduce(({ map, entry }) => { + const [filterKey, { scope, immune }] = entry; + const [chartId, column] = getDashboardFilterByKey(filterKey); + const scopes = { + ...map[chartId].scopes, + [column]: { + scope, + immune, + }, + }; + return { + ...map, + [chartId]: { + ...map[chartId], + scopes, + }, + }; + }, dashboardFilters); + + return updatedDashboardFilters; + } else if (action.type === REMOVE_FILTER) { const { chartId } = action; const { [chartId]: deletedFilter, ...updatedFilters } = dashboardFilters; - buildActiveFilters(updatedFilters); - buildFilterColorMap(updatedFilters); + buildActiveFilters(updatedFilters, dashboardLayoutComponents); + buildFilterColorMap(updatedFilters, dashboardLayoutComponents); return updatedFilters; } else if (action.type in actionHandlers) { @@ -113,8 +160,8 @@ export default function dashboardFiltersReducer(dashboardFilters = {}, action) { dashboardFilters[action.chartId], ), }; - buildActiveFilters(updatedFilters); - buildFilterColorMap(updatedFilters); + buildActiveFilters(updatedFilters, dashboardLayoutComponents); + buildFilterColorMap(updatedFilters, dashboardLayoutComponents); return updatedFilters; } diff --git a/superset/assets/src/dashboard/reducers/getInitialState.js b/superset/assets/src/dashboard/reducers/getInitialState.js index 185a8b0..9939f15 100644 --- a/superset/assets/src/dashboard/reducers/getInitialState.js +++ b/superset/assets/src/dashboard/reducers/getInitialState.js @@ -22,7 +22,10 @@ import shortid from 'shortid'; import { CategoricalColorNamespace } from '@superset-ui/color'; import { chart } from '../../chart/chartReducer'; -import { dashboardFilter } from './dashboardFilters'; +import { + DASHBOARD_FILTER_SCOPE_GLOBAL, + dashboardFilter, +} from './dashboardFilters'; import { initSliceEntities } from './sliceEntities'; import { getParam } from '../../modules/utils'; import { applyDefaultFormData } from '../../explore/store'; @@ -98,6 +101,20 @@ export default function(bootstrapData) { let newSlicesContainer; let newSlicesContainerWidth = 0; + const filterImmuneSliceFields = dashboard.metadata.filter_immune_slice_fields; + // { + // '105': ['country_name'], + // }; + const filterImmuneSlices = dashboard.metadata.filter_immune_slices; + // [104]; + const filterScopes = dashboard.metadata.filter_scopes || {}; + // '108': { + // __time_range: { + // scope: ['TAB-oMbymVL_s'], + // immune: [105], + // }, + // } + const chartQueries = {}; const dashboardFilters = {}; const slices = {}; @@ -173,6 +190,34 @@ export default function(bootstrapData) { }); } + // backward compatible: + // merge scoped filter settings with global immune settings + const scopesByColumnName = Object.keys(columns).reduce( + (map, column) => { + const { scope, immune } = { + ...DASHBOARD_FILTER_SCOPE_GLOBAL, + ...filterScopes[key], + }; + const immuneChartIds = new Set(filterImmuneSlices.slice()); + if (Object.keys(filterImmuneSliceFields).length) { + Object.keys(filterImmuneSliceFields).forEach(strChartId => { + if (filterImmuneSliceFields[strChartId].includes(column)) { + immuneChartIds.add(parseInt(strChartId, 10)); + } + }); + } + + return { + ...map, + [column]: { + scope, + immune: [...immuneChartIds].concat(immune), + }, + }; + }, + {}, + ); + const componentId = chartIdToLayoutId[key]; const directPathToFilter = (layout[componentId].parents || []).slice(); directPathToFilter.push(componentId); @@ -184,6 +229,7 @@ export default function(bootstrapData) { directPathToFilter, columns, labels, + scopes: scopesByColumnName, isInstantFilter: !!slice.form_data.instant_filtering, isDateFilter: Object.keys(columns).includes(TIME_RANGE), }; @@ -198,8 +244,8 @@ export default function(bootstrapData) { layout[layoutId].meta.sliceName = slice.slice_name; } }); - buildActiveFilters(dashboardFilters); - buildFilterColorMap(dashboardFilters); + buildActiveFilters(dashboardFilters, layout); + buildFilterColorMap(dashboardFilters, layout); // store the header as a layout component so we can undo/redo changes layout[DASHBOARD_HEADER_ID] = { @@ -233,9 +279,6 @@ export default function(bootstrapData) { id: dashboard.id, slug: dashboard.slug, metadata: { - filterImmuneSliceFields: - dashboard.metadata.filter_immune_slice_fields || {}, - filterImmuneSlices: dashboard.metadata.filter_immune_slices || [], timed_refresh_immune_slices: dashboard.metadata.timed_refresh_immune_slices, }, diff --git a/superset/assets/src/dashboard/util/activeDashboardFilters.js b/superset/assets/src/dashboard/util/activeDashboardFilters.js index ea8e4e5..d8fc364 100644 --- a/superset/assets/src/dashboard/util/activeDashboardFilters.js +++ b/superset/assets/src/dashboard/util/activeDashboardFilters.js @@ -16,49 +16,121 @@ * specific language governing permissions and limitations * under the License. */ -let activeFilters = {}; +import { + getDashboardFilterByKey, + getDashboardFilterKey, +} from './getDashboardFilterKey'; +import { CHART_TYPE } from '../util/componentTypes'; + let allFilterIds = []; +let activeFilters = {}; +let appliedFilterValuesByChart = {}; +let allComponents = {}; +// output: { [id_column]: { values, scope } } export function getActiveFilters() { return activeFilters; } -// currently filterbox is a chart, +// currently filter_box is a chart, // when define filter scopes, they have to be out pulled out in a few places. -// after we make filterbox a dashboard build-in component, +// after we make filter_box a dashboard build-in component, // will not need this check anymore export function isFilterBox(chartId) { return allFilterIds.includes(chartId); } -export function getAllFilterIds() { - return allFilterIds; +// output: { [column]: values } +export function getAppliedFilterValues(chartId) { + if (!(chartId in appliedFilterValuesByChart)) { + appliedFilterValuesByChart[chartId] = Object.entries(activeFilters).reduce( + (map, entry) => { + const [filterKey, { scope: chartIds, values }] = entry; + if (chartIds.includes(chartId)) { + const [, column] = getDashboardFilterByKey(filterKey); + return { + ...map, + [column]: values, + }; + } + return map; + }, + {}, + ); + } + return appliedFilterValuesByChart[chartId]; +} + +export function getChartIdsInFilterScope({ filterScope }) { + function traverse(chartIds, component, immuneChartIds) { + if (!component) { + return; + } + + if ( + component.type === CHART_TYPE && + component.meta && + component.meta.chartId && + !immuneChartIds.includes(component.meta.chartId) + ) { + chartIds.push(component.meta.chartId); + } else if (component.children) { + component.children.forEach(child => + traverse(chartIds, allComponents[child], immuneChartIds), + ); + } + } + + const chartIds = []; + const { scope: scopeComponentIds, immune: immuneChartIds } = filterScope; + scopeComponentIds.forEach(componentId => + traverse(chartIds, allComponents[componentId], immuneChartIds), + ); + + return chartIds; } -// non-empty filters from dashboardFilters, -// this function does not take into account: filter immune or filter scope settings -export function buildActiveFilters(allDashboardFilters = {}) { - allFilterIds = Object.values(allDashboardFilters).map(filter => filter.chartId); +// non-empty filters list in dashboardFilters, +// it contains selected values and filter scope +// values: array of selected values +// scope: array of chartIds +export function buildActiveFilters(allDashboardFilters = {}, components = {}) { + allFilterIds = Object.values(allDashboardFilters).map( + filter => filter.chartId, + ); + // clear cache + allComponents = components; + appliedFilterValuesByChart = {}; activeFilters = Object.values(allDashboardFilters).reduce( (result, filter) => { - const { chartId, columns } = filter; + const { chartId, columns, scopes } = filter; + const nonEmptyFilters = {}; - Object.keys(columns).forEach(key => { + Object.keys(columns).forEach(column => { if ( - Array.isArray(columns[key]) - ? columns[key].length - : columns[key] !== undefined + Array.isArray(columns[column]) + ? columns[column].length + : columns[column] !== undefined ) { - /* eslint-disable no-param-reassign */ - result[chartId] = { - ...result[chartId], - [key]: columns[key], + const scope = getChartIdsInFilterScope({ + filterScope: scopes[column], + }); + // remove filter itself + if (scope.length) { + scope.splice(scope.indexOf(chartId), 1); + } + nonEmptyFilters[getDashboardFilterKey(chartId, column)] = { + values: columns[column], + scope, }; } }); - return result; + return { + ...result, + ...nonEmptyFilters, + }; }, {}, ); diff --git a/superset/assets/src/dashboard/util/charts/getEffectiveExtraFilters.js b/superset/assets/src/dashboard/util/charts/getEffectiveExtraFilters.js index fa6ad2a..53ead91 100644 --- a/superset/assets/src/dashboard/util/charts/getEffectiveExtraFilters.js +++ b/superset/assets/src/dashboard/util/charts/getEffectiveExtraFilters.js @@ -16,43 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -export default function getEffectiveExtraFilters({ - dashboardMetadata, - filters, - sliceId, -}) { - const immuneSlices = dashboardMetadata.filterImmuneSlices || []; - - if (sliceId && immuneSlices.includes(sliceId)) { - // The slice is immune to dashboard filters - return []; - } - - // Build a list of fields the slice is immune to filters on +export default function getEffectiveExtraFilters(filters) { const effectiveFilters = []; - let immuneToFields = []; - if ( - sliceId && - dashboardMetadata.filterImmuneSliceFields && - dashboardMetadata.filterImmuneSliceFields[sliceId] - ) { - immuneToFields = dashboardMetadata.filterImmuneSliceFields[sliceId]; - } - - Object.keys(filters).forEach(filteringSliceId => { - if (filteringSliceId === sliceId.toString()) { - // Filters applied by the slice don't apply to itself - return; - } - const filtersFromSlice = filters[filteringSliceId]; - Object.keys(filtersFromSlice).forEach(field => { - if (!immuneToFields.includes(field)) { - effectiveFilters.push({ - col: field, - op: 'in', - val: filtersFromSlice[field], - }); - } + Object.entries(filters).forEach(entry => { + const [column, values] = entry; + effectiveFilters.push({ + col: column, + op: 'in', + val: values, }); }); diff --git a/superset/assets/src/dashboard/util/charts/getFormDataWithExtraFilters.js b/superset/assets/src/dashboard/util/charts/getFormDataWithExtraFilters.js index 5869a09..5aa8d0f 100644 --- a/superset/assets/src/dashboard/util/charts/getFormDataWithExtraFilters.js +++ b/superset/assets/src/dashboard/util/charts/getFormDataWithExtraFilters.js @@ -22,13 +22,11 @@ import getEffectiveExtraFilters from './getEffectiveExtraFilters'; // We cache formData objects so that our connected container components don't always trigger // render cascades. we cannot leverage the reselect library because our cache size is >1 -const cachedDashboardMetadataByChart = {}; const cachedFiltersByChart = {}; const cachedFormdataByChart = {}; export default function getFormDataWithExtraFilters({ chart = {}, - dashboardMetadata, filters, colorScheme, colorNamespace, @@ -40,7 +38,6 @@ export default function getFormDataWithExtraFilters({ // if dashboard metadata + filters have not changed, use cache if possible if ( - (cachedDashboardMetadataByChart[sliceId] || {}) === dashboardMetadata && (cachedFiltersByChart[sliceId] || {}) === filters && (colorScheme == null || cachedFormdataByChart[sliceId].color_scheme === colorScheme) && @@ -55,14 +52,9 @@ export default function getFormDataWithExtraFilters({ ...chart.formData, ...(colorScheme && { color_scheme: colorScheme }), label_colors: labelColors, - extra_filters: getEffectiveExtraFilters({ - dashboardMetadata, - filters, - sliceId, - }), + extra_filters: getEffectiveExtraFilters(filters), }; - cachedDashboardMetadataByChart[sliceId] = dashboardMetadata; cachedFiltersByChart[sliceId] = filters; cachedFormdataByChart[sliceId] = formData; diff --git a/superset/assets/src/dashboard/util/getCurrentScopeChartIds.js b/superset/assets/src/dashboard/util/getCurrentScopeChartIds.js deleted file mode 100644 index ca73256..0000000 --- a/superset/assets/src/dashboard/util/getCurrentScopeChartIds.js +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { CHART_TYPE } from '../util/componentTypes'; - -export default function getCurrentScopeChartIds({ - scopeComponentIds, - filterField, - filterImmuneSlices, - filterImmuneSliceFields, - components, -}) { - function traverse(component) { - if (!component) { - return; - } - - if ( - component.type === CHART_TYPE && - component.meta && - component.meta.chartId - ) { - chartIds.push(component.meta.chartId); - } else if (component.children) { - component.children.forEach(child => traverse(components[child])); - } - } - - let chartIds = []; - scopeComponentIds.forEach(componentId => (traverse(components[componentId]))); - - if (filterImmuneSlices && filterImmuneSlices.length) { - chartIds = chartIds.filter(id => !filterImmuneSlices.includes(id)); - } - - if (filterImmuneSliceFields) { - chartIds = chartIds.filter(id => - !(id.toString() in filterImmuneSliceFields) || - !filterImmuneSliceFields[id].includes(filterField) - ); - } - - return chartIds; -} diff --git a/superset/assets/src/dashboard/util/getDashboardFilterKey.js b/superset/assets/src/dashboard/util/getDashboardFilterKey.js index ffb4ab3..b1e560f 100644 --- a/superset/assets/src/dashboard/util/getDashboardFilterKey.js +++ b/superset/assets/src/dashboard/util/getDashboardFilterKey.js @@ -22,6 +22,6 @@ export function getDashboardFilterKey(chartId, column) { export function getDashboardFilterByKey(key) { const [chartId, ...parts] = key.split('_'); - const columnName = parts.slice().join('_'); - return [chartId, columnName]; + const column = parts.slice().join('_'); + return [parseInt(chartId, 10), column]; } diff --git a/superset/assets/src/dashboard/util/getDashboardUrl.js b/superset/assets/src/dashboard/util/getDashboardUrl.js index 243c8cb..0d9a406 100644 --- a/superset/assets/src/dashboard/util/getDashboardUrl.js +++ b/superset/assets/src/dashboard/util/getDashboardUrl.js @@ -18,8 +18,26 @@ */ /* eslint camelcase: 0 */ +import { getDashboardFilterByKey } from './getDashboardFilterKey'; + +// filters: { [id_column]: values } export default function getDashboardUrl(pathname, filters = {}, hash = '') { - const preselect_filters = encodeURIComponent(JSON.stringify(filters)); + // convert flattened { [id_column]: values } object + // to nested filter object + const obj = Object.entries(filters).reduce((map, entry) => { + const [filterKey, { values }] = entry; + const [chartId, column] = getDashboardFilterByKey(filterKey); + + return { + ...map, + [chartId]: { + ...map[chartId], + [column]: values, + }, + }; + }, {}); + + const preselect_filters = encodeURIComponent(JSON.stringify(obj)); const hashSection = hash ? `#${hash}` : ''; return `${pathname}?preselect_filters=${preselect_filters}${hashSection}`; } diff --git a/superset/assets/src/dashboard/util/getFilterScopeFromNodesTree.js b/superset/assets/src/dashboard/util/getFilterScopeFromNodesTree.js new file mode 100644 index 0000000..b9a03fc --- /dev/null +++ b/superset/assets/src/dashboard/util/getFilterScopeFromNodesTree.js @@ -0,0 +1,77 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { CHART_TYPE, TAB_TYPE } from './componentTypes'; + +// input [{value, label, children: []}], +// output { +// filterKey1: { scope: [tab1, tab2], immune: [chart1, chart2] } +// filterKey2: { scope: [tab1, tab2], immune: [chart1, chart2] } +// } +export default function getFilterScopeFromNodesTree({ + nodes = [], + checkedChartIds = [], +}) { + function traverse({ currentNode, parent = '', scope, immune }) { + if (!currentNode) { + return; + } + + const { value: nodeValue, children } = currentNode; + // any chart type child is checked? + const chartChildren = children.filter(({ type }) => type === CHART_TYPE); + if (chartChildren.some(({ value }) => checkedChartIds.includes(value))) { + if (!scope.has(parent)) { + scope.add(nodeValue); + } + children.forEach(({ value }) => { + if (!checkedChartIds.includes(value)) { + immune.push(value); + } + }); + } + + const tabChildren = children.filter(({ type }) => type === TAB_TYPE); + tabChildren.forEach(child => { + traverse({ + currentNode: child, + parent: nodeValue, + scope, + immune, + }); + }); + } + + const scope = new Set(); + const immune = []; + if (nodes && nodes.length) { + nodes.forEach(node => { + traverse({ + currentNode: node, + parent: '', + scope, + immune, + }); + }); + } + + return { + scope: [...scope], + immune, + }; +} diff --git a/superset/assets/src/dashboard/util/getDashboardFilterKey.js b/superset/assets/src/dashboard/util/getFilterValuesByFilterId.js similarity index 58% copy from superset/assets/src/dashboard/util/getDashboardFilterKey.js copy to superset/assets/src/dashboard/util/getFilterValuesByFilterId.js index ffb4ab3..66ba225 100644 --- a/superset/assets/src/dashboard/util/getDashboardFilterKey.js +++ b/superset/assets/src/dashboard/util/getFilterValuesByFilterId.js @@ -16,12 +16,23 @@ * specific language governing permissions and limitations * under the License. */ -export function getDashboardFilterKey(chartId, column) { - return `${chartId}_${column}`; -} +import { getDashboardFilterByKey } from './getDashboardFilterKey'; -export function getDashboardFilterByKey(key) { - const [chartId, ...parts] = key.split('_'); - const columnName = parts.slice().join('_'); - return [chartId, columnName]; +// input: { [id_column1]: values, [id_column2]: values } +// output: { column1: values, column2: values } +export default function getFilterValuesByFilterId({ + activeFilters = {}, + filterId, +}) { + return Object.entries(activeFilters).reduce((map, entry) => { + const [filterKey, { values }] = entry; + const [chartId, column] = getDashboardFilterByKey(filterKey); + if (chartId === filterId) { + return { + ...map, + [column]: values, + }; + } + return map; + }, {}); } diff --git a/superset/assets/src/dashboard/util/propShapes.jsx b/superset/assets/src/dashboard/util/propShapes.jsx index be04b81..2c3aa11 100644 --- a/superset/assets/src/dashboard/util/propShapes.jsx +++ b/superset/assets/src/dashboard/util/propShapes.jsx @@ -24,6 +24,7 @@ import headerStyleOptions from './headerStyleOptions'; export const componentShape = PropTypes.shape({ id: PropTypes.string.isRequired, type: PropTypes.oneOf(Object.values(componentTypes)).isRequired, + parents: PropTypes.arrayOf(PropTypes.string), children: PropTypes.arrayOf(PropTypes.string), meta: PropTypes.shape({ // Dimensions @@ -79,14 +80,24 @@ export const filterIndicatorPropShape = PropTypes.shape({ isInstantFilter: PropTypes.bool.isRequired, label: PropTypes.string.isRequired, name: PropTypes.string.isRequired, - scope: PropTypes.arrayOf(PropTypes.string), values: PropTypes.array.isRequired, }); -export const filterScopeNodePropShape = PropTypes.shape({ - children: PropTypes.arrayOf(filterScopeNodePropShape), - label: PropTypes.string.isRequired, - value: PropTypes.string.isRequired, +// export const activeFilterPropShape = PropTypes.shape({ +// filterKey: PropTypes.string.isRequired, +// values: PropTypes.array.isRequired, +// scope: PropTypes.array.isRequired, +// }); + +export const dashboardFilterPropShape = PropTypes.shape({ + chartId: PropTypes.number.isRequired, + componentId: PropTypes.string.isRequired, + directPathToFilter: PropTypes.arrayOf(PropTypes.string).isRequired, + isDateFilter: PropTypes.bool.isRequired, + isInstantFilter: PropTypes.bool.isRequired, + columns: PropTypes.object, + labels: PropTypes.object, + scopes: PropTypes.object, }); export const dashboardStatePropShape = PropTypes.shape({ @@ -110,16 +121,3 @@ export const dashboardInfoPropShape = PropTypes.shape({ common: PropTypes.object, userId: PropTypes.string.isRequired, }); - -export const loadStatsPropShape = PropTypes.objectOf( - PropTypes.shape({ - didLoad: PropTypes.bool.isRequired, - minQueryStartTime: PropTypes.number.isRequired, - id: PropTypes.string.isRequired, - type: PropTypes.string.isRequired, - parent_id: PropTypes.string, - parent_type: PropTypes.string, - index: PropTypes.number.isRequired, - slice_ids: PropTypes.arrayOf(PropTypes.number).isRequired, - }), -);
