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:
parent
4ea5021e88
commit
abb0878388
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ export interface JSActionEntityConfig extends EntityConfig {
|
|||
moduleId?: string;
|
||||
moduleInstanceId?: string;
|
||||
isPublic?: boolean;
|
||||
actionNames: Set<string>;
|
||||
}
|
||||
|
||||
export interface JSActionEntity {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = (
|
||||
|
|
|
|||
|
|
@ -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>[] =>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
13
app/client/src/utils/workerInstances.ts
Normal file
13
app/client/src/utils/workerInstances.ts
Normal 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",
|
||||
},
|
||||
),
|
||||
);
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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: {},
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user