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 <[email protected]>
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,