diff --git a/app/client/src/api/ActionAPI.tsx b/app/client/src/api/ActionAPI.tsx index 32599dc8c6..fcb347ee7e 100644 --- a/app/client/src/api/ActionAPI.tsx +++ b/app/client/src/api/ActionAPI.tsx @@ -1,6 +1,9 @@ import API, { HttpMethod } from "./Api"; import { ApiResponse, GenericApiResponse, ResponseMeta } from "./ApiResponses"; -import { APIRequest, EXECUTE_ACTION_TIMEOUT_MS } from "constants/ApiConstants"; +import { + APIRequest, + DEFAULT_EXECUTE_ACTION_TIMEOUT_MS, +} from "constants/ApiConstants"; import { AxiosPromise } from "axios"; import { Datasource } from "./DatasourcesApi"; import { PaginationType } from "pages/Editor/APIEditor/Pagination"; @@ -50,6 +53,7 @@ export interface APIConfigRequest { queryParameters: Property[]; paginationType: PaginationType; bodyFormData: BodyFormData[]; + timeoutInMillisecond: number; } export interface QueryConfig { @@ -197,9 +201,10 @@ class ActionAPI extends API { static executeAction( executeAction: ExecuteActionRequest, + timeout?: number, ): AxiosPromise { return API.post(ActionAPI.url + "/execute", executeAction, undefined, { - timeout: EXECUTE_ACTION_TIMEOUT_MS, + timeout: timeout || DEFAULT_EXECUTE_ACTION_TIMEOUT_MS, }); } diff --git a/app/client/src/api/DatasourcesApi.ts b/app/client/src/api/DatasourcesApi.ts index c07f40f031..8d0a062a45 100644 --- a/app/client/src/api/DatasourcesApi.ts +++ b/app/client/src/api/DatasourcesApi.ts @@ -1,7 +1,7 @@ import API from "./Api"; import { GenericApiResponse } from "./ApiResponses"; import { AxiosPromise } from "axios"; -import { EXECUTE_ACTION_TIMEOUT_MS } from "constants/ApiConstants"; +import { DEFAULT_TEST_DATA_SOURCE_TIMEOUT_MS } from "constants/ApiConstants"; interface DatasourceAuthentication { authType?: string; @@ -50,7 +50,7 @@ class DatasourcesApi extends API { static testDatasource(datasourceConfig: Partial): Promise<{}> { return API.post(`${DatasourcesApi.url}/test`, datasourceConfig, undefined, { - timeout: EXECUTE_ACTION_TIMEOUT_MS, + timeout: DEFAULT_TEST_DATA_SOURCE_TIMEOUT_MS, }); } diff --git a/app/client/src/constants/ActionConstants.tsx b/app/client/src/constants/ActionConstants.tsx index 01c191663a..4f9c229ffd 100644 --- a/app/client/src/constants/ActionConstants.tsx +++ b/app/client/src/constants/ActionConstants.tsx @@ -53,6 +53,7 @@ export interface PageAction { pluginType: ActionType; name: string; jsonPathKeys: string[]; + timeoutInMillisecond: number; } export interface ExecuteErrorPayload { diff --git a/app/client/src/constants/ApiConstants.tsx b/app/client/src/constants/ApiConstants.tsx index e6af2d8a83..ac4de7cce7 100644 --- a/app/client/src/constants/ApiConstants.tsx +++ b/app/client/src/constants/ApiConstants.tsx @@ -3,7 +3,8 @@ export type ContentType = | "application/x-www-form-urlencoded"; export const REQUEST_TIMEOUT_MS = 10000; -export const EXECUTE_ACTION_TIMEOUT_MS = 15000; +export const DEFAULT_EXECUTE_ACTION_TIMEOUT_MS = 15000; +export const DEFAULT_TEST_DATA_SOURCE_TIMEOUT_MS = 30000; export const API_REQUEST_HEADERS: APIHeaders = { "Content-Type": "application/json", diff --git a/app/client/src/sagas/ActionSagas.ts b/app/client/src/sagas/ActionSagas.ts index b2f543ff12..01b8d91c4c 100644 --- a/app/client/src/sagas/ActionSagas.ts +++ b/app/client/src/sagas/ActionSagas.ts @@ -83,6 +83,22 @@ export const getAction = ( return action ? action.config : undefined; }; +export const getActionTimeout = ( + state: AppState, + actionId: string, +): number | undefined => { + const action = _.find(state.entities.actions, a => a.config.id === actionId); + if (action) { + const timeout = action.config.actionConfiguration.timeoutInMillisecond; + if (timeout) { + // Extra timeout padding to account for network calls + return timeout + 5000; + } + return undefined; + } + return undefined; +}; + const createActionSuccessResponse = ( response: ActionApiResponse, ): ActionResponse => ({ @@ -185,8 +201,10 @@ export function* executeActionSaga( params, paginationField: pagination, }; + const timeout = yield select(getActionTimeout, actionId); const response: ActionApiResponse = yield ActionAPI.executeAction( executeActionRequest, + timeout, ); if (isErrorResponse(response)) { const payload = createActionErrorResponse(response); @@ -552,11 +570,15 @@ export function* runApiActionSaga( const { paginationField } = reduxAction.payload; const params = yield call(getActionParams, jsonPathKeys); - const response: ActionApiResponse = yield ActionAPI.executeAction({ - action, - params, - paginationField, - }); + const timeout = yield select(getActionTimeout, values.id); + const response: ActionApiResponse = yield ActionAPI.executeAction( + { + action, + params, + paginationField, + }, + timeout, + ); let payload = createActionSuccessResponse(response); if (response.responseMeta && response.responseMeta.error) { payload = createActionErrorResponse(response); @@ -597,6 +619,7 @@ function* executePageLoadAction(pageAction: PageAction) { }; const response: ActionApiResponse = yield ActionAPI.executeAction( executeActionRequest, + pageAction.timeoutInMillisecond, ); if (isErrorResponse(response)) { diff --git a/app/client/src/sagas/QueryPaneSagas.ts b/app/client/src/sagas/QueryPaneSagas.ts index 0020cc4768..61c19a3dd4 100644 --- a/app/client/src/sagas/QueryPaneSagas.ts +++ b/app/client/src/sagas/QueryPaneSagas.ts @@ -27,7 +27,7 @@ import { getCurrentPageId, } from "selectors/editorSelectors"; import { initialize } from "redux-form"; -import { getAction, getActionParams } from "./ActionSagas"; +import { getAction, getActionParams, getActionTimeout } from "./ActionSagas"; import { AppState } from "reducers"; import ActionAPI, { RestAction, @@ -214,11 +214,15 @@ export function* executeQuerySaga( const { paginationField } = actionPayload.payload; const params = yield call(getActionParams, jsonPathKeys); - const response: ActionApiResponse = yield ActionAPI.executeAction({ - action, - params, - paginationField, - }); + const timeout = yield select(getActionTimeout, values.id); + const response: ActionApiResponse = yield ActionAPI.executeAction( + { + action, + params, + paginationField, + }, + timeout, + ); if (response.responseMeta && response.responseMeta.error) { throw response.responseMeta.error;