PromucFlow_constructor/app/client/src/workers/Evaluation/setters.ts
Druthi Polisetty 2fc20cfe8e
feat: widget property setters (#23441)
## Description


- This PR adds setter methods to update widget property
programmatically.

Example:-

`Input1.setText("setter methods are cool!");`

Docs link : 
https://docs.appsmith.com/reference/widgets
For any selected widget check the `Methods` section

#### PR fixes following issue(s)
Fixes 


#### Type of change

- New feature (non-breaking change which adds functionality)

## Testing
>
#### How Has This Been Tested?
- [x] Manual
- [x] Jest
- [x] Cypress
>
>
#### Test Plan
https://github.com/appsmithorg/TestSmith/issues/2409

#### Issues raised during DP testing
- [x] [Errors are not logged in the
debugger](https://github.com/appsmithorg/appsmith/pull/23441#issuecomment-1564017346)
separate GitHub issue
https://github.com/appsmithorg/appsmith/issues/24609
- [x]
https://github.com/appsmithorg/appsmith/pull/23441#issuecomment-1564155545
( `setVisibility("false")` )
- [x]
https://github.com/appsmithorg/appsmith/pull/23441#issuecomment-1580525843
- [x]
https://github.com/appsmithorg/appsmith/pull/23441#issuecomment-1576582825
- Blocker for testing
- [x]
https://github.com/appsmithorg/appsmith/pull/23441#issuecomment-1577956441
- [x]
https://github.com/appsmithorg/appsmith/pull/23441#issuecomment-1577930108
- Not a issue (lint error query)
- [x]
https://github.com/appsmithorg/appsmith/pull/23441#issuecomment-1593471791
- [x]
https://github.com/appsmithorg/appsmith/pull/23441#issuecomment-1591440488
- [x]
https://github.com/appsmithorg/appsmith/pull/23441#issuecomment-1586747864
- [x]
https://github.com/appsmithorg/appsmith/pull/23441#issuecomment-1596738201
- [x]
https://github.com/appsmithorg/appsmith/pull/23441#issuecomment-1598541537
- [x]
https://github.com/appsmithorg/appsmith/pull/23441#issuecomment-1611413076
- [x]
https://github.com/appsmithorg/appsmith/pull/23441#issuecomment-1612621567
- [ ]
https://github.com/appsmithorg/appsmith/pull/23441#issuecomment-1619654507
- [ ]
https://github.com/appsmithorg/appsmith/pull/23441#issuecomment-1621256722

>
>
## Checklist:
#### Dev activity
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] PR is being merged under a feature flag


#### QA activity:
- [ ] [Speedbreak
features](https://github.com/appsmithorg/TestSmith/wiki/Test-plan-implementation#speedbreaker-features-to-consider-for-every-change)
have been covered
- [x] Test plan covers all impacted features and [areas of
interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans/_edit#areas-of-interest)
- [x] Test plan has been peer reviewed by project stakeholders and other
QA members
- [ ] Manually tested functionality on DP
- [ ] We had an implementation alignment call with stakeholders post QA
Round 2
- [ ] Cypress test cases have been added and approved by SDET/manual QA
- [ ] Added `Test Plan Approved` label after Cypress tests were reviewed
- [ ] Added `Test Plan Approved` label after JUnit tests were reviewed

---------

Co-authored-by: Rishabh Rathod <rishabh.rathod@appsmith.com>
2023-07-08 19:37:26 +05:30

176 lines
4.9 KiB
TypeScript

import {
getEntityNameAndPropertyPath,
isWidget,
overrideWidgetProperties,
} from "@appsmith/workers/Evaluation/evaluationUtils";
import type { EvalMetaUpdates } from "@appsmith/workers/common/DataTreeEvaluator/types";
import { evalTreeWithChanges } from "./evalTreeWithChanges";
import { dataTreeEvaluator } from "./handlers/evalTree";
import { get, set } from "lodash";
import { validate } from "./validations";
import type {
ConfigTree,
DataTree,
DataTreeEntity,
DataTreeEntityConfig,
} from "entities/DataTree/dataTreeFactory";
import { getFnWithGuards, isAsyncGuard } from "./fns/utils/fnGuard";
import { shouldAddSetter } from "./evaluate";
class Setters {
/** stores the setter accessor as key and true as value
*
* example - ```{ "Table1.setVisibility": true, "Table1.setData": true }```
*/
private setterMethodLookup: Record<string, true> = {};
private applySetterMethod(
path: string,
value: unknown,
setterMethodName: string,
) {
const { entityName, propertyPath } = getEntityNameAndPropertyPath(path);
if (!dataTreeEvaluator) return;
const evalTree = dataTreeEvaluator.getEvalTree();
const configTree = dataTreeEvaluator.getConfigTree();
const entity = evalTree[entityName];
const entityConfig = configTree[entityName];
const updatedProperties: string[][] = [];
const overriddenProperties: string[] = [];
const evalMetaUpdates: EvalMetaUpdates = [];
let parsedValue = value;
if (value === undefined) {
const error = new Error(
`The value passed to ${entityName}.${setterMethodName}() evaluates to undefined.`,
);
error.name = entityName + "." + setterMethodName + " failed";
throw error;
}
const { validationPaths } = entityConfig;
if (validationPaths) {
const validationConfig = validationPaths[propertyPath] || {};
const config = {
...validationConfig,
params: { ...(validationConfig.params || {}) },
};
config.params.strict = true;
const { isValid, messages, parsed } = validate(
config,
value,
entity as Record<string, unknown>,
propertyPath,
);
parsedValue = parsed;
if (!isValid) {
const message = messages && messages[0] ? messages[0].message : "";
const error = new Error(
`${entityName + "." + setterMethodName}: ${message}`,
);
error.name = entityName + "." + setterMethodName + " failed";
throw error;
}
}
if (isWidget(entity)) {
overrideWidgetProperties({
entity,
propertyPath,
value: parsedValue,
currentTree: evalTree,
configTree,
evalMetaUpdates,
fullPropertyPath: path,
isNewWidget: false,
shouldUpdateGlobalContext: true,
overriddenProperties,
});
overriddenProperties.forEach((propPath) => {
updatedProperties.push([entityName, propPath]);
});
}
set(evalTree, path, parsedValue);
set(self, path, parsedValue);
return new Promise((resolve) => {
updatedProperties.push([entityName, propertyPath]);
evalTreeWithChanges(updatedProperties, evalMetaUpdates);
resolve(parsedValue);
});
}
/** Generates a new setter method */
private factory(path: string, setterMethodName: string, entityName: string) {
/** register the setter method in the lookup */
set(this.setterMethodLookup, [entityName, setterMethodName], true);
const fn = async (value: unknown) => {
if (!dataTreeEvaluator) return;
return this.applySetterMethod(path, value, setterMethodName);
};
return getFnWithGuards(fn, setterMethodName, [isAsyncGuard]);
}
clear() {
this.setterMethodLookup = {};
}
has(entityName: string, propertyName: string) {
return get(this.setterMethodLookup, [entityName, propertyName], false);
}
getMap() {
return this.setterMethodLookup;
}
getEntitySettersFromConfig(
entityConfig: DataTreeEntityConfig,
entityName: string,
entity: DataTreeEntity,
) {
const setterMethodMap: Record<string, any> = {};
if (!entityConfig) return setterMethodMap;
if (entityConfig.__setters) {
for (const setterMethodName of Object.keys(entityConfig.__setters)) {
const path = entityConfig.__setters[setterMethodName].path;
if (!shouldAddSetter(entityConfig.__setters[setterMethodName], entity))
continue;
setterMethodMap[setterMethodName] = this.factory(
path,
setterMethodName,
entityName,
);
}
}
return setterMethodMap;
}
init(configTree: ConfigTree, dataTree: DataTree) {
const configTreeEntries = Object.entries(configTree);
for (const [entityName, entityConfig] of configTreeEntries) {
const entity = dataTree[entityName];
this.getEntitySettersFromConfig(entityConfig, entityName, entity);
}
}
}
const setters = new Setters();
export default setters;