Avoid double validation (#2687)
This commit is contained in:
parent
f8974f1911
commit
164de6fb7e
|
|
@ -43,6 +43,9 @@ describe("isChildPropertyPath function", () => {
|
||||||
["Table1.selectedRow", "1Table1.selectedRow", false],
|
["Table1.selectedRow", "1Table1.selectedRow", false],
|
||||||
["Table1.selectedRow", "Table11selectedRow", false],
|
["Table1.selectedRow", "Table11selectedRow", false],
|
||||||
["Table1.selectedRow", "Table1.selectedRow", true],
|
["Table1.selectedRow", "Table1.selectedRow", true],
|
||||||
|
["Dropdown1.options", "Dropdown1.options[1]", true],
|
||||||
|
["Dropdown1.options[1]", "Dropdown1.options[1].value", true],
|
||||||
|
["Dropdown1", "Dropdown1.options[1].value", true],
|
||||||
];
|
];
|
||||||
cases.forEach((testCase) => {
|
cases.forEach((testCase) => {
|
||||||
const result = isChildPropertyPath(testCase[0], testCase[1]);
|
const result = isChildPropertyPath(testCase[0], testCase[1]);
|
||||||
|
|
@ -249,12 +249,11 @@ export const unsafeFunctionForEval = [
|
||||||
"setInterval",
|
"setInterval",
|
||||||
"Promise",
|
"Promise",
|
||||||
];
|
];
|
||||||
|
|
||||||
export const isChildPropertyPath = (
|
export const isChildPropertyPath = (
|
||||||
parentPropertyPath: string,
|
parentPropertyPath: string,
|
||||||
childPropertyPath: string,
|
childPropertyPath: string,
|
||||||
): boolean => {
|
): boolean =>
|
||||||
const regexTest = new RegExp(
|
parentPropertyPath === childPropertyPath ||
|
||||||
`^${parentPropertyPath.replace(".", "\\.")}(\\.\\S+)?$`,
|
childPropertyPath.startsWith(`${parentPropertyPath}.`) ||
|
||||||
);
|
childPropertyPath.startsWith(`${parentPropertyPath}[`);
|
||||||
return regexTest.test(childPropertyPath);
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -436,7 +436,7 @@ const WIDGET_CONFIG_MAP: WidgetTypeConfigMap = {
|
||||||
|
|
||||||
const BASE_WIDGET: DataTreeWidget = {
|
const BASE_WIDGET: DataTreeWidget = {
|
||||||
widgetId: "randomID",
|
widgetId: "randomID",
|
||||||
widgetName: "randomName",
|
widgetName: "randomWidgetName",
|
||||||
bottomRow: 0,
|
bottomRow: 0,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
leftColumn: 0,
|
leftColumn: 0,
|
||||||
|
|
@ -452,7 +452,7 @@ const BASE_WIDGET: DataTreeWidget = {
|
||||||
|
|
||||||
const BASE_ACTION: DataTreeAction = {
|
const BASE_ACTION: DataTreeAction = {
|
||||||
actionId: "randomId",
|
actionId: "randomId",
|
||||||
name: "randomName",
|
name: "randomActionName",
|
||||||
config: {
|
config: {
|
||||||
timeoutInMillisecond: 10,
|
timeoutInMillisecond: 10,
|
||||||
},
|
},
|
||||||
|
|
@ -645,6 +645,7 @@ describe("DataTreeEvaluator", () => {
|
||||||
...unEvalTree,
|
...unEvalTree,
|
||||||
Api1: {
|
Api1: {
|
||||||
...BASE_ACTION,
|
...BASE_ACTION,
|
||||||
|
name: "Api1",
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
test: "Hey",
|
test: "Hey",
|
||||||
|
|
@ -669,7 +670,6 @@ describe("DataTreeEvaluator", () => {
|
||||||
]);
|
]);
|
||||||
expect(updatedDependencyMap).toStrictEqual({
|
expect(updatedDependencyMap).toStrictEqual({
|
||||||
Api1: ["Api1.data"],
|
Api1: ["Api1.data"],
|
||||||
Input1: ["Input1.text"],
|
|
||||||
Text1: ["Text1.text"],
|
Text1: ["Text1.text"],
|
||||||
Text2: ["Text2.text"],
|
Text2: ["Text2.text"],
|
||||||
Text3: ["Text3.text"],
|
Text3: ["Text3.text"],
|
||||||
|
|
@ -693,7 +693,6 @@ describe("DataTreeEvaluator", () => {
|
||||||
"Table1.selectedRowIndex": [],
|
"Table1.selectedRowIndex": [],
|
||||||
"Table1.selectedRowIndices": [],
|
"Table1.selectedRowIndices": [],
|
||||||
"Text4.text": [],
|
"Text4.text": [],
|
||||||
"Input1.text": [],
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -710,6 +709,7 @@ describe("DataTreeEvaluator", () => {
|
||||||
},
|
},
|
||||||
Api1: {
|
Api1: {
|
||||||
...BASE_ACTION,
|
...BASE_ACTION,
|
||||||
|
name: "Api1",
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
test: "Hey",
|
test: "Hey",
|
||||||
|
|
@ -751,7 +751,6 @@ describe("DataTreeEvaluator", () => {
|
||||||
"Dropdown1.selectedOptionValue",
|
"Dropdown1.selectedOptionValue",
|
||||||
"Dropdown1.selectedOptionValueArr",
|
"Dropdown1.selectedOptionValueArr",
|
||||||
],
|
],
|
||||||
Input1: ["Input1.text"],
|
|
||||||
"Text2.text": ["Text1.text"],
|
"Text2.text": ["Text1.text"],
|
||||||
"Text3.text": ["Text1.text"],
|
"Text3.text": ["Text1.text"],
|
||||||
"Dropdown1.selectedOptionValue": [],
|
"Dropdown1.selectedOptionValue": [],
|
||||||
|
|
@ -761,7 +760,6 @@ describe("DataTreeEvaluator", () => {
|
||||||
"Table1.selectedRowIndex": [],
|
"Table1.selectedRowIndex": [],
|
||||||
"Table1.selectedRowIndices": [],
|
"Table1.selectedRowIndices": [],
|
||||||
"Text4.text": ["Table1.selectedRow.test"],
|
"Text4.text": ["Table1.selectedRow.test"],
|
||||||
"Input1.text": [],
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ import {
|
||||||
removeFunctionsFromDataTree,
|
removeFunctionsFromDataTree,
|
||||||
translateDiffEventToDataTreeDiffEvent,
|
translateDiffEventToDataTreeDiffEvent,
|
||||||
validateWidgetProperty,
|
validateWidgetProperty,
|
||||||
|
getImmediateParentsOfPropertyPaths,
|
||||||
} from "./evaluationUtils";
|
} from "./evaluationUtils";
|
||||||
import {
|
import {
|
||||||
EXECUTION_PARAM_KEY,
|
EXECUTION_PARAM_KEY,
|
||||||
|
|
@ -205,6 +206,7 @@ export class DataTreeEvaluator {
|
||||||
widgetConfigMap: WidgetTypeConfigMap = {};
|
widgetConfigMap: WidgetTypeConfigMap = {};
|
||||||
evalTree: DataTree = {};
|
evalTree: DataTree = {};
|
||||||
allKeys: Record<string, true> = {};
|
allKeys: Record<string, true> = {};
|
||||||
|
validationPaths: Record<string, Set<string>> = {};
|
||||||
oldUnEvalTree: DataTree = {};
|
oldUnEvalTree: DataTree = {};
|
||||||
errors: EvalError[] = [];
|
errors: EvalError[] = [];
|
||||||
parsedValueCache: Map<
|
parsedValueCache: Map<
|
||||||
|
|
@ -327,25 +329,11 @@ export class DataTreeEvaluator {
|
||||||
removedPaths.forEach((removedPath) => {
|
removedPaths.forEach((removedPath) => {
|
||||||
_.unset(this.evalTree, removedPath);
|
_.unset(this.evalTree, removedPath);
|
||||||
});
|
});
|
||||||
|
|
||||||
const evaluatedTree = this.evaluateTree(this.evalTree, subTreeSortOrder);
|
const evaluatedTree = this.evaluateTree(this.evalTree, subTreeSortOrder);
|
||||||
const evalStop = performance.now();
|
const evalStop = performance.now();
|
||||||
|
|
||||||
const validateStart = performance.now();
|
|
||||||
// Validate and parse updated widgets
|
|
||||||
const updatedWidgets = new Set(
|
|
||||||
subTreeSortOrder.map((path) => path.split(".")[0]),
|
|
||||||
);
|
|
||||||
|
|
||||||
const validatedTree = getValidatedTree(
|
|
||||||
this.widgetConfigMap,
|
|
||||||
evaluatedTree,
|
|
||||||
updatedWidgets,
|
|
||||||
);
|
|
||||||
const validateEnd = performance.now();
|
|
||||||
|
|
||||||
// Remove functions
|
// Remove functions
|
||||||
this.evalTree = removeFunctionsFromDataTree(validatedTree);
|
this.evalTree = removeFunctionsFromDataTree(evaluatedTree);
|
||||||
const totalEnd = performance.now();
|
const totalEnd = performance.now();
|
||||||
// TODO: For some reason we are passing some reference which are getting mutated.
|
// 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
|
// Need to check why big api responses are getting split between two eval runs
|
||||||
|
|
@ -360,7 +348,6 @@ export class DataTreeEvaluator {
|
||||||
calculateSortOrderStop - calculateSortOrderStart
|
calculateSortOrderStop - calculateSortOrderStart
|
||||||
).toFixed(2),
|
).toFixed(2),
|
||||||
evaluate: (evalStop - evalStart).toFixed(2),
|
evaluate: (evalStop - evalStart).toFixed(2),
|
||||||
validate: (validateEnd - validateStart).toFixed(2),
|
|
||||||
};
|
};
|
||||||
LOGS.push({ timeTakenForSubTreeEval });
|
LOGS.push({ timeTakenForSubTreeEval });
|
||||||
return this.evalTree;
|
return this.evalTree;
|
||||||
|
|
@ -382,7 +369,7 @@ export class DataTreeEvaluator {
|
||||||
// Add all the sorted nodes in the final list
|
// Add all the sorted nodes in the final list
|
||||||
finalSortOrder = [...finalSortOrder, ...subSortOrderArray];
|
finalSortOrder = [...finalSortOrder, ...subSortOrderArray];
|
||||||
|
|
||||||
parents = this.getImmediateParentsOfPropertyPaths(subSortOrderArray);
|
parents = getImmediateParentsOfPropertyPaths(subSortOrderArray);
|
||||||
// If we find parents of the property paths in the sorted array, we should continue finding all the nodes dependent
|
// If we find parents of the property paths in the sorted array, we should continue finding all the nodes dependent
|
||||||
// on the parents
|
// on the parents
|
||||||
computeSortOrder = parents.length > 0;
|
computeSortOrder = parents.length > 0;
|
||||||
|
|
@ -407,27 +394,6 @@ export class DataTreeEvaluator {
|
||||||
return finalSortOrderArray;
|
return finalSortOrderArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The idea is to find the immediate parents of the property paths
|
|
||||||
// e.g. For Table1.selectedRow.email, the parent is Table1.selectedRow
|
|
||||||
getImmediateParentsOfPropertyPaths(
|
|
||||||
propertyPaths: Array<string>,
|
|
||||||
): Array<string> {
|
|
||||||
// Use a set to ensure that we dont have duplicates
|
|
||||||
const parents: Set<string> = new Set();
|
|
||||||
|
|
||||||
propertyPaths.forEach((path) => {
|
|
||||||
const parentProperty = path.substr(0, path.lastIndexOf("."));
|
|
||||||
|
|
||||||
if (parentProperty.length != 0) {
|
|
||||||
parents.add(parentProperty);
|
|
||||||
} else {
|
|
||||||
// We have reached the top of the path. No parent exists
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return Array.from(parents);
|
|
||||||
}
|
|
||||||
|
|
||||||
getEvaluationSortOrder(
|
getEvaluationSortOrder(
|
||||||
changes: Array<string>,
|
changes: Array<string>,
|
||||||
inverseMap: DependencyMap,
|
inverseMap: DependencyMap,
|
||||||
|
|
@ -455,9 +421,34 @@ export class DataTreeEvaluator {
|
||||||
return sortOrder;
|
return sortOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getValidationPaths(unevalDataTree: DataTree): Record<string, Set<string>> {
|
||||||
|
const result: Record<string, Set<string>> = {};
|
||||||
|
for (const key in unevalDataTree) {
|
||||||
|
const entity = unevalDataTree[key];
|
||||||
|
if (isAction(entity)) {
|
||||||
|
// TODO: add the properties to a global map somewhere
|
||||||
|
result[entity.name] = new Set(
|
||||||
|
["config", "isLoading", "data"].map((e) => `${entity.name}.${e}`),
|
||||||
|
);
|
||||||
|
} else if (isWidget(entity)) {
|
||||||
|
if (!this.widgetConfigMap[entity.type])
|
||||||
|
throw new CrashingError(
|
||||||
|
`${entity.widgetName} has unrecognised entity type: ${entity.type}`,
|
||||||
|
);
|
||||||
|
const { validations } = this.widgetConfigMap[entity.type];
|
||||||
|
|
||||||
|
result[entity.widgetName] = new Set(
|
||||||
|
Object.keys(validations).map((e) => `${entity.widgetName}.${e}`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
createDependencyMap(unEvalTree: DataTree): DependencyMap {
|
createDependencyMap(unEvalTree: DataTree): DependencyMap {
|
||||||
let dependencyMap: DependencyMap = {};
|
let dependencyMap: DependencyMap = {};
|
||||||
this.allKeys = getAllPaths(unEvalTree);
|
this.allKeys = getAllPaths(unEvalTree);
|
||||||
|
this.validationPaths = this.getValidationPaths(unEvalTree);
|
||||||
Object.keys(unEvalTree).forEach((entityName) => {
|
Object.keys(unEvalTree).forEach((entityName) => {
|
||||||
const entity = unEvalTree[entityName];
|
const entity = unEvalTree[entityName];
|
||||||
if (isAction(entity) || isWidget(entity)) {
|
if (isAction(entity) || isWidget(entity)) {
|
||||||
|
|
@ -908,12 +899,16 @@ export class DataTreeEvaluator {
|
||||||
// In worst case, it tends to take ~12.5% of entire diffCalc (8 ms out of 67ms for 132 array of NEW)
|
// In worst case, it tends to take ~12.5% of entire diffCalc (8 ms out of 67ms for 132 array of NEW)
|
||||||
// TODO: Optimise by only getting paths of changed node
|
// TODO: Optimise by only getting paths of changed node
|
||||||
this.allKeys = getAllPaths(unEvalDataTree);
|
this.allKeys = getAllPaths(unEvalDataTree);
|
||||||
|
this.validationPaths = this.getValidationPaths(unEvalDataTree);
|
||||||
// Transform the diff library events to Appsmith evaluator events
|
// Transform the diff library events to Appsmith evaluator events
|
||||||
differences
|
differences
|
||||||
.map(translateDiffEventToDataTreeDiffEvent)
|
.map(translateDiffEventToDataTreeDiffEvent)
|
||||||
.forEach((dataTreeDiff) => {
|
.forEach((dataTreeDiff) => {
|
||||||
const entityName = dataTreeDiff.payload.propertyPath.split(".")[0];
|
const entityName = dataTreeDiff.payload.propertyPath.split(".")[0];
|
||||||
const entity = unEvalDataTree[entityName];
|
let entity = unEvalDataTree[entityName];
|
||||||
|
if (dataTreeDiff.event === DataTreeDiffEvent.DELETE) {
|
||||||
|
entity = this.oldUnEvalTree[entityName];
|
||||||
|
}
|
||||||
const entityType = isValidEntity(entity) ? entity.ENTITY_TYPE : "noop";
|
const entityType = isValidEntity(entity) ? entity.ENTITY_TYPE : "noop";
|
||||||
|
|
||||||
if (entityType !== "noop") {
|
if (entityType !== "noop") {
|
||||||
|
|
@ -956,13 +951,9 @@ export class DataTreeEvaluator {
|
||||||
removedPaths.push(dataTreeDiff.payload.propertyPath);
|
removedPaths.push(dataTreeDiff.payload.propertyPath);
|
||||||
// If an existing widget was deleted, remove all the bindings from the global dependency map
|
// If an existing widget was deleted, remove all the bindings from the global dependency map
|
||||||
if (
|
if (
|
||||||
entityType === ENTITY_TYPE.WIDGET &&
|
isWidget(entity) &&
|
||||||
dataTreeDiff.payload.propertyPath === entityName
|
dataTreeDiff.payload.propertyPath === entityName
|
||||||
) {
|
) {
|
||||||
const entity: DataTreeWidget = unEvalDataTree[
|
|
||||||
entityName
|
|
||||||
] as DataTreeWidget;
|
|
||||||
|
|
||||||
const widgetBindings = this.listEntityDependencies(
|
const widgetBindings = this.listEntityDependencies(
|
||||||
entity,
|
entity,
|
||||||
entityName,
|
entityName,
|
||||||
|
|
@ -1098,40 +1089,34 @@ export class DataTreeEvaluator {
|
||||||
removedPaths: Array<string>,
|
removedPaths: Array<string>,
|
||||||
) {
|
) {
|
||||||
const changePaths: Set<string> = new Set(dependenciesOfRemovedPaths);
|
const changePaths: Set<string> = new Set(dependenciesOfRemovedPaths);
|
||||||
differences.forEach((d) => {
|
for (const d of differences) {
|
||||||
if (d.path) {
|
if (!Array.isArray(d.path) || d.path.length === 0) continue; // Null check for typescript
|
||||||
// Apply the changes into the evalTree so that it gets the latest changes
|
// Apply the changes into the evalTree so that it gets the latest changes
|
||||||
applyChange(this.evalTree, undefined, d);
|
applyChange(this.evalTree, undefined, d);
|
||||||
|
|
||||||
// If this is a property path change, simply add for evaluation
|
// If this is a property path change, simply add for evaluation and move on
|
||||||
if (d.path.length > 1) {
|
if (d.path.length > 1) {
|
||||||
const propertyPath = convertPathToString(d.path);
|
changePaths.add(convertPathToString(d.path));
|
||||||
changePaths.add(propertyPath);
|
continue;
|
||||||
|
}
|
||||||
// If this is an array update, trim the array index and add it to the change paths for evaluation
|
// A top level entity (widget/action) has been added or deleted
|
||||||
// This is because sometimes inside an object of array time, if only a particular entry changes, the
|
if (d.path.length === 1) {
|
||||||
// difference comes as propertyPath[0].fieldChanged. Another entity could depend on propertyPath and not
|
const entityName = d.path[0];
|
||||||
// propertyPath[0]. The said entity must be evaluated.
|
/**
|
||||||
// To do this, we are trimming the array index
|
* We want to add all pre-existing dynamic and static bindings in dynamic paths of this entity to get evaluated and validated.
|
||||||
if (propertyPath.lastIndexOf("[") > 0) {
|
* Example:
|
||||||
changePaths.add(
|
* - Table1.tableData = {{Api1.data}}
|
||||||
propertyPath.substr(0, propertyPath.lastIndexOf("[")),
|
* - Api1 gets created.
|
||||||
);
|
* - This function gets called with a diff {path:["Api1"]}
|
||||||
|
* We want to add `Api.data` to changedPaths so that `Table1.tableData` can be discovered below.
|
||||||
|
*/
|
||||||
|
if (entityName in this.validationPaths) {
|
||||||
|
for (const dependency of this.validationPaths[entityName]) {
|
||||||
|
changePaths.add(dependency);
|
||||||
}
|
}
|
||||||
} else if (d.path.length === 1) {
|
|
||||||
/*
|
|
||||||
When we see a new widget has been added or or delete an old widget ( d.path.length === 1)
|
|
||||||
We want to add all the dependencies in the sorted order to make
|
|
||||||
sure all the bindings are evaluated.
|
|
||||||
*/
|
|
||||||
this.sortedDependencies.forEach((dependency) => {
|
|
||||||
if (d.path && dependency.split(".")[0] === d.path[0]) {
|
|
||||||
changePaths.add(dependency);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
// If a nested property path has changed and someone (say x) is dependent on the parent of the said property,
|
// If a nested property path has changed and someone (say x) is dependent on the parent of the said property,
|
||||||
// x must also be evaluated. For example, the following relationship exists in dependency map:
|
// x must also be evaluated. For example, the following relationship exists in dependency map:
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,8 @@
|
||||||
import { DependencyMap, isDynamicValue } from "../utils/DynamicBindingUtils";
|
import {
|
||||||
|
DependencyMap,
|
||||||
|
isDynamicValue,
|
||||||
|
isChildPropertyPath,
|
||||||
|
} from "../utils/DynamicBindingUtils";
|
||||||
import { WidgetType } from "../constants/WidgetConstants";
|
import { WidgetType } from "../constants/WidgetConstants";
|
||||||
import { WidgetProps } from "../widgets/BaseWidget";
|
import { WidgetProps } from "../widgets/BaseWidget";
|
||||||
import { WidgetTypeConfigMap } from "../utils/WidgetFactory";
|
import { WidgetTypeConfigMap } from "../utils/WidgetFactory";
|
||||||
|
|
@ -6,12 +10,18 @@ import { VALIDATORS } from "./validations";
|
||||||
import { Diff } from "deep-diff";
|
import { Diff } from "deep-diff";
|
||||||
import {
|
import {
|
||||||
DataTree,
|
DataTree,
|
||||||
|
DataTreeAction,
|
||||||
DataTreeEntity,
|
DataTreeEntity,
|
||||||
DataTreeWidget,
|
DataTreeWidget,
|
||||||
ENTITY_TYPE,
|
ENTITY_TYPE,
|
||||||
} from "../entities/DataTree/dataTreeFactory";
|
} from "../entities/DataTree/dataTreeFactory";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
|
||||||
|
// Dropdown1.options[1].value -> Dropdown1.options[1]
|
||||||
|
// Dropdown1.options[1] -> Dropdown1.options
|
||||||
|
// Dropdown1.options -> Dropdown1
|
||||||
|
export const IMMEDIATE_PARENT_REGEX = /^(.*)(\..*|\[.*\])$/;
|
||||||
|
|
||||||
export enum DataTreeDiffEvent {
|
export enum DataTreeDiffEvent {
|
||||||
NEW = "NEW",
|
NEW = "NEW",
|
||||||
DELETE = "DELETE",
|
DELETE = "DELETE",
|
||||||
|
|
@ -115,13 +125,6 @@ export const translateDiffEventToDataTreeDiffEvent = (
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isPropertyPathOrNestedPath = (
|
|
||||||
path: string,
|
|
||||||
comparePath: string,
|
|
||||||
): boolean => {
|
|
||||||
return path === comparePath || comparePath.startsWith(`${path}.`);
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Table1.selectedRow
|
Table1.selectedRow
|
||||||
Table1.selectedRow.email: ["Input1.defaultText"]
|
Table1.selectedRow.email: ["Input1.defaultText"]
|
||||||
|
|
@ -137,7 +140,7 @@ export const addDependantsOfNestedPropertyPaths = (
|
||||||
withNestedPaths.add(propertyPath);
|
withNestedPaths.add(propertyPath);
|
||||||
dependantNodes
|
dependantNodes
|
||||||
.filter((dependantNodePath) =>
|
.filter((dependantNodePath) =>
|
||||||
isPropertyPathOrNestedPath(propertyPath, dependantNodePath),
|
isChildPropertyPath(propertyPath, dependantNodePath),
|
||||||
)
|
)
|
||||||
.forEach((dependantNodePath) => {
|
.forEach((dependantNodePath) => {
|
||||||
inverseMap[dependantNodePath].forEach((path) => {
|
inverseMap[dependantNodePath].forEach((path) => {
|
||||||
|
|
@ -148,7 +151,7 @@ export const addDependantsOfNestedPropertyPaths = (
|
||||||
return [...withNestedPaths.values()];
|
return [...withNestedPaths.values()];
|
||||||
};
|
};
|
||||||
|
|
||||||
export function isWidget(entity: DataTreeEntity): boolean {
|
export function isWidget(entity: DataTreeEntity): entity is DataTreeWidget {
|
||||||
return (
|
return (
|
||||||
typeof entity === "object" &&
|
typeof entity === "object" &&
|
||||||
"ENTITY_TYPE" in entity &&
|
"ENTITY_TYPE" in entity &&
|
||||||
|
|
@ -156,7 +159,7 @@ export function isWidget(entity: DataTreeEntity): boolean {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isAction(entity: DataTreeEntity): boolean {
|
export function isAction(entity: DataTreeEntity): entity is DataTreeAction {
|
||||||
return (
|
return (
|
||||||
typeof entity === "object" &&
|
typeof entity === "object" &&
|
||||||
"ENTITY_TYPE" in entity &&
|
"ENTITY_TYPE" in entity &&
|
||||||
|
|
@ -197,17 +200,18 @@ export const makeParentsDependOnChildren = (
|
||||||
});
|
});
|
||||||
return depMap;
|
return depMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const makeParentsDependOnChild = (
|
export const makeParentsDependOnChild = (
|
||||||
depMap: DependencyMap,
|
depMap: DependencyMap,
|
||||||
child: string,
|
child: string,
|
||||||
): DependencyMap => {
|
): DependencyMap => {
|
||||||
const result: DependencyMap = depMap;
|
const result: DependencyMap = depMap;
|
||||||
let curKey = child;
|
let curKey = child;
|
||||||
const rgx = /^(.*)\..*$/;
|
|
||||||
let matches: Array<string> | null;
|
let matches: Array<string> | null;
|
||||||
// Note: The `=` is intentional
|
// Note: The `=` is intentional
|
||||||
// Stops looping when match is null
|
// Stops looping when match is null
|
||||||
while ((matches = curKey.match(rgx)) !== null) {
|
while ((matches = curKey.match(IMMEDIATE_PARENT_REGEX)) !== null) {
|
||||||
const parentKey = matches[1];
|
const parentKey = matches[1];
|
||||||
// Todo: switch everything to set.
|
// Todo: switch everything to set.
|
||||||
const existing = new Set(result[parentKey] || []);
|
const existing = new Set(result[parentKey] || []);
|
||||||
|
|
@ -218,6 +222,25 @@ export const makeParentsDependOnChild = (
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The idea is to find the immediate parents of the property paths
|
||||||
|
// e.g. For Table1.selectedRow.email, the parent is Table1.selectedRow
|
||||||
|
export const getImmediateParentsOfPropertyPaths = (
|
||||||
|
propertyPaths: Array<string>,
|
||||||
|
): Array<string> => {
|
||||||
|
// Use a set to ensure that we dont have duplicates
|
||||||
|
const parents: Set<string> = new Set();
|
||||||
|
|
||||||
|
propertyPaths.forEach((path) => {
|
||||||
|
const matches = path.match(IMMEDIATE_PARENT_REGEX);
|
||||||
|
|
||||||
|
if (matches !== null) {
|
||||||
|
parents.add(matches[1]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(parents);
|
||||||
|
};
|
||||||
|
|
||||||
export function validateWidgetProperty(
|
export function validateWidgetProperty(
|
||||||
widgetConfigMap: WidgetTypeConfigMap,
|
widgetConfigMap: WidgetTypeConfigMap,
|
||||||
widgetType: WidgetType,
|
widgetType: WidgetType,
|
||||||
|
|
@ -245,14 +268,8 @@ export function validateWidgetProperty(
|
||||||
export function getValidatedTree(
|
export function getValidatedTree(
|
||||||
widgetConfigMap: WidgetTypeConfigMap,
|
widgetConfigMap: WidgetTypeConfigMap,
|
||||||
tree: DataTree,
|
tree: DataTree,
|
||||||
only?: Set<string>,
|
|
||||||
) {
|
) {
|
||||||
return Object.keys(tree).reduce((tree, entityKey: string) => {
|
return Object.keys(tree).reduce((tree, entityKey: string) => {
|
||||||
if (only && only.size) {
|
|
||||||
if (!only.has(entityKey)) {
|
|
||||||
return tree;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const entity = tree[entityKey] as DataTreeWidget;
|
const entity = tree[entityKey] as DataTreeWidget;
|
||||||
if (!isWidget(entity)) {
|
if (!isWidget(entity)) {
|
||||||
return tree;
|
return tree;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user