diff --git a/app/client/src/workers/Evaluation/__tests__/evaluation.test.ts b/app/client/src/workers/Evaluation/__tests__/evaluation.test.ts index 0caf123f7e..e68382f9cb 100644 --- a/app/client/src/workers/Evaluation/__tests__/evaluation.test.ts +++ b/app/client/src/workers/Evaluation/__tests__/evaluation.test.ts @@ -27,7 +27,7 @@ jest.mock("klona/full", () => ({ }, })); -const WIDGET_CONFIG_MAP: WidgetTypeConfigMap = { +export const WIDGET_CONFIG_MAP: WidgetTypeConfigMap = { CONTAINER_WIDGET: { defaultProperties: {}, derivedProperties: {}, @@ -231,7 +231,7 @@ const WIDGET_CONFIG_MAP: WidgetTypeConfigMap = { }, }; -const BASE_WIDGET = { +export const BASE_WIDGET = { widgetId: "randomID", widgetName: "randomWidgetName", bottomRow: 0, @@ -249,7 +249,7 @@ const BASE_WIDGET = { meta: {}, } as unknown as WidgetEntity; -const BASE_WIDGET_CONFIG = { +export const BASE_WIDGET_CONFIG = { logBlackList: {}, widgetId: "randomID", type: "SKELETON_WIDGET", diff --git a/app/client/src/workers/Evaluation/evalTreeWithChanges.test.ts b/app/client/src/workers/Evaluation/evalTreeWithChanges.test.ts new file mode 100644 index 0000000000..cee6048f50 --- /dev/null +++ b/app/client/src/workers/Evaluation/evalTreeWithChanges.test.ts @@ -0,0 +1,532 @@ +import type { WidgetEntityConfig } from "@appsmith/entities/DataTree/types"; +import { DataTreeDiffEvent } from "@appsmith/workers/Evaluation/evaluationUtils"; +import { RenderModes } from "constants/WidgetConstants"; +import { ENTITY_TYPE } from "entities/DataTree/dataTreeFactory"; +import type { ConfigTree } from "entities/DataTree/dataTreeTypes"; +import { generateDataTreeWidget } from "entities/DataTree/dataTreeWidget"; +import produce from "immer"; +import type { WidgetEntity } from "plugins/Linting/lib/entity/WidgetEntity"; +import type { UpdateDataTreeMessageData } from "sagas/EvalWorkerActionSagas"; +import DataTreeEvaluator from "workers/common/DataTreeEvaluator"; +import * as evalTreeWithChanges from "./evalTreeWithChanges"; +export const BASE_WIDGET = { + widgetId: "randomID", + widgetName: "randomWidgetName", + bottomRow: 0, + isLoading: false, + leftColumn: 0, + parentColumnSpace: 0, + parentRowSpace: 0, + renderMode: RenderModes.CANVAS, + rightColumn: 0, + topRow: 0, + type: "SKELETON_WIDGET", + parentId: "0", + version: 1, + ENTITY_TYPE: ENTITY_TYPE.WIDGET, + meta: {}, +} as unknown as WidgetEntity; + +export const BASE_WIDGET_CONFIG = { + logBlackList: {}, + widgetId: "randomID", + type: "SKELETON_WIDGET", + ENTITY_TYPE: ENTITY_TYPE.WIDGET, +} as unknown as WidgetEntityConfig; + +const WIDGET_CONFIG_MAP = { + TEXT_WIDGET: { + defaultProperties: {}, + derivedProperties: { + value: "{{ this.text }}", + }, + metaProperties: {}, + }, +}; + +const configTree: ConfigTree = { + Text1: generateDataTreeWidget( + { + ...BASE_WIDGET_CONFIG, + ...BASE_WIDGET, + widgetName: "Text1", + text: "Label", + type: "TEXT_WIDGET", + } as any, + {}, + new Set(), + ).configEntity, + Text2: generateDataTreeWidget( + { + ...BASE_WIDGET_CONFIG, + ...BASE_WIDGET, + widgetName: "Text2", + text: "{{Text1.text}}", + dynamicBindingPathList: [{ key: "text" }], + type: "TEXT_WIDGET", + } as any, + {}, + new Set(), + ).configEntity, +}; + +const unEvalTree = { + Text1: generateDataTreeWidget( + { + ...BASE_WIDGET_CONFIG, + ...BASE_WIDGET, + widgetName: "Text1", + text: "Label", + type: "TEXT_WIDGET", + } as any, + {}, + new Set(), + ).unEvalEntity, + Text2: generateDataTreeWidget( + { + ...BASE_WIDGET_CONFIG, + ...BASE_WIDGET, + widgetName: "Text2", + text: "{{Text1.text}}", + dynamicBindingPathList: [{ key: "text" }], + type: "TEXT_WIDGET", + } as any, + {}, + new Set(), + ).unEvalEntity, +}; + +describe("evaluateAndPushResponse", () => { + let pushResponseToMainThreadMock: any; + beforeAll(() => { + pushResponseToMainThreadMock = jest + .spyOn(evalTreeWithChanges, "pushResponseToMainThread") + .mockImplementation(() => {}); // spy on foo + }); + beforeAll(() => { + jest.clearAllMocks(); + }); + test("should call pushResponseToMainThread when we evaluate and push updates", () => { + evalTreeWithChanges.evaluateAndPushResponse( + undefined, + { + unEvalUpdates: [], + evalOrder: [], + jsUpdates: {}, + }, + [], + [], + ); + // check if push response has been called + expect(pushResponseToMainThreadMock).toHaveBeenCalled(); + }); +}); + +describe("getAffectedNodesInTheDataTree", () => { + test("should merge paths from unEvalUpdates and evalOrder", () => { + const result = evalTreeWithChanges.getAffectedNodesInTheDataTree( + [ + { + event: DataTreeDiffEvent.NOOP, + payload: { + propertyPath: "Text2.text", + value: "", + }, + } as any, + ], + ["Text1.text"], + ); + expect(result).toEqual(["Text2.text", "Text1.text"]); + }); + test("should extract unique paths from unEvalUpdates and evalOrder", () => { + const result = evalTreeWithChanges.getAffectedNodesInTheDataTree( + [ + { + event: DataTreeDiffEvent.NOOP, + payload: { + propertyPath: "Text1.text", + value: "", + }, + }, + ], + ["Text1.text"], + ); + expect(result).toEqual(["Text1.text"]); + }); +}); +describe("evaluateAndGenerateResponse", () => { + let evaluator: DataTreeEvaluator; + const UPDATED_LABEL = "updated Label"; + + const getParsedUpdatesFromWebWorkerResp = ( + webworkerResponse: UpdateDataTreeMessageData, + ) => { + const updates = JSON.parse(webworkerResponse.workerResponse.updates); + //scrub out all __evaluation__ patches + return updates.filter((p: any) => !p.rhs.__evaluation__); + }; + beforeEach(() => { + evaluator = new DataTreeEvaluator(WIDGET_CONFIG_MAP); + evaluator.setupFirstTree(unEvalTree, configTree); + evaluator.evalAndValidateFirstTree(); + }); + + test("inital evaluation successful should be successful", () => { + expect(evaluator.evalTree).toHaveProperty("Text2.text", "Label"); + }); + + test("should respond with default values when dataTreeEvaluator is not provided", () => { + const webworkerResponse = evalTreeWithChanges.evaluateAndGenerateResponse( + undefined, + { + unEvalUpdates: [], + evalOrder: [], + jsUpdates: {}, + }, + [], + [], + ); + const parsedUpdates = getParsedUpdatesFromWebWorkerResp(webworkerResponse); + + expect(parsedUpdates).toEqual([]); + expect(webworkerResponse).toEqual({ + unevalTree: {}, + workerResponse: { + dependencies: {}, + errors: [], + evalMetaUpdates: [], + evaluationOrder: [], + isCreateFirstTree: false, + isNewWidgetAdded: false, + jsUpdates: {}, + jsVarsCreatedEvent: [], + logs: [], + removedPaths: [], + staleMetaIds: [], + unEvalUpdates: [], + undefinedEvalValuesMap: {}, + updates: "[]", + }, + }); + }); + test("should generate no updates when the updateTreeResponse is empty", () => { + const webworkerResponse = evalTreeWithChanges.evaluateAndGenerateResponse( + evaluator, + { + unEvalUpdates: [], + evalOrder: [], + jsUpdates: {}, + }, + [], + [], + ); + const parsedUpdates = getParsedUpdatesFromWebWorkerResp(webworkerResponse); + + expect(parsedUpdates).toEqual([]); + }); + test("should send the new unevalTree in the web worker response", () => { + const updatedLabelUnevalTree = produce(unEvalTree, (draft: any) => { + if (draft.Text1?.text) { + draft.Text1.text = UPDATED_LABEL; + } + }); + expect(evaluator.getOldUnevalTree()).toEqual(unEvalTree); + const updateTreeResponse = evaluator.setupUpdateTree( + updatedLabelUnevalTree, + configTree, + ); + // the new unevalTree gets set in setupUpdateTree + expect(evaluator.getOldUnevalTree()).toEqual(updatedLabelUnevalTree); + + const { unevalTree } = evalTreeWithChanges.evaluateAndGenerateResponse( + evaluator, + updateTreeResponse, + [], + [], + ); + expect(unevalTree).toEqual(updatedLabelUnevalTree); + }); + + describe("updates", () => { + test("should generate updates based on the unEvalUpdates", () => { + const updatedLabelUnevalTree = produce(unEvalTree, (draft: any) => { + draft.Text1.text = UPDATED_LABEL; + draft.Text1.label = UPDATED_LABEL; + }); + const updateTreeResponse = evaluator.setupUpdateTree( + updatedLabelUnevalTree, + configTree, + ); + + // ignore label Text1.label uneval update and just include Text1.text uneval update + updateTreeResponse.unEvalUpdates = [ + { + event: DataTreeDiffEvent.NOOP, + payload: { + propertyPath: "Text1.text", + value: "", + }, + }, + ] as any; + // the eval tree should have the uneval update but the diff should not be generated because the unEvalUpdates has been altered + expect(evaluator.evalTree).toHaveProperty("Text1.text", UPDATED_LABEL); + + const webworkerResponse = evalTreeWithChanges.evaluateAndGenerateResponse( + evaluator, + updateTreeResponse, + [], + [], + ); + const parsedUpdates = + getParsedUpdatesFromWebWorkerResp(webworkerResponse); + // Text1.label update should be ignored + expect(parsedUpdates).not.toEqual( + expect.arrayContaining([ + { + kind: "N", + path: ["Text1", "label"], + rhs: UPDATED_LABEL, + }, + ]), + ); + }); + test("should generate updates based on the evalOrder", () => { + const updatedLabelUnevalTree = produce(unEvalTree, (draft: any) => { + draft.Text1.text = UPDATED_LABEL; + }); + const updateTreeResponse = evaluator.setupUpdateTree( + updatedLabelUnevalTree, + configTree, + ); + + // ignore label Text1.label uneval update and just include Text1.text uneval update + // expect(updateTreeResponse.evalOrder).toEqual([]); + updateTreeResponse.evalOrder = []; + + const webworkerResponse = evalTreeWithChanges.evaluateAndGenerateResponse( + evaluator, + updateTreeResponse, + [], + [], + ); + const parsedUpdates = + getParsedUpdatesFromWebWorkerResp(webworkerResponse); + + // Text1.label update should be ignored + expect(parsedUpdates).not.toEqual( + expect.arrayContaining([ + { + kind: "N", + path: ["Text2", "text"], + rhs: "updated Label", + }, + ]), + ); + }); + test("should generate the correct updates to be sent to the main thread's state when the value tied to a binding changes ", () => { + const updatedLabelUnevalTree = produce(unEvalTree, (draft: any) => { + if (draft.Text1?.text) { + draft.Text1.text = UPDATED_LABEL; + } + }); + const updateTreeResponse = evaluator.setupUpdateTree( + updatedLabelUnevalTree, + configTree, + ); + + const webworkerResponse = evalTreeWithChanges.evaluateAndGenerateResponse( + evaluator, + updateTreeResponse, + [], + [], + ); + + const parsedUpdates = + getParsedUpdatesFromWebWorkerResp(webworkerResponse); + expect(parsedUpdates).toEqual( + expect.arrayContaining([ + { + kind: "N", + path: ["Text1", "text"], + rhs: "updated Label", + }, + { + kind: "N", + path: ["Text2", "text"], + rhs: "updated Label", + }, + ]), + ); + + expect(evaluator.evalTree).toHaveProperty("Text2.text", UPDATED_LABEL); + }); + test("should merge additional updates to the dataTree as well as push the updates back to the main thread's state when unEvalUpdates is ignored", () => { + const updatedLabelUnevalTree = produce(unEvalTree, (draft: any) => { + if (draft.Text1?.text) { + draft.Text1.text = UPDATED_LABEL; + } + }); + const updateTreeResponse = evaluator.setupUpdateTree( + updatedLabelUnevalTree, + configTree, + ); + //set the unEvalUpdates is empty so that evaluation ignores diffing the node + updateTreeResponse.unEvalUpdates = []; + + const webworkerResponse = evalTreeWithChanges.evaluateAndGenerateResponse( + evaluator, + updateTreeResponse, + [], + ["Text1.text"], + ); + const parsedUpdates = + getParsedUpdatesFromWebWorkerResp(webworkerResponse); + expect(parsedUpdates).toEqual( + expect.arrayContaining([ + { + kind: "N", + path: ["Text1", "text"], + rhs: UPDATED_LABEL, + }, + ]), + ); + + expect(evaluator.evalTree).toHaveProperty("Text1.text", UPDATED_LABEL); + }); + }); + + describe("evalMetaUpdates", () => { + test("should add metaUpdates in the webworker's response", () => { + const updatedLabelUnevalTree = produce(unEvalTree, (draft: any) => { + if (draft.Text1?.text) { + draft.Text1.text = UPDATED_LABEL; + } + }); + const response = evaluator.setupUpdateTree( + updatedLabelUnevalTree, + configTree, + ); + + const metaUpdates = [ + { + widgetId: unEvalTree.Text1.widgetId, + metaPropertyPath: ["someMetaValuePath"], + value: "someValue", + }, + ]; + const { workerResponse } = + evalTreeWithChanges.evaluateAndGenerateResponse( + evaluator, + response, + metaUpdates, + [], + ); + + expect(workerResponse.evalMetaUpdates).toEqual(metaUpdates); + }); + test("should sanitise metaUpdates in the webworker's response and strip out non serialisable properties", () => { + const updatedLabelUnevalTree = produce(unEvalTree, (draft: any) => { + if (draft.Text1?.text) { + draft.Text1.text = UPDATED_LABEL; + } + }); + const response = evaluator.setupUpdateTree( + updatedLabelUnevalTree, + configTree, + ); + + const metaUpdates = [ + { + widgetId: unEvalTree.Text1.widgetId, + metaPropertyPath: ["someMetaValuePath"], + value: function () {}, + }, + ]; + const { workerResponse } = + evalTreeWithChanges.evaluateAndGenerateResponse( + evaluator, + response, + metaUpdates, + [], + ); + + // the function properties should be stripped out + expect(workerResponse.evalMetaUpdates).toEqual([ + { + widgetId: unEvalTree.Text1.widgetId, + metaPropertyPath: ["someMetaValuePath"], + }, + ]); + }); + }); + + describe("unEvalUpdates", () => { + test("should add unEvalUpdates to the web worker response", () => { + const updatedLabelUnevalTree = produce(unEvalTree, (draft: any) => { + if (draft.Text1?.text) { + draft.Text1.text = UPDATED_LABEL; + } + }); + const updateTreeResponse = evaluator.setupUpdateTree( + updatedLabelUnevalTree, + configTree, + ); + + const webworkerResponse = evalTreeWithChanges.evaluateAndGenerateResponse( + evaluator, + updateTreeResponse, + [], + [], + ); + const parsedUpdates = + getParsedUpdatesFromWebWorkerResp(webworkerResponse); + expect(webworkerResponse.workerResponse.unEvalUpdates).toEqual([ + { + event: DataTreeDiffEvent.NOOP, + payload: { propertyPath: "Text1.text", value: "" }, + }, + ]); + expect(parsedUpdates).toEqual( + expect.arrayContaining([ + { + kind: "N", + path: ["Text1", "text"], + rhs: UPDATED_LABEL, + }, + ]), + ); + }); + test("should ignore generating updates when unEvalUpdates is empty", () => { + const updatedLabelUnevalTree = produce(unEvalTree, (draft: any) => { + if (draft.Text1?.text) { + draft.Text1.text = UPDATED_LABEL; + } + }); + const updateTreeResponse = evaluator.setupUpdateTree( + updatedLabelUnevalTree, + configTree, + ); + //set the evalOrder is empty so that evaluation ignores diffing the node + updateTreeResponse.unEvalUpdates = []; + + const webworkerResponse = evalTreeWithChanges.evaluateAndGenerateResponse( + evaluator, + updateTreeResponse, + [], + [], + ); + const parsedUpdates = + getParsedUpdatesFromWebWorkerResp(webworkerResponse); + + expect(parsedUpdates).not.toEqual( + expect.arrayContaining([ + { + kind: "N", + path: ["Text1", "text"], + rhs: UPDATED_LABEL, + }, + ]), + ); + }); + }); +}); diff --git a/app/client/src/workers/Evaluation/evalTreeWithChanges.ts b/app/client/src/workers/Evaluation/evalTreeWithChanges.ts index 8835a78d48..9618a43a37 100644 --- a/app/client/src/workers/Evaluation/evalTreeWithChanges.ts +++ b/app/client/src/workers/Evaluation/evalTreeWithChanges.ts @@ -1,105 +1,58 @@ -import type { DataTree, UnEvalTree } from "entities/DataTree/dataTreeTypes"; import { dataTreeEvaluator } from "./handlers/evalTree"; -import type { DataTreeDiff } from "@appsmith/workers/Evaluation/evaluationUtils"; import type { EvalMetaUpdates } from "@appsmith/workers/common/DataTreeEvaluator/types"; -import type { DependencyMap, EvalError } from "utils/DynamicBindingUtils"; import { makeEntityConfigsAsObjProperties } from "@appsmith/workers/Evaluation/dataTreeUtils"; -import type { EvalTreeResponseData } from "./types"; +import type { EvalTreeResponseData, UpdateTreeResponse } from "./types"; import { MessageType, sendMessage } from "utils/MessageUtil"; import { MAIN_THREAD_ACTION } from "@appsmith/workers/Evaluation/evalWorkerActions"; import type { UpdateDataTreeMessageData } from "sagas/EvalWorkerActionSagas"; -import type { JSUpdate } from "utils/JSPaneUtils"; import { generateOptimisedUpdatesAndSetPrevState, getNewDataTreeUpdates, uniqueOrderUpdatePaths, } from "./helpers"; +import type DataTreeEvaluator from "workers/common/DataTreeEvaluator"; +import type { DataTreeDiff } from "@appsmith/workers/Evaluation/evaluationUtils"; + +const getDefaultEvalResponse = (): EvalTreeResponseData => ({ + updates: "[]", + dependencies: {}, + errors: [], + evalMetaUpdates: [], + evaluationOrder: [], + jsUpdates: {}, + logs: [], + unEvalUpdates: [], + isCreateFirstTree: false, + staleMetaIds: [], + removedPaths: [], + isNewWidgetAdded: false, + undefinedEvalValuesMap: {}, + jsVarsCreatedEvent: [], +}); export function evalTreeWithChanges( updatedValuePaths: string[][], metaUpdates: EvalMetaUpdates = [], ) { - let evalOrder: string[] = []; - let jsUpdates: Record = {}; - let unEvalUpdates: DataTreeDiff[] = []; - const isCreateFirstTree = false; - let dataTree: DataTree = {}; - const errors: EvalError[] = []; - const logs: any[] = []; - const dependencies: DependencyMap = {}; - let evalMetaUpdates: EvalMetaUpdates = [...metaUpdates]; - let staleMetaIds: string[] = []; - const removedPaths: Array<{ entityId: string; fullpath: string }> = []; - let unevalTree: UnEvalTree = {}; - + let setupUpdateTreeResponse = {} as UpdateTreeResponse; if (dataTreeEvaluator) { - const setupUpdateTreeResponse = + setupUpdateTreeResponse = dataTreeEvaluator.setupUpdateTreeWithDifferences(updatedValuePaths); - - evalOrder = setupUpdateTreeResponse.evalOrder; - unEvalUpdates = setupUpdateTreeResponse.unEvalUpdates; - jsUpdates = setupUpdateTreeResponse.jsUpdates; - - const updateResponse = dataTreeEvaluator.evalAndValidateSubTree( - evalOrder, - dataTreeEvaluator.oldConfigTree, - unEvalUpdates, - ); - - dataTree = makeEntityConfigsAsObjProperties(dataTreeEvaluator.evalTree, { - evalProps: dataTreeEvaluator.evalProps, - }); - - /** Make sure evalMetaUpdates is sanitized to prevent postMessage failure */ - evalMetaUpdates = JSON.parse( - JSON.stringify([...evalMetaUpdates, ...updateResponse.evalMetaUpdates]), - ); - - staleMetaIds = updateResponse.staleMetaIds; - unevalTree = dataTreeEvaluator.getOldUnevalTree(); } - const allUnevalUpdates = unEvalUpdates.map( - (update) => update.payload.propertyPath, - ); - const completeEvalOrder = uniqueOrderUpdatePaths([ - ...allUnevalUpdates, - ...evalOrder, - ]); - const setterAndLocalStorageUpdates = getNewDataTreeUpdates( - uniqueOrderUpdatePaths(updatedValuePaths.map((val) => val.join("."))), - dataTree, + const setterAndLocalStorageUpdatePaths = uniqueOrderUpdatePaths( + updatedValuePaths.map((val) => val.join(".")), ); - const updates = generateOptimisedUpdatesAndSetPrevState( - dataTree, + evaluateAndPushResponse( dataTreeEvaluator, - completeEvalOrder, - setterAndLocalStorageUpdates, + setupUpdateTreeResponse, + metaUpdates, + setterAndLocalStorageUpdatePaths, ); +} - const evalTreeResponse: EvalTreeResponseData = { - updates, - dependencies, - errors, - evalMetaUpdates, - evaluationOrder: evalOrder, - jsUpdates, - logs, - unEvalUpdates, - isCreateFirstTree, - staleMetaIds, - removedPaths, - isNewWidgetAdded: false, - undefinedEvalValuesMap: dataTreeEvaluator?.undefinedEvalValuesMap || {}, - jsVarsCreatedEvent: [], - }; - - const data: UpdateDataTreeMessageData = { - workerResponse: evalTreeResponse, - unevalTree, - }; - +export const pushResponseToMainThread = (data: UpdateDataTreeMessageData) => { sendMessage.call(self, { messageType: MessageType.DEFAULT, body: { @@ -107,4 +60,106 @@ export function evalTreeWithChanges( method: MAIN_THREAD_ACTION.UPDATE_DATATREE, }, }); -} +}; + +export const getAffectedNodesInTheDataTree = ( + unEvalUpdates: DataTreeDiff[], + evalOrder: string[], +) => { + const allUnevalUpdates = unEvalUpdates.map( + (update) => update.payload.propertyPath, + ); + // merge unevalUpdate paths and evalOrder paths + return uniqueOrderUpdatePaths([...allUnevalUpdates, ...evalOrder]); +}; + +export const evaluateAndPushResponse = ( + dataTreeEvaluator: DataTreeEvaluator | undefined, + setupUpdateTreeResponse: UpdateTreeResponse, + metaUpdates: EvalMetaUpdates, + additionalPathsAddedAsUpdates: string[], +) => { + const response = evaluateAndGenerateResponse( + dataTreeEvaluator, + setupUpdateTreeResponse, + metaUpdates, + additionalPathsAddedAsUpdates, + ); + return pushResponseToMainThread(response); +}; + +export const evaluateAndGenerateResponse = ( + dataTreeEvaluator: DataTreeEvaluator | undefined, + setupUpdateTreeResponse: UpdateTreeResponse, + metaUpdates: EvalMetaUpdates, + additionalPathsAddedAsUpdates: string[], +): UpdateDataTreeMessageData => { + // generate default response first and later add updates to it + const defaultResponse = getDefaultEvalResponse(); + + if (!dataTreeEvaluator) { + const updates = generateOptimisedUpdatesAndSetPrevState( + {}, + dataTreeEvaluator, + [], + ); + defaultResponse.updates = updates; + defaultResponse.evalMetaUpdates = [...(metaUpdates || [])]; + return { + workerResponse: defaultResponse, + unevalTree: {}, + }; + } + + const { evalOrder, jsUpdates, unEvalUpdates } = setupUpdateTreeResponse; + defaultResponse.evaluationOrder = evalOrder; + defaultResponse.unEvalUpdates = unEvalUpdates; + defaultResponse.jsUpdates = jsUpdates; + + const updateResponse = dataTreeEvaluator.evalAndValidateSubTree( + evalOrder, + dataTreeEvaluator.oldConfigTree, + unEvalUpdates, + ); + + const dataTree = makeEntityConfigsAsObjProperties( + dataTreeEvaluator.evalTree, + { + evalProps: dataTreeEvaluator.evalProps, + }, + ); + + /** Make sure evalMetaUpdates is sanitized to prevent postMessage failure */ + defaultResponse.evalMetaUpdates = JSON.parse( + JSON.stringify([...(metaUpdates || []), ...updateResponse.evalMetaUpdates]), + ); + + defaultResponse.staleMetaIds = updateResponse.staleMetaIds; + const unevalTree = dataTreeEvaluator.getOldUnevalTree(); + + // when additional paths are required to be added as updates, we extract the updates from the data tree using these paths. + const additionalUpdates = getNewDataTreeUpdates( + additionalPathsAddedAsUpdates, + dataTree, + ); + // the affected paths is a combination of the eval order and the uneval updates + // we use this collection to limit the diff between the old and new data tree + const affectedNodePaths = getAffectedNodesInTheDataTree( + unEvalUpdates, + evalOrder, + ); + + defaultResponse.updates = generateOptimisedUpdatesAndSetPrevState( + dataTree, + dataTreeEvaluator, + affectedNodePaths, + additionalUpdates, + ); + dataTreeEvaluator.undefinedEvalValuesMap = + dataTreeEvaluator.undefinedEvalValuesMap || {}; + + return { + workerResponse: defaultResponse, + unevalTree, + }; +}; diff --git a/app/client/src/workers/Evaluation/handlers/evalTrigger.ts b/app/client/src/workers/Evaluation/handlers/evalTrigger.ts index 836e82bc67..4893ef9c0a 100644 --- a/app/client/src/workers/Evaluation/handlers/evalTrigger.ts +++ b/app/client/src/workers/Evaluation/handlers/evalTrigger.ts @@ -1,6 +1,7 @@ import { dataTreeEvaluator } from "./evalTree"; import type { EvalWorkerASyncRequest } from "../types"; import ExecutionMetaData from "../fns/utils/ExecutionMetaData"; +import { evaluateAndPushResponse } from "../evalTreeWithChanges"; export default async function (request: EvalWorkerASyncRequest) { const { data } = request; @@ -26,14 +27,13 @@ export default async function (request: EvalWorkerASyncRequest) { //TODO: the evalTrigger can be optimised to not diff all JS actions { isAllAffected: true, ids: [] }, ); - - dataTreeEvaluator.evalAndValidateSubTree( - evalOrder, - unEvalTree.configTree, - unEvalUpdates, + evaluateAndPushResponse( + dataTreeEvaluator, + { evalOrder, unEvalUpdates, jsUpdates: {} }, + [], + [], ); } - return dataTreeEvaluator.evaluateTriggers( dynamicTrigger, dataTreeEvaluator.getEvalTree(), diff --git a/app/client/src/workers/Evaluation/helpers.ts b/app/client/src/workers/Evaluation/helpers.ts index 0276ed8f2b..c0199046a1 100644 --- a/app/client/src/workers/Evaluation/helpers.ts +++ b/app/client/src/workers/Evaluation/helpers.ts @@ -383,13 +383,13 @@ export const generateOptimisedUpdatesAndSetPrevState = ( mergeAdditionalUpdates?: any, ) => { const { error, serialisedUpdates } = generateSerialisedUpdates( - dataTreeEvaluator.getPrevState(), + dataTreeEvaluator?.getPrevState() || {}, dataTree, constrainedDiffPaths, mergeAdditionalUpdates, ); - if (error) { + if (error && dataTreeEvaluator?.errors) { dataTreeEvaluator.errors.push(error); } dataTreeEvaluator?.setPrevState(dataTree); diff --git a/app/client/src/workers/Evaluation/types.ts b/app/client/src/workers/Evaluation/types.ts index e151e520eb..42169630fa 100644 --- a/app/client/src/workers/Evaluation/types.ts +++ b/app/client/src/workers/Evaluation/types.ts @@ -63,3 +63,9 @@ export interface EvalTreeResponseData { webworkerTelemetry?: Record; updates: string; } + +export interface UpdateTreeResponse { + unEvalUpdates: DataTreeDiff[]; + evalOrder: string[]; + jsUpdates: Record; +}