fix: evalTrigger mutation fix (#34106)

## Description
This fixes a gap in our evaluation flow where we were not sending
evaluation updates during an evaluation in the evalTrigger. We have
resolved that by sending updates in the evalTrigger, also we have
created a separate function called evaluateAndGenerateWebWorkerResponse
which unifies the logic between sending updates in evalTrigger as well
as evalTreeWithChanges. We have added several unit test cases in this PR
to test the evaluation flow.


Fixes #33823  

> [!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/9558723818>
> Commit: 8b7bc93e3d1a8ce93c722a94c8846f9359d40686
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=9558723818&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: ``

<!-- 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

- **Refactor**
- Improved the evaluation and update process for data tree structures
with new helper functions and interfaces.
- Enhanced error handling with optional chaining in `setPrevState`
function.

- **New Features**
- Introduced `evaluateAndPushResponse`, `evaluateAndGenerateResponse`,
and `getAffectedNodesInTheDataTree` functions for better data tree
evaluation and updates.
- Added `UpdateTreeResponse` interface for structured update responses.

- **Bug Fixes**
- Adjusted error handling in the evaluation process to ensure
robustness.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Vemparala Surya Vamsi 2024-06-18 15:15:24 +05:30 committed by GitHub
parent 4bc1785392
commit f2a6341c58
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 682 additions and 89 deletions

View File

@ -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",

View File

@ -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,
},
]),
);
});
});
});

View File

@ -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<string, JSUpdate> = {};
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,
};
};

View File

@ -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(),

View File

@ -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);

View File

@ -63,3 +63,9 @@ export interface EvalTreeResponseData {
webworkerTelemetry?: Record<string, WebworkerSpanData | SpanAttributes>;
updates: string;
}
export interface UpdateTreeResponse {
unEvalUpdates: DataTreeDiff[];
evalOrder: string[];
jsUpdates: Record<string, JSUpdate>;
}