diff --git a/superset/assets/spec/javascripts/dashboard/components/RefreshIntervalModal_spec.jsx b/superset/assets/spec/javascripts/dashboard/components/RefreshIntervalModal_spec.jsx index ac06cfc32..8dfb4016f 100644 --- a/superset/assets/spec/javascripts/dashboard/components/RefreshIntervalModal_spec.jsx +++ b/superset/assets/spec/javascripts/dashboard/components/RefreshIntervalModal_spec.jsx @@ -24,6 +24,7 @@ import RefreshIntervalModal from '../../../../src/dashboard/components/RefreshIn describe('RefreshIntervalModal', () => { const mockedProps = { triggerNode: , + refreshFrequency: 10, }; it('is valid', () => { expect( @@ -34,4 +35,8 @@ describe('RefreshIntervalModal', () => { const wrapper = mount(); expect(wrapper.find('.fa-edit')).toHaveLength(1); }); + it('should render a interval seconds', () => { + const wrapper = mount(); + expect(wrapper.prop('refreshFrequency')).toEqual(10); + }); }); diff --git a/superset/assets/src/dashboard/actions/dashboardState.js b/superset/assets/src/dashboard/actions/dashboardState.js index 135658d93..086481919 100644 --- a/superset/assets/src/dashboard/actions/dashboardState.js +++ b/superset/assets/src/dashboard/actions/dashboardState.js @@ -123,6 +123,11 @@ export function onSave() { return { type: ON_SAVE }; } +export const SET_REFRESH_FREQUENCY = 'SET_REFRESH_FREQUENCY'; +export function setRefreshFrequency(refreshFrequency) { + return { type: SET_REFRESH_FREQUENCY, refreshFrequency }; +} + export function saveDashboardRequestSuccess() { return dispatch => { dispatch(onSave()); diff --git a/superset/assets/src/dashboard/components/Header.jsx b/superset/assets/src/dashboard/components/Header.jsx index b2238d0e0..796a2df09 100644 --- a/superset/assets/src/dashboard/components/Header.jsx +++ b/superset/assets/src/dashboard/components/Header.jsx @@ -77,6 +77,8 @@ const propTypes = { redoLength: PropTypes.number.isRequired, setMaxUndoHistoryExceeded: PropTypes.func.isRequired, maxUndoHistoryToast: PropTypes.func.isRequired, + refreshFrequency: PropTypes.number.isRequired, + setRefreshFrequency: PropTypes.func.isRequired, }; class Header extends React.PureComponent { @@ -100,6 +102,11 @@ class Header extends React.PureComponent { this.overwriteDashboard = this.overwriteDashboard.bind(this); } + componentDidMount() { + const refreshFrequency = this.props.refreshFrequency; + this.props.startPeriodicRender(refreshFrequency * 1000); + } + componentWillReceiveProps(nextProps) { if ( UNDO_LIMIT - nextProps.undoLength <= 0 && @@ -234,6 +241,8 @@ class Header extends React.PureComponent { dashboardInfo, hasUnsavedChanges, isLoading, + refreshFrequency, + setRefreshFrequency, } = this.props; const userCanEdit = dashboardInfo.dash_edit_perm; @@ -346,6 +355,8 @@ class Header extends React.PureComponent { onChange={onChange} forceRefreshAllCharts={this.forceRefresh} startPeriodicRender={this.startPeriodicRender} + refreshFrequency={refreshFrequency} + setRefreshFrequency={setRefreshFrequency} updateCss={updateCss} editMode={editMode} hasUnsavedChanges={hasUnsavedChanges} diff --git a/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx b/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx index 204d5f74d..4c36d9a3c 100644 --- a/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx +++ b/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx @@ -40,6 +40,8 @@ const propTypes = { onChange: PropTypes.func.isRequired, updateCss: PropTypes.func.isRequired, forceRefreshAllCharts: PropTypes.func.isRequired, + refreshFrequency: PropTypes.number.isRequired, + setRefreshFrequency: PropTypes.func.isRequired, startPeriodicRender: PropTypes.func.isRequired, editMode: PropTypes.bool.isRequired, userCanEdit: PropTypes.bool.isRequired, @@ -66,6 +68,7 @@ class HeaderActionsDropdown extends React.PureComponent { }; this.changeCss = this.changeCss.bind(this); + this.changeRefreshInterval = this.changeRefreshInterval.bind(this); } componentWillMount() { @@ -95,12 +98,17 @@ class HeaderActionsDropdown extends React.PureComponent { this.props.updateCss(css); } + changeRefreshInterval(refreshInterval) { + this.props.setRefreshFrequency(refreshInterval); + this.props.startPeriodicRender(refreshInterval * 1000); + } + render() { const { dashboardTitle, dashboardId, - startPeriodicRender, forceRefreshAllCharts, + refreshFrequency, editMode, css, hasUnsavedChanges, @@ -135,6 +143,7 @@ class HeaderActionsDropdown extends React.PureComponent { layout={layout} filters={filters} expandedSlices={expandedSlices} + refreshFrequency={refreshFrequency} css={css} onSave={onSave} isMenuItem @@ -160,9 +169,8 @@ class HeaderActionsDropdown extends React.PureComponent { {t('Force refresh dashboard')} - startPeriodicRender(refreshInterval * 1000) - } + refreshFrequency={refreshFrequency} + onChange={this.changeRefreshInterval} triggerNode={{t('Set auto-refresh interval')}} /> {editMode && ( diff --git a/superset/assets/src/dashboard/components/RefreshIntervalModal.jsx b/superset/assets/src/dashboard/components/RefreshIntervalModal.jsx index 8734df90d..b314431b3 100644 --- a/superset/assets/src/dashboard/components/RefreshIntervalModal.jsx +++ b/superset/assets/src/dashboard/components/RefreshIntervalModal.jsx @@ -25,13 +25,8 @@ import ModalTrigger from '../../components/ModalTrigger'; const propTypes = { triggerNode: PropTypes.node.isRequired, - initialRefreshFrequency: PropTypes.number, - onChange: PropTypes.func, -}; - -const defaultProps = { - initialRefreshFrequency: 0, - onChange: () => {}, + refreshFrequency: PropTypes.number.isRequired, + onChange: PropTypes.func.isRequired, }; const options = [ @@ -51,7 +46,7 @@ class RefreshIntervalModal extends React.PureComponent { constructor(props) { super(props); this.state = { - refreshFrequency: props.initialRefreshFrequency, + refreshFrequency: props.refreshFrequency, }; } render() { @@ -81,6 +76,5 @@ class RefreshIntervalModal extends React.PureComponent { } } RefreshIntervalModal.propTypes = propTypes; -RefreshIntervalModal.defaultProps = defaultProps; export default RefreshIntervalModal; diff --git a/superset/assets/src/dashboard/components/SaveModal.jsx b/superset/assets/src/dashboard/components/SaveModal.jsx index f9a7a6cc7..aa5436979 100644 --- a/superset/assets/src/dashboard/components/SaveModal.jsx +++ b/superset/assets/src/dashboard/components/SaveModal.jsx @@ -41,6 +41,7 @@ const propTypes = { onSave: PropTypes.func.isRequired, isMenuItem: PropTypes.bool, canOverwrite: PropTypes.bool.isRequired, + refreshFrequency: PropTypes.number.isRequired, }; const defaultProps = { @@ -95,6 +96,7 @@ class SaveModal extends React.PureComponent { expandedSlices, filters, dashboardId, + refreshFrequency, } = this.props; const data = { @@ -105,6 +107,7 @@ class SaveModal extends React.PureComponent { saveType === SAVE_TYPE_NEWDASHBOARD ? newDashName : dashboardTitle, default_filters: safeStringify(filters), duplicate_slices: this.state.duplicateSlices, + refresh_frequency: refreshFrequency, }; if (saveType === SAVE_TYPE_NEWDASHBOARD && !newDashName) { diff --git a/superset/assets/src/dashboard/containers/DashboardHeader.jsx b/superset/assets/src/dashboard/containers/DashboardHeader.jsx index 4eaca36a9..570d790ae 100644 --- a/superset/assets/src/dashboard/containers/DashboardHeader.jsx +++ b/superset/assets/src/dashboard/containers/DashboardHeader.jsx @@ -34,6 +34,7 @@ import { saveDashboardRequest, setMaxUndoHistoryExceeded, maxUndoHistoryToast, + setRefreshFrequency, } from '../actions/dashboardState'; import { @@ -68,6 +69,7 @@ function mapStateToProps({ (undoableLayout.present[DASHBOARD_HEADER_ID] || {}).meta || {} ).text, expandedSlices: dashboardState.expandedSlices, + refreshFrequency: dashboardState.refreshFrequency, css: dashboardState.css, charts, userId: dashboardInfo.userId, @@ -101,6 +103,7 @@ function mapDispatchToProps(dispatch) { setMaxUndoHistoryExceeded, maxUndoHistoryToast, logEvent, + setRefreshFrequency, }, dispatch, ); diff --git a/superset/assets/src/dashboard/reducers/dashboardState.js b/superset/assets/src/dashboard/reducers/dashboardState.js index 72c4d3678..24066ebca 100644 --- a/superset/assets/src/dashboard/reducers/dashboardState.js +++ b/superset/assets/src/dashboard/reducers/dashboardState.js @@ -30,6 +30,7 @@ import { TOGGLE_EXPAND_SLICE, TOGGLE_FAVE_STAR, UPDATE_CSS, + SET_REFRESH_FREQUENCY, } from '../actions/dashboardState'; export default function dashboardStateReducer(state = {}, action) { @@ -147,6 +148,9 @@ export default function dashboardStateReducer(state = {}, action) { const { hasUnsavedChanges } = action.payload; return { ...state, hasUnsavedChanges }; }, + [SET_REFRESH_FREQUENCY]() { + return { ...state, refreshFrequency: action.refreshFrequency }; + }, }; if (action.type in actionHandlers) { diff --git a/superset/assets/src/dashboard/reducers/getInitialState.js b/superset/assets/src/dashboard/reducers/getInitialState.js index 57354c86a..a9c4d8f95 100644 --- a/superset/assets/src/dashboard/reducers/getInitialState.js +++ b/superset/assets/src/dashboard/reducers/getInitialState.js @@ -186,6 +186,7 @@ export default function(bootstrapData) { refresh: false, filters, expandedSlices: dashboard.metadata.expanded_slices || {}, + refreshFrequency: dashboard.metadata.refresh_frequency || 0, css: dashboard.css || '', editMode: dashboard.dash_edit_perm && editMode, showBuilderPane: dashboard.dash_edit_perm && editMode, diff --git a/superset/views/core.py b/superset/views/core.py index 7e6d9d328..051ea562f 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -1735,6 +1735,7 @@ class Superset(BaseSupersetView): 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', '{}')) applicable_filters = \ {key: v for key, v in default_filters_data.items()