diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx b/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx index cf637d79ee2..818b049919c 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx +++ b/superset-frontend/src/SqlLab/components/SqlEditor/index.tsx @@ -130,6 +130,29 @@ import { resolveView, useViews } from 'src/core/views'; /** Per-tab localStorage key storing the active northPane view ID. */ const NORTH_PANE_VIEW_KEY = (tabId: string) => `sqllab.northPaneView.${tabId}`; +// The northPane keys are dynamic per-tab strings rather than members of the +// typed LocalStorageKeys enum, so the typed helpers don't apply. Guard the raw +// access here so a storage-restricted browser can't crash the editor mount. +const readNorthPaneStorage = (key: string): string | null => { + try { + return localStorage.getItem(key); + } catch { + return null; + } +}; + +const writeNorthPaneStorage = (key: string, value: string | null): void => { + try { + if (value === null) { + localStorage.removeItem(key); + } else { + localStorage.setItem(key, value); + } + } catch { + // localStorage may be unavailable (blocked/quota/private mode); ignore. + } +}; + const bootstrapData = getBootstrapData(); const scheduledQueriesConf = bootstrapData?.common?.conf?.SCHEDULED_QUERIES; @@ -292,25 +315,23 @@ const SqlEditor: FC = ({ // SQL editor layout. Set by an extension via PENDING_NORTH_PANE_VIEW_KEY // before calling createTab(); persisted per-tab in localStorage. const [northPaneViewId, setNorthPaneViewId] = useState(() => { - const pendingViewId = localStorage.getItem(PENDING_NORTH_PANE_VIEW_KEY); + const pendingViewId = readNorthPaneStorage(PENDING_NORTH_PANE_VIEW_KEY); if (pendingViewId) { - localStorage.removeItem(PENDING_NORTH_PANE_VIEW_KEY); - localStorage.setItem( + writeNorthPaneStorage(PENDING_NORTH_PANE_VIEW_KEY, null); + writeNorthPaneStorage( NORTH_PANE_VIEW_KEY(northPaneStorageId), pendingViewId, ); return pendingViewId; } - return localStorage.getItem(NORTH_PANE_VIEW_KEY(northPaneStorageId)); + return readNorthPaneStorage(NORTH_PANE_VIEW_KEY(northPaneStorageId)); }); useEffect(() => { - const persistKey = NORTH_PANE_VIEW_KEY(northPaneStorageId); - if (northPaneViewId) { - localStorage.setItem(persistKey, northPaneViewId); - } else { - localStorage.removeItem(persistKey); - } + writeNorthPaneStorage( + NORTH_PANE_VIEW_KEY(northPaneStorageId), + northPaneViewId, + ); }, [northPaneStorageId, northPaneViewId]); useEffect(() => { diff --git a/superset-frontend/src/core/sqlLab/index.ts b/superset-frontend/src/core/sqlLab/index.ts index 707b731c1b1..0b5cb699078 100644 --- a/superset-frontend/src/core/sqlLab/index.ts +++ b/superset-frontend/src/core/sqlLab/index.ts @@ -161,19 +161,29 @@ const makeTab = ( catalog: string | null = null, schema: string | null = null, closed: boolean = false, + backendId?: string, ): Tab => { const panels: Panel[] = []; // TODO: Populate panels const editorGetter = closed ? () => Promise.reject(new Error(`Tab ${id} has been closed`)) : () => getEditorAsync(id); - return new Tab(id, name, dbId, catalog, schema, editorGetter, panels); + return new Tab( + id, + name, + dbId, + catalog, + schema, + editorGetter, + panels, + backendId, + ); }; const getTab = (id: string): Tab | undefined => { const queryEditor = findQueryEditor(id); if (queryEditor?.dbId !== undefined) { - const { name, dbId, catalog, schema } = queryEditor; - return makeTab(id, name, dbId, catalog, schema); + const { name, dbId, catalog, schema, tabViewId } = queryEditor; + return makeTab(id, name, dbId, catalog, schema, false, tabViewId); } return undefined; }; @@ -441,6 +451,7 @@ const onDidCloseTab: typeof sqlLabApi.onDidCloseTab = ( action.queryEditor.catalog, action.queryEditor.schema, true, // closed + action.queryEditor.tabViewId, ), thisArgs, ); @@ -507,6 +518,8 @@ const onDidCreateTab: typeof sqlLabApi.onDidCreateTab = ( action.queryEditor.dbId ?? 0, action.queryEditor.catalog, action.queryEditor.schema ?? undefined, + false, + action.queryEditor.tabViewId, ), thisArgs, ); @@ -574,6 +587,8 @@ const createTab: typeof sqlLabApi.createTab = async ( newTab.dbId ?? 0, newTab.catalog, newTab.schema ?? undefined, + false, + newTab.tabViewId, ); }; diff --git a/superset-frontend/src/core/sqlLab/models.ts b/superset-frontend/src/core/sqlLab/models.ts index ce593c582f1..231312cf499 100644 --- a/superset-frontend/src/core/sqlLab/models.ts +++ b/superset-frontend/src/core/sqlLab/models.ts @@ -34,6 +34,8 @@ export class Panel implements sqlLabType.Panel { export class Tab implements sqlLabType.Tab { id: string; + backendId?: string; + title: string; databaseId: number; @@ -54,6 +56,7 @@ export class Tab implements sqlLabType.Tab { schema: string | null = null, editorGetter: () => Promise, panels: Panel[] = [], + backendId?: string, ) { this.id = id; this.title = title; @@ -62,6 +65,7 @@ export class Tab implements sqlLabType.Tab { this.schema = schema; this.editorGetter = editorGetter; this.panels = panels; + this.backendId = backendId; } getEditor(): Promise {