fix: support dataDerivedPaths for revalidation (#27408)

This commit is contained in:
Rishabh Rathod 2023-09-27 22:34:43 +05:30 committed by GitHub
parent 920d454d2b
commit ab6b3e4e4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 198 additions and 218 deletions

View File

@ -252,10 +252,8 @@ describe("2. privateWidgets", () => {
Text3: true, Text3: true,
}; };
const actualPrivateWidgetsList = getAllPrivateWidgetsInDataTree( const actualPrivateWidgetsList =
testDataTree, getAllPrivateWidgetsInDataTree(testConfigTree);
testConfigTree,
);
expect(expectedPrivateWidgetsList).toStrictEqual(actualPrivateWidgetsList); expect(expectedPrivateWidgetsList).toStrictEqual(actualPrivateWidgetsList);
}); });
@ -833,7 +831,6 @@ describe("7. Test addErrorToEntityProperty method", () => {
} as EvaluationError; } as EvaluationError;
addErrorToEntityProperty({ addErrorToEntityProperty({
errors: [error], errors: [error],
dataTree: dataTreeEvaluator.evalTree,
evalProps: dataTreeEvaluator.evalProps, evalProps: dataTreeEvaluator.evalProps,
fullPropertyPath: "Api1.data", fullPropertyPath: "Api1.data",
configTree: dataTreeEvaluator.oldConfigTree, configTree: dataTreeEvaluator.oldConfigTree,

View File

@ -579,31 +579,25 @@ export function getSafeToRenderDataTree(
export const addErrorToEntityProperty = ({ export const addErrorToEntityProperty = ({
configTree, configTree,
dataTree,
errors, errors,
evalProps, evalProps,
fullPropertyPath, fullPropertyPath,
}: { }: {
errors: EvaluationError[]; errors: EvaluationError[];
dataTree: DataTree;
fullPropertyPath: string; fullPropertyPath: string;
evalProps: EvalProps; evalProps: EvalProps;
configTree: ConfigTree; configTree: ConfigTree;
}) => { }) => {
const { entityName, propertyPath } = const { entityName, propertyPath } =
getEntityNameAndPropertyPath(fullPropertyPath); getEntityNameAndPropertyPath(fullPropertyPath);
const isPrivateEntityPath = getAllPrivateWidgetsInDataTree( const isPrivateEntityPath =
dataTree, getAllPrivateWidgetsInDataTree(configTree)[entityName];
configTree,
)[entityName];
const logBlackList = get(configTree, `${entityName}.logBlackList`, {}); const logBlackList = get(configTree, `${entityName}.logBlackList`, {});
if (propertyPath && !(propertyPath in logBlackList) && !isPrivateEntityPath) { if (propertyPath && !(propertyPath in logBlackList) && !isPrivateEntityPath) {
const errorPath = `${entityName}.${EVAL_ERROR_PATH}['${propertyPath}']`; const errorPath = `${entityName}.${EVAL_ERROR_PATH}['${propertyPath}']`;
const existingErrors = get(evalProps, errorPath, []) as EvaluationError[]; const existingErrors = get(evalProps, errorPath, []) as EvaluationError[];
set(evalProps, errorPath, existingErrors.concat(errors)); set(evalProps, errorPath, existingErrors.concat(errors));
} }
return dataTree;
}; };
export const resetValidationErrorsForEntityProperty = ({ export const resetValidationErrorsForEntityProperty = ({
@ -712,15 +706,13 @@ export const isPrivateEntityPath = (
}; };
export const getAllPrivateWidgetsInDataTree = ( export const getAllPrivateWidgetsInDataTree = (
dataTree: DataTree,
configTree: ConfigTree, configTree: ConfigTree,
): PrivateWidgets => { ): PrivateWidgets => {
let privateWidgets: PrivateWidgets = {}; let privateWidgets: PrivateWidgets = {};
Object.keys(dataTree).forEach((entityName) => { Object.keys(configTree).forEach((entityName) => {
const entity = dataTree[entityName];
const entityConfig = configTree[entityName] as WidgetEntityConfig; const entityConfig = configTree[entityName] as WidgetEntityConfig;
if (isWidget(entity) && !_.isEmpty(entityConfig.privateWidgets)) { if (isWidget(entityConfig) && !_.isEmpty(entityConfig.privateWidgets)) {
privateWidgets = { ...privateWidgets, ...entityConfig.privateWidgets }; privateWidgets = { ...privateWidgets, ...entityConfig.privateWidgets };
} }
}); });
@ -732,7 +724,7 @@ export const getDataTreeWithoutPrivateWidgets = (
dataTree: DataTree, dataTree: DataTree,
configTree: ConfigTree, configTree: ConfigTree,
): DataTree => { ): DataTree => {
const privateWidgets = getAllPrivateWidgetsInDataTree(dataTree, configTree); const privateWidgets = getAllPrivateWidgetsInDataTree(configTree);
const privateWidgetNames = Object.keys(privateWidgets); const privateWidgetNames = Object.keys(privateWidgets);
const treeWithoutPrivateWidgets = _.omit(dataTree, privateWidgetNames); const treeWithoutPrivateWidgets = _.omit(dataTree, privateWidgetNames);
return treeWithoutPrivateWidgets; return treeWithoutPrivateWidgets;

View File

@ -129,6 +129,7 @@ export type EvalProps = {
[entityName: string]: DataTreeEvaluationProps; [entityName: string]: DataTreeEvaluationProps;
}; };
export type EvalPathsIdenticalToState = Record<string, string>;
export default class DataTreeEvaluator { export default class DataTreeEvaluator {
dependencyMap: DependencyMap = new DependencyMap(); dependencyMap: DependencyMap = new DependencyMap();
sortedDependencies: SortedDependencies = []; sortedDependencies: SortedDependencies = [];
@ -158,7 +159,6 @@ export default class DataTreeEvaluator {
*/ */
validationDependencyMap: DependencyMap = new DependencyMap(); validationDependencyMap: DependencyMap = new DependencyMap();
validationDependencies: Record<string, string[]> = {}; validationDependencies: Record<string, string[]> = {};
sortedValidationDependencies: SortedDependencies = [];
inverseValidationDependencies: Record<string, string[]> = {}; inverseValidationDependencies: Record<string, string[]> = {};
/** /**
@ -167,7 +167,7 @@ export default class DataTreeEvaluator {
evalProps: EvalProps = {}; evalProps: EvalProps = {};
//when attaching values to __evaluations__ segment of the state there are cases where this value is identical to the widget property //when attaching values to __evaluations__ segment of the state there are cases where this value is identical to the widget property
//in those cases do not it to the dataTree and update this map. The main thread can decompress these updates and we can minimise the data transfer //in those cases do not it to the dataTree and update this map. The main thread can decompress these updates and we can minimise the data transfer
evalPathsIdenticalToState: any = {}; evalPathsIdenticalToState: EvalPathsIdenticalToState = {};
undefinedEvalValuesMap: Record<string, boolean> = {}; undefinedEvalValuesMap: Record<string, boolean> = {};
prevState = {}; prevState = {};
@ -188,7 +188,7 @@ export default class DataTreeEvaluator {
this.widgetConfigMap = widgetConfigMap; this.widgetConfigMap = widgetConfigMap;
} }
getEvalPathsIdenticalToState(): Record<string, string> { getEvalPathsIdenticalToState(): EvalPathsIdenticalToState {
return this.evalPathsIdenticalToState || {}; return this.evalPathsIdenticalToState || {};
} }
getEvalTree() { getEvalTree() {
@ -283,9 +283,6 @@ export default class DataTreeEvaluator {
const sortDependenciesStartTime = performance.now(); const sortDependenciesStartTime = performance.now();
this.sortedDependencies = this.sortDependencies(this.dependencyMap); this.sortedDependencies = this.sortDependencies(this.dependencyMap);
this.sortedValidationDependencies = this.sortDependencies(
this.validationDependencyMap,
);
const sortDependenciesEndTime = performance.now(); const sortDependenciesEndTime = performance.now();
const secondCloneStartTime = performance.now(); const secondCloneStartTime = performance.now();
@ -349,23 +346,24 @@ export default class DataTreeEvaluator {
undefined, undefined,
this.oldConfigTree, this.oldConfigTree,
); );
const evaluationEndTime = performance.now(); const evaluationEndTime = performance.now();
const validationStartTime = performance.now(); const validationStartTime = performance.now();
// Validate Widgets
this.setEvalTree( const validatedEvalTree = getValidatedTree(
getValidatedTree( evaluatedTree,
evaluatedTree, {
{ evalProps: this.evalProps,
evalProps: this.evalProps, evalPathsIdenticalToState: this.evalPathsIdenticalToState,
evalPathsIdenticalToState: this.evalPathsIdenticalToState, pathsValidated: evaluationOrder,
}, },
this.oldConfigTree, this.oldConfigTree,
),
); );
const validationEndTime = performance.now(); const validationEndTime = performance.now();
this.setEvalTree(validatedEvalTree);
const timeTakenForEvalAndValidateFirstTree = { const timeTakenForEvalAndValidateFirstTree = {
evaluation: getFixedTimeDifference( evaluation: getFixedTimeDifference(
evaluationEndTime, evaluationEndTime,
@ -468,13 +466,13 @@ export default class DataTreeEvaluator {
configTree, configTree,
); );
const oldUnEvalTreeWithStrigifiedJSFunctions = Object.assign( const oldUnEvalTreeWithStringifiedJSFunctions = Object.assign(
{}, {},
this.oldUnEvalTree, this.oldUnEvalTree,
stringifiedOldUnEvalTreeJSCollections, stringifiedOldUnEvalTreeJSCollections,
); );
const localUnEvalTreeWithStrigifiedJSFunctions = Object.assign( const localUnEvalTreeWithStringifiedJSFunctions = Object.assign(
{}, {},
localUnEvalTree, localUnEvalTree,
stringifiedLocalUnEvalTreeJSCollection, stringifiedLocalUnEvalTreeJSCollection,
@ -482,8 +480,8 @@ export default class DataTreeEvaluator {
const differences: Diff<DataTree, DataTree>[] = const differences: Diff<DataTree, DataTree>[] =
diff( diff(
oldUnEvalTreeWithStrigifiedJSFunctions, oldUnEvalTreeWithStringifiedJSFunctions,
localUnEvalTreeWithStrigifiedJSFunctions, localUnEvalTreeWithStringifiedJSFunctions,
) || []; ) || [];
// Since eval tree is listening to possible events that don't cause differences // Since eval tree is listening to possible events that don't cause differences
// We want to check if no diffs are present and bail out early // We want to check if no diffs are present and bail out early
@ -524,7 +522,7 @@ export default class DataTreeEvaluator {
const updateDependencyStartTime = performance.now(); const updateDependencyStartTime = performance.now();
// TODO => Optimize using dataTree diff // TODO => Optimize using dataTree diff
this.allKeys = getAllPaths(localUnEvalTreeWithStrigifiedJSFunctions); this.allKeys = getAllPaths(localUnEvalTreeWithStringifiedJSFunctions);
// Find all the paths that have changed as part of the difference and update the // Find all the paths that have changed as part of the difference and update the
// global dependency map if an existing dynamic binding has now become legal // global dependency map if an existing dynamic binding has now become legal
const { const {
@ -795,15 +793,14 @@ export default class DataTreeEvaluator {
this.setEvalTree(newEvalTree); this.setEvalTree(newEvalTree);
const reValidateStartTime = performance.now(); const reValidateStartTime = performance.now();
const validationOrder = new Set([
const pathsChanged = new Set([
...evaluationOrder, ...evaluationOrder,
...nonDynamicFieldValidationOrder, ...nonDynamicFieldValidationOrder,
]); ]);
const reValidatedPaths = this.reValidateTree( const pathsToValidate = this.getPathsToValidate([...pathsChanged]);
[...validationOrder], this.validateEvalDependentPaths(pathsToValidate);
newEvalTree,
configTree,
);
const reValidateEndTime = performance.now(); const reValidateEndTime = performance.now();
const timeTakenForEvalAndValidateSubTree = { const timeTakenForEvalAndValidateSubTree = {
@ -820,7 +817,7 @@ export default class DataTreeEvaluator {
return { return {
evalMetaUpdates, evalMetaUpdates,
staleMetaIds, staleMetaIds,
reValidatedPaths, reValidatedPaths: pathsToValidate,
}; };
} }
@ -1039,7 +1036,6 @@ export default class DataTreeEvaluator {
const parsedValue = validateAndParseWidgetProperty({ const parsedValue = validateAndParseWidgetProperty({
fullPropertyPath, fullPropertyPath,
widget: widgetEntity, widget: widgetEntity,
currentTree,
configTree: oldConfigTree, configTree: oldConfigTree,
evalPropertyValue, evalPropertyValue,
unEvalPropertyValue, unEvalPropertyValue,
@ -1055,10 +1051,16 @@ export default class DataTreeEvaluator {
fullPropertyPath, fullPropertyPath,
parsedValue, parsedValue,
propertyPath, propertyPath,
evalPropertyValue,
isNewWidget, isNewWidget,
}); });
// setting evalPropertyValue in unParsedEvalTree
set(
this.getUnParsedEvalTree(),
fullPropertyPath,
evalPropertyValue,
);
staleMetaIds = staleMetaIds.concat( staleMetaIds = staleMetaIds.concat(
getStaleMetaStateIds({ getStaleMetaStateIds({
entity: widgetEntity, entity: widgetEntity,
@ -1103,7 +1105,7 @@ export default class DataTreeEvaluator {
evalPathsIdenticalToState: this.evalPathsIdenticalToState, evalPathsIdenticalToState: this.evalPathsIdenticalToState,
evalProps: this.evalProps, evalProps: this.evalProps,
isParsedValueTheSame: true, isParsedValueTheSame: true,
statePath: fullPropertyPath, fullPropertyPath,
value: evalPropertyValue, value: evalPropertyValue,
}); });
@ -1148,7 +1150,7 @@ export default class DataTreeEvaluator {
evalPathsIdenticalToState: this.evalPathsIdenticalToState, evalPathsIdenticalToState: this.evalPathsIdenticalToState,
evalProps: this.evalProps, evalProps: this.evalProps,
isParsedValueTheSame: true, isParsedValueTheSame: true,
statePath: fullPropertyPath, fullPropertyPath,
value: evalValue, value: evalValue,
}); });
@ -1313,7 +1315,6 @@ export default class DataTreeEvaluator {
), ),
evalProps: this.evalProps, evalProps: this.evalProps,
fullPropertyPath, fullPropertyPath,
dataTree: data,
configTree, configTree,
}); });
} }
@ -1355,7 +1356,6 @@ export default class DataTreeEvaluator {
], ],
evalProps: this.evalProps, evalProps: this.evalProps,
fullPropertyPath, fullPropertyPath,
dataTree: data,
configTree, configTree,
}); });
} }
@ -1439,7 +1439,6 @@ export default class DataTreeEvaluator {
currentTree, currentTree,
entity, entity,
evalMetaUpdates, evalMetaUpdates,
evalPropertyValue,
fullPropertyPath, fullPropertyPath,
isNewWidget, isNewWidget,
parsedValue, parsedValue,
@ -1453,7 +1452,6 @@ export default class DataTreeEvaluator {
isNewWidget: boolean; isNewWidget: boolean;
parsedValue: unknown; parsedValue: unknown;
propertyPath: string; propertyPath: string;
evalPropertyValue: unknown;
}) { }) {
const overwriteObj = overrideWidgetProperties({ const overwriteObj = overrideWidgetProperties({
entity, entity,
@ -1471,69 +1469,51 @@ export default class DataTreeEvaluator {
} }
// setting parseValue in dataTree // setting parseValue in dataTree
set(currentTree, fullPropertyPath, parsedValue); set(currentTree, fullPropertyPath, parsedValue);
// setting evalPropertyValue in unParsedEvalTree
set(this.getUnParsedEvalTree(), fullPropertyPath, evalPropertyValue);
} }
reValidateWidgetDependentProperty({ validateEntityDependentProperty({
configTree,
currentTree,
fullPropertyPath, fullPropertyPath,
widget,
}: { }: {
fullPropertyPath: string; fullPropertyPath: string;
widget: WidgetEntity; }) {
currentTree: DataTree; const evalTree = this.getEvalTree();
configTree: ConfigTree; const configTree = this.getConfigTree();
}): string[] { const { entityName } = getEntityNameAndPropertyPath(fullPropertyPath);
const reValidatedPaths: string[] = []; const entity = evalTree[entityName];
if (this.inverseValidationDependencies[fullPropertyPath]) {
const pathsToRevalidate =
this.inverseValidationDependencies[fullPropertyPath];
reValidatedPaths.push(...pathsToRevalidate); validateAndParseWidgetProperty({
fullPropertyPath,
pathsToRevalidate.forEach((fullPath) => { widget: entity as WidgetEntity,
validateAndParseWidgetProperty({ configTree,
fullPropertyPath: fullPath, // we supply non-transformed evaluated value
widget, evalPropertyValue: get(this.getUnParsedEvalTree(), fullPropertyPath),
currentTree, unEvalPropertyValue: get(
configTree, this.oldUnEvalTree,
// we supply non-transformed evaluated value fullPropertyPath,
evalPropertyValue: get(this.getUnParsedEvalTree(), fullPath), ) as unknown as string,
unEvalPropertyValue: get( evalProps: this.evalProps,
this.oldUnEvalTree, evalPathsIdenticalToState: this.evalPathsIdenticalToState,
fullPath, });
) as unknown as string,
evalProps: this.evalProps,
evalPathsIdenticalToState: this.evalPathsIdenticalToState,
});
});
}
return reValidatedPaths;
} }
reValidateTree( getPathsToValidate(pathsEvaluated: string[]) {
validationOrder: string[], const pathsToValidate = new Set<string>();
currentTree: DataTree, pathsEvaluated.forEach((fullPropertyPath) => {
configTree: ConfigTree, if (this.inverseValidationDependencies[fullPropertyPath]) {
) { const pathsToRevalidate =
const reValidatedPaths: string[] = []; this.inverseValidationDependencies[fullPropertyPath];
validationOrder.forEach((fullPropertyPath) => { pathsToRevalidate.forEach((path) => pathsToValidate.add(path));
const { entityName, propertyPath } =
getEntityNameAndPropertyPath(fullPropertyPath);
const entity = currentTree[entityName];
if (isWidget(entity) && !isPathDynamicTrigger(entity, propertyPath)) {
const pathsValidated = this.reValidateWidgetDependentProperty({
widget: entity,
fullPropertyPath,
currentTree,
configTree,
});
reValidatedPaths.push(...pathsValidated);
} }
}); });
return reValidatedPaths; return [...pathsToValidate];
}
validateEvalDependentPaths(pathsToValidate: string[]) {
pathsToValidate.forEach((fullPropertyPath) => {
this.validateEntityDependentProperty({
fullPropertyPath,
});
});
} }
// validates the user input saved as action property based on a validationConfig // validates the user input saved as action property based on a validationConfig
@ -1572,7 +1552,6 @@ export default class DataTreeEvaluator {
errors: evalErrors, errors: evalErrors,
evalProps: this.evalProps, evalProps: this.evalProps,
fullPropertyPath, fullPropertyPath,
dataTree: currentTree,
configTree, configTree,
}); });
} }

View File

@ -6,7 +6,7 @@ import type {
WidgetEntity, WidgetEntity,
WidgetEntityConfig, WidgetEntityConfig,
} from "entities/DataTree/dataTreeFactory"; } from "entities/DataTree/dataTreeFactory";
import { get, isObject, isUndefined, set } from "lodash"; import { isObject, isUndefined, set, get } from "lodash";
import type { EvaluationError } from "utils/DynamicBindingUtils"; import type { EvaluationError } from "utils/DynamicBindingUtils";
import { import {
getEvalValuePath, getEvalValuePath,
@ -20,7 +20,7 @@ import {
resetValidationErrorsForEntityProperty, resetValidationErrorsForEntityProperty,
} from "@appsmith/workers/Evaluation/evaluationUtils"; } from "@appsmith/workers/Evaluation/evaluationUtils";
import { validate } from "workers/Evaluation/validations"; import { validate } from "workers/Evaluation/validations";
import type { EvalProps } from "."; import type { EvalPathsIdenticalToState, EvalProps } from ".";
import type { ValidationResponse } from "constants/WidgetValidation"; import type { ValidationResponse } from "constants/WidgetValidation";
const LARGE_COLLECTION_SIZE = 100; const LARGE_COLLECTION_SIZE = 100;
@ -37,14 +37,21 @@ export function setToEvalPathsIdenticalToState({
evalPath, evalPath,
evalPathsIdenticalToState, evalPathsIdenticalToState,
evalProps, evalProps,
fullPropertyPath,
isParsedValueTheSame, isParsedValueTheSame,
statePath,
value, value,
}: any) { }: {
evalPath: string;
evalPathsIdenticalToState: EvalPathsIdenticalToState;
evalProps: EvalProps;
isParsedValueTheSame: boolean;
fullPropertyPath: string;
value: unknown;
}) {
const isLargeCollection = getIsLargeCollection(value); const isLargeCollection = getIsLargeCollection(value);
if (isParsedValueTheSame && isLargeCollection) { if (isParsedValueTheSame && isLargeCollection) {
evalPathsIdenticalToState[evalPath] = statePath; evalPathsIdenticalToState[evalPath] = fullPropertyPath;
} else { } else {
delete evalPathsIdenticalToState[evalPath]; delete evalPathsIdenticalToState[evalPath];
@ -53,7 +60,6 @@ export function setToEvalPathsIdenticalToState({
} }
export function validateAndParseWidgetProperty({ export function validateAndParseWidgetProperty({
configTree, configTree,
currentTree,
evalPathsIdenticalToState, evalPathsIdenticalToState,
evalPropertyValue, evalPropertyValue,
evalProps, evalProps,
@ -63,12 +69,11 @@ export function validateAndParseWidgetProperty({
}: { }: {
fullPropertyPath: string; fullPropertyPath: string;
widget: WidgetEntity; widget: WidgetEntity;
currentTree: DataTree;
configTree: ConfigTree; configTree: ConfigTree;
evalPropertyValue: unknown; evalPropertyValue: unknown;
unEvalPropertyValue: string; unEvalPropertyValue: string;
evalProps: EvalProps; evalProps: EvalProps;
evalPathsIdenticalToState: any; evalPathsIdenticalToState: EvalPathsIdenticalToState;
}): unknown { }): unknown {
const { propertyPath } = getEntityNameAndPropertyPath(fullPropertyPath); const { propertyPath } = getEntityNameAndPropertyPath(fullPropertyPath);
if (isPathDynamicTrigger(widget, propertyPath)) { if (isPathDynamicTrigger(widget, propertyPath)) {
@ -112,7 +117,6 @@ export function validateAndParseWidgetProperty({
errors: evalErrors, errors: evalErrors,
evalProps, evalProps,
fullPropertyPath, fullPropertyPath,
dataTree: currentTree,
configTree, configTree,
}); });
} }
@ -128,7 +132,7 @@ export function validateAndParseWidgetProperty({
evalPathsIdenticalToState, evalPathsIdenticalToState,
evalProps, evalProps,
isParsedValueTheSame, isParsedValueTheSame,
statePath: fullPropertyPath, fullPropertyPath,
value: evaluatedValue, value: evaluatedValue,
}); });
@ -162,74 +166,93 @@ export function validateActionProperty(
} }
return validate(config, value, {}, ""); return validate(config, value, {}, "");
} }
/**
* Validates all the nodes of the tree to make sure all the values are as expected according to the validation config
*
* For example :- If Button.isDisabled is set to false in propertyPane then it would be passed as "false" in unEvalTree and validateTree method makes sure to convert it to boolean.
* @param tree
* @param option
* @param configTree
* @returns
*/
export function getValidatedTree( export function getValidatedTree(
tree: DataTree, dataTree: DataTree,
option: { evalProps: EvalProps; evalPathsIdenticalToState: any }, option: {
evalProps: EvalProps;
evalPathsIdenticalToState: EvalPathsIdenticalToState;
pathsValidated: string[];
},
configTree: ConfigTree, configTree: ConfigTree,
) { ) {
const { evalPathsIdenticalToState, evalProps } = option; const { evalPathsIdenticalToState, evalProps, pathsValidated } = option;
return Object.keys(tree).reduce((tree, entityKey: string) => { for (const [entityName, entity] of Object.entries(dataTree)) {
const entity = tree[entityKey];
if (!isWidget(entity)) { if (!isWidget(entity)) {
return tree; continue;
} }
const entityConfig = configTree[entityKey] as WidgetEntityConfig; const entityConfig = configTree[entityName] as WidgetEntityConfig;
Object.entries(entityConfig.validationPaths).forEach( const validationPathsMap = Object.entries(entityConfig.validationPaths);
([property, validation]) => {
const value = get(entity, property);
// const value = get(parsedEntity, property);
// Pass it through parse
const { isValid, messages, parsed, transformed } =
validateWidgetProperty(validation, value, entity, property);
set(entity, property, parsed);
const evaluatedValue = isValid
? parsed
: isUndefined(transformed)
? value
: transformed;
const isParsedValueTheSame = parsed === evaluatedValue; for (const [propertyPath, validationConfig] of validationPathsMap) {
const fullPropertyPath = `${entityKey}.${property}`; const fullPropertyPath = `${entityName}.${propertyPath}`;
const evalPath = getEvalValuePath(fullPropertyPath, {
isPopulated: false,
fullPath: true,
});
setToEvalPathsIdenticalToState({ if (pathsValidated.includes(fullPropertyPath)) continue;
evalPath,
evalPathsIdenticalToState,
evalProps,
isParsedValueTheSame,
statePath: fullPropertyPath,
value: evaluatedValue,
});
resetValidationErrorsForEntityProperty({ const value = get(entity, propertyPath);
// Pass it through parse
const { isValid, messages, parsed, transformed } = validateWidgetProperty(
validationConfig,
value,
entity,
propertyPath,
);
set(entity, propertyPath, parsed);
const evaluatedValue = isValid
? parsed
: isUndefined(transformed)
? value
: transformed;
const isParsedValueTheSame = parsed === evaluatedValue;
const evalPath = getEvalValuePath(fullPropertyPath, {
isPopulated: false,
fullPath: true,
});
setToEvalPathsIdenticalToState({
evalPath,
evalPathsIdenticalToState,
evalProps,
isParsedValueTheSame,
fullPropertyPath,
value: evaluatedValue,
});
resetValidationErrorsForEntityProperty({
evalProps,
fullPropertyPath,
});
if (!isValid) {
const evalErrors: EvaluationError[] =
messages?.map((message) => ({
errorType: PropertyEvaluationErrorType.VALIDATION,
errorMessage: message,
severity: Severity.ERROR,
raw: value,
})) ?? [];
addErrorToEntityProperty({
errors: evalErrors,
evalProps, evalProps,
fullPropertyPath, fullPropertyPath,
configTree,
}); });
}
if (!isValid) { }
const evalErrors: EvaluationError[] = }
messages?.map((message) => ({ return dataTree;
errorType: PropertyEvaluationErrorType.VALIDATION,
errorMessage: message,
severity: Severity.ERROR,
raw: value,
})) ?? [];
addErrorToEntityProperty({
errors: evalErrors,
evalProps,
fullPropertyPath,
dataTree: tree,
configTree,
});
}
},
);
return { ...tree, [entityKey]: entity };
}, tree);
} }

View File

@ -119,7 +119,6 @@ export const updateDependencyMap = ({
const dependenciesOfRemovedPaths: Array<string> = []; const dependenciesOfRemovedPaths: Array<string> = [];
const removedPaths: Array<{ entityId: string; fullpath: string }> = []; const removedPaths: Array<{ entityId: string; fullpath: string }> = [];
let didUpdateDependencyMap = false; let didUpdateDependencyMap = false;
let didUpdateValidationDependencyMap = false;
const { const {
allKeys, allKeys,
dependencyMap, dependencyMap,
@ -161,10 +160,10 @@ export const updateDependencyMap = ({
} }
const didUpdateDep = dependencyMap.addNodes(allAddedPaths, false); const didUpdateDep = dependencyMap.addNodes(allAddedPaths, false);
const didUpdateValidationDep = validationDependencyMap.addNodes(allAddedPaths);
validationDependencyMap.addNodes(allAddedPaths);
if (didUpdateDep) didUpdateDependencyMap = true; if (didUpdateDep) didUpdateDependencyMap = true;
if (didUpdateValidationDep) didUpdateValidationDependencyMap = true;
if (isWidgetActionOrJsObject(entity)) { if (isWidgetActionOrJsObject(entity)) {
if (!isDynamicLeaf(unEvalDataTree, fullPropertyPath, configTree)) { if (!isDynamicLeaf(unEvalDataTree, fullPropertyPath, configTree)) {
const entityDependencyMap = getEntityDependencies( const entityDependencyMap = getEntityDependencies(
@ -200,7 +199,6 @@ export const updateDependencyMap = ({
path, path,
validationDependencies[path], validationDependencies[path],
); );
didUpdateValidationDependencyMap = true;
} }
} }
} else { } else {
@ -229,7 +227,6 @@ export const updateDependencyMap = ({
path, path,
validationDependencies[path], validationDependencies[path],
); );
didUpdateValidationDependencyMap = true;
} }
} }
} }
@ -255,10 +252,9 @@ export const updateDependencyMap = ({
} }
const didUpdateDeps = dependencyMap.removeNodes(allDeletedPaths); const didUpdateDeps = dependencyMap.removeNodes(allDeletedPaths);
const didUpdateValidationDeps = validationDependencyMap.removeNodes(allDeletedPaths);
validationDependencyMap.removeNodes(allDeletedPaths);
if (didUpdateDeps) didUpdateDependencyMap = true; if (didUpdateDeps) didUpdateDependencyMap = true;
if (didUpdateValidationDeps) didUpdateValidationDependencyMap = true;
if (isWidgetActionOrJsObject(entity)) { if (isWidgetActionOrJsObject(entity)) {
const entityId = getEntityId(entity); const entityId = getEntityId(entity);
@ -314,10 +310,6 @@ export const updateDependencyMap = ({
translatedDiffs, translatedDiffs,
); );
} }
if (didUpdateValidationDependencyMap) {
dataTreeEvalRef.sortedValidationDependencies =
dataTreeEvalRef.sortDependencies(validationDependencyMap);
}
/** We need this in order clear out the paths that could have errors when a property is deleted */ /** We need this in order clear out the paths that could have errors when a property is deleted */
if (removedPaths.length) { if (removedPaths.length) {

View File

@ -1,5 +1,5 @@
import { find, toPath, union } from "lodash"; import { find, toPath, union } from "lodash";
import type { EvalError, DependencyMap } from "utils/DynamicBindingUtils"; import type { EvalError } from "utils/DynamicBindingUtils";
import { EvalErrorTypes } from "utils/DynamicBindingUtils"; import { EvalErrorTypes } from "utils/DynamicBindingUtils";
import { extractIdentifierInfoFromCode } from "@shared/ast"; import { extractIdentifierInfoFromCode } from "@shared/ast";
import { import {
@ -12,7 +12,6 @@ import {
import type { import type {
ConfigTree, ConfigTree,
DataTreeEntity, DataTreeEntity,
WidgetEntity,
WidgetEntityConfig, WidgetEntityConfig,
} from "entities/DataTree/dataTreeFactory"; } from "entities/DataTree/dataTreeFactory";
import { import {
@ -111,30 +110,6 @@ export const extractInfoFromBindings = (
); );
}; };
export function listValidationDependencies(
entity: WidgetEntity,
entityName: string,
entityConfig: WidgetEntityConfig,
): DependencyMap {
const validationDependency: DependencyMap = {};
if (isWidget(entity)) {
const { validationPaths } = entityConfig;
Object.entries(validationPaths).forEach(
([propertyPath, validationConfig]) => {
if (validationConfig.dependentPaths) {
const dependencyArray = validationConfig.dependentPaths.map(
(path) => `${entityName}.${path}`,
);
validationDependency[`${entityName}.${propertyPath}`] =
dependencyArray;
}
},
);
}
return validationDependency;
}
/**This function returns a unique array containing a merge of both arrays /**This function returns a unique array containing a merge of both arrays
* @param currentArr * @param currentArr
* @param updateArr * @param updateArr

View File

@ -6,6 +6,8 @@ import type {
} from "entities/DataTree/dataTreeFactory"; } from "entities/DataTree/dataTreeFactory";
import type { DependencyMap } from "utils/DynamicBindingUtils"; import type { DependencyMap } from "utils/DynamicBindingUtils";
const DATA_DERIVED_PROPERTY_PLACEHOLDER = "*";
export function getValidationDependencies( export function getValidationDependencies(
entity: DataTreeEntity, entity: DataTreeEntity,
entityName: string, entityName: string,
@ -19,9 +21,29 @@ export function getValidationDependencies(
Object.entries(validationPaths).forEach( Object.entries(validationPaths).forEach(
([propertyPath, validationConfig]) => { ([propertyPath, validationConfig]) => {
if (validationConfig.dependentPaths) { if (validationConfig.dependentPaths) {
const dependencyArray = validationConfig.dependentPaths.map( const propertyPathsSplitArray = propertyPath.split(".");
(path) => `${entityName}.${path}`, const dependencyArray = validationConfig.dependentPaths.map((path) => {
); const pathSplitArray = path.split(".");
/**
* Below logic add support for data derived paths in validation dependencies
* dependentPaths: ["primaryColumns.*.computedValue"]
*
* Here, items.*.value is a data derived path and we need to replace * with the actual value resulting in "primaryColumns.columnName.computedValue" as dependency.
*/
const index = pathSplitArray.indexOf(
DATA_DERIVED_PROPERTY_PLACEHOLDER,
);
if (index > -1) {
// replace * in pathSplitArray with same position value in propertyPathsSplitArray
for (let i = 0; i < pathSplitArray.length; i++) {
if (pathSplitArray[i] === DATA_DERIVED_PROPERTY_PLACEHOLDER) {
pathSplitArray[i] = propertyPathsSplitArray[i];
}
}
return `${entityName}.${pathSplitArray.join(".")}`;
}
return `${entityName}.${path}`;
});
validationDependency[`${entityName}.${propertyPath}`] = dependencyArray; validationDependency[`${entityName}.${propertyPath}`] = dependencyArray;
} }
}, },