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 6ec9e1a  [Ready][Dashboard] disable force refresh when chart is still 
loading (#6034)
6ec9e1a is described below

commit 6ec9e1a4e94beb1d6a1f49e25569473ba99715c1
Author: Grace Guo <grace....@airbnb.com>
AuthorDate: Wed Oct 24 10:21:23 2018 -0700

    [Ready][Dashboard] disable force refresh when chart is still loading (#6034)
    
    * [Dashboard] disable force refresh when chart is still loading
    
    - disable force refresh for chart, when chart is still loading
    - disable force refresh for dashboard, when any one of chart is loading
    
    * remove css change (introduced by mistake)
    
    * add integration tests for disable/enable force refresh
---
 .../cypress/integration/dashboard/controls.js      | 99 ++++++++++++----------
 .../cypress/integration/dashboard/fav_star.js      | 35 ++++++++
 .../cypress/integration/dashboard/index.test.js    |  2 +
 superset/assets/src/chart/chartReducer.js          |  2 +-
 .../assets/src/dashboard/components/Header.jsx     |  8 +-
 .../dashboard/components/HeaderActionsDropdown.jsx |  4 +-
 .../dashboard/components/SliceHeaderControls.jsx   | 14 +--
 .../src/dashboard/containers/DashboardHeader.jsx   |  2 +
 .../src/dashboard/util/isDashboardLoading.js       |  5 ++
 .../assets/src/explore/reducers/getInitialState.js |  3 +-
 10 files changed, 120 insertions(+), 54 deletions(-)

diff --git a/superset/assets/cypress/integration/dashboard/controls.js 
b/superset/assets/cypress/integration/dashboard/controls.js
index b79abf8..0f163ee 100644
--- a/superset/assets/cypress/integration/dashboard/controls.js
+++ b/superset/assets/cypress/integration/dashboard/controls.js
@@ -1,71 +1,84 @@
-import { WORLD_HEALTH_DASHBOARD, CHECK_DASHBOARD_FAVORITE_ENDPOINT } from 
'./dashboard.helper';
+import { WORLD_HEALTH_DASHBOARD } from './dashboard.helper';
 import readResponseBlob from '../../utils/readResponseBlob';
 
 export default () => describe('top-level controls', () => {
-  let sliceIds = [];
-  let dashboard = {};
-  let isFavoriteDashboard = false;
+  const sliceRequests = [];
+  const forceRefreshRequests = [];
+  let mapId;
 
   beforeEach(() => {
     cy.server();
     cy.login();
-
-    cy.route(CHECK_DASHBOARD_FAVORITE_ENDPOINT).as('countFavStar');
     cy.visit(WORLD_HEALTH_DASHBOARD);
 
     cy.get('#app').then((data) => {
       const bootstrapData = JSON.parse(data[0].dataset.bootstrap);
-      dashboard = bootstrapData.dashboard_data;
-      sliceIds = dashboard.slices.map(slice => (slice.slice_id));
-    });
+      const dashboard = bootstrapData.dashboard_data;
+      const sliceIds = dashboard.slices.map(slice => (slice.slice_id));
+      mapId = dashboard.slices.find(slice => (slice.form_data.viz_type === 
'world_map')).slice_id;
 
-    cy.wait('@countFavStar').then((xhr) => {
-      isFavoriteDashboard = xhr.response.body.count === 1;
+      sliceIds
+        .forEach((id) => {
+          const sliceRequest = `getJson_${id}`;
+          sliceRequests.push(`@${sliceRequest}`);
+          cy.route('POST', 
`/superset/explore_json/?form_data={"slice_id":${id}}`).as(sliceRequest);
+
+          const forceRefresh = `getJson_${id}_force`;
+          forceRefreshRequests.push(`@${forceRefresh}`);
+          cy.route('POST', 
`/superset/explore_json/?form_data={"slice_id":${id}}&force=true`).as(forceRefresh);
+        });
     });
   });
+  afterEach(() => {
+    sliceRequests.length = 0;
+    forceRefreshRequests.length = 0;
+  });
 
-  it('should allow favor/unfavor', () => {
-    if (!isFavoriteDashboard) {
-      cy.get('.favstar').find('i').should('have.class', 'fa-star-o');
-      cy.get('.favstar').trigger('click');
-      cy.get('.favstar').find('i').should('have.class', 'fa-star')
-        .and('not.have.class', 'fa-star-o');
-    } else {
-      cy.get('.favstar').find('i').should('have.class', 'fa-star')
-        .and('not.have.class', 'fa-star-o');
-      cy.get('.favstar').trigger('click');
-      cy.get('.fave-unfave-icon').find('i').should('have.class', 'fa-star-o')
-        .and('not.have.class', 'fa-star');
-    }
+  it('should allow chart level refresh', () => {
+    cy.wait(sliceRequests);
+    cy.get('.grid-container .world_map').should('be.exist');
+    cy.get(`#slice_${mapId}-controls`).click();
+    cy.get(`#slice_${mapId}-controls`).next()
+      .find('.refresh-tooltip').trigger('click', { force: true });
 
-    // reset to original fav state
-    cy.get('.favstar').trigger('click');
-  });
+    // not allow dashboard level force refresh when any chart is loading
+    cy.get('#save-dash-split-button').trigger('click', { forece: true });
+    cy.contains('Force refresh dashboard').parent().should('have.class', 
'disabled');
+    // not allow chart level force refresh when it is loading
+    cy.get(`#slice_${mapId}-controls`).next()
+      .find('.refresh-tooltip')
+      .parent()
+      .parent()
+      .should('have.class', 'disabled');
 
-  it('should allow auto refresh', () => {
-    const sliceRequests = [];
-    const forceRefreshRequests = [];
-    sliceIds
-      .forEach((id) => {
-        const sliceRequest = `getJson_${id}`;
-        sliceRequests.push(`@${sliceRequest}`);
-        cy.route('POST', 
`/superset/explore_json/?form_data={"slice_id":${id}}`).as(sliceRequest);
+    cy.wait(`@getJson_${mapId}_force`);
+    cy.get('#save-dash-split-button').trigger('click');
+    cy.contains('Force refresh dashboard').parent().not('have.class', 
'disabled');
+  });
 
-        const forceRefresh = `getJson_${id}_force`;
-        forceRefreshRequests.push(`@${forceRefresh}`);
-        cy.route('POST', 
`/superset/explore_json/?form_data={"slice_id":${id}}&force=true`).as(forceRefresh);
-      });
+  it('should allow dashboard level force refresh', () => {
+    // should not show dashobard level force refresh
+    cy.get('#save-dash-split-button').trigger('click');
+    cy.contains('Force refresh dashboard').parent().should('have.class', 
'disabled');
 
+    // wait the all dash finish loading.
     cy.wait(sliceRequests);
     cy.get('#save-dash-split-button').trigger('click');
-    cy.contains('Force refresh dashboard').click();
+    cy.contains('Force refresh dashboard').trigger('click', { force: true });
+    cy.get('#save-dash-split-button').trigger('click');
+    cy.contains('Force refresh dashboard').parent().should('have.class', 
'disabled');
 
+    // wait all charts force refreshed
     cy.wait(forceRefreshRequests).then((xhrs) => {
       // is_cached in response should be false
-      xhrs.forEach(async (xhr) => {
-        const responseBody = await readResponseBlob(xhr.response.body);
-        expect(responseBody.is_cached).to.equal(false);
+      xhrs.forEach((xhr) => {
+        readResponseBlob(xhr.response.body).then((responseBody) => {
+          expect(responseBody.is_cached).to.equal(false);
+        });
       });
     });
+
+    cy.get('#save-dash-split-button').trigger('click');
+    cy.contains('Force refresh dashboard').parent().not('have.class', 
'disabled');
   });
 });
diff --git a/superset/assets/cypress/integration/dashboard/fav_star.js 
b/superset/assets/cypress/integration/dashboard/fav_star.js
new file mode 100644
index 0000000..bb5957a
--- /dev/null
+++ b/superset/assets/cypress/integration/dashboard/fav_star.js
@@ -0,0 +1,35 @@
+import { WORLD_HEALTH_DASHBOARD, CHECK_DASHBOARD_FAVORITE_ENDPOINT } from 
'./dashboard.helper';
+
+export default () => describe('favorite dashboard', () => {
+  let isFavoriteDashboard = false;
+
+  beforeEach(() => {
+    cy.server();
+    cy.login();
+
+    cy.route(CHECK_DASHBOARD_FAVORITE_ENDPOINT).as('countFavStar');
+    cy.visit(WORLD_HEALTH_DASHBOARD);
+
+    cy.wait('@countFavStar').then((xhr) => {
+      isFavoriteDashboard = xhr.response.body.count === 1;
+    });
+  });
+
+  it('should allow favor/unfavor', () => {
+    if (!isFavoriteDashboard) {
+      cy.get('.favstar').find('i').should('have.class', 'fa-star-o');
+      cy.get('.favstar').trigger('click');
+      cy.get('.favstar').find('i').should('have.class', 'fa-star')
+        .and('not.have.class', 'fa-star-o');
+    } else {
+      cy.get('.favstar').find('i').should('have.class', 'fa-star')
+        .and('not.have.class', 'fa-star-o');
+      cy.get('.favstar').trigger('click');
+      cy.get('.fave-unfave-icon').find('i').should('have.class', 'fa-star-o')
+        .and('not.have.class', 'fa-star');
+    }
+
+    // reset to original fav state
+    cy.get('.favstar').trigger('click');
+  });
+});
diff --git a/superset/assets/cypress/integration/dashboard/index.test.js 
b/superset/assets/cypress/integration/dashboard/index.test.js
index 664e3ac..5db178d 100644
--- a/superset/assets/cypress/integration/dashboard/index.test.js
+++ b/superset/assets/cypress/integration/dashboard/index.test.js
@@ -1,11 +1,13 @@
 import DashboardControlsTest from './controls';
 import DashboardEditModeTest from './edit_mode';
+import DashboardFavStarTest from './fav_star';
 import DashboardFilterTest from './filter';
 import DashboardLoadTest from './load';
 
 describe('Dashboard', () => {
   DashboardControlsTest();
   DashboardEditModeTest();
+  DashboardFavStarTest();
   DashboardFilterTest();
   DashboardLoadTest();
 });
diff --git a/superset/assets/src/chart/chartReducer.js 
b/superset/assets/src/chart/chartReducer.js
index 5563d5c..5f9a421 100644
--- a/superset/assets/src/chart/chartReducer.js
+++ b/superset/assets/src/chart/chartReducer.js
@@ -8,7 +8,7 @@ export const chart = {
   chartAlert: null,
   chartStatus: 'loading',
   chartUpdateEndTime: null,
-  chartUpdateStartTime: now(),
+  chartUpdateStartTime: 0,
   latestQueryFormData: {},
   queryRequest: null,
   queryResponse: null,
diff --git a/superset/assets/src/dashboard/components/Header.jsx 
b/superset/assets/src/dashboard/components/Header.jsx
index dd2af7f..1dfe846 100644
--- a/superset/assets/src/dashboard/components/Header.jsx
+++ b/superset/assets/src/dashboard/components/Header.jsx
@@ -28,6 +28,7 @@ const propTypes = {
   expandedSlices: PropTypes.object.isRequired,
   css: PropTypes.string.isRequired,
   isStarred: PropTypes.bool.isRequired,
+  isLoading: PropTypes.bool.isRequired,
   onSave: PropTypes.func.isRequired,
   onChange: PropTypes.func.isRequired,
   fetchFaveStar: PropTypes.func.isRequired,
@@ -94,7 +95,10 @@ class Header extends React.PureComponent {
   }
 
   forceRefresh() {
-    return this.props.fetchCharts(Object.values(this.props.charts), true);
+    if (!this.props.isLoading) {
+      return this.props.fetchCharts(Object.values(this.props.charts), true);
+    }
+    return false;
   }
 
   handleChangeText(nextText) {
@@ -185,6 +189,7 @@ class Header extends React.PureComponent {
       showBuilderPane,
       dashboardInfo,
       hasUnsavedChanges,
+      isLoading,
     } = this.props;
 
     const userCanEdit = dashboardInfo.dash_edit_perm;
@@ -305,6 +310,7 @@ class Header extends React.PureComponent {
             hasUnsavedChanges={hasUnsavedChanges}
             userCanEdit={userCanEdit}
             userCanSave={userCanSaveAs}
+            isLoading={isLoading}
           />
         </div>
       </div>
diff --git a/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx 
b/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx
index d012e34..9967ce0 100644
--- a/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx
+++ b/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx
@@ -26,6 +26,7 @@ const propTypes = {
   editMode: PropTypes.bool.isRequired,
   userCanEdit: PropTypes.bool.isRequired,
   userCanSave: PropTypes.bool.isRequired,
+  isLoading: PropTypes.bool.isRequired,
   layout: PropTypes.object.isRequired,
   filters: PropTypes.object.isRequired,
   expandedSlices: PropTypes.object.isRequired,
@@ -91,6 +92,7 @@ class HeaderActionsDropdown extends React.PureComponent {
       onSave,
       userCanEdit,
       userCanSave,
+      isLoading,
     } = this.props;
 
     const emailTitle = t('Superset Dashboard');
@@ -137,7 +139,7 @@ class HeaderActionsDropdown extends React.PureComponent {
 
         {userCanSave && <MenuItem divider />}
 
-        <MenuItem onClick={forceRefreshAllCharts}>
+        <MenuItem onClick={forceRefreshAllCharts} disabled={isLoading}>
           {t('Force refresh dashboard')}
         </MenuItem>
         <RefreshIntervalModal
diff --git a/superset/assets/src/dashboard/components/SliceHeaderControls.jsx 
b/superset/assets/src/dashboard/components/SliceHeaderControls.jsx
index 4116a34..b5ee0b8 100644
--- a/superset/assets/src/dashboard/components/SliceHeaderControls.jsx
+++ b/superset/assets/src/dashboard/components/SliceHeaderControls.jsx
@@ -88,11 +88,13 @@ class SliceHeaderControls extends React.PureComponent {
   }
 
   refreshChart() {
-    this.props.forceRefresh(this.props.slice.slice_id);
-    Logger.append(LOG_ACTIONS_REFRESH_CHART, {
-      slice_id: this.props.slice.slice_id,
-      is_cached: this.props.isCached,
-    });
+    if (this.props.updatedDttm) {
+      this.props.forceRefresh(this.props.slice.slice_id);
+      Logger.append(LOG_ACTIONS_REFRESH_CHART, {
+        slice_id: this.props.slice.slice_id,
+        is_cached: this.props.isCached,
+      });
+    }
   }
 
   toggleControls() {
@@ -122,7 +124,7 @@ class SliceHeaderControls extends React.PureComponent {
         </Dropdown.Toggle>
 
         <Dropdown.Menu>
-          <MenuItem onClick={this.refreshChart}>
+          <MenuItem onClick={this.refreshChart} disabled={!updatedDttm}>
             {t('Force refresh')}
             <div className="refresh-tooltip">{refreshTooltip}</div>
           </MenuItem>
diff --git a/superset/assets/src/dashboard/containers/DashboardHeader.jsx 
b/superset/assets/src/dashboard/containers/DashboardHeader.jsx
index 3e54c62..459f0d8 100644
--- a/superset/assets/src/dashboard/containers/DashboardHeader.jsx
+++ b/superset/assets/src/dashboard/containers/DashboardHeader.jsx
@@ -2,6 +2,7 @@ import { bindActionCreators } from 'redux';
 import { connect } from 'react-redux';
 
 import DashboardHeader from '../components/Header';
+import isDashboardLoading from '../util/isDashboardLoading';
 
 import {
   setEditMode,
@@ -51,6 +52,7 @@ function mapStateToProps({
     charts,
     userId: dashboardInfo.userId,
     isStarred: !!dashboardState.isStarred,
+    isLoading: isDashboardLoading(charts),
     hasUnsavedChanges: !!dashboardState.hasUnsavedChanges,
     maxUndoHistoryExceeded: !!dashboardState.maxUndoHistoryExceeded,
     editMode: !!dashboardState.editMode,
diff --git a/superset/assets/src/dashboard/util/isDashboardLoading.js 
b/superset/assets/src/dashboard/util/isDashboardLoading.js
new file mode 100644
index 0000000..01054f5
--- /dev/null
+++ b/superset/assets/src/dashboard/util/isDashboardLoading.js
@@ -0,0 +1,5 @@
+export default function isDashboardLoading(charts) {
+  return Object.values(charts).some(
+    chart => chart.chartUpdateStartTime >= (chart.chartUpdateEndTime || 0),
+  );
+}
diff --git a/superset/assets/src/explore/reducers/getInitialState.js 
b/superset/assets/src/explore/reducers/getInitialState.js
index 9c6f921..21a6edd 100644
--- a/superset/assets/src/explore/reducers/getInitialState.js
+++ b/superset/assets/src/explore/reducers/getInitialState.js
@@ -1,7 +1,6 @@
 import shortid from 'shortid';
 
 import getToastsFromPyFlashMessages from 
'../../messageToasts/utils/getToastsFromPyFlashMessages';
-import { now } from '../../modules/dates';
 import { getChartKey } from '../exploreUtils';
 import { getControlsState, getFormDataFromControls } from '../store';
 
@@ -37,7 +36,7 @@ export default function getInitialState(bootstrapData) {
         chartAlert: null,
         chartStatus: 'loading',
         chartUpdateEndTime: null,
-        chartUpdateStartTime: now(),
+        chartUpdateStartTime: 0,
         latestQueryFormData: getFormDataFromControls(controls),
         sliceFormData,
         queryController: null,

Reply via email to