This is an automated email from the ASF dual-hosted git repository. graceguo pushed a commit to branch gg-Test2 in repository https://gitbox.apache.org/repos/asf/incubator-superset.git
commit 8f5739bc20bf47097d6a6fb206f33ce40a2435ec Author: Grace Guo <[email protected]> AuthorDate: Tue May 28 15:31:20 2019 -0700 [SQL Lab] Show warning when user used up localStorage --- .../javascripts/sqllab/utils/emptyQueryResults.js} | 18 ++++++------- superset/assets/src/SqlLab/App.jsx | 14 +++++++++- superset/assets/src/SqlLab/components/App.jsx | 31 +++++++++++++++++++++- .../assets/src/SqlLab/components/SouthPane.jsx | 5 ++-- superset/assets/src/SqlLab/constants.js | 3 +++ .../assets/src/SqlLab/reducers/getInitialState.js | 1 + superset/assets/src/SqlLab/reducers/index.js | 2 ++ .../reducers/{index.js => localStorageUsage.js} | 14 +++------- .../index.js => utils/emptyQueryResults.js} | 25 ++++++++++------- 9 files changed, 80 insertions(+), 33 deletions(-) diff --git a/superset/assets/src/SqlLab/reducers/index.js b/superset/assets/spec/javascripts/sqllab/utils/emptyQueryResults.js similarity index 71% copy from superset/assets/src/SqlLab/reducers/index.js copy to superset/assets/spec/javascripts/sqllab/utils/emptyQueryResults.js index 9bea4f1..0606b40 100644 --- a/superset/assets/src/SqlLab/reducers/index.js +++ b/superset/assets/spec/javascripts/sqllab/utils/emptyQueryResults.js @@ -16,14 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -import { combineReducers } from 'redux'; +import emptyQueryResults from '../../../../src/SqlLab/utils/emptyQueryResults'; +import { queries } from '../fixtures'; -import sqlLab from './sqlLab'; -import messageToasts from '../../messageToasts/reducers/index'; -import common from './common'; +describe('emptyQueryResults', () => { + it('should empty query.results if query.startDttm is > LOCALSTORAGE_MAX_QUERY_AGE', () => { + // sample object contains old query + const queries = { -export default combineReducers({ - sqlLab, - messageToasts, - common, -}); + } + }); +}); \ No newline at end of file diff --git a/superset/assets/src/SqlLab/App.jsx b/superset/assets/src/SqlLab/App.jsx index 359cd73..24bfaa8 100644 --- a/superset/assets/src/SqlLab/App.jsx +++ b/superset/assets/src/SqlLab/App.jsx @@ -27,6 +27,7 @@ import getInitialState from './reducers/getInitialState'; import rootReducer from './reducers/index'; import { initEnhancer } from '../reduxUtils'; import App from './components/App'; +import emptyQueryResults from './utils/emptyQueryResults'; import setupApp from '../setup/setupApp'; import './main.less'; @@ -50,8 +51,19 @@ const sqlLabPersistStateConfig = { // it caused configurations passed from server-side got override. // see PR 6257 for details delete state[path].common; // eslint-disable-line no-param-reassign - subset[path] = state[path]; + + subset[path] = { + ...state[path], + queries: emptyQueryResults(state[path].queries), + }; }); + + const data = localStorage.getItem('redux') || ''; + const currentSize = Math.round((data.length * 16) / (8 * 1024) * 100) / 100; + if (state.localStorageUsage !== currentSize) { + state.localStorageUsage = currentSize; // eslint-disable-line no-param-reassign + } + return subset; }, }, diff --git a/superset/assets/src/SqlLab/components/App.jsx b/superset/assets/src/SqlLab/components/App.jsx index e4e516a..ad1f266 100644 --- a/superset/assets/src/SqlLab/components/App.jsx +++ b/superset/assets/src/SqlLab/components/App.jsx @@ -21,6 +21,8 @@ import PropTypes from 'prop-types'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import $ from 'jquery'; +import { t } from '@superset-ui/translation'; +import debounce from 'lodash/debounce'; import TabbedSqlEditors from './TabbedSqlEditors'; import QueryAutoRefresh from './QueryAutoRefresh'; @@ -28,6 +30,9 @@ import QuerySearch from './QuerySearch'; import ToastPresenter from '../../messageToasts/containers/ToastPresenter'; import * as Actions from '../actions/sqlLab'; +// Upper limits for localStorage usage. +const SQLLAB_LOCALSTORAGE_MAX = 5120; + class App extends React.PureComponent { constructor(props) { super(props); @@ -35,6 +40,8 @@ class App extends React.PureComponent { hash: window.location.hash, contentHeight: '0px', }; + + this.showLocalStorageUsageWarning = debounce(this.showLocalStorageUsageWarning, 2000); } componentDidMount() { /* eslint-disable react/no-did-mount-set-state */ @@ -42,6 +49,11 @@ class App extends React.PureComponent { window.addEventListener('hashchange', this.onHashChanged.bind(this)); window.addEventListener('resize', this.handleResize.bind(this)); } + componentDidUpdate() { + if (this.props.localStorageUsage >= 0.9 * SQLLAB_LOCALSTORAGE_MAX) { + this.showLocalStorageUsageWarning(this.props.localStorageUsage); + } + } componentWillUnmount() { window.removeEventListener('hashchange', this.onHashChanged.bind(this)); window.removeEventListener('resize', this.handleResize.bind(this)); @@ -65,6 +77,15 @@ class App extends React.PureComponent { const alertHeight = alertEl.length > 0 ? alertEl.outerHeight() : 0; return `${window.innerHeight - headerHeight - tabsHeight - warningHeight - alertHeight}px`; } + showLocalStorageUsageWarning(currentUsage) { + this.props.actions.addDangerToast( + t('SQL Lab uses your browser\'s local storage to store queries and results.' + + `\n Currently, you are using ${currentUsage.toFixed(2)} KB out of ${SQLLAB_LOCALSTORAGE_MAX} KB. storage space.` + + '\n To keep SQL Lab from crashing, please delete some query tabs.' + + '\n You can re-access these queries by using the Save feature before you delete the tab.' + + '\n Note that you will need to close other SQL Lab windows before you do this.'), + ); + } handleResize() { this.setState({ contentHeight: this.getHeight() }); } @@ -91,8 +112,16 @@ class App extends React.PureComponent { App.propTypes = { actions: PropTypes.object, + localStorageUsage: PropTypes.number.isRequired, }; +function mapStateToProps(state) { + const { localStorageUsage } = state; + return { + localStorageUsage, + }; +} + function mapDispatchToProps(dispatch) { return { actions: bindActionCreators(Actions, dispatch), @@ -101,6 +130,6 @@ function mapDispatchToProps(dispatch) { export { App }; export default connect( - null, + mapStateToProps, mapDispatchToProps, )(App); diff --git a/superset/assets/src/SqlLab/components/SouthPane.jsx b/superset/assets/src/SqlLab/components/SouthPane.jsx index c3dd57b..60d670c 100644 --- a/superset/assets/src/SqlLab/components/SouthPane.jsx +++ b/superset/assets/src/SqlLab/components/SouthPane.jsx @@ -27,7 +27,7 @@ import { t } from '@superset-ui/translation'; import * as Actions from '../actions/sqlLab'; import QueryHistory from './QueryHistory'; import ResultSet from './ResultSet'; -import { STATUS_OPTIONS, STATE_BSSTYLE_MAP } from '../constants'; +import { STATUS_OPTIONS, STATE_BSSTYLE_MAP, LOCALSTORAGE_MAX_QUERY_AGE } from '../constants'; const TAB_HEIGHT = 44; @@ -85,7 +85,8 @@ export class SouthPane extends React.PureComponent { latestQuery = props.editorQueries[props.editorQueries.length - 1]; } let results; - if (latestQuery) { + if (latestQuery && + (Date.now() - latestQuery.startDttm) <= LOCALSTORAGE_MAX_QUERY_AGE) { results = ( <ResultSet showControls diff --git a/superset/assets/src/SqlLab/constants.js b/superset/assets/src/SqlLab/constants.js index 3bf8ce0..8bbec6f 100644 --- a/superset/assets/src/SqlLab/constants.js +++ b/superset/assets/src/SqlLab/constants.js @@ -43,3 +43,6 @@ export const TIME_OPTIONS = [ '90 days ago', '1 year ago', ]; + +// 24 hours +export const LOCALSTORAGE_MAX_QUERY_AGE = 24 * 60 * 60 * 1000; diff --git a/superset/assets/src/SqlLab/reducers/getInitialState.js b/superset/assets/src/SqlLab/reducers/getInitialState.js index 535cb3d..2d78154 100644 --- a/superset/assets/src/SqlLab/reducers/getInitialState.js +++ b/superset/assets/src/SqlLab/reducers/getInitialState.js @@ -52,6 +52,7 @@ export default function getInitialState({ defaultDbId, ...restBootstrapData }) { messageToasts: getToastsFromPyFlashMessages( (restBootstrapData.common || {}).flash_messages || [], ), + localStorageUsage: 0, common: { flash_messages: restBootstrapData.common.flash_messages, conf: restBootstrapData.common.conf, diff --git a/superset/assets/src/SqlLab/reducers/index.js b/superset/assets/src/SqlLab/reducers/index.js index 9bea4f1..9d6fd60 100644 --- a/superset/assets/src/SqlLab/reducers/index.js +++ b/superset/assets/src/SqlLab/reducers/index.js @@ -19,11 +19,13 @@ import { combineReducers } from 'redux'; import sqlLab from './sqlLab'; +import localStorageUsage from './localStorageUsage'; import messageToasts from '../../messageToasts/reducers/index'; import common from './common'; export default combineReducers({ sqlLab, + localStorageUsage, messageToasts, common, }); diff --git a/superset/assets/src/SqlLab/reducers/index.js b/superset/assets/src/SqlLab/reducers/localStorageUsage.js similarity index 76% copy from superset/assets/src/SqlLab/reducers/index.js copy to superset/assets/src/SqlLab/reducers/localStorageUsage.js index 9bea4f1..eafbb07 100644 --- a/superset/assets/src/SqlLab/reducers/index.js +++ b/superset/assets/src/SqlLab/reducers/localStorageUsage.js @@ -16,14 +16,6 @@ * specific language governing permissions and limitations * under the License. */ -import { combineReducers } from 'redux'; - -import sqlLab from './sqlLab'; -import messageToasts from '../../messageToasts/reducers/index'; -import common from './common'; - -export default combineReducers({ - sqlLab, - messageToasts, - common, -}); +export default function localStorageUsageReducer(state = 0) { + return state; +} diff --git a/superset/assets/src/SqlLab/reducers/index.js b/superset/assets/src/SqlLab/utils/emptyQueryResults.js similarity index 63% copy from superset/assets/src/SqlLab/reducers/index.js copy to superset/assets/src/SqlLab/utils/emptyQueryResults.js index 9bea4f1..dfe365a 100644 --- a/superset/assets/src/SqlLab/reducers/index.js +++ b/superset/assets/src/SqlLab/utils/emptyQueryResults.js @@ -16,14 +16,21 @@ * specific language governing permissions and limitations * under the License. */ -import { combineReducers } from 'redux'; +import { LOCALSTORAGE_MAX_QUERY_AGE } from '../constants'; -import sqlLab from './sqlLab'; -import messageToasts from '../../messageToasts/reducers/index'; -import common from './common'; +export default function emptyQueryResults(queries) { + return Object.keys(queries) + .reduce((updatedQueries, key) => { + const { startDttm } = queries[key]; + const query = { + ...queries[key], + results: Date.now() - startDttm > LOCALSTORAGE_MAX_QUERY_AGE ? + {} : queries[key].results, + }; -export default combineReducers({ - sqlLab, - messageToasts, - common, -}); + return { + ...updatedQueries, + [key]: query, + }; + }, {}); +} \ No newline at end of file
