2021-01-04 10:16:08 +00:00
|
|
|
import {
|
|
|
|
|
actionChannel,
|
|
|
|
|
call,
|
|
|
|
|
fork,
|
|
|
|
|
put,
|
|
|
|
|
select,
|
|
|
|
|
take,
|
|
|
|
|
} from "redux-saga/effects";
|
2020-12-28 08:42:43 +00:00
|
|
|
|
2020-10-21 04:25:32 +00:00
|
|
|
import {
|
2020-11-03 10:16:11 +00:00
|
|
|
EvaluationReduxAction,
|
2020-10-21 04:25:32 +00:00
|
|
|
ReduxAction,
|
|
|
|
|
ReduxActionErrorTypes,
|
|
|
|
|
ReduxActionTypes,
|
|
|
|
|
} from "constants/ReduxActionConstants";
|
2020-11-03 10:16:11 +00:00
|
|
|
import {
|
|
|
|
|
getDataTree,
|
|
|
|
|
getUnevaluatedDataTree,
|
|
|
|
|
} from "selectors/dataTreeSelectors";
|
2020-10-21 04:25:32 +00:00
|
|
|
import WidgetFactory, { WidgetTypeConfigMap } from "../utils/WidgetFactory";
|
2020-12-28 08:42:43 +00:00
|
|
|
import { GracefulWorkerService } from "../utils/WorkerUtil";
|
2020-10-21 04:25:32 +00:00
|
|
|
import Worker from "worker-loader!../workers/evaluation.worker";
|
|
|
|
|
import {
|
|
|
|
|
EVAL_WORKER_ACTIONS,
|
|
|
|
|
EvalError,
|
|
|
|
|
EvalErrorTypes,
|
|
|
|
|
} from "../utils/DynamicBindingUtils";
|
|
|
|
|
import log from "loglevel";
|
|
|
|
|
import { WidgetType } from "../constants/WidgetConstants";
|
|
|
|
|
import { WidgetProps } from "../widgets/BaseWidget";
|
2020-11-03 10:16:11 +00:00
|
|
|
import PerformanceTracker, {
|
|
|
|
|
PerformanceTransactionName,
|
|
|
|
|
} from "../utils/PerformanceTracker";
|
2020-11-24 07:01:37 +00:00
|
|
|
import { Variant } from "components/ads/common";
|
|
|
|
|
import { Toaster } from "components/ads/Toast";
|
2020-11-11 11:32:14 +00:00
|
|
|
import * as Sentry from "@sentry/react";
|
2020-12-14 18:48:13 +00:00
|
|
|
import { EXECUTION_PARAM_KEY } from "../constants/ActionConstants";
|
2020-12-30 13:26:44 +00:00
|
|
|
import { Action } from "redux";
|
2021-01-14 11:15:25 +00:00
|
|
|
import _ from "lodash";
|
2020-10-21 04:25:32 +00:00
|
|
|
|
|
|
|
|
let widgetTypeConfigMap: WidgetTypeConfigMap;
|
|
|
|
|
|
2020-12-28 08:42:43 +00:00
|
|
|
const worker = new GracefulWorkerService(Worker);
|
2020-10-21 04:25:32 +00:00
|
|
|
|
|
|
|
|
const evalErrorHandler = (errors: EvalError[]) => {
|
2020-12-07 05:45:14 +00:00
|
|
|
if (!errors) return;
|
2020-12-24 04:32:25 +00:00
|
|
|
errors.forEach((error) => {
|
2020-10-21 04:25:32 +00:00
|
|
|
if (error.type === EvalErrorTypes.DEPENDENCY_ERROR) {
|
2020-11-24 07:01:37 +00:00
|
|
|
Toaster.show({
|
|
|
|
|
text: error.message,
|
|
|
|
|
variant: Variant.danger,
|
2020-10-21 04:25:32 +00:00
|
|
|
});
|
|
|
|
|
}
|
2021-01-05 11:45:37 +00:00
|
|
|
if (error.type === EvalErrorTypes.EVAL_TREE_ERROR) {
|
2020-11-24 07:01:37 +00:00
|
|
|
Toaster.show({
|
|
|
|
|
text: "Unexpected error occurred while evaluating the app",
|
|
|
|
|
variant: Variant.danger,
|
2020-11-11 11:32:14 +00:00
|
|
|
});
|
2021-01-05 11:45:37 +00:00
|
|
|
}
|
|
|
|
|
if (error.type === EvalErrorTypes.BAD_UNEVAL_TREE_ERROR) {
|
2020-11-11 11:32:14 +00:00
|
|
|
Sentry.captureException(error);
|
|
|
|
|
}
|
2020-10-21 04:25:32 +00:00
|
|
|
log.debug(error);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2020-11-03 10:16:11 +00:00
|
|
|
function* postEvalActionDispatcher(actions: ReduxAction<unknown>[]) {
|
|
|
|
|
for (const action of actions) {
|
|
|
|
|
yield put(action);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function* evaluateTreeSaga(postEvalActions?: ReduxAction<unknown>[]) {
|
|
|
|
|
PerformanceTracker.startAsyncTracking(
|
|
|
|
|
PerformanceTransactionName.DATA_TREE_EVALUATION,
|
|
|
|
|
);
|
2021-01-04 10:16:08 +00:00
|
|
|
const unevalTree = yield select(getUnevaluatedDataTree);
|
|
|
|
|
log.debug({ unevalTree });
|
2020-12-28 08:42:43 +00:00
|
|
|
|
|
|
|
|
const workerResponse = yield call(
|
|
|
|
|
worker.request,
|
|
|
|
|
EVAL_WORKER_ACTIONS.EVAL_TREE,
|
|
|
|
|
{
|
2021-01-04 10:16:08 +00:00
|
|
|
unevalTree,
|
2020-12-28 08:42:43 +00:00
|
|
|
widgetTypeConfigMap,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
2021-01-04 10:16:08 +00:00
|
|
|
const { errors, dataTree, dependencies, logs } = workerResponse;
|
|
|
|
|
log.debug({ dataTree: dataTree });
|
2020-12-29 09:25:56 +00:00
|
|
|
logs.forEach((evalLog: any) => log.debug(evalLog));
|
2020-10-21 04:25:32 +00:00
|
|
|
evalErrorHandler(errors);
|
|
|
|
|
yield put({
|
|
|
|
|
type: ReduxActionTypes.SET_EVALUATED_TREE,
|
2021-01-04 10:16:08 +00:00
|
|
|
payload: dataTree,
|
|
|
|
|
});
|
|
|
|
|
yield put({
|
|
|
|
|
type: ReduxActionTypes.SET_EVALUATION_DEPENDENCY_MAP,
|
|
|
|
|
payload: dependencies,
|
2020-10-21 04:25:32 +00:00
|
|
|
});
|
2020-11-03 10:16:11 +00:00
|
|
|
PerformanceTracker.stopAsyncTracking(
|
|
|
|
|
PerformanceTransactionName.DATA_TREE_EVALUATION,
|
|
|
|
|
);
|
|
|
|
|
if (postEvalActions && postEvalActions.length) {
|
|
|
|
|
yield call(postEvalActionDispatcher, postEvalActions);
|
|
|
|
|
}
|
2020-10-21 04:25:32 +00:00
|
|
|
}
|
|
|
|
|
|
2020-12-14 18:48:13 +00:00
|
|
|
export function* evaluateSingleValue(
|
|
|
|
|
binding: string,
|
|
|
|
|
executionParams: Record<string, any> = {},
|
|
|
|
|
) {
|
2020-12-28 08:42:43 +00:00
|
|
|
const dataTree = yield select(getDataTree);
|
|
|
|
|
|
|
|
|
|
const workerResponse = yield call(
|
|
|
|
|
worker.request,
|
|
|
|
|
EVAL_WORKER_ACTIONS.EVAL_SINGLE,
|
|
|
|
|
{
|
2020-12-21 06:14:20 +00:00
|
|
|
dataTree: Object.assign({}, dataTree, {
|
|
|
|
|
[EXECUTION_PARAM_KEY]: executionParams,
|
|
|
|
|
}),
|
2020-10-21 04:25:32 +00:00
|
|
|
binding,
|
2020-12-28 08:42:43 +00:00
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const { errors, value } = workerResponse;
|
|
|
|
|
|
|
|
|
|
evalErrorHandler(errors);
|
|
|
|
|
return value;
|
2020-10-21 04:25:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function* evaluateDynamicTrigger(
|
|
|
|
|
dynamicTrigger: string,
|
2020-11-20 09:30:50 +00:00
|
|
|
callbackData?: Array<any>,
|
2020-10-21 04:25:32 +00:00
|
|
|
) {
|
2020-12-28 08:42:43 +00:00
|
|
|
const unEvalTree = yield select(getUnevaluatedDataTree);
|
|
|
|
|
|
|
|
|
|
const workerResponse = yield call(
|
|
|
|
|
worker.request,
|
|
|
|
|
EVAL_WORKER_ACTIONS.EVAL_TRIGGER,
|
|
|
|
|
{ dataTree: unEvalTree, dynamicTrigger, callbackData },
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const { errors, triggers } = workerResponse;
|
|
|
|
|
evalErrorHandler(errors);
|
|
|
|
|
return triggers;
|
2020-10-21 04:25:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function* clearEvalCache() {
|
2020-12-28 08:42:43 +00:00
|
|
|
yield call(worker.request, EVAL_WORKER_ACTIONS.CLEAR_CACHE);
|
|
|
|
|
|
|
|
|
|
return true;
|
2020-10-21 04:25:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function* clearEvalPropertyCache(propertyPath: string) {
|
2020-12-28 08:42:43 +00:00
|
|
|
yield call(worker.request, EVAL_WORKER_ACTIONS.CLEAR_PROPERTY_CACHE, {
|
|
|
|
|
propertyPath,
|
|
|
|
|
});
|
2020-10-21 04:25:32 +00:00
|
|
|
}
|
|
|
|
|
|
2020-12-01 05:26:35 +00:00
|
|
|
/**
|
|
|
|
|
* clears all cache keys of a widget
|
|
|
|
|
*
|
|
|
|
|
* @param widgetName
|
|
|
|
|
*/
|
|
|
|
|
export function* clearEvalPropertyCacheOfWidget(widgetName: string) {
|
2020-12-28 08:42:43 +00:00
|
|
|
yield call(
|
|
|
|
|
worker.request,
|
|
|
|
|
EVAL_WORKER_ACTIONS.CLEAR_PROPERTY_CACHE_OF_WIDGET,
|
|
|
|
|
{
|
2020-12-01 05:26:35 +00:00
|
|
|
widgetName,
|
2020-12-28 08:42:43 +00:00
|
|
|
},
|
|
|
|
|
);
|
2020-12-01 05:26:35 +00:00
|
|
|
}
|
|
|
|
|
|
2020-10-21 04:25:32 +00:00
|
|
|
export function* validateProperty(
|
|
|
|
|
widgetType: WidgetType,
|
|
|
|
|
property: string,
|
|
|
|
|
value: any,
|
|
|
|
|
props: WidgetProps,
|
|
|
|
|
) {
|
2020-12-28 08:42:43 +00:00
|
|
|
return yield call(worker.request, EVAL_WORKER_ACTIONS.VALIDATE_PROPERTY, {
|
2021-01-04 10:16:08 +00:00
|
|
|
widgetTypeConfigMap,
|
2020-12-28 08:42:43 +00:00
|
|
|
widgetType,
|
|
|
|
|
property,
|
|
|
|
|
value,
|
|
|
|
|
props,
|
|
|
|
|
});
|
2020-10-21 04:25:32 +00:00
|
|
|
}
|
|
|
|
|
|
2020-12-30 13:26:44 +00:00
|
|
|
const FIRST_EVAL_REDUX_ACTIONS = [
|
|
|
|
|
// Pages
|
|
|
|
|
ReduxActionTypes.FETCH_PAGE_SUCCESS,
|
|
|
|
|
ReduxActionTypes.FETCH_PUBLISHED_PAGE_SUCCESS,
|
|
|
|
|
];
|
|
|
|
|
|
2020-10-21 04:25:32 +00:00
|
|
|
const EVALUATE_REDUX_ACTIONS = [
|
2020-12-30 13:26:44 +00:00
|
|
|
...FIRST_EVAL_REDUX_ACTIONS,
|
2020-10-21 04:25:32 +00:00
|
|
|
// 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.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,
|
|
|
|
|
// Batches
|
|
|
|
|
ReduxActionTypes.BATCH_UPDATES_SUCCESS,
|
|
|
|
|
];
|
|
|
|
|
|
2021-01-14 11:15:25 +00:00
|
|
|
const shouldProcessAction = (action: ReduxAction<unknown>) => {
|
|
|
|
|
// debugger;
|
|
|
|
|
if (
|
|
|
|
|
action.type === ReduxActionTypes.BATCH_UPDATES_SUCCESS &&
|
|
|
|
|
Array.isArray(action.payload)
|
|
|
|
|
) {
|
|
|
|
|
const batchedActionTypes = action.payload.map(
|
|
|
|
|
(batchedAction) => batchedAction.type,
|
|
|
|
|
);
|
|
|
|
|
return (
|
|
|
|
|
_.intersection(EVALUATE_REDUX_ACTIONS, batchedActionTypes).length > 0
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
|
2020-12-30 13:26:44 +00:00
|
|
|
function evalQueueBuffer() {
|
2021-01-04 10:16:08 +00:00
|
|
|
let canTake = false;
|
2020-12-30 13:26:44 +00:00
|
|
|
let postEvalActions: any = [];
|
|
|
|
|
const take = () => {
|
2021-01-04 10:16:08 +00:00
|
|
|
if (canTake) {
|
2020-12-30 13:26:44 +00:00
|
|
|
const resp = postEvalActions;
|
|
|
|
|
postEvalActions = [];
|
2021-01-04 10:16:08 +00:00
|
|
|
canTake = false;
|
|
|
|
|
return { postEvalActions: resp, type: "BUFFERED_ACTION" };
|
2020-12-30 13:26:44 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
const flush = () => {
|
2021-01-04 10:16:08 +00:00
|
|
|
if (canTake) {
|
2020-12-30 13:26:44 +00:00
|
|
|
return [take() as Action];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const put = (action: EvaluationReduxAction<unknown | unknown[]>) => {
|
2021-01-14 11:15:25 +00:00
|
|
|
if (!shouldProcessAction(action)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-01-04 10:16:08 +00:00
|
|
|
canTake = true;
|
2021-01-14 11:15:25 +00:00
|
|
|
|
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: () => {
|
2021-01-04 10:16:08 +00:00
|
|
|
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();
|
2021-01-04 10:16:08 +00:00
|
|
|
const initAction = yield take(FIRST_EVAL_REDUX_ACTIONS);
|
|
|
|
|
yield fork(evaluateTreeSaga, initAction.postEvalActions);
|
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-01-14 11:15:25 +00:00
|
|
|
if (shouldProcessAction(action)) {
|
|
|
|
|
yield call(evaluateTreeSaga, action.postEvalActions);
|
|
|
|
|
}
|
2020-10-21 04:25:32 +00:00
|
|
|
}
|
|
|
|
|
// TODO(hetu) need an action to stop listening and evaluate (exit app)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-21 04:25:32 +00:00
|
|
|
}
|