| /** |
| * 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. |
| */ |
| import persistState from 'redux-localstorage'; |
| import { pickBy } from 'lodash'; |
| import { isFeatureEnabled, FeatureFlag } from '@superset-ui/core'; |
| import { filterUnsavedQueryEditorList } from 'src/SqlLab/components/EditorAutoSync'; |
| 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 => { |
| if (isFeatureEnabled(FeatureFlag.SqllabBackendPersistence)) { |
| const { |
| queryEditors, |
| editorTabLastUpdatedAt, |
| unsavedQueryEditor, |
| tables, |
| queries, |
| tabHistory, |
| lastUpdatedActiveTab, |
| destroyedQueryEditors, |
| } = state.sqlLab; |
| const unsavedQueryEditors = filterUnsavedQueryEditorList( |
| queryEditors, |
| unsavedQueryEditor, |
| editorTabLastUpdatedAt, |
| ); |
| const hasUnsavedActiveTabState = |
| tabHistory.slice(-1)[0] !== lastUpdatedActiveTab; |
| const hasUnsavedDeletedQueryEditors = |
| Object.keys(destroyedQueryEditors).length > 0; |
| if ( |
| unsavedQueryEditors.length > 0 || |
| hasUnsavedActiveTabState || |
| hasUnsavedDeletedQueryEditors |
| ) { |
| const hasFinishedMigrationFromLocalStorage = |
| unsavedQueryEditors.every( |
| ({ inLocalStorage }) => !inLocalStorage, |
| ); |
| subset.sqlLab = { |
| queryEditors: unsavedQueryEditors, |
| ...(!hasFinishedMigrationFromLocalStorage && { |
| tabHistory, |
| tables: tables.filter(table => table.inLocalStorage), |
| queries: pickBy( |
| queries, |
| query => query.inLocalStorage && !query.isDataPreview, |
| ), |
| }), |
| ...(hasUnsavedActiveTabState && { |
| tabHistory, |
| }), |
| destroyedQueryEditors, |
| }; |
| } |
| return; |
| } |
| // 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; |
| }, |
| }, |
| }; |
| |
| // TODO: requires redux-localstorage > 1.0 for typescript support |
| /** @type {any} */ |
| export const persistSqlLabStateEnhancer = persistState( |
| sqlLabPersistStateConfig.paths, |
| sqlLabPersistStateConfig.config, |
| ); |