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

264 lines
6.3 KiB
TypeScript
Raw Normal View History

import {
actionChannel,
call,
fork,
put,
select,
take,
} from "redux-saga/effects";
import {
EvaluationReduxAction,
ReduxAction,
ReduxActionTypes,
ReduxActionWithoutPayload,
} from "constants/ReduxActionConstants";
import {
getDataTree,
getUnevaluatedDataTree,
} from "selectors/dataTreeSelectors";
import WidgetFactory, { WidgetTypeConfigMap } from "../utils/WidgetFactory";
2021-02-22 05:00:16 +00:00
import { GracefulWorkerService } from "utils/WorkerUtil";
import Worker from "worker-loader!../workers/evaluation.worker";
2021-07-20 10:02:56 +00:00
import { EVAL_WORKER_ACTIONS } from "utils/DynamicBindingUtils";
import log from "loglevel";
2021-02-22 05:00:16 +00:00
import { WidgetProps } from "widgets/BaseWidget";
import PerformanceTracker, {
PerformanceTransactionName,
} from "../utils/PerformanceTracker";
import * as Sentry from "@sentry/react";
2020-12-30 13:26:44 +00:00
import { Action } from "redux";
import {
2021-07-20 10:02:56 +00:00
EVALUATE_REDUX_ACTIONS,
FIRST_EVAL_REDUX_ACTIONS,
setDependencyMap,
setEvaluatedTree,
shouldProcessBatchedAction,
} from "actions/evaluationActions";
import {
2021-07-20 10:02:56 +00:00
evalErrorHandler,
logSuccessfulBindings,
postEvalActionDispatcher,
updateTernDefinitions,
} from "./PostEvaluationSagas";
let widgetTypeConfigMap: WidgetTypeConfigMap;
const worker = new GracefulWorkerService(Worker);
function* evaluateTreeSaga(
postEvalActions?: Array<ReduxAction<unknown> | ReduxActionWithoutPayload>,
isFirstEvaluation = false,
) {
2021-03-31 07:40:59 +00:00
const unevalTree = yield select(getUnevaluatedDataTree);
log.debug({ unevalTree });
PerformanceTracker.startAsyncTracking(
PerformanceTransactionName.DATA_TREE_EVALUATION,
);
const workerResponse = yield call(
worker.request,
EVAL_WORKER_ACTIONS.EVAL_TREE,
{
unevalTree,
widgetTypeConfigMap,
},
);
const {
dataTree,
dependencies,
errors,
evaluationOrder,
logs,
updates,
} = workerResponse;
2021-03-31 07:40:59 +00:00
PerformanceTracker.stopAsyncTracking(
PerformanceTransactionName.DATA_TREE_EVALUATION,
);
PerformanceTracker.startAsyncTracking(
PerformanceTransactionName.SET_EVALUATED_TREE,
);
2021-07-20 10:02:56 +00:00
yield put(setEvaluatedTree(dataTree, updates));
PerformanceTracker.stopAsyncTracking(
2021-03-31 07:40:59 +00:00
PerformanceTransactionName.SET_EVALUATED_TREE,
);
const updatedDataTree = yield select(getDataTree);
log.debug({ dataTree: updatedDataTree });
logs.forEach((evalLog: any) => log.debug(evalLog));
yield call(evalErrorHandler, errors, updatedDataTree, evaluationOrder);
yield fork(
logSuccessfulBindings,
unevalTree,
updatedDataTree,
evaluationOrder,
);
yield fork(
updateTernDefinitions,
updatedDataTree,
isFirstEvaluation,
updates,
);
2021-07-20 10:02:56 +00:00
yield put(setDependencyMap(dependencies));
if (postEvalActions && postEvalActions.length) {
yield call(postEvalActionDispatcher, postEvalActions);
}
}
2021-01-14 14:37:21 +00:00
export function* evaluateActionBindings(
bindings: string[],
executionParams: Record<string, any> | string = {},
2020-12-14 18:48:13 +00:00
) {
const workerResponse = yield call(
worker.request,
2021-01-14 14:37:21 +00:00
EVAL_WORKER_ACTIONS.EVAL_ACTION_BINDINGS,
{
2021-01-14 14:37:21 +00:00
bindings,
executionParams,
},
);
2021-01-14 14:37:21 +00:00
const { errors, values } = workerResponse;
yield call(evalErrorHandler, errors);
2021-01-14 14:37:21 +00:00
return values;
}
export function* evaluateDynamicTrigger(
dynamicTrigger: string,
2020-11-20 09:30:50 +00:00
callbackData?: Array<any>,
) {
const unEvalTree = yield select(getUnevaluatedDataTree);
const workerResponse = yield call(
worker.request,
EVAL_WORKER_ACTIONS.EVAL_TRIGGER,
{ dataTree: unEvalTree, dynamicTrigger, callbackData },
);
const { errors, triggers } = workerResponse;
yield call(evalErrorHandler, errors);
return triggers;
}
export function* clearEvalCache() {
yield call(worker.request, EVAL_WORKER_ACTIONS.CLEAR_CACHE);
return true;
}
export function* clearEvalPropertyCache(propertyPath: string) {
yield call(worker.request, EVAL_WORKER_ACTIONS.CLEAR_PROPERTY_CACHE, {
propertyPath,
});
}
/**
* clears all cache keys of a widget
*
* @param widgetName
*/
export function* clearEvalPropertyCacheOfWidget(widgetName: string) {
yield call(
worker.request,
EVAL_WORKER_ACTIONS.CLEAR_PROPERTY_CACHE_OF_WIDGET,
{
widgetName,
},
);
}
export function* validateProperty(
property: string,
value: any,
props: WidgetProps,
) {
const unevalTree = yield select(getUnevaluatedDataTree);
const validation = unevalTree[props.widgetName].validationPaths[property];
return yield call(worker.request, EVAL_WORKER_ACTIONS.VALIDATE_PROPERTY, {
property,
value,
props,
validation,
});
}
2020-12-30 13:26:44 +00:00
function evalQueueBuffer() {
let canTake = false;
2020-12-30 13:26:44 +00:00
let postEvalActions: any = [];
const take = () => {
if (canTake) {
2020-12-30 13:26:44 +00:00
const resp = postEvalActions;
postEvalActions = [];
canTake = false;
return { postEvalActions: resp, type: "BUFFERED_ACTION" };
2020-12-30 13:26:44 +00:00
}
};
const flush = () => {
if (canTake) {
2020-12-30 13:26:44 +00:00
return [take() as Action];
}
return [];
};
const put = (action: EvaluationReduxAction<unknown | unknown[]>) => {
2021-07-20 10:02:56 +00:00
if (!shouldProcessBatchedAction(action)) {
return;
}
canTake = true;
2020-12-30 13:26:44 +00:00
// TODO: If the action is the same as before, we can send only one and ignore duplicates.
if (action.postEvalActions) {
postEvalActions.push(...action.postEvalActions);
}
};
return {
take,
put,
isEmpty: () => {
return !canTake;
2020-12-30 13:26:44 +00:00
},
flush,
};
}
function* evaluationChangeListenerSaga() {
// Explicitly shutdown old worker if present
yield call(worker.shutdown);
yield call(worker.start);
widgetTypeConfigMap = WidgetFactory.getWidgetTypeConfigMap();
const initAction = yield take(FIRST_EVAL_REDUX_ACTIONS);
yield fork(evaluateTreeSaga, initAction.postEvalActions, true);
2020-12-30 13:26:44 +00:00
const evtActionChannel = yield actionChannel(
EVALUATE_REDUX_ACTIONS,
evalQueueBuffer(),
);
while (true) {
const action: EvaluationReduxAction<unknown | unknown[]> = yield take(
evtActionChannel,
);
2021-07-20 10:02:56 +00:00
if (FIRST_EVAL_REDUX_ACTIONS.includes(action.type)) {
yield call(evaluateTreeSaga, initAction.postEvalActions, true);
} else {
if (shouldProcessBatchedAction(action)) {
yield call(evaluateTreeSaga, action.postEvalActions);
}
}
}
}
export default function* evaluationSagaListeners() {
2020-12-30 13:26:44 +00:00
yield take(ReduxActionTypes.START_EVALUATION);
while (true) {
try {
yield call(evaluationChangeListenerSaga);
} catch (e) {
log.error(e);
Sentry.captureException(e);
}
}
}