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

kgabryje pushed a commit to branch what-if
in repository https://gitbox.apache.org/repos/asf/superset.git

commit ab8144a5010f71df2d74a9019be23335518d67be
Author: Kamil Gabryjelski <[email protected]>
AuthorDate: Thu Dec 18 14:32:18 2025 +0100

    Abort signal for /interpret
---
 .../components/WhatIfDrawer/WhatIfAIInsights.tsx   | 37 +++++++++++++--
 .../dashboard/components/WhatIfDrawer/index.tsx    | 53 ++++++++++++++++------
 .../dashboard/components/WhatIfDrawer/whatIfApi.ts |  6 ++-
 3 files changed, 76 insertions(+), 20 deletions(-)

diff --git 
a/superset-frontend/src/dashboard/components/WhatIfDrawer/WhatIfAIInsights.tsx 
b/superset-frontend/src/dashboard/components/WhatIfDrawer/WhatIfAIInsights.tsx
index a845d771f3..9f4e1616ee 100644
--- 
a/superset-frontend/src/dashboard/components/WhatIfDrawer/WhatIfAIInsights.tsx
+++ 
b/superset-frontend/src/dashboard/components/WhatIfDrawer/WhatIfAIInsights.tsx
@@ -135,6 +135,17 @@ const WhatIfAIInsights = ({
   const chartComparisons = useChartComparison(affectedChartIds);
   const allChartsLoaded = useAllChartsLoaded(affectedChartIds);
 
+  // AbortController for cancelling in-flight /interpret requests
+  const abortControllerRef = useRef<AbortController | null>(null);
+
+  // Cleanup: cancel any pending requests on unmount
+  useEffect(
+    () => () => {
+      abortControllerRef.current?.abort();
+    },
+    [],
+  );
+
   // Track modification changes to reset status when user adjusts the slider
   const modificationsKey = getModificationsKey(modifications);
   const prevModificationsKeyRef = useRef<string>(modificationsKey);
@@ -182,6 +193,8 @@ const WhatIfAIInsights = ({
       console.log(
         '[WhatIfAIInsights] Modifications changed, resetting status to idle',
       );
+      // Cancel any in-flight request when modifications change
+      abortControllerRef.current?.abort();
       // eslint-disable-next-line react-hooks/set-state-in-effect -- 
Intentional: resetting state when modifications change
       setStatus('idle');
       setResponse(null);
@@ -194,18 +207,32 @@ const WhatIfAIInsights = ({
       return;
     }
 
+    // Cancel any in-flight request before starting a new one
+    abortControllerRef.current?.abort();
+
+    // Create a new AbortController for this request
+    const abortController = new AbortController();
+    abortControllerRef.current = abortController;
+
     setStatus('loading');
     setError(null);
 
     try {
-      const result = await fetchWhatIfInterpretation({
-        modifications,
-        charts: chartComparisons,
-        dashboardName: dashboardTitle,
-      });
+      const result = await fetchWhatIfInterpretation(
+        {
+          modifications,
+          charts: chartComparisons,
+          dashboardName: dashboardTitle,
+        },
+        abortController.signal,
+      );
       setResponse(result);
       setStatus('success');
     } catch (err) {
+      // Don't update state if the request was aborted
+      if (err instanceof Error && err.name === 'AbortError') {
+        return;
+      }
       setError(
         err instanceof Error
           ? err.message
diff --git a/superset-frontend/src/dashboard/components/WhatIfDrawer/index.tsx 
b/superset-frontend/src/dashboard/components/WhatIfDrawer/index.tsx
index a381f60f01..f3b0883d80 100644
--- a/superset-frontend/src/dashboard/components/WhatIfDrawer/index.tsx
+++ b/superset-frontend/src/dashboard/components/WhatIfDrawer/index.tsx
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { useCallback, useMemo, useState } from 'react';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
 import { useDispatch, useSelector } from 'react-redux';
 import { t, logging } from '@superset-ui/core';
 import { css, styled, Alert, useTheme } from '@apache-superset/core/ui';
@@ -210,6 +210,17 @@ const WhatIfPanel = ({ onClose, topOffset }: 
WhatIfPanelProps) => {
   const [applyCounter, setApplyCounter] = useState(0);
   const [showAIReasoning, setShowAIReasoning] = useState(false);
 
+  // AbortController for cancelling in-flight /suggest_related requests
+  const suggestionsAbortControllerRef = useRef<AbortController | null>(null);
+
+  // Cleanup: cancel any pending requests on unmount
+  useEffect(
+    () => () => {
+      suggestionsAbortControllerRef.current?.abort();
+    },
+    [],
+  );
+
   const slices = useSelector(
     (state: RootState) => state.sliceEntities.slices as { [id: number]: Slice 
},
   );
@@ -251,6 +262,9 @@ const WhatIfPanel = ({ onClose, topOffset }: 
WhatIfPanelProps) => {
   const handleApply = useCallback(async () => {
     if (!selectedColumn) return;
 
+    // Cancel any in-flight suggestions request
+    suggestionsAbortControllerRef.current?.abort();
+
     // Immediately clear previous results and increment counter to reset AI 
insights component
     setAppliedModifications([]);
     setAffectedChartIds([]);
@@ -269,21 +283,28 @@ const WhatIfPanel = ({ onClose, topOffset }: 
WhatIfPanelProps) => {
 
     // If cascading effects enabled, fetch AI suggestions
     if (enableCascadingEffects) {
+      // Create a new AbortController for this request
+      const abortController = new AbortController();
+      suggestionsAbortControllerRef.current = abortController;
+
       setIsLoadingSuggestions(true);
       try {
-        const suggestions = await fetchRelatedColumnSuggestions({
-          selectedColumn,
-          userMultiplier: multiplier,
-          availableColumns: numericColumns.map(col => ({
-            columnName: col.columnName,
-            description: col.description,
-            verboseName: col.verboseName,
-            datasourceId: col.datasourceId,
-          })),
-          dashboardName: dashboardInfo?.dash_edit_perm
-            ? dashboardInfo?.dashboard_title
-            : undefined,
-        });
+        const suggestions = await fetchRelatedColumnSuggestions(
+          {
+            selectedColumn,
+            userMultiplier: multiplier,
+            availableColumns: numericColumns.map(col => ({
+              columnName: col.columnName,
+              description: col.description,
+              verboseName: col.verboseName,
+              datasourceId: col.datasourceId,
+            })),
+            dashboardName: dashboardInfo?.dash_edit_perm
+              ? dashboardInfo?.dashboard_title
+              : undefined,
+          },
+          abortController.signal,
+        );
 
         // Add AI suggestions to modifications
         const aiModifications: ExtendedWhatIfModification[] =
@@ -297,6 +318,10 @@ const WhatIfPanel = ({ onClose, topOffset }: 
WhatIfPanelProps) => {
 
         allModifications = [...allModifications, ...aiModifications];
       } catch (error) {
+        // Don't log or update state if the request was aborted
+        if (error instanceof Error && error.name === 'AbortError') {
+          return;
+        }
         logging.error('Failed to get AI suggestions:', error);
         // Continue with just user modification
       }
diff --git 
a/superset-frontend/src/dashboard/components/WhatIfDrawer/whatIfApi.ts 
b/superset-frontend/src/dashboard/components/WhatIfDrawer/whatIfApi.ts
index 64f51601cb..cc1b247d93 100644
--- a/superset-frontend/src/dashboard/components/WhatIfDrawer/whatIfApi.ts
+++ b/superset-frontend/src/dashboard/components/WhatIfDrawer/whatIfApi.ts
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-import { SupersetClient } from '@superset-ui/core';
+import { SupersetClient, Signal } from '@superset-ui/core';
 import {
   WhatIfInterpretRequest,
   WhatIfInterpretResponse,
@@ -42,9 +42,11 @@ interface ApiResponse {
 
 export async function fetchWhatIfInterpretation(
   request: WhatIfInterpretRequest,
+  signal?: Signal,
 ): Promise<WhatIfInterpretResponse> {
   const response = await SupersetClient.post({
     endpoint: '/api/v1/what_if/interpret',
+    signal,
     jsonPayload: {
       modifications: request.modifications.map(mod => ({
         column: mod.column,
@@ -102,9 +104,11 @@ interface ApiSuggestRelatedResponse {
 
 export async function fetchRelatedColumnSuggestions(
   request: WhatIfSuggestRelatedRequest,
+  signal?: Signal,
 ): Promise<WhatIfSuggestRelatedResponse> {
   const response = await SupersetClient.post({
     endpoint: '/api/v1/what_if/suggest_related',
+    signal,
     jsonPayload: {
       selected_column: request.selectedColumn,
       user_multiplier: request.userMultiplier,

Reply via email to