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,
};
const actualPrivateWidgetsList = getAllPrivateWidgetsInDataTree(
testDataTree,
testConfigTree,
);
const actualPrivateWidgetsList =
getAllPrivateWidgetsInDataTree(testConfigTree);
expect(expectedPrivateWidgetsList).toStrictEqual(actualPrivateWidgetsList);
});
@ -833,7 +831,6 @@ describe("7. Test addErrorToEntityProperty method", () => {
} as EvaluationError;
addErrorToEntityProperty({
errors: [error],
dataTree: dataTreeEvaluator.evalTree,
evalProps: dataTreeEvaluator.evalProps,
fullPropertyPath: "Api1.data",
configTree: dataTreeEvaluator.oldConfigTree,

View File

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

View File

@ -129,6 +129,7 @@ export type EvalProps = {
[entityName: string]: DataTreeEvaluationProps;
};
export type EvalPathsIdenticalToState = Record<string, string>;
export default class DataTreeEvaluator {
dependencyMap: DependencyMap = new DependencyMap();
sortedDependencies: SortedDependencies = [];
@ -158,7 +159,6 @@ export default class DataTreeEvaluator {
*/
validationDependencyMap: DependencyMap = new DependencyMap();
validationDependencies: Record<string, string[]> = {};
sortedValidationDependencies: SortedDependencies = [];
inverseValidationDependencies: Record<string, string[]> = {};
/**
@ -167,7 +167,7 @@ export default class DataTreeEvaluator {
evalProps: EvalProps = {};
//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
evalPathsIdenticalToState: any = {};
evalPathsIdenticalToState: EvalPathsIdenticalToState = {};
undefinedEvalValuesMap: Record<string, boolean> = {};
prevState = {};
@ -188,7 +188,7 @@ export default class DataTreeEvaluator {
this.widgetConfigMap = widgetConfigMap;
}
getEvalPathsIdenticalToState(): Record<string, string> {
getEvalPathsIdenticalToState(): EvalPathsIdenticalToState {
return this.evalPathsIdenticalToState || {};
}
getEvalTree() {
@ -283,9 +283,6 @@ export default class DataTreeEvaluator {
const sortDependenciesStartTime = performance.now();
this.sortedDependencies = this.sortDependencies(this.dependencyMap);
this.sortedValidationDependencies = this.sortDependencies(
this.validationDependencyMap,
);
const sortDependenciesEndTime = performance.now();
const secondCloneStartTime = performance.now();
@ -349,23 +346,24 @@ export default class DataTreeEvaluator {
undefined,
this.oldConfigTree,
);
const evaluationEndTime = performance.now();
const validationStartTime = performance.now();
// Validate Widgets
this.setEvalTree(
getValidatedTree(
evaluatedTree,
{
evalProps: this.evalProps,
evalPathsIdenticalToState: this.evalPathsIdenticalToState,
},
this.oldConfigTree,
),
const validatedEvalTree = getValidatedTree(
evaluatedTree,
{
evalProps: this.evalProps,
evalPathsIdenticalToState: this.evalPathsIdenticalToState,
pathsValidated: evaluationOrder,
},
this.oldConfigTree,
);
const validationEndTime = performance.now();
this.setEvalTree(validatedEvalTree);
const timeTakenForEvalAndValidateFirstTree = {
evaluation: getFixedTimeDifference(
evaluationEndTime,
@ -468,13 +466,13 @@ export default class DataTreeEvaluator {
configTree,
);
const oldUnEvalTreeWithStrigifiedJSFunctions = Object.assign(
const oldUnEvalTreeWithStringifiedJSFunctions = Object.assign(
{},
this.oldUnEvalTree,
stringifiedOldUnEvalTreeJSCollections,
);
const localUnEvalTreeWithStrigifiedJSFunctions = Object.assign(
const localUnEvalTreeWithStringifiedJSFunctions = Object.assign(
{},
localUnEvalTree,
stringifiedLocalUnEvalTreeJSCollection,
@ -482,8 +480,8 @@ export default class DataTreeEvaluator {
const differences: Diff<DataTree, DataTree>[] =
diff(
oldUnEvalTreeWithStrigifiedJSFunctions,
localUnEvalTreeWithStrigifiedJSFunctions,
oldUnEvalTreeWithStringifiedJSFunctions,
localUnEvalTreeWithStringifiedJSFunctions,
) || [];
// 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
@ -524,7 +522,7 @@ export default class DataTreeEvaluator {
const updateDependencyStartTime = performance.now();
// 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
// global dependency map if an existing dynamic binding has now become legal
const {
@ -795,15 +793,14 @@ export default class DataTreeEvaluator {
this.setEvalTree(newEvalTree);
const reValidateStartTime = performance.now();
const validationOrder = new Set([
const pathsChanged = new Set([
...evaluationOrder,
...nonDynamicFieldValidationOrder,
]);
const reValidatedPaths = this.reValidateTree(
[...validationOrder],
newEvalTree,
configTree,
);
const pathsToValidate = this.getPathsToValidate([...pathsChanged]);
this.validateEvalDependentPaths(pathsToValidate);
const reValidateEndTime = performance.now();
const timeTakenForEvalAndValidateSubTree = {
@ -820,7 +817,7 @@ export default class DataTreeEvaluator {
return {
evalMetaUpdates,
staleMetaIds,
reValidatedPaths,
reValidatedPaths: pathsToValidate,
};
}
@ -1039,7 +1036,6 @@ export default class DataTreeEvaluator {
const parsedValue = validateAndParseWidgetProperty({
fullPropertyPath,
widget: widgetEntity,
currentTree,
configTree: oldConfigTree,
evalPropertyValue,
unEvalPropertyValue,
@ -1055,10 +1051,16 @@ export default class DataTreeEvaluator {
fullPropertyPath,
parsedValue,
propertyPath,
evalPropertyValue,
isNewWidget,
});
// setting evalPropertyValue in unParsedEvalTree
set(
this.getUnParsedEvalTree(),
fullPropertyPath,
evalPropertyValue,
);
staleMetaIds = staleMetaIds.concat(
getStaleMetaStateIds({
entity: widgetEntity,
@ -1103,7 +1105,7 @@ export default class DataTreeEvaluator {
evalPathsIdenticalToState: this.evalPathsIdenticalToState,
evalProps: this.evalProps,
isParsedValueTheSame: true,
statePath: fullPropertyPath,
fullPropertyPath,
value: evalPropertyValue,
});
@ -1148,7 +1150,7 @@ export default class DataTreeEvaluator {
evalPathsIdenticalToState: this.evalPathsIdenticalToState,
evalProps: this.evalProps,
isParsedValueTheSame: true,
statePath: fullPropertyPath,
fullPropertyPath,
value: evalValue,
});
@ -1313,7 +1315,6 @@ export default class DataTreeEvaluator {
),
evalProps: this.evalProps,
fullPropertyPath,
dataTree: data,
configTree,
});
}
@ -1355,7 +1356,6 @@ export default class DataTreeEvaluator {
],
evalProps: this.evalProps,
fullPropertyPath,
dataTree: data,
configTree,
});
}
@ -1439,7 +1439,6 @@ export default class DataTreeEvaluator {
currentTree,
entity,
evalMetaUpdates,
evalPropertyValue,
fullPropertyPath,
isNewWidget,
parsedValue,
@ -1453,7 +1452,6 @@ export default class DataTreeEvaluator {
isNewWidget: boolean;
parsedValue: unknown;
propertyPath: string;
evalPropertyValue: unknown;
}) {
const overwriteObj = overrideWidgetProperties({
entity,
@ -1471,69 +1469,51 @@ export default class DataTreeEvaluator {
}
// setting parseValue in dataTree
set(currentTree, fullPropertyPath, parsedValue);
// setting evalPropertyValue in unParsedEvalTree
set(this.getUnParsedEvalTree(), fullPropertyPath, evalPropertyValue);
}
reValidateWidgetDependentProperty({
configTree,
currentTree,
validateEntityDependentProperty({
fullPropertyPath,
widget,
}: {
fullPropertyPath: string;
widget: WidgetEntity;
currentTree: DataTree;
configTree: ConfigTree;
}): string[] {
const reValidatedPaths: string[] = [];
if (this.inverseValidationDependencies[fullPropertyPath]) {
const pathsToRevalidate =
this.inverseValidationDependencies[fullPropertyPath];
}) {
const evalTree = this.getEvalTree();
const configTree = this.getConfigTree();
const { entityName } = getEntityNameAndPropertyPath(fullPropertyPath);
const entity = evalTree[entityName];
reValidatedPaths.push(...pathsToRevalidate);
pathsToRevalidate.forEach((fullPath) => {
validateAndParseWidgetProperty({
fullPropertyPath: fullPath,
widget,
currentTree,
configTree,
// we supply non-transformed evaluated value
evalPropertyValue: get(this.getUnParsedEvalTree(), fullPath),
unEvalPropertyValue: get(
this.oldUnEvalTree,
fullPath,
) as unknown as string,
evalProps: this.evalProps,
evalPathsIdenticalToState: this.evalPathsIdenticalToState,
});
});
}
return reValidatedPaths;
validateAndParseWidgetProperty({
fullPropertyPath,
widget: entity as WidgetEntity,
configTree,
// we supply non-transformed evaluated value
evalPropertyValue: get(this.getUnParsedEvalTree(), fullPropertyPath),
unEvalPropertyValue: get(
this.oldUnEvalTree,
fullPropertyPath,
) as unknown as string,
evalProps: this.evalProps,
evalPathsIdenticalToState: this.evalPathsIdenticalToState,
});
}
reValidateTree(
validationOrder: string[],
currentTree: DataTree,
configTree: ConfigTree,
) {
const reValidatedPaths: string[] = [];
validationOrder.forEach((fullPropertyPath) => {
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);
getPathsToValidate(pathsEvaluated: string[]) {
const pathsToValidate = new Set<string>();
pathsEvaluated.forEach((fullPropertyPath) => {
if (this.inverseValidationDependencies[fullPropertyPath]) {
const pathsToRevalidate =
this.inverseValidationDependencies[fullPropertyPath];
pathsToRevalidate.forEach((path) => pathsToValidate.add(path));
}
});
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
@ -1572,7 +1552,6 @@ export default class DataTreeEvaluator {
errors: evalErrors,
evalProps: this.evalProps,
fullPropertyPath,
dataTree: currentTree,
configTree,
});
}

View File

@ -6,7 +6,7 @@ import type {
WidgetEntity,
WidgetEntityConfig,
} 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 {
getEvalValuePath,
@ -20,7 +20,7 @@ import {
resetValidationErrorsForEntityProperty,
} from "@appsmith/workers/Evaluation/evaluationUtils";
import { validate } from "workers/Evaluation/validations";
import type { EvalProps } from ".";
import type { EvalPathsIdenticalToState, EvalProps } from ".";
import type { ValidationResponse } from "constants/WidgetValidation";
const LARGE_COLLECTION_SIZE = 100;
@ -37,14 +37,21 @@ export function setToEvalPathsIdenticalToState({
evalPath,
evalPathsIdenticalToState,
evalProps,
fullPropertyPath,
isParsedValueTheSame,
statePath,
value,
}: any) {
}: {
evalPath: string;
evalPathsIdenticalToState: EvalPathsIdenticalToState;
evalProps: EvalProps;
isParsedValueTheSame: boolean;
fullPropertyPath: string;
value: unknown;
}) {
const isLargeCollection = getIsLargeCollection(value);
if (isParsedValueTheSame && isLargeCollection) {
evalPathsIdenticalToState[evalPath] = statePath;
evalPathsIdenticalToState[evalPath] = fullPropertyPath;
} else {
delete evalPathsIdenticalToState[evalPath];
@ -53,7 +60,6 @@ export function setToEvalPathsIdenticalToState({
}
export function validateAndParseWidgetProperty({
configTree,
currentTree,
evalPathsIdenticalToState,
evalPropertyValue,
evalProps,
@ -63,12 +69,11 @@ export function validateAndParseWidgetProperty({
}: {
fullPropertyPath: string;
widget: WidgetEntity;
currentTree: DataTree;
configTree: ConfigTree;
evalPropertyValue: unknown;
unEvalPropertyValue: string;
evalProps: EvalProps;
evalPathsIdenticalToState: any;
evalPathsIdenticalToState: EvalPathsIdenticalToState;
}): unknown {
const { propertyPath } = getEntityNameAndPropertyPath(fullPropertyPath);
if (isPathDynamicTrigger(widget, propertyPath)) {
@ -112,7 +117,6 @@ export function validateAndParseWidgetProperty({
errors: evalErrors,
evalProps,
fullPropertyPath,
dataTree: currentTree,
configTree,
});
}
@ -128,7 +132,7 @@ export function validateAndParseWidgetProperty({
evalPathsIdenticalToState,
evalProps,
isParsedValueTheSame,
statePath: fullPropertyPath,
fullPropertyPath,
value: evaluatedValue,
});
@ -162,74 +166,93 @@ export function validateActionProperty(
}
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(
tree: DataTree,
option: { evalProps: EvalProps; evalPathsIdenticalToState: any },
dataTree: DataTree,
option: {
evalProps: EvalProps;
evalPathsIdenticalToState: EvalPathsIdenticalToState;
pathsValidated: string[];
},
configTree: ConfigTree,
) {
const { evalPathsIdenticalToState, evalProps } = option;
return Object.keys(tree).reduce((tree, entityKey: string) => {
const entity = tree[entityKey];
const { evalPathsIdenticalToState, evalProps, pathsValidated } = option;
for (const [entityName, entity] of Object.entries(dataTree)) {
if (!isWidget(entity)) {
return tree;
continue;
}
const entityConfig = configTree[entityKey] as WidgetEntityConfig;
const entityConfig = configTree[entityName] as WidgetEntityConfig;
Object.entries(entityConfig.validationPaths).forEach(
([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 validationPathsMap = Object.entries(entityConfig.validationPaths);
const isParsedValueTheSame = parsed === evaluatedValue;
const fullPropertyPath = `${entityKey}.${property}`;
const evalPath = getEvalValuePath(fullPropertyPath, {
isPopulated: false,
fullPath: true,
});
for (const [propertyPath, validationConfig] of validationPathsMap) {
const fullPropertyPath = `${entityName}.${propertyPath}`;
setToEvalPathsIdenticalToState({
evalPath,
evalPathsIdenticalToState,
evalProps,
isParsedValueTheSame,
statePath: fullPropertyPath,
value: evaluatedValue,
});
if (pathsValidated.includes(fullPropertyPath)) continue;
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,
fullPropertyPath,
configTree,
});
if (!isValid) {
const evalErrors: EvaluationError[] =
messages?.map((message) => ({
errorType: PropertyEvaluationErrorType.VALIDATION,
errorMessage: message,
severity: Severity.ERROR,
raw: value,
})) ?? [];
addErrorToEntityProperty({
errors: evalErrors,
evalProps,
fullPropertyPath,
dataTree: tree,
configTree,
});
}
},
);
return { ...tree, [entityKey]: entity };
}, tree);
}
}
}
return dataTree;
}

View File

@ -119,7 +119,6 @@ export const updateDependencyMap = ({
const dependenciesOfRemovedPaths: Array<string> = [];
const removedPaths: Array<{ entityId: string; fullpath: string }> = [];
let didUpdateDependencyMap = false;
let didUpdateValidationDependencyMap = false;
const {
allKeys,
dependencyMap,
@ -161,10 +160,10 @@ export const updateDependencyMap = ({
}
const didUpdateDep = dependencyMap.addNodes(allAddedPaths, false);
const didUpdateValidationDep =
validationDependencyMap.addNodes(allAddedPaths);
validationDependencyMap.addNodes(allAddedPaths);
if (didUpdateDep) didUpdateDependencyMap = true;
if (didUpdateValidationDep) didUpdateValidationDependencyMap = true;
if (isWidgetActionOrJsObject(entity)) {
if (!isDynamicLeaf(unEvalDataTree, fullPropertyPath, configTree)) {
const entityDependencyMap = getEntityDependencies(
@ -200,7 +199,6 @@ export const updateDependencyMap = ({
path,
validationDependencies[path],
);
didUpdateValidationDependencyMap = true;
}
}
} else {
@ -229,7 +227,6 @@ export const updateDependencyMap = ({
path,
validationDependencies[path],
);
didUpdateValidationDependencyMap = true;
}
}
}
@ -255,10 +252,9 @@ export const updateDependencyMap = ({
}
const didUpdateDeps = dependencyMap.removeNodes(allDeletedPaths);
const didUpdateValidationDeps =
validationDependencyMap.removeNodes(allDeletedPaths);
validationDependencyMap.removeNodes(allDeletedPaths);
if (didUpdateDeps) didUpdateDependencyMap = true;
if (didUpdateValidationDeps) didUpdateValidationDependencyMap = true;
if (isWidgetActionOrJsObject(entity)) {
const entityId = getEntityId(entity);
@ -314,10 +310,6 @@ export const updateDependencyMap = ({
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 */
if (removedPaths.length) {

View File

@ -1,5 +1,5 @@
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 { extractIdentifierInfoFromCode } from "@shared/ast";
import {
@ -12,7 +12,6 @@ import {
import type {
ConfigTree,
DataTreeEntity,
WidgetEntity,
WidgetEntityConfig,
} from "entities/DataTree/dataTreeFactory";
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
* @param currentArr
* @param updateArr

View File

@ -6,6 +6,8 @@ import type {
} from "entities/DataTree/dataTreeFactory";
import type { DependencyMap } from "utils/DynamicBindingUtils";
const DATA_DERIVED_PROPERTY_PLACEHOLDER = "*";
export function getValidationDependencies(
entity: DataTreeEntity,
entityName: string,
@ -19,9 +21,29 @@ export function getValidationDependencies(
Object.entries(validationPaths).forEach(
([propertyPath, validationConfig]) => {
if (validationConfig.dependentPaths) {
const dependencyArray = validationConfig.dependentPaths.map(
(path) => `${entityName}.${path}`,
);
const propertyPathsSplitArray = propertyPath.split(".");
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;
}
},