PromucFlow_constructor/app/client/src/sagas/ActionSagas.ts

883 lines
25 KiB
TypeScript
Raw Normal View History

2019-10-21 15:12:45 +00:00
import {
ReduxAction,
ReduxActionErrorTypes,
ReduxActionTypes,
2019-11-25 05:07:27 +00:00
} from "constants/ReduxActionConstants";
import {
all,
call,
put,
2020-02-18 10:41:52 +00:00
select,
takeEvery,
takeLatest,
} from "redux-saga/effects";
2019-11-28 03:56:44 +00:00
import {
2020-02-18 10:41:52 +00:00
EventType,
ExecuteActionPayload,
2020-03-06 09:45:21 +00:00
ExecuteActionPayloadEvent,
2019-11-28 03:56:44 +00:00
PageAction,
} from "constants/ActionConstants";
2019-10-21 15:12:45 +00:00
import ActionAPI, {
2019-11-11 11:42:52 +00:00
ActionApiResponse,
2019-10-21 15:12:45 +00:00
ActionCreateUpdateResponse,
2019-11-12 09:43:13 +00:00
ActionResponse,
2019-10-21 15:12:45 +00:00
ExecuteActionRequest,
2020-02-18 10:41:52 +00:00
PaginationField,
2019-11-20 10:57:05 +00:00
Property,
2019-11-25 05:07:27 +00:00
} from "api/ActionAPI";
2020-01-30 13:23:04 +00:00
import { AppState } from "reducers";
2019-10-21 15:12:45 +00:00
import _ from "lodash";
2019-11-25 05:07:27 +00:00
import { mapToPropList } from "utils/AppsmithUtils";
2020-02-03 12:19:10 +00:00
import { AppToaster } from "components/editorComponents/ToastComponent";
2019-11-25 05:07:27 +00:00
import { GenericApiResponse } from "api/ApiResponses";
2020-06-16 10:23:19 +00:00
import PageApi from "api/PageApi";
import { updateCanvasWithDSL } from "sagas/PageSagas";
import {
2020-01-24 09:54:40 +00:00
copyActionError,
copyActionSuccess,
createActionSuccess,
deleteActionSuccess,
2020-02-18 10:41:52 +00:00
executeApiActionRequest,
executeApiActionSuccess,
2020-03-12 20:27:39 +00:00
fetchActionsForPageSuccess,
FetchActionsPayload,
2020-01-24 09:54:40 +00:00
moveActionError,
moveActionSuccess,
updateActionSuccess,
2020-06-16 10:23:19 +00:00
updateApiNameDraft,
fetchActionsForPage,
2019-11-25 05:07:27 +00:00
} from "actions/actionActions";
2019-11-20 10:57:05 +00:00
import {
getDynamicBindings,
getDynamicValue,
2019-11-20 10:57:05 +00:00
isDynamicValue,
2020-01-27 13:53:33 +00:00
removeBindingsFromObject,
2019-11-25 05:07:27 +00:00
} from "utils/DynamicBindingUtils";
2019-11-13 07:34:59 +00:00
import { validateResponse } from "./ErrorSagas";
2019-11-20 10:57:05 +00:00
import { getFormData } from "selectors/formSelectors";
2019-11-25 05:07:27 +00:00
import { API_EDITOR_FORM_NAME } from "constants/forms";
2020-01-30 13:23:04 +00:00
import { executeAction, executeActionError } from "actions/widgetActions";
import { evaluateDataTree } from "selectors/dataTreeSelectors";
2019-12-23 12:12:58 +00:00
import { transformRestAction } from "transformers/RestActionTransformer";
2020-02-18 10:41:52 +00:00
import {
ActionDescription,
RunActionPayload,
} from "entities/DataTree/dataTreeFactory";
import {
getCurrentApplicationId,
getPageList,
2020-06-16 10:23:19 +00:00
getCurrentPageId,
2020-02-18 10:41:52 +00:00
} from "selectors/editorSelectors";
import history from "utils/history";
import {
BUILDER_PAGE_URL,
getApplicationViewerPageURL,
} from "constants/routes";
import { ToastType } from "react-toastify";
2020-03-06 04:59:24 +00:00
import AnalyticsUtil from "utils/AnalyticsUtil";
2020-03-23 12:40:17 +00:00
import * as log from "loglevel";
import { QUERY_CONSTANT } from "constants/QueryEditorConstants";
2020-06-04 13:49:22 +00:00
import { RestAction } from "entities/Action";
2020-06-16 10:23:19 +00:00
import { validateEntityName } from "components/editorComponents/EntityNameComponent";
import { ActionData } from "reducers/entityReducers/actionsReducer";
import { getActions } from "selectors/entitiesSelector";
2019-10-21 15:12:45 +00:00
2019-11-25 09:15:11 +00:00
export const getAction = (
2019-11-05 05:09:50 +00:00
state: AppState,
actionId: string,
): RestAction | undefined => {
2020-01-30 13:23:04 +00:00
const action = _.find(state.entities.actions, a => a.config.id === actionId);
return action ? action.config : undefined;
2019-10-21 15:12:45 +00:00
};
2020-05-07 08:07:29 +00:00
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;
};
2020-03-12 20:27:39 +00:00
const createActionSuccessResponse = (
response: ActionApiResponse,
): ActionResponse => ({
2019-11-12 09:43:13 +00:00
...response.data,
...response.clientMeta,
});
2020-03-12 20:27:39 +00:00
const isErrorResponse = (response: ActionApiResponse) => {
return (
(response.responseMeta && response.responseMeta.error) ||
!response.data.isExecutionSuccess
2020-03-12 20:27:39 +00:00
);
};
2020-03-06 04:59:24 +00:00
function getCurrentPageNameByActionId(
state: AppState,
actionId: string,
): string {
const action = state.entities.actions.find(action => {
return action.config.id === actionId;
});
const pageId = action ? action.config.pageId : "";
return getPageNameByPageId(state, pageId);
}
function getPageNameByPageId(state: AppState, pageId: string): string {
const page = state.entities.pageList.pages.find(
page => page.pageId === pageId,
);
2020-03-12 20:27:39 +00:00
return page ? page.pageName : "";
2020-03-06 04:59:24 +00:00
}
2019-11-12 09:43:13 +00:00
const createActionErrorResponse = (
response: ActionApiResponse,
): ActionResponse => ({
body: response.responseMeta.error || { error: "Error" },
statusCode: response.responseMeta.error
2020-03-12 20:27:39 +00:00
? response.responseMeta.error.code.toString()
2019-11-12 09:43:13 +00:00
: "Error",
headers: {},
requestHeaders: {},
requestBody: null,
2019-11-12 09:43:13 +00:00
duration: "0",
size: "0",
});
2019-12-02 09:50:25 +00:00
export function* evaluateDynamicBoundValueSaga(path: string): any {
2020-03-23 12:40:17 +00:00
log.debug("Evaluating data tree to get action binding value");
const tree = yield select(evaluateDataTree);
2020-02-18 10:41:52 +00:00
const dynamicResult = getDynamicValue(`{{${path}}}`, tree);
return dynamicResult.result;
2019-10-21 15:12:45 +00:00
}
2019-11-20 10:57:05 +00:00
export function* getActionParams(jsonPathKeys: string[] | undefined) {
if (_.isNil(jsonPathKeys)) return [];
2020-03-18 03:05:06 +00:00
const values: any = yield all(
jsonPathKeys.map((jsonPath: string) => {
return call(evaluateDynamicBoundValueSaga, jsonPath);
}),
2019-11-20 10:57:05 +00:00
);
const dynamicBindings: Record<string, string> = {};
jsonPathKeys.forEach((key, i) => {
let value = values[i];
if (typeof value === "object") value = JSON.stringify(value);
dynamicBindings[key] = value;
2019-11-20 10:57:05 +00:00
});
return mapToPropList(dynamicBindings);
}
2020-02-18 10:41:52 +00:00
// function* executeJSActionSaga(jsAction: ExecuteJSActionPayload) {
// const tree = yield select(getParsedDataTree);
// const result = JSExecutionManagerSingleton.evaluateSync(
// jsAction.jsFunction,
// tree,
// );
//
// yield put({
// type: ReduxActionTypes.SAVE_JS_EXECUTION_RECORD,
// payload: {
// [jsAction.jsFunctionId]: result,
// },
// });
// }
2019-11-28 03:56:44 +00:00
2020-03-12 20:27:39 +00:00
export function* executeActionSaga(
2020-02-18 10:41:52 +00:00
apiAction: RunActionPayload,
2020-03-06 09:45:21 +00:00
event: ExecuteActionPayloadEvent,
2020-02-07 02:32:52 +00:00
) {
2020-02-18 10:41:52 +00:00
const { actionId, onSuccess, onError } = apiAction;
2019-11-13 07:34:59 +00:00
try {
2020-02-18 10:41:52 +00:00
yield put(executeApiActionRequest({ id: apiAction.actionId }));
const api: RestAction = yield select(getAction, actionId);
2019-11-20 10:57:05 +00:00
const params: Property[] = yield call(getActionParams, api.jsonPathKeys);
2020-02-18 10:41:52 +00:00
const pagination =
2020-03-06 09:45:21 +00:00
event.type === EventType.ON_NEXT_PAGE
2020-02-18 10:41:52 +00:00
? "NEXT"
2020-03-06 09:45:21 +00:00
: event.type === EventType.ON_PREV_PAGE
2020-02-18 10:41:52 +00:00
? "PREV"
: undefined;
2019-11-13 07:34:59 +00:00
const executeActionRequest: ExecuteActionRequest = {
2020-02-18 10:41:52 +00:00
action: { id: actionId },
2019-11-20 10:57:05 +00:00
params,
2020-02-18 10:41:52 +00:00
paginationField: pagination,
2019-11-13 07:34:59 +00:00
};
2020-05-07 08:07:29 +00:00
const timeout = yield select(getActionTimeout, actionId);
2019-11-13 07:34:59 +00:00
const response: ActionApiResponse = yield ActionAPI.executeAction(
executeActionRequest,
2020-05-07 08:07:29 +00:00
timeout,
2019-10-21 15:12:45 +00:00
);
2020-03-12 20:27:39 +00:00
if (isErrorResponse(response)) {
const payload = createActionErrorResponse(response);
2020-02-18 10:41:52 +00:00
if (onError) {
yield put(
executeAction({
dynamicString: onError,
event: {
2020-03-06 09:45:21 +00:00
...event,
2020-02-18 10:41:52 +00:00
type: EventType.ON_ERROR,
},
responseData: payload,
}),
);
2020-03-06 09:45:21 +00:00
} else {
if (event.callback) {
event.callback({ success: false });
}
2019-11-13 07:34:59 +00:00
}
2020-01-30 13:23:04 +00:00
yield put(
executeActionError({
2020-02-18 10:41:52 +00:00
actionId,
2020-01-30 13:23:04 +00:00
error: response.responseMeta.error,
}),
);
2019-11-13 07:34:59 +00:00
} else {
2020-03-12 20:27:39 +00:00
const payload = createActionSuccessResponse(response);
2020-02-18 10:41:52 +00:00
yield put(
2020-03-06 09:45:21 +00:00
executeApiActionSuccess({
id: apiAction.actionId,
response: payload,
}),
2020-02-18 10:41:52 +00:00
);
if (onSuccess) {
yield put(
executeAction({
dynamicString: onSuccess,
event: {
2020-03-06 09:45:21 +00:00
...event,
2020-02-18 10:41:52 +00:00
type: EventType.ON_SUCCESS,
},
responseData: payload,
}),
);
2020-03-06 09:45:21 +00:00
} else {
if (event.callback) {
event.callback({ success: true });
}
2019-11-13 07:34:59 +00:00
}
}
2019-11-13 07:34:59 +00:00
return response;
} catch (error) {
2020-01-30 13:23:04 +00:00
yield put(
executeActionError({
2020-02-18 10:41:52 +00:00
actionId: actionId,
2020-01-30 13:23:04 +00:00
error,
}),
);
2020-02-18 10:41:52 +00:00
if (onError) {
yield put(
executeAction({
dynamicString: `{{${onError}}}`,
event: {
2020-03-06 09:45:21 +00:00
...event,
2020-02-18 10:41:52 +00:00
type: EventType.ON_ERROR,
},
responseData: {},
}),
);
2020-03-06 09:45:21 +00:00
} else {
if (event.callback) {
event.callback({ success: false });
}
2020-02-18 10:41:52 +00:00
}
2019-11-06 06:35:15 +00:00
}
2019-10-21 15:12:45 +00:00
}
2020-03-06 09:45:21 +00:00
function* navigateActionSaga(
2020-04-20 05:42:46 +00:00
action: { pageNameOrUrl: string; params: Record<string, string> },
2020-03-06 09:45:21 +00:00
event: ExecuteActionPayloadEvent,
) {
2020-02-18 10:41:52 +00:00
const pageList = yield select(getPageList);
const applicationId = yield select(getCurrentApplicationId);
2020-04-20 05:42:46 +00:00
const page = _.find(pageList, { pageName: action.pageNameOrUrl });
2020-02-18 10:41:52 +00:00
if (page) {
2020-04-20 05:42:46 +00:00
AnalyticsUtil.logEvent("NAVIGATE", {
pageName: action.pageNameOrUrl,
pageParams: action.params,
});
2020-02-18 10:41:52 +00:00
// TODO need to make this check via RENDER_MODE;
2020-04-01 08:09:57 +00:00
const path =
history.location.pathname.indexOf("/edit") !== -1
? BUILDER_PAGE_URL(applicationId, page.pageId, action.params)
: getApplicationViewerPageURL(
applicationId,
page.pageId,
action.params,
);
2020-02-18 10:41:52 +00:00
history.push(path);
2020-03-06 09:45:21 +00:00
if (event.callback) event.callback({ success: true });
} else {
2020-04-20 05:42:46 +00:00
AnalyticsUtil.logEvent("NAVIGATE", {
navUrl: action.pageNameOrUrl,
});
// Add a default protocol if it doesn't exist.
let url = action.pageNameOrUrl;
if (url.indexOf("://") === -1) {
url = "https://" + url;
}
window.location.assign(url);
}
}
2020-02-18 10:41:52 +00:00
export function* executeActionTriggers(
trigger: ActionDescription<any>,
2020-03-06 09:45:21 +00:00
event: ExecuteActionPayloadEvent,
2020-02-18 10:41:52 +00:00
) {
2020-05-05 08:23:22 +00:00
try {
switch (trigger.type) {
case "RUN_ACTION":
yield call(executeActionSaga, trigger.payload, event);
break;
case "NAVIGATE_TO":
yield call(navigateActionSaga, trigger.payload, event);
break;
case "SHOW_ALERT":
AppToaster.show({
message: trigger.payload.message,
type: trigger.payload.style,
});
if (event.callback) event.callback({ success: true });
break;
case "SHOW_MODAL_BY_NAME":
yield put(trigger);
if (event.callback) event.callback({ success: true });
break;
case "CLOSE_MODAL":
yield put(trigger);
if (event.callback) event.callback({ success: true });
break;
default:
yield put(
executeActionError({
error: "Trigger type unknown",
actionId: "",
}),
);
}
} catch (e) {
yield put(
executeActionError({
error: "Failed to execute action",
actionId: "",
}),
);
if (event.callback) event.callback({ success: false });
2020-02-18 10:41:52 +00:00
}
}
2020-02-18 10:41:52 +00:00
export function* executeAppAction(action: ReduxAction<ExecuteActionPayload>) {
const { dynamicString, event, responseData } = action.payload;
2020-03-23 12:40:17 +00:00
log.debug("Evaluating data tree to get action trigger");
2020-06-10 12:16:50 +00:00
log.debug({ dynamicString });
const tree = yield select(evaluateDataTree);
2020-06-10 12:16:50 +00:00
log.debug({ tree });
2020-02-18 10:41:52 +00:00
const { triggers } = getDynamicValue(dynamicString, tree, responseData, true);
2020-06-10 12:16:50 +00:00
log.debug({ triggers });
2020-03-17 15:57:35 +00:00
if (triggers && triggers.length) {
2020-02-18 10:41:52 +00:00
yield all(
2020-03-06 09:45:21 +00:00
triggers.map(trigger => call(executeActionTriggers, trigger, event)),
2020-01-30 13:23:04 +00:00
);
2020-03-17 15:57:35 +00:00
} else {
if (event.callback) event.callback({ success: true });
2019-10-21 15:12:45 +00:00
}
}
export function* createActionSaga(actionPayload: ReduxAction<RestAction>) {
2019-11-13 07:34:59 +00:00
try {
const response: ActionCreateUpdateResponse = yield ActionAPI.createAPI(
actionPayload.payload,
);
const isValidResponse = yield validateResponse(response);
if (isValidResponse) {
AppToaster.show({
message: `${actionPayload.payload.name} Action created`,
2020-02-18 10:41:52 +00:00
type: ToastType.SUCCESS,
2019-11-13 07:34:59 +00:00
});
2020-03-06 04:59:24 +00:00
const pageName = yield select(
getCurrentPageNameByActionId,
response.data.id,
);
AnalyticsUtil.logEvent("CREATE_API", {
apiId: response.data.id,
apiName: response.data.name,
pageName: pageName,
});
2019-11-13 07:34:59 +00:00
yield put(createActionSuccess(response.data));
}
} catch (error) {
yield put({
type: ReduxActionErrorTypes.CREATE_ACTION_ERROR,
2020-01-24 09:54:40 +00:00
payload: actionPayload.payload,
2019-10-21 15:12:45 +00:00
});
}
}
export function* fetchActionsSaga(action: ReduxAction<FetchActionsPayload>) {
2019-11-13 07:34:59 +00:00
try {
2020-01-24 09:54:40 +00:00
const { applicationId } = action.payload;
const response: GenericApiResponse<RestAction[]> = yield ActionAPI.fetchActions(
2020-01-24 09:54:40 +00:00
applicationId,
);
2019-11-13 07:34:59 +00:00
const isValidResponse = yield validateResponse(response);
if (isValidResponse) {
yield put({
type: ReduxActionTypes.FETCH_ACTIONS_SUCCESS,
payload: response.data,
});
}
} catch (error) {
2019-10-21 15:12:45 +00:00
yield put({
type: ReduxActionErrorTypes.FETCH_ACTIONS_ERROR,
2019-11-13 07:34:59 +00:00
payload: { error },
2019-10-21 15:12:45 +00:00
});
}
}
2020-02-21 12:16:49 +00:00
export function* fetchActionsForPageSaga(
action: ReduxAction<{ pageId: string }>,
) {
try {
const { pageId } = action.payload;
const response: GenericApiResponse<RestAction[]> = yield call(
ActionAPI.fetchActionsByPageId,
pageId,
);
const isValidResponse = yield validateResponse(response);
if (isValidResponse) {
yield put(fetchActionsForPageSuccess(response.data));
}
} catch (error) {
yield put({
type: ReduxActionErrorTypes.FETCH_ACTIONS_FOR_PAGE_ERROR,
payload: { error },
});
}
}
2019-10-21 15:12:45 +00:00
export function* updateActionSaga(
actionPayload: ReduxAction<{ data: RestAction }>,
) {
2019-11-13 07:34:59 +00:00
try {
const isApi = actionPayload.payload.data.pluginType === "API";
2019-12-23 12:12:58 +00:00
const { data } = actionPayload.payload;
let action = data;
if (isApi) {
action = transformRestAction(data);
}
2020-06-16 10:23:19 +00:00
action.name = (yield select(getActions)).find(
(act: any) => act.config.id === action.id,
)?.config.name;
2019-11-13 07:34:59 +00:00
const response: GenericApiResponse<RestAction> = yield ActionAPI.updateAPI(
2019-12-23 12:12:58 +00:00
action,
2019-11-13 07:34:59 +00:00
);
const isValidResponse = yield validateResponse(response);
if (isValidResponse) {
2020-03-06 04:59:24 +00:00
const pageName = yield select(
getCurrentPageNameByActionId,
response.data.id,
);
if (action.pluginType === QUERY_CONSTANT) {
AnalyticsUtil.logEvent("SAVE_QUERY", {
queryName: action.name,
pageName,
});
}
2020-03-06 04:59:24 +00:00
AnalyticsUtil.logEvent("SAVE_API", {
apiId: response.data.id,
apiName: response.data.name,
pageName: pageName,
});
2019-11-13 07:34:59 +00:00
yield put(updateActionSuccess({ data: response.data }));
2020-06-10 17:42:51 +00:00
// if (actionPayload.payload.data.pluginType !== "DB") {
// yield put(runApiAction(data.id));
// }
2019-11-13 07:34:59 +00:00
}
} catch (error) {
yield put({
type: ReduxActionErrorTypes.UPDATE_ACTION_ERROR,
2019-12-11 15:14:38 +00:00
payload: { error, id: actionPayload.payload.data.id },
2019-10-21 15:12:45 +00:00
});
}
}
2020-03-06 04:59:24 +00:00
export function* deleteActionSaga(
actionPayload: ReduxAction<{ id: string; name: string }>,
) {
2019-11-13 07:34:59 +00:00
try {
const id = actionPayload.payload.id;
2020-03-06 04:59:24 +00:00
const name = actionPayload.payload.name;
const response: GenericApiResponse<RestAction> = yield ActionAPI.deleteAction(
id,
);
2019-11-13 07:34:59 +00:00
const isValidResponse = yield validateResponse(response);
if (isValidResponse) {
AppToaster.show({
message: `${response.data.name} Action deleted`,
2020-02-18 10:41:52 +00:00
type: ToastType.SUCCESS,
2019-11-13 07:34:59 +00:00
});
2020-03-06 04:59:24 +00:00
const pageName = yield select(getCurrentPageNameByActionId, id);
AnalyticsUtil.logEvent("DELETE_API", {
apiName: name,
pageName: pageName,
apiID: id,
});
2019-11-13 07:34:59 +00:00
yield put(deleteActionSuccess({ id }));
}
} catch (error) {
yield put({
type: ReduxActionErrorTypes.DELETE_ACTION_ERROR,
2019-12-11 15:14:38 +00:00
payload: { error, id: actionPayload.payload.id },
2019-10-21 15:12:45 +00:00
});
}
}
2020-02-07 02:32:52 +00:00
export function* runApiActionSaga(
reduxAction: ReduxAction<{
id: string;
paginationField: PaginationField;
}>,
) {
2019-11-20 10:57:05 +00:00
try {
const {
values,
dirty,
valid,
2020-02-07 02:32:52 +00:00
}: {
values: RestAction;
dirty: boolean;
valid: boolean;
} = yield select(getFormData, API_EDITOR_FORM_NAME);
const actionObject: PageAction = yield select(getAction, values.id);
2019-11-20 10:57:05 +00:00
let action: ExecuteActionRequest["action"] = { id: values.id };
let jsonPathKeys = actionObject.jsonPathKeys;
2019-11-20 10:57:05 +00:00
if (!valid) {
console.error("Form error");
return;
}
if (dirty) {
2020-02-07 02:32:52 +00:00
action = _.omit(transformRestAction(values), "id") as RestAction;
2019-11-20 10:57:05 +00:00
const actionString = JSON.stringify(action);
if (isDynamicValue(actionString)) {
const { jsSnippets } = getDynamicBindings(actionString);
2019-11-20 10:57:05 +00:00
// Replace cause the existing keys could have been updated
jsonPathKeys = jsSnippets.filter(jsSnippet => !!jsSnippet);
2019-11-20 10:57:05 +00:00
} else {
jsonPathKeys = [];
}
}
2020-02-07 02:32:52 +00:00
const { paginationField } = reduxAction.payload;
2019-11-20 10:57:05 +00:00
const params = yield call(getActionParams, jsonPathKeys);
2020-05-07 08:07:29 +00:00
const timeout = yield select(getActionTimeout, values.id);
const response: ActionApiResponse = yield ActionAPI.executeAction(
{
action,
params,
paginationField,
},
timeout,
);
2020-03-12 20:27:39 +00:00
let payload = createActionSuccessResponse(response);
2019-11-20 10:57:05 +00:00
if (response.responseMeta && response.responseMeta.error) {
payload = createActionErrorResponse(response);
}
const id = values.id || "DRY_RUN";
2020-03-06 04:59:24 +00:00
const pageName = yield select(getCurrentPageNameByActionId, values.id);
AnalyticsUtil.logEvent("RUN_API", {
apiId: values.id,
apiName: values.name,
pageName: pageName,
responseTime: response.clientMeta.duration,
apiType: "INTERNAL",
});
2019-11-20 10:57:05 +00:00
yield put({
type: ReduxActionTypes.RUN_API_SUCCESS,
payload: { [id]: payload },
});
} catch (error) {
yield put({
type: ReduxActionErrorTypes.RUN_API_ERROR,
2020-03-03 06:51:59 +00:00
payload: { error, id: reduxAction.payload.id },
2019-11-20 10:57:05 +00:00
});
}
}
2020-03-12 20:27:39 +00:00
function* executePageLoadAction(pageAction: PageAction) {
2020-03-13 09:17:04 +00:00
yield put(executeApiActionRequest({ id: pageAction.id }));
2020-03-12 20:27:39 +00:00
const params: Property[] = yield call(
getActionParams,
pageAction.jsonPathKeys,
2020-01-30 10:55:37 +00:00
);
2020-03-12 20:27:39 +00:00
const executeActionRequest: ExecuteActionRequest = {
action: { id: pageAction.id },
params,
};
const response: ActionApiResponse = yield ActionAPI.executeAction(
executeActionRequest,
2020-05-07 08:07:29 +00:00
pageAction.timeoutInMillisecond,
2020-03-12 20:27:39 +00:00
);
if (isErrorResponse(response)) {
yield put(
executeActionError({
actionId: pageAction.id,
error: response.responseMeta.error,
}),
2020-01-30 10:55:37 +00:00
);
2020-03-12 20:27:39 +00:00
} else {
const payload = createActionSuccessResponse(response);
yield put(
executeApiActionSuccess({
id: pageAction.id,
response: payload,
}),
2020-02-18 10:41:52 +00:00
);
}
}
2020-03-12 20:27:39 +00:00
function* executePageLoadActionsSaga(action: ReduxAction<PageAction[][]>) {
const pageActions = action.payload;
for (const actionSet of pageActions) {
2020-03-23 12:40:17 +00:00
// Load all sets in parallel
2020-03-19 07:12:58 +00:00
yield* yield all(actionSet.map(a => call(executePageLoadAction, a)));
2020-03-12 20:27:39 +00:00
}
}
2020-01-24 09:54:40 +00:00
function* moveActionSaga(
action: ReduxAction<{
id: string;
destinationPageId: string;
originalPageId: string;
name: string;
}>,
) {
const drafts = yield select(state => state.ui.apiPane.drafts);
const dirty = action.payload.id in drafts;
const actionObject: RestAction = dirty
? drafts[action.payload.id]
: yield select(getAction, action.payload.id);
2020-01-27 13:53:33 +00:00
const withoutBindings = removeBindingsFromObject(actionObject);
2020-01-24 09:54:40 +00:00
try {
const response = yield ActionAPI.moveAction({
2020-01-27 13:53:33 +00:00
action: {
...withoutBindings,
name: action.payload.name,
},
2020-01-24 09:54:40 +00:00
destinationPageId: action.payload.destinationPageId,
});
const isValidResponse = yield validateResponse(response);
if (isValidResponse) {
AppToaster.show({
message: `${response.data.name} Action moved`,
2020-02-18 10:41:52 +00:00
type: ToastType.SUCCESS,
2020-01-24 09:54:40 +00:00
});
}
2020-03-06 04:59:24 +00:00
const pageName = yield select(getPageNameByPageId, response.data.pageId);
AnalyticsUtil.logEvent("MOVE_API", {
apiName: response.data.name,
pageName: pageName,
apiID: response.data.id,
});
2020-01-27 13:53:33 +00:00
yield put(moveActionSuccess(response.data));
2020-01-24 09:54:40 +00:00
} catch (e) {
AppToaster.show({
message: `Error while moving action ${actionObject.name}`,
2020-02-18 10:41:52 +00:00
type: ToastType.ERROR,
2020-01-24 09:54:40 +00:00
});
yield put(
moveActionError({
id: action.payload.id,
originalPageId: action.payload.originalPageId,
}),
);
}
}
function* copyActionSaga(
action: ReduxAction<{ id: string; destinationPageId: string; name: string }>,
) {
const drafts = yield select(state => state.ui.apiPane.drafts);
const dirty = action.payload.id in drafts;
2020-01-27 13:53:33 +00:00
let actionObject = dirty
2020-01-24 09:54:40 +00:00
? drafts[action.payload.id]
: yield select(getAction, action.payload.id);
2020-01-27 13:53:33 +00:00
if (action.payload.destinationPageId !== actionObject.pageId) {
actionObject = removeBindingsFromObject(actionObject);
}
2020-01-24 09:54:40 +00:00
try {
const copyAction = {
...(_.omit(actionObject, "id") as RestAction),
name: action.payload.name,
pageId: action.payload.destinationPageId,
};
const response = yield ActionAPI.createAPI(copyAction);
const isValidResponse = yield validateResponse(response);
if (isValidResponse) {
AppToaster.show({
message: `${actionObject.name} Action copied`,
2020-02-18 10:41:52 +00:00
type: ToastType.SUCCESS,
2020-01-24 09:54:40 +00:00
});
}
2020-03-06 04:59:24 +00:00
const pageName = yield select(getPageNameByPageId, response.data.pageId);
AnalyticsUtil.logEvent("DUPLICATE_API", {
apiName: response.data.name,
pageName: pageName,
apiID: response.data.id,
});
2020-01-24 09:54:40 +00:00
yield put(copyActionSuccess(response.data));
} catch (e) {
AppToaster.show({
message: `Error while copying action ${actionObject.name}`,
2020-02-18 10:41:52 +00:00
type: ToastType.ERROR,
2020-01-24 09:54:40 +00:00
});
yield put(copyActionError(action.payload));
}
}
2020-06-16 10:23:19 +00:00
function* editApiNameSaga(action: ReduxAction<{ id: string; value: string }>) {
const actionNames = yield select(state =>
state.entities.actions.map((action: ActionData) => action.config.name),
);
const draftActionNames = yield select(state =>
Object.values(state.ui.apiPane.apiName.drafts),
);
//TODO: If an api is in saving state, then it should not use that name as well.
const validation = validateEntityName(action.payload.value, [
...actionNames,
...draftActionNames,
]);
yield put(
updateApiNameDraft({
id: action.payload.id,
draft: {
value: action.payload.value,
validation: validation,
},
}),
);
}
export function* refactorActionName(
id: string,
pageId: string,
oldName: string,
newName: string,
) {
// fetch page of the action
const pageResponse = yield call(PageApi.fetchPage, {
pageId: pageId,
});
// check if page request is successful
const isPageRequestSuccessful = yield validateResponse(pageResponse);
if (isPageRequestSuccessful) {
// get the layoutId from the page response
const layoutId = pageResponse.data.layouts[0].id;
// call to refactor action
const refactorResponse = yield ActionAPI.updateActionName({
layoutId,
pageId: pageId,
oldName: oldName,
newName: newName,
});
const isRefactorSuccessful = yield validateResponse(refactorResponse);
const currentPageId = yield select(getCurrentPageId);
if (isRefactorSuccessful) {
if (currentPageId === pageId) {
yield updateCanvasWithDSL(refactorResponse.data, pageId, layoutId);
} else {
yield put(fetchActionsForPage(pageId));
}
}
}
}
function* saveApiNameSaga(action: ReduxAction<{ id: string }>) {
// Takes from drafts, checks if the name isValid, saves
2020-06-17 13:16:14 +00:00
try {
const apiNameDraftState = yield select(state => state.ui.apiPane.apiName);
const apiId = action.payload.id;
const apiNameDraft = apiNameDraftState.drafts[apiId];
if (apiNameDraft) {
const validation = apiNameDraft.validation;
if (!validation.isValid) {
// If its invalid, then don't save and just remove draft and validation.
yield put(
updateApiNameDraft({
id: action.payload.id,
}),
);
} else {
const api = yield select(state =>
state.entities.actions.find(
(action: ActionData) => action.config.id === apiId,
),
);
2020-06-16 10:23:19 +00:00
2020-06-17 13:16:14 +00:00
yield refactorActionName(
api.config.id,
api.config.pageId,
api.config.name,
apiNameDraft.value,
);
}
2020-06-16 10:23:19 +00:00
}
2020-06-17 13:16:14 +00:00
} catch (e) {
yield put(
updateApiNameDraft({
id: action.payload.id,
}),
);
AppToaster.show({
message: `Unable to update API name`,
type: ToastType.ERROR,
});
console.error(e);
2020-06-16 10:23:19 +00:00
}
}
2019-10-21 15:12:45 +00:00
export function* watchActionSagas() {
yield all([
takeEvery(ReduxActionTypes.FETCH_ACTIONS_INIT, fetchActionsSaga),
2020-03-17 15:57:35 +00:00
takeEvery(ReduxActionTypes.EXECUTE_ACTION, executeAppAction),
2019-11-20 10:57:05 +00:00
takeLatest(ReduxActionTypes.RUN_API_REQUEST, runApiActionSaga),
2020-04-22 09:15:24 +00:00
takeEvery(ReduxActionTypes.CREATE_ACTION_INIT, createActionSaga),
takeLatest(ReduxActionTypes.UPDATE_ACTION_INIT, updateActionSaga),
takeLatest(ReduxActionTypes.DELETE_ACTION_INIT, deleteActionSaga),
2020-06-16 10:23:19 +00:00
takeLatest(ReduxActionTypes.EDIT_API_NAME, editApiNameSaga),
takeLatest(ReduxActionTypes.SAVE_API_NAME, saveApiNameSaga),
takeLatest(
ReduxActionTypes.EXECUTE_PAGE_LOAD_ACTIONS,
executePageLoadActionsSaga,
),
2020-01-24 09:54:40 +00:00
takeLatest(ReduxActionTypes.MOVE_ACTION_INIT, moveActionSaga),
takeLatest(ReduxActionTypes.COPY_ACTION_INIT, copyActionSaga),
2020-02-21 12:16:49 +00:00
takeLatest(
ReduxActionTypes.FETCH_ACTIONS_FOR_PAGE_INIT,
fetchActionsForPageSaga,
),
2019-10-21 15:12:45 +00:00
]);
}