fix: bump client side chart timeouts to use the SUPERSET_WEBSERVER_TIMEOUT (#28018)

(cherry picked from commit 99c414e4dad9ad608d41af04de0c6f7f53758960)
diff --git a/superset-frontend/src/SqlLab/fixtures.ts b/superset-frontend/src/SqlLab/fixtures.ts
index 845e220..742145c 100644
--- a/superset-frontend/src/SqlLab/fixtures.ts
+++ b/superset-frontend/src/SqlLab/fixtures.ts
@@ -679,6 +679,7 @@
       DISPLAY_MAX_ROW: 100,
       SQLALCHEMY_DOCS_URL: 'test_SQLALCHEMY_DOCS_URL',
       SQLALCHEMY_DISPLAY_TEXT: 'test_SQLALCHEMY_DISPLAY_TEXT',
+      SUPERSET_WEBSERVER_TIMEOUT: '300',
     },
   },
 };
diff --git a/superset-frontend/src/components/Chart/chartAction.js b/superset-frontend/src/components/Chart/chartAction.js
index 9597a3c..eb51021 100644
--- a/superset-frontend/src/components/Chart/chartAction.js
+++ b/superset-frontend/src/components/Chart/chartAction.js
@@ -248,17 +248,20 @@
 
 export function runAnnotationQuery({
   annotation,
-  timeout = 60,
+  timeout,
   formData = null,
   key,
   isDashboardRequest = false,
   force = false,
 }) {
   return function (dispatch, getState) {
-    const sliceKey = key || Object.keys(getState().charts)[0];
+    const { charts, common } = getState();
+    const sliceKey = key || Object.keys(charts)[0];
+    const queryTimeout = timeout || common.conf.SUPERSET_WEBSERVER_TIMEOUT;
+
     // make a copy of formData, not modifying original formData
     const fd = {
-      ...(formData || getState().charts[sliceKey].latestQueryFormData),
+      ...(formData || charts[sliceKey].latestQueryFormData),
     };
 
     if (!annotation.sourceType) {
@@ -309,7 +312,7 @@
     return SupersetClient.post({
       url,
       signal,
-      timeout: timeout * 1000,
+      timeout: queryTimeout * 1000,
       headers: { 'Content-Type': 'application/json' },
       jsonPayload: buildV1ChartDataPayload({
         formData: fd,
@@ -396,18 +399,20 @@
 export function exploreJSON(
   formData,
   force = false,
-  timeout = 60,
+  timeout,
   key,
   dashboardId,
   ownState,
 ) {
-  return async dispatch => {
+  return async (dispatch, getState) => {
     const logStart = Logger.getTimestamp();
     const controller = new AbortController();
+    const queryTimeout =
+      timeout || getState().common.conf.SUPERSET_WEBSERVER_TIMEOUT;
 
     const requestParams = {
       signal: controller.signal,
-      timeout: timeout * 1000,
+      timeout: queryTimeout * 1000,
     };
     if (dashboardId) requestParams.dashboard_id = dashboardId;
 
@@ -519,7 +524,7 @@
 export function postChartFormData(
   formData,
   force = false,
-  timeout = 60,
+  timeout,
   key,
   dashboardId,
   ownState,
diff --git a/superset-frontend/src/components/Chart/chartActions.test.js b/superset-frontend/src/components/Chart/chartActions.test.js
index c2a58e6..129c17d 100644
--- a/superset-frontend/src/components/Chart/chartActions.test.js
+++ b/superset-frontend/src/components/Chart/chartActions.test.js
@@ -28,6 +28,27 @@
 import * as asyncEvent from 'src/middleware/asyncEvent';
 import { handleChartDataResponse } from 'src/components/Chart/chartAction';
 
+import configureMockStore from 'redux-mock-store';
+import thunk from 'redux-thunk';
+import { initialState } from 'src/SqlLab/fixtures';
+
+const middlewares = [thunk];
+const mockStore = configureMockStore(middlewares);
+
+const mockGetState = () => ({
+  charts: {
+    chartKey: {
+      latestQueryFormData: {
+        time_grain_sqla: 'P1D',
+        granularity_sqla: 'Date',
+      },
+    },
+  },
+  common: {
+    conf: {},
+  },
+});
+
 describe('chart actions', () => {
   const MOCK_URL = '/mockURL';
   let dispatch;
@@ -94,7 +115,7 @@
 
     it('should query with the built query', async () => {
       const actionThunk = actions.postChartFormData({}, null);
-      await actionThunk(dispatch);
+      await actionThunk(dispatch, mockGetState);
 
       expect(fetchMock.calls(MOCK_URL)).toHaveLength(1);
       expect(fetchMock.calls(MOCK_URL)[0][1].body).toBe(
@@ -165,7 +186,7 @@
     it('should dispatch CHART_UPDATE_STARTED action before the query', () => {
       const actionThunk = actions.postChartFormData({});
 
-      return actionThunk(dispatch).then(() => {
+      return actionThunk(dispatch, mockGetState).then(() => {
         // chart update, trigger query, update form data, success
         expect(dispatch.callCount).toBe(5);
         expect(fetchMock.calls(MOCK_URL)).toHaveLength(1);
@@ -175,7 +196,7 @@
 
     it('should dispatch TRIGGER_QUERY action with the query', () => {
       const actionThunk = actions.postChartFormData({});
-      return actionThunk(dispatch).then(() => {
+      return actionThunk(dispatch, mockGetState).then(() => {
         // chart update, trigger query, update form data, success
         expect(dispatch.callCount).toBe(5);
         expect(fetchMock.calls(MOCK_URL)).toHaveLength(1);
@@ -185,7 +206,7 @@
 
     it('should dispatch UPDATE_QUERY_FORM_DATA action with the query', () => {
       const actionThunk = actions.postChartFormData({});
-      return actionThunk(dispatch).then(() => {
+      return actionThunk(dispatch, mockGetState).then(() => {
         // chart update, trigger query, update form data, success
         expect(dispatch.callCount).toBe(5);
         expect(fetchMock.calls(MOCK_URL)).toHaveLength(1);
@@ -195,7 +216,7 @@
 
     it('should dispatch logEvent async action', () => {
       const actionThunk = actions.postChartFormData({});
-      return actionThunk(dispatch).then(() => {
+      return actionThunk(dispatch, mockGetState).then(() => {
         // chart update, trigger query, update form data, success
         expect(dispatch.callCount).toBe(5);
         expect(fetchMock.calls(MOCK_URL)).toHaveLength(1);
@@ -209,7 +230,7 @@
 
     it('should dispatch CHART_UPDATE_SUCCEEDED action upon success', () => {
       const actionThunk = actions.postChartFormData({});
-      return actionThunk(dispatch).then(() => {
+      return actionThunk(dispatch, mockGetState).then(() => {
         // chart update, trigger query, update form data, success
         expect(dispatch.callCount).toBe(5);
         expect(fetchMock.calls(MOCK_URL)).toHaveLength(1);
@@ -226,7 +247,7 @@
       const timeoutInSec = 1 / 1000;
       const actionThunk = actions.postChartFormData({}, false, timeoutInSec);
 
-      return actionThunk(dispatch).then(() => {
+      return actionThunk(dispatch, mockGetState).then(() => {
         // chart update, trigger query, update form data, fail
         expect(fetchMock.calls(MOCK_URL)).toHaveLength(1);
         expect(dispatch.callCount).toBe(5);
@@ -245,7 +266,7 @@
       const timeoutInSec = 100; // Set to a time that is longer than the time this will take to fail
       const actionThunk = actions.postChartFormData({}, false, timeoutInSec);
 
-      return actionThunk(dispatch).then(() => {
+      return actionThunk(dispatch, mockGetState).then(() => {
         // chart update, trigger query, update form data, fail
         expect(dispatch.callCount).toBe(5);
         const updateFailedAction = dispatch.args[4][0];
@@ -278,17 +299,6 @@
 
   describe('runAnnotationQuery', () => {
     const mockDispatch = jest.fn();
-    const mockGetState = () => ({
-      charts: {
-        chartKey: {
-          latestQueryFormData: {
-            time_grain_sqla: 'P1D',
-            granularity_sqla: 'Date',
-          },
-        },
-      },
-    });
-
     beforeEach(() => {
       jest.clearAllMocks();
     });
@@ -342,3 +352,72 @@
     });
   });
 });
+
+describe('chart actions timeout', () => {
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('should use the timeout from arguments when given', () => {
+    const postSpy = jest.spyOn(SupersetClient, 'post');
+    postSpy.mockImplementation(() => Promise.resolve({ json: { result: [] } }));
+    const timeout = 10; // Set the timeout value here
+    const formData = { datasource: 'table__1' }; // Set the formData here
+    const key = 'chartKey'; // Set the chart key here
+
+    const store = mockStore(initialState);
+    store.dispatch(
+      actions.runAnnotationQuery({
+        annotation: {
+          value: 'annotationValue',
+          sourceType: 'Event',
+          overrides: {},
+        },
+        timeout,
+        formData,
+        key,
+      }),
+    );
+
+    const expectedPayload = {
+      url: expect.any(String),
+      signal: expect.any(AbortSignal),
+      timeout: timeout * 1000,
+      headers: { 'Content-Type': 'application/json' },
+      jsonPayload: expect.any(Object),
+    };
+
+    expect(postSpy).toHaveBeenCalledWith(expectedPayload);
+  });
+
+  it('should use the timeout from common.conf when not passed as an argument', () => {
+    const postSpy = jest.spyOn(SupersetClient, 'post');
+    postSpy.mockImplementation(() => Promise.resolve({ json: { result: [] } }));
+    const formData = { datasource: 'table__1' }; // Set the formData here
+    const key = 'chartKey'; // Set the chart key here
+
+    const store = mockStore(initialState);
+    store.dispatch(
+      actions.runAnnotationQuery({
+        annotation: {
+          value: 'annotationValue',
+          sourceType: 'Event',
+          overrides: {},
+        },
+        undefined,
+        formData,
+        key,
+      }),
+    );
+
+    const expectedPayload = {
+      url: expect.any(String),
+      signal: expect.any(AbortSignal),
+      timeout: initialState.common.conf.SUPERSET_WEBSERVER_TIMEOUT * 1000,
+      headers: { 'Content-Type': 'application/json' },
+      jsonPayload: expect.any(Object),
+    };
+
+    expect(postSpy).toHaveBeenCalledWith(expectedPayload);
+  });
+});