PromucFlow_constructor/app/client/src/workers/Evaluation/setters.ts

176 lines
4.9 KiB
TypeScript
Raw Normal View History

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 14:07:26 +00:00
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;