You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by gr...@apache.org on 2019/10/15 01:00:42 UTC

[incubator-superset] 01/01: another PR

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 <gr...@airbnb.com>
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,
-  }),
-);