You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ju...@apache.org on 2023/08/30 23:10:05 UTC

[superset] branch master updated: chore: consolidate sqllab store into SPA store (#25088)

This is an automated email from the ASF dual-hosted git repository.

justinpark pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 846c79ef55 chore: consolidate sqllab store into SPA store (#25088)
846c79ef55 is described below

commit 846c79ef552e7d0436e006470ac3b4574cba4daf
Author: JUST.in DO IT <ju...@airbnb.com>
AuthorDate: Wed Aug 30 16:09:57 2023 -0700

    chore: consolidate sqllab store into SPA store (#25088)
---
 superset-frontend/src/SqlLab/App.jsx               | 88 +---------------------
 superset-frontend/src/SqlLab/actions/sqlLab.js     | 20 ++++-
 .../middlewares/persistSqlLabStateEnhancer.js      | 85 +++++++++++++++++++++
 .../src/SqlLab/reducers/getInitialState.ts         |  9 +--
 superset-frontend/src/SqlLab/reducers/sqlLab.js    |  3 +-
 .../SqlLab/utils/reduxStateToLocalStorageHelper.js | 23 ++++++
 superset-frontend/src/views/store.ts               | 22 +++++-
 7 files changed, 152 insertions(+), 98 deletions(-)

diff --git a/superset-frontend/src/SqlLab/App.jsx b/superset-frontend/src/SqlLab/App.jsx
index 37a45fc6fb..ae8b81f4a8 100644
--- a/superset-frontend/src/SqlLab/App.jsx
+++ b/superset-frontend/src/SqlLab/App.jsx
@@ -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 {
@@ -30,16 +29,11 @@ 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 { persistSqlLabStateEnhancer } from 'src/SqlLab/middlewares/persistSqlLabStateEnhancer';
 import getInitialState from './reducers/getInitialState';
 import { reducers } from './reducers/index';
 import App from './components/App';
-import {
-  emptyTablePersistData,
-  emptyQueryResults,
-  clearQueryEditors,
-} from './utils/reduxStateToLocalStorageHelper';
-import { BYTES_PER_CHAR, KB_STORAGE } from './constants';
+import { rehydratePersistedState } from './utils/reduxStateToLocalStorageHelper';
 import setupApp from '../setup/setupApp';
 
 import '../assets/stylesheets/reactable-pagination.less';
@@ -54,90 +48,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: [persistSqlLabStateEnhancer],
   }),
 });
 
-// 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');
diff --git a/superset-frontend/src/SqlLab/actions/sqlLab.js b/superset-frontend/src/SqlLab/actions/sqlLab.js
index 8d39d3bbdb..7c27a74a1e 100644
--- a/superset-frontend/src/SqlLab/actions/sqlLab.js
+++ b/superset-frontend/src/SqlLab/actions/sqlLab.js
@@ -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';
@@ -136,8 +139,21 @@ export function getUpToDateQuery(rootState, queryEditor, key) {
   };
 }
 
-export function resetState() {
-  return { type: RESET_STATE };
+export function resetState(data) {
+  return (dispatch, getState) => {
+    const { common } = getState();
+    const initialState = getInitialState({
+      ...getBootstrapData(),
+      common,
+      ...data,
+    });
+
+    dispatch({
+      type: RESET_STATE,
+      sqlLabInitialState: initialState.sqlLab,
+    });
+    rehydratePersistedState(dispatch, initialState);
+  };
 }
 
 export function updateQueryEditor(alterations) {
diff --git a/superset-frontend/src/SqlLab/middlewares/persistSqlLabStateEnhancer.js b/superset-frontend/src/SqlLab/middlewares/persistSqlLabStateEnhancer.js
new file mode 100644
index 0000000000..4e32095e28
--- /dev/null
+++ b/superset-frontend/src/SqlLab/middlewares/persistSqlLabStateEnhancer.js
@@ -0,0 +1,85 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+// TODO: requires redux-localstorage > 1.0 for typescript support
+import persistState from 'redux-localstorage';
+import {
+  emptyTablePersistData,
+  emptyQueryResults,
+  clearQueryEditors,
+} from '../utils/reduxStateToLocalStorageHelper';
+import { BYTES_PER_CHAR, KB_STORAGE } from '../constants';
+
+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 persistSqlLabStateEnhancer = persistState(
+  sqlLabPersistStateConfig.paths,
+  sqlLabPersistStateConfig.config,
+);
diff --git a/superset-frontend/src/SqlLab/reducers/getInitialState.ts b/superset-frontend/src/SqlLab/reducers/getInitialState.ts
index 24db593530..214680d53c 100644
--- a/superset-frontend/src/SqlLab/reducers/getInitialState.ts
+++ b/superset-frontend/src/SqlLab/reducers/getInitialState.ts
@@ -41,7 +41,7 @@ export default function getInitialState({
   tab_state_ids: tabStateIds = [],
   databases,
   queries: queries_,
-  user,
+  ...otherBootstrapData
 }: BootstrapData & Partial<InitialState>) {
   /**
    * Before YYYY-MM-DD, the state for SQL Lab was stored exclusively in the
@@ -205,10 +205,7 @@ export default function getInitialState({
       (common || {})?.flash_messages || [],
     ),
     localStorageUsageInKilobytes: 0,
-    common: {
-      flash_messages: common.flash_messages,
-      conf: common.conf,
-    },
-    user,
+    common,
+    ...otherBootstrapData,
   };
 }
diff --git a/superset-frontend/src/SqlLab/reducers/sqlLab.js b/superset-frontend/src/SqlLab/reducers/sqlLab.js
index 2b82f42d09..0c7fe0e840 100644
--- a/superset-frontend/src/SqlLab/reducers/sqlLab.js
+++ b/superset-frontend/src/SqlLab/reducers/sqlLab.js
@@ -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 {
@@ -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 };
diff --git a/superset-frontend/src/SqlLab/utils/reduxStateToLocalStorageHelper.js b/superset-frontend/src/SqlLab/utils/reduxStateToLocalStorageHelper.js
index 2fb1da1783..281f08bcb3 100644
--- a/superset-frontend/src/SqlLab/utils/reduxStateToLocalStorageHelper.js
+++ b/superset-frontend/src/SqlLab/utils/reduxStateToLocalStorageHelper.js
@@ -17,6 +17,7 @@
  * under the License.
  */
 import pick from 'lodash/pick';
+import { tableApiUtil } from 'src/hooks/apiResources/tables';
 import {
   BYTES_PER_CHAR,
   KB_STORAGE,
@@ -96,3 +97,25 @@ export function clearQueryEditors(queryEditors) {
       ),
   );
 }
+
+export function rehydratePersistedState(dispatch, state) {
+  // Rehydrate server side persisted table metadata
+  state.sqlLab.tables.forEach(({ 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 },
+          {},
+        ),
+      );
+    }
+  });
+}
diff --git a/superset-frontend/src/views/store.ts b/superset-frontend/src/views/store.ts
index 3a46f31dab..f1aa94170b 100644
--- a/superset-frontend/src/views/store.ts
+++ b/superset-frontend/src/views/store.ts
@@ -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';
@@ -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 { persistSqlLabStateEnhancer } from 'src/SqlLab/middlewares/persistSqlLabStateEnhancer';
+import sqlLabReducer from 'src/SqlLab/reducers/sqlLab';
+import getInitialState from 'src/SqlLab/reducers/getInitialState';
 import { DatasourcesState } from 'src/dashboard/types';
 import {
   DatasourcesActionPayload,
@@ -113,6 +122,8 @@ const CombinedDatasourceReducers = (
 };
 
 const reducers = {
+  sqlLab: sqlLabReducer,
+  localStorageUsage: noopReducer(0),
   messageToasts: messageToastReducer,
   common: noopReducer(bootstrapData.common),
   user: userReducer,
@@ -140,14 +151,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: {
@@ -156,9 +167,12 @@ export function setupStore({
     },
     middleware: getMiddleware,
     devTools: process.env.WEBPACK_MODE === 'development' && !disableDebugger,
+    ...(!isFeatureEnabled(FeatureFlag.SQLLAB_BACKEND_PERSISTENCE) && {
+      enhancers: [persistSqlLabStateEnhancer as StoreEnhancer],
+    }),
     ...overrides,
   });
 }
 
-export const store: Store = setupStore();
+export const store = setupStore();
 export type RootState = ReturnType<typeof store.getState>;