PromucFlow_constructor/app/client/src/sagas/EvaluationsSaga.ts
Hetu Nandu c155a511f1
chore: Separate files for Redux Types (#38559)
Co-authored-by: Diljit <diljit@appsmith.com>
2025-01-10 10:21:54 +05:30

979 lines
27 KiB
TypeScript

import type { ActionPattern, CallEffect, ForkEffect } from "redux-saga/effects";
import {
actionChannel,
all,
call,
delay,
fork,
put,
select,
spawn,
take,
} from "redux-saga/effects";
import type {
ReduxAction,
ReduxActionType,
AnyReduxAction,
} from "actions/ReduxActionTypes";
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
import {
getDataTree,
getUnevaluatedDataTree,
} from "selectors/dataTreeSelectors";
import { getMetaWidgets, getWidgets, getWidgetsMeta } from "sagas/selectors";
import type { WidgetTypeConfigMap } from "WidgetProvider/factory";
import WidgetFactory from "WidgetProvider/factory";
import { evalWorker } from "utils/workerInstances";
import type { EvalError, EvaluationError } from "utils/DynamicBindingUtils";
import { PropertyEvaluationErrorType } from "utils/DynamicBindingUtils";
import { EVAL_WORKER_ACTIONS } from "ee/workers/Evaluation/evalWorkerActions";
import log from "loglevel";
import type { WidgetProps } from "widgets/BaseWidget";
import * as Sentry from "@sentry/react";
import type { Action } from "redux";
import {
EVAL_AND_LINT_REDUX_ACTIONS,
FIRST_EVAL_REDUX_ACTIONS,
getRequiresLinting,
} from "ee/actions/evaluationActionsList";
import {
setDependencyMap,
setEvaluatedTree,
shouldForceEval,
shouldLog,
shouldProcessAction,
shouldTriggerEvaluation,
} from "actions/evaluationActions";
import ConfigTreeActions from "utils/configTree";
import {
showExecutionErrors,
handleJSFunctionExecutionErrorLog,
logJSVarCreatedEvent,
logSuccessfulBindings,
postEvalActionDispatcher,
updateTernDefinitions,
} from "./PostEvaluationSagas";
import type { JSAction, JSCollection } from "entities/JSCollection";
import { getAppMode } from "ee/selectors/applicationSelectors";
import { APP_MODE } from "entities/App";
import { get, isEmpty } from "lodash";
import type { TriggerMeta } from "ee/sagas/ActionExecution/ActionExecutionSagas";
import { executeActionTriggers } from "ee/sagas/ActionExecution/ActionExecutionSagas";
import {
EventType,
TriggerKind,
} from "constants/AppsmithActionConstants/ActionConstants";
import { validate } from "workers/Evaluation/validations";
import { REPLAY_DELAY } from "entities/Replay/replayUtils";
import type { EvaluationVersion } from "constants/EvalConstants";
import type { LogObject } from "entities/AppsmithConsole";
import { ENTITY_TYPE } from "ee/entities/AppsmithConsole/utils";
import type { Replayable } from "entities/Replay/ReplayEntity/ReplayEditor";
import type { FormEvaluationState } from "reducers/evaluationReducers/formEvaluationReducer";
import type { FormEvalActionPayload } from "./FormEvaluationSaga";
import { getSelectedAppTheme } from "selectors/appThemingSelectors";
import { resetWidgetsMetaState, updateMetaState } from "actions/metaActions";
import {
getAllActionValidationConfig,
getAllJSActionsData,
} from "ee/selectors/entitiesSelector";
import type { WidgetEntityConfig } from "ee/entities/DataTree/types";
import type {
ConfigTree,
DataTree,
UnEvalTree,
} from "entities/DataTree/dataTreeTypes";
import { initiateLinting, lintWorker } from "./LintingSagas";
import type {
EvalTreeRequestData,
EvalTreeResponseData,
} from "workers/Evaluation/types";
import type { ActionDescription } from "ee/workers/Evaluation/fns";
import { handleEvalWorkerRequestSaga } from "./EvalWorkerActionSagas";
import { getAppsmithConfigs } from "ee/configs";
import {
type actionDataPayload,
type updateActionDataPayloadType,
} from "actions/pluginActionActions";
import { executeJSUpdates } from "actions/jsPaneActions";
import { setEvaluatedActionSelectorField } from "actions/actionSelectorActions";
import { waitForWidgetConfigBuild } from "./InitSagas";
import { logDynamicTriggerExecution } from "ee/sagas/analyticsSaga";
import { selectFeatureFlags } from "ee/selectors/featureFlagsSelectors";
import { fetchFeatureFlagsInit } from "actions/userActions";
import {
getAffectedJSObjectIdsFromAction,
parseUpdatesAndDeleteUndefinedUpdates,
} from "./EvaluationsSagaUtils";
import { getFeatureFlagsFetched } from "selectors/usersSelectors";
import { getIsCurrentEditorWorkflowType } from "ee/selectors/workflowSelectors";
import { evalErrorHandler } from "./EvalErrorHandler";
import AnalyticsUtil from "ee/utils/AnalyticsUtil";
import { endSpan, startRootSpan } from "instrumentation/generateTraces";
import { transformTriggerEvalErrors } from "ee/sagas/helpers";
import {
getApplicationLastDeployedAt,
getCurrentApplicationId,
getCurrentPageId,
} from "selectors/editorSelectors";
import { getInstanceId } from "ee/selectors/tenantSelectors";
import type {
AffectedJSObjects,
EvaluationReduxAction,
} from "actions/EvaluationReduxActionTypes";
const APPSMITH_CONFIGS = getAppsmithConfigs();
let widgetTypeConfigMap: WidgetTypeConfigMap;
export function* updateDataTreeHandler(
data: {
evalTreeResponse: EvalTreeResponseData;
unevalTree: UnEvalTree;
requiresLogging: boolean;
configTree: ConfigTree;
},
postEvalActions?: Array<AnyReduxAction>,
) {
const { configTree, evalTreeResponse, requiresLogging, unevalTree } = data;
const postEvalActionsToDispatch: Array<AnyReduxAction> =
postEvalActions || [];
const {
dependencies,
errors,
evalMetaUpdates = [],
evaluationOrder,
isCreateFirstTree = false,
isNewWidgetAdded,
jsUpdates,
jsVarsCreatedEvent,
logs,
removedPaths,
staleMetaIds,
undefinedEvalValuesMap,
unEvalUpdates,
updates,
} = evalTreeResponse;
const appMode: ReturnType<typeof getAppMode> = yield select(getAppMode);
if (!isEmpty(staleMetaIds)) {
yield put(resetWidgetsMetaState(staleMetaIds));
}
const parsedUpdates = parseUpdatesAndDeleteUndefinedUpdates(updates);
yield put(setEvaluatedTree(parsedUpdates));
ConfigTreeActions.setConfigTree(configTree);
// if evalMetaUpdates are present only then dispatch updateMetaState
if (evalMetaUpdates.length) {
yield put(updateMetaState(evalMetaUpdates));
}
log.debug({ evalMetaUpdatesLength: evalMetaUpdates.length });
const updatedDataTree: DataTree = yield select(getDataTree);
log.debug({ jsUpdates: jsUpdates });
log.debug({ dataTree: updatedDataTree });
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
logs?.forEach((evalLog: any) => log.debug(evalLog));
yield call(
evalErrorHandler,
errors,
updatedDataTree,
evaluationOrder,
configTree,
removedPaths,
);
AnalyticsUtil.setBlockErrorLogs(isCreateFirstTree);
if (appMode !== APP_MODE.PUBLISHED) {
const jsData: Record<string, unknown> = yield select(getAllJSActionsData);
postEvalActionsToDispatch.push(executeJSUpdates(jsUpdates));
if (requiresLogging) {
yield fork(
logSuccessfulBindings,
unevalTree,
updatedDataTree,
evaluationOrder,
isCreateFirstTree,
isNewWidgetAdded,
configTree,
undefinedEvalValuesMap,
);
}
yield fork(
updateTernDefinitions,
updatedDataTree,
configTree,
unEvalUpdates,
isCreateFirstTree,
jsData,
);
}
yield put(setDependencyMap(dependencies));
if (postEvalActionsToDispatch && postEvalActionsToDispatch.length) {
yield call(postEvalActionDispatcher, postEvalActionsToDispatch);
}
yield call(logJSVarCreatedEvent, jsVarsCreatedEvent);
}
/**
* This saga is responsible for evaluating the data tree
* @param postEvalActions
* @param shouldReplay
* @param requiresLinting
* @param forceEvaluation - if true, will re-evaluate the entire tree
* @returns
* @example
* yield call(evaluateTreeSaga, postEvalActions, shouldReplay, requiresLinting, forceEvaluation)
*/
export function* evaluateTreeSaga(
unEvalAndConfigTree: ReturnType<typeof getUnevaluatedDataTree>,
postEvalActions?: Array<AnyReduxAction>,
shouldReplay = true,
forceEvaluation = false,
requiresLogging = false,
affectedJSObjects: AffectedJSObjects = defaultAffectedJSObjects,
) {
const allActionValidationConfig: ReturnType<
typeof getAllActionValidationConfig
> = yield select(getAllActionValidationConfig);
const unevalTree = unEvalAndConfigTree.unEvalTree;
const widgets: ReturnType<typeof getWidgets> = yield select(getWidgets);
const metaWidgets: ReturnType<typeof getMetaWidgets> =
yield select(getMetaWidgets);
const theme: ReturnType<typeof getSelectedAppTheme> =
yield select(getSelectedAppTheme);
log.debug({ unevalTree, configTree: unEvalAndConfigTree.configTree });
const instanceId: string = yield select(getInstanceId);
const applicationId: string = yield select(getCurrentApplicationId);
const pageId: string = yield select(getCurrentPageId);
const lastDeployedAt: string = yield select(getApplicationLastDeployedAt);
const appMode: ReturnType<typeof getAppMode> = yield select(getAppMode);
const widgetsMeta: ReturnType<typeof getWidgetsMeta> =
yield select(getWidgetsMeta);
const shouldRespondWithLogs = log.getLevel() === log.levels.DEBUG;
const evalTreeRequestData: EvalTreeRequestData = {
cacheProps: {
appMode,
appId: applicationId,
pageId,
timestamp: lastDeployedAt,
instanceId,
},
unevalTree: unEvalAndConfigTree,
widgetTypeConfigMap,
widgets,
theme,
shouldReplay,
allActionValidationConfig,
forceEvaluation,
metaWidgets,
appMode,
widgetsMeta,
shouldRespondWithLogs,
affectedJSObjects,
};
const workerResponse: EvalTreeResponseData = yield call(
evalWorker.request,
EVAL_WORKER_ACTIONS.EVAL_TREE,
evalTreeRequestData,
);
yield call(
updateDataTreeHandler,
{
evalTreeResponse: workerResponse,
unevalTree,
configTree: unEvalAndConfigTree.configTree,
requiresLogging,
},
postEvalActions,
);
}
export function* evaluateActionBindings(
bindings: string[],
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
executionParams: Record<string, any> | string = {},
) {
const span = startRootSpan("evaluateActionBindings");
const workerResponse: { errors: EvalError[]; values: unknown } = yield call(
evalWorker.request,
EVAL_WORKER_ACTIONS.EVAL_ACTION_BINDINGS,
{
bindings,
executionParams,
},
);
const { errors, values } = workerResponse;
yield call(evalErrorHandler, errors);
endSpan(span);
return values;
}
export function* evaluateAndExecuteDynamicTrigger(
dynamicTrigger: string,
eventType: EventType,
triggerMeta: TriggerMeta,
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
callbackData?: Array<any>,
globalContext?: Record<string, unknown>,
) {
const rootSpan = startRootSpan("DataTreeFactory.create");
const unEvalTree: ReturnType<typeof getUnevaluatedDataTree> = yield select(
getUnevaluatedDataTree,
);
endSpan(rootSpan);
log.debug({ execute: dynamicTrigger });
const response: { errors: EvaluationError[]; result: unknown } = yield call(
evalWorker.request,
EVAL_WORKER_ACTIONS.EVAL_TRIGGER,
{
unEvalTree,
dynamicTrigger,
callbackData,
globalContext,
eventType,
triggerMeta,
},
);
const { errors = [] } = response;
const transformedErrors: EvaluationError[] = yield call(
transformTriggerEvalErrors,
errors,
);
if (transformedErrors.length) {
yield fork(showExecutionErrors, transformedErrors);
}
yield fork(logDynamicTriggerExecution, {
dynamicTrigger,
errors: transformedErrors,
triggerMeta,
});
return response;
}
export interface ResponsePayload {
data: {
reason?: string;
resolve?: unknown;
};
success: boolean;
}
/*
* It is necessary to respond back as the worker is waiting with a pending promise and wanting to know if it should
* resolve or reject it with the data the execution has provided
*/
export function* executeTriggerRequestSaga(
trigger: ActionDescription,
eventType: EventType,
triggerMeta: TriggerMeta,
) {
const responsePayload = {
data: null,
error: null,
};
try {
responsePayload.data = yield call(
executeActionTriggers,
trigger,
eventType,
triggerMeta,
);
} catch (error) {
// When error occurs in execution of triggers,
// a success: false is sent to reject the promise
// @ts-expect-error: reason is of type string
responsePayload.error = {
// @ts-expect-error: reason is of type string
message: error.responseData?.[0] || error.message,
};
}
return responsePayload;
}
export function* clearEvalCache() {
/**
* Reset cache in worker before resetting the dataTree
* This order is important because there could be pending evaluation requests that are being processed by the worker
* The diffs generated by the already queued eval request when applied to a reset data tree will cause unexpected crash.
*/
yield call(evalWorker.request, EVAL_WORKER_ACTIONS.CLEAR_CACHE);
yield put({ type: ReduxActionTypes.RESET_DATA_TREE });
return true;
}
interface JSFunctionExecutionResponse {
errors: unknown[];
result: unknown;
logs?: LogObject[];
}
function* executeAsyncJSFunction(
action: JSAction,
collection: JSCollection,
onPageLoad: boolean,
) {
const { id: collectionId, name: collectionName } = collection;
const functionCall = `${collectionName}.${action.name}()`;
const triggerMeta = {
source: {
id: collectionId,
name: `${collectionName}.${action.name}`,
type: ENTITY_TYPE.JSACTION,
},
triggerPropertyName: `${collectionName}.${action.name}`,
triggerKind: TriggerKind.JS_FUNCTION_EXECUTION,
onPageLoad: onPageLoad,
};
const eventType = EventType.ON_JS_FUNCTION_EXECUTE;
const response: JSFunctionExecutionResponse = yield call(
evaluateAndExecuteDynamicTrigger,
functionCall,
eventType,
triggerMeta,
);
return response;
}
export function* executeJSFunction(
action: JSAction,
collection: JSCollection,
onPageLoad: boolean,
) {
const response: {
errors: unknown[];
result: unknown;
logs?: LogObject[];
} = yield call(executeAsyncJSFunction, action, collection, onPageLoad);
const { errors, result } = response;
const isDirty = !!errors.length;
// After every function execution, log execution errors if present
yield call(handleJSFunctionExecutionErrorLog, action, collection, errors);
return { result, isDirty, errors };
}
export // TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function* validateProperty(property: string, value: any, props: WidgetProps) {
const rootSpan = startRootSpan("DataTreeFactory.create");
const unEvalAndConfigTree: ReturnType<typeof getUnevaluatedDataTree> =
yield select(getUnevaluatedDataTree);
endSpan(rootSpan);
const configTree = unEvalAndConfigTree.configTree;
const entityConfig = configTree[props.widgetName] as WidgetEntityConfig;
const validation = entityConfig?.validationPaths[property];
const response: unknown = yield call(
evalWorker.request,
EVAL_WORKER_ACTIONS.VALIDATE_PROPERTY,
{
property,
value,
props,
validation,
},
);
return response;
}
// We are clubbing all pending action's affected JS objects into the buffered action
// So that during that evaluation cycle all affected JS objects are correctly diffed
function mergeJSBufferedActions(
prevAffectedJSAction: AffectedJSObjects,
newAffectedJSAction: AffectedJSObjects,
) {
if (prevAffectedJSAction.isAllAffected || newAffectedJSAction.isAllAffected) {
return {
isAllAffected: true,
ids: [],
};
}
return {
isAllAffected: false,
ids: [...prevAffectedJSAction.ids, ...newAffectedJSAction.ids],
};
}
export const defaultAffectedJSObjects: AffectedJSObjects = {
isAllAffected: false,
ids: [],
};
interface BUFFERED_ACTION {
hasDebouncedHandleUpdate: boolean;
hasBufferedAction: boolean;
actionDataPayloadConsolidated: actionDataPayload[];
}
export function evalQueueBuffer() {
let canTake = false;
let hasDebouncedHandleUpdate = false;
let hasBufferedAction = false;
let actionDataPayloadConsolidated: actionDataPayload = [];
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let collectedPostEvalActions: any = [];
let collectedAffectedJSObjects: AffectedJSObjects = defaultAffectedJSObjects;
const take = () => {
if (canTake) {
const resp = collectedPostEvalActions;
collectedPostEvalActions = [];
const affectedJSObjects = collectedAffectedJSObjects;
collectedAffectedJSObjects = defaultAffectedJSObjects;
canTake = false;
const actionDataPayloadConsolidatedRes = actionDataPayloadConsolidated;
const hasDebouncedHandleUpdateRes = hasDebouncedHandleUpdate;
const hasBufferedActionRes = hasBufferedAction;
actionDataPayloadConsolidated = [];
hasDebouncedHandleUpdate = false;
hasBufferedAction = false;
return {
hasDebouncedHandleUpdate: hasDebouncedHandleUpdateRes,
hasBufferedAction: hasBufferedActionRes,
actionDataPayloadConsolidated: actionDataPayloadConsolidatedRes,
postEvalActions: resp,
affectedJSObjects,
type: ReduxActionTypes.BUFFERED_ACTION,
};
}
};
const flush = () => {
if (canTake) {
return [take() as Action];
}
return [];
};
const put = (action: EvaluationReduxAction<unknown | unknown[]>) => {
if (!shouldProcessAction(action)) {
return;
}
if (action.type === ReduxActionTypes.UPDATE_ACTION_DATA) {
const { actionDataPayload } =
action.payload as updateActionDataPayloadType;
if (actionDataPayload && actionDataPayload.length) {
actionDataPayloadConsolidated = [
...actionDataPayloadConsolidated,
...actionDataPayload,
];
}
hasDebouncedHandleUpdate = true;
canTake = true;
return;
}
hasBufferedAction = true;
canTake = true;
// extract the affected JS action ids from the action and pass them
// as a part of the buffered action
const affectedJSObjects = getAffectedJSObjectIdsFromAction(action);
collectedAffectedJSObjects = mergeJSBufferedActions(
collectedAffectedJSObjects,
affectedJSObjects,
);
const postEvalActions = getPostEvalActions(action);
collectedPostEvalActions.push(...postEvalActions);
};
return {
take,
put,
isEmpty: () => {
return !canTake;
},
flush,
};
}
/**
* Extract the post eval actions from an evaluation action
* Batched actions have post eval actions inside them, extract that
*
* **/
function getPostEvalActions(
action: EvaluationReduxAction<unknown | unknown[]>,
): AnyReduxAction[] {
const postEvalActions: AnyReduxAction[] = [];
if (action.postEvalActions) {
postEvalActions.push(...action.postEvalActions);
}
if (
action.type === ReduxActionTypes.BATCH_UPDATES_SUCCESS &&
Array.isArray(action.payload)
) {
action.payload.forEach((batchedAction) => {
if (batchedAction.postEvalActions) {
postEvalActions.push(
...(batchedAction.postEvalActions as AnyReduxAction[]),
);
}
});
}
return postEvalActions;
}
function* evalAndLintingHandler(
isBlockingCall = true,
action: ReduxAction<unknown>,
options: Partial<{
shouldReplay: boolean;
forceEvaluation: boolean;
requiresLogging: boolean;
affectedJSObjects: AffectedJSObjects;
}>,
) {
const span = startRootSpan("evalAndLintingHandler");
const { affectedJSObjects, forceEvaluation, requiresLogging, shouldReplay } =
options;
const requiresLinting = getRequiresLinting(action);
const requiresEval = shouldTriggerEvaluation(action);
log.debug({
action,
triggeredLinting: requiresLinting,
triggeredEvaluation: requiresEval,
});
if (!requiresEval && !requiresLinting) {
endSpan(span);
return;
}
const rootSpan = startRootSpan("DataTreeFactory.create");
// Generate all the data needed for both eval and linting
const unEvalAndConfigTree: ReturnType<typeof getUnevaluatedDataTree> =
yield select(getUnevaluatedDataTree);
endSpan(rootSpan);
const postEvalActions = getPostEvalActions(action);
const fn: (...args: unknown[]) => CallEffect<unknown> | ForkEffect<unknown> =
isBlockingCall ? call : fork;
const effects = [];
if (requiresEval) {
effects.push(
fn(
evaluateTreeSaga,
unEvalAndConfigTree,
postEvalActions,
shouldReplay,
forceEvaluation,
requiresLogging,
affectedJSObjects,
),
);
}
if (requiresLinting) {
effects.push(fn(initiateLinting, unEvalAndConfigTree, forceEvaluation));
}
yield all(effects);
endSpan(span);
}
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function* evaluationChangeListenerSaga(): any {
const firstEvalActionChannel = yield actionChannel(FIRST_EVAL_REDUX_ACTIONS);
// Explicitly shutdown old worker if present
yield all([call(evalWorker.shutdown), call(lintWorker.shutdown)]);
const [evalWorkerListenerChannel] = yield all([
call(evalWorker.start),
call(lintWorker.start),
]);
const isFFFetched = yield select(getFeatureFlagsFetched);
if (!isFFFetched) {
yield call(fetchFeatureFlagsInit);
yield take(ReduxActionTypes.FETCH_FEATURE_FLAGS_SUCCESS);
}
const featureFlags: Record<string, boolean> =
yield select(selectFeatureFlags);
yield call(evalWorker.request, EVAL_WORKER_ACTIONS.SETUP, {
cloudHosting: !!APPSMITH_CONFIGS.cloudHosting,
featureFlags: featureFlags,
});
yield spawn(handleEvalWorkerRequestSaga, evalWorkerListenerChannel);
const initAction: EvaluationReduxAction<unknown> = yield take(
firstEvalActionChannel,
);
firstEvalActionChannel.close();
// Wait for widget config build to complete before starting evaluation only if the current editor is not a workflow
const isCurrentEditorWorkflowType = yield select(
getIsCurrentEditorWorkflowType,
);
if (!isCurrentEditorWorkflowType) {
yield call(waitForWidgetConfigBuild);
}
widgetTypeConfigMap = WidgetFactory.getWidgetTypeConfigMap();
yield fork(evalAndLintingHandler, false, initAction, {
shouldReplay: false,
forceEvaluation: false,
// during startup all JS objects are affected
affectedJSObjects: {
ids: [],
isAllAffected: true,
},
});
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const evtActionChannel: ActionPattern<Action<any>> = yield actionChannel(
EVAL_AND_LINT_REDUX_ACTIONS,
evalQueueBuffer(),
);
while (true) {
const action: EvaluationReduxAction<unknown | unknown[]> =
yield take(evtActionChannel);
const { payload, type } = action;
if (type === ReduxActionTypes.UPDATE_ACTION_DATA) {
yield call(
evalWorker.request,
EVAL_WORKER_ACTIONS.UPDATE_ACTION_DATA,
(payload as updateActionDataPayloadType).actionDataPayload,
);
continue;
}
if (type !== ReduxActionTypes.BUFFERED_ACTION) {
const affectedJSObjects = getAffectedJSObjectIdsFromAction(action);
yield call(evalAndLintingHandler, true, action, {
shouldReplay: get(action, "payload.shouldReplay"),
forceEvaluation: shouldForceEval(action),
requiresLogging: shouldLog(action),
affectedJSObjects,
});
continue;
}
// all buffered debounced actions are handled here
const {
actionDataPayloadConsolidated,
hasBufferedAction,
hasDebouncedHandleUpdate,
} = action as unknown as BUFFERED_ACTION;
if (hasDebouncedHandleUpdate) {
yield call(
evalWorker.request,
EVAL_WORKER_ACTIONS.UPDATE_ACTION_DATA,
actionDataPayloadConsolidated,
);
}
if (hasBufferedAction) {
// We are dequing actions from the buffer and inferring the JS actions affected by each
// action. Through this we know ahead the nodes we need to specifically diff, thereby improving performance.
const affectedJSObjects = getAffectedJSObjectIdsFromAction(action);
yield call(evalAndLintingHandler, true, action, {
shouldReplay: get(action, "payload.shouldReplay"),
forceEvaluation: shouldForceEval(action),
requiresLogging: shouldLog(action),
affectedJSObjects,
});
}
}
}
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function* evaluateActionSelectorFieldSaga(action: any) {
const { id, type, value } = action.payload;
try {
const workerResponse: {
errors: Array<unknown>;
result: unknown;
} = yield call(evalWorker.request, EVAL_WORKER_ACTIONS.EVAL_EXPRESSION, {
expression: value,
});
const lintErrors = (workerResponse.errors || []).filter(
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(error: any) => error.errorType !== PropertyEvaluationErrorType.LINT,
);
if (workerResponse.result) {
const validation = validate({ type }, workerResponse.result, {}, "");
if (!validation.isValid)
validation.messages?.map((message) => {
lintErrors.unshift({
...validation,
...{
errorType: PropertyEvaluationErrorType.VALIDATION,
errorMessage: message,
},
});
});
}
yield put(
setEvaluatedActionSelectorField({
id,
evaluatedValue: {
value: workerResponse.result as string,
errors: lintErrors,
},
}),
);
} catch (e) {
log.error(e);
Sentry.captureException(e);
}
}
export function* updateReplayEntitySaga(
actionPayload: ReduxAction<{
entityId: string;
entity: Replayable;
entityType: ENTITY_TYPE;
}>,
) {
//Delay updates to replay object to not persist every keystroke
yield delay(REPLAY_DELAY);
const { entity, entityId, entityType } = actionPayload.payload;
const workerResponse: unknown = yield call(
evalWorker.request,
EVAL_WORKER_ACTIONS.UPDATE_REPLAY_OBJECT,
{
entityId,
entity,
entityType,
},
);
return workerResponse;
}
export function* workerComputeUndoRedo(operation: string, entityId: string) {
const workerResponse: unknown = yield call(evalWorker.request, operation, {
entityId,
});
return workerResponse;
}
// Type to represent the state of the evaluation reducer
export interface FormEvaluationConfig
extends ReduxAction<FormEvalActionPayload> {
currentEvalState: FormEvaluationState;
}
// Function to trigger the form eval job in the worker
export function* evalFormConfig(formEvaluationConfigObj: FormEvaluationConfig) {
const workerResponse: unknown = yield call(
evalWorker.request,
EVAL_WORKER_ACTIONS.INIT_FORM_EVAL,
formEvaluationConfigObj,
);
return workerResponse;
}
export function* setAppVersionOnWorkerSaga(action: {
type: ReduxActionType;
payload: EvaluationVersion;
}) {
const version: EvaluationVersion = action.payload;
yield call(evalWorker.request, EVAL_WORKER_ACTIONS.SET_EVALUATION_VERSION, {
version,
});
}
export default function* evaluationSagaListeners() {
yield take(ReduxActionTypes.START_EVALUATION);
while (true) {
try {
yield call(evaluationChangeListenerSaga);
} catch (e) {
log.error(e);
Sentry.captureException(e);
}
}
}