chore: optimise first evaluation by adding worker scope cache (CE) (#38068)

## Description
- Add evalContextCache to reduce the number of times the evalContext is created from the contextTree
- remove resetWorkerGlobalScope execution before every evalSync
- Instantiate the evalWorker in src/index.tsx


Fixes #`Issue Number`  
_or_  
Fixes `Issue URL`
> [!WARNING]  
> _If no issue exists, please create an issue first, and check with the
maintainers if the issue is valid._

## Automation

/ok-to-test tags="@tag.All"

### 🔍 Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/12295048843>
> Commit: 04b1e859b02282ba9efa96aea25acc9f20098061
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=12295048843&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.All`
> Spec:
> <hr>Thu, 12 Dec 2024 12:21:04 UTC
<!-- end of auto-generated comment: Cypress test results  -->


## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [ ] No


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced a new property `actionNames` to enhance action tracking
within JavaScript action entities.
- Added a new feature flag `release_evaluation_scope_cache` for improved
feature management.
- Implemented a new function `isPropertyAnEntityAction` to identify
action properties in JavaScript entities.
- Enhanced the `loadAppEntities` method to improve JavaScript library
loading processes.
- Updated the evaluation context initialization process to utilize
`getDataTreeContext`.
- Expanded the `WIDGET_CONFIG_MAP` to include detailed configurations
for various widget types.

- **Bug Fixes**
- Enhanced error handling for unsafe function calls in evaluation logic.
- Improved error handling and logging for library installation and
uninstallation processes.

- **Tests**
- Expanded test coverage for action bindings and dependencies in the
`DataTreeEvaluator`.
- Updated tests to validate the new `actionNames` property and its
integration.
- Modified tests to ensure correct functionality of the `evaluateSync`
function.
- Added new test cases to assess the behavior of the evaluator with
widget updates.

- **Chores**
- Streamlined imports and initialization of worker instances across
various files.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Diljit 2024-12-13 09:44:19 +05:30 committed by GitHub
parent 4ea5021e88
commit abb0878388
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 201 additions and 89 deletions

View File

@ -191,6 +191,7 @@ describe("generateDataTreeJSAction", () => {
myVar1: "SMART_SUBSTITUTE",
myVar2: "SMART_SUBSTITUTE",
},
actionNames: new Set(["myFun2", "myFun1"]),
};
const resultData = generateDataTreeJSAction(jsCollection);
@ -389,6 +390,7 @@ describe("generateDataTreeJSAction", () => {
myVar1: "SMART_SUBSTITUTE",
myVar2: "SMART_SUBSTITUTE",
},
actionNames: new Set(["myFun2", "myFun1"]),
};
const result = generateDataTreeJSAction(jsCollection);

View File

@ -46,7 +46,7 @@ export const generateDataTreeJSAction = (
const dependencyMap: DependencyMap = {};
dependencyMap["body"] = [];
const actions = js.config.actions;
const actions = js.config.actions || [];
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const actionsData: Record<string, any> = {};
@ -89,6 +89,7 @@ export const generateDataTreeJSAction = (
dynamicBindingPathList: dynamicBindingPathList,
variables: listVariables,
dependencyMap: dependencyMap,
actionNames: new Set(actions.map((action) => action.name)),
},
};
};

View File

@ -96,6 +96,7 @@ export interface JSActionEntityConfig extends EntityConfig {
moduleId?: string;
moduleInstanceId?: string;
isPublic?: boolean;
actionNames: Set<string>;
}
export interface JSActionEntity {

View File

@ -43,6 +43,7 @@ export const FEATURE_FLAG = {
"release_table_custom_loading_state_enabled",
release_custom_widget_ai_builder: "release_custom_widget_ai_builder",
ab_request_new_integration_enabled: "ab_request_new_integration_enabled",
release_evaluation_scope_cache: "release_evaluation_scope_cache",
release_table_html_column_type_enabled:
"release_table_html_column_type_enabled",
} as const;
@ -83,6 +84,7 @@ export const DEFAULT_FEATURE_FLAG_VALUE: FeatureFlags = {
release_table_custom_loading_state_enabled: false,
release_custom_widget_ai_builder: false,
ab_request_new_integration_enabled: false,
release_evaluation_scope_cache: false,
release_table_html_column_type_enabled: false,
};

View File

@ -40,8 +40,7 @@ export enum ExecutionType {
/**
* This method returns new dataTree with entity function and platform function
*/
export const addDataTreeToContext = (args: {
EVAL_CONTEXT: EvalContext;
export const getDataTreeContext = (args: {
dataTree: Readonly<DataTree>;
removeEntityFunctions?: boolean;
isTriggerBased: boolean;
@ -50,10 +49,11 @@ export const addDataTreeToContext = (args: {
const {
configTree,
dataTree,
EVAL_CONTEXT,
isTriggerBased,
removeEntityFunctions = false,
} = args;
const EVAL_CONTEXT: EvalContext = {};
const dataTreeEntries = Object.entries(dataTree);
const entityFunctionCollection: Record<string, Record<string, Function>> = {};
@ -95,16 +95,23 @@ export const addDataTreeToContext = (args: {
);
}
if (removeEntityFunctions)
return removeEntityFunctionsFromEvalContext(
if (removeEntityFunctions) {
removeEntityFunctionsFromEvalContext(
entityFunctionCollection,
EVAL_CONTEXT,
);
if (!isTriggerBased) return;
return EVAL_CONTEXT;
}
if (!isTriggerBased) {
return EVAL_CONTEXT;
}
// if eval is not trigger based i.e., sync eval then we skip adding entity function to evalContext
addEntityFunctionsToEvalContext(EVAL_CONTEXT, entityFunctionCollection);
return EVAL_CONTEXT;
};
export const addEntityFunctionsToEvalContext = (

View File

@ -1104,6 +1104,19 @@ export const isNotEntity = (entity: DataTreeEntity) => {
export const isEntityAction = (entity: DataTreeEntity) => {
return isAction(entity);
};
export const isPropertyAnEntityAction = (
entity: DataTreeEntity,
propertyPath: string,
entityConfig: DataTreeEntityConfig,
) => {
if (!isJSAction(entity)) return false;
const { actionNames } = entityConfig as JSActionEntityConfig;
return actionNames.has(propertyPath);
};
export const convertMicroDiffToDeepDiff = (
microDiffDifferences: Difference[],
): Diff<unknown, unknown>[] =>

View File

@ -25,7 +25,6 @@ import {
waitForSegmentInit,
waitForFetchUserSuccess,
} from "ee/sagas/userSagas";
import { waitForFetchEnvironments } from "ee/sagas/EnvironmentSagas";
import { fetchJSCollectionsForView } from "actions/jsActionActions";
import {
fetchAppThemesAction,
@ -154,14 +153,6 @@ export default class AppViewerEngine extends AppEngine {
yield call(waitForSegmentInit, true);
endSpan(waitForSegmentSpan);
const waitForEnvironmentsSpan = startNestedSpan(
"AppViewerEngine.waitForFetchEnvironments",
rootSpan,
);
yield call(waitForFetchEnvironments);
endSpan(waitForEnvironmentsSpan);
yield put(fetchAllPageEntityCompletion([executePageLoadActions()]));
endSpan(loadAppEntitiesSpan);

View File

@ -1,5 +1,7 @@
// This file must be executed as early as possible to ensure the preloads are triggered ASAP
import "./preload-route-chunks";
// Initialise eval worker instance
import "utils/workerInstances";
import React from "react";
import "./wdyr";

View File

@ -103,7 +103,8 @@ import log from "loglevel";
import { EMPTY_RESPONSE } from "components/editorComponents/emptyResponse";
import type { AppState } from "ee/reducers";
import { DEFAULT_EXECUTE_ACTION_TIMEOUT_MS } from "ee/constants/ApiConstants";
import { evaluateActionBindings, evalWorker } from "sagas/EvaluationsSaga";
import { evaluateActionBindings } from "sagas/EvaluationsSaga";
import { evalWorker } from "utils/workerInstances";
import { isBlobUrl, parseBlobUrl } from "utils/AppsmithUtils";
import { getType, Types } from "utils/TypeHelpers";
import { matchPath } from "react-router";

View File

@ -5,7 +5,7 @@ import { showToastOnExecutionError } from "sagas/ActionExecution/errorUtils";
import { setUserCurrentGeoLocation } from "actions/browserRequestActions";
import type { Channel } from "redux-saga";
import { channel } from "redux-saga";
import { evalWorker } from "sagas/EvaluationsSaga";
import { evalWorker } from "utils/workerInstances";
import type {
TGetGeoLocationDescription,
TWatchGeoLocationDescription,

View File

@ -12,10 +12,10 @@ import type { TMessage } from "utils/MessageUtil";
import { MessageType } from "utils/MessageUtil";
import type { ResponsePayload } from "../sagas/EvaluationsSaga";
import {
evalWorker,
executeTriggerRequestSaga,
updateDataTreeHandler,
} from "../sagas/EvaluationsSaga";
import { evalWorker } from "utils/workerInstances";
import { handleStoreOperations } from "./ActionExecution/StoreActionSaga";
import type { EvalTreeResponseData } from "workers/Evaluation/types";
import isEmpty from "lodash/isEmpty";

View File

@ -2,8 +2,8 @@ import {
defaultAffectedJSObjects,
evalQueueBuffer,
evaluateTreeSaga,
evalWorker,
} from "./EvaluationsSaga";
import { evalWorker } from "utils/workerInstances";
import { expectSaga } from "redux-saga-test-plan";
import { EVAL_WORKER_ACTIONS } from "ee/workers/Evaluation/evalWorkerActions";
import { select } from "redux-saga/effects";

View File

@ -25,7 +25,7 @@ import {
import { getMetaWidgets, getWidgets, getWidgetsMeta } from "sagas/selectors";
import type { WidgetTypeConfigMap } from "WidgetProvider/factory";
import WidgetFactory from "WidgetProvider/factory";
import { GracefulWorkerService } from "utils/WorkerUtil";
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";
@ -120,18 +120,6 @@ import { getInstanceId } from "ee/selectors/tenantSelectors";
const APPSMITH_CONFIGS = getAppsmithConfigs();
export const evalWorker = new GracefulWorkerService(
new Worker(
new URL("../workers/Evaluation/evaluation.worker.ts", import.meta.url),
{
type: "module",
// Note: the `Worker` part of the name is slightly important LinkRelPreload_spec.js
// relies on it to find workers in the list of all requests.
name: "evalWorker",
},
),
);
let widgetTypeConfigMap: WidgetTypeConfigMap;
export function* updateDataTreeHandler(
@ -902,5 +890,3 @@ export default function* evaluationSagaListeners() {
}
}
}
export { evalWorker as EvalWorker };

View File

@ -21,7 +21,7 @@ import { getCurrentApplicationId } from "selectors/editorSelectors";
import CodemirrorTernService from "utils/autocomplete/CodemirrorTernService";
import { EVAL_WORKER_ACTIONS } from "ee/workers/Evaluation/evalWorkerActions";
import { validateResponse } from "./ErrorSagas";
import { EvalWorker } from "./EvaluationsSaga";
import { evalWorker as EvalWorker } from "utils/workerInstances";
import log from "loglevel";
import { APP_MODE } from "entities/App";
import { getAppMode } from "ee/selectors/applicationSelectors";

View File

@ -41,6 +41,7 @@ import type { EvalTreeResponseData } from "workers/Evaluation/types";
import { endSpan, startRootSpan } from "UITelemetry/generateTraces";
import { getJSActionPathNameToDisplay } from "ee/utils/actionExecutionUtils";
import { showToastOnExecutionError } from "./ActionExecution/errorUtils";
import { waitForFetchEnvironments } from "ee/sagas/EnvironmentSagas";
let successfulBindingsMap: SuccessfulBindingMap | undefined;
@ -190,6 +191,9 @@ export function* logSuccessfulBindings(
}
export function* postEvalActionDispatcher(actions: Array<AnyReduxAction>) {
// Wait for environments api fetch before dispatching actions
yield call(waitForFetchEnvironments);
for (const action of actions) {
yield put(action);
}

View File

@ -0,0 +1,13 @@
import { GracefulWorkerService } from "./WorkerUtil";
export const evalWorker = new GracefulWorkerService(
new Worker(
new URL("../workers/Evaluation/evaluation.worker.ts", import.meta.url),
{
type: "module",
// Note: the `Worker` part of the name is slightly important LinkRelPreload_spec.js
// relies on it to find workers in the list of all requests.
name: "evalWorker",
},
),
);

View File

@ -5,7 +5,7 @@ import { EvalErrorTypes, getEvalValuePath } from "utils/DynamicBindingUtils";
import type { JSUpdate, ParsedJSSubAction } from "utils/JSPaneUtils";
import { parseJSObject, isJSFunctionProperty } from "@shared/ast";
import type DataTreeEvaluator from "workers/common/DataTreeEvaluator";
import evaluateSync from "workers/Evaluation/evaluate";
import { evaluateSync } from "workers/Evaluation/evaluate";
import type { DataTreeDiff } from "ee/workers/Evaluation/evaluationUtils";
import {
DataTreeDiffEvent,

View File

@ -195,9 +195,6 @@ describe("saveResolvedFunctionsAndJSUpdates", function () {
{
key: "myFun2",
},
{
key: "myFun2",
},
],
bindingPaths: {
body: "SMART_SUBSTITUTE",
@ -216,6 +213,7 @@ describe("saveResolvedFunctionsAndJSUpdates", function () {
pluginType: "JS",
name: "JSObject1",
actionId: "64013546b956c26882acc587",
actionNames: new Set(["myFun1", "myFun2"]),
} as JSActionEntityConfig,
};
const entityName = "JSObject1";

View File

@ -6,7 +6,7 @@ import type { EvalContext } from "workers/Evaluation/evaluate";
import { createEvaluationContext } from "workers/Evaluation/evaluate";
import { MessageType } from "utils/MessageUtil";
import {
addDataTreeToContext,
getDataTreeContext,
addPlatformFunctionsToEvalContext,
} from "ee/workers/Evaluation/Actions";
import TriggerEmitter, { BatchKey } from "../fns/utils/TriggerEmitter";
@ -548,12 +548,13 @@ describe("Test addDataTreeToContext method", () => {
const evalContext: EvalContext = {};
beforeAll(() => {
addDataTreeToContext({
EVAL_CONTEXT: evalContext,
const EVAL_CONTEXT = getDataTreeContext({
dataTree: dataTree as unknown as DataTree,
configTree,
isTriggerBased: true,
});
Object.assign(evalContext, EVAL_CONTEXT);
addPlatformFunctionsToEvalContext(evalContext);
});

View File

@ -1,4 +1,5 @@
import evaluate, {
import {
evaluateSync,
createEvaluationContext,
evaluateAsync,
} from "workers/Evaluation/evaluate";
@ -54,29 +55,29 @@ describe("evaluateSync", () => {
});
it("unescapes string before evaluation", () => {
const js = '\\"Hello!\\"';
const response = evaluate(js, {}, false);
const response = evaluateSync(js, {}, false);
expect(response.result).toBe("Hello!");
});
it("evaluate string post unescape in v1", () => {
const js = '[1, 2, 3].join("\\\\n")';
const response = evaluate(js, {}, false);
const response = evaluateSync(js, {}, false);
expect(response.result).toBe("1\n2\n3");
});
it("evaluate string without unescape in v2", () => {
self.evaluationVersion = 2;
const js = '[1, 2, 3].join("\\n")';
const response = evaluate(js, {}, false);
const response = evaluateSync(js, {}, false);
expect(response.result).toBe("1\n2\n3");
});
it("throws error for undefined js", () => {
// @ts-expect-error: Types are not available
expect(() => evaluate(undefined, {})).toThrow(TypeError);
expect(() => evaluateSync(undefined, {})).toThrow(TypeError);
});
it("Returns for syntax errors", () => {
const response1 = evaluate("wrongJS", {}, false);
const response1 = evaluateSync("wrongJS", {}, false);
expect(response1).toStrictEqual({
result: undefined,
@ -100,7 +101,7 @@ describe("evaluateSync", () => {
},
],
});
const response2 = evaluate("{}.map()", {}, false);
const response2 = evaluateSync("{}.map()", {}, false);
expect(response2).toStrictEqual({
result: undefined,
@ -130,21 +131,21 @@ describe("evaluateSync", () => {
});
it("evaluates value from data tree", () => {
const js = "Input1.text";
const response = evaluate(js, dataTree, false);
const response = evaluateSync(js, dataTree, false);
expect(response.result).toBe("value");
});
it("disallows unsafe function calls", () => {
const js = "setImmediate(() => {}, 100)";
const response = evaluate(js, dataTree, false);
const response = evaluateSync(js, dataTree, false);
expect(response).toStrictEqual({
result: undefined,
errors: [
{
errorMessage: {
name: "ReferenceError",
message: "setImmediate is not defined",
name: "TypeError",
message: "setImmediate is not a function",
},
errorType: "PARSE",
kind: {
@ -166,51 +167,51 @@ describe("evaluateSync", () => {
});
it("has access to extra library functions", () => {
const js = "_.add(1,2)";
const response = evaluate(js, dataTree, false);
const response = evaluateSync(js, dataTree, false);
expect(response.result).toBe(3);
});
it("evaluates functions with callback data", () => {
const js = "(arg1, arg2) => arg1.value + arg2";
const callbackData = [{ value: "test" }, "1"];
const response = evaluate(js, dataTree, false, {}, callbackData);
const response = evaluateSync(js, dataTree, false, {}, callbackData);
expect(response.result).toBe("test1");
});
it("handles EXPRESSIONS with new lines", () => {
let js = "\n";
let response = evaluate(js, dataTree, false);
let response = evaluateSync(js, dataTree, false);
expect(response.errors.length).toBe(0);
js = "\n\n\n";
response = evaluate(js, dataTree, false);
response = evaluateSync(js, dataTree, false);
expect(response.errors.length).toBe(0);
});
it("handles TRIGGERS with new lines", () => {
let js = "\n";
let response = evaluate(js, dataTree, false, undefined, undefined);
let response = evaluateSync(js, dataTree, false, undefined, undefined);
expect(response.errors.length).toBe(0);
js = "\n\n\n";
response = evaluate(js, dataTree, false, undefined, undefined);
response = evaluateSync(js, dataTree, false, undefined, undefined);
expect(response.errors.length).toBe(0);
});
it("handles ANONYMOUS_FUNCTION with new lines", () => {
let js = "\n";
let response = evaluate(js, dataTree, false, undefined, undefined);
let response = evaluateSync(js, dataTree, false, undefined, undefined);
expect(response.errors.length).toBe(0);
js = "\n\n\n";
response = evaluate(js, dataTree, false, undefined, undefined);
response = evaluateSync(js, dataTree, false, undefined, undefined);
expect(response.errors.length).toBe(0);
});
it("has access to this context", () => {
const js = "this.contextVariable";
const thisContext = { contextVariable: "test" };
const response = evaluate(js, dataTree, false, { thisContext });
const response = evaluateSync(js, dataTree, false, { thisContext });
expect(response.result).toBe("test");
// there should not be any error when accessing "this" variables
@ -220,7 +221,7 @@ describe("evaluateSync", () => {
it("has access to additional global context", () => {
const js = "contextVariable";
const globalContext = { contextVariable: "test" };
const response = evaluate(js, dataTree, false, { globalContext });
const response = evaluateSync(js, dataTree, false, { globalContext });
expect(response.result).toBe("test");
expect(response.errors).toHaveLength(0);

View File

@ -18,7 +18,6 @@ import WidgetFactory from "WidgetProvider/factory";
import { generateDataTreeWidget } from "entities/DataTree/dataTreeWidget";
import { sortObjectWithArray } from "../../../utils/treeUtils";
import klona from "klona";
import { APP_MODE } from "entities/App";
const klonaFullSpy = jest.fn();

View File

@ -23,7 +23,7 @@ import {
PrimitiveErrorModifier,
TypeErrorModifier,
} from "./errorModifier";
import { addDataTreeToContext } from "ee/workers/Evaluation/Actions";
import { getDataTreeContext } from "ee/workers/Evaluation/Actions";
import { set } from "lodash";
import { klona } from "klona";
import { getEntityNameAndPropertyPath } from "ee/workers/Evaluation/evaluationUtils";
@ -103,7 +103,7 @@ const ignoreGlobalObjectKeys = new Set([
"location",
]);
function resetWorkerGlobalScope() {
export function resetWorkerGlobalScope() {
const jsLibraryAccessorSet = JSLibraryAccessor.getSet();
for (const key of Object.keys(self)) {
@ -273,14 +273,15 @@ export const createEvaluationContext = (args: createEvaluationContextArgs) => {
Object.assign(EVAL_CONTEXT, context.globalContext);
}
addDataTreeToContext({
EVAL_CONTEXT,
const dataTreeContext = getDataTreeContext({
dataTree,
configTree,
removeEntityFunctions: !!removeEntityFunctions,
isTriggerBased,
});
Object.assign(EVAL_CONTEXT, dataTreeContext);
overrideEvalContext(EVAL_CONTEXT, context?.overrideContext);
return EVAL_CONTEXT;
@ -365,7 +366,7 @@ export function setEvalContext({
Object.assign(self, evalContext);
}
export default function evaluateSync(
export function evaluateSync(
userScript: string,
dataTree: DataTree,
isJSCollection: boolean,
@ -373,7 +374,8 @@ export default function evaluateSync(
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
evalArguments?: Array<any>,
configTree?: ConfigTree,
configTree: ConfigTree = {},
evalContextCache?: EvalContext,
): EvalResult {
return (function () {
const errors: EvaluationError[] = [];
@ -394,16 +396,34 @@ export default function evaluateSync(
};
}
resetWorkerGlobalScope();
self["$isDataField"] = true;
const EVAL_CONTEXT: EvalContext = {};
setEvalContext({
dataTree,
configTree,
isDataField: true,
isTriggerBased: isJSCollection,
context,
evalArguments,
});
///// Adding callback data
EVAL_CONTEXT.ARGUMENTS = evalArguments;
//// Adding contextual data not part of data tree
EVAL_CONTEXT.THIS_CONTEXT = context?.thisContext || {};
if (context?.globalContext) {
Object.assign(EVAL_CONTEXT, context.globalContext);
}
if (evalContextCache) {
Object.assign(EVAL_CONTEXT, evalContextCache);
} else {
const dataTreeContext = getDataTreeContext({
dataTree,
configTree,
removeEntityFunctions: false,
isTriggerBased: isJSCollection,
});
Object.assign(EVAL_CONTEXT, dataTreeContext);
}
overrideEvalContext(EVAL_CONTEXT, context?.overrideContext);
Object.assign(self, EVAL_CONTEXT);
try {
result = indirectEval(script);

View File

@ -15,7 +15,7 @@ import type {
import { isWidget } from "ee/workers/Evaluation/evaluationUtils";
import { klona } from "klona";
import { getDynamicBindings, isDynamicValue } from "utils/DynamicBindingUtils";
import evaluateSync, { setEvalContext } from "../evaluate";
import { evaluateSync, setEvalContext } from "../evaluate";
import type { DescendantWidgetMap } from "sagas/WidgetOperationUtils";
import type { MetaState } from "reducers/entityReducers/metaReducer";
import type { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";

View File

@ -1,4 +1,3 @@
import { createMessage, customJSLibraryMessages } from "ee/constants/messages";
import difference from "lodash/difference";
import type { Def } from "tern";
import { invalidEntityIdentifiers } from "workers/common/DependencyMap/utils";
@ -34,7 +33,7 @@ enum LibraryInstallError {
class ImportError extends Error {
code = LibraryInstallError.ImportError;
constructor(url: string) {
super(createMessage(customJSLibraryMessages.IMPORT_URL_ERROR, url));
super(`The script at ${url} cannot be installed.`);
this.name = "ImportError";
}
}
@ -42,7 +41,7 @@ class ImportError extends Error {
class TernDefinitionError extends Error {
code = LibraryInstallError.TernDefinitionError;
constructor(name: string) {
super(createMessage(customJSLibraryMessages.DEFS_FAILED_ERROR, name));
super(`Failed to generate autocomplete definitions for ${name}.`);
this.name = "TernDefinitionError";
}
}

View File

@ -1,9 +1,7 @@
import type { FeatureFlags } from "ee/entities/FeatureFlag";
export class WorkerEnv {
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static flags: any;
static flags: FeatureFlags = {} as FeatureFlags;
static cloudHosting: boolean;
static setFeatureFlags(featureFlags: FeatureFlags) {

View File

@ -38,6 +38,7 @@ import type { DataTreeDiff } from "ee/workers/Evaluation/evaluationUtils";
import {
convertMicroDiffToDeepDiff,
getAllPathsBasedOnDiffPaths,
isPropertyAnEntityAction,
} from "ee/workers/Evaluation/evaluationUtils";
import {
@ -86,8 +87,11 @@ import {
EXECUTION_PARAM_REFERENCE_REGEX,
THIS_DOT_PARAMS_KEY,
} from "constants/AppsmithActionConstants/ActionConstants";
import type { EvalResult, EvaluateContext } from "workers/Evaluation/evaluate";
import evaluateSync, {
import {
evaluateSync,
resetWorkerGlobalScope,
type EvalResult,
type EvaluateContext,
evaluateAsync,
setEvalContext,
} from "workers/Evaluation/evaluate";
@ -148,6 +152,8 @@ import {
EComputationCacheName,
type ICacheProps,
} from "../AppComputationCache/types";
import { getDataTreeContext } from "ee/workers/Evaluation/Actions";
import { WorkerEnv } from "workers/Evaluation/handlers/workerEnv";
type SortedDependencies = Array<string>;
export interface EvalProps {
@ -1059,6 +1065,8 @@ export default class DataTreeEvaluator {
staleMetaIds: string[];
contextTree: DataTree;
} {
resetWorkerGlobalScope();
const safeTree = klonaJSON(unEvalTree);
const dataStore = DataStore.getDataStore();
const dataStoreClone = klonaJSON(dataStore);
@ -1084,6 +1092,16 @@ export default class DataTreeEvaluator {
const { isFirstTree, metaWidgets, unevalUpdates } = options;
let staleMetaIds: string[] = [];
let evalContextCache;
if (WorkerEnv.flags.release_evaluation_scope_cache) {
evalContextCache = getDataTreeContext({
dataTree: contextTree,
configTree: oldConfigTree,
isTriggerBased: false,
});
}
try {
for (const fullPropertyPath of evaluationOrder) {
const { entityName, propertyPath } =
@ -1093,6 +1111,11 @@ export default class DataTreeEvaluator {
if (!isWidgetActionOrJsObject(entity)) continue;
// Skip evaluations for actions in JSObjects
if (isPropertyAnEntityAction(entity, propertyPath, entityConfig)) {
continue;
}
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let unEvalPropertyValue = get(contextTree as any, fullPropertyPath);
@ -1144,6 +1167,7 @@ export default class DataTreeEvaluator {
contextData,
undefined,
fullPropertyPath,
evalContextCache,
);
} catch (error) {
this.errors.push({
@ -1209,6 +1233,13 @@ export default class DataTreeEvaluator {
set(contextTree, fullPropertyPath, parsedValue);
set(safeTree, fullPropertyPath, klonaJSON(parsedValue));
if (
WorkerEnv.flags.release_evaluation_scope_cache &&
evalContextCache
) {
set(evalContextCache, fullPropertyPath, klonaJSON(parsedValue));
}
staleMetaIds = staleMetaIds.concat(
getStaleMetaStateIds({
entity: widgetEntity,
@ -1254,6 +1285,18 @@ export default class DataTreeEvaluator {
set(contextTree, fullPropertyPath, evalPropertyValue);
set(safeTree, fullPropertyPath, klonaJSON(evalPropertyValue));
if (
WorkerEnv.flags.release_evaluation_scope_cache &&
evalContextCache
) {
set(
evalContextCache,
fullPropertyPath,
klonaJSON(evalPropertyValue),
);
}
break;
}
case ENTITY_TYPE.JSACTION: {
@ -1294,6 +1337,18 @@ export default class DataTreeEvaluator {
set(contextTree, fullPropertyPath, evalValue);
set(safeTree, fullPropertyPath, valueForSafeTree);
if (
WorkerEnv.flags.release_evaluation_scope_cache &&
evalContextCache
) {
set(
evalContextCache,
fullPropertyPath,
klonaJSON(evalPropertyValue),
);
}
JSObjectCollection.setVariableValue(evalValue, fullPropertyPath);
JSObjectCollection.setPrevUnEvalState({
fullPath: fullPropertyPath,
@ -1306,6 +1361,17 @@ export default class DataTreeEvaluator {
default:
set(contextTree, fullPropertyPath, evalPropertyValue);
set(safeTree, fullPropertyPath, klonaJSON(evalPropertyValue));
if (
WorkerEnv.flags.release_evaluation_scope_cache &&
evalContextCache
) {
set(
evalContextCache,
fullPropertyPath,
klonaJSON(evalPropertyValue),
);
}
}
}
} catch (error) {
@ -1417,6 +1483,7 @@ export default class DataTreeEvaluator {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
callBackData?: Array<any>,
fullPropertyPath?: string,
evalContextCache?: EvaluateContext,
) {
// Get the {{binding}} bound values
let entity: DataTreeEntity | undefined = undefined;
@ -1467,6 +1534,7 @@ export default class DataTreeEvaluator {
!!entity && isAnyJSAction(entity),
contextData,
callBackData,
evalContextCache,
);
if (fullPropertyPath && evalErrors.length) {
@ -1560,6 +1628,7 @@ export default class DataTreeEvaluator {
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
callbackData?: Array<any>,
evalContextCache?: EvaluateContext,
): EvalResult {
let evalResponse: EvalResult;
@ -1574,6 +1643,8 @@ export default class DataTreeEvaluator {
isJSObject,
contextData,
callbackData,
{},
evalContextCache,
);
} catch (error) {
evalResponse = {

View File

@ -768,6 +768,7 @@ describe("isDataField", () => {
dependencyMap: {
body: ["myFun2", "myFun1"],
},
actionNames: new Set(["myFun1", "myFun2"]),
},
JSObject2: {
actionId: "644242aeadc0936a9b0e71cc",
@ -821,6 +822,7 @@ describe("isDataField", () => {
dependencyMap: {
body: ["myFun2", "myFun1"],
},
actionNames: new Set(["myFun1", "myFun2"]),
},
MainContainer: {
defaultProps: {},