PromucFlow_constructor/app/client/src/sagas/evaluationsSaga.ts
2020-10-24 19:59:53 +05:30

219 lines
6.2 KiB
TypeScript

import {
all,
call,
fork,
put,
select,
take,
takeLatest,
} from "redux-saga/effects";
import { eventChannel, EventChannel } from "redux-saga";
import {
ReduxAction,
ReduxActionErrorTypes,
ReduxActionTypes,
} from "constants/ReduxActionConstants";
import { getUnevaluatedDataTree } from "selectors/dataTreeSelectors";
import WidgetFactory, { WidgetTypeConfigMap } from "../utils/WidgetFactory";
import Worker from "worker-loader!../workers/evaluation.worker";
import {
EVAL_WORKER_ACTIONS,
EvalError,
EvalErrorTypes,
} from "../utils/DynamicBindingUtils";
import { ToastType } from "react-toastify";
import { AppToaster } from "../components/editorComponents/ToastComponent";
import log from "loglevel";
import _ from "lodash";
import { WidgetType } from "../constants/WidgetConstants";
import { WidgetProps } from "../widgets/BaseWidget";
let evaluationWorker: Worker;
let workerChannel: EventChannel<any>;
let widgetTypeConfigMap: WidgetTypeConfigMap;
const initEvaluationWorkers = () => {
widgetTypeConfigMap = WidgetFactory.getWidgetTypeConfigMap();
evaluationWorker = new Worker();
workerChannel = eventChannel(emitter => {
evaluationWorker.addEventListener("message", emitter);
// The subscriber must return an unsubscribe function
return () => {
evaluationWorker.removeEventListener("message", emitter);
};
});
};
const evalErrorHandler = (errors: EvalError[]) => {
errors.forEach(error => {
if (error.type === EvalErrorTypes.DEPENDENCY_ERROR) {
AppToaster.show({
message: error.message,
type: ToastType.ERROR,
});
}
log.debug(error);
});
};
function* evaluateTreeSaga() {
const unEvalTree = yield select(getUnevaluatedDataTree);
log.debug({ unEvalTree });
evaluationWorker.postMessage({
action: EVAL_WORKER_ACTIONS.EVAL_TREE,
dataTree: unEvalTree,
widgetTypeConfigMap,
});
const workerResponse = yield take(workerChannel);
const { errors, dataTree } = workerResponse.data;
const parsedDataTree = JSON.parse(dataTree);
log.debug({ dataTree: parsedDataTree });
evalErrorHandler(errors);
yield put({
type: ReduxActionTypes.SET_EVALUATED_TREE,
payload: parsedDataTree,
});
}
export function* evaluateSingleValue(binding: string) {
if (evaluationWorker) {
const unEvalTree = yield select(getUnevaluatedDataTree);
evaluationWorker.postMessage({
action: EVAL_WORKER_ACTIONS.EVAL_SINGLE,
dataTree: unEvalTree,
binding,
});
const workerResponse = yield take(workerChannel);
const { errors, value } = workerResponse.data;
evalErrorHandler(errors);
return value;
}
}
export function* evaluateDynamicTrigger(
dynamicTrigger: string,
callbackData: any,
) {
if (evaluationWorker) {
const unEvalTree = yield select(getUnevaluatedDataTree);
evaluationWorker.postMessage({
action: EVAL_WORKER_ACTIONS.EVAL_TRIGGER,
dataTree: unEvalTree,
dynamicTrigger,
callbackData,
});
const workerResponse = yield take(workerChannel);
const { errors, triggers } = workerResponse.data;
evalErrorHandler(errors);
return triggers;
}
return [];
}
export function* clearEvalCache() {
if (evaluationWorker) {
evaluationWorker.postMessage({
action: EVAL_WORKER_ACTIONS.CLEAR_CACHE,
});
yield take(workerChannel);
return true;
}
}
export function* clearEvalPropertyCache(propertyPath: string) {
if (evaluationWorker) {
evaluationWorker.postMessage({
action: EVAL_WORKER_ACTIONS.CLEAR_PROPERTY_CACHE,
propertyPath,
});
yield take(workerChannel);
}
}
export function* validateProperty(
widgetType: WidgetType,
property: string,
value: any,
props: WidgetProps,
) {
if (evaluationWorker) {
evaluationWorker.postMessage({
action: EVAL_WORKER_ACTIONS.VALIDATE_PROPERTY,
widgetType,
property,
value,
props,
});
const response = yield take(workerChannel);
return response.data;
}
return { isValid: true, parsed: value };
}
const EVALUATE_REDUX_ACTIONS = [
// Actions
ReduxActionTypes.FETCH_ACTIONS_SUCCESS,
ReduxActionTypes.FETCH_ACTIONS_VIEW_MODE_SUCCESS,
ReduxActionErrorTypes.FETCH_ACTIONS_ERROR,
ReduxActionErrorTypes.FETCH_ACTIONS_VIEW_MODE_ERROR,
ReduxActionTypes.FETCH_ACTIONS_FOR_PAGE_SUCCESS,
ReduxActionTypes.SUBMIT_CURL_FORM_SUCCESS,
ReduxActionTypes.CREATE_ACTION_SUCCESS,
ReduxActionTypes.UPDATE_ACTION_PROPERTY,
ReduxActionTypes.DELETE_ACTION_SUCCESS,
ReduxActionTypes.COPY_ACTION_SUCCESS,
ReduxActionTypes.MOVE_ACTION_SUCCESS,
ReduxActionTypes.RUN_ACTION_REQUEST,
ReduxActionTypes.RUN_ACTION_SUCCESS,
ReduxActionErrorTypes.RUN_ACTION_ERROR,
ReduxActionTypes.EXECUTE_API_ACTION_REQUEST,
ReduxActionTypes.EXECUTE_API_ACTION_SUCCESS,
ReduxActionErrorTypes.EXECUTE_ACTION_ERROR,
// App Data
ReduxActionTypes.SET_APP_MODE,
ReduxActionTypes.FETCH_USER_DETAILS_SUCCESS,
ReduxActionTypes.SET_URL_DATA,
ReduxActionTypes.UPDATE_APP_STORE,
// Widgets
ReduxActionTypes.UPDATE_LAYOUT,
ReduxActionTypes.UPDATE_WIDGET_PROPERTY,
ReduxActionTypes.UPDATE_WIDGET_NAME_SUCCESS,
// Widget Meta
ReduxActionTypes.SET_META_PROP,
ReduxActionTypes.RESET_WIDGET_META,
// Pages
ReduxActionTypes.FETCH_PAGE_SUCCESS,
ReduxActionTypes.FETCH_PUBLISHED_PAGE_SUCCESS,
// Batches
ReduxActionTypes.BATCH_UPDATES_SUCCESS,
];
function* evaluationChangeListenerSaga() {
initEvaluationWorkers();
yield call(evaluateTreeSaga);
while (true) {
const action: ReduxAction<any> = yield take(EVALUATE_REDUX_ACTIONS);
// When batching success action happens, we need to only evaluate
// if the batch had any action we need to evaluate properties for
if (action.type === ReduxActionTypes.BATCH_UPDATES_SUCCESS) {
const batchedActionTypes = action.payload.map(
(batchedAction: ReduxAction<any>) => batchedAction.type,
);
if (
_.intersection(EVALUATE_REDUX_ACTIONS, batchedActionTypes).length === 0
) {
continue;
}
}
log.debug(`Evaluating`, { action });
yield fork(evaluateTreeSaga);
}
// TODO(hetu) need an action to stop listening and evaluate (exit app)
}
export default function* evaluationSagaListeners() {
yield all([
takeLatest(ReduxActionTypes.START_EVALUATION, evaluationChangeListenerSaga),
]);
}