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

michaelsmolina 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 e729b2dbb4 fix: SQL Lab tab events (#35105)
e729b2dbb4 is described below

commit e729b2dbb42ada7feb6484a2236be73932b630bc
Author: Michael S. Molina <70410625+michael-s-mol...@users.noreply.github.com>
AuthorDate: Thu Sep 11 17:53:51 2025 -0300

    fix: SQL Lab tab events (#35105)
---
 superset-frontend/src/SqlLab/actions/sqlLab.js     |  2 +
 .../src/SqlLab/actions/sqlLab.test.js              |  4 ++
 superset-frontend/src/SqlLab/fixtures.ts           |  4 ++
 .../src/SqlLab/reducers/getInitialState.ts         |  4 ++
 superset-frontend/src/SqlLab/types.ts              |  1 +
 .../SqlLab/utils/reduxStateToLocalStorageHelper.ts |  1 +
 superset-frontend/src/core/sqlLab.ts               | 63 ++++++++++++++++------
 .../src/hooks/apiResources/sqlEditorTabs.test.ts   |  1 +
 8 files changed, 65 insertions(+), 15 deletions(-)

diff --git a/superset-frontend/src/SqlLab/actions/sqlLab.js 
b/superset-frontend/src/SqlLab/actions/sqlLab.js
index 1bff50b5b5..019a1fe3ef 100644
--- a/superset-frontend/src/SqlLab/actions/sqlLab.js
+++ b/superset-frontend/src/SqlLab/actions/sqlLab.js
@@ -395,6 +395,7 @@ export function runQueryFromSqlEditor(
       dbId: qe.dbId,
       sql: qe.selectedText || qe.sql,
       sqlEditorId: qe.tabViewId ?? qe.id,
+      immutableId: qe.immutableId,
       tab: qe.name,
       catalog: qe.catalog,
       schema: qe.schema,
@@ -537,6 +538,7 @@ export function addQueryEditor(queryEditor) {
   const newQueryEditor = {
     ...queryEditor,
     id: nanoid(11),
+    immutableId: nanoid(11),
     loaded: true,
     inLocalStorage: true,
   };
diff --git a/superset-frontend/src/SqlLab/actions/sqlLab.test.js 
b/superset-frontend/src/SqlLab/actions/sqlLab.test.js
index 4f344c973b..0280a283a6 100644
--- a/superset-frontend/src/SqlLab/actions/sqlLab.test.js
+++ b/superset-frontend/src/SqlLab/actions/sqlLab.test.js
@@ -441,6 +441,7 @@ describe('async actions', () => {
             queryLimit: undefined,
             maxRow: undefined,
             id: 'abcd',
+            immutableId: 'abcd',
             templateParams: undefined,
             inLocalStorage: true,
             loaded: true,
@@ -570,6 +571,7 @@ describe('async actions', () => {
           type: actions.ADD_QUERY_EDITOR,
           queryEditor: {
             ...queryEditor,
+            immutableId: 'abcd',
             inLocalStorage: true,
             loaded: true,
           },
@@ -597,6 +599,7 @@ describe('async actions', () => {
             type: actions.ADD_QUERY_EDITOR,
             queryEditor: {
               id: 'abcd',
+              immutableId: 'abcd',
               sql: expect.stringContaining('SELECT ...'),
               name: `Untitled Query 7`,
               dbId: defaultQueryEditor.dbId,
@@ -753,6 +756,7 @@ describe('async actions', () => {
             queryEditor: {
               ...queryEditor,
               id: 'abcd',
+              immutableId: 'abcd',
               loaded: true,
               inLocalStorage: true,
             },
diff --git a/superset-frontend/src/SqlLab/fixtures.ts 
b/superset-frontend/src/SqlLab/fixtures.ts
index 9ac8bed6e7..3ebc1db598 100644
--- a/superset-frontend/src/SqlLab/fixtures.ts
+++ b/superset-frontend/src/SqlLab/fixtures.ts
@@ -188,6 +188,7 @@ export const table = {
 export const defaultQueryEditor = {
   version: LatestQueryEditorVersion,
   id: 'dfsadfs',
+  immutableId: 'immutable-id',
   autorun: false,
   dbId: 1,
   latestQueryId: null,
@@ -204,6 +205,7 @@ export const defaultQueryEditor = {
 export const extraQueryEditor1 = {
   ...defaultQueryEditor,
   id: 'diekd23',
+  immutableId: 'immutable-id',
   sql: 'SELECT *\nFROM\nWHERE\nLIMIT',
   name: 'Untitled Query 2',
   selectedText: 'SELECT',
@@ -212,6 +214,7 @@ export const extraQueryEditor1 = {
 export const extraQueryEditor2 = {
   ...defaultQueryEditor,
   id: 'owkdi998',
+  immutableId: 'immutable-id',
   sql: '',
   name: 'Untitled Query 3',
 };
@@ -219,6 +222,7 @@ export const extraQueryEditor2 = {
 export const extraQueryEditor3 = {
   ...defaultQueryEditor,
   id: 'kvk23',
+  immutableId: 'immutable-id',
   sql: '',
   name: 'Untitled Query 4',
   tabViewId: 37,
diff --git a/superset-frontend/src/SqlLab/reducers/getInitialState.ts 
b/superset-frontend/src/SqlLab/reducers/getInitialState.ts
index 91386e3fe6..7d9f0fddac 100644
--- a/superset-frontend/src/SqlLab/reducers/getInitialState.ts
+++ b/superset-frontend/src/SqlLab/reducers/getInitialState.ts
@@ -17,6 +17,7 @@
  * under the License.
  */
 import { t } from '@superset-ui/core';
+import { nanoid } from 'nanoid';
 import type { BootstrapData } from 'src/types/bootstrapTypes';
 import type { InitialState } from 'src/hooks/apiResources/sqlLab';
 import {
@@ -55,6 +56,7 @@ export default function getInitialState({
   let queryEditors: Record<string, QueryEditor> = {};
   const defaultQueryEditor = {
     version: LatestQueryEditorVersion,
+    immutableId: nanoid(11),
     loaded: true,
     name: t('Untitled query'),
     sql: '',
@@ -78,6 +80,7 @@ export default function getInitialState({
       queryEditor = {
         version: activeTab.extra_json?.version ?? QueryEditorVersion.V1,
         id: id.toString(),
+        immutableId: activeTab.extra_json?.immutableId ?? nanoid(11),
         loaded: true,
         name: activeTab.label,
         sql: activeTab.sql || '',
@@ -100,6 +103,7 @@ export default function getInitialState({
       queryEditor = {
         ...defaultQueryEditor,
         id: id.toString(),
+        immutableId: nanoid(11),
         loaded: false,
         name: label,
         dbId: undefined,
diff --git a/superset-frontend/src/SqlLab/types.ts 
b/superset-frontend/src/SqlLab/types.ts
index 9665a88fae..5532f155b7 100644
--- a/superset-frontend/src/SqlLab/types.ts
+++ b/superset-frontend/src/SqlLab/types.ts
@@ -49,6 +49,7 @@ export interface CursorPosition {
 export interface QueryEditor {
   version: QueryEditorVersion;
   id: string;
+  immutableId: string;
   dbId?: number;
   name: string;
   title?: string; // keep it optional for backward compatibility
diff --git 
a/superset-frontend/src/SqlLab/utils/reduxStateToLocalStorageHelper.ts 
b/superset-frontend/src/SqlLab/utils/reduxStateToLocalStorageHelper.ts
index e8e1e15673..683b082b38 100644
--- a/superset-frontend/src/SqlLab/utils/reduxStateToLocalStorageHelper.ts
+++ b/superset-frontend/src/SqlLab/utils/reduxStateToLocalStorageHelper.ts
@@ -45,6 +45,7 @@ const PERSISTENT_QUERY_EDITOR_KEYS = new Set([
   'dbId',
   'height',
   'id',
+  'immutableId',
   'latestQueryId',
   'northPercent',
   'queryLimit',
diff --git a/superset-frontend/src/core/sqlLab.ts 
b/superset-frontend/src/core/sqlLab.ts
index e59bb65742..03321e4565 100644
--- a/superset-frontend/src/core/sqlLab.ts
+++ b/superset-frontend/src/core/sqlLab.ts
@@ -197,6 +197,16 @@ export class QueryErrorResultContext
   }
 }
 
+const getActiveEditorImmutableId = () => {
+  const { sqlLab }: { sqlLab: SqlLabRootState['sqlLab'] } = store.getState();
+  const { queryEditors, tabHistory } = sqlLab;
+  const activeEditorId = tabHistory[tabHistory.length - 1];
+  const activeEditor = queryEditors.find(
+    editor => editor.id === activeEditorId,
+  );
+  return activeEditor?.immutableId;
+};
+
 const activeEditorId = () => {
   const { sqlLab }: { sqlLab: SqlLabRootState['sqlLab'] } = store.getState();
   const { tabHistory } = sqlLab;
@@ -225,19 +235,40 @@ const getCurrentTab: typeof sqlLabType.getCurrentTab = () 
=> {
   return undefined;
 };
 
-const predicate = (
-  actionType: string,
-  currentTabOnly: boolean = true,
-): AnyListenerPredicate<RootState> => {
-  // Uses closure to capture the active editor ID at the time the listener is 
created
-  const id = activeEditorId();
-  return action =>
-    // Compares the original id with the current active editor ID
-    action.type === actionType && (!currentTabOnly || activeEditorId() === id);
+const predicate = (actionType: string): AnyListenerPredicate<RootState> => {
+  // Capture the immutable ID of the active editor at the time the listener is 
created
+  // This ID never changes for a tab, ensuring stable event routing
+  const registrationImmutableId = getActiveEditorImmutableId();
+
+  return action => {
+    if (action.type !== actionType) return false;
+
+    // If we don't have a registration ID, don't filter events
+    if (!registrationImmutableId) return true;
+
+    // For query events, use the immutableId directly from the action payload
+    if (action.query?.immutableId) {
+      return action.query.immutableId === registrationImmutableId;
+    }
+
+    // For tab events, we need to find the immutable ID of the affected tab
+    if (action.queryEditor?.id) {
+      const { sqlLab }: { sqlLab: SqlLabRootState['sqlLab'] } =
+        store.getState();
+      const { queryEditors } = sqlLab;
+      const queryEditor = queryEditors.find(
+        editor => editor.id === action.queryEditor.id,
+      );
+      return queryEditor?.immutableId === registrationImmutableId;
+    }
+
+    // Fallback: do not allow the event if we can't determine the source
+    return false;
+  };
 };
 
 export const onDidQueryRun: typeof sqlLabType.onDidQueryRun = (
-  listener: (editor: sqlLabType.QueryContext) => void,
+  listener: (queryContext: sqlLabType.QueryContext) => void,
   thisArgs?: any,
 ): Disposable =>
   createActionListener(
@@ -272,11 +303,11 @@ export const onDidQueryRun: typeof 
sqlLabType.onDidQueryRun = (
   );
 
 export const onDidQuerySuccess: typeof sqlLabType.onDidQuerySuccess = (
-  listener: (query: sqlLabType.QueryResultContext) => void,
+  listener: (queryResultContext: sqlLabType.QueryResultContext) => void,
   thisArgs?: any,
 ): Disposable =>
   createActionListener(
-    predicate(QUERY_SUCCESS, false),
+    predicate(QUERY_SUCCESS),
     listener,
     (action: ReturnType<typeof querySuccess>) => {
       const { query, results } = action;
@@ -323,7 +354,7 @@ export const onDidQuerySuccess: typeof 
sqlLabType.onDidQuerySuccess = (
   );
 
 export const onDidQueryStop: typeof sqlLabType.onDidQueryStop = (
-  listener: (query: sqlLabType.QueryContext) => void,
+  listener: (queryContext: sqlLabType.QueryContext) => void,
   thisArgs?: any,
 ): Disposable =>
   createActionListener(
@@ -356,11 +387,13 @@ export const onDidQueryStop: typeof 
sqlLabType.onDidQueryStop = (
   );
 
 export const onDidQueryFail: typeof sqlLabType.onDidQueryFail = (
-  listener: (query: sqlLabType.QueryErrorResultContext) => void,
+  listener: (
+    queryErrorResultContext: sqlLabType.QueryErrorResultContext,
+  ) => void,
   thisArgs?: any,
 ): Disposable =>
   createActionListener(
-    predicate(QUERY_FAILED, false),
+    predicate(QUERY_FAILED),
     listener,
     (action: ReturnType<typeof createQueryFailedAction>) => {
       const { query, msg: errorMessage, errors } = action;
diff --git a/superset-frontend/src/hooks/apiResources/sqlEditorTabs.test.ts 
b/superset-frontend/src/hooks/apiResources/sqlEditorTabs.test.ts
index 42f738f3c9..5d6c9a55a9 100644
--- a/superset-frontend/src/hooks/apiResources/sqlEditorTabs.test.ts
+++ b/superset-frontend/src/hooks/apiResources/sqlEditorTabs.test.ts
@@ -33,6 +33,7 @@ import {
 const expectedQueryEditor = {
   version: LatestQueryEditorVersion,
   id: '123',
+  immutableId: 'immutable-id',
   dbId: 456,
   name: 'tab 1',
   sql: 'SELECT * from example_table',

Reply via email to