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

312 lines
8.8 KiB
TypeScript
Raw Normal View History

2019-10-21 15:12:45 +00:00
import {
ReduxAction,
ReduxActionErrorTypes,
ReduxActionTypes,
} from "../constants/ReduxActionConstants";
import { Intent } from "@blueprintjs/core";
import {
all,
call,
select,
put,
takeEvery,
takeLatest,
} from "redux-saga/effects";
2019-10-21 15:12:45 +00:00
import { ActionPayload, PageAction } from "../constants/ActionConstants";
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,
RestAction,
} from "../api/ActionAPI";
2019-11-06 06:35:15 +00:00
import { AppState, DataTree } from "../reducers";
2019-10-21 15:12:45 +00:00
import _ from "lodash";
import { mapToPropList } from "../utils/AppsmithUtils";
2019-11-05 05:09:50 +00:00
import AppToaster from "../components/editorComponents/ToastComponent";
2019-10-21 15:12:45 +00:00
import { GenericApiResponse } from "../api/ApiResponses";
import {
createActionSuccess,
deleteActionSuccess,
updateActionSuccess,
} from "../actions/actionActions";
import { API_EDITOR_ID_URL, API_EDITOR_URL } from "../constants/routes";
2019-11-06 06:35:15 +00:00
import { getDynamicBoundValue } from "../utils/DynamicBindingUtils";
import history from "../utils/history";
2019-11-13 07:34:59 +00:00
import { validateResponse } from "./ErrorSagas";
import {
ERROR_MESSAGE_SELECT_ACTION,
ERROR_MESSAGE_SELECT_ACTION_TYPE,
} from "constants/messages";
2019-10-21 15:12:45 +00:00
2019-11-06 06:35:15 +00:00
const getDataTree = (state: AppState): DataTree => {
2019-10-21 15:12:45 +00:00
return state.entities;
};
2019-11-05 05:09:50 +00:00
const getAction = (
state: AppState,
actionId: string,
): RestAction | undefined => {
return _.find(state.entities.actions.data, { id: actionId });
2019-10-21 15:12:45 +00:00
};
2019-11-12 09:43:13 +00:00
const createActionResponse = (response: ActionApiResponse): ActionResponse => ({
...response.data,
...response.clientMeta,
});
const createActionErrorResponse = (
response: ActionApiResponse,
): ActionResponse => ({
body: response.responseMeta.error || { error: "Error" },
statusCode: response.responseMeta.error
? response.responseMeta.error.code
: "Error",
headers: {},
duration: "0",
size: "0",
});
2019-11-06 06:35:15 +00:00
export function* evaluateJSONPathSaga(path: string): any {
2019-10-21 15:12:45 +00:00
const dataTree = yield select(getDataTree);
2019-11-06 06:35:15 +00:00
return getDynamicBoundValue(dataTree, path);
2019-10-21 15:12:45 +00:00
}
2019-11-12 08:52:13 +00:00
export function* executeAPIQueryActionSaga(apiAction: ActionPayload) {
2019-11-13 07:34:59 +00:00
try {
const api: PageAction = yield select(getAction, apiAction.actionId);
if (!api) {
yield put({
type: ReduxActionTypes.EXECUTE_ACTION_ERROR,
payload: "No action selected",
});
return;
}
2019-10-21 15:12:45 +00:00
2019-11-13 07:34:59 +00:00
const executeActionRequest: ExecuteActionRequest = {
action: {
id: apiAction.actionId,
},
};
if (!_.isNil(api.jsonPathKeys)) {
const values: any = _.flatten(
yield all(
api.jsonPathKeys.map((jsonPath: string) => {
return call(evaluateJSONPathSaga, jsonPath);
}),
),
);
const dynamicBindings: Record<string, string> = {};
api.jsonPathKeys.forEach((key, i) => {
dynamicBindings[key] = values[i];
});
executeActionRequest.params = mapToPropList(dynamicBindings);
}
const response: ActionApiResponse = yield ActionAPI.executeAction(
executeActionRequest,
2019-10-21 15:12:45 +00:00
);
2019-11-13 07:34:59 +00:00
let payload = createActionResponse(response);
if (response.responseMeta && response.responseMeta.error) {
payload = createActionErrorResponse(response);
if (apiAction.onError) {
yield put({
type: ReduxActionTypes.EXECUTE_ACTION,
payload: apiAction.onError,
});
}
2019-11-12 07:57:12 +00:00
yield put({
2019-11-13 07:34:59 +00:00
type: ReduxActionTypes.EXECUTE_ACTION_ERROR,
payload: { [apiAction.actionId]: payload },
2019-11-12 07:57:12 +00:00
});
2019-11-13 07:34:59 +00:00
} else {
if (apiAction.onSuccess) {
yield put({
type: ReduxActionTypes.EXECUTE_ACTION,
payload: apiAction.onSuccess,
});
}
2019-11-12 07:57:12 +00:00
yield put({
2019-11-13 07:34:59 +00:00
type: ReduxActionTypes.EXECUTE_ACTION_SUCCESS,
payload: { [apiAction.actionId]: payload },
2019-11-12 07:57:12 +00:00
});
}
2019-11-13 07:34:59 +00:00
return response;
} catch (error) {
2019-11-12 07:57:12 +00:00
yield put({
2019-11-13 07:34:59 +00:00
type: ReduxActionTypes.EXECUTE_ACTION_ERROR,
payload: { error },
2019-11-12 07:57:12 +00:00
});
2019-11-06 06:35:15 +00:00
}
2019-10-21 15:12:45 +00:00
}
function validateActionPayload(actionPayload: ActionPayload) {
const validation = {
isValid: true,
messages: [] as string[],
};
const noActionId = actionPayload.actionId === undefined;
validation.isValid = validation.isValid && !noActionId;
if (noActionId) {
validation.messages.push(ERROR_MESSAGE_SELECT_ACTION);
}
const noActionType = actionPayload.actionType === undefined;
validation.isValid = validation.isValid && !noActionType;
if (noActionType) {
validation.messages.push(ERROR_MESSAGE_SELECT_ACTION_TYPE);
}
return validation;
}
export function* executeActionSaga(actionPayloads: ActionPayload[]): any {
yield all(
_.map(actionPayloads, (actionPayload: ActionPayload) => {
const actionValidation = validateActionPayload(actionPayload);
if (!actionValidation.isValid) {
console.error(actionValidation.messages.join(", "));
return undefined;
}
switch (actionPayload.actionType) {
case "API":
return call(executeAPIQueryActionSaga, actionPayload);
case "QUERY":
return call(executeAPIQueryActionSaga, actionPayload);
default:
return undefined;
}
}),
);
}
export function* executeReduxActionSaga(action: ReduxAction<ActionPayload[]>) {
2019-10-21 15:12:45 +00:00
if (!_.isNil(action.payload)) {
yield call(executeActionSaga, action.payload);
2019-11-13 07:34:59 +00:00
} else {
yield put({
type: ReduxActionTypes.EXECUTE_ACTION_ERROR,
payload: "No action payload",
});
2019-10-21 15:12:45 +00:00
}
}
2019-11-12 09:43:13 +00:00
function* dryRunActionSaga(action: ReduxAction<RestAction>) {
const executeActionRequest: ExecuteActionRequest = {
action: {
...action.payload,
},
};
// TODO(hetu): No support for dynamic bindings in dry runs yet
const response: ActionApiResponse = yield ActionAPI.executeAction(
executeActionRequest,
);
let payload = createActionResponse(response);
if (response.responseMeta && response.responseMeta.error) {
payload = createActionErrorResponse(response);
}
yield put({
type: ReduxActionTypes.EXECUTE_ACTION_SUCCESS,
payload: { [action.type]: payload },
});
}
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`,
intent: Intent.SUCCESS,
});
yield put(createActionSuccess(response.data));
history.push(API_EDITOR_ID_URL(response.data.id));
}
} catch (error) {
yield put({
type: ReduxActionErrorTypes.CREATE_ACTION_ERROR,
payload: { error },
2019-10-21 15:12:45 +00:00
});
}
}
export function* fetchActionsSaga() {
2019-11-13 07:34:59 +00:00
try {
const response: GenericApiResponse<
RestAction[]
> = yield ActionAPI.fetchActions();
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
});
}
}
export function* updateActionSaga(
actionPayload: ReduxAction<{ data: RestAction }>,
) {
2019-11-13 07:34:59 +00:00
try {
const response: GenericApiResponse<RestAction> = yield ActionAPI.updateAPI(
actionPayload.payload.data,
);
const isValidResponse = yield validateResponse(response);
if (isValidResponse) {
AppToaster.show({
message: `${actionPayload.payload.data.name} Action updated`,
intent: Intent.SUCCESS,
});
yield put(updateActionSuccess({ data: response.data }));
}
} catch (error) {
yield put({
type: ReduxActionErrorTypes.UPDATE_ACTION_ERROR,
payload: { error },
2019-10-21 15:12:45 +00:00
});
}
}
export function* deleteActionSaga(actionPayload: ReduxAction<{ id: string }>) {
2019-11-13 07:34:59 +00:00
try {
const id = actionPayload.payload.id;
const response: GenericApiResponse<
RestAction
> = yield ActionAPI.deleteAction(id);
const isValidResponse = yield validateResponse(response);
if (isValidResponse) {
AppToaster.show({
message: `${response.data.name} Action deleted`,
intent: Intent.SUCCESS,
});
yield put(deleteActionSuccess({ id }));
history.push(API_EDITOR_URL);
}
} catch (error) {
yield put({
type: ReduxActionErrorTypes.DELETE_ACTION_ERROR,
payload: { error },
2019-10-21 15:12:45 +00:00
});
}
}
export function* watchActionSagas() {
yield all([
takeEvery(ReduxActionTypes.FETCH_ACTIONS_INIT, fetchActionsSaga),
takeLatest(ReduxActionTypes.EXECUTE_ACTION, executeReduxActionSaga),
takeLatest(ReduxActionTypes.CREATE_ACTION_INIT, createActionSaga),
takeLatest(ReduxActionTypes.UPDATE_ACTION_INIT, updateActionSaga),
takeLatest(ReduxActionTypes.DELETE_ACTION_INIT, deleteActionSaga),
2019-11-12 09:43:13 +00:00
takeLatest(ReduxActionTypes.DRY_RUN_ACTION, dryRunActionSaga),
2019-10-21 15:12:45 +00:00
]);
}