Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: consolidate sqllab store into SPA store #25088

Merged
merged 6 commits into from
Aug 30, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 4 additions & 82 deletions superset-frontend/src/SqlLab/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
* under the License.
*/
import React from 'react';
import persistState from 'redux-localstorage';
import { Provider } from 'react-redux';
import { hot } from 'react-hot-loader/root';
import {
Expand All @@ -30,16 +29,13 @@ import { GlobalStyles } from 'src/GlobalStyles';
import { setupStore, userReducer } from 'src/views/store';
import setupExtensions from 'src/setup/setupExtensions';
import getBootstrapData from 'src/utils/getBootstrapData';
import { tableApiUtil } from 'src/hooks/apiResources/tables';
import getInitialState from './reducers/getInitialState';
import { reducers } from './reducers/index';
import App from './components/App';
import {
emptyTablePersistData,
emptyQueryResults,
clearQueryEditors,
persistSqlLabStateEnhander,
michael-s-molina marked this conversation as resolved.
Show resolved Hide resolved
rehydratePersistedState,
} from './utils/reduxStateToLocalStorageHelper';
import { BYTES_PER_CHAR, KB_STORAGE } from './constants';
import setupApp from '../setup/setupApp';

import '../assets/stylesheets/reactable-pagination.less';
Expand All @@ -54,90 +50,16 @@ const bootstrapData = getBootstrapData();
initFeatureFlags(bootstrapData.common.feature_flags);

const initialState = getInitialState(bootstrapData);
const sqlLabPersistStateConfig = {
paths: ['sqlLab'],
config: {
slicer: paths => state => {
const subset = {};
paths.forEach(path => {
// this line is used to remove old data from browser localStorage.
// we used to persist all redux state into localStorage, but
// it caused configurations passed from server-side got override.
// see PR 6257 for details
delete state[path].common; // eslint-disable-line no-param-reassign
if (path === 'sqlLab') {
subset[path] = {
...state[path],
tables: emptyTablePersistData(state[path].tables),
queries: emptyQueryResults(state[path].queries),
queryEditors: clearQueryEditors(state[path].queryEditors),
unsavedQueryEditor: clearQueryEditors([
state[path].unsavedQueryEditor,
])[0],
};
}
});

const data = JSON.stringify(subset);
// 2 digit precision
const currentSize =
Math.round(((data.length * BYTES_PER_CHAR) / KB_STORAGE) * 100) / 100;
if (state.localStorageUsageInKilobytes !== currentSize) {
state.localStorageUsageInKilobytes = currentSize; // eslint-disable-line no-param-reassign
}

return subset;
},
merge: (initialState, persistedState = {}) => {
const result = {
...initialState,
...persistedState,
sqlLab: {
...(persistedState?.sqlLab || {}),
// Overwrite initialState over persistedState for sqlLab
// since a logic in getInitialState overrides the value from persistedState
...initialState.sqlLab,
},
};
return result;
},
},
};

export const store = setupStore({
initialState,
rootReducers: { ...reducers, user: userReducer },
...(!isFeatureEnabled(FeatureFlag.SQLLAB_BACKEND_PERSISTENCE) && {
enhancers: [
persistState(
sqlLabPersistStateConfig.paths,
sqlLabPersistStateConfig.config,
),
],
enhancers: [persistSqlLabStateEnhander],
}),
});

// Rehydrate server side persisted table metadata
initialState.sqlLab.tables.forEach(
({ name: table, schema, dbId, persistData }) => {
if (dbId && schema && table && persistData?.columns) {
store.dispatch(
tableApiUtil.upsertQueryData(
'tableMetadata',
{ dbId, schema, table },
persistData,
),
);
store.dispatch(
tableApiUtil.upsertQueryData(
'tableExtendedMetadata',
{ dbId, schema, table },
{},
),
);
}
},
);
rehydratePersistedState(store.dispatch, initialState);

// Highlight the navbar menu
const menus = document.querySelectorAll('.nav.navbar-nav li.dropdown');
Expand Down
22 changes: 20 additions & 2 deletions superset-frontend/src/SqlLab/actions/sqlLab.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,11 @@ import {
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
import COMMON_ERR_MESSAGES from 'src/utils/errorMessages';
import { LOG_ACTIONS_SQLLAB_FETCH_FAILED_QUERY } from 'src/logger/LogUtils';
import getBootstrapData from 'src/utils/getBootstrapData';
import { logEvent } from 'src/logger/actions';
import { newQueryTabName } from '../utils/newQueryTabName';
import getInitialState from '../reducers/getInitialState';
import { rehydratePersistedState } from '../utils/reduxStateToLocalStorageHelper';

export const RESET_STATE = 'RESET_STATE';
export const ADD_QUERY_EDITOR = 'ADD_QUERY_EDITOR';
Expand Down Expand Up @@ -136,8 +139,23 @@ export function getUpToDateQuery(rootState, queryEditor, key) {
};
}

export function resetState() {
return { type: RESET_STATE };
export function resetState(data) {
return (dispatch, getState) => {
const { common, user, config } = getState();
const initialState = getInitialState({
michael-s-molina marked this conversation as resolved.
Show resolved Hide resolved
...getBootstrapData(),
common,
user,
config,
...data,
});

dispatch({
type: RESET_STATE,
sqlLabInitialState: initialState.sqlLab,
});
rehydratePersistedState(dispatch, initialState);
};
}

export function updateQueryEditor(alterations) {
Expand Down
3 changes: 1 addition & 2 deletions superset-frontend/src/SqlLab/reducers/sqlLab.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
* under the License.
*/
import { normalizeTimestamp, QueryState, t } from '@superset-ui/core';
import getInitialState from './getInitialState';
import * as actions from '../actions/sqlLab';
import { now } from '../../utils/dates';
import {
Expand Down Expand Up @@ -165,7 +164,7 @@ export default function sqlLabReducer(state = {}, action) {
return { ...state, queries: newQueries };
},
[actions.RESET_STATE]() {
return { ...getInitialState() };
return { ...action.sqlLabInitialState };
},
[actions.MERGE_TABLE]() {
const at = { ...action.table };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
* specific language governing permissions and limitations
* under the License.
*/
import persistState from 'redux-localstorage';
import pick from 'lodash/pick';
import { tableApiUtil } from 'src/hooks/apiResources/tables';
import {
BYTES_PER_CHAR,
KB_STORAGE,
Expand Down Expand Up @@ -96,3 +98,86 @@ export function clearQueryEditors(queryEditors) {
),
);
}

const CLEAR_ENTITY_HELPERS_MAP = {
tables: emptyTablePersistData,
queries: emptyQueryResults,
queryEditors: clearQueryEditors,
unsavedQueryEditor: qe => clearQueryEditors([qe])[0],
};

const sqlLabPersistStateConfig = {
paths: ['sqlLab'],
config: {
slicer: paths => state => {
const subset = {};
paths.forEach(path => {
// this line is used to remove old data from browser localStorage.
// we used to persist all redux state into localStorage, but
// it caused configurations passed from server-side got override.
// see PR 6257 for details
delete state[path].common; // eslint-disable-line no-param-reassign
if (path === 'sqlLab') {
subset[path] = Object.fromEntries(
Object.entries(state[path]).map(([key, value]) => [
key,
CLEAR_ENTITY_HELPERS_MAP[key]?.(value) ?? value,
]),
);
}
});

const data = JSON.stringify(subset);
// 2 digit precision
const currentSize =
Math.round(((data.length * BYTES_PER_CHAR) / KB_STORAGE) * 100) / 100;
if (state.localStorageUsageInKilobytes !== currentSize) {
state.localStorageUsageInKilobytes = currentSize; // eslint-disable-line no-param-reassign
}

return subset;
},
merge: (initialState, persistedState = {}) => {
const result = {
...initialState,
...persistedState,
sqlLab: {
...(persistedState?.sqlLab || {}),
// Overwrite initialState over persistedState for sqlLab
// since a logic in getInitialState overrides the value from persistedState
...initialState.sqlLab,
},
};
return result;
},
},
};

export const persistSqlLabStateEnhander = persistState(
michael-s-molina marked this conversation as resolved.
Show resolved Hide resolved
michael-s-molina marked this conversation as resolved.
Show resolved Hide resolved
sqlLabPersistStateConfig.paths,
sqlLabPersistStateConfig.config,
);

export function rehydratePersistedState(dispatch, persistedState) {
michael-s-molina marked this conversation as resolved.
Show resolved Hide resolved
// Rehydrate server side persisted table metadata
persistedState.sqlLab.tables.forEach(
michael-s-molina marked this conversation as resolved.
Show resolved Hide resolved
({ name: table, schema, dbId, persistData }) => {
if (dbId && schema && table && persistData?.columns) {
dispatch(
tableApiUtil.upsertQueryData(
'tableMetadata',
{ dbId, schema, table },
persistData,
),
);
dispatch(
tableApiUtil.upsertQueryData(
'tableExtendedMetadata',
{ dbId, schema, table },
{},
),
);
}
},
);
}
21 changes: 17 additions & 4 deletions superset-frontend/src/views/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@
* specific language governing permissions and limitations
* under the License.
*/
import { configureStore, ConfigureStoreOptions, Store } from '@reduxjs/toolkit';
import {
configureStore,
ConfigureStoreOptions,
StoreEnhancer,
} from '@reduxjs/toolkit';
import thunk from 'redux-thunk';
import { api } from 'src/hooks/apiResources/queryApi';
import messageToastReducer from 'src/components/MessageToasts/reducers';
Expand All @@ -34,6 +38,11 @@ import logger from 'src/middleware/loggerMiddleware';
import saveModal from 'src/explore/reducers/saveModalReducer';
import explore from 'src/explore/reducers/exploreReducer';
import exploreDatasources from 'src/explore/reducers/datasourcesReducer';
import { FeatureFlag, isFeatureEnabled } from '@superset-ui/core';

import { persistSqlLabStateEnhander } from 'src/SqlLab/utils/reduxStateToLocalStorageHelper';
michael-s-molina marked this conversation as resolved.
Show resolved Hide resolved
import { reducers as sqlLabReducers } from 'src/SqlLab/reducers';
import getInitialState from 'src/SqlLab/reducers/getInitialState';
import { DatasourcesState } from 'src/dashboard/types';
import {
DatasourcesActionPayload,
Expand Down Expand Up @@ -113,6 +122,7 @@ const CombinedDatasourceReducers = (
};

const reducers = {
...sqlLabReducers,
michael-s-molina marked this conversation as resolved.
Show resolved Hide resolved
messageToasts: messageToastReducer,
common: noopReducer(bootstrapData.common),
user: userReducer,
Expand Down Expand Up @@ -140,14 +150,14 @@ const reducers = {
*/
export function setupStore({
disableDebugger = false,
initialState = {},
initialState = getInitialState(bootstrapData),
rootReducers = reducers,
...overrides
}: {
disableDebugger?: boolean;
initialState?: ConfigureStoreOptions['preloadedState'];
rootReducers?: ConfigureStoreOptions['reducer'];
} & Partial<ConfigureStoreOptions> = {}): Store {
} & Partial<ConfigureStoreOptions> = {}) {
return configureStore({
preloadedState: initialState,
reducer: {
Expand All @@ -156,9 +166,12 @@ export function setupStore({
},
middleware: getMiddleware,
devTools: process.env.WEBPACK_MODE === 'development' && !disableDebugger,
...(!isFeatureEnabled(FeatureFlag.SQLLAB_BACKEND_PERSISTENCE) && {
enhancers: [persistSqlLabStateEnhander as StoreEnhancer],
michael-s-molina marked this conversation as resolved.
Show resolved Hide resolved
}),
...overrides,
});
}

export const store: Store = setupStore();
export const store = setupStore();
export type RootState = ReturnType<typeof store.getState>;