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

graceguo pushed a commit to branch feature--dashboard-scoped-filter
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git


The following commit(s) were added to 
refs/heads/feature--dashboard-scoped-filter by this push:
     new f869e19  [WIP][dashboard scoped filter] part 3: merge filter scope 
settings into dashboard redux state (#8522)
f869e19 is described below

commit f869e1920e3fb4688a1bc6440d55441509ecc649
Author: Grace Guo <grace....@airbnb.com>
AuthorDate: Thu Nov 14 17:07:09 2019 -0800

    [WIP][dashboard scoped filter] part 3: merge filter scope settings into 
dashboard redux state (#8522)
    
    * merge filter scope settings into dashboard redux state
    
    * fix/add unit tests
    
    * minor bug fixes
    
    * fix save filter Scopes behavior
    
    * resolve review comments
    
    * fix save filter scope settings
    
    * minor comments
---
 .../dashboard/actions/dashboardLayout_spec.js      |  70 +++++++----
 .../dashboard/components/Dashboard_spec.jsx        | 121 ++++---------------
 .../components/FilterIndicatorsContainer_spec.jsx  |  43 ++++---
 .../dashboard/fixtures/mockDashboardFilters.js     |   1 +
 .../dashboard/fixtures/mockDashboardLayout.js      |  51 ++++++++
 .../dashboard/reducers/dashboardFilters_spec.js    |  35 +++++-
 .../dashboard/util/getDashboardUrl_spec.js         |   8 +-
 .../util/getFormDataWithExtraFilters_spec.js       |  32 +----
 .../src/dashboard/actions/dashboardFilters.js      |  16 +++
 .../src/dashboard/actions/dashboardLayout.js       |  40 ++++++-
 .../assets/src/dashboard/actions/dashboardState.js |  17 ++-
 .../assets/src/dashboard/components/Dashboard.jsx  |  82 +++++--------
 .../components/FilterIndicatorsContainer.jsx       |  89 ++++++--------
 .../assets/src/dashboard/components/Header.jsx     |   5 -
 .../dashboard/components/HeaderActionsDropdown.jsx |   6 +-
 .../assets/src/dashboard/components/SaveModal.jsx  |   4 -
 .../src/dashboard/components/SliceHeader.jsx       |   2 -
 .../dashboard/components/SliceHeaderControls.jsx   |   5 +-
 .../components/filterscope/FilterFieldTree.jsx     |  27 +----
 .../components/filterscope/FilterScopeModal.jsx    |   2 +-
 .../components/filterscope/FilterScopeSelector.jsx |  65 ++++++-----
 .../components/filterscope/FilterScopeTree.jsx     |  27 +----
 .../{FilterScopeTree.jsx => treeIcons.jsx}         |  54 +--------
 .../dashboard/components/gridComponents/Chart.jsx  |  16 ++-
 superset/assets/src/dashboard/containers/Chart.jsx |  11 +-
 .../assets/src/dashboard/containers/Dashboard.jsx  |  10 +-
 .../src/dashboard/containers/DashboardHeader.jsx   |   2 -
 .../src/dashboard/containers/FilterIndicators.jsx  |   6 +-
 .../src/dashboard/containers/FilterScope.jsx       |  11 +-
 .../src/dashboard/reducers/dashboardFilters.js     |  53 ++++++++-
 .../src/dashboard/reducers/dashboardLayout.js      |   1 +
 .../src/dashboard/reducers/getInitialState.js      |  49 +++++++-
 .../stylesheets/filter-scope-selector.less         |   1 +
 .../src/dashboard/util/activeDashboardFilters.js   | 129 ++++++++++++++++-----
 .../util/charts/getEffectiveExtraFilters.js        |  47 +-------
 .../util/charts/getFormDataWithExtraFilters.js     |  13 +--
 .../src/dashboard/util/getCurrentScopeChartIds.js  |  62 ----------
 .../assets/src/dashboard/util/getDashboardUrl.js   |   9 +-
 .../src/dashboard/util/getFilterFieldNodesTree.js  |   2 +-
 .../src/dashboard/util/getFilterScopeNodesTree.js  |   2 +-
 ...erScopeTree.js => getFilterValuesByFilterId.js} |  24 ++--
 .../src/dashboard/util/getKeyForFilterScopeTree.js |   6 +-
 ...erScopeTree.js => isInDifferentFilterScopes.js} |  21 ++--
 .../src/dashboard/util/newEntitiesFromDrop.js      |   4 +
 superset/assets/src/dashboard/util/propShapes.jsx  |  27 +++--
 ...hboardUrl.js => serializeActiveFilterValues.js} |  21 +++-
 .../dashboard/util/serializeFilterScopes.js}       |  25 ++--
 superset/views/core.py                             |  14 ++-
 48 files changed, 706 insertions(+), 662 deletions(-)

diff --git 
a/superset/assets/spec/javascripts/dashboard/actions/dashboardLayout_spec.js 
b/superset/assets/spec/javascripts/dashboard/actions/dashboardLayout_spec.js
index 0dfca9c..1da0568 100644
--- a/superset/assets/spec/javascripts/dashboard/actions/dashboardLayout_spec.js
+++ b/superset/assets/spec/javascripts/dashboard/actions/dashboardLayout_spec.js
@@ -39,6 +39,7 @@ import {
 } from '../../../../src/dashboard/actions/dashboardLayout';
 
 import { setUnsavedChanges } from 
'../../../../src/dashboard/actions/dashboardState';
+import * as dashboardFilters from 
'../../../../src/dashboard/actions/dashboardFilters';
 import {
   addWarningToast,
   ADD_TOAST,
@@ -80,6 +81,12 @@ describe('dashboardLayout actions', () => {
 
     return { getState, dispatch, state };
   }
+  beforeEach(() => {
+    sinon.spy(dashboardFilters, 'updateLayoutComponents');
+  });
+  afterEach(() => {
+    dashboardFilters.updateLayoutComponents.restore();
+  });
 
   describe('updateComponents', () => {
     it('should dispatch an updateLayout action', () => {
@@ -92,6 +99,9 @@ describe('dashboardLayout actions', () => {
         type: UPDATE_COMPONENTS,
         payload: { nextComponents },
       });
+
+      // update component should not trigger action for dashboardFilters
+      expect(dashboardFilters.updateLayoutComponents.callCount).toEqual(0);
     });
 
     it('should dispatch a setUnsavedChanges action if 
hasUnsavedChanges=false', () => {
@@ -101,8 +111,10 @@ describe('dashboardLayout actions', () => {
       const nextComponents = { 1: {} };
       const thunk = updateComponents(nextComponents);
       thunk(dispatch, getState);
-      expect(dispatch.callCount).toBe(2);
       expect(dispatch.getCall(1).args[0]).toEqual(setUnsavedChanges(true));
+
+      // update component should not trigger action for dashboardFilters
+      expect(dashboardFilters.updateLayoutComponents.callCount).toEqual(0);
     });
   });
 
@@ -111,11 +123,13 @@ describe('dashboardLayout actions', () => {
       const { getState, dispatch } = setup();
       const thunk = deleteComponent('id', 'parentId');
       thunk(dispatch, getState);
-      expect(dispatch.callCount).toBe(1);
       expect(dispatch.getCall(0).args[0]).toEqual({
         type: DELETE_COMPONENT,
         payload: { id: 'id', parentId: 'parentId' },
       });
+
+      // delete components should trigger action for dashboardFilters
+      expect(dashboardFilters.updateLayoutComponents.callCount).toEqual(1);
     });
 
     it('should dispatch a setUnsavedChanges action if 
hasUnsavedChanges=false', () => {
@@ -124,8 +138,10 @@ describe('dashboardLayout actions', () => {
       });
       const thunk = deleteComponent('id', 'parentId');
       thunk(dispatch, getState);
-      expect(dispatch.callCount).toBe(2);
-      expect(dispatch.getCall(1).args[0]).toEqual(setUnsavedChanges(true));
+      expect(dispatch.getCall(2).args[0]).toEqual(setUnsavedChanges(true));
+
+      // delete components should trigger action for dashboardFilters
+      expect(dashboardFilters.updateLayoutComponents.callCount).toEqual(1);
     });
   });
 
@@ -149,7 +165,8 @@ describe('dashboardLayout actions', () => {
         },
       });
 
-      expect(dispatch.callCount).toBe(2);
+      // update dashboard title should not trigger action for dashboardFilters
+      expect(dashboardFilters.updateLayoutComponents.callCount).toEqual(0);
     });
   });
 
@@ -159,11 +176,13 @@ describe('dashboardLayout actions', () => {
       const dropResult = {};
       const thunk = createTopLevelTabs(dropResult);
       thunk(dispatch, getState);
-      expect(dispatch.callCount).toBe(1);
       expect(dispatch.getCall(0).args[0]).toEqual({
         type: CREATE_TOP_LEVEL_TABS,
         payload: { dropResult },
       });
+
+      // create top level tabs should trigger action for dashboardFilters
+      expect(dashboardFilters.updateLayoutComponents.callCount).toEqual(1);
     });
 
     it('should dispatch a setUnsavedChanges action if 
hasUnsavedChanges=false', () => {
@@ -173,8 +192,10 @@ describe('dashboardLayout actions', () => {
       const dropResult = {};
       const thunk = createTopLevelTabs(dropResult);
       thunk(dispatch, getState);
-      expect(dispatch.callCount).toBe(2);
-      expect(dispatch.getCall(1).args[0]).toEqual(setUnsavedChanges(true));
+      expect(dispatch.getCall(2).args[0]).toEqual(setUnsavedChanges(true));
+
+      // create top level tabs should trigger action for dashboardFilters
+      expect(dashboardFilters.updateLayoutComponents.callCount).toEqual(1);
     });
   });
 
@@ -184,11 +205,13 @@ describe('dashboardLayout actions', () => {
       const dropResult = {};
       const thunk = deleteTopLevelTabs(dropResult);
       thunk(dispatch, getState);
-      expect(dispatch.callCount).toBe(1);
       expect(dispatch.getCall(0).args[0]).toEqual({
         type: DELETE_TOP_LEVEL_TABS,
         payload: {},
       });
+
+      // delete top level tabs should trigger action for dashboardFilters
+      expect(dashboardFilters.updateLayoutComponents.callCount).toEqual(1);
     });
 
     it('should dispatch a setUnsavedChanges action if 
hasUnsavedChanges=false', () => {
@@ -198,8 +221,10 @@ describe('dashboardLayout actions', () => {
       const dropResult = {};
       const thunk = deleteTopLevelTabs(dropResult);
       thunk(dispatch, getState);
-      expect(dispatch.callCount).toBe(2);
-      expect(dispatch.getCall(1).args[0]).toEqual(setUnsavedChanges(true));
+      expect(dispatch.getCall(2).args[0]).toEqual(setUnsavedChanges(true));
+
+      // delete top level tabs should trigger action for dashboardFilters
+      expect(dashboardFilters.updateLayoutComponents.callCount).toEqual(1);
     });
   });
 
@@ -261,6 +286,9 @@ describe('dashboardLayout actions', () => {
       thunk2(dispatch, getState);
 
       expect(dispatch.callCount).toBe(3);
+
+      // resize components should not trigger action for dashboardFilters
+      expect(dashboardFilters.updateLayoutComponents.callCount).toEqual(0);
     });
   });
 
@@ -286,7 +314,8 @@ describe('dashboardLayout actions', () => {
         },
       });
 
-      expect(dispatch.callCount).toBe(2);
+      // create components should trigger action for dashboardFilters
+      expect(dashboardFilters.updateLayoutComponents.callCount).toEqual(1);
     });
 
     it('should move a component if the component is not new', () => {
@@ -315,7 +344,8 @@ describe('dashboardLayout actions', () => {
         },
       });
 
-      expect(dispatch.callCount).toBe(2);
+      // create components should trigger action for dashboardFilters
+      expect(dashboardFilters.updateLayoutComponents.callCount).toEqual(1);
     });
 
     it('should dispatch a toast if the drop overflows the destination', () => {
@@ -379,9 +409,6 @@ describe('dashboardLayout actions', () => {
           parentId: 'parentId',
         },
       });
-
-      // move thunk, delete thunk, delete result actions
-      expect(dispatch.callCount).toBe(3);
     });
 
     it('should create top-level tabs if dropped on root', () => {
@@ -404,8 +431,6 @@ describe('dashboardLayout actions', () => {
           dropResult,
         },
       });
-
-      expect(dispatch.callCount).toBe(2);
     });
 
     it('should dispatch a toast if drop top-level tab into nested tab', () => {
@@ -497,8 +522,10 @@ describe('dashboardLayout actions', () => {
       const thunk = redoLayoutAction();
       thunk(dispatch, getState);
 
-      expect(dispatch.callCount).toBe(1);
       expect(dispatch.getCall(0).args[0]).toEqual(UndoActionCreators.redo());
+
+      // redo/undo should trigger action for dashboardFilters
+      expect(dashboardFilters.updateLayoutComponents.callCount).toEqual(1);
     });
 
     it('should dispatch a setUnsavedChanges(true) action if 
hasUnsavedChanges=false', () => {
@@ -507,9 +534,10 @@ describe('dashboardLayout actions', () => {
       });
       const thunk = redoLayoutAction();
       thunk(dispatch, getState);
+      expect(dispatch.getCall(2).args[0]).toEqual(setUnsavedChanges(true));
 
-      expect(dispatch.callCount).toBe(2);
-      expect(dispatch.getCall(1).args[0]).toEqual(setUnsavedChanges(true));
+      // redo/undo should trigger action for dashboardFilters
+      expect(dashboardFilters.updateLayoutComponents.callCount).toEqual(1);
     });
   });
 });
diff --git 
a/superset/assets/spec/javascripts/dashboard/components/Dashboard_spec.jsx 
b/superset/assets/spec/javascripts/dashboard/components/Dashboard_spec.jsx
index 02e71e4..fbf44fa 100644
--- a/superset/assets/spec/javascripts/dashboard/components/Dashboard_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/Dashboard_spec.jsx
@@ -24,7 +24,7 @@ import Dashboard from 
'../../../../src/dashboard/components/Dashboard';
 import DashboardBuilder from 
'../../../../src/dashboard/containers/DashboardBuilder';
 
 // mock data
-import chartQueries, { sliceId as chartId } from 
'../fixtures/mockChartQueries';
+import chartQueries from '../fixtures/mockChartQueries';
 import datasources from '../../../fixtures/mockDatasource';
 import dashboardInfo from '../fixtures/mockDashboardInfo';
 import { dashboardLayout } from '../fixtures/mockDashboardLayout';
@@ -46,7 +46,7 @@ describe('Dashboard', () => {
     dashboardState,
     dashboardInfo,
     charts: chartQueries,
-    filters: {},
+    activeFilters: {},
     slices: sliceEntities.slices,
     datasources,
     layout: dashboardLayout.present,
@@ -61,10 +61,12 @@ describe('Dashboard', () => {
     return wrapper;
   }
 
+  // activeFilters map use id_column) as key
   const OVERRIDE_FILTERS = {
-    1: { region: [] },
-    2: { country_name: ['USA'] },
-    3: { region: [], country_name: ['USA'] },
+    '1_region': [],
+    '2_country_name': ['USA'],
+    '3_region': [],
+    '3_country_name': ['USA'],
   };
 
   it('should render a DashboardBuilder', () => {
@@ -72,85 +74,6 @@ describe('Dashboard', () => {
     expect(wrapper.find(DashboardBuilder)).toHaveLength(1);
   });
 
-  describe('refreshExcept', () => {
-    const overrideDashboardInfo = {
-      ...dashboardInfo,
-      metadata: {
-        ...dashboardInfo.metadata,
-        filterImmuneSliceFields: { [chartQueries[chartId].id]: ['region'] },
-      },
-    };
-
-    const overrideCharts = {
-      ...chartQueries,
-      1001: {
-        ...chartQueries[chartId],
-        id: 1001,
-      },
-    };
-
-    const overrideSlices = {
-      ...props.slices,
-      1001: {
-        ...props.slices[chartId],
-        slice_id: 1001,
-      },
-    };
-
-    it('should call triggerQuery for all non-exempt slices', () => {
-      const wrapper = setup({ charts: overrideCharts, slices: overrideSlices 
});
-      const spy = sinon.spy(props.actions, 'triggerQuery');
-      wrapper.instance().refreshExcept('1001');
-      spy.restore();
-      expect(spy.callCount).toBe(Object.keys(overrideCharts).length - 1);
-    });
-
-    it('should not call triggerQuery for filterImmuneSlices', () => {
-      const wrapper = setup({
-        charts: overrideCharts,
-        dashboardInfo: {
-          ...dashboardInfo,
-          metadata: {
-            ...dashboardInfo.metadata,
-            filterImmuneSlices: Object.keys(overrideCharts).map(id =>
-              Number(id),
-            ),
-          },
-        },
-      });
-      const spy = sinon.spy(props.actions, 'triggerQuery');
-      wrapper.instance().refreshExcept();
-      spy.restore();
-      expect(spy.callCount).toBe(0);
-    });
-
-    it('should not call triggerQuery for filterImmuneSliceFields', () => {
-      const wrapper = setup({
-        filters: OVERRIDE_FILTERS,
-        dashboardInfo: overrideDashboardInfo,
-      });
-      const spy = sinon.spy(props.actions, 'triggerQuery');
-      wrapper.instance().refreshExcept('1');
-      expect(spy.callCount).toBe(0);
-      spy.restore();
-    });
-
-    it('should call triggerQuery if filter has more filter-able fields', () => 
{
-      const wrapper = setup({
-        filters: OVERRIDE_FILTERS,
-        dashboardInfo: overrideDashboardInfo,
-      });
-      const spy = sinon.spy(props.actions, 'triggerQuery');
-
-      // if filter have additional fields besides immune ones,
-      // should apply filter.
-      wrapper.instance().refreshExcept('3');
-      expect(spy.callCount).toBe(1);
-
-      spy.restore();
-    });
-  });
-
   describe('componentWillReceiveProps', () => {
     const layoutWithExtraChart = {
       ...props.layout,
@@ -186,17 +109,17 @@ describe('Dashboard', () => {
   describe('componentDidUpdate', () => {
     let wrapper;
     let prevProps;
-    let refreshExceptSpy;
+    let refreshSpy;
 
     beforeEach(() => {
-      wrapper = setup({ filters: OVERRIDE_FILTERS });
+      wrapper = setup({ activeFilters: OVERRIDE_FILTERS });
       wrapper.instance().appliedFilters = OVERRIDE_FILTERS;
       prevProps = wrapper.instance().props;
-      refreshExceptSpy = sinon.spy(wrapper.instance(), 'refreshExcept');
+      refreshSpy = sinon.spy(wrapper.instance(), 'refreshCharts');
     });
 
     afterEach(() => {
-      refreshExceptSpy.restore();
+      refreshSpy.restore();
     });
 
     it('should not call refresh when is editMode', () => {
@@ -207,15 +130,15 @@ describe('Dashboard', () => {
         },
       });
       wrapper.instance().componentDidUpdate(prevProps);
-      expect(refreshExceptSpy.callCount).toBe(0);
+      expect(refreshSpy.callCount).toBe(0);
     });
 
     it('should not call refresh when there is no change', () => {
       wrapper.setProps({
-        filters: OVERRIDE_FILTERS,
+        activeFilters: OVERRIDE_FILTERS,
       });
       wrapper.instance().componentDidUpdate(prevProps);
-      expect(refreshExceptSpy.callCount).toBe(0);
+      expect(refreshSpy.callCount).toBe(0);
       expect(wrapper.instance().appliedFilters).toBe(OVERRIDE_FILTERS);
     });
 
@@ -224,12 +147,12 @@ describe('Dashboard', () => {
         gender: ['boy', 'girl'],
       };
       wrapper.setProps({
-        filters: {
+        activeFilters: {
           ...OVERRIDE_FILTERS,
           ...newFilter,
         },
       });
-      expect(refreshExceptSpy.callCount).toBe(1);
+      expect(refreshSpy.callCount).toBe(1);
       expect(wrapper.instance().appliedFilters).toEqual({
         ...OVERRIDE_FILTERS,
         ...newFilter,
@@ -238,23 +161,23 @@ describe('Dashboard', () => {
 
     it('should call refresh if a filter is removed', () => {
       wrapper.setProps({
-        filters: {},
+        activeFilters: {},
       });
-      expect(refreshExceptSpy.callCount).toBe(1);
+      expect(refreshSpy.callCount).toBe(1);
       expect(wrapper.instance().appliedFilters).toEqual({});
     });
 
     it('should call refresh if a filter is changed', () => {
       wrapper.setProps({
-        filters: {
+        activeFilters: {
           ...OVERRIDE_FILTERS,
-          region: ['Canada'],
+          '1_region': ['Canada'],
         },
       });
-      expect(refreshExceptSpy.callCount).toBe(1);
+      expect(refreshSpy.callCount).toBe(1);
       expect(wrapper.instance().appliedFilters).toEqual({
         ...OVERRIDE_FILTERS,
-        region: ['Canada'],
+        '1_region': ['Canada'],
       });
     });
   });
diff --git 
a/superset/assets/spec/javascripts/dashboard/components/FilterIndicatorsContainer_spec.jsx
 
b/superset/assets/spec/javascripts/dashboard/components/FilterIndicatorsContainer_spec.jsx
index 0e5392f..089f185 100644
--- 
a/superset/assets/spec/javascripts/dashboard/components/FilterIndicatorsContainer_spec.jsx
+++ 
b/superset/assets/spec/javascripts/dashboard/components/FilterIndicatorsContainer_spec.jsx
@@ -20,28 +20,34 @@ import React from 'react';
 import { shallow } from 'enzyme';
 
 import { dashboardFilters } from '../fixtures/mockDashboardFilters';
+import { sliceId as chartId } from '../fixtures/mockChartQueries';
 import { filterId, column } from '../fixtures/mockSliceEntities';
 import FilterIndicatorsContainer from 
'../../../../src/dashboard/components/FilterIndicatorsContainer';
 import FilterIndicator from 
'../../../../src/dashboard/components/FilterIndicator';
 import * as colorMap from 
'../../../../src/dashboard/util/dashboardFiltersColorMap';
+import { buildActiveFilters } from 
'../../../../src/dashboard/util/activeDashboardFilters';
+import { getDashboardFilterKey } from 
'../../../../src/dashboard/util/getDashboardFilterKey';
+import { DASHBOARD_ROOT_ID } from '../../../../src/dashboard/util/constants';
+import { dashboardWithFilter } from '../fixtures/mockDashboardLayout';
 
 describe('FilterIndicatorsContainer', () => {
-  const chartId = 1;
   const mockedProps = {
     dashboardFilters,
     chartId,
     chartStatus: 'success',
-    filterImmuneSlices: [],
-    filterImmuneSliceFields: {},
     setDirectPathToChild: () => {},
     filterFieldOnFocus: {},
   };
 
-  colorMap.getFilterColorKey = jest.fn(() => 'id_column');
   colorMap.getFilterColorMap = jest.fn(() => ({
-    id_column: 'badge-1',
+    [getDashboardFilterKey({ chartId, column })]: 'badge-1',
   }));
 
+  buildActiveFilters({
+    dashboardFilters,
+    components: dashboardWithFilter,
+  });
+
   function setup(overrideProps) {
     return shallow(
       <FilterIndicatorsContainer {...mockedProps} {...overrideProps} />,
@@ -58,18 +64,25 @@ describe('FilterIndicatorsContainer', () => {
     expect(wrapper.find(FilterIndicator)).toHaveLength(0);
   });
 
-  it('should not show indicator when chart is immune', () => {
-    const wrapper = setup({ filterImmuneSlices: [chartId] });
-    expect(wrapper.find(FilterIndicator)).toHaveLength(0);
-  });
-
-  it('should not show indicator when chart field is immune', () => {
-    const wrapper = setup({ filterImmuneSliceFields: { [chartId]: [column] } 
});
-    expect(wrapper.find(FilterIndicator)).toHaveLength(0);
-  });
-
   it('should show indicator', () => {
     const wrapper = setup();
     expect(wrapper.find(FilterIndicator)).toHaveLength(1);
   });
+
+  it('should not show indicator when chart is immune', () => {
+    const overwriteDashboardFilters = {
+      ...dashboardFilters,
+      [filterId]: {
+        ...dashboardFilters[filterId],
+        scopes: {
+          region: {
+            scope: [DASHBOARD_ROOT_ID],
+            immune: [chartId],
+          },
+        },
+      },
+    };
+    const wrapper = setup({ dashboardFilters: overwriteDashboardFilters });
+    expect(wrapper.find(FilterIndicator)).toHaveLength(0);
+  });
 });
diff --git 
a/superset/assets/spec/javascripts/dashboard/fixtures/mockDashboardFilters.js 
b/superset/assets/spec/javascripts/dashboard/fixtures/mockDashboardFilters.js
index 7f13db7..c5640c5 100644
--- 
a/superset/assets/spec/javascripts/dashboard/fixtures/mockDashboardFilters.js
+++ 
b/superset/assets/spec/javascripts/dashboard/fixtures/mockDashboardFilters.js
@@ -36,6 +36,7 @@ export const dashboardFilters = {
     ],
     scopes: {
       region: DASHBOARD_FILTER_SCOPE_GLOBAL,
+      gender: DASHBOARD_FILTER_SCOPE_GLOBAL,
     },
     isDateFilter: false,
     isInstantFilter: true,
diff --git 
a/superset/assets/spec/javascripts/dashboard/fixtures/mockDashboardLayout.js 
b/superset/assets/spec/javascripts/dashboard/fixtures/mockDashboardLayout.js
index 3267c5c..ee1dc4f 100644
--- a/superset/assets/spec/javascripts/dashboard/fixtures/mockDashboardLayout.js
+++ b/superset/assets/spec/javascripts/dashboard/fixtures/mockDashboardLayout.js
@@ -207,3 +207,54 @@ export const filterComponent = {
     sliceName: 'Filter',
   },
 };
+
+export const dashboardWithFilter = {
+  [DASHBOARD_ROOT_ID]: {
+    type: DASHBOARD_ROOT_TYPE,
+    id: DASHBOARD_ROOT_ID,
+    children: [DASHBOARD_GRID_ID],
+  },
+
+  [DASHBOARD_GRID_ID]: {
+    type: DASHBOARD_GRID_TYPE,
+    id: DASHBOARD_GRID_ID,
+    children: ['ROW_ID'],
+    meta: {},
+  },
+
+  [DASHBOARD_HEADER_ID]: {
+    type: DASHBOARD_HEADER_TYPE,
+    id: DASHBOARD_HEADER_ID,
+    meta: {
+      text: 'New dashboard',
+    },
+  },
+
+  ROW_ID: {
+    ...newComponentFactory(ROW_TYPE),
+    id: 'ROW_ID',
+    children: ['CHART_ID', 'FILTER_ID'],
+  },
+
+  FILTER_ID: {
+    ...newComponentFactory(CHART_TYPE),
+    id: 'FILTER_ID',
+    meta: {
+      chartId: filterId,
+      width: 3,
+      height: 10,
+      chartName: 'filter name',
+    },
+  },
+
+  CHART_ID: {
+    ...newComponentFactory(CHART_TYPE),
+    id: 'CHART_ID',
+    meta: {
+      chartId,
+      width: 3,
+      height: 10,
+      chartName: 'Mock chart name',
+    },
+  },
+};
diff --git 
a/superset/assets/spec/javascripts/dashboard/reducers/dashboardFilters_spec.js 
b/superset/assets/spec/javascripts/dashboard/reducers/dashboardFilters_spec.js
index e234248..b759610 100644
--- 
a/superset/assets/spec/javascripts/dashboard/reducers/dashboardFilters_spec.js
+++ 
b/superset/assets/spec/javascripts/dashboard/reducers/dashboardFilters_spec.js
@@ -21,6 +21,7 @@ import {
   ADD_FILTER,
   REMOVE_FILTER,
   CHANGE_FILTER,
+  UPDATE_DASHBOARD_FILTERS_SCOPE,
 } from '../../../../src/dashboard/actions/dashboardFilters';
 import dashboardFiltersReducer, {
   DASHBOARD_FILTER_SCOPE_GLOBAL,
@@ -35,6 +36,7 @@ import {
   column,
 } from '../fixtures/mockSliceEntities';
 import { filterComponent } from '../fixtures/mockDashboardLayout';
+import * as activeDashboardFilters from 
'../../../../src/dashboard/util/activeDashboardFilters';
 
 describe('dashboardFilters reducer', () => {
   const form_data = sliceEntitiesForDashboard.slices[filterId].form_data;
@@ -98,6 +100,7 @@ describe('dashboardFilters reducer', () => {
         },
         scopes: {
           [column]: DASHBOARD_FILTER_SCOPE_GLOBAL,
+          gender: DASHBOARD_FILTER_SCOPE_GLOBAL,
         },
       },
     });
@@ -129,7 +132,8 @@ describe('dashboardFilters reducer', () => {
           [column]: column,
         },
         scopes: {
-          [column]: DASHBOARD_FILTER_SCOPE_GLOBAL,
+          region: DASHBOARD_FILTER_SCOPE_GLOBAL,
+          gender: DASHBOARD_FILTER_SCOPE_GLOBAL,
         },
       },
     });
@@ -143,4 +147,33 @@ describe('dashboardFilters reducer', () => {
       }),
     ).toEqual({});
   });
+
+  it('should buildActiveFilters on UPDATE_DASHBOARD_FILTERS_SCOPE', () => {
+    const regionScope = {
+      scope: ['TAB-1'],
+      immune: [],
+    };
+    const genderScope = {
+      scope: ['ROOT_ID'],
+      immune: [1],
+    };
+    const scopes = {
+      [`${filterId}_region`]: regionScope,
+      [`${filterId}_gender`]: genderScope,
+    };
+    activeDashboardFilters.buildActiveFilters = jest.fn();
+    expect(
+      dashboardFiltersReducer(dashboardFilters, {
+        type: UPDATE_DASHBOARD_FILTERS_SCOPE,
+        scopes,
+      })[filterId].scopes,
+    ).toEqual({
+      region: regionScope,
+      gender: genderScope,
+    });
+
+    // when UPDATE_DASHBOARD_FILTERS_SCOPE is changed, applicable filters to a 
chart
+    // might be changed.
+    expect(activeDashboardFilters.buildActiveFilters).toBeCalled();
+  });
 });
diff --git 
a/superset/assets/spec/javascripts/dashboard/util/getDashboardUrl_spec.js 
b/superset/assets/spec/javascripts/dashboard/util/getDashboardUrl_spec.js
index 76a2da6..8b6b7fc 100644
--- a/superset/assets/spec/javascripts/dashboard/util/getDashboardUrl_spec.js
+++ b/superset/assets/spec/javascripts/dashboard/util/getDashboardUrl_spec.js
@@ -17,10 +17,16 @@
  * under the License.
  */
 import getDashboardUrl from '../../../../src/dashboard/util/getDashboardUrl';
+import { DASHBOARD_FILTER_SCOPE_GLOBAL } from 
'../../../../src/dashboard/reducers/dashboardFilters';
 
 describe('getChartIdsFromLayout', () => {
   it('should encode filters', () => {
-    const filters = { 35: { key: ['value'] } };
+    const filters = {
+      '35_key': {
+        values: ['value'],
+        scope: DASHBOARD_FILTER_SCOPE_GLOBAL,
+      },
+    };
     const url = getDashboardUrl('path', filters);
     expect(url).toBe(
       
'path?preselect_filters=%7B%2235%22%3A%7B%22key%22%3A%5B%22value%22%5D%7D%7D',
diff --git 
a/superset/assets/spec/javascripts/dashboard/util/getFormDataWithExtraFilters_spec.js
 
b/superset/assets/spec/javascripts/dashboard/util/getFormDataWithExtraFilters_spec.js
index 544b2d4..95cb141 100644
--- 
a/superset/assets/spec/javascripts/dashboard/util/getFormDataWithExtraFilters_spec.js
+++ 
b/superset/assets/spec/javascripts/dashboard/util/getFormDataWithExtraFilters_spec.js
@@ -33,15 +33,9 @@ describe('getFormDataWithExtraFilters', () => {
         ],
       },
     },
-    dashboardMetadata: {
-      filterImmuneSlices: [],
-      filterImmuneSliceFields: {},
-    },
     filters: {
-      filterId: {
-        region: ['Spain'],
-        color: ['pink', 'purple'],
-      },
+      region: ['Spain'],
+      color: ['pink', 'purple'],
     },
     sliceId: chartId,
   };
@@ -60,26 +54,4 @@ describe('getFormDataWithExtraFilters', () => {
       val: ['pink', 'purple'],
     });
   });
-
-  it('should not add additional filters if the slice is immune to them', () => 
{
-    const result = getFormDataWithExtraFilters({
-      ...mockArgs,
-      dashboardMetadata: {
-        filterImmuneSlices: [chartId],
-      },
-    });
-    expect(result.extra_filters).toHaveLength(0);
-  });
-
-  it('should not add additional filters for fields to which the slice is 
immune', () => {
-    const result = getFormDataWithExtraFilters({
-      ...mockArgs,
-      dashboardMetadata: {
-        filterImmuneSliceFields: {
-          [chartId]: ['region'],
-        },
-      },
-    });
-    expect(result.extra_filters).toHaveLength(1);
-  });
 });
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..1122918 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 {
@@ -29,6 +30,10 @@ import {
 } from '../util/constants';
 import dropOverflowsParent from '../util/dropOverflowsParent';
 import findParentId from '../util/findParentId';
+import isInDifferentFilterScopes from '../util/isInDifferentFilterScopes';
+
+// Component CRUD -------------------------------------------------------------
+export const UPDATE_COMPONENTS = 'UPDATE_COMPONENTS';
 
 // this is a helper that takes an action as input and dispatches
 // an additional setUnsavedChanges(true) action after the dispatch in the case
@@ -42,15 +47,22 @@ function setUnsavedChangesAfterAction(action) {
       dispatch(result);
     }
 
+    const isComponentLevelEvent =
+      result.type === UPDATE_COMPONENTS &&
+      result.payload &&
+      result.payload.nextComponents;
+    // trigger dashboardFilters state update if dashboard layout is changed.
+    if (!isComponentLevelEvent) {
+      const components = getState().dashboardLayout.present;
+      dispatch(updateLayoutComponents(components));
+    }
+
     if (!getState().dashboardState.hasUnsavedChanges) {
       dispatch(setUnsavedChanges(true));
     }
   };
 }
 
-// Component CRUD -------------------------------------------------------------
-export const UPDATE_COMPONENTS = 'UPDATE_COMPONENTS';
-
 export const updateComponents = setUnsavedChangesAfterAction(
   nextComponents => ({
     type: UPDATE_COMPONENTS,
@@ -199,12 +211,13 @@ export function handleComponentDrop(dropResult) {
 
     // call getState() again down here in case redux state is stale after
     // previous dispatch(es)
-    const { dashboardLayout: undoableLayout } = getState();
+    const { dashboardFilters, dashboardLayout: undoableLayout } = getState();
 
     // if we moved a child from a Tab or Row parent and it was the only child, 
delete the parent.
     if (!isNewComponent) {
       const { present: layout } = undoableLayout;
-      const sourceComponent = layout[source.id];
+      const sourceComponent = layout[source.id] || {};
+      const destinationComponent = layout[destination.id] || {};
       if (
         (sourceComponent.type === TABS_TYPE ||
           sourceComponent.type === ROW_TYPE) &&
@@ -217,6 +230,23 @@ export function handleComponentDrop(dropResult) {
         });
         dispatch(deleteComponent(source.id, parentId));
       }
+
+      // show warning if item has been moved between different scope
+      if (
+        isInDifferentFilterScopes({
+          dashboardFilters,
+          source: (sourceComponent.parents || []).concat(source.id),
+          destination: (destinationComponent.parents || []).concat(
+            destination.id,
+          ),
+        })
+      ) {
+        dispatch(
+          addWarningToast(
+            t('This chart has been moved to a different filter scope.'),
+          ),
+        );
+      }
     }
 
     return null;
diff --git a/superset/assets/src/dashboard/actions/dashboardState.js 
b/superset/assets/src/dashboard/actions/dashboardState.js
index b1d850f..cea6e25 100644
--- a/superset/assets/src/dashboard/actions/dashboardState.js
+++ b/superset/assets/src/dashboard/actions/dashboardState.js
@@ -38,6 +38,10 @@ import {
   addDangerToast,
 } from '../../messageToasts/actions';
 import { UPDATE_COMPONENTS_PARENTS_LIST } from '../actions/dashboardLayout';
+import serializeActiveFilterValues from '../util/serializeActiveFilterValues';
+import serializeFilterScopes from '../util/serializeFilterScopes';
+import { getActiveFilters } from '../util/activeDashboardFilters';
+import { safeStringify } from '../../utils/safeStringify';
 
 export const SET_UNSAVED_CHANGES = 'SET_UNSAVED_CHANGES';
 export function setUnsavedChanges(hasUnsavedChanges) {
@@ -178,10 +182,19 @@ export function saveDashboardRequest(data, id, saveType) {
       directPathToFilter.push(componentId);
       dispatch(updateDirectPathToFilter(chartId, directPathToFilter));
     });
-
+    // serialize selected values for each filter field, grouped by filter id
+    const serializedFilters = serializeActiveFilterValues(getActiveFilters());
+    // serialize filter scope for each filter field, grouped by filter id
+    const serializedFilterScopes = serializeFilterScopes(dashboardFilters);
     return SupersetClient.post({
       endpoint: `/superset/${path}/${id}/`,
-      postPayload: { data },
+      postPayload: {
+        data: {
+          ...data,
+          default_filters: safeStringify(serializedFilters),
+          filter_scopes: safeStringify(serializedFilterScopes),
+        },
+      },
     })
       .then(response => {
         dispatch(saveDashboardRequestSuccess());
diff --git a/superset/assets/src/dashboard/components/Dashboard.jsx 
b/superset/assets/src/dashboard/components/Dashboard.jsx
index 334704e..441f807 100644
--- a/superset/assets/src/dashboard/components/Dashboard.jsx
+++ b/superset/assets/src/dashboard/components/Dashboard.jsx
@@ -28,9 +28,7 @@ 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';
@@ -48,9 +46,8 @@ const propTypes = {
   dashboardState: dashboardStatePropShape.isRequired,
   charts: PropTypes.objectOf(chartPropShape).isRequired,
   slices: PropTypes.objectOf(slicePropShape).isRequired,
-  filters: PropTypes.object.isRequired,
+  activeFilters: PropTypes.object.isRequired,
   datasources: PropTypes.object.isRequired,
-  loadStats: loadStatsPropShape.isRequired,
   layout: PropTypes.object.isRequired,
   impressionId: PropTypes.string.isRequired,
   initMessages: PropTypes.array,
@@ -118,31 +115,42 @@ class Dashboard extends React.PureComponent {
     const { hasUnsavedChanges, editMode } = this.props.dashboardState;
 
     const appliedFilters = this.appliedFilters;
-    const { filters } = this.props;
+    const { activeFilters } = this.props;
     // do not apply filter when dashboard in edit mode
-    if (!editMode && safeStringify(appliedFilters) !== safeStringify(filters)) 
{
+    if (
+      !editMode &&
+      safeStringify(appliedFilters) !== safeStringify(activeFilters)
+    ) {
       // refresh charts if a filter was removed, added, or changed
-      let changedFilterKey = null;
-      const currFilterKeys = Object.keys(filters);
+      const currFilterKeys = Object.keys(activeFilters);
       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));
+      const affectedChartIds = [];
+      [...allKeys].forEach(filterKey => {
+        if (!currFilterKeys.includes(filterKey)) {
+          // removed filter?
+          [].push.apply(affectedChartIds, appliedFilters[filterKey].scope);
+        } else if (!appliedFilterKeys.includes(filterKey)) {
+          // added filter?
+          [].push.apply(affectedChartIds, activeFilters[filterKey].scope);
+        } else if (
+          safeStringify(activeFilters[filterKey].values) !==
+            safeStringify(appliedFilters[filterKey].values) ||
+          safeStringify(activeFilters[filterKey].scope) !==
+            safeStringify(appliedFilters[filterKey].scope)
         ) {
-          changedFilterKey = key;
+          // changed filter field value?
+          const affectedScope = activeFilters[filterKey].scope.concat(
+            appliedFilters[filterKey].scope,
+          );
+          [].push.apply(affectedChartIds, affectedScope);
         }
       });
 
-      if (
-        !!changedFilterKey ||
-        currFilterKeys.length !== appliedFilterKeys.length // remove 1 or more 
filters
-      ) {
-        this.refreshExcept(changedFilterKey);
-        this.appliedFilters = filters;
-      }
+      const idSet = new Set(affectedChartIds);
+      this.refreshCharts([...idSet]);
+      this.appliedFilters = activeFilters;
     }
 
     if (hasUnsavedChanges) {
@@ -157,35 +165,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);
-      }
+  refreshCharts(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 f287108..795c645 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,60 +73,50 @@ 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)
-        ) {
-          Object.keys(columns).forEach(name => {
-            const colorMapKey = getDashboardFilterKey({
-              chartId,
-              column: name,
+        if (currentChartId !== chartId) {
+          Object.keys(columns)
+            .filter(name =>
+              getChartIdsInFilterScope({ filterScope: scopes[name] }).includes(
+                currentChartId,
+              ),
+            )
+            .forEach(name => {
+              const colorMapKey = getDashboardFilterKey({
+                chartId,
+                column: name,
+              });
+              const indicator = {
+                chartId,
+                colorCode: dashboardFiltersColorMap[colorMapKey],
+                componentId,
+                directPathToFilter: directPathToFilter.concat(`LABEL-${name}`),
+                isDateFilter,
+                isInstantFilter,
+                name,
+                label: labels[name] || name,
+                values:
+                  isEmpty(columns[name]) ||
+                  (isDateFilter && columns[name] === 'No filter')
+                    ? []
+                    : [].concat(columns[name]),
+                isFilterFieldActive:
+                  chartId === filterFieldOnFocus.chartId &&
+                  name === filterFieldOnFocus.column,
+              };
+
+              if (isEmpty(indicator.values)) {
+                indicators[1].push(indicator);
+              } else {
+                indicators[0].push(indicator);
+              }
             });
-            const directPathToLabel = directPathToFilter.slice();
-            directPathToLabel.push(`LABEL-${name}`);
-            const indicator = {
-              chartId,
-              colorCode: dashboardFiltersColorMap[colorMapKey],
-              componentId,
-              directPathToFilter: directPathToLabel,
-              scope,
-              isDateFilter,
-              isInstantFilter,
-              name,
-              label: labels[name] || name,
-              values:
-                isEmpty(columns[name]) ||
-                (isDateFilter && columns[name] === 'No filter')
-                  ? []
-                  : [].concat(columns[name]),
-              isFilterFieldActive:
-                chartId === filterFieldOnFocus.chartId &&
-                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 {
-              indicators[0].push(indicator);
-            }
-          });
         }
 
         return indicators;
diff --git a/superset/assets/src/dashboard/components/Header.jsx 
b/superset/assets/src/dashboard/components/Header.jsx
index 569dac9..843eeb5 100644
--- a/superset/assets/src/dashboard/components/Header.jsx
+++ b/superset/assets/src/dashboard/components/Header.jsx
@@ -53,7 +53,6 @@ const propTypes = {
   dashboardTitle: PropTypes.string.isRequired,
   charts: PropTypes.objectOf(chartPropShape).isRequired,
   layout: PropTypes.object.isRequired,
-  filters: PropTypes.object.isRequired,
   expandedSlices: PropTypes.object.isRequired,
   css: PropTypes.string.isRequired,
   colorNamespace: PropTypes.string,
@@ -217,7 +216,6 @@ class Header extends React.PureComponent {
       css,
       colorNamespace,
       colorScheme,
-      filters,
       dashboardInfo,
       refreshFrequency,
     } = this.props;
@@ -235,7 +233,6 @@ class Header extends React.PureComponent {
       color_scheme: colorScheme,
       label_colors: labelColors,
       dashboard_title: dashboardTitle,
-      default_filters: safeStringify(filters),
       refresh_frequency: refreshFrequency,
     };
 
@@ -263,7 +260,6 @@ class Header extends React.PureComponent {
     const {
       dashboardTitle,
       layout,
-      filters,
       expandedSlices,
       css,
       colorNamespace,
@@ -415,7 +411,6 @@ class Header extends React.PureComponent {
             dashboardId={dashboardInfo.id}
             dashboardTitle={dashboardTitle}
             layout={layout}
-            filters={filters}
             expandedSlices={expandedSlices}
             css={css}
             colorNamespace={colorNamespace}
diff --git a/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx 
b/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx
index 6fc0182..cf89e5e 100644
--- a/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx
+++ b/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx
@@ -29,6 +29,7 @@ import injectCustomCss from '../util/injectCustomCss';
 import { SAVE_TYPE_NEWDASHBOARD } from '../util/constants';
 import URLShortLinkModal from '../../components/URLShortLinkModal';
 import getDashboardUrl from '../util/getDashboardUrl';
+import { getActiveFilters } from '../util/activeDashboardFilters';
 
 const propTypes = {
   addSuccessToast: PropTypes.func.isRequired,
@@ -50,7 +51,6 @@ const propTypes = {
   userCanSave: PropTypes.bool.isRequired,
   isLoading: PropTypes.bool.isRequired,
   layout: PropTypes.object.isRequired,
-  filters: PropTypes.object.isRequired,
   expandedSlices: PropTypes.object.isRequired,
   onSave: PropTypes.func.isRequired,
 };
@@ -120,7 +120,6 @@ class HeaderActionsDropdown extends React.PureComponent {
       colorScheme,
       hasUnsavedChanges,
       layout,
-      filters,
       expandedSlices,
       onSave,
       userCanEdit,
@@ -148,7 +147,6 @@ class HeaderActionsDropdown extends React.PureComponent {
             dashboardTitle={dashboardTitle}
             saveType={SAVE_TYPE_NEWDASHBOARD}
             layout={layout}
-            filters={filters}
             expandedSlices={expandedSlices}
             refreshFrequency={refreshFrequency}
             css={css}
@@ -200,7 +198,7 @@ class HeaderActionsDropdown extends React.PureComponent {
         <URLShortLinkModal
           url={getDashboardUrl(
             window.location.pathname,
-            this.props.filters,
+            getActiveFilters(),
             window.location.hash,
           )}
           emailSubject={emailSubject}
diff --git a/superset/assets/src/dashboard/components/SaveModal.jsx 
b/superset/assets/src/dashboard/components/SaveModal.jsx
index d926bc8..2f53c8a 100644
--- a/superset/assets/src/dashboard/components/SaveModal.jsx
+++ b/superset/assets/src/dashboard/components/SaveModal.jsx
@@ -26,7 +26,6 @@ import { t } from '@superset-ui/translation';
 import ModalTrigger from '../../components/ModalTrigger';
 import Checkbox from '../../components/Checkbox';
 import { SAVE_TYPE_OVERWRITE, SAVE_TYPE_NEWDASHBOARD } from 
'../util/constants';
-import { safeStringify } from '../../utils/safeStringify';
 
 const propTypes = {
   addSuccessToast: PropTypes.func.isRequired,
@@ -37,7 +36,6 @@ const propTypes = {
   layout: PropTypes.object.isRequired,
   saveType: PropTypes.oneOf([SAVE_TYPE_OVERWRITE, SAVE_TYPE_NEWDASHBOARD]),
   triggerNode: PropTypes.node.isRequired,
-  filters: PropTypes.object.isRequired,
   css: PropTypes.string.isRequired,
   colorNamespace: PropTypes.string,
   colorScheme: PropTypes.string,
@@ -101,7 +99,6 @@ class SaveModal extends React.PureComponent {
       colorNamespace,
       colorScheme,
       expandedSlices,
-      filters,
       dashboardId,
       refreshFrequency,
     } = this.props;
@@ -120,7 +117,6 @@ class SaveModal extends React.PureComponent {
       expanded_slices: expandedSlices,
       dashboard_title:
         saveType === SAVE_TYPE_NEWDASHBOARD ? newDashName : dashboardTitle,
-      default_filters: safeStringify(filters),
       duplicate_slices: this.state.duplicateSlices,
       refresh_frequency: refreshFrequency,
     };
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/FilterFieldTree.jsx 
b/superset/assets/src/dashboard/components/filterscope/FilterFieldTree.jsx
index 7b778ed..6ac3a1d 100644
--- a/superset/assets/src/dashboard/components/filterscope/FilterFieldTree.jsx
+++ b/superset/assets/src/dashboard/components/filterscope/FilterFieldTree.jsx
@@ -19,14 +19,8 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import CheckboxTree from 'react-checkbox-tree';
-import { t } from '@superset-ui/translation';
 
-import 'react-checkbox-tree/lib/react-checkbox-tree.css';
-import {
-  CheckboxChecked,
-  CheckboxUnchecked,
-  CheckboxHalfChecked,
-} from '../../../components/CheckboxIcons';
+import treeIcons from './treeIcons';
 import renderFilterFieldTreeNodes from './renderFilterFieldTreeNodes';
 import { filterScopeSelectorTreeNodePropShape } from '../../util/propShapes';
 
@@ -48,23 +42,6 @@ const defaultProps = {
   activeKey: null,
 };
 
-const FILTER_FIELD_CHECKBOX_TREE_ICONS = {
-  check: <CheckboxChecked />,
-  uncheck: <CheckboxUnchecked />,
-  halfCheck: <CheckboxHalfChecked />,
-  expandClose: <span className="rct-icon rct-icon-expand-close" />,
-  expandOpen: <span className="rct-icon rct-icon-expand-open" />,
-  expandAll: (
-    <span className="rct-icon rct-icon-expand-all">{t('Expand all')}</span>
-  ),
-  collapseAll: (
-    <span className="rct-icon rct-icon-collapse-all">{t('Collapse all')}</span>
-  ),
-  parentClose: <span className="rct-icon rct-icon-parent-close" />,
-  parentOpen: <span className="rct-icon rct-icon-parent-open" />,
-  leaf: <span className="rct-icon rct-icon-leaf" />,
-};
-
 export default function FilterFieldTree({
   activeKey,
   nodes = [],
@@ -85,7 +62,7 @@ export default function FilterFieldTree({
       onClick={onClick}
       onCheck={onCheck}
       onExpand={onExpand}
-      icons={FILTER_FIELD_CHECKBOX_TREE_ICONS}
+      icons={treeIcons}
     />
   );
 }
diff --git 
a/superset/assets/src/dashboard/components/filterscope/FilterScopeModal.jsx 
b/superset/assets/src/dashboard/components/filterscope/FilterScopeModal.jsx
index b2a8be8..fc4b766 100644
--- a/superset/assets/src/dashboard/components/filterscope/FilterScopeModal.jsx
+++ b/superset/assets/src/dashboard/components/filterscope/FilterScopeModal.jsx
@@ -35,7 +35,7 @@ export default class FilterScopeModal extends 
React.PureComponent {
   }
 
   handleCloseModal() {
-    if (this.modal) {
+    if (this.modal.current) {
       this.modal.current.close();
     }
   }
diff --git 
a/superset/assets/src/dashboard/components/filterscope/FilterScopeSelector.jsx 
b/superset/assets/src/dashboard/components/filterscope/FilterScopeSelector.jsx
index ae42a57..9435d7b 100644
--- 
a/superset/assets/src/dashboard/components/filterscope/FilterScopeSelector.jsx
+++ 
b/superset/assets/src/dashboard/components/filterscope/FilterScopeSelector.jsx
@@ -26,24 +26,26 @@ import buildFilterScopeTreeEntry from 
'../../util/buildFilterScopeTreeEntry';
 import getFilterScopeNodesTree from '../../util/getFilterScopeNodesTree';
 import getFilterFieldNodesTree from '../../util/getFilterFieldNodesTree';
 import getFilterScopeParentNodes from '../../util/getFilterScopeParentNodes';
-import getCurrentScopeChartIds from '../../util/getCurrentScopeChartIds';
 import getKeyForFilterScopeTree from '../../util/getKeyForFilterScopeTree';
 import getSelectedChartIdForFilterScopeTree from 
'../../util/getSelectedChartIdForFilterScopeTree';
+import getFilterScopeFromNodesTree from 
'../../util/getFilterScopeFromNodesTree';
 import getRevertedFilterScope from '../../util/getRevertedFilterScope';
 import FilterScopeTree from './FilterScopeTree';
 import FilterFieldTree from './FilterFieldTree';
+import { getChartIdsInFilterScope } from '../../util/activeDashboardFilters';
 import {
   getChartIdAndColumnFromFilterKey,
   getDashboardFilterKey,
 } from '../../util/getDashboardFilterKey';
 import { ALL_FILTERS_ROOT } from '../../util/constants';
+import { dashboardFilterPropShape } from '../../util/propShapes';
 
 const propTypes = {
-  dashboardFilters: PropTypes.object.isRequired,
+  dashboardFilters: PropTypes.objectOf(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,
 };
 
@@ -51,12 +53,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
@@ -88,14 +85,12 @@ export default class FilterScopeSelector extends 
React.PureComponent {
                 filterFields: [filterKey],
                 selectedChartId: filterId,
               });
-              const checked = getCurrentScopeChartIds({
-                scopeComponentIds: ['ROOT_ID'], // 
dashboardFilters[chartId].scopes[columnName],
-                filterField: columnName,
-                filterImmuneSlices,
-                filterImmuneSliceFields,
-                components: layout,
-              });
               const expanded = getFilterScopeParentNodes(nodes, 1);
+              // display filter_box chart as checked, but do not show checkbox
+              const chartIdsInFilterScope = getChartIdsInFilterScope({
+                filterScope: dashboardFilters[filterId].scopes[columnName],
+              });
+
               return {
                 ...mapByChartId,
                 [filterKey]: {
@@ -103,7 +98,7 @@ export default class FilterScopeSelector extends 
React.PureComponent {
                   nodes,
                   // filtered nodes in display if searchText is not empty
                   nodesFiltered: [...nodes],
-                  checked,
+                  checked: chartIdsInFilterScope,
                   expanded,
                 },
               };
@@ -304,18 +299,28 @@ export default class FilterScopeSelector extends 
React.PureComponent {
   onSave() {
     const { filterScopeMap } = this.state;
 
-    console.log(
-      'i am current state',
-      this.allfilterFields.reduce(
-        (map, key) => ({
+    const allFilterFieldScopes = this.allfilterFields.reduce(
+      (map, filterKey) => {
+        const nodes = filterScopeMap[filterKey].nodes;
+        const checkedChartIds = filterScopeMap[filterKey].checked;
+
+        return {
           ...map,
-          [key]: filterScopeMap[key].checked,
-        }),
-        {},
-      ),
+          [filterKey]: getFilterScopeFromNodesTree({
+            filterKey,
+            nodes,
+            checkedChartIds,
+          }),
+        };
+      },
+      {},
     );
 
-    // save does not close modal
+    this.props.updateDashboardFiltersScope(allFilterFieldScopes);
+    this.props.setUnsavedChanges(true);
+
+    // click Save button will do save and close modal
+    this.props.onCloseModal();
   }
 
   filterTree() {
@@ -486,7 +491,7 @@ export default class FilterScopeSelector extends 
React.PureComponent {
         <div className="filter-scope-container">
           <div className="filter-scope-header">
             <h4>{t('Configure filter scopes')}</h4>
-            {this.renderEditingFiltersName()}
+            {showSelector && this.renderEditingFiltersName()}
           </div>
 
           {!showSelector ? (
@@ -503,7 +508,7 @@ export default class FilterScopeSelector extends 
React.PureComponent {
           )}
         </div>
         <div className="dashboard-modal-actions-container">
-          <Button onClick={this.onClose}>{t('Cancel')}</Button>
+          <Button onClick={this.onClose}>{t('Close')}</Button>
           {showSelector && (
             <Button bsStyle="primary" onClick={this.onSave}>
               {t('Save')}
diff --git 
a/superset/assets/src/dashboard/components/filterscope/FilterScopeTree.jsx 
b/superset/assets/src/dashboard/components/filterscope/FilterScopeTree.jsx
index e433f15..e280335 100644
--- a/superset/assets/src/dashboard/components/filterscope/FilterScopeTree.jsx
+++ b/superset/assets/src/dashboard/components/filterscope/FilterScopeTree.jsx
@@ -19,15 +19,9 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import CheckboxTree from 'react-checkbox-tree';
-import 'react-checkbox-tree/lib/react-checkbox-tree.css';
-import { t } from '@superset-ui/translation';
 
-import {
-  CheckboxChecked,
-  CheckboxUnchecked,
-  CheckboxHalfChecked,
-} from '../../../components/CheckboxIcons';
 import renderFilterScopeTreeNodes from './renderFilterScopeTreeNodes';
+import treeIcons from './treeIcons';
 import { filterScopeSelectorTreeNodePropShape } from '../../util/propShapes';
 
 const propTypes = {
@@ -49,23 +43,6 @@ const defaultProps = {
 
 const NOOP = () => {};
 
-const FILTER_SCOPE_CHECKBOX_TREE_ICONS = {
-  check: <CheckboxChecked />,
-  uncheck: <CheckboxUnchecked />,
-  halfCheck: <CheckboxHalfChecked />,
-  expandClose: <span className="rct-icon rct-icon-expand-close" />,
-  expandOpen: <span className="rct-icon rct-icon-expand-open" />,
-  expandAll: (
-    <span className="rct-icon rct-icon-expand-all">{t('Expand all')}</span>
-  ),
-  collapseAll: (
-    <span className="rct-icon rct-icon-collapse-all">{t('Collapse all')}</span>
-  ),
-  parentClose: <span className="rct-icon rct-icon-parent-close" />,
-  parentOpen: <span className="rct-icon rct-icon-parent-open" />,
-  leaf: <span className="rct-icon rct-icon-leaf" />,
-};
-
 export default function FilterScopeTree({
   nodes = [],
   checked = [],
@@ -85,7 +62,7 @@ export default function FilterScopeTree({
       onCheck={onCheck}
       onExpand={onExpand}
       onClick={NOOP}
-      icons={FILTER_SCOPE_CHECKBOX_TREE_ICONS}
+      icons={treeIcons}
     />
   );
 }
diff --git 
a/superset/assets/src/dashboard/components/filterscope/FilterScopeTree.jsx 
b/superset/assets/src/dashboard/components/filterscope/treeIcons.jsx
similarity index 54%
copy from 
superset/assets/src/dashboard/components/filterscope/FilterScopeTree.jsx
copy to superset/assets/src/dashboard/components/filterscope/treeIcons.jsx
index e433f15..a11fec6 100644
--- a/superset/assets/src/dashboard/components/filterscope/FilterScopeTree.jsx
+++ b/superset/assets/src/dashboard/components/filterscope/treeIcons.jsx
@@ -17,39 +17,16 @@
  * under the License.
  */
 import React from 'react';
-import PropTypes from 'prop-types';
-import CheckboxTree from 'react-checkbox-tree';
-import 'react-checkbox-tree/lib/react-checkbox-tree.css';
 import { t } from '@superset-ui/translation';
+import 'react-checkbox-tree/lib/react-checkbox-tree.css';
 
 import {
   CheckboxChecked,
   CheckboxUnchecked,
   CheckboxHalfChecked,
 } from '../../../components/CheckboxIcons';
-import renderFilterScopeTreeNodes from './renderFilterScopeTreeNodes';
-import { filterScopeSelectorTreeNodePropShape } from '../../util/propShapes';
-
-const propTypes = {
-  nodes: PropTypes.arrayOf(filterScopeSelectorTreeNodePropShape).isRequired,
-  checked: PropTypes.arrayOf(
-    PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
-  ).isRequired,
-  expanded: PropTypes.arrayOf(
-    PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
-  ).isRequired,
-  onCheck: PropTypes.func.isRequired,
-  onExpand: PropTypes.func.isRequired,
-  selectedChartId: PropTypes.oneOfType([null, PropTypes.number]),
-};
 
-const defaultProps = {
-  selectedChartId: null,
-};
-
-const NOOP = () => {};
-
-const FILTER_SCOPE_CHECKBOX_TREE_ICONS = {
+const treeIcons = {
   check: <CheckboxChecked />,
   uncheck: <CheckboxUnchecked />,
   halfCheck: <CheckboxHalfChecked />,
@@ -66,29 +43,4 @@ const FILTER_SCOPE_CHECKBOX_TREE_ICONS = {
   leaf: <span className="rct-icon rct-icon-leaf" />,
 };
 
-export default function FilterScopeTree({
-  nodes = [],
-  checked = [],
-  expanded = [],
-  onCheck,
-  onExpand,
-  selectedChartId,
-}) {
-  return (
-    <CheckboxTree
-      showExpandAll
-      expandOnClick
-      showNodeIcon={false}
-      nodes={renderFilterScopeTreeNodes({ nodes, selectedChartId })}
-      checked={checked}
-      expanded={expanded}
-      onCheck={onCheck}
-      onExpand={onExpand}
-      onClick={NOOP}
-      icons={FILTER_SCOPE_CHECKBOX_TREE_ICONS}
-    />
-  );
-}
-
-FilterScopeTree.propTypes = propTypes;
-FilterScopeTree.defaultProps = defaultProps;
+export default treeIcons;
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..7ed9bd8 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,18 +48,15 @@ function mapStateToProps(state) {
     dashboardState,
     charts,
     datasources,
-    // filters prop: All the filter_box's state in this dashboard
+    // filters prop: a map structure for all the active filter_box's values 
and scope in this dashboard,
+    // for each filter field. map key is [chartId_column]
     // 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
-    filters: getActiveFilters(),
+    activeFilters: getActiveFilters(),
     slices: sliceEntities.slices,
     layout: dashboardLayout.present,
     impressionId,
-    loadStats: getLoadStatsPerTopLevelComponent({
-      layout: dashboardLayout.present,
-      chartQueries: charts,
-    }),
   };
 }
 
diff --git a/superset/assets/src/dashboard/containers/DashboardHeader.jsx 
b/superset/assets/src/dashboard/containers/DashboardHeader.jsx
index 9d11998..88e54ac 100644
--- a/superset/assets/src/dashboard/containers/DashboardHeader.jsx
+++ b/superset/assets/src/dashboard/containers/DashboardHeader.jsx
@@ -52,7 +52,6 @@ import {
 
 import { logEvent } from '../../logger/actions';
 import { DASHBOARD_HEADER_ID } from '../util/constants';
-import { getActiveFilters } from '../util/activeDashboardFilters';
 
 function mapStateToProps({
   dashboardLayout: undoableLayout,
@@ -65,7 +64,6 @@ function mapStateToProps({
     undoLength: undoableLayout.past.length,
     redoLength: undoableLayout.future.length,
     layout: undoableLayout.present,
-    filters: getActiveFilters(),
     dashboardTitle: (
       (undoableLayout.present[DASHBOARD_HEADER_ID] || {}).meta || {}
     ).text,
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 f88d665..d816df9 100644
--- a/superset/assets/src/dashboard/containers/FilterScope.jsx
+++ b/superset/assets/src/dashboard/containers/FilterScope.jsx
@@ -19,15 +19,13 @@
 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 }) 
{
+function mapStateToProps({ dashboardLayout, dashboardFilters }) {
   return {
     dashboardFilters,
-    filterImmuneSlices: dashboardInfo.metadata.filterImmuneSlices || [],
-    filterImmuneSliceFields:
-      dashboardInfo.metadata.filterImmuneSliceFields || {},
     layout: dashboardLayout.present,
   };
 }
@@ -35,7 +33,8 @@ function mapStateToProps({ dashboardLayout, dashboardFilters, 
dashboardInfo }) {
 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 4c1f14d..7886ddc 100644
--- a/superset/assets/src/dashboard/reducers/dashboardFilters.js
+++ b/superset/assets/src/dashboard/reducers/dashboardFilters.js
@@ -22,12 +22,15 @@ 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 { DASHBOARD_ROOT_ID } from '../util/constants';
+import { getChartIdAndColumnFromFilterKey } from 
'../util/getDashboardFilterKey';
 
 export const DASHBOARD_FILTER_SCOPE_GLOBAL = {
   scope: [DASHBOARD_ROOT_ID],
@@ -35,7 +38,7 @@ export const DASHBOARD_FILTER_SCOPE_GLOBAL = {
 };
 
 export const dashboardFilter = {
-  chartId: 0,
+  chartId: null,
   componentId: null,
   filterName: null,
   directPathToFilter: [],
@@ -46,6 +49,8 @@ export const dashboardFilter = {
   scopes: {},
 };
 
+const CHANGE_FILTER_VALUE_ACTIONS = [ADD_FILTER, REMOVE_FILTER, CHANGE_FILTER];
+
 export default function dashboardFiltersReducer(dashboardFilters = {}, action) 
{
   const actionHandlers = {
     [ADD_FILTER]() {
@@ -113,10 +118,43 @@ export default function 
dashboardFiltersReducer(dashboardFilters = {}, action) {
     },
   };
 
-  if (action.type === REMOVE_FILTER) {
+  if (action.type === UPDATE_LAYOUT_COMPONENTS) {
+    buildActiveFilters({
+      dashboardFilters,
+      components: action.components,
+    });
+    return dashboardFilters;
+  } else if (action.type === UPDATE_DASHBOARD_FILTERS_SCOPE) {
+    const allDashboardFiltersScope = action.scopes;
+    // update filter scope for each filter field
+    const updatedFilters = Object.entries(allDashboardFiltersScope).reduce(
+      (map, entry) => {
+        const [filterKey, { scope, immune }] = entry;
+        const { chartId, column } = 
getChartIdAndColumnFromFilterKey(filterKey);
+        const scopes = {
+          ...map[chartId].scopes,
+          [column]: {
+            scope,
+            immune,
+          },
+        };
+        return {
+          ...map,
+          [chartId]: {
+            ...map[chartId],
+            scopes,
+          },
+        };
+      },
+      dashboardFilters,
+    );
+
+    buildActiveFilters({ dashboardFilters: updatedFilters });
+    return updatedFilters;
+  } else if (action.type === REMOVE_FILTER) {
     const { chartId } = action;
     const { [chartId]: deletedFilter, ...updatedFilters } = dashboardFilters;
-    buildActiveFilters(updatedFilters);
+    buildActiveFilters({ dashboardFilters: updatedFilters });
     buildFilterColorMap(updatedFilters);
 
     return updatedFilters;
@@ -127,8 +165,11 @@ export default function 
dashboardFiltersReducer(dashboardFilters = {}, action) {
         dashboardFilters[action.chartId],
       ),
     };
-    buildActiveFilters(updatedFilters);
-    buildFilterColorMap(updatedFilters);
+
+    if (CHANGE_FILTER_VALUE_ACTIONS.includes(action.type)) {
+      buildActiveFilters({ dashboardFilters: updatedFilters });
+      buildFilterColorMap(updatedFilters);
+    }
 
     return updatedFilters;
   }
diff --git a/superset/assets/src/dashboard/reducers/dashboardLayout.js 
b/superset/assets/src/dashboard/reducers/dashboardLayout.js
index 23459e0..d473cb6 100644
--- a/superset/assets/src/dashboard/reducers/dashboardLayout.js
+++ b/superset/assets/src/dashboard/reducers/dashboardLayout.js
@@ -155,6 +155,7 @@ const actionHandlers = {
       const destinationChildren = destinationEntity.children;
       const newRow = newComponentFactory(ROW_TYPE);
       newRow.children = [destinationChildren[destination.index]];
+      newRow.parents = (destinationEntity.parents || 
[]).concat(destination.id);
       destinationChildren[destination.index] = newRow.id;
       nextEntities[newRow.id] = newRow;
     }
diff --git a/superset/assets/src/dashboard/reducers/getInitialState.js 
b/superset/assets/src/dashboard/reducers/getInitialState.js
index 185a8b0..91f7483 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,11 @@ export default function(bootstrapData) {
   let newSlicesContainer;
   let newSlicesContainerWidth = 0;
 
+  const filterImmuneSliceFields =
+    dashboard.metadata.filter_immune_slice_fields || {};
+  const filterImmuneSlices = dashboard.metadata.filter_immune_slices || [];
+  const filterScopes = dashboard.metadata.filter_scopes || {};
+
   const chartQueries = {};
   const dashboardFilters = {};
   const slices = {};
@@ -173,6 +181,34 @@ export default function(bootstrapData) {
           });
         }
 
+        // backward compatible:
+        // merge scoped filter settings with old global immune settings
+        const scopesByChartId = Object.keys(columns).reduce((map, column) => {
+          const scopeSettings = {
+            ...filterScopes[key],
+          };
+          const { scope, immune } = {
+            ...DASHBOARD_FILTER_SCOPE_GLOBAL,
+            ...scopeSettings[column],
+          };
+          const immuneChartIds = new Set(filterImmuneSlices);
+          Object.keys(filterImmuneSliceFields)
+            .filter(strChartId =>
+              filterImmuneSliceFields[strChartId].includes(column),
+            )
+            .forEach(strChartId => {
+              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 +220,7 @@ export default function(bootstrapData) {
           directPathToFilter,
           columns,
           labels,
+          scopes: scopesByChartId,
           isInstantFilter: !!slice.form_data.instant_filtering,
           isDateFilter: Object.keys(columns).includes(TIME_RANGE),
         };
@@ -198,8 +235,11 @@ export default function(bootstrapData) {
       layout[layoutId].meta.sliceName = slice.slice_name;
     }
   });
-  buildActiveFilters(dashboardFilters);
-  buildFilterColorMap(dashboardFilters);
+  buildActiveFilters({
+    dashboardFilters,
+    components: layout,
+  });
+  buildFilterColorMap(dashboardFilters, layout);
 
   // store the header as a layout component so we can undo/redo changes
   layout[DASHBOARD_HEADER_ID] = {
@@ -233,9 +273,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/stylesheets/filter-scope-selector.less 
b/superset/assets/src/dashboard/stylesheets/filter-scope-selector.less
index dd19a66..6df2826 100644
--- a/superset/assets/src/dashboard/stylesheets/filter-scope-selector.less
+++ b/superset/assets/src/dashboard/stylesheets/filter-scope-selector.less
@@ -88,6 +88,7 @@
       label {
         font-weight: normal;
         margin: 0 0 0 16px;
+        word-break: break-all;
       }
     }
 
diff --git a/superset/assets/src/dashboard/util/activeDashboardFilters.js 
b/superset/assets/src/dashboard/util/activeDashboardFilters.js
index 8fa00d0..3674eb3 100644
--- a/superset/assets/src/dashboard/util/activeDashboardFilters.js
+++ b/superset/assets/src/dashboard/util/activeDashboardFilters.js
@@ -16,52 +16,119 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-let activeFilters = {};
+import { isEmpty } from 'lodash';
+import { mapValues, flow, keyBy } from 'lodash/fp';
+
+import {
+  getChartIdAndColumnFromFilterKey,
+  getDashboardFilterKey,
+} from './getDashboardFilterKey';
+import { CHART_TYPE } from '../util/componentTypes';
+
 let allFilterBoxChartIds = [];
+let activeFilters = {};
+let appliedFilterValuesByChart = {};
+let allComponents = {};
 
+// output: { [id_column]: { values, scope } }
 export function getActiveFilters() {
   return activeFilters;
 }
 
-// currently filterbox 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,
-// will not need this check anymore
+// currently filter_box is a chart,
+// when selecting filter scopes, they have to be out pulled out in a few 
places.
+// after we make filter_box a dashboard build-in component, will not need this 
check anymore.
 export function isFilterBox(chartId) {
   return allFilterBoxChartIds.includes(chartId);
 }
 
-export function getAllFilterBoxChartIds() {
-  return allFilterBoxChartIds;
+// this function is to find all filter values applied to a chart,
+// it goes through all active filters and their scopes.
+// return: { [column]: array of selected values }
+export function getAppliedFilterValues(chartId) {
+  // use cached data if possible
+  if (!(chartId in appliedFilterValuesByChart)) {
+    const applicableFilters = Object.entries(activeFilters).filter(
+      ([, { scope: chartIds }]) => chartIds.includes(chartId),
+    );
+    appliedFilterValuesByChart[chartId] = flow(
+      keyBy(
+        ([filterKey]) => getChartIdAndColumnFromFilterKey(filterKey).column,
+      ),
+      mapValues(([, { values }]) => values),
+    )(applicableFilters);
+  }
+  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 = {}) {
-  allFilterBoxChartIds = Object.values(allDashboardFilters).map(
+// non-empty filter fields in dashboardFilters,
+// activeFilters map contains selected values and filter scope.
+// values: array of selected values
+// scope: array of chartIds that applicable to the filter field.
+export function buildActiveFilters({ dashboardFilters = {}, components = {} }) 
{
+  allFilterBoxChartIds = Object.values(dashboardFilters).map(
     filter => filter.chartId,
   );
 
-  activeFilters = Object.values(allDashboardFilters).reduce(
-    (result, filter) => {
-      const { chartId, columns } = filter;
+  // clear cache
+  if (!isEmpty(components)) {
+    allComponents = components;
+  }
+  appliedFilterValuesByChart = {};
+  activeFilters = Object.values(dashboardFilters).reduce((result, filter) => {
+    const { chartId, columns, scopes } = filter;
+    const nonEmptyFilters = {};
 
-      Object.keys(columns).forEach(key => {
-        if (
-          Array.isArray(columns[key])
-            ? columns[key].length
-            : columns[key] !== undefined
-        ) {
-          /* eslint-disable no-param-reassign */
-          result[chartId] = {
-            ...result[chartId],
-            [key]: columns[key],
-          };
-        }
-      });
+    Object.keys(columns).forEach(column => {
+      if (
+        Array.isArray(columns[column])
+          ? columns[column].length
+          : columns[column] !== undefined
+      ) {
+        // remove filter itself
+        const scope = getChartIdsInFilterScope({
+          filterScope: scopes[column],
+        }).filter(id => chartId !== id);
 
-      return result;
-    },
-    {},
-  );
+        nonEmptyFilters[getDashboardFilterKey({ chartId, column })] = {
+          values: columns[column],
+          scope,
+        };
+      }
+    });
+
+    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..b7a768b 100644
--- a/superset/assets/src/dashboard/util/charts/getEffectiveExtraFilters.js
+++ b/superset/assets/src/dashboard/util/charts/getEffectiveExtraFilters.js
@@ -16,45 +16,10 @@
  * 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
-  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],
-        });
-      }
-    });
-  });
-
-  return effectiveFilters;
+export default function getEffectiveExtraFilters(filters) {
+  return Object.entries(filters).map(([column, values]) => ({
+    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..3566931 100644
--- a/superset/assets/src/dashboard/util/charts/getFormDataWithExtraFilters.js
+++ b/superset/assets/src/dashboard/util/charts/getFormDataWithExtraFilters.js
@@ -22,13 +22,14 @@ 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 = {};
 
+// this function merge chart's formData with dashboard filters value,
+// and generate a new formData which will be used in the new query.
+// filters param only contains those applicable to this chart.
 export default function getFormDataWithExtraFilters({
   chart = {},
-  dashboardMetadata,
   filters,
   colorScheme,
   colorNamespace,
@@ -40,7 +41,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 +55,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 60d86b5..0000000
--- a/superset/assets/src/dashboard/util/getCurrentScopeChartIds.js
+++ /dev/null
@@ -1,62 +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,
-}) {
-  let chartIds = [];
-
-  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]));
-    }
-  }
-
-  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/getDashboardUrl.js 
b/superset/assets/src/dashboard/util/getDashboardUrl.js
index 243c8cb..bf2040c 100644
--- a/superset/assets/src/dashboard/util/getDashboardUrl.js
+++ b/superset/assets/src/dashboard/util/getDashboardUrl.js
@@ -16,10 +16,13 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/* eslint camelcase: 0 */
+import serializeActiveFilterValues from './serializeActiveFilterValues';
 
 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 = serializeActiveFilterValues(filters);
+  const preselectFilters = encodeURIComponent(JSON.stringify(obj));
   const hashSection = hash ? `#${hash}` : '';
-  return `${pathname}?preselect_filters=${preselect_filters}${hashSection}`;
+  return `${pathname}?preselect_filters=${preselectFilters}${hashSection}`;
 }
diff --git a/superset/assets/src/dashboard/util/getFilterFieldNodesTree.js 
b/superset/assets/src/dashboard/util/getFilterFieldNodesTree.js
index b55d28f..5d9b59e 100644
--- a/superset/assets/src/dashboard/util/getFilterFieldNodesTree.js
+++ b/superset/assets/src/dashboard/util/getFilterFieldNodesTree.js
@@ -39,7 +39,7 @@ export default function getFilterFieldNodesTree({ 
dashboardFilters = {} }) {
   return [
     {
       value: ALL_FILTERS_ROOT,
-      label: t('Select/deselect all filters'),
+      label: t('All filters'),
       children: allFilters,
     },
   ];
diff --git a/superset/assets/src/dashboard/util/getFilterScopeNodesTree.js 
b/superset/assets/src/dashboard/util/getFilterScopeNodesTree.js
index 470ac08..60669bd 100644
--- a/superset/assets/src/dashboard/util/getFilterScopeNodesTree.js
+++ b/superset/assets/src/dashboard/util/getFilterScopeNodesTree.js
@@ -86,7 +86,7 @@ function traverse({
   if (FILTER_SCOPE_CONTAINER_TYPES.includes(type)) {
     let label = null;
     if (type === DASHBOARD_ROOT_TYPE) {
-      label = t('Select/deselect all charts');
+      label = t('All charts');
     } else {
       label =
         currentNode.meta && currentNode.meta.text
diff --git a/superset/assets/src/dashboard/util/getKeyForFilterScopeTree.js 
b/superset/assets/src/dashboard/util/getFilterValuesByFilterId.js
similarity index 58%
copy from superset/assets/src/dashboard/util/getKeyForFilterScopeTree.js
copy to superset/assets/src/dashboard/util/getFilterValuesByFilterId.js
index e85dd51..75f9c70 100644
--- a/superset/assets/src/dashboard/util/getKeyForFilterScopeTree.js
+++ b/superset/assets/src/dashboard/util/getFilterValuesByFilterId.js
@@ -16,13 +16,23 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { safeStringify } from '../../utils/safeStringify';
+import { getChartIdAndColumnFromFilterKey } from './getDashboardFilterKey';
 
-export default function getKeyForFilterScopeTree({
-  activeFilterField,
-  checkedFilterFields,
+// input: { [id_column1]: values, [id_column2]: values }
+// output: { column1: values, column2: values }
+export default function getFilterValuesByFilterId({
+  activeFilters = {},
+  filterId,
 }) {
-  return activeFilterField
-    ? safeStringify([activeFilterField])
-    : safeStringify(checkedFilterFields);
+  return Object.entries(activeFilters).reduce((map, entry) => {
+    const [filterKey, { values }] = entry;
+    const { chartId, column } = getChartIdAndColumnFromFilterKey(filterKey);
+    if (chartId === filterId) {
+      return {
+        ...map,
+        [column]: values,
+      };
+    }
+    return map;
+  }, {});
 }
diff --git a/superset/assets/src/dashboard/util/getKeyForFilterScopeTree.js 
b/superset/assets/src/dashboard/util/getKeyForFilterScopeTree.js
index e85dd51..808bbf8 100644
--- a/superset/assets/src/dashboard/util/getKeyForFilterScopeTree.js
+++ b/superset/assets/src/dashboard/util/getKeyForFilterScopeTree.js
@@ -22,7 +22,7 @@ export default function getKeyForFilterScopeTree({
   activeFilterField,
   checkedFilterFields,
 }) {
-  return activeFilterField
-    ? safeStringify([activeFilterField])
-    : safeStringify(checkedFilterFields);
+  return safeStringify(
+    activeFilterField ? [activeFilterField] : checkedFilterFields,
+  );
 }
diff --git a/superset/assets/src/dashboard/util/getKeyForFilterScopeTree.js 
b/superset/assets/src/dashboard/util/isInDifferentFilterScopes.js
similarity index 64%
copy from superset/assets/src/dashboard/util/getKeyForFilterScopeTree.js
copy to superset/assets/src/dashboard/util/isInDifferentFilterScopes.js
index e85dd51..3789d07 100644
--- a/superset/assets/src/dashboard/util/getKeyForFilterScopeTree.js
+++ b/superset/assets/src/dashboard/util/isInDifferentFilterScopes.js
@@ -16,13 +16,18 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { safeStringify } from '../../utils/safeStringify';
-
-export default function getKeyForFilterScopeTree({
-  activeFilterField,
-  checkedFilterFields,
+export default function isInDifferentFilterScopes({
+  dashboardFilters = {},
+  source = [],
+  destination = [],
 }) {
-  return activeFilterField
-    ? safeStringify([activeFilterField])
-    : safeStringify(checkedFilterFields);
+  const sourceSet = new Set(source);
+  const destinationSet = new Set(destination);
+
+  const allScopes = [].concat(
+    ...Object.values(dashboardFilters).map(({ scopes }) =>
+      [].concat(...Object.values(scopes).map(({ scope }) => scope)),
+    ),
+  );
+  return allScopes.some(tab => destinationSet.has(tab) !== sourceSet.has(tab));
 }
diff --git a/superset/assets/src/dashboard/util/newEntitiesFromDrop.js 
b/superset/assets/src/dashboard/util/newEntitiesFromDrop.js
index e1febff..c0e5157 100644
--- a/superset/assets/src/dashboard/util/newEntitiesFromDrop.js
+++ b/superset/assets/src/dashboard/util/newEntitiesFromDrop.js
@@ -30,6 +30,7 @@ export default function newEntitiesFromDrop({ dropResult, 
layout }) {
   const dropEntity = layout[destination.id];
   const dropType = dropEntity.type;
   let newDropChild = newComponentFactory(dragType, dragging.meta);
+  newDropChild.parents = (dropEntity.parents || []).concat(dropEntity.id);
 
   if (componentIsResizable(dragging)) {
     newDropChild.meta.width = // don't set a 0 width
@@ -48,11 +49,14 @@ export default function newEntitiesFromDrop({ dropResult, 
layout }) {
   if (wrapChildInRow) {
     const rowWrapper = newComponentFactory(ROW_TYPE);
     rowWrapper.children = [newDropChild.id];
+    rowWrapper.parents = (dropEntity.parents || []).concat(dropEntity.id);
     newEntities[rowWrapper.id] = rowWrapper;
     newDropChild = rowWrapper;
+    newDropChild.parents = rowWrapper.parents.concat(rowWrapper.id);
   } else if (dragType === TABS_TYPE) {
     // create a new tab component
     const tabChild = newComponentFactory(TAB_TYPE);
+    tabChild.parents = (dropEntity.parents || []).concat(dropEntity.id);
     newDropChild.children = [tabChild.id];
     newEntities[tabChild.id] = tabChild;
   }
diff --git a/superset/assets/src/dashboard/util/propShapes.jsx 
b/superset/assets/src/dashboard/util/propShapes.jsx
index b59f41e..6fb83e8 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,10 +80,21 @@ 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 dashboardFilterPropShape = PropTypes.shape({
+  chartId: PropTypes.number.isRequired,
+  componentId: PropTypes.string.isRequired,
+  filterName: 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({
   sliceIds: PropTypes.arrayOf(PropTypes.number).isRequired,
   expandedSlices: PropTypes.object,
@@ -128,16 +140,3 @@ export const filterScopeSelectorTreeNodePropShape = 
PropTypes.oneOfType([
   PropTypes.shape(parentShape),
   leafType,
 ]);
-
-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,
-  }),
-);
diff --git a/superset/assets/src/dashboard/util/getDashboardUrl.js 
b/superset/assets/src/dashboard/util/serializeActiveFilterValues.js
similarity index 57%
copy from superset/assets/src/dashboard/util/getDashboardUrl.js
copy to superset/assets/src/dashboard/util/serializeActiveFilterValues.js
index 243c8cb..6497b10 100644
--- a/superset/assets/src/dashboard/util/getDashboardUrl.js
+++ b/superset/assets/src/dashboard/util/serializeActiveFilterValues.js
@@ -16,10 +16,21 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/* eslint camelcase: 0 */
+import { getChartIdAndColumnFromFilterKey } from './getDashboardFilterKey';
 
-export default function getDashboardUrl(pathname, filters = {}, hash = '') {
-  const preselect_filters = encodeURIComponent(JSON.stringify(filters));
-  const hashSection = hash ? `#${hash}` : '';
-  return `${pathname}?preselect_filters=${preselect_filters}${hashSection}`;
+// input: { [id_column1]: values, [id_column2]: values }
+// output: { id: { column1: values, column2: values } }
+export default function serializeActiveFilterValues(activeFilters) {
+  return Object.entries(activeFilters).reduce((map, entry) => {
+    const [filterKey, { values }] = entry;
+    const { chartId, column } = getChartIdAndColumnFromFilterKey(filterKey);
+    const entryByChartId = {
+      ...map[chartId],
+      [column]: values,
+    };
+    return {
+      ...map,
+      [chartId]: entryByChartId,
+    };
+  }, {});
 }
diff --git 
a/superset/assets/spec/javascripts/dashboard/util/getDashboardUrl_spec.js 
b/superset/assets/src/dashboard/util/serializeFilterScopes.js
similarity index 67%
copy from 
superset/assets/spec/javascripts/dashboard/util/getDashboardUrl_spec.js
copy to superset/assets/src/dashboard/util/serializeFilterScopes.js
index 76a2da6..303171f 100644
--- a/superset/assets/spec/javascripts/dashboard/util/getDashboardUrl_spec.js
+++ b/superset/assets/src/dashboard/util/serializeFilterScopes.js
@@ -16,14 +16,19 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import getDashboardUrl from '../../../../src/dashboard/util/getDashboardUrl';
-
-describe('getChartIdsFromLayout', () => {
-  it('should encode filters', () => {
-    const filters = { 35: { key: ['value'] } };
-    const url = getDashboardUrl('path', filters);
-    expect(url).toBe(
-      
'path?preselect_filters=%7B%2235%22%3A%7B%22key%22%3A%5B%22value%22%5D%7D%7D',
+export default function serializeFilterScopes(dashboardFilters) {
+  return Object.values(dashboardFilters).reduce((map, { chartId, scopes }) => {
+    const scopesById = Object.keys(scopes).reduce(
+      (scopesByColumn, column) => ({
+        ...scopesByColumn,
+        [column]: scopes[column],
+      }),
+      {},
     );
-  });
-});
+
+    return {
+      ...map,
+      [chartId]: scopesById,
+    };
+  }, {});
+}
diff --git a/superset/views/core.py b/superset/views/core.py
index cbbd1a8..707e0be 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -1657,12 +1657,18 @@ class Superset(BaseSupersetView):
         dashboard.css = data.get("css")
         dashboard.dashboard_title = data["dashboard_title"]
 
-        if "filter_immune_slices" not in md:
-            md["filter_immune_slices"] = []
         if "timed_refresh_immune_slices" not in md:
             md["timed_refresh_immune_slices"] = []
-        if "filter_immune_slice_fields" not in md:
-            md["filter_immune_slice_fields"] = {}
+
+        if "filter_scopes" in data:
+            md.pop("filter_immune_slices", None)
+            md.pop("filter_immune_slice_fields", None)
+            md["filter_scopes"] = json.loads(data.get("filter_scopes", "{}"))
+        else:
+            if "filter_immune_slices" not in md:
+                md["filter_immune_slices"] = []
+            if "filter_immune_slice_fields" not in md:
+                md["filter_immune_slice_fields"] = {}
         md["expanded_slices"] = data["expanded_slices"]
         md["refresh_frequency"] = data.get("refresh_frequency", 0)
         default_filters_data = json.loads(data.get("default_filters", "{}"))

Reply via email to