2022-02-18 06:58:36 +00:00
|
|
|
import { all, call, put, select, take, takeLatest } from "redux-saga/effects";
|
2021-08-27 09:25:28 +00:00
|
|
|
import {
|
|
|
|
|
executePluginActionError,
|
|
|
|
|
executePluginActionRequest,
|
|
|
|
|
executePluginActionSuccess,
|
|
|
|
|
runAction,
|
|
|
|
|
updateAction,
|
|
|
|
|
} from "actions/pluginActionActions";
|
|
|
|
|
import {
|
|
|
|
|
ApplicationPayload,
|
|
|
|
|
ReduxAction,
|
|
|
|
|
ReduxActionErrorTypes,
|
|
|
|
|
ReduxActionTypes,
|
|
|
|
|
} from "constants/ReduxActionConstants";
|
|
|
|
|
import ActionAPI, {
|
|
|
|
|
ActionExecutionResponse,
|
|
|
|
|
ActionResponse,
|
|
|
|
|
ExecuteActionRequest,
|
|
|
|
|
PaginationField,
|
|
|
|
|
} from "api/ActionAPI";
|
|
|
|
|
import {
|
|
|
|
|
getAction,
|
|
|
|
|
getCurrentPageNameByActionId,
|
|
|
|
|
isActionDirty,
|
|
|
|
|
isActionSaving,
|
2022-03-17 12:05:17 +00:00
|
|
|
getJSCollection,
|
2021-08-27 09:25:28 +00:00
|
|
|
} from "selectors/entitiesSelector";
|
|
|
|
|
import {
|
|
|
|
|
getAppMode,
|
|
|
|
|
getCurrentApplication,
|
|
|
|
|
} from "selectors/applicationSelectors";
|
2022-03-16 14:29:52 +00:00
|
|
|
import _, { get, isString, set } from "lodash";
|
2021-08-27 09:25:28 +00:00
|
|
|
import AppsmithConsole from "utils/AppsmithConsole";
|
|
|
|
|
import { ENTITY_TYPE, PLATFORM_ERROR } from "entities/AppsmithConsole";
|
|
|
|
|
import { validateResponse } from "sagas/ErrorSagas";
|
|
|
|
|
import AnalyticsUtil, { EventName } from "utils/AnalyticsUtil";
|
|
|
|
|
import { Action, PluginType } from "entities/Action";
|
|
|
|
|
import LOG_TYPE from "entities/AppsmithConsole/logtype";
|
|
|
|
|
import { Toaster } from "components/ads/Toast";
|
|
|
|
|
import {
|
|
|
|
|
createMessage,
|
|
|
|
|
ERROR_ACTION_EXECUTE_FAIL,
|
|
|
|
|
ERROR_FAIL_ON_PAGE_LOAD_ACTIONS,
|
|
|
|
|
ERROR_PLUGIN_ACTION_EXECUTE,
|
2022-02-11 18:08:46 +00:00
|
|
|
} from "@appsmith/constants/messages";
|
2021-08-27 09:25:28 +00:00
|
|
|
import { Variant } from "components/ads/common";
|
|
|
|
|
import {
|
|
|
|
|
EventType,
|
|
|
|
|
PageAction,
|
|
|
|
|
RESP_HEADER_DATATYPE,
|
|
|
|
|
} from "constants/AppsmithActionConstants/ActionConstants";
|
|
|
|
|
import {
|
|
|
|
|
getCurrentPageId,
|
2021-12-24 13:59:02 +00:00
|
|
|
getIsSavingEntity,
|
2021-08-27 09:25:28 +00:00
|
|
|
getLayoutOnLoadActions,
|
|
|
|
|
} from "selectors/editorSelectors";
|
|
|
|
|
import PerformanceTracker, {
|
|
|
|
|
PerformanceTransactionName,
|
|
|
|
|
} from "utils/PerformanceTracker";
|
|
|
|
|
import * as log from "loglevel";
|
|
|
|
|
import { EMPTY_RESPONSE } from "components/editorComponents/ApiResponseView";
|
|
|
|
|
import { AppState } from "reducers";
|
2022-01-07 06:08:17 +00:00
|
|
|
import { DEFAULT_EXECUTE_ACTION_TIMEOUT_MS } from "@appsmith/constants/ApiConstants";
|
2021-08-27 09:25:28 +00:00
|
|
|
import { evaluateActionBindings } from "sagas/EvaluationsSaga";
|
2022-03-02 16:01:50 +00:00
|
|
|
import { isBlobUrl, parseBlobUrl } from "utils/AppsmithUtils";
|
2021-08-27 09:25:28 +00:00
|
|
|
import { getType, Types } from "utils/TypeHelpers";
|
|
|
|
|
import { matchPath } from "react-router";
|
|
|
|
|
import {
|
2022-03-25 10:43:26 +00:00
|
|
|
API_EDITOR_BASE_PATH,
|
|
|
|
|
API_EDITOR_ID_PATH,
|
|
|
|
|
API_EDITOR_PATH_WITH_SELECTED_PAGE_ID,
|
|
|
|
|
INTEGRATION_EDITOR_PATH,
|
|
|
|
|
QUERIES_EDITOR_BASE_PATH,
|
|
|
|
|
QUERIES_EDITOR_ID_PATH,
|
2022-02-25 09:31:06 +00:00
|
|
|
CURL_IMPORT_PAGE_PATH,
|
2021-08-27 09:25:28 +00:00
|
|
|
} from "constants/routes";
|
2022-03-25 10:43:26 +00:00
|
|
|
import { SAAS_EDITOR_API_ID_PATH } from "pages/Editor/SaaSEditor/constants";
|
2021-12-23 14:17:20 +00:00
|
|
|
import {
|
|
|
|
|
ActionTriggerType,
|
|
|
|
|
RunPluginActionDescription,
|
|
|
|
|
} from "entities/DataTree/actionTriggers";
|
2021-08-27 09:25:28 +00:00
|
|
|
import { APP_MODE } from "entities/App";
|
2021-09-09 15:10:22 +00:00
|
|
|
import { FileDataTypes } from "widgets/constants";
|
2021-08-27 09:25:28 +00:00
|
|
|
import { hideDebuggerErrors } from "actions/debuggerActions";
|
2021-09-23 07:21:57 +00:00
|
|
|
import {
|
2021-12-23 14:17:20 +00:00
|
|
|
ActionValidationError,
|
|
|
|
|
getErrorAsString,
|
2021-09-23 07:21:57 +00:00
|
|
|
PluginActionExecutionError,
|
2021-12-23 14:17:20 +00:00
|
|
|
PluginTriggerFailureError,
|
2021-09-23 07:21:57 +00:00
|
|
|
UserCancelledActionExecutionError,
|
|
|
|
|
} from "sagas/ActionExecution/errorUtils";
|
2021-10-18 14:03:44 +00:00
|
|
|
import { trimQueryString } from "utils/helpers";
|
2022-03-17 12:05:17 +00:00
|
|
|
import { JSCollection } from "entities/JSCollection";
|
2021-12-23 14:17:20 +00:00
|
|
|
import {
|
|
|
|
|
executeAppAction,
|
|
|
|
|
TriggerMeta,
|
|
|
|
|
} from "sagas/ActionExecution/ActionExecutionSagas";
|
2022-02-18 06:58:36 +00:00
|
|
|
import { requestModalConfirmationSaga } from "sagas/UtilSagas";
|
2022-02-28 17:37:21 +00:00
|
|
|
import { ModalType } from "reducers/uiReducers/modalActionReducer";
|
2022-02-25 09:31:06 +00:00
|
|
|
import { getFormNames, getFormValues } from "redux-form";
|
|
|
|
|
import { CURL_IMPORT_FORM } from "constants/forms";
|
|
|
|
|
import { submitCurlImportForm } from "actions/importActions";
|
2022-03-25 10:43:26 +00:00
|
|
|
import { getBasePath } from "pages/Editor/Explorer/helpers";
|
2022-03-16 14:29:52 +00:00
|
|
|
import { isTrueObject } from "workers/evaluationUtils";
|
2022-04-06 07:22:18 +00:00
|
|
|
import { handleExecuteJSFunctionSaga } from "sagas/JSPaneSagas";
|
2021-08-27 09:25:28 +00:00
|
|
|
enum ActionResponseDataTypes {
|
|
|
|
|
BINARY = "BINARY",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const getActionTimeout = (
|
|
|
|
|
state: AppState,
|
|
|
|
|
actionId: string,
|
|
|
|
|
): number | undefined => {
|
|
|
|
|
const action = _.find(
|
|
|
|
|
state.entities.actions,
|
|
|
|
|
(a) => a.config.id === actionId,
|
|
|
|
|
);
|
|
|
|
|
if (action) {
|
|
|
|
|
const timeout = _.get(
|
|
|
|
|
action,
|
|
|
|
|
"config.actionConfiguration.timeoutInMillisecond",
|
|
|
|
|
DEFAULT_EXECUTE_ACTION_TIMEOUT_MS,
|
|
|
|
|
);
|
|
|
|
|
if (timeout) {
|
|
|
|
|
// Extra timeout padding to account for network calls
|
|
|
|
|
return timeout + 5000;
|
|
|
|
|
}
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
return undefined;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const createActionExecutionResponse = (
|
|
|
|
|
response: ActionExecutionResponse,
|
|
|
|
|
): ActionResponse => {
|
|
|
|
|
const payload = response.data;
|
|
|
|
|
if (payload.statusCode === "200 OK" && payload.hasOwnProperty("headers")) {
|
|
|
|
|
const respHeaders = payload.headers;
|
|
|
|
|
if (
|
|
|
|
|
respHeaders.hasOwnProperty(RESP_HEADER_DATATYPE) &&
|
|
|
|
|
respHeaders[RESP_HEADER_DATATYPE].length > 0 &&
|
|
|
|
|
respHeaders[RESP_HEADER_DATATYPE][0] === ActionResponseDataTypes.BINARY &&
|
|
|
|
|
getType(payload.body) === Types.STRING
|
|
|
|
|
) {
|
|
|
|
|
// Decoding from base64 to handle the binary files because direct
|
|
|
|
|
// conversion of binary files to string causes corruption in the final output
|
|
|
|
|
// this is to only handle the download of binary files
|
|
|
|
|
payload.body = atob(payload.body as string);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
...payload,
|
|
|
|
|
...response.clientMeta,
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
const isErrorResponse = (response: ActionExecutionResponse) => {
|
|
|
|
|
return !response.data.isExecutionSuccess;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* @param blobUrl string A blob url with type added a query param
|
|
|
|
|
* @returns promise that resolves to file content
|
|
|
|
|
*/
|
|
|
|
|
function* readBlob(blobUrl: string): any {
|
|
|
|
|
const [url, fileType] = parseBlobUrl(blobUrl);
|
|
|
|
|
const file = yield fetch(url).then((r) => r.blob());
|
|
|
|
|
|
2021-09-03 11:02:28 +00:00
|
|
|
return yield new Promise((resolve) => {
|
2021-08-27 09:25:28 +00:00
|
|
|
const reader = new FileReader();
|
|
|
|
|
if (fileType === FileDataTypes.Base64) {
|
|
|
|
|
reader.readAsDataURL(file);
|
|
|
|
|
} else if (fileType === FileDataTypes.Binary) {
|
|
|
|
|
reader.readAsBinaryString(file);
|
|
|
|
|
} else {
|
|
|
|
|
reader.readAsText(file);
|
|
|
|
|
}
|
|
|
|
|
reader.onloadend = () => {
|
|
|
|
|
resolve(reader.result);
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Api1
|
|
|
|
|
* URL: https://example.com/{{Text1.text}}
|
|
|
|
|
* Body: {
|
|
|
|
|
* "name": "{{this.params.name}}",
|
|
|
|
|
* "age": {{this.params.age}},
|
|
|
|
|
* "gender": {{Dropdown1.selectedOptionValue}}
|
|
|
|
|
* }
|
|
|
|
|
*
|
|
|
|
|
* If you call
|
|
|
|
|
* Api1.run(undefined, undefined, { name: "Hetu", age: Input1.text });
|
|
|
|
|
*
|
|
|
|
|
* executionParams is { name: "Hetu", age: Input1.text }
|
|
|
|
|
* bindings is [
|
|
|
|
|
* "Text1.text",
|
|
|
|
|
* "Dropdown1.selectedOptionValue",
|
|
|
|
|
* "this.params.name",
|
|
|
|
|
* "this.params.age",
|
|
|
|
|
* ]
|
|
|
|
|
*
|
|
|
|
|
* Return will be [
|
|
|
|
|
* { key: "Text1.text", value: "updateUser" },
|
|
|
|
|
* { key: "Dropdown1.selectedOptionValue", value: "M" },
|
|
|
|
|
* { key: "this.params.name", value: "Hetu" },
|
|
|
|
|
* { key: "this.params.age", value: 26 },
|
|
|
|
|
* ]
|
|
|
|
|
* @param bindings
|
|
|
|
|
* @param executionParams
|
|
|
|
|
*/
|
|
|
|
|
function* evaluateActionParams(
|
|
|
|
|
bindings: string[] | undefined,
|
2022-03-02 16:01:50 +00:00
|
|
|
formData: FormData,
|
2021-08-27 09:25:28 +00:00
|
|
|
executionParams?: Record<string, any> | string,
|
|
|
|
|
) {
|
|
|
|
|
if (_.isNil(bindings) || bindings.length === 0) return [];
|
|
|
|
|
|
|
|
|
|
// Evaluated all bindings of the actions. Pass executionParams if any
|
|
|
|
|
const values: any = yield call(
|
|
|
|
|
evaluateActionBindings,
|
|
|
|
|
bindings,
|
|
|
|
|
executionParams,
|
|
|
|
|
);
|
|
|
|
|
|
2022-03-02 16:01:50 +00:00
|
|
|
// Add keys values to formData for the multipart submission
|
2021-08-27 09:25:28 +00:00
|
|
|
for (let i = 0; i < bindings.length; i++) {
|
|
|
|
|
const key = bindings[i];
|
|
|
|
|
let value = values[i];
|
2022-03-16 14:29:52 +00:00
|
|
|
|
|
|
|
|
if (isTrueObject(value)) {
|
|
|
|
|
const blobUrlPaths: string[] = [];
|
|
|
|
|
Object.keys(value).forEach((propertyName) => {
|
|
|
|
|
if (isBlobUrl(value[propertyName])) {
|
|
|
|
|
blobUrlPaths.push(propertyName);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
for (const blobUrlPath of blobUrlPaths) {
|
|
|
|
|
const blobUrl = value[blobUrlPath] as string;
|
|
|
|
|
const resolvedBlobValue = yield call(readBlob, blobUrl);
|
|
|
|
|
set(value, blobUrlPath, resolvedBlobValue);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-30 13:10:05 +00:00
|
|
|
if (typeof value === "object") {
|
|
|
|
|
value = JSON.stringify(value);
|
|
|
|
|
}
|
2021-08-27 09:25:28 +00:00
|
|
|
if (isBlobUrl(value)) {
|
|
|
|
|
value = yield call(readBlob, value);
|
|
|
|
|
}
|
2022-03-30 13:10:05 +00:00
|
|
|
value = new Blob([value], { type: "text/plain" });
|
2022-03-02 16:01:50 +00:00
|
|
|
|
|
|
|
|
formData.append(encodeURIComponent(key), value);
|
2021-08-27 09:25:28 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default function* executePluginActionTriggerSaga(
|
|
|
|
|
pluginAction: RunPluginActionDescription["payload"],
|
|
|
|
|
eventType: EventType,
|
2021-12-23 14:17:20 +00:00
|
|
|
triggerMeta: TriggerMeta,
|
2021-08-27 09:25:28 +00:00
|
|
|
) {
|
2021-12-23 14:17:20 +00:00
|
|
|
const { actionId, onError, onSuccess, params } = pluginAction;
|
|
|
|
|
if (getType(params) !== Types.OBJECT) {
|
|
|
|
|
throw new ActionValidationError(
|
|
|
|
|
ActionTriggerType.RUN_PLUGIN_ACTION,
|
|
|
|
|
"params",
|
|
|
|
|
Types.OBJECT,
|
|
|
|
|
getType(params),
|
|
|
|
|
);
|
|
|
|
|
}
|
2021-08-27 09:25:28 +00:00
|
|
|
PerformanceTracker.startAsyncTracking(
|
|
|
|
|
PerformanceTransactionName.EXECUTE_ACTION,
|
|
|
|
|
{
|
|
|
|
|
actionId: actionId,
|
|
|
|
|
},
|
|
|
|
|
actionId,
|
|
|
|
|
);
|
|
|
|
|
const appMode = yield select(getAppMode);
|
|
|
|
|
const action: Action = yield select(getAction, actionId);
|
|
|
|
|
const currentApp: ApplicationPayload = yield select(getCurrentApplication);
|
|
|
|
|
AnalyticsUtil.logEvent("EXECUTE_ACTION", {
|
|
|
|
|
type: action.pluginType,
|
|
|
|
|
name: action.name,
|
|
|
|
|
pageId: action.pageId,
|
|
|
|
|
appId: currentApp.id,
|
|
|
|
|
appMode: appMode,
|
|
|
|
|
appName: currentApp.name,
|
|
|
|
|
isExampleApp: currentApp.appIsExample,
|
|
|
|
|
});
|
|
|
|
|
const pagination =
|
|
|
|
|
eventType === EventType.ON_NEXT_PAGE
|
|
|
|
|
? "NEXT"
|
|
|
|
|
: eventType === EventType.ON_PREV_PAGE
|
|
|
|
|
? "PREV"
|
|
|
|
|
: undefined;
|
|
|
|
|
AppsmithConsole.info({
|
|
|
|
|
text: "Execution started from widget request",
|
|
|
|
|
source: {
|
|
|
|
|
type: ENTITY_TYPE.ACTION,
|
|
|
|
|
name: action.name,
|
|
|
|
|
id: actionId,
|
|
|
|
|
},
|
|
|
|
|
state: action.actionConfiguration,
|
|
|
|
|
});
|
2021-09-23 07:21:57 +00:00
|
|
|
const executePluginActionResponse: ExecutePluginActionResponse = yield call(
|
|
|
|
|
executePluginActionSaga,
|
|
|
|
|
action.id,
|
|
|
|
|
pagination,
|
|
|
|
|
params,
|
|
|
|
|
);
|
|
|
|
|
const { isError, payload } = executePluginActionResponse;
|
2021-08-27 09:25:28 +00:00
|
|
|
|
|
|
|
|
if (isError) {
|
|
|
|
|
AppsmithConsole.addError({
|
|
|
|
|
id: actionId,
|
|
|
|
|
logType: LOG_TYPE.ACTION_EXECUTION_ERROR,
|
|
|
|
|
text: `Execution failed with status ${payload.statusCode}`,
|
|
|
|
|
source: {
|
|
|
|
|
type: ENTITY_TYPE.ACTION,
|
|
|
|
|
name: action.name,
|
|
|
|
|
id: actionId,
|
|
|
|
|
},
|
|
|
|
|
state: payload.request,
|
|
|
|
|
messages: [
|
|
|
|
|
{
|
2021-12-23 14:17:20 +00:00
|
|
|
// Need to stringify cause this gets rendered directly
|
|
|
|
|
// and rendering objects can crash the app
|
|
|
|
|
message: !isString(payload.body)
|
|
|
|
|
? JSON.stringify(payload.body)
|
|
|
|
|
: payload.body,
|
2021-08-27 09:25:28 +00:00
|
|
|
type: PLATFORM_ERROR.PLUGIN_EXECUTION,
|
2021-09-01 10:15:45 +00:00
|
|
|
subType: payload.errorType,
|
2021-08-27 09:25:28 +00:00
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
});
|
2021-12-23 14:17:20 +00:00
|
|
|
if (onError) {
|
|
|
|
|
yield call(executeAppAction, {
|
|
|
|
|
event: { type: eventType },
|
|
|
|
|
dynamicString: onError,
|
2022-03-02 06:37:20 +00:00
|
|
|
callbackData: [payload.body, params],
|
2021-12-23 14:17:20 +00:00
|
|
|
...triggerMeta,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
throw new PluginTriggerFailureError(
|
|
|
|
|
createMessage(ERROR_PLUGIN_ACTION_EXECUTE, action.name),
|
|
|
|
|
[payload.body, params],
|
|
|
|
|
);
|
|
|
|
|
}
|
2021-08-27 09:25:28 +00:00
|
|
|
} else {
|
|
|
|
|
AppsmithConsole.info({
|
|
|
|
|
logType: LOG_TYPE.ACTION_EXECUTION_SUCCESS,
|
|
|
|
|
text: "Executed successfully from widget request",
|
|
|
|
|
timeTaken: payload.duration,
|
|
|
|
|
source: {
|
|
|
|
|
type: ENTITY_TYPE.ACTION,
|
|
|
|
|
name: action.name,
|
|
|
|
|
id: actionId,
|
|
|
|
|
},
|
|
|
|
|
state: {
|
|
|
|
|
response: payload.body,
|
|
|
|
|
request: payload.request,
|
|
|
|
|
},
|
|
|
|
|
});
|
2021-12-23 14:17:20 +00:00
|
|
|
if (onSuccess) {
|
|
|
|
|
yield call(executeAppAction, {
|
|
|
|
|
event: { type: eventType },
|
|
|
|
|
dynamicString: onSuccess,
|
2022-03-02 06:37:20 +00:00
|
|
|
callbackData: [payload.body, params],
|
2021-12-23 14:17:20 +00:00
|
|
|
...triggerMeta,
|
|
|
|
|
});
|
|
|
|
|
}
|
2021-08-27 09:25:28 +00:00
|
|
|
}
|
2021-09-03 11:02:28 +00:00
|
|
|
return [payload.body, params];
|
2021-08-27 09:25:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function* runActionShortcutSaga() {
|
|
|
|
|
const location = window.location.pathname;
|
2022-03-25 10:43:26 +00:00
|
|
|
const basePath = getBasePath();
|
2021-08-27 09:25:28 +00:00
|
|
|
const match: any = matchPath(location, {
|
|
|
|
|
path: [
|
2022-03-25 10:43:26 +00:00
|
|
|
trimQueryString(`${basePath}${API_EDITOR_BASE_PATH}`),
|
|
|
|
|
trimQueryString(`${basePath}${API_EDITOR_ID_PATH}`),
|
|
|
|
|
trimQueryString(`${basePath}${QUERIES_EDITOR_BASE_PATH}`),
|
|
|
|
|
trimQueryString(`${basePath}${QUERIES_EDITOR_ID_PATH}`),
|
|
|
|
|
trimQueryString(`${basePath}${API_EDITOR_PATH_WITH_SELECTED_PAGE_ID}`),
|
|
|
|
|
trimQueryString(`${basePath}${INTEGRATION_EDITOR_PATH}`),
|
|
|
|
|
trimQueryString(`${basePath}${SAAS_EDITOR_API_ID_PATH}`),
|
|
|
|
|
`${basePath}${CURL_IMPORT_PAGE_PATH}`,
|
2021-08-27 09:25:28 +00:00
|
|
|
],
|
|
|
|
|
exact: true,
|
|
|
|
|
strict: false,
|
|
|
|
|
});
|
2022-02-25 09:31:06 +00:00
|
|
|
|
|
|
|
|
// get the current form name
|
|
|
|
|
const currentFormNames = yield select(getFormNames());
|
|
|
|
|
|
2021-08-27 09:25:28 +00:00
|
|
|
if (!match || !match.params) return;
|
|
|
|
|
const { apiId, pageId, queryId } = match.params;
|
|
|
|
|
const actionId = apiId || queryId;
|
2022-02-25 09:31:06 +00:00
|
|
|
if (actionId) {
|
|
|
|
|
const trackerId = apiId
|
|
|
|
|
? PerformanceTransactionName.RUN_API_SHORTCUT
|
|
|
|
|
: PerformanceTransactionName.RUN_QUERY_SHORTCUT;
|
|
|
|
|
PerformanceTracker.startTracking(trackerId, {
|
|
|
|
|
actionId,
|
|
|
|
|
pageId,
|
|
|
|
|
});
|
|
|
|
|
AnalyticsUtil.logEvent(trackerId as EventName, {
|
|
|
|
|
actionId,
|
|
|
|
|
});
|
|
|
|
|
yield put(runAction(actionId));
|
|
|
|
|
} else if (
|
|
|
|
|
!!currentFormNames &&
|
|
|
|
|
currentFormNames.includes(CURL_IMPORT_FORM) &&
|
|
|
|
|
!actionId
|
|
|
|
|
) {
|
|
|
|
|
// if the current form names include the curl form and there are no actionIds i.e. its not an api or query
|
|
|
|
|
// get the form values and call the submit curl import form function with its data
|
|
|
|
|
const formValues = yield select(getFormValues(CURL_IMPORT_FORM));
|
|
|
|
|
|
|
|
|
|
// if the user has not edited the curl input field, assign an empty string to it, so it doesnt throw an error.
|
|
|
|
|
if (!formValues?.curl) formValues["curl"] = "";
|
|
|
|
|
|
|
|
|
|
yield put(submitCurlImportForm(formValues));
|
|
|
|
|
} else {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-08-27 09:25:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function* runActionSaga(
|
|
|
|
|
reduxAction: ReduxAction<{
|
|
|
|
|
id: string;
|
|
|
|
|
paginationField: PaginationField;
|
|
|
|
|
}>,
|
|
|
|
|
) {
|
|
|
|
|
const actionId = reduxAction.payload.id;
|
|
|
|
|
const isSaving = yield select(isActionSaving(actionId));
|
|
|
|
|
const isDirty = yield select(isActionDirty(actionId));
|
2021-12-24 13:59:02 +00:00
|
|
|
const isSavingEntity = yield select(getIsSavingEntity);
|
|
|
|
|
if (isSaving || isDirty || isSavingEntity) {
|
2021-08-27 09:25:28 +00:00
|
|
|
if (isDirty && !isSaving) {
|
|
|
|
|
yield put(updateAction({ id: actionId }));
|
|
|
|
|
}
|
|
|
|
|
yield take(ReduxActionTypes.UPDATE_ACTION_SUCCESS);
|
|
|
|
|
}
|
|
|
|
|
const actionObject = yield select(getAction, actionId);
|
|
|
|
|
const datasourceUrl = get(
|
|
|
|
|
actionObject,
|
|
|
|
|
"datasource.datasourceConfiguration.url",
|
|
|
|
|
);
|
|
|
|
|
AppsmithConsole.info({
|
|
|
|
|
text: "Execution started from user request",
|
|
|
|
|
source: {
|
|
|
|
|
type: ENTITY_TYPE.ACTION,
|
|
|
|
|
name: actionObject.name,
|
|
|
|
|
id: actionId,
|
|
|
|
|
},
|
|
|
|
|
state: {
|
|
|
|
|
...actionObject.actionConfiguration,
|
|
|
|
|
...(datasourceUrl && {
|
|
|
|
|
url: datasourceUrl,
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const { id, paginationField } = reduxAction.payload;
|
|
|
|
|
|
|
|
|
|
let payload = EMPTY_RESPONSE;
|
|
|
|
|
let isError = true;
|
|
|
|
|
let error = "";
|
|
|
|
|
try {
|
|
|
|
|
const executePluginActionResponse: ExecutePluginActionResponse = yield call(
|
|
|
|
|
executePluginActionSaga,
|
|
|
|
|
id,
|
|
|
|
|
paginationField,
|
|
|
|
|
);
|
|
|
|
|
payload = executePluginActionResponse.payload;
|
|
|
|
|
isError = executePluginActionResponse.isError;
|
|
|
|
|
} catch (e) {
|
2021-09-23 07:21:57 +00:00
|
|
|
// When running from the pane, we just want to end the saga if the user has
|
|
|
|
|
// cancelled the call. No need to log any errors
|
2021-08-27 09:25:28 +00:00
|
|
|
if (e instanceof UserCancelledActionExecutionError) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
log.error(e);
|
|
|
|
|
error = e.message;
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-14 11:04:43 +00:00
|
|
|
// Error should be readable error if present.
|
2021-12-23 14:17:20 +00:00
|
|
|
// Otherwise, payload's body.
|
2021-10-14 11:04:43 +00:00
|
|
|
// Default to "An unexpected error occurred" if none is available
|
|
|
|
|
|
|
|
|
|
const readableError = payload.readableError
|
|
|
|
|
? getErrorAsString(payload.readableError)
|
|
|
|
|
: undefined;
|
|
|
|
|
|
|
|
|
|
const payloadBodyError = payload.body
|
|
|
|
|
? getErrorAsString(payload.body)
|
|
|
|
|
: undefined;
|
|
|
|
|
|
|
|
|
|
const defaultError = "An unexpected error occurred";
|
|
|
|
|
|
2021-08-27 09:25:28 +00:00
|
|
|
if (isError) {
|
2021-10-14 11:04:43 +00:00
|
|
|
error = readableError || payloadBodyError || defaultError;
|
|
|
|
|
|
|
|
|
|
// In case of debugger, both the current error message
|
|
|
|
|
// and the readableError needs to be present,
|
|
|
|
|
// since the readableError may be malformed for certain errors.
|
|
|
|
|
|
|
|
|
|
const appsmithConsoleErrorMessageList = [
|
|
|
|
|
{
|
|
|
|
|
message: error,
|
|
|
|
|
type: PLATFORM_ERROR.PLUGIN_EXECUTION,
|
|
|
|
|
subType: payload.errorType,
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
if (error === readableError && !!payloadBodyError) {
|
|
|
|
|
appsmithConsoleErrorMessageList.push({
|
|
|
|
|
message: payloadBodyError,
|
|
|
|
|
type: PLATFORM_ERROR.PLUGIN_EXECUTION,
|
|
|
|
|
subType: payload.errorType,
|
|
|
|
|
});
|
2021-08-27 09:25:28 +00:00
|
|
|
}
|
2021-10-14 11:04:43 +00:00
|
|
|
|
2021-08-27 09:25:28 +00:00
|
|
|
AppsmithConsole.addError({
|
|
|
|
|
id: actionId,
|
|
|
|
|
logType: LOG_TYPE.ACTION_EXECUTION_ERROR,
|
|
|
|
|
text: `Execution failed${
|
|
|
|
|
payload.statusCode ? ` with status ${payload.statusCode}` : ""
|
|
|
|
|
}`,
|
|
|
|
|
source: {
|
|
|
|
|
type: ENTITY_TYPE.ACTION,
|
|
|
|
|
name: actionObject.name,
|
|
|
|
|
id: actionId,
|
|
|
|
|
},
|
2021-10-14 11:04:43 +00:00
|
|
|
messages: appsmithConsoleErrorMessageList,
|
2021-08-27 09:25:28 +00:00
|
|
|
state: payload.request,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Toaster.show({
|
|
|
|
|
text: createMessage(ERROR_ACTION_EXECUTE_FAIL, actionObject.name),
|
|
|
|
|
variant: Variant.danger,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
yield put({
|
|
|
|
|
type: ReduxActionErrorTypes.RUN_ACTION_ERROR,
|
2022-03-02 15:52:35 +00:00
|
|
|
payload: {
|
|
|
|
|
error: appsmithConsoleErrorMessageList[0],
|
|
|
|
|
id: reduxAction.payload.id,
|
|
|
|
|
},
|
2021-08-27 09:25:28 +00:00
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const pageName = yield select(getCurrentPageNameByActionId, actionId);
|
|
|
|
|
let eventName: EventName = "RUN_API";
|
|
|
|
|
if (actionObject.pluginType === PluginType.DB) {
|
|
|
|
|
eventName = "RUN_QUERY";
|
|
|
|
|
}
|
|
|
|
|
if (actionObject.pluginType === PluginType.SAAS) {
|
|
|
|
|
eventName = "RUN_SAAS_API";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AnalyticsUtil.logEvent(eventName, {
|
|
|
|
|
actionId,
|
|
|
|
|
actionName: actionObject.name,
|
|
|
|
|
pageName: pageName,
|
|
|
|
|
responseTime: payload.duration,
|
|
|
|
|
apiType: "INTERNAL",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
yield put({
|
|
|
|
|
type: ReduxActionTypes.RUN_ACTION_SUCCESS,
|
|
|
|
|
payload: { [actionId]: payload },
|
|
|
|
|
});
|
|
|
|
|
if (payload.isExecutionSuccess) {
|
|
|
|
|
AppsmithConsole.info({
|
|
|
|
|
logType: LOG_TYPE.ACTION_EXECUTION_SUCCESS,
|
|
|
|
|
text: "Executed successfully from user request",
|
|
|
|
|
timeTaken: payload.duration,
|
|
|
|
|
source: {
|
|
|
|
|
type: ENTITY_TYPE.ACTION,
|
|
|
|
|
name: actionObject.name,
|
|
|
|
|
id: actionId,
|
|
|
|
|
},
|
|
|
|
|
state: {
|
|
|
|
|
response: payload.body,
|
|
|
|
|
request: payload.request,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-17 12:05:17 +00:00
|
|
|
function* executeOnPageLoadJSAction(pageAction: PageAction) {
|
|
|
|
|
const collectionId = pageAction.collectionId;
|
|
|
|
|
if (collectionId) {
|
|
|
|
|
const collection: JSCollection = yield select(
|
|
|
|
|
getJSCollection,
|
|
|
|
|
collectionId,
|
2021-08-27 09:25:28 +00:00
|
|
|
);
|
2022-03-17 12:05:17 +00:00
|
|
|
const jsAction = collection.actions.find((d) => d.id === pageAction.id);
|
|
|
|
|
if (!!jsAction) {
|
2022-04-06 07:22:18 +00:00
|
|
|
if (jsAction.confirmBeforeExecute) {
|
|
|
|
|
const modalPayload = {
|
|
|
|
|
name: pageAction.name,
|
|
|
|
|
modalOpen: true,
|
|
|
|
|
modalType: ModalType.RUN_ACTION,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const confirmed = yield call(
|
|
|
|
|
requestModalConfirmationSaga,
|
|
|
|
|
modalPayload,
|
|
|
|
|
);
|
|
|
|
|
if (!confirmed) {
|
|
|
|
|
yield put({
|
|
|
|
|
type: ReduxActionTypes.RUN_ACTION_CANCELLED,
|
|
|
|
|
payload: { id: pageAction.id },
|
|
|
|
|
});
|
|
|
|
|
throw new UserCancelledActionExecutionError();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const data = {
|
|
|
|
|
collectionName: collection.name,
|
|
|
|
|
action: jsAction,
|
|
|
|
|
collectionId: collectionId,
|
|
|
|
|
};
|
|
|
|
|
yield call(handleExecuteJSFunctionSaga, data);
|
2022-03-17 12:05:17 +00:00
|
|
|
}
|
2021-08-27 09:25:28 +00:00
|
|
|
}
|
2022-03-17 12:05:17 +00:00
|
|
|
}
|
2021-08-27 09:25:28 +00:00
|
|
|
|
2022-03-17 12:05:17 +00:00
|
|
|
function* executePageLoadAction(pageAction: PageAction) {
|
|
|
|
|
if (pageAction.hasOwnProperty("collectionId")) {
|
|
|
|
|
yield call(executeOnPageLoadJSAction, pageAction);
|
|
|
|
|
} else {
|
|
|
|
|
const pageId = yield select(getCurrentPageId);
|
|
|
|
|
let currentApp: ApplicationPayload = yield select(getCurrentApplication);
|
|
|
|
|
currentApp = currentApp || {};
|
|
|
|
|
const appMode = yield select(getAppMode);
|
|
|
|
|
AnalyticsUtil.logEvent("EXECUTE_ACTION", {
|
|
|
|
|
type: pageAction.pluginType,
|
|
|
|
|
name: pageAction.name,
|
|
|
|
|
pageId: pageId,
|
|
|
|
|
appMode: appMode,
|
|
|
|
|
appId: currentApp.id,
|
|
|
|
|
onPageLoad: true,
|
|
|
|
|
appName: currentApp.name,
|
|
|
|
|
isExampleApp: currentApp.appIsExample,
|
2021-08-27 09:25:28 +00:00
|
|
|
});
|
|
|
|
|
|
2022-03-17 12:05:17 +00:00
|
|
|
let payload = EMPTY_RESPONSE;
|
|
|
|
|
let isError = true;
|
|
|
|
|
const error = `The action "${pageAction.name}" has failed.`;
|
|
|
|
|
try {
|
|
|
|
|
const executePluginActionResponse: ExecutePluginActionResponse = yield call(
|
|
|
|
|
executePluginActionSaga,
|
|
|
|
|
pageAction,
|
|
|
|
|
);
|
|
|
|
|
payload = executePluginActionResponse.payload;
|
|
|
|
|
isError = executePluginActionResponse.isError;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
log.error(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isError) {
|
|
|
|
|
AppsmithConsole.addError({
|
2021-08-27 09:25:28 +00:00
|
|
|
id: pageAction.id,
|
2022-03-17 12:05:17 +00:00
|
|
|
logType: LOG_TYPE.ACTION_EXECUTION_ERROR,
|
|
|
|
|
text: `Execution failed with status ${payload.statusCode}`,
|
|
|
|
|
source: {
|
|
|
|
|
type: ENTITY_TYPE.ACTION,
|
|
|
|
|
name: pageAction.name,
|
|
|
|
|
id: pageAction.id,
|
|
|
|
|
},
|
|
|
|
|
state: payload.request,
|
|
|
|
|
messages: [
|
|
|
|
|
{
|
|
|
|
|
message: error,
|
|
|
|
|
type: PLATFORM_ERROR.PLUGIN_EXECUTION,
|
|
|
|
|
subType: payload.errorType,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
yield put(
|
|
|
|
|
executePluginActionError({
|
|
|
|
|
actionId: pageAction.id,
|
|
|
|
|
isPageLoad: true,
|
|
|
|
|
error: { message: error },
|
|
|
|
|
data: payload,
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
PerformanceTracker.stopAsyncTracking(
|
|
|
|
|
PerformanceTransactionName.EXECUTE_ACTION,
|
|
|
|
|
{
|
|
|
|
|
failed: true,
|
|
|
|
|
},
|
|
|
|
|
pageAction.id,
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
PerformanceTracker.stopAsyncTracking(
|
|
|
|
|
PerformanceTransactionName.EXECUTE_ACTION,
|
|
|
|
|
undefined,
|
|
|
|
|
pageAction.id,
|
|
|
|
|
);
|
|
|
|
|
yield put(
|
|
|
|
|
executePluginActionSuccess({
|
|
|
|
|
id: pageAction.id,
|
|
|
|
|
response: payload,
|
|
|
|
|
isPageLoad: true,
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
yield take(ReduxActionTypes.SET_EVALUATED_TREE);
|
|
|
|
|
}
|
2021-08-27 09:25:28 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function* executePageLoadActionsSaga() {
|
|
|
|
|
try {
|
|
|
|
|
const pageActions: PageAction[][] = yield select(getLayoutOnLoadActions);
|
|
|
|
|
const actionCount = _.flatten(pageActions).length;
|
|
|
|
|
PerformanceTracker.startAsyncTracking(
|
|
|
|
|
PerformanceTransactionName.EXECUTE_PAGE_LOAD_ACTIONS,
|
|
|
|
|
{ numActions: actionCount },
|
|
|
|
|
);
|
|
|
|
|
for (const actionSet of pageActions) {
|
|
|
|
|
// Load all sets in parallel
|
|
|
|
|
yield* yield all(
|
|
|
|
|
actionSet.map((apiAction) => call(executePageLoadAction, apiAction)),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
PerformanceTracker.stopAsyncTracking(
|
|
|
|
|
PerformanceTransactionName.EXECUTE_PAGE_LOAD_ACTIONS,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// We show errors in the debugger once onPageLoad actions
|
|
|
|
|
// are executed
|
|
|
|
|
yield put(hideDebuggerErrors(false));
|
|
|
|
|
} catch (e) {
|
|
|
|
|
log.error(e);
|
|
|
|
|
|
|
|
|
|
Toaster.show({
|
|
|
|
|
text: createMessage(ERROR_FAIL_ON_PAGE_LOAD_ACTIONS),
|
|
|
|
|
variant: Variant.danger,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type ExecutePluginActionResponse = {
|
|
|
|
|
payload: ActionResponse;
|
|
|
|
|
isError: boolean;
|
|
|
|
|
};
|
|
|
|
|
/*
|
|
|
|
|
* This saga handles the complete plugin action execution flow. It will respond with a
|
|
|
|
|
* payload and isError property which indicates if the response is of an error type.
|
|
|
|
|
* In case of the execution was not completed, it will throw errors of type
|
|
|
|
|
* PluginActionExecutionError which needs to be handled by any saga that calls this.
|
|
|
|
|
* */
|
|
|
|
|
function* executePluginActionSaga(
|
|
|
|
|
actionOrActionId: PageAction | string,
|
|
|
|
|
paginationField?: PaginationField,
|
|
|
|
|
params?: Record<string, unknown>,
|
|
|
|
|
) {
|
|
|
|
|
let pluginAction;
|
|
|
|
|
let actionId;
|
|
|
|
|
if (isString(actionOrActionId)) {
|
|
|
|
|
pluginAction = yield select(getAction, actionOrActionId);
|
|
|
|
|
actionId = actionOrActionId;
|
|
|
|
|
} else {
|
|
|
|
|
pluginAction = actionOrActionId;
|
|
|
|
|
actionId = actionOrActionId.id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pluginAction.confirmBeforeExecute) {
|
2022-02-28 17:37:21 +00:00
|
|
|
const modalPayload = {
|
|
|
|
|
name: pluginAction.name,
|
|
|
|
|
modalOpen: true,
|
|
|
|
|
modalType: ModalType.RUN_ACTION,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const confirmed = yield call(requestModalConfirmationSaga, modalPayload);
|
|
|
|
|
|
2021-08-27 09:25:28 +00:00
|
|
|
if (!confirmed) {
|
2021-11-01 04:54:06 +00:00
|
|
|
yield put({
|
|
|
|
|
type: ReduxActionTypes.RUN_ACTION_CANCELLED,
|
|
|
|
|
payload: { id: actionId },
|
|
|
|
|
});
|
2021-08-27 09:25:28 +00:00
|
|
|
throw new UserCancelledActionExecutionError();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
PerformanceTracker.startAsyncTracking(
|
|
|
|
|
PerformanceTransactionName.EXECUTE_ACTION,
|
|
|
|
|
{
|
|
|
|
|
actionId: actionId,
|
|
|
|
|
},
|
|
|
|
|
actionId,
|
|
|
|
|
);
|
|
|
|
|
yield put(executePluginActionRequest({ id: actionId }));
|
|
|
|
|
|
|
|
|
|
const appMode = yield select(getAppMode);
|
|
|
|
|
const timeout = yield select(getActionTimeout, actionId);
|
|
|
|
|
|
|
|
|
|
const executeActionRequest: ExecuteActionRequest = {
|
|
|
|
|
actionId: actionId,
|
|
|
|
|
viewMode: appMode === APP_MODE.PUBLISHED,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (paginationField) {
|
|
|
|
|
executeActionRequest.paginationField = paginationField;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-02 16:01:50 +00:00
|
|
|
const formData = new FormData();
|
|
|
|
|
formData.append("executeActionDTO", JSON.stringify(executeActionRequest));
|
|
|
|
|
yield call(evaluateActionParams, pluginAction.jsonPathKeys, formData, params);
|
|
|
|
|
|
2021-08-27 09:25:28 +00:00
|
|
|
const response: ActionExecutionResponse = yield ActionAPI.executeAction(
|
2022-03-02 16:01:50 +00:00
|
|
|
formData,
|
2021-08-27 09:25:28 +00:00
|
|
|
timeout,
|
|
|
|
|
);
|
|
|
|
|
PerformanceTracker.stopAsyncTracking(
|
|
|
|
|
PerformanceTransactionName.EXECUTE_ACTION,
|
|
|
|
|
);
|
|
|
|
|
try {
|
|
|
|
|
yield validateResponse(response);
|
|
|
|
|
const payload = createActionExecutionResponse(response);
|
|
|
|
|
yield put(
|
|
|
|
|
executePluginActionSuccess({
|
|
|
|
|
id: actionId,
|
|
|
|
|
response: payload,
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
return {
|
|
|
|
|
payload,
|
|
|
|
|
isError: isErrorResponse(response),
|
|
|
|
|
};
|
|
|
|
|
} catch (e) {
|
|
|
|
|
yield put(
|
|
|
|
|
executePluginActionSuccess({
|
|
|
|
|
id: actionId,
|
|
|
|
|
response: EMPTY_RESPONSE,
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
throw new PluginActionExecutionError("Response not valid", false, response);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function* watchPluginActionExecutionSagas() {
|
|
|
|
|
yield all([
|
|
|
|
|
takeLatest(ReduxActionTypes.RUN_ACTION_REQUEST, runActionSaga),
|
|
|
|
|
takeLatest(
|
|
|
|
|
ReduxActionTypes.RUN_ACTION_SHORTCUT_REQUEST,
|
|
|
|
|
runActionShortcutSaga,
|
|
|
|
|
),
|
|
|
|
|
takeLatest(
|
|
|
|
|
ReduxActionTypes.EXECUTE_PAGE_LOAD_ACTIONS,
|
|
|
|
|
executePageLoadActionsSaga,
|
|
|
|
|
),
|
|
|
|
|
]);
|
|
|
|
|
}
|