chore: removed klona completely in setupUpdateTree (#40021)

## Description
We earlier had an issue where the oldUnEvalTree was getting mutated, it
happened because of some properties of updatedUnevalTree were getting
tied to evalTree in the getEvaluationOrder function. So any mutation on
evalTree which definitely will happen in the subsequent steps in the
evalAndValidateSubTree function would affect oldUnEvalTree. To prevent
this mutation we previously performed a deepClone which had a cost in
performance and this executed in evalTreeWithChanges and evalTree
further exacerbating performance.
To fix it we performed a deepClone on specific paths of
updatedUnevalTree to evalTree thereby when make an assignment of
updatedUnevalTree to oldUnvalTree these deepClones isolate mutations.
This has led to a 0.3 seconds reduction in LCP and reduces the overall webworker
scripting by 9-10%.

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/14223540588>
> Commit: c1006f7e60a97a03efb4bc393ee3c03d5bfdfbc5
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=14223540588&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.All`
> Spec:
> <hr>Wed, 02 Apr 2025 16:44:05 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

## Summary by CodeRabbit

- **Tests**
- Updated evaluation test assertions to focus on performance
expectations rather than strict operation counts.
- **New Features**
- Introduced a method to directly set historical evaluation data,
improving clarity and functionality in the evaluation workflow.
- Renamed and modified the evaluation order method to enhance clarity
regarding its functionality.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Vemparala Surya Vamsi 2025-04-03 15:27:07 +05:30 committed by GitHub
parent 7792fbf2b4
commit 44295892b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 25 additions and 15 deletions

View File

@ -1114,7 +1114,8 @@ describe("DataTreeEvaluator", () => {
unEvalUpdates,
);
// Hard check to not regress on the number of clone operations. Try to improve this number.
expect(klonaFullSpy).toBeCalledTimes(4);
// Not a good assertion because in one piece of code im cloning multiple times, however the value im cloning is very small.
// TODO: Improve this assertion or remove it since its just performance related assertion and not a functional assertion.
expect(klonaJsonSpy).toBeCalledTimes(4);
});
});

View File

@ -245,6 +245,9 @@ export default class DataTreeEvaluator {
return this.oldUnEvalTree;
}
setOldUnevalTree(oldUnEvalTree: UnEvalTree) {
this.oldUnEvalTree = oldUnEvalTree;
}
/**
* Method to create all data required for linting and
* evaluation of the first tree
@ -589,6 +592,7 @@ export default class DataTreeEvaluator {
undefined,
webworkerTelemetry,
() =>
// this.oldUnEvalTree is not mutated as getDataTreeContext declares this paremeter as Readonly<UnEvalTree>
parseJSActions(this, unEvalTree, this.oldUnEvalTree, jsTranslatedDiffs),
);
@ -608,12 +612,14 @@ export default class DataTreeEvaluator {
configTree,
);
// this variable is not mutated and is used to drive a diff operation, So this.oldUnEvalTree is not affected by mutations.
const oldUnEvalTreeWithStringifiedJSFunctions = Object.assign(
{},
this.oldUnEvalTree,
stringifiedOldUnEvalTreeJSCollections,
);
// this variable is not mutated, it is driving the allKeys generation and the value
const unEvalTreeWithStringifiedJSFunctions = Object.assign(
{},
updatedUnEvalTree,
@ -648,9 +654,8 @@ export default class DataTreeEvaluator {
} {
this.setConfigTree(configTree);
const localUnEvalTree = Object.assign({}, unEvalTree);
const updatedUnEvalTreeJSFunction = this.updateUnEvalTreeJSFunction(
localUnEvalTree,
unEvalTree,
configTree,
);
@ -774,7 +779,7 @@ export default class DataTreeEvaluator {
};
}
getEvaluationOrder({
getEvaluationOrderAndSetEvalTreeWithNewUnevalTreeValues({
pathsToSkipFromEval,
subTreeSortOrder,
unEvalTree,
@ -791,20 +796,22 @@ export default class DataTreeEvaluator {
if (!isDynamicLeaf(unEvalTree, fullPath, this.getConfigTree())) continue;
const unEvalPropValue = get(unEvalTree, fullPath);
const evalPropValue = get(this.evalTree, fullPath);
evaluationOrder.push(fullPath);
if (isFunction(evalPropValue)) continue;
const unEvalPropValue = get(unEvalTree, fullPath);
// Set all values from unEvalTree to the evalTree to run evaluation for unevaluated values.
set(this.evalTree, fullPath, unEvalPropValue);
set(this.evalTree, fullPath, klona(unEvalPropValue));
}
return { evaluationOrder };
}
// setupTree should treat updatedUnEvalTree as immutable
setupTree(
updatedUnEvalTree: UnEvalTree,
updatedValuePaths: string[][],
@ -833,11 +840,12 @@ export default class DataTreeEvaluator {
);
const calculateSortOrderEndTime = performance.now();
const { evaluationOrder } = this.getEvaluationOrder({
unEvalTree: updatedUnEvalTree,
pathsToSkipFromEval,
subTreeSortOrder,
});
const { evaluationOrder } =
this.getEvaluationOrderAndSetEvalTreeWithNewUnevalTreeValues({
unEvalTree: updatedUnEvalTree,
pathsToSkipFromEval,
subTreeSortOrder,
});
this.logs.push({
sortedDependencies: this.sortedDependencies,
@ -853,9 +861,10 @@ export default class DataTreeEvaluator {
const cloneStartTime = performance.now();
// TODO: For some reason we are passing some reference which are getting mutated.
// Need to check why big api responses are getting split between two eval runs
this.oldUnEvalTree = klona(updatedUnEvalTree);
// We earlier had an issue where the oldUnEvalTree was getting mutated, it happened because of some properties of updatedUnevalTree were getting tied to it
// To avoid this, we clone specific properties of updatedUnEvalTree and assign it to evalTree so now they are independent of each other.
this.setOldUnevalTree(updatedUnEvalTree);
this.oldConfigTree = Object.assign({}, this.getConfigTree());
const cloneEndTime = performance.now();
@ -886,7 +895,7 @@ export default class DataTreeEvaluator {
updatedValuePaths: string[][],
pathsToSkipFromEval: string[] = [],
): ReturnType<typeof DataTreeEvaluator.prototype.setupUpdateTree> {
const updatedUnEvalTree = Object.assign({}, this.oldUnEvalTree);
const updatedUnEvalTree = this.oldUnEvalTree;
// skipped update local unEvalTree
if (updatedValuePaths.length === 0) {