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

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


The following commit(s) were added to refs/heads/master by this push:
     new 6b8bda6  [dashboard] After update filter, trigger new queries when 
charts are visible (#7233)
6b8bda6 is described below

commit 6b8bda609676364436941a491c6cd91a7e6032b9
Author: Grace Guo <[email protected]>
AuthorDate: Tue May 7 23:41:18 2019 -0700

    [dashboard] After update filter, trigger new queries when charts are 
visible (#7233)
    
    * trigger query when chart is visible
    
    * add integration test
---
 .../integration/dashboard/dashboard.helper.js      |   3 +-
 .../cypress/integration/dashboard/index.test.js    |   2 +
 .../assets/cypress/integration/dashboard/tabs.js   | 157 ++++++++++
 .../dashboard/components/Dashboard_spec.jsx        |  10 +-
 .../components/ExploreViewContainer_spec.jsx       |  32 +-
 superset/assets/src/chart/Chart.jsx                |  48 +--
 .../assets/src/dashboard/components/Dashboard.jsx  |  19 +-
 .../src/dashboard/components/DashboardBuilder.jsx  |   1 +
 .../src/dashboard/components/DashboardGrid.jsx     |   3 +
 .../dashboard/components/gridComponents/Chart.jsx  |  30 +-
 .../components/gridComponents/ChartHolder.jsx      |   2 +
 .../dashboard/components/gridComponents/Column.jsx |   2 +
 .../dashboard/components/gridComponents/Row.jsx    |   2 +
 .../dashboard/components/gridComponents/Tab.jsx    |   2 +
 .../dashboard/components/gridComponents/Tabs.jsx   |   1 +
 .../assets/src/dashboard/containers/Dashboard.jsx  |   4 +-
 .../dashboard/containers/DashboardComponent.jsx    |   1 +
 .../explore/components/ExploreViewContainer.jsx    |   1 -
 superset/cli.py                                    |   3 +
 superset/data/__init__.py                          |   1 +
 superset/data/tabbed_dashboard.py                  | 324 +++++++++++++++++++++
 21 files changed, 591 insertions(+), 57 deletions(-)

diff --git a/superset/assets/cypress/integration/dashboard/dashboard.helper.js 
b/superset/assets/cypress/integration/dashboard/dashboard.helper.js
index 7e3788f..1c16a82 100644
--- a/superset/assets/cypress/integration/dashboard/dashboard.helper.js
+++ b/superset/assets/cypress/integration/dashboard/dashboard.helper.js
@@ -16,7 +16,8 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-export const WORLD_HEALTH_DASHBOARD = '/superset/dashboard/world_health';
+export const WORLD_HEALTH_DASHBOARD = '/superset/dashboard/world_health/';
+export const TABBED_DASHBOARD = '/superset/dashboard/tabbed_dash/';
 
 export const CHECK_DASHBOARD_FAVORITE_ENDPOINT = 
'/superset/favstar/Dashboard/*/count';
 
diff --git a/superset/assets/cypress/integration/dashboard/index.test.js 
b/superset/assets/cypress/integration/dashboard/index.test.js
index 9763b91..cc608e7 100644
--- a/superset/assets/cypress/integration/dashboard/index.test.js
+++ b/superset/assets/cypress/integration/dashboard/index.test.js
@@ -22,6 +22,7 @@ import DashboardFavStarTest from './fav_star';
 import DashboardFilterTest from './filter';
 import DashboardLoadTest from './load';
 import DashboardSaveTest from './save';
+import DashboardTabsTest from './tabs';
 
 describe('Dashboard', () => {
   DashboardControlsTest();
@@ -30,4 +31,5 @@ describe('Dashboard', () => {
   DashboardFilterTest();
   DashboardLoadTest();
   DashboardSaveTest();
+  DashboardTabsTest();
 });
diff --git a/superset/assets/cypress/integration/dashboard/tabs.js 
b/superset/assets/cypress/integration/dashboard/tabs.js
new file mode 100644
index 0000000..5029a00
--- /dev/null
+++ b/superset/assets/cypress/integration/dashboard/tabs.js
@@ -0,0 +1,157 @@
+/**
+ * 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 { TABBED_DASHBOARD } from './dashboard.helper';
+
+export default () => describe('tabs', () => {
+  let filterId;
+  let treemapId;
+  let linechartId;
+  let boxplotId;
+
+  // cypress can not handle window.scrollTo
+  // https://github.com/cypress-io/cypress/issues/2761
+  // add this exception handler to pass test
+  const handleException = () => {
+    // return false to prevent the error from
+    // failing this test
+    cy.on('uncaught:exception', () => false);
+  };
+
+  beforeEach(() => {
+    cy.server();
+    cy.login();
+
+    cy.visit(TABBED_DASHBOARD);
+
+    cy.get('#app').then((data) => {
+      const bootstrapData = JSON.parse(data[0].dataset.bootstrap);
+      const dashboard = bootstrapData.dashboard_data;
+      filterId = dashboard.slices.find(slice => (slice.form_data.viz_type === 
'filter_box')).slice_id;
+      boxplotId = dashboard.slices.find(slice => (slice.form_data.viz_type === 
'box_plot')).slice_id;
+      treemapId = dashboard.slices.find(slice => (slice.form_data.viz_type === 
'treemap')).slice_id;
+      linechartId = dashboard.slices.find(slice => (slice.form_data.viz_type 
=== 'line')).slice_id;
+
+      const filterFormdata = {
+        slice_id: filterId,
+      };
+      const filterRequest = 
`/superset/explore_json/?form_data=${JSON.stringify(filterFormdata)}`;
+      cy.route('POST', filterRequest).as('filterRequest');
+
+      const treemapFormdata = {
+        slice_id: treemapId,
+      };
+      const treemapRequest = 
`/superset/explore_json/?form_data=${JSON.stringify(treemapFormdata)}`;
+      cy.route('POST', treemapRequest).as('treemapRequest');
+
+      const linechartFormdata = {
+        slice_id: linechartId,
+      };
+      const linechartRequest = 
`/superset/explore_json/?form_data=${JSON.stringify(linechartFormdata)}`;
+      cy.route('POST', linechartRequest).as('linechartRequest');
+
+      const boxplotFormdata = {
+        slice_id: boxplotId,
+      };
+      const boxplotRequest = 
`/superset/explore_json/?form_data=${JSON.stringify(boxplotFormdata)}`;
+      cy.route('POST', boxplotRequest).as('boxplotRequest');
+    });
+  });
+
+  it('should load charts when tab is visible', () => {
+    // landing in first tab, should see 2 charts
+    cy.wait('@filterRequest');
+    cy.get('.grid-container .filter_box').should('be.exist');
+    cy.wait('@treemapRequest');
+    cy.get('.grid-container .treemap').should('be.exist');
+    cy.get('.grid-container .box_plot').should('not.be.exist');
+    cy.get('.grid-container .line').should('not.be.exist');
+
+    // click row level tab, see 1 more chart
+    cy.get('.tab-content ul.nav.nav-tabs li')
+      .last()
+      .find('.editable-title input')
+      .click();
+    cy.wait('@linechartRequest');
+    cy.get('.grid-container .line').should('be.exist');
+
+    // click top level tab, see 1 more chart
+    handleException();
+    cy.get('.dashboard-component-tabs')
+      .first()
+      .find('ul.nav.nav-tabs li')
+      .last()
+      .find('.editable-title input')
+      .click();
+    cy.wait('@boxplotRequest');
+    cy.get('.grid-container .box_plot').should('be.exist');
+  });
+
+  it('should send new queries when tab becomes visible', () => {
+    // landing in first tab
+    cy.wait('@filterRequest');
+    cy.wait('@treemapRequest');
+
+    // creating route and stubbing filtered route
+    cy.route('POST', '/superset/explore_json/*').as('updatedChartRequest');
+
+    // apply filter
+    cy.get('.Select-control')
+      .first()
+      .find('input')
+      .first()
+      .type('South Asia{enter}', { force: true });
+
+    // send new query from same tab
+    cy.wait('@updatedChartRequest')
+      .then((xhr) => {
+        const requestFormData = xhr.request.body;
+        const requestParams = JSON.parse(requestFormData.get('form_data'));
+        expect(requestParams.extra_filters[0])
+          .deep.eq({ col: 'region', op: 'in', val: ['South Asia'] });
+      });
+
+    // click row level tab, send 1 more query
+    cy.get('.tab-content ul.nav.nav-tabs li')
+      .last()
+      .click();
+    cy.wait('@updatedChartRequest')
+      .then((xhr) => {
+        const requestFormData = xhr.request.body;
+        const requestParams = JSON.parse(requestFormData.get('form_data'));
+        expect(requestParams.extra_filters[0])
+          .deep.eq({ col: 'region', op: 'in', val: ['South Asia'] });
+      });
+
+    // click top level tab, send 1 more query
+    handleException();
+    cy.get('.dashboard-component-tabs')
+      .first()
+      .find('ul.nav.nav-tabs li')
+      .last()
+      .find('.editable-title input')
+      .click();
+    cy.wait('@updatedChartRequest')
+      .then((xhr) => {
+        const requestFormData = xhr.request.body;
+        const requestParams = JSON.parse(requestFormData.get('form_data'));
+        expect(requestParams.extra_filters[0])
+          .deep.eq({ col: 'region', op: 'in', val: ['South Asia'] });
+      });
+  });
+});
diff --git 
a/superset/assets/spec/javascripts/dashboard/components/Dashboard_spec.jsx 
b/superset/assets/spec/javascripts/dashboard/components/Dashboard_spec.jsx
index de637cd..05d3675 100644
--- a/superset/assets/spec/javascripts/dashboard/components/Dashboard_spec.jsx
+++ b/superset/assets/spec/javascripts/dashboard/components/Dashboard_spec.jsx
@@ -39,7 +39,7 @@ describe('Dashboard', () => {
     actions: {
       addSliceToDashboard() {},
       removeSliceFromDashboard() {},
-      postChartFormData() {},
+      triggerQuery() {},
       logEvent() {},
     },
     initMessages: [],
@@ -82,15 +82,15 @@ describe('Dashboard', () => {
       },
     };
 
-    it('should call postChartFormData for all non-exempt slices', () => {
+    it('should call triggerQuery for all non-exempt slices', () => {
       const wrapper = setup({ charts: overrideCharts, slices: overrideSlices 
});
-      const spy = sinon.spy(props.actions, 'postChartFormData');
+      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 postChartFormData for filter_immune_slices', () => {
+    it('should not call triggerQuery for filter_immune_slices', () => {
       const wrapper = setup({
         charts: overrideCharts,
         dashboardInfo: {
@@ -103,7 +103,7 @@ describe('Dashboard', () => {
           },
         },
       });
-      const spy = sinon.spy(props.actions, 'postChartFormData');
+      const spy = sinon.spy(props.actions, 'triggerQuery');
       wrapper.instance().refreshExcept();
       spy.restore();
       expect(spy.callCount).toBe(0);
diff --git 
a/superset/assets/spec/javascripts/explore/components/ExploreViewContainer_spec.jsx
 
b/superset/assets/spec/javascripts/explore/components/ExploreViewContainer_spec.jsx
index ee3a006..bdaa642 100644
--- 
a/superset/assets/spec/javascripts/explore/components/ExploreViewContainer_spec.jsx
+++ 
b/superset/assets/spec/javascripts/explore/components/ExploreViewContainer_spec.jsx
@@ -19,6 +19,7 @@
 import React from 'react';
 import configureStore from 'redux-mock-store';
 import thunk from 'redux-thunk';
+import sinon from 'sinon';
 import { shallow } from 'enzyme';
 
 import getInitialState from 'src/explore/reducers/getInitialState';
@@ -58,7 +59,7 @@ describe('ExploreViewContainer', () => {
     wrapper = shallow(<ExploreViewContainer />, {
       context: { store },
       disableLifecycleMethods: true,
-    });
+    }).dive();
   });
 
   it('renders', () => {
@@ -68,14 +69,37 @@ describe('ExploreViewContainer', () => {
   });
 
   it('renders QueryAndSaveButtons', () => {
-    expect(wrapper.dive().find(QueryAndSaveBtns)).toHaveLength(1);
+    expect(wrapper.find(QueryAndSaveBtns)).toHaveLength(1);
   });
 
   it('renders ControlPanelsContainer', () => {
-    expect(wrapper.dive().find(ControlPanelsContainer)).toHaveLength(1);
+    expect(wrapper.find(ControlPanelsContainer)).toHaveLength(1);
   });
 
   it('renders ChartContainer', () => {
-    expect(wrapper.dive().find(ChartContainer)).toHaveLength(1);
+    expect(wrapper.find(ChartContainer)).toHaveLength(1);
+  });
+
+  describe('componentWillReceiveProps()', () => {
+    it('when controls change, should call resetControls', () => {
+      expect(wrapper.instance().props.controls.viz_type.value).toBe('table');
+      const resetControls = sinon.stub(wrapper.instance().props.actions, 
'resetControls');
+      const triggerQuery = sinon.stub(wrapper.instance().props.actions, 
'triggerQuery');
+
+      // triggers componentWillReceiveProps
+      wrapper.setProps({
+        controls: {
+          viz_type: {
+            value: 'bar',
+          },
+        },
+      });
+      expect(resetControls.callCount).toBe(1);
+      // exploreview container should not force chart run query
+      // it should be controlled by redux state.
+      expect(triggerQuery.callCount).toBe(0);
+      resetControls.reset();
+      triggerQuery.reset();
+    });
   });
 });
diff --git a/superset/assets/src/chart/Chart.jsx 
b/superset/assets/src/chart/Chart.jsx
index cfc6ce8..0218a17 100644
--- a/superset/assets/src/chart/Chart.jsx
+++ b/superset/assets/src/chart/Chart.jsx
@@ -22,6 +22,7 @@ import { Alert } from 'react-bootstrap';
 
 import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
 import { Logger, LOG_ACTIONS_RENDER_CHART_CONTAINER } from 
'../logger/LogUtils';
+import { safeStringify } from '../utils/safeStringify';
 import Loading from '../components/Loading';
 import RefreshChartOverlay from '../components/RefreshChartOverlay';
 import StackTraceMessage from '../components/StackTraceMessage';
@@ -69,25 +70,38 @@ class Chart extends React.PureComponent {
     super(props);
     this.handleRenderContainerFailure = 
this.handleRenderContainerFailure.bind(this);
   }
+
   componentDidMount() {
     if (this.props.triggerQuery) {
-      if (this.props.chartId > 0 && 
isFeatureEnabled(FeatureFlag.CLIENT_CACHE)) {
-        // Load saved chart with a GET request
-        this.props.actions.getSavedChart(
-          this.props.formData,
-          false,
-          this.props.timeout,
-          this.props.chartId,
-        );
-      } else {
-        // Create chart with POST request
-        this.props.actions.postChartFormData(
-          this.props.formData,
-          false,
-          this.props.timeout,
-          this.props.chartId,
-        );
-      }
+      this.runQuery();
+    }
+  }
+
+  componentDidUpdate(prevProps) {
+    if (this.props.triggerQuery &&
+      safeStringify(prevProps.formData) !== safeStringify(this.props.formData)
+    ) {
+      this.runQuery();
+    }
+  }
+
+  runQuery() {
+    if (this.props.chartId > 0 && isFeatureEnabled(FeatureFlag.CLIENT_CACHE)) {
+      // Load saved chart with a GET request
+      this.props.actions.getSavedChart(
+        this.props.formData,
+        false,
+        this.props.timeout,
+        this.props.chartId,
+      );
+    } else {
+      // Create chart with POST request
+      this.props.actions.postChartFormData(
+        this.props.formData,
+        false,
+        this.props.timeout,
+        this.props.chartId,
+      );
     }
   }
 
diff --git a/superset/assets/src/dashboard/components/Dashboard.jsx 
b/superset/assets/src/dashboard/components/Dashboard.jsx
index b26cde6..b584559 100644
--- a/superset/assets/src/dashboard/components/Dashboard.jsx
+++ b/superset/assets/src/dashboard/components/Dashboard.jsx
@@ -30,7 +30,6 @@ import {
   loadStatsPropShape,
 } from '../util/propShapes';
 import { areObjectsEqual } from '../../reduxUtils';
-import getFormDataWithExtraFilters from 
'../util/charts/getFormDataWithExtraFilters';
 import { LOG_ACTIONS_MOUNT_DASHBOARD } from '../../logger/LogUtils';
 import OmniContainer from '../../components/OmniContainer';
 
@@ -40,7 +39,7 @@ const propTypes = {
   actions: PropTypes.shape({
     addSliceToDashboard: PropTypes.func.isRequired,
     removeSliceFromDashboard: PropTypes.func.isRequired,
-    postChartFormData: PropTypes.func.isRequired,
+    triggerQuery: PropTypes.func.isRequired,
     logEvent: PropTypes.func.isRequired,
   }).isRequired,
   dashboardInfo: dashboardInfoPropShape.isRequired,
@@ -149,21 +148,7 @@ class Dashboard extends React.PureComponent {
     this.getAllCharts().forEach(chart => {
       // filterKey is a string, immune array contains numbers
       if (String(chart.id) !== filterKey && immune.indexOf(chart.id) === -1) {
-        const updatedFormData = getFormDataWithExtraFilters({
-          chart,
-          dashboardMetadata: this.props.dashboardInfo.metadata,
-          filters: this.props.dashboardState.filters,
-          colorScheme: this.props.dashboardState.colorScheme,
-          colorNamespace: this.props.dashboardState.colorNamespace,
-          sliceId: chart.id,
-        });
-
-        this.props.actions.postChartFormData(
-          updatedFormData,
-          false,
-          this.props.timeout,
-          chart.id,
-        );
+        this.props.actions.triggerQuery(true, chart.id);
       }
     });
   }
diff --git a/superset/assets/src/dashboard/components/DashboardBuilder.jsx 
b/superset/assets/src/dashboard/components/DashboardBuilder.jsx
index 4a39955..eadaab4 100644
--- a/superset/assets/src/dashboard/components/DashboardBuilder.jsx
+++ b/superset/assets/src/dashboard/components/DashboardBuilder.jsx
@@ -222,6 +222,7 @@ class DashboardBuilder extends React.Component {
                           // see isValidChild for why tabs do not increment 
the depth of their children
                           depth={DASHBOARD_ROOT_DEPTH + 1} // (topLevelTabs ? 
0 : 1)}
                           width={width}
+                          isComponentVisible={index === tabIndex}
                         />
                       </TabPane>
                     ))}
diff --git a/superset/assets/src/dashboard/components/DashboardGrid.jsx 
b/superset/assets/src/dashboard/components/DashboardGrid.jsx
index b036ad0..0666f47 100644
--- a/superset/assets/src/dashboard/components/DashboardGrid.jsx
+++ b/superset/assets/src/dashboard/components/DashboardGrid.jsx
@@ -30,6 +30,7 @@ const propTypes = {
   editMode: PropTypes.bool.isRequired,
   gridComponent: componentShape.isRequired,
   handleComponentDrop: PropTypes.func.isRequired,
+  isComponentVisible: PropTypes.bool.isRequired,
   resizeComponent: PropTypes.func.isRequired,
   width: PropTypes.number.isRequired,
 };
@@ -114,6 +115,7 @@ class DashboardGrid extends React.PureComponent {
       depth,
       editMode,
       width,
+      isComponentVisible,
     } = this.props;
 
     const columnPlusGutterWidth =
@@ -154,6 +156,7 @@ class DashboardGrid extends React.PureComponent {
               index={index}
               availableColumnCount={GRID_COLUMN_COUNT}
               columnWidth={columnWidth}
+              isComponentVisible={isComponentVisible}
               onResizeStart={this.handleResizeStart}
               onResize={this.handleResize}
               onResizeStop={this.handleResizeStop}
diff --git a/superset/assets/src/dashboard/components/gridComponents/Chart.jsx 
b/superset/assets/src/dashboard/components/gridComponents/Chart.jsx
index ff11208..9b1b5f1 100644
--- a/superset/assets/src/dashboard/components/gridComponents/Chart.jsx
+++ b/superset/assets/src/dashboard/components/gridComponents/Chart.jsx
@@ -37,6 +37,7 @@ const propTypes = {
   width: PropTypes.number.isRequired,
   height: PropTypes.number.isRequired,
   updateSliceName: PropTypes.func.isRequired,
+  isComponentVisible: PropTypes.bool,
 
   // from redux
   chart: PropTypes.shape(chartPropShape).isRequired,
@@ -61,6 +62,7 @@ const propTypes = {
 
 const defaultProps = {
   isCached: false,
+  isComponentVisible: true,
 };
 
 // we use state + shouldComponentUpdate() logic to prevent perf-wrecking
@@ -99,19 +101,27 @@ class Chart extends React.Component {
       return true;
     }
 
-    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]) {
+    // allow chart update/re-render only if visible:
+    // under selected tab or no tab layout
+    if (nextProps.isComponentVisible) {
+      if (nextProps.chart.triggerQuery) {
         return true;
       }
-    }
 
-    if (
-      nextProps.width !== this.props.width ||
-      nextProps.height !== this.props.height
-    ) {
-      clearTimeout(this.resizeTimeout);
-      this.resizeTimeout = setTimeout(this.resize, RESIZE_TIMEOUT);
+      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]) {
+          return true;
+        }
+      }
+
+      if (
+        nextProps.width !== this.props.width ||
+        nextProps.height !== this.props.height
+      ) {
+        clearTimeout(this.resizeTimeout);
+        this.resizeTimeout = setTimeout(this.resize, RESIZE_TIMEOUT);
+      }
     }
 
     return false;
diff --git 
a/superset/assets/src/dashboard/components/gridComponents/ChartHolder.jsx 
b/superset/assets/src/dashboard/components/gridComponents/ChartHolder.jsx
index 836c0e7..706023a 100644
--- a/superset/assets/src/dashboard/components/gridComponents/ChartHolder.jsx
+++ b/superset/assets/src/dashboard/components/gridComponents/ChartHolder.jsx
@@ -109,6 +109,7 @@ class ChartHolder extends React.Component {
       onResizeStop,
       handleComponentDrop,
       editMode,
+      isComponentVisible,
     } = this.props;
 
     // inherit the size of parent columns
@@ -163,6 +164,7 @@ class ChartHolder extends React.Component {
                 )}
                 sliceName={component.meta.sliceName || ''}
                 updateSliceName={this.handleUpdateSliceName}
+                isComponentVisible={isComponentVisible}
               />
               {editMode && (
                 <HoverMenu position="top">
diff --git a/superset/assets/src/dashboard/components/gridComponents/Column.jsx 
b/superset/assets/src/dashboard/components/gridComponents/Column.jsx
index 7170e4a..78d272b 100644
--- a/superset/assets/src/dashboard/components/gridComponents/Column.jsx
+++ b/superset/assets/src/dashboard/components/gridComponents/Column.jsx
@@ -112,6 +112,7 @@ class Column extends React.PureComponent {
       onResizeStop,
       handleComponentDrop,
       editMode,
+      isComponentVisible,
     } = this.props;
 
     const columnItems = columnComponent.children || [];
@@ -191,6 +192,7 @@ class Column extends React.PureComponent {
                     onResizeStart={onResizeStart}
                     onResize={onResize}
                     onResizeStop={onResizeStop}
+                    isComponentVisible={isComponentVisible}
                   />
                 ))}
 
diff --git a/superset/assets/src/dashboard/components/gridComponents/Row.jsx 
b/superset/assets/src/dashboard/components/gridComponents/Row.jsx
index 585e57c..f9076bc 100644
--- a/superset/assets/src/dashboard/components/gridComponents/Row.jsx
+++ b/superset/assets/src/dashboard/components/gridComponents/Row.jsx
@@ -113,6 +113,7 @@ class Row extends React.PureComponent {
       onResizeStop,
       handleComponentDrop,
       editMode,
+      isComponentVisible,
     } = this.props;
 
     const rowItems = rowComponent.children || [];
@@ -177,6 +178,7 @@ class Row extends React.PureComponent {
                   onResizeStart={onResizeStart}
                   onResize={onResize}
                   onResizeStop={onResizeStop}
+                  isComponentVisible={isComponentVisible}
                 />
               ))}
 
diff --git a/superset/assets/src/dashboard/components/gridComponents/Tab.jsx 
b/superset/assets/src/dashboard/components/gridComponents/Tab.jsx
index d441c8e..e9e543c 100644
--- a/superset/assets/src/dashboard/components/gridComponents/Tab.jsx
+++ b/superset/assets/src/dashboard/components/gridComponents/Tab.jsx
@@ -133,6 +133,7 @@ export default class Tab extends React.PureComponent {
       onResize,
       onResizeStop,
       editMode,
+      isComponentVisible,
     } = this.props;
 
     return (
@@ -169,6 +170,7 @@ export default class Tab extends React.PureComponent {
             onResizeStart={onResizeStart}
             onResize={onResize}
             onResizeStop={onResizeStop}
+            isComponentVisible={isComponentVisible}
           />
         ))}
         {/* Make bottom of tab droppable */}
diff --git a/superset/assets/src/dashboard/components/gridComponents/Tabs.jsx 
b/superset/assets/src/dashboard/components/gridComponents/Tabs.jsx
index 2b8934e..dfa0cae 100644
--- a/superset/assets/src/dashboard/components/gridComponents/Tabs.jsx
+++ b/superset/assets/src/dashboard/components/gridComponents/Tabs.jsx
@@ -238,6 +238,7 @@ class Tabs extends React.PureComponent {
                       onResize={onResize}
                       onResizeStop={onResizeStop}
                       onDropOnTab={this.handleDropOnTab}
+                      isComponentVisible={selectedTabIndex === tabIndex}
                     />
                   )}
                 </BootstrapTab>
diff --git a/superset/assets/src/dashboard/containers/Dashboard.jsx 
b/superset/assets/src/dashboard/containers/Dashboard.jsx
index e5cf4fb..c1609a9 100644
--- a/superset/assets/src/dashboard/containers/Dashboard.jsx
+++ b/superset/assets/src/dashboard/containers/Dashboard.jsx
@@ -25,7 +25,7 @@ import {
   addSliceToDashboard,
   removeSliceFromDashboard,
 } from '../actions/dashboardState';
-import { postChartFormData } from '../../chart/chartAction';
+import { triggerQuery } from '../../chart/chartAction';
 import { logEvent } from '../../logger/actions';
 import getLoadStatsPerTopLevelComponent from 
'../util/logging/getLoadStatsPerTopLevelComponent';
 
@@ -64,7 +64,7 @@ function mapDispatchToProps(dispatch) {
       {
         addSliceToDashboard,
         removeSliceFromDashboard,
-        postChartFormData,
+        triggerQuery,
         logEvent,
       },
       dispatch,
diff --git a/superset/assets/src/dashboard/containers/DashboardComponent.jsx 
b/superset/assets/src/dashboard/containers/DashboardComponent.jsx
index a1a1c37..2bd3060 100644
--- a/superset/assets/src/dashboard/containers/DashboardComponent.jsx
+++ b/superset/assets/src/dashboard/containers/DashboardComponent.jsx
@@ -48,6 +48,7 @@ const propTypes = {
 
 const defaultProps = {
   directPathToChild: [],
+  isComponentVisible: true,
 };
 
 function mapStateToProps(
diff --git a/superset/assets/src/explore/components/ExploreViewContainer.jsx 
b/superset/assets/src/explore/components/ExploreViewContainer.jsx
index 9e6647f..56b5e43 100644
--- a/superset/assets/src/explore/components/ExploreViewContainer.jsx
+++ b/superset/assets/src/explore/components/ExploreViewContainer.jsx
@@ -106,7 +106,6 @@ class ExploreViewContainer extends React.Component {
   componentWillReceiveProps(nextProps) {
     if (nextProps.controls.viz_type.value !== 
this.props.controls.viz_type.value) {
       this.props.actions.resetControls();
-      this.props.actions.triggerQuery(true, this.props.chart.id);
     }
     if (
       nextProps.controls.datasource &&
diff --git a/superset/cli.py b/superset/cli.py
index e631e0b..5242910 100755
--- a/superset/cli.py
+++ b/superset/cli.py
@@ -117,6 +117,9 @@ def load_examples_run(load_test_data):
         print('Loading DECK.gl demo')
         data.load_deck_dash()
 
+    print('Loading [Tabbed dashboard]')
+    data.load_tabbed_dashboard()
+
 
 @app.cli.command()
 @click.option('--load-test-data', '-t', is_flag=True, help='Load additional 
test data')
diff --git a/superset/data/__init__.py b/superset/data/__init__.py
index 5090eff..b36a300 100644
--- a/superset/data/__init__.py
+++ b/superset/data/__init__.py
@@ -28,5 +28,6 @@ from .multiformat_time_series import 
load_multiformat_time_series  # noqa
 from .paris import load_paris_iris_geojson  # noqa
 from .random_time_series import load_random_time_series_data  # noqa
 from .sf_population_polygons import load_sf_population_polygons  # noqa
+from .tabbed_dashboard import load_tabbed_dashboard # noqa
 from .unicode_test_data import load_unicode_test_data  # noqa
 from .world_bank import load_world_bank_health_n_pop  # noqa
diff --git a/superset/data/tabbed_dashboard.py 
b/superset/data/tabbed_dashboard.py
new file mode 100644
index 0000000..4c81f85
--- /dev/null
+++ b/superset/data/tabbed_dashboard.py
@@ -0,0 +1,324 @@
+# 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.
+"""Loads datasets, dashboards and slices in a new superset instance"""
+# pylint: disable=C,R,W
+import json
+import os
+import textwrap
+
+import pandas as pd
+from sqlalchemy import DateTime, String
+
+from superset import db
+from superset.connectors.sqla.models import SqlMetric
+from superset.utils import core as utils
+from .helpers import (
+    config,
+    Dash,
+    DATA_FOLDER,
+    get_example_data,
+    get_slice_json,
+    merge_slice,
+    misc_dash_slices,
+    Slice,
+    TBL,
+    update_slice_ids,
+)
+
+
+def load_tabbed_dashboard():
+    """Creating a tabbed dashboard"""
+
+    print("Creating a dashboard with nested tabs")
+    slug = 'tabbed_dash'
+    dash = db.session.query(Dash).filter_by(slug=slug).first()
+
+    if not dash:
+        dash = Dash()
+
+    # reuse charts in "World's Bank Data and create
+    # new dashboard with nested tabs
+    tabbed_dash_slices = set()
+    tabbed_dash_slices.add('Region Filter')
+    tabbed_dash_slices.add('Growth Rate')
+    tabbed_dash_slices.add('Treemap')
+    tabbed_dash_slices.add('Box plot')
+
+    js = textwrap.dedent("""\
+    {
+      "CHART-c0EjR-OZ0n": {
+        "children": [],
+        "id": "CHART-c0EjR-OZ0n",
+        "meta": {
+          "chartId": 870,
+          "height": 50,
+          "sliceName": "Box plot",
+          "width": 4
+        },
+        "parents": [
+          "ROOT_ID",
+          "TABS-lV0r00f4H1",
+          "TAB-NF3dlrWGS",
+          "ROW-7G2o5uDvfo"
+        ],
+        "type": "CHART"
+      },
+      "CHART-dxV7Il74hH": {
+        "children": [],
+        "id": "CHART-dxV7Il74hH",
+        "meta": {
+          "chartId": 797,
+          "height": 50,
+          "sliceName": "Treemap",
+          "width": 4
+        },
+        "parents": [
+          "ROOT_ID",
+          "TABS-lV0r00f4H1",
+          "TAB-gcQJxApOZS",
+          "ROW-3PphCz4GD"
+        ],
+        "type": "CHART"
+      },
+      "CHART-jJ5Yj1Ptaz": {
+        "children": [],
+        "id": "CHART-jJ5Yj1Ptaz",
+        "meta": {
+          "chartId": 789,
+          "height": 50,
+          "sliceName": "World's Population",
+          "width": 4
+        },
+        "parents": [
+          "ROOT_ID",
+          "TABS-lV0r00f4H1",
+          "TAB-NF3dlrWGS",
+          "TABS-CSjo6VfNrj",
+          "TAB-z81Q87PD7",
+          "ROW-G73z9PIHn"
+        ],
+        "type": "CHART"
+      },
+      "CHART-z4gmEuCqQ5": {
+        "children": [],
+        "id": "CHART-z4gmEuCqQ5",
+        "meta": {
+          "chartId": 788,
+          "height": 50,
+          "sliceName": "Region Filter",
+          "width": 4
+        },
+        "parents": [
+          "ROOT_ID",
+          "TABS-lV0r00f4H1",
+          "TAB-NF3dlrWGS",
+          "TABS-CSjo6VfNrj",
+          "TAB-EcNm_wh922",
+          "ROW-LCjsdSetJ"
+        ],
+        "type": "CHART"
+      },
+      "DASHBOARD_VERSION_KEY": "v2",
+      "GRID_ID": {
+        "children": [],
+        "id": "GRID_ID",
+        "type": "GRID"
+      },
+      "HEADER_ID": {
+        "id": "HEADER_ID",
+        "meta": {
+          "text": "Tabbed Dashboard"
+        },
+        "type": "HEADER"
+      },
+      "ROOT_ID": {
+        "children": [
+          "TABS-lV0r00f4H1"
+        ],
+        "id": "ROOT_ID",
+        "type": "ROOT"
+      },
+      "ROW-3PphCz4GD": {
+        "children": [
+          "CHART-dxV7Il74hH"
+        ],
+        "id": "ROW-3PphCz4GD",
+        "meta": {
+          "background": "BACKGROUND_TRANSPARENT"
+        },
+        "parents": [
+          "ROOT_ID",
+          "TABS-lV0r00f4H1",
+          "TAB-gcQJxApOZS"
+        ],
+        "type": "ROW"
+      },
+      "ROW-7G2o5uDvfo": {
+        "children": [
+          "CHART-c0EjR-OZ0n"
+        ],
+        "id": "ROW-7G2o5uDvfo",
+        "meta": {
+          "background": "BACKGROUND_TRANSPARENT"
+        },
+        "parents": [
+          "ROOT_ID",
+          "TABS-lV0r00f4H1",
+          "TAB-NF3dlrWGS"
+        ],
+        "type": "ROW"
+      },
+      "ROW-G73z9PIHn": {
+        "children": [
+          "CHART-jJ5Yj1Ptaz"
+        ],
+        "id": "ROW-G73z9PIHn",
+        "meta": {
+          "background": "BACKGROUND_TRANSPARENT"
+        },
+        "parents": [
+          "ROOT_ID",
+          "TABS-lV0r00f4H1",
+          "TAB-NF3dlrWGS",
+          "TABS-CSjo6VfNrj",
+          "TAB-z81Q87PD7"
+        ],
+        "type": "ROW"
+      },
+      "ROW-LCjsdSetJ": {
+        "children": [
+          "CHART-z4gmEuCqQ5"
+        ],
+        "id": "ROW-LCjsdSetJ",
+        "meta": {
+          "background": "BACKGROUND_TRANSPARENT"
+        },
+        "parents": [
+          "ROOT_ID",
+          "TABS-lV0r00f4H1",
+          "TAB-NF3dlrWGS",
+          "TABS-CSjo6VfNrj",
+          "TAB-EcNm_wh922"
+        ],
+        "type": "ROW"
+      },
+      "TAB-EcNm_wh922": {
+        "children": [
+          "ROW-LCjsdSetJ"
+        ],
+        "id": "TAB-EcNm_wh922",
+        "meta": {
+          "text": "row tab 1"
+        },
+        "parents": [
+          "ROOT_ID",
+          "TABS-lV0r00f4H1",
+          "TAB-NF3dlrWGS",
+          "TABS-CSjo6VfNrj"
+        ],
+        "type": "TAB"
+      },
+      "TAB-NF3dlrWGS": {
+        "children": [
+          "ROW-7G2o5uDvfo",
+          "TABS-CSjo6VfNrj"
+        ],
+        "id": "TAB-NF3dlrWGS",
+        "meta": {
+          "text": "Tab A"
+        },
+        "parents": [
+          "ROOT_ID",
+          "TABS-lV0r00f4H1"
+        ],
+        "type": "TAB"
+      },
+      "TAB-gcQJxApOZS": {
+        "children": [
+          "ROW-3PphCz4GD"
+        ],
+        "id": "TAB-gcQJxApOZS",
+        "meta": {
+          "text": "Tab B"
+        },
+        "parents": [
+          "ROOT_ID",
+          "TABS-lV0r00f4H1"
+        ],
+        "type": "TAB"
+      },
+      "TAB-z81Q87PD7": {
+        "children": [
+          "ROW-G73z9PIHn"
+        ],
+        "id": "TAB-z81Q87PD7",
+        "meta": {
+          "text": "row tab 2"
+        },
+        "parents": [
+          "ROOT_ID",
+          "TABS-lV0r00f4H1",
+          "TAB-NF3dlrWGS",
+          "TABS-CSjo6VfNrj"
+        ],
+        "type": "TAB"
+      },
+      "TABS-CSjo6VfNrj": {
+        "children": [
+          "TAB-EcNm_wh922",
+          "TAB-z81Q87PD7"
+        ],
+        "id": "TABS-CSjo6VfNrj",
+        "meta": {},
+        "parents": [
+          "ROOT_ID",
+          "TABS-lV0r00f4H1",
+          "TAB-NF3dlrWGS"
+        ],
+        "type": "TABS"
+      },
+      "TABS-lV0r00f4H1": {
+        "children": [
+          "TAB-NF3dlrWGS",
+          "TAB-gcQJxApOZS"
+        ],
+        "id": "TABS-lV0r00f4H1",
+        "meta": {},
+        "parents": [
+          "ROOT_ID"
+        ],
+        "type": "TABS"
+      }
+    }
+        """)
+    pos = json.loads(js)
+    slices = [
+        db.session.query(Slice)
+            .filter_by(slice_name=name)
+            .first()
+        for name in tabbed_dash_slices
+    ]
+
+    slices = sorted(slices, key=lambda x: x.id)
+    update_slice_ids(pos, slices)
+    dash.position_json = json.dumps(pos, indent=4)
+    dash.slices = slices
+    dash.dashboard_title = 'Tabbed Dashboard'
+    dash.slug = slug
+
+    db.session.merge(dash)
+    db.session.commit()

Reply via email to