diff --git a/superset-frontend/spec/javascripts/dashboard/reducers/dashboardState_spec.js b/superset-frontend/spec/javascripts/dashboard/reducers/dashboardState_spec.js index 8f6de34e1f5..5adf553b508 100644 --- a/superset-frontend/spec/javascripts/dashboard/reducers/dashboardState_spec.js +++ b/superset-frontend/spec/javascripts/dashboard/reducers/dashboardState_spec.js @@ -120,14 +120,29 @@ describe('dashboardState reducer', () => { }); it('should set unsaved changes, max undo history, and editMode to false on save', () => { + const result = dashboardStateReducer( + { hasUnsavedChanges: true }, + { type: ON_SAVE }, + ); + expect(result.hasUnsavedChanges).toBe(false); + expect(result.maxUndoHistoryExceeded).toBe(false); + expect(result.editMode).toBe(false); + expect(result.updatedColorScheme).toBe(false); + }); + + it('should set lastModifiedTime on save', () => { + const lastModifiedTime = new Date().getTime() / 1000; + dashboardStateReducer( + { + lastModifiedTime, + }, + {}, + ); + expect( - dashboardStateReducer({ hasUnsavedChanges: true }, { type: ON_SAVE }), - ).toEqual({ - hasUnsavedChanges: false, - maxUndoHistoryExceeded: false, - editMode: false, - updatedColorScheme: false, - }); + dashboardStateReducer({ hasUnsavedChanges: true }, { type: ON_SAVE }) + .lastModifiedTime, + ).toBeGreaterThanOrEqual(lastModifiedTime); }); it('should clear focused filter field', () => { diff --git a/superset-frontend/src/dashboard/components/Header.jsx b/superset-frontend/src/dashboard/components/Header.jsx index dad2f0e9a94..52f0b24c256 100644 --- a/superset-frontend/src/dashboard/components/Header.jsx +++ b/superset-frontend/src/dashboard/components/Header.jsx @@ -79,6 +79,7 @@ const propTypes = { logEvent: PropTypes.func.isRequired, hasUnsavedChanges: PropTypes.bool.isRequired, maxUndoHistoryExceeded: PropTypes.bool.isRequired, + lastModifiedTime: PropTypes.number.isRequired, // redux onUndo: PropTypes.func.isRequired, @@ -269,6 +270,7 @@ class Header extends React.PureComponent { dashboardInfo, refreshFrequency: currentRefreshFrequency, shouldPersistRefreshFrequency, + lastModifiedTime, } = this.props; const scale = CategoricalColorNamespace.getScale( @@ -290,7 +292,7 @@ class Header extends React.PureComponent { label_colors: labelColors, dashboard_title: dashboardTitle, refresh_frequency: refreshFrequency, - last_modified_time: dashboardInfo.lastModifiedTime, + last_modified_time: lastModifiedTime, }; // make sure positions data less than DB storage limitation: @@ -345,6 +347,7 @@ class Header extends React.PureComponent { refreshFrequency, shouldPersistRefreshFrequency, setRefreshFrequency, + lastModifiedTime, } = this.props; const userCanEdit = dashboardInfo.dash_edit_perm; @@ -516,6 +519,7 @@ class Header extends React.PureComponent { showPropertiesModal={this.showPropertiesModal} refreshLimit={refreshLimit} refreshWarning={refreshWarning} + lastModifiedTime={lastModifiedTime} /> diff --git a/superset-frontend/src/dashboard/components/HeaderActionsDropdown.jsx b/superset-frontend/src/dashboard/components/HeaderActionsDropdown.jsx index 33b5236b197..0976a18f640 100644 --- a/superset-frontend/src/dashboard/components/HeaderActionsDropdown.jsx +++ b/superset-frontend/src/dashboard/components/HeaderActionsDropdown.jsx @@ -61,6 +61,7 @@ const propTypes = { showPropertiesModal: PropTypes.func.isRequired, refreshLimit: PropTypes.number, refreshWarning: PropTypes.string, + lastModifiedTime: PropTypes.number.isRequired, }; const defaultProps = { @@ -138,6 +139,7 @@ class HeaderActionsDropdown extends React.PureComponent { isLoading, refreshLimit, refreshWarning, + lastModifiedTime, } = this.props; const emailTitle = t('Superset Dashboard'); @@ -166,6 +168,7 @@ class HeaderActionsDropdown extends React.PureComponent { expandedSlices={expandedSlices} refreshFrequency={refreshFrequency} shouldPersistRefreshFrequency={shouldPersistRefreshFrequency} + lastModifiedTime={lastModifiedTime} customCss={customCss} colorNamespace={colorNamespace} colorScheme={colorScheme} diff --git a/superset-frontend/src/dashboard/components/SaveModal.jsx b/superset-frontend/src/dashboard/components/SaveModal.jsx index bb4b064c917..40b29c29556 100644 --- a/superset-frontend/src/dashboard/components/SaveModal.jsx +++ b/superset-frontend/src/dashboard/components/SaveModal.jsx @@ -44,6 +44,7 @@ const propTypes = { isMenuItem: PropTypes.bool, canOverwrite: PropTypes.bool.isRequired, refreshFrequency: PropTypes.number.isRequired, + lastModifiedTime: PropTypes.number.isRequired, }; const defaultProps = { @@ -106,6 +107,7 @@ class SaveModal extends React.PureComponent { dashboardId, refreshFrequency: currentRefreshFrequency, shouldPersistRefreshFrequency, + lastModifiedTime, } = this.props; const scale = CategoricalColorNamespace.getScale( @@ -129,7 +131,7 @@ class SaveModal extends React.PureComponent { saveType === SAVE_TYPE_NEWDASHBOARD ? newDashName : dashboardTitle, duplicate_slices: this.state.duplicateSlices, refresh_frequency: refreshFrequency, - last_modified_time: dashboardInfo.lastModifiedTime, + last_modified_time: lastModifiedTime, }; if (saveType === SAVE_TYPE_NEWDASHBOARD && !newDashName) { diff --git a/superset-frontend/src/dashboard/containers/DashboardHeader.jsx b/superset-frontend/src/dashboard/containers/DashboardHeader.jsx index 4bdc24971a0..3fbba7d0137 100644 --- a/superset-frontend/src/dashboard/containers/DashboardHeader.jsx +++ b/superset-frontend/src/dashboard/containers/DashboardHeader.jsx @@ -83,6 +83,7 @@ function mapStateToProps({ isLoading: isDashboardLoading(charts), hasUnsavedChanges: !!dashboardState.hasUnsavedChanges, maxUndoHistoryExceeded: !!dashboardState.maxUndoHistoryExceeded, + lastModifiedTime: dashboardState.lastModifiedTime, editMode: !!dashboardState.editMode, slug: dashboardInfo.slug, metadata: dashboardInfo.metadata, diff --git a/superset-frontend/src/dashboard/reducers/dashboardState.js b/superset-frontend/src/dashboard/reducers/dashboardState.js index 1ed24625171..b85df0e90a4 100644 --- a/superset-frontend/src/dashboard/reducers/dashboardState.js +++ b/superset-frontend/src/dashboard/reducers/dashboardState.js @@ -106,6 +106,8 @@ export default function dashboardStateReducer(state = {}, action) { maxUndoHistoryExceeded: false, editMode: false, updatedColorScheme: false, + // server-side compare last_modified_time in second level + lastModifiedTime: new Date().getTime() / 1000, }; }, [SET_UNSAVED_CHANGES]() { diff --git a/superset-frontend/src/dashboard/reducers/getInitialState.js b/superset-frontend/src/dashboard/reducers/getInitialState.js index 3e5b6ea7577..75dccf3b772 100644 --- a/superset-frontend/src/dashboard/reducers/getInitialState.js +++ b/superset-frontend/src/dashboard/reducers/getInitialState.js @@ -266,7 +266,6 @@ export default function getInitialState(bootstrapData) { id: dashboard.id, slug: dashboard.slug, metadata: dashboard.metadata, - lastModifiedTime: dashboard.last_modified_time, userId: user_id, dash_edit_perm: dashboard.dash_edit_perm, dash_save_perm: dashboard.dash_save_perm, @@ -301,6 +300,7 @@ export default function getInitialState(bootstrapData) { isPublished: dashboard.published, hasUnsavedChanges: false, maxUndoHistoryExceeded: false, + lastModifiedTime: dashboard.last_modified_time, }, dashboardLayout, messageToasts: [],