2021-02-22 11:14:08 +00:00
|
|
|
import {
|
|
|
|
|
DependencyMap,
|
|
|
|
|
EvalError,
|
|
|
|
|
EvalErrorTypes,
|
2021-09-29 12:03:11 +00:00
|
|
|
EvaluationError,
|
2021-02-22 11:14:08 +00:00
|
|
|
getDynamicBindings,
|
|
|
|
|
getEntityDynamicBindingPathList,
|
2021-06-21 11:09:51 +00:00
|
|
|
getEvalErrorPath,
|
|
|
|
|
getEvalValuePath,
|
2021-02-22 11:14:08 +00:00
|
|
|
isChildPropertyPath,
|
|
|
|
|
isPathADynamicBinding,
|
|
|
|
|
isPathADynamicTrigger,
|
2021-06-21 11:09:51 +00:00
|
|
|
PropertyEvaluationErrorType,
|
2021-02-22 11:14:08 +00:00
|
|
|
} from "utils/DynamicBindingUtils";
|
|
|
|
|
import { WidgetTypeConfigMap } from "utils/WidgetFactory";
|
|
|
|
|
import {
|
|
|
|
|
DataTree,
|
|
|
|
|
DataTreeAction,
|
|
|
|
|
DataTreeEntity,
|
2021-10-01 15:35:05 +00:00
|
|
|
DataTreeJSAction,
|
2021-02-22 11:14:08 +00:00
|
|
|
DataTreeObjectEntity,
|
|
|
|
|
DataTreeWidget,
|
|
|
|
|
ENTITY_TYPE,
|
2021-04-26 05:41:32 +00:00
|
|
|
EvaluationSubstitutionType,
|
2022-01-20 10:59:03 +00:00
|
|
|
PrivateWidgets,
|
2021-02-22 11:14:08 +00:00
|
|
|
} from "entities/DataTree/dataTreeFactory";
|
|
|
|
|
import {
|
|
|
|
|
addDependantsOfNestedPropertyPaths,
|
2021-06-21 11:09:51 +00:00
|
|
|
addErrorToEntityProperty,
|
2021-02-22 11:14:08 +00:00
|
|
|
convertPathToString,
|
|
|
|
|
CrashingError,
|
2021-07-20 10:50:22 +00:00
|
|
|
DataTreeDiff,
|
2021-02-22 11:14:08 +00:00
|
|
|
DataTreeDiffEvent,
|
|
|
|
|
getAllPaths,
|
2021-04-21 14:34:25 +00:00
|
|
|
getEntityNameAndPropertyPath,
|
2021-02-22 11:14:08 +00:00
|
|
|
getImmediateParentsOfPropertyPaths,
|
|
|
|
|
getValidatedTree,
|
2021-10-01 15:35:05 +00:00
|
|
|
isAction,
|
|
|
|
|
isDynamicLeaf,
|
|
|
|
|
isJSAction,
|
|
|
|
|
isWidget,
|
2021-02-22 11:14:08 +00:00
|
|
|
makeParentsDependOnChildren,
|
|
|
|
|
removeFunctions,
|
|
|
|
|
translateDiffEventToDataTreeDiffEvent,
|
|
|
|
|
trimDependantChangePaths,
|
|
|
|
|
validateWidgetProperty,
|
2022-01-27 09:50:05 +00:00
|
|
|
validateActionProperty,
|
2021-11-08 06:49:22 +00:00
|
|
|
getParams,
|
|
|
|
|
updateJSCollectionInDataTree,
|
|
|
|
|
removeFunctionsAndVariableJSCollection,
|
2021-02-22 11:14:08 +00:00
|
|
|
} from "workers/evaluationUtils";
|
|
|
|
|
import _ from "lodash";
|
|
|
|
|
import { applyChange, Diff, diff } from "deep-diff";
|
|
|
|
|
import toposort from "toposort";
|
|
|
|
|
import {
|
|
|
|
|
EXECUTION_PARAM_KEY,
|
2022-01-14 06:20:01 +00:00
|
|
|
EXECUTION_PARAM_REFERENCE_REGEX,
|
2022-02-04 12:28:46 +00:00
|
|
|
THIS_DOT_PARAMS_KEY,
|
2021-03-30 05:29:03 +00:00
|
|
|
} from "constants/AppsmithActionConstants/ActionConstants";
|
2021-02-22 11:14:08 +00:00
|
|
|
import { DATA_BIND_REGEX } from "constants/BindingsConstants";
|
2021-12-23 14:17:20 +00:00
|
|
|
import evaluateSync, {
|
2021-10-05 13:52:27 +00:00
|
|
|
createGlobalData,
|
|
|
|
|
EvalResult,
|
2022-02-04 12:28:46 +00:00
|
|
|
EvaluateContext,
|
2021-10-05 13:52:27 +00:00
|
|
|
EvaluationScriptType,
|
|
|
|
|
getScriptToEval,
|
2021-12-23 14:17:20 +00:00
|
|
|
evaluateAsync,
|
|
|
|
|
isFunctionAsync,
|
2021-10-05 13:52:27 +00:00
|
|
|
} from "workers/evaluate";
|
2021-04-26 05:41:32 +00:00
|
|
|
import { substituteDynamicBindingWithValues } from "workers/evaluationSubstitution";
|
2021-06-21 11:09:51 +00:00
|
|
|
import { Severity } from "entities/AppsmithConsole";
|
2021-10-05 13:52:27 +00:00
|
|
|
import { getLintingErrors } from "workers/lint";
|
2021-11-05 05:49:19 +00:00
|
|
|
import { error as logError } from "loglevel";
|
2021-12-02 10:03:43 +00:00
|
|
|
import { extractIdentifiersFromCode } from "workers/ast";
|
2021-12-23 14:17:20 +00:00
|
|
|
import { JSUpdate } from "utils/JSPaneUtils";
|
2022-01-28 11:10:05 +00:00
|
|
|
import {
|
|
|
|
|
addWidgetPropertyDependencies,
|
|
|
|
|
overrideWidgetProperties,
|
|
|
|
|
} from "./evaluationUtils";
|
2022-01-27 09:50:05 +00:00
|
|
|
import {
|
|
|
|
|
ActionValidationConfigMap,
|
|
|
|
|
ValidationConfig,
|
|
|
|
|
} from "constants/PropertyControlConstants";
|
2022-02-11 10:52:27 +00:00
|
|
|
const clone = require("rfdc/default");
|
2022-04-01 13:16:59 +00:00
|
|
|
|
2021-02-22 11:14:08 +00:00
|
|
|
export default class DataTreeEvaluator {
|
|
|
|
|
dependencyMap: DependencyMap = {};
|
|
|
|
|
sortedDependencies: Array<string> = [];
|
|
|
|
|
inverseDependencyMap: DependencyMap = {};
|
|
|
|
|
widgetConfigMap: WidgetTypeConfigMap = {};
|
|
|
|
|
evalTree: DataTree = {};
|
|
|
|
|
allKeys: Record<string, true> = {};
|
2022-01-20 10:59:03 +00:00
|
|
|
privateWidgets: PrivateWidgets = {};
|
2021-02-22 11:14:08 +00:00
|
|
|
oldUnEvalTree: DataTree = {};
|
|
|
|
|
errors: EvalError[] = [];
|
2021-09-08 17:32:22 +00:00
|
|
|
resolvedFunctions: Record<string, any> = {};
|
2021-11-08 06:49:22 +00:00
|
|
|
currentJSCollectionState: Record<string, any> = {};
|
2021-02-22 11:14:08 +00:00
|
|
|
logs: any[] = [];
|
2022-01-27 09:50:05 +00:00
|
|
|
allActionValidationConfig?: { [actionId: string]: ActionValidationConfigMap };
|
2021-12-25 04:55:22 +00:00
|
|
|
public hasCyclicalDependency = false;
|
2022-01-27 09:50:05 +00:00
|
|
|
constructor(
|
|
|
|
|
widgetConfigMap: WidgetTypeConfigMap,
|
|
|
|
|
allActionValidationConfig?: {
|
|
|
|
|
[actionId: string]: ActionValidationConfigMap;
|
|
|
|
|
},
|
|
|
|
|
) {
|
|
|
|
|
this.allActionValidationConfig = allActionValidationConfig;
|
2021-02-22 11:14:08 +00:00
|
|
|
this.widgetConfigMap = widgetConfigMap;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-29 09:04:40 +00:00
|
|
|
/**
|
|
|
|
|
* This method takes unEvalTree as input and does following
|
|
|
|
|
* 1. parseJSActions and updates JS
|
|
|
|
|
* 2. Creates dependencyMap, sorted dependencyMap
|
|
|
|
|
* 3. Generates inverseDependencyTree
|
|
|
|
|
* 4. Finally, evaluates the unEvalTree and returns that with JSUpdates
|
|
|
|
|
*
|
|
|
|
|
* @param {DataTree} unEvalTree
|
|
|
|
|
* @return {*}
|
|
|
|
|
* @memberof DataTreeEvaluator
|
|
|
|
|
*/
|
2021-11-08 06:49:22 +00:00
|
|
|
createFirstTree(unEvalTree: DataTree) {
|
2021-02-22 11:14:08 +00:00
|
|
|
const totalStart = performance.now();
|
2021-11-29 09:04:40 +00:00
|
|
|
// cloneDeep will make sure not to omit key which has value as undefined.
|
2022-02-11 10:52:27 +00:00
|
|
|
let localUnEvalTree = clone(unEvalTree);
|
2021-11-08 06:49:22 +00:00
|
|
|
let jsUpdates: Record<string, JSUpdate> = {};
|
|
|
|
|
//parse js collection to get functions
|
|
|
|
|
//save current state of js collection action and variables to be added to uneval tree
|
|
|
|
|
//save functions in resolveFunctions (as functions) to be executed as functions are not allowed in evalTree
|
|
|
|
|
//and functions are saved in dataTree as strings
|
|
|
|
|
const parsedCollections = this.parseJSActions(localUnEvalTree);
|
|
|
|
|
jsUpdates = parsedCollections.jsUpdates;
|
|
|
|
|
localUnEvalTree = this.getUpdatedLocalUnEvalTreeAfterJSUpdates(
|
|
|
|
|
jsUpdates,
|
|
|
|
|
localUnEvalTree,
|
|
|
|
|
);
|
2021-02-22 11:14:08 +00:00
|
|
|
// Create dependency map
|
|
|
|
|
const createDependencyStart = performance.now();
|
2021-11-08 06:49:22 +00:00
|
|
|
this.dependencyMap = this.createDependencyMap(localUnEvalTree);
|
2021-02-22 11:14:08 +00:00
|
|
|
const createDependencyEnd = performance.now();
|
|
|
|
|
// Sort
|
|
|
|
|
const sortDependenciesStart = performance.now();
|
|
|
|
|
this.sortedDependencies = this.sortDependencies(this.dependencyMap);
|
|
|
|
|
const sortDependenciesEnd = performance.now();
|
|
|
|
|
// Inverse
|
|
|
|
|
this.inverseDependencyMap = this.getInverseDependencyTree();
|
|
|
|
|
// Evaluate
|
|
|
|
|
const evaluateStart = performance.now();
|
|
|
|
|
const evaluatedTree = this.evaluateTree(
|
2021-11-08 06:49:22 +00:00
|
|
|
localUnEvalTree,
|
2021-09-08 17:32:22 +00:00
|
|
|
this.resolvedFunctions,
|
2021-02-22 11:14:08 +00:00
|
|
|
this.sortedDependencies,
|
|
|
|
|
);
|
|
|
|
|
const evaluateEnd = performance.now();
|
|
|
|
|
// Validate Widgets
|
|
|
|
|
const validateStart = performance.now();
|
2021-06-21 11:09:51 +00:00
|
|
|
this.evalTree = getValidatedTree(evaluatedTree);
|
2021-02-22 11:14:08 +00:00
|
|
|
const validateEnd = performance.now();
|
2021-03-13 14:12:21 +00:00
|
|
|
|
2022-02-11 10:52:27 +00:00
|
|
|
this.oldUnEvalTree = clone(localUnEvalTree);
|
2021-02-22 11:14:08 +00:00
|
|
|
const totalEnd = performance.now();
|
|
|
|
|
const timeTakenForFirstTree = {
|
|
|
|
|
total: (totalEnd - totalStart).toFixed(2),
|
|
|
|
|
createDependencies: (createDependencyEnd - createDependencyStart).toFixed(
|
|
|
|
|
2,
|
|
|
|
|
),
|
|
|
|
|
sortDependencies: (sortDependenciesEnd - sortDependenciesStart).toFixed(
|
|
|
|
|
2,
|
|
|
|
|
),
|
|
|
|
|
evaluate: (evaluateEnd - evaluateStart).toFixed(2),
|
|
|
|
|
validate: (validateEnd - validateStart).toFixed(2),
|
|
|
|
|
dependencies: {
|
|
|
|
|
map: JSON.parse(JSON.stringify(this.dependencyMap)),
|
|
|
|
|
inverseMap: JSON.parse(JSON.stringify(this.inverseDependencyMap)),
|
|
|
|
|
sortedList: JSON.parse(JSON.stringify(this.sortedDependencies)),
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
this.logs.push({ timeTakenForFirstTree });
|
2021-11-08 06:49:22 +00:00
|
|
|
return { evalTree: this.evalTree, jsUpdates: jsUpdates };
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-17 12:05:17 +00:00
|
|
|
isJSObjectFunction(dataTree: DataTree, jsObjectName: string, key: string) {
|
|
|
|
|
const entity = dataTree[jsObjectName];
|
|
|
|
|
if (isJSAction(entity)) {
|
|
|
|
|
return entity.meta.hasOwnProperty(key);
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-08 06:49:22 +00:00
|
|
|
updateLocalUnEvalTree(dataTree: DataTree) {
|
|
|
|
|
//add functions and variables to unevalTree
|
|
|
|
|
Object.keys(this.currentJSCollectionState).forEach((update) => {
|
|
|
|
|
const updates = this.currentJSCollectionState[update];
|
|
|
|
|
if (!!dataTree[update]) {
|
|
|
|
|
Object.keys(updates).forEach((key) => {
|
2022-03-17 12:05:17 +00:00
|
|
|
const data = _.get(dataTree, `${update}.${key}.data`, undefined);
|
|
|
|
|
if (this.isJSObjectFunction(dataTree, update, key)) {
|
|
|
|
|
_.set(dataTree, `${update}.${key}`, new String(updates[key]));
|
|
|
|
|
_.set(dataTree, `${update}.${key}.data`, data);
|
|
|
|
|
} else {
|
|
|
|
|
_.set(dataTree, `${update}.${key}`, updates[key]);
|
|
|
|
|
}
|
2021-11-08 06:49:22 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getUpdatedLocalUnEvalTreeAfterJSUpdates(
|
|
|
|
|
jsUpdates: Record<string, JSUpdate>,
|
|
|
|
|
localUnEvalTree: DataTree,
|
|
|
|
|
) {
|
|
|
|
|
if (!_.isEmpty(jsUpdates)) {
|
|
|
|
|
Object.keys(jsUpdates).forEach((jsEntity) => {
|
|
|
|
|
const entity = localUnEvalTree[jsEntity];
|
|
|
|
|
const parsedBody = jsUpdates[jsEntity].parsedBody;
|
|
|
|
|
if (isJSAction(entity)) {
|
|
|
|
|
if (!!parsedBody) {
|
|
|
|
|
//add/delete/update functions from dataTree
|
|
|
|
|
localUnEvalTree = updateJSCollectionInDataTree(
|
|
|
|
|
parsedBody,
|
|
|
|
|
entity,
|
|
|
|
|
localUnEvalTree,
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
//if parse error remove functions and variables from dataTree
|
|
|
|
|
localUnEvalTree = removeFunctionsAndVariableJSCollection(
|
|
|
|
|
localUnEvalTree,
|
|
|
|
|
entity,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return localUnEvalTree;
|
2021-02-22 11:14:08 +00:00
|
|
|
}
|
|
|
|
|
|
2021-07-20 05:04:12 +00:00
|
|
|
updateDataTree(
|
|
|
|
|
unEvalTree: DataTree,
|
2021-08-04 05:34:44 +00:00
|
|
|
): {
|
|
|
|
|
evaluationOrder: string[];
|
|
|
|
|
unEvalUpdates: DataTreeDiff[];
|
2021-11-08 06:49:22 +00:00
|
|
|
jsUpdates: Record<string, JSUpdate>;
|
2021-08-04 05:34:44 +00:00
|
|
|
} {
|
2021-11-08 06:49:22 +00:00
|
|
|
let localUnEvalTree = Object.assign({}, unEvalTree);
|
2021-02-22 11:14:08 +00:00
|
|
|
const totalStart = performance.now();
|
2021-11-08 06:49:22 +00:00
|
|
|
let jsUpdates: Record<string, JSUpdate> = {};
|
2021-02-22 11:14:08 +00:00
|
|
|
// Calculate diff
|
|
|
|
|
const diffCheckTimeStart = performance.now();
|
2021-11-08 06:49:22 +00:00
|
|
|
//update uneval tree from previously saved current state of collection
|
|
|
|
|
this.updateLocalUnEvalTree(localUnEvalTree);
|
|
|
|
|
//get difference in js collection body to be parsed
|
|
|
|
|
const oldUnEvalTreeJSCollections = this.getJSEntities(this.oldUnEvalTree);
|
|
|
|
|
const localUnEvalTreeJSCollection = this.getJSEntities(localUnEvalTree);
|
|
|
|
|
const jsDifferences: Diff<
|
|
|
|
|
Record<string, DataTreeJSAction>,
|
|
|
|
|
Record<string, DataTreeJSAction>
|
|
|
|
|
>[] = diff(oldUnEvalTreeJSCollections, localUnEvalTreeJSCollection) || [];
|
|
|
|
|
|
|
|
|
|
const jsTranslatedDiffs = _.flatten(
|
|
|
|
|
jsDifferences.map((diff) =>
|
|
|
|
|
translateDiffEventToDataTreeDiffEvent(diff, localUnEvalTree),
|
|
|
|
|
),
|
|
|
|
|
);
|
2021-12-23 14:17:20 +00:00
|
|
|
//save parsed functions in resolveJSFunctions, update current state of js collection
|
2021-11-08 06:49:22 +00:00
|
|
|
const parsedCollections = this.parseJSActions(
|
|
|
|
|
localUnEvalTree,
|
|
|
|
|
jsTranslatedDiffs,
|
|
|
|
|
this.oldUnEvalTree,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
jsUpdates = parsedCollections.jsUpdates;
|
|
|
|
|
//update local data tree if js body has updated (remove/update/add js functions or variables)
|
|
|
|
|
localUnEvalTree = this.getUpdatedLocalUnEvalTreeAfterJSUpdates(
|
|
|
|
|
jsUpdates,
|
|
|
|
|
localUnEvalTree,
|
|
|
|
|
);
|
|
|
|
|
|
2021-09-08 17:32:22 +00:00
|
|
|
const differences: Diff<DataTree, DataTree>[] =
|
|
|
|
|
diff(this.oldUnEvalTree, localUnEvalTree) || [];
|
2022-01-28 11:10:05 +00:00
|
|
|
// Since eval tree is listening to possible events that don't cause differences
|
2021-02-22 11:14:08 +00:00
|
|
|
// We want to check if no diffs are present and bail out early
|
|
|
|
|
if (differences.length === 0) {
|
2021-06-21 11:09:51 +00:00
|
|
|
return {
|
|
|
|
|
evaluationOrder: [],
|
2021-08-04 05:34:44 +00:00
|
|
|
unEvalUpdates: [],
|
2021-11-08 06:49:22 +00:00
|
|
|
jsUpdates: {},
|
2021-06-21 11:09:51 +00:00
|
|
|
};
|
2021-02-22 11:14:08 +00:00
|
|
|
}
|
2022-03-03 07:42:23 +00:00
|
|
|
//find all differences which can lead to updating of dependency map
|
2021-08-04 05:34:44 +00:00
|
|
|
const translatedDiffs = _.flatten(
|
2021-09-08 17:32:22 +00:00
|
|
|
differences.map((diff) =>
|
|
|
|
|
translateDiffEventToDataTreeDiffEvent(diff, localUnEvalTree),
|
|
|
|
|
),
|
2021-08-04 05:34:44 +00:00
|
|
|
);
|
|
|
|
|
this.logs.push({ differences, translatedDiffs });
|
2021-02-22 11:14:08 +00:00
|
|
|
const diffCheckTimeStop = performance.now();
|
|
|
|
|
// Check if dependencies have changed
|
|
|
|
|
const updateDependenciesStart = performance.now();
|
|
|
|
|
|
2022-02-11 10:52:27 +00:00
|
|
|
this.logs.push({ differences: clone(differences), translatedDiffs });
|
2021-09-08 17:32:22 +00:00
|
|
|
|
2021-02-22 11:14:08 +00:00
|
|
|
// 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 {
|
|
|
|
|
dependenciesOfRemovedPaths,
|
|
|
|
|
removedPaths,
|
2021-09-08 17:32:22 +00:00
|
|
|
} = this.updateDependencyMap(translatedDiffs, localUnEvalTree);
|
2021-02-22 11:14:08 +00:00
|
|
|
const updateDependenciesStop = performance.now();
|
|
|
|
|
|
2022-03-03 07:42:23 +00:00
|
|
|
this.applyDifferencesToEvalTree(differences);
|
|
|
|
|
|
2021-02-22 11:14:08 +00:00
|
|
|
const calculateSortOrderStart = performance.now();
|
|
|
|
|
|
|
|
|
|
const subTreeSortOrder: string[] = this.calculateSubTreeSortOrder(
|
|
|
|
|
differences,
|
|
|
|
|
dependenciesOfRemovedPaths,
|
|
|
|
|
removedPaths,
|
2021-09-08 17:32:22 +00:00
|
|
|
localUnEvalTree,
|
2021-02-22 11:14:08 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const calculateSortOrderStop = performance.now();
|
|
|
|
|
|
|
|
|
|
// Evaluate
|
|
|
|
|
const evalStart = performance.now();
|
2021-03-30 05:29:03 +00:00
|
|
|
|
|
|
|
|
// Remove anything from the sort order that is not a dynamic leaf since only those need evaluation
|
|
|
|
|
const evaluationOrder = subTreeSortOrder.filter((propertyPath) => {
|
|
|
|
|
// We are setting all values from our uneval tree to the old eval tree we have
|
|
|
|
|
// So that the actual uneval value can be evaluated
|
2021-09-08 17:32:22 +00:00
|
|
|
if (isDynamicLeaf(localUnEvalTree, propertyPath)) {
|
|
|
|
|
const unEvalPropValue = _.get(localUnEvalTree, propertyPath);
|
|
|
|
|
const evalPropValue = _.get(this.evalTree, propertyPath);
|
|
|
|
|
if (!_.isFunction(evalPropValue)) {
|
|
|
|
|
_.set(this.evalTree, propertyPath, unEvalPropValue);
|
|
|
|
|
}
|
2021-03-30 05:29:03 +00:00
|
|
|
return true;
|
2021-02-22 11:14:08 +00:00
|
|
|
}
|
2021-03-30 05:29:03 +00:00
|
|
|
return false;
|
2021-02-22 11:14:08 +00:00
|
|
|
});
|
2022-04-01 13:16:59 +00:00
|
|
|
|
2021-06-21 11:09:51 +00:00
|
|
|
this.logs.push({
|
|
|
|
|
sortedDependencies: this.sortedDependencies,
|
|
|
|
|
inverse: this.inverseDependencyMap,
|
|
|
|
|
updatedDependencyMap: this.dependencyMap,
|
2021-09-08 17:32:22 +00:00
|
|
|
evaluationOrder: evaluationOrder,
|
2021-06-21 11:09:51 +00:00
|
|
|
});
|
2021-02-22 11:14:08 +00:00
|
|
|
|
|
|
|
|
// Remove any deleted paths from the eval tree
|
|
|
|
|
removedPaths.forEach((removedPath) => {
|
|
|
|
|
_.unset(this.evalTree, removedPath);
|
|
|
|
|
});
|
2021-09-08 17:32:22 +00:00
|
|
|
const newEvalTree = this.evaluateTree(
|
|
|
|
|
this.evalTree,
|
|
|
|
|
this.resolvedFunctions,
|
|
|
|
|
evaluationOrder,
|
|
|
|
|
);
|
2021-02-22 11:14:08 +00:00
|
|
|
const evalStop = performance.now();
|
|
|
|
|
|
2021-07-20 05:04:12 +00:00
|
|
|
const evalTreeDiffsStart = performance.now();
|
|
|
|
|
|
|
|
|
|
const evalTreeDiffsStop = performance.now();
|
|
|
|
|
|
2021-02-22 11:14:08 +00:00
|
|
|
const totalEnd = performance.now();
|
|
|
|
|
// 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
|
2022-02-11 10:52:27 +00:00
|
|
|
this.oldUnEvalTree = clone(localUnEvalTree);
|
2021-07-20 05:04:12 +00:00
|
|
|
this.evalTree = newEvalTree;
|
2021-02-22 11:14:08 +00:00
|
|
|
const timeTakenForSubTreeEval = {
|
|
|
|
|
total: (totalEnd - totalStart).toFixed(2),
|
|
|
|
|
findDifferences: (diffCheckTimeStop - diffCheckTimeStart).toFixed(2),
|
2021-07-20 05:04:12 +00:00
|
|
|
findEvalDifferences: (evalTreeDiffsStop - evalTreeDiffsStart).toFixed(2),
|
2021-02-22 11:14:08 +00:00
|
|
|
updateDependencies: (
|
|
|
|
|
updateDependenciesStop - updateDependenciesStart
|
|
|
|
|
).toFixed(2),
|
|
|
|
|
calculateSortOrder: (
|
|
|
|
|
calculateSortOrderStop - calculateSortOrderStart
|
|
|
|
|
).toFixed(2),
|
|
|
|
|
evaluate: (evalStop - evalStart).toFixed(2),
|
|
|
|
|
};
|
|
|
|
|
this.logs.push({ timeTakenForSubTreeEval });
|
2021-06-04 07:09:36 +00:00
|
|
|
return {
|
2021-07-08 07:04:47 +00:00
|
|
|
evaluationOrder,
|
2021-08-04 05:34:44 +00:00
|
|
|
unEvalUpdates: translatedDiffs,
|
2021-11-08 06:49:22 +00:00
|
|
|
jsUpdates: jsUpdates,
|
2021-06-04 07:09:36 +00:00
|
|
|
};
|
2021-02-22 11:14:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getCompleteSortOrder(
|
|
|
|
|
changes: Array<string>,
|
|
|
|
|
inverseMap: DependencyMap,
|
|
|
|
|
): Array<string> {
|
|
|
|
|
let finalSortOrder: Array<string> = [];
|
|
|
|
|
let computeSortOrder = true;
|
|
|
|
|
// Initialize parents with the current sent of property paths that need to be evaluated
|
|
|
|
|
let parents = changes;
|
|
|
|
|
let subSortOrderArray: Array<string>;
|
|
|
|
|
while (computeSortOrder) {
|
|
|
|
|
// Get all the nodes that would be impacted by the evaluation of the nodes in parents array in sorted order
|
|
|
|
|
subSortOrderArray = this.getEvaluationSortOrder(parents, inverseMap);
|
|
|
|
|
|
|
|
|
|
// Add all the sorted nodes in the final list
|
|
|
|
|
finalSortOrder = [...finalSortOrder, ...subSortOrderArray];
|
|
|
|
|
|
|
|
|
|
parents = getImmediateParentsOfPropertyPaths(subSortOrderArray);
|
|
|
|
|
// If we find parents of the property paths in the sorted array, we should continue finding all the nodes dependent
|
|
|
|
|
// on the parents
|
|
|
|
|
computeSortOrder = parents.length > 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove duplicates from this list. Since we explicitly walk down the tree and implicitly (by fetching parents) walk
|
|
|
|
|
// up the tree, there are bound to be many duplicates.
|
2021-02-24 16:26:00 +00:00
|
|
|
const uniqueKeysInSortOrder = new Set(finalSortOrder);
|
2021-02-22 11:14:08 +00:00
|
|
|
|
2021-02-24 16:26:00 +00:00
|
|
|
// if a property path evaluation gets triggered by diff top order changes
|
|
|
|
|
// this could lead to incorrect sort order in spite of the bfs traversal
|
|
|
|
|
const sortOrderPropertyPaths: string[] = [];
|
|
|
|
|
this.sortedDependencies.forEach((path) => {
|
|
|
|
|
if (uniqueKeysInSortOrder.has(path)) {
|
|
|
|
|
sortOrderPropertyPaths.push(path);
|
|
|
|
|
// remove from the uniqueKeysInSortOrder
|
|
|
|
|
uniqueKeysInSortOrder.delete(path);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
// Add any remaining paths in the uniqueKeysInSortOrder
|
|
|
|
|
const completeSortOrder = [
|
|
|
|
|
...Array.from(uniqueKeysInSortOrder),
|
|
|
|
|
...sortOrderPropertyPaths,
|
|
|
|
|
];
|
2021-02-22 11:14:08 +00:00
|
|
|
|
|
|
|
|
//Trim this list to now remove the property paths which are simply entity names
|
|
|
|
|
const finalSortOrderArray: Array<string> = [];
|
2021-02-24 16:26:00 +00:00
|
|
|
completeSortOrder.forEach((propertyPath) => {
|
2021-02-22 11:14:08 +00:00
|
|
|
const lastIndexOfDot = propertyPath.lastIndexOf(".");
|
|
|
|
|
// Only do this for property paths and not the entity themselves
|
|
|
|
|
if (lastIndexOfDot !== -1) {
|
|
|
|
|
finalSortOrderArray.push(propertyPath);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return finalSortOrderArray;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getEvaluationSortOrder(
|
|
|
|
|
changes: Array<string>,
|
|
|
|
|
inverseMap: DependencyMap,
|
|
|
|
|
): Array<string> {
|
|
|
|
|
const sortOrder: Array<string> = [...changes];
|
|
|
|
|
let iterator = 0;
|
|
|
|
|
while (iterator < sortOrder.length) {
|
|
|
|
|
// Find all the nodes who are to be evaluated when sortOrder[iterator] changes
|
|
|
|
|
const newNodes = inverseMap[sortOrder[iterator]];
|
|
|
|
|
|
|
|
|
|
// If we find more nodes that would be impacted by the evaluation of the node being investigated
|
|
|
|
|
// we add these to the sort order.
|
|
|
|
|
if (newNodes) {
|
|
|
|
|
newNodes.forEach((toBeEvaluatedNode) => {
|
|
|
|
|
// Only add the nodes if they haven't been already added for evaluation in the list. Since we are doing
|
|
|
|
|
// breadth first traversal, we should be safe in not changing the evaluation order and adding this now at this
|
|
|
|
|
// point instead of the previous index found.
|
|
|
|
|
if (!sortOrder.includes(toBeEvaluatedNode)) {
|
|
|
|
|
sortOrder.push(toBeEvaluatedNode);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
iterator++;
|
|
|
|
|
}
|
|
|
|
|
return sortOrder;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
createDependencyMap(unEvalTree: DataTree): DependencyMap {
|
|
|
|
|
let dependencyMap: DependencyMap = {};
|
|
|
|
|
this.allKeys = getAllPaths(unEvalTree);
|
|
|
|
|
Object.keys(unEvalTree).forEach((entityName) => {
|
|
|
|
|
const entity = unEvalTree[entityName];
|
2021-09-08 17:32:22 +00:00
|
|
|
if (isAction(entity) || isWidget(entity) || isJSAction(entity)) {
|
2021-02-22 11:14:08 +00:00
|
|
|
const entityListedDependencies = this.listEntityDependencies(
|
|
|
|
|
entity,
|
|
|
|
|
entityName,
|
|
|
|
|
);
|
|
|
|
|
dependencyMap = { ...dependencyMap, ...entityListedDependencies };
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
Object.keys(dependencyMap).forEach((key) => {
|
|
|
|
|
dependencyMap[key] = _.flatten(
|
2021-12-02 10:03:43 +00:00
|
|
|
dependencyMap[key].map((path) => {
|
|
|
|
|
try {
|
|
|
|
|
return extractReferencesFromBinding(path, this.allKeys);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
this.errors.push({
|
|
|
|
|
type: EvalErrorTypes.EXTRACT_DEPENDENCY_ERROR,
|
|
|
|
|
message: e.message,
|
|
|
|
|
context: {
|
|
|
|
|
script: path,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
}),
|
2021-02-22 11:14:08 +00:00
|
|
|
);
|
|
|
|
|
});
|
2022-03-03 10:59:52 +00:00
|
|
|
dependencyMap = makeParentsDependOnChildren(dependencyMap, this.allKeys);
|
2021-02-22 11:14:08 +00:00
|
|
|
return dependencyMap;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-20 10:59:03 +00:00
|
|
|
getPrivateWidgets(dataTree: DataTree): PrivateWidgets {
|
|
|
|
|
let privateWidgets: PrivateWidgets = {};
|
|
|
|
|
Object.keys(dataTree).forEach((entityName) => {
|
|
|
|
|
const entity = dataTree[entityName];
|
|
|
|
|
if (isWidget(entity) && !_.isEmpty(entity.privateWidgets)) {
|
|
|
|
|
privateWidgets = { ...privateWidgets, ...entity.privateWidgets };
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return privateWidgets;
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-22 11:14:08 +00:00
|
|
|
listEntityDependencies(
|
2021-09-08 17:32:22 +00:00
|
|
|
entity: DataTreeWidget | DataTreeAction | DataTreeJSAction,
|
2021-02-22 11:14:08 +00:00
|
|
|
entityName: string,
|
|
|
|
|
): DependencyMap {
|
2022-01-28 11:10:05 +00:00
|
|
|
let dependencies: DependencyMap = {};
|
2021-11-24 04:43:50 +00:00
|
|
|
|
2021-04-26 05:41:32 +00:00
|
|
|
if (isWidget(entity)) {
|
2021-10-05 13:52:27 +00:00
|
|
|
// Adding the dynamic triggers in the dependency list as they need linting whenever updated
|
2022-04-07 11:01:31 +00:00
|
|
|
// we don't make it dependent on anything else
|
|
|
|
|
if (entity.dynamicTriggerPathList) {
|
|
|
|
|
Object.values(entity.dynamicTriggerPathList).forEach(({ key }) => {
|
|
|
|
|
dependencies[`${entityName}.${key}`] = [];
|
2021-10-05 13:52:27 +00:00
|
|
|
});
|
|
|
|
|
}
|
2022-01-28 11:10:05 +00:00
|
|
|
const widgetDependencies = addWidgetPropertyDependencies({
|
|
|
|
|
entity,
|
|
|
|
|
entityName,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
dependencies = {
|
|
|
|
|
...dependencies,
|
|
|
|
|
...widgetDependencies,
|
|
|
|
|
};
|
2021-04-26 05:41:32 +00:00
|
|
|
}
|
2022-01-28 11:10:05 +00:00
|
|
|
|
2021-11-08 06:49:22 +00:00
|
|
|
if (isAction(entity) || isJSAction(entity)) {
|
2021-04-26 05:41:32 +00:00
|
|
|
Object.entries(entity.dependencyMap).forEach(
|
2021-05-06 12:58:06 +00:00
|
|
|
([path, entityDependencies]) => {
|
|
|
|
|
const actionDependentPaths: Array<string> = [];
|
|
|
|
|
const mainPath = `${entityName}.${path}`;
|
|
|
|
|
// Only add dependencies for paths which exist at the moment in appsmith world
|
|
|
|
|
if (this.allKeys.hasOwnProperty(mainPath)) {
|
|
|
|
|
// Only add dependent paths which exist in the data tree. Skip all the other paths to avoid creating
|
|
|
|
|
// a cyclical dependency.
|
|
|
|
|
entityDependencies.forEach((dependentPath) => {
|
|
|
|
|
const completePath = `${entityName}.${dependentPath}`;
|
|
|
|
|
if (this.allKeys.hasOwnProperty(completePath)) {
|
|
|
|
|
actionDependentPaths.push(completePath);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
dependencies[mainPath] = actionDependentPaths;
|
|
|
|
|
}
|
2021-04-26 05:41:32 +00:00
|
|
|
},
|
|
|
|
|
);
|
2021-02-22 11:14:08 +00:00
|
|
|
}
|
2021-09-08 17:32:22 +00:00
|
|
|
if (isJSAction(entity)) {
|
2022-01-28 11:10:05 +00:00
|
|
|
// making functions dependent on their function body entities
|
2021-09-08 17:32:22 +00:00
|
|
|
if (entity.bindingPaths) {
|
|
|
|
|
Object.keys(entity.bindingPaths).forEach((propertyPath) => {
|
|
|
|
|
const existingDeps =
|
|
|
|
|
dependencies[`${entityName}.${propertyPath}`] || [];
|
2022-03-17 12:05:17 +00:00
|
|
|
const unevalPropValue = _.get(entity, propertyPath).toString();
|
|
|
|
|
const { jsSnippets } = getDynamicBindings(unevalPropValue, entity);
|
2021-09-08 17:32:22 +00:00
|
|
|
dependencies[`${entityName}.${propertyPath}`] = existingDeps.concat(
|
|
|
|
|
jsSnippets.filter((jsSnippet) => !!jsSnippet),
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-11-24 04:43:50 +00:00
|
|
|
|
|
|
|
|
if (isAction(entity) || isWidget(entity)) {
|
2022-01-28 11:10:05 +00:00
|
|
|
// add the dynamic binding paths to the dependency map
|
2021-11-24 04:43:50 +00:00
|
|
|
const dynamicBindingPathList = getEntityDynamicBindingPathList(entity);
|
|
|
|
|
if (dynamicBindingPathList.length) {
|
|
|
|
|
dynamicBindingPathList.forEach((dynamicPath) => {
|
|
|
|
|
const propertyPath = dynamicPath.key;
|
|
|
|
|
const unevalPropValue = _.get(entity, propertyPath);
|
|
|
|
|
const { jsSnippets } = getDynamicBindings(unevalPropValue);
|
|
|
|
|
const existingDeps =
|
|
|
|
|
dependencies[`${entityName}.${propertyPath}`] || [];
|
|
|
|
|
dependencies[`${entityName}.${propertyPath}`] = existingDeps.concat(
|
|
|
|
|
jsSnippets.filter((jsSnippet) => !!jsSnippet),
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-22 11:14:08 +00:00
|
|
|
return dependencies;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
evaluateTree(
|
|
|
|
|
oldUnevalTree: DataTree,
|
2021-09-08 17:32:22 +00:00
|
|
|
resolvedFunctions: Record<string, any>,
|
2021-02-22 11:14:08 +00:00
|
|
|
sortedDependencies: Array<string>,
|
|
|
|
|
): DataTree {
|
2022-02-11 10:52:27 +00:00
|
|
|
const tree = clone(oldUnevalTree);
|
2021-02-22 11:14:08 +00:00
|
|
|
try {
|
|
|
|
|
return sortedDependencies.reduce(
|
2021-04-21 14:34:25 +00:00
|
|
|
(currentTree: DataTree, fullPropertyPath: string) => {
|
|
|
|
|
const { entityName, propertyPath } = getEntityNameAndPropertyPath(
|
|
|
|
|
fullPropertyPath,
|
|
|
|
|
);
|
2021-04-26 05:41:32 +00:00
|
|
|
const entity = currentTree[entityName] as
|
|
|
|
|
| DataTreeWidget
|
|
|
|
|
| DataTreeAction;
|
2021-04-21 14:34:25 +00:00
|
|
|
const unEvalPropertyValue = _.get(
|
|
|
|
|
currentTree as any,
|
|
|
|
|
fullPropertyPath,
|
|
|
|
|
);
|
2022-01-27 09:50:05 +00:00
|
|
|
|
2021-02-22 11:14:08 +00:00
|
|
|
const isABindingPath =
|
2021-11-08 06:49:22 +00:00
|
|
|
(isAction(entity) || isWidget(entity) || isJSAction(entity)) &&
|
2021-04-21 14:34:25 +00:00
|
|
|
isPathADynamicBinding(entity, propertyPath);
|
2021-10-05 13:52:27 +00:00
|
|
|
const isATriggerPath =
|
|
|
|
|
isWidget(entity) && isPathADynamicTrigger(entity, propertyPath);
|
2021-02-22 11:14:08 +00:00
|
|
|
let evalPropertyValue;
|
|
|
|
|
const requiresEval =
|
2021-10-05 13:52:27 +00:00
|
|
|
isABindingPath &&
|
|
|
|
|
!isATriggerPath &&
|
2021-11-08 06:49:22 +00:00
|
|
|
(isDynamicValue(unEvalPropertyValue) || isJSAction(entity));
|
2021-06-21 11:09:51 +00:00
|
|
|
if (propertyPath) {
|
|
|
|
|
_.set(currentTree, getEvalErrorPath(fullPropertyPath), []);
|
|
|
|
|
}
|
2021-02-22 11:14:08 +00:00
|
|
|
if (requiresEval) {
|
2021-04-26 05:41:32 +00:00
|
|
|
const evaluationSubstitutionType =
|
|
|
|
|
entity.bindingPaths[propertyPath] ||
|
|
|
|
|
EvaluationSubstitutionType.TEMPLATE;
|
2021-12-14 08:30:43 +00:00
|
|
|
|
2022-02-04 12:28:46 +00:00
|
|
|
const contextData: EvaluateContext = {};
|
|
|
|
|
if (isAction(entity)) {
|
|
|
|
|
contextData.thisContext = {
|
|
|
|
|
params: {},
|
|
|
|
|
};
|
|
|
|
|
}
|
2021-02-22 11:14:08 +00:00
|
|
|
try {
|
2021-04-26 05:41:32 +00:00
|
|
|
evalPropertyValue = this.getDynamicValue(
|
2021-02-22 11:14:08 +00:00
|
|
|
unEvalPropertyValue,
|
2021-04-26 05:41:32 +00:00
|
|
|
currentTree,
|
2021-09-08 17:32:22 +00:00
|
|
|
resolvedFunctions,
|
2021-04-26 05:41:32 +00:00
|
|
|
evaluationSubstitutionType,
|
2022-02-04 12:28:46 +00:00
|
|
|
contextData,
|
2021-05-26 12:32:43 +00:00
|
|
|
undefined,
|
|
|
|
|
fullPropertyPath,
|
2021-02-22 11:14:08 +00:00
|
|
|
);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
this.errors.push({
|
|
|
|
|
type: EvalErrorTypes.EVAL_PROPERTY_ERROR,
|
|
|
|
|
message: e.message,
|
|
|
|
|
context: {
|
2021-04-21 14:34:25 +00:00
|
|
|
propertyPath: fullPropertyPath,
|
2021-02-22 11:14:08 +00:00
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
evalPropertyValue = undefined;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
evalPropertyValue = unEvalPropertyValue;
|
|
|
|
|
}
|
2021-10-05 13:52:27 +00:00
|
|
|
if (isWidget(entity) && !isATriggerPath) {
|
2021-04-21 14:34:25 +00:00
|
|
|
if (propertyPath) {
|
2022-01-28 11:10:05 +00:00
|
|
|
let parsedValue = this.validateAndParseWidgetProperty({
|
2021-04-21 14:34:25 +00:00
|
|
|
fullPropertyPath,
|
2022-01-28 11:10:05 +00:00
|
|
|
widget: entity,
|
2021-02-22 11:14:08 +00:00
|
|
|
currentTree,
|
|
|
|
|
evalPropertyValue,
|
|
|
|
|
unEvalPropertyValue,
|
2022-01-28 11:10:05 +00:00
|
|
|
});
|
|
|
|
|
const overwriteObj = overrideWidgetProperties(
|
|
|
|
|
entity,
|
|
|
|
|
propertyPath,
|
|
|
|
|
parsedValue,
|
|
|
|
|
currentTree,
|
2021-02-22 11:14:08 +00:00
|
|
|
);
|
2022-01-28 11:10:05 +00:00
|
|
|
|
|
|
|
|
if (overwriteObj && overwriteObj.overwriteParsedValue) {
|
|
|
|
|
parsedValue = overwriteObj.newValue;
|
2021-02-22 11:14:08 +00:00
|
|
|
}
|
2021-04-21 14:34:25 +00:00
|
|
|
return _.set(currentTree, fullPropertyPath, parsedValue);
|
2021-02-22 11:14:08 +00:00
|
|
|
}
|
2021-04-21 14:34:25 +00:00
|
|
|
return _.set(currentTree, fullPropertyPath, evalPropertyValue);
|
2021-10-05 13:52:27 +00:00
|
|
|
} else if (isATriggerPath) {
|
2021-12-15 05:18:45 +00:00
|
|
|
const errors = this.lintTriggerPath(
|
|
|
|
|
evalPropertyValue,
|
|
|
|
|
entity,
|
|
|
|
|
currentTree,
|
|
|
|
|
);
|
2021-10-05 13:52:27 +00:00
|
|
|
addErrorToEntityProperty(errors, currentTree, fullPropertyPath);
|
|
|
|
|
return currentTree;
|
2021-06-21 11:09:51 +00:00
|
|
|
} else if (isAction(entity)) {
|
2022-01-27 09:50:05 +00:00
|
|
|
if (this.allActionValidationConfig) {
|
|
|
|
|
const configProperty = propertyPath.replace(
|
|
|
|
|
"config",
|
|
|
|
|
"actionConfiguration",
|
|
|
|
|
);
|
2022-04-05 07:46:11 +00:00
|
|
|
const validationConfig =
|
|
|
|
|
!!this.allActionValidationConfig[entity.actionId] &&
|
|
|
|
|
this.allActionValidationConfig[entity.actionId][configProperty];
|
|
|
|
|
if (!!validationConfig && !_.isEmpty(validationConfig)) {
|
|
|
|
|
this.validateActionProperty(
|
|
|
|
|
fullPropertyPath,
|
|
|
|
|
entity,
|
|
|
|
|
currentTree,
|
|
|
|
|
evalPropertyValue,
|
|
|
|
|
unEvalPropertyValue,
|
|
|
|
|
validationConfig,
|
|
|
|
|
);
|
|
|
|
|
}
|
2022-01-27 09:50:05 +00:00
|
|
|
}
|
2021-06-21 11:09:51 +00:00
|
|
|
const safeEvaluatedValue = removeFunctions(evalPropertyValue);
|
|
|
|
|
_.set(
|
|
|
|
|
currentTree,
|
|
|
|
|
getEvalValuePath(fullPropertyPath),
|
|
|
|
|
safeEvaluatedValue,
|
|
|
|
|
);
|
|
|
|
|
_.set(currentTree, fullPropertyPath, evalPropertyValue);
|
|
|
|
|
return currentTree;
|
2021-11-08 06:49:22 +00:00
|
|
|
} else if (isJSAction(entity)) {
|
|
|
|
|
return currentTree;
|
2021-02-22 11:14:08 +00:00
|
|
|
} else {
|
2021-04-21 14:34:25 +00:00
|
|
|
return _.set(currentTree, fullPropertyPath, evalPropertyValue);
|
2021-02-22 11:14:08 +00:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
tree,
|
|
|
|
|
);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
this.errors.push({
|
|
|
|
|
type: EvalErrorTypes.EVAL_TREE_ERROR,
|
|
|
|
|
message: e.message,
|
|
|
|
|
});
|
|
|
|
|
return tree;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-27 09:50:05 +00:00
|
|
|
setAllActionValidationConfig(allActionValidationConfig: {
|
|
|
|
|
[actionId: string]: ActionValidationConfigMap;
|
|
|
|
|
}): void {
|
|
|
|
|
this.allActionValidationConfig = allActionValidationConfig;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-20 10:50:22 +00:00
|
|
|
sortDependencies(
|
|
|
|
|
dependencyMap: DependencyMap,
|
|
|
|
|
diffs?: (DataTreeDiff | DataTreeDiff[])[],
|
|
|
|
|
): Array<string> {
|
2021-02-22 11:14:08 +00:00
|
|
|
const dependencyTree: Array<[string, string]> = [];
|
|
|
|
|
Object.keys(dependencyMap).forEach((key: string) => {
|
|
|
|
|
if (dependencyMap[key].length) {
|
|
|
|
|
dependencyMap[key].forEach((dep) => dependencyTree.push([key, dep]));
|
|
|
|
|
} else {
|
|
|
|
|
// Set no dependency
|
|
|
|
|
dependencyTree.push([key, ""]);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// sort dependencies and remove empty dependencies
|
|
|
|
|
return toposort(dependencyTree)
|
|
|
|
|
.reverse()
|
|
|
|
|
.filter((d) => !!d);
|
|
|
|
|
} catch (e) {
|
2021-03-10 06:58:52 +00:00
|
|
|
// Cyclic dependency found. Extract all node and entity type
|
|
|
|
|
const node = e.message.match(
|
|
|
|
|
new RegExp('Cyclic dependency, node was:"(.*)"'),
|
|
|
|
|
)[1];
|
|
|
|
|
|
|
|
|
|
let entityType = "UNKNOWN";
|
|
|
|
|
const entityName = node.split(".")[0];
|
2021-06-23 13:25:18 +00:00
|
|
|
const entity = _.get(this.oldUnEvalTree, entityName);
|
2021-03-10 06:58:52 +00:00
|
|
|
if (entity && isWidget(entity)) {
|
|
|
|
|
entityType = entity.type;
|
|
|
|
|
} else if (entity && isAction(entity)) {
|
|
|
|
|
entityType = entity.pluginType;
|
|
|
|
|
}
|
2021-02-22 11:14:08 +00:00
|
|
|
this.errors.push({
|
2021-06-04 07:09:36 +00:00
|
|
|
type: EvalErrorTypes.CYCLICAL_DEPENDENCY_ERROR,
|
2021-03-10 06:58:52 +00:00
|
|
|
message: "Cyclic dependency found while evaluating.",
|
|
|
|
|
context: {
|
|
|
|
|
node,
|
|
|
|
|
entityType,
|
2021-07-20 10:50:22 +00:00
|
|
|
dependencyMap,
|
|
|
|
|
diffs,
|
2021-03-10 06:58:52 +00:00
|
|
|
},
|
2021-02-22 11:14:08 +00:00
|
|
|
});
|
2021-11-05 05:49:19 +00:00
|
|
|
logError("CYCLICAL DEPENDENCY MAP", dependencyMap);
|
2021-12-25 04:55:22 +00:00
|
|
|
this.hasCyclicalDependency = true;
|
2021-02-22 11:14:08 +00:00
|
|
|
throw new CrashingError(e.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getDynamicValue(
|
|
|
|
|
dynamicBinding: string,
|
|
|
|
|
data: DataTree,
|
2021-09-08 17:32:22 +00:00
|
|
|
resolvedFunctions: Record<string, any>,
|
2021-04-26 05:41:32 +00:00
|
|
|
evaluationSubstitutionType: EvaluationSubstitutionType,
|
2022-02-04 12:28:46 +00:00
|
|
|
contextData?: EvaluateContext,
|
2021-02-22 11:14:08 +00:00
|
|
|
callBackData?: Array<any>,
|
2021-05-26 12:32:43 +00:00
|
|
|
fullPropertyPath?: string,
|
2021-02-22 11:14:08 +00:00
|
|
|
) {
|
|
|
|
|
// Get the {{binding}} bound values
|
2021-11-08 06:49:22 +00:00
|
|
|
let entity: DataTreeEntity | undefined = undefined;
|
|
|
|
|
let propertyPath: string;
|
2021-09-08 17:32:22 +00:00
|
|
|
if (fullPropertyPath) {
|
|
|
|
|
const entityName = fullPropertyPath.split(".")[0];
|
2021-11-08 06:49:22 +00:00
|
|
|
propertyPath = fullPropertyPath.split(".")[1];
|
2021-09-08 17:32:22 +00:00
|
|
|
entity = data[entityName];
|
|
|
|
|
}
|
2021-12-23 14:17:20 +00:00
|
|
|
// Get the {{binding}} bound values
|
2021-09-08 17:32:22 +00:00
|
|
|
const { jsSnippets, stringSegments } = getDynamicBindings(
|
|
|
|
|
dynamicBinding,
|
|
|
|
|
entity,
|
|
|
|
|
);
|
2021-02-22 11:14:08 +00:00
|
|
|
if (stringSegments.length) {
|
|
|
|
|
// Get the Data Tree value of those "binding "paths
|
|
|
|
|
const values = jsSnippets.map((jsSnippet, index) => {
|
2021-11-08 06:49:22 +00:00
|
|
|
const toBeSentForEval =
|
|
|
|
|
entity && isJSAction(entity) && propertyPath === "body"
|
|
|
|
|
? jsSnippet.replace(/export default/g, "")
|
|
|
|
|
: jsSnippet;
|
2021-02-22 11:14:08 +00:00
|
|
|
if (jsSnippet) {
|
|
|
|
|
const result = this.evaluateDynamicBoundValue(
|
2021-11-08 06:49:22 +00:00
|
|
|
toBeSentForEval,
|
2021-06-21 11:09:51 +00:00
|
|
|
data,
|
2021-09-08 17:32:22 +00:00
|
|
|
resolvedFunctions,
|
2022-02-11 10:52:27 +00:00
|
|
|
!!entity && isJSAction(entity),
|
2022-02-04 12:28:46 +00:00
|
|
|
contextData,
|
2021-02-22 11:14:08 +00:00
|
|
|
callBackData,
|
|
|
|
|
);
|
2021-06-21 11:09:51 +00:00
|
|
|
if (fullPropertyPath && result.errors.length) {
|
|
|
|
|
addErrorToEntityProperty(result.errors, data, fullPropertyPath);
|
|
|
|
|
}
|
2021-02-22 11:14:08 +00:00
|
|
|
return result.result;
|
|
|
|
|
} else {
|
|
|
|
|
return stringSegments[index];
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2022-01-28 11:10:05 +00:00
|
|
|
// We don't need to substitute template of the result if only one binding exists
|
2021-10-01 15:35:05 +00:00
|
|
|
// But it should not be of prepared statements since that does need a string
|
|
|
|
|
if (
|
|
|
|
|
stringSegments.length === 1 &&
|
|
|
|
|
evaluationSubstitutionType !== EvaluationSubstitutionType.PARAMETER
|
|
|
|
|
) {
|
|
|
|
|
return values[0];
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
// else return a combined value according to the evaluation type
|
|
|
|
|
return substituteDynamicBindingWithValues(
|
|
|
|
|
dynamicBinding,
|
|
|
|
|
stringSegments,
|
|
|
|
|
values,
|
|
|
|
|
evaluationSubstitutionType,
|
|
|
|
|
);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
if (fullPropertyPath) {
|
|
|
|
|
addErrorToEntityProperty(
|
|
|
|
|
[
|
|
|
|
|
{
|
|
|
|
|
raw: dynamicBinding,
|
|
|
|
|
errorType: PropertyEvaluationErrorType.PARSE,
|
|
|
|
|
errorMessage: e.message,
|
|
|
|
|
severity: Severity.ERROR,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
data,
|
|
|
|
|
fullPropertyPath,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
2021-02-22 11:14:08 +00:00
|
|
|
}
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-23 14:17:20 +00:00
|
|
|
async evaluateTriggers(
|
|
|
|
|
userScript: string,
|
|
|
|
|
dataTree: DataTree,
|
|
|
|
|
requestId: string,
|
|
|
|
|
resolvedFunctions: Record<string, any>,
|
|
|
|
|
callbackData: Array<unknown>,
|
2022-03-02 06:37:20 +00:00
|
|
|
context?: EvaluateContext,
|
2021-12-23 14:17:20 +00:00
|
|
|
) {
|
|
|
|
|
const { jsSnippets } = getDynamicBindings(userScript);
|
|
|
|
|
return evaluateAsync(
|
|
|
|
|
jsSnippets[0] || userScript,
|
|
|
|
|
dataTree,
|
|
|
|
|
requestId,
|
|
|
|
|
resolvedFunctions,
|
2022-03-02 06:37:20 +00:00
|
|
|
context,
|
2021-12-23 14:17:20 +00:00
|
|
|
callbackData,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-22 11:14:08 +00:00
|
|
|
// Paths are expected to have "{name}.{path}" signature
|
|
|
|
|
// Also returns any action triggers found after evaluating value
|
|
|
|
|
evaluateDynamicBoundValue(
|
2021-03-13 14:12:21 +00:00
|
|
|
js: string,
|
2021-06-21 11:09:51 +00:00
|
|
|
data: DataTree,
|
2021-09-08 17:32:22 +00:00
|
|
|
resolvedFunctions: Record<string, any>,
|
2022-02-11 10:52:27 +00:00
|
|
|
createGlobalData: boolean,
|
2022-02-04 12:28:46 +00:00
|
|
|
contextData?: EvaluateContext,
|
2021-02-22 11:14:08 +00:00
|
|
|
callbackData?: Array<any>,
|
|
|
|
|
): EvalResult {
|
|
|
|
|
try {
|
2022-02-04 12:28:46 +00:00
|
|
|
return evaluateSync(
|
|
|
|
|
js,
|
|
|
|
|
data,
|
|
|
|
|
resolvedFunctions,
|
2022-02-11 10:52:27 +00:00
|
|
|
createGlobalData,
|
2022-02-04 12:28:46 +00:00
|
|
|
contextData,
|
|
|
|
|
callbackData,
|
|
|
|
|
);
|
2021-02-22 11:14:08 +00:00
|
|
|
} catch (e) {
|
2021-06-21 11:09:51 +00:00
|
|
|
return {
|
|
|
|
|
result: undefined,
|
|
|
|
|
errors: [
|
|
|
|
|
{
|
|
|
|
|
errorType: PropertyEvaluationErrorType.PARSE,
|
|
|
|
|
raw: js,
|
|
|
|
|
severity: Severity.ERROR,
|
|
|
|
|
errorMessage: e.message,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
};
|
2021-02-22 11:14:08 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-28 11:10:05 +00:00
|
|
|
validateAndParseWidgetProperty({
|
|
|
|
|
currentTree,
|
|
|
|
|
evalPropertyValue,
|
|
|
|
|
fullPropertyPath,
|
|
|
|
|
unEvalPropertyValue,
|
|
|
|
|
widget,
|
|
|
|
|
}: {
|
|
|
|
|
fullPropertyPath: string;
|
|
|
|
|
widget: DataTreeWidget;
|
|
|
|
|
currentTree: DataTree;
|
|
|
|
|
evalPropertyValue: any;
|
|
|
|
|
unEvalPropertyValue: string;
|
|
|
|
|
}): any {
|
2021-04-21 14:34:25 +00:00
|
|
|
const { propertyPath } = getEntityNameAndPropertyPath(fullPropertyPath);
|
2021-02-22 11:14:08 +00:00
|
|
|
if (isPathADynamicTrigger(widget, propertyPath)) {
|
2021-12-23 14:17:20 +00:00
|
|
|
// TODO find a way to validate triggers
|
2022-01-28 11:10:05 +00:00
|
|
|
return unEvalPropertyValue;
|
2021-02-22 11:14:08 +00:00
|
|
|
}
|
2021-04-21 14:34:25 +00:00
|
|
|
const validation = widget.validationPaths[propertyPath];
|
2021-07-26 05:50:46 +00:00
|
|
|
|
2021-09-29 12:03:11 +00:00
|
|
|
const { isValid, messages, parsed, transformed } = validateWidgetProperty(
|
2021-07-26 05:50:46 +00:00
|
|
|
validation,
|
2021-12-23 14:17:20 +00:00
|
|
|
evalPropertyValue,
|
2021-02-22 11:14:08 +00:00
|
|
|
widget,
|
feat: JSON Form widget (#8472)
* initial layout
* updated parser to support nested array
* array field rendering
* changes
* ts fix
* minor revert FormWidget
* modified schema structure
* select and switch fields
* added checkbox field
* added RadioGroupField
* partial DateField and defaults, typing refactoring
* added label and field type change
* minor ts changes
* changes
* modified widget/utils for nested panelConfig, modified schema to object approach
* array/object label support
* hide field configuration when children not present
* added tooltip
* field visibility option
* disabled state
* upgraded tslib, form initial values
* custom field configuration - add/hide/edit
* field configuration - label change
* return input when field configuration reaches max depth
* minor changes
* form - scroll, fixedfooter, enitity defn and other minior changes
* form title
* unregister on unmount
* fixes
* zero state
* fix field padding
* patched updating form values, removed linting warnings
* configured action buttons
* minor fix
* minor change
* property pane - sort fields in field configuration
* refactor include all properties
* checkbox properties
* date properties
* refactor typings and radio group properties
* switch, multselect, select, array, object properties
* minor changes
* default value
* ts fixes
* checkbox field properties implementation
* date field prop implementation
* switch field
* select field and fix deep nested meta properties
* multiselect implementation
* minor change
* input field implementation
* fix position jump on field type change
* initial accordian
* field state property and auto-complete of JSONFormComputeControl
* merge fixes
* renamed FormBuilder to JSONForm
* source data validation minor change
* custom field default value fix
* Editable keys for custom field
* minor fixes
* replaced useFieldArray with custom logic, added widget icon
* array and object accordian with border/background styling
* minor change
* disabled states for array and objects
* default value minor fix
* form level styles
* modified logic for isDisabled for array and object, added disabledWhenInvalid, exposed isValid to fieldState for text input, removed useDisableChildren
* added isValid for all field types
* fixed reset to default values
* debounce form values update
* minor change
* minor change
* fix crash - source data change multi-select to array, fix crash - change of options
* fix positioning
* detect date type in source data
* fix crash - when object is passed to regex input field
* fixed default sourceData path for fields
* accodion keep children mounted on collapse
* jest test for schemaParser
* widget/helper and useRegisterFieldInvalid test
* tests for property config helper and generatePanelPropertyConfig
* fix input field validation not appearing
* fix date field type detection
* rename data -> formData
* handle null/undefined field value change in sourceData
* added null/undefined as valid values for defaultValue text field
* auto detect email field
* set formData default value on initial load
* switch field inline positioning
* field margin fix for row direction
* select full width
* fiex date field default value - out of range
* fix any field type to array
* array default value logic change
* base cypress test changes
* initial json form render cy test
* key sanitization
* fix fieldState update logic
* required design, object/array background color, accordion changes, fix - add new custom field
* minor change
* cypress tests
* fix date formatted value, field state cypress test
* cypress - field properties test and fixes
* rename test file
* fix accessort change to blank value, cypress tests
* fix array field default value for modified accessor
* minor fix
* added animate loading
* fix empty state, add new custom field
* test data fix
* fix warnings
* fix timePrecision visibility
* button styling
* ported input v2
* fix jest tests
* fix cypress tests
* perf changes
* perf improvement
* added comments
* multiselect changes
* input field perf refactor
* array field, object field refactor performance
* checkbox field refactor
* refectored date, radio, select and switch
* fixes
* test fixes
* fixes
* minor fix
* rename field renderer
* remove tracked fieldRenderer field
* cypress test fixes
* cypress changes
* array default value fixes
* arrayfield passedDefaultValue
* auto enabled JS mode for few properties, reverted swith and date property controls
* cypress changes
* added widget sniping mode and fixed object passedDefaultValue
* multiselect v2
* select v2
* fix jest tests
* test fixes
* field limit
* rename field type dropdown texts
* field type changes fixes
* jest fixes
* loading state submit button
* default source data for new widget
* modify limit message
* multiseelct default value changes and cypress fix
* select default value
* keep default value intact on field type change
* TextTable cypress text fix
* review changes
* fixed footer changes
* collapse styles section by default
* fixed footer changes
* form modes
* custom field key rentention
* fixed footer fix in view mode
* non ascii characters
* fix meta merge in dataTreeWidget
* minor fixes
* rename useRegisterFieldInvalid.ts -> useRegisterFieldValidity.ts
* modified dependency injection into evaluated values
* refactored fixedfooter logic
* minor change
* accessor update
* minor change
* fixes
* QA fixes date field, scroll content
* fix phone number field, removed visiblity option from array item
* fix sourceData autocomplete
* reset logic
* fix multiselect reset
* form values hydration on widget drag
* code review changes
* reverted order of merge dataTreeWidget
* fixes
* added button titles, fixed hydration issue
* default value fixes
* upgraded react hook form, modified array-level/field-level default value logic
* fixed select validation
* added icon entity explorer, modified icon align control
* modify accessor validation for mongo db _id
* update email field regex
* review changes
* explicitly handle empty source data validation
2022-03-24 07:13:25 +00:00
|
|
|
propertyPath,
|
2021-02-22 11:14:08 +00:00
|
|
|
);
|
2021-07-26 05:50:46 +00:00
|
|
|
|
2021-02-22 11:14:08 +00:00
|
|
|
const evaluatedValue = isValid
|
|
|
|
|
? parsed
|
|
|
|
|
: _.isUndefined(transformed)
|
|
|
|
|
? evalPropertyValue
|
|
|
|
|
: transformed;
|
|
|
|
|
const safeEvaluatedValue = removeFunctions(evaluatedValue);
|
2021-06-21 11:09:51 +00:00
|
|
|
_.set(
|
|
|
|
|
widget,
|
2022-03-30 13:28:19 +00:00
|
|
|
getEvalValuePath(fullPropertyPath, {
|
|
|
|
|
isPopulated: false,
|
|
|
|
|
fullPath: false,
|
|
|
|
|
}),
|
2021-06-21 11:09:51 +00:00
|
|
|
safeEvaluatedValue,
|
|
|
|
|
);
|
2021-06-04 07:09:36 +00:00
|
|
|
if (!isValid) {
|
2021-09-29 12:03:11 +00:00
|
|
|
const evalErrors: EvaluationError[] =
|
|
|
|
|
messages?.map((message) => {
|
|
|
|
|
return {
|
2021-06-21 11:09:51 +00:00
|
|
|
raw: unEvalPropertyValue,
|
|
|
|
|
errorMessage: message || "",
|
|
|
|
|
errorType: PropertyEvaluationErrorType.VALIDATION,
|
|
|
|
|
severity: Severity.ERROR,
|
2021-09-29 12:03:11 +00:00
|
|
|
};
|
|
|
|
|
}) ?? [];
|
|
|
|
|
addErrorToEntityProperty(evalErrors, currentTree, fullPropertyPath);
|
2021-02-22 11:14:08 +00:00
|
|
|
}
|
|
|
|
|
|
2022-01-28 11:10:05 +00:00
|
|
|
return parsed;
|
2021-02-22 11:14:08 +00:00
|
|
|
}
|
2022-01-28 11:10:05 +00:00
|
|
|
|
2022-01-27 09:50:05 +00:00
|
|
|
// validates the user input saved as action property based on a validationConfig
|
|
|
|
|
validateActionProperty(
|
|
|
|
|
fullPropertyPath: string,
|
|
|
|
|
action: DataTreeAction,
|
|
|
|
|
currentTree: DataTree,
|
|
|
|
|
evalPropertyValue: any,
|
|
|
|
|
unEvalPropertyValue: string,
|
|
|
|
|
validationConfig: ValidationConfig,
|
|
|
|
|
) {
|
|
|
|
|
if (evalPropertyValue && validationConfig) {
|
|
|
|
|
// runs VALIDATOR function and returns errors
|
|
|
|
|
const { isValid, messages } = validateActionProperty(
|
|
|
|
|
validationConfig,
|
|
|
|
|
evalPropertyValue,
|
|
|
|
|
);
|
|
|
|
|
if (!isValid) {
|
|
|
|
|
const evalErrors: EvaluationError[] =
|
|
|
|
|
messages?.map((message) => {
|
|
|
|
|
return {
|
|
|
|
|
raw: unEvalPropertyValue,
|
|
|
|
|
errorMessage: message || "",
|
|
|
|
|
errorType: PropertyEvaluationErrorType.VALIDATION,
|
|
|
|
|
severity: Severity.ERROR,
|
|
|
|
|
};
|
|
|
|
|
}) ?? [];
|
|
|
|
|
// saves error in dataTree at fullPropertyPath
|
|
|
|
|
// Later errors can consumed by the forms and debugger
|
|
|
|
|
addErrorToEntityProperty(evalErrors, currentTree, fullPropertyPath);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-02-22 11:14:08 +00:00
|
|
|
|
2021-11-08 06:49:22 +00:00
|
|
|
saveResolvedFunctionsAndJSUpdates(
|
|
|
|
|
entity: DataTreeJSAction,
|
|
|
|
|
jsUpdates: Record<string, any>,
|
2021-09-08 17:32:22 +00:00
|
|
|
unEvalDataTree: DataTree,
|
2021-11-08 06:49:22 +00:00
|
|
|
entityName: string,
|
2021-09-08 17:32:22 +00:00
|
|
|
) {
|
2021-11-08 06:49:22 +00:00
|
|
|
const regex = new RegExp(/^export default[\s]*?({[\s\S]*?})/);
|
|
|
|
|
const correctFormat = regex.test(entity.body);
|
|
|
|
|
if (correctFormat) {
|
|
|
|
|
const body = entity.body.replace(/export default/g, "");
|
|
|
|
|
try {
|
2022-02-11 10:52:27 +00:00
|
|
|
const { result } = evaluateSync(body, unEvalDataTree, {}, true);
|
2021-11-08 06:49:22 +00:00
|
|
|
delete this.resolvedFunctions[`${entityName}`];
|
|
|
|
|
delete this.currentJSCollectionState[`${entityName}`];
|
|
|
|
|
if (result) {
|
|
|
|
|
const actions: any = [];
|
|
|
|
|
const variables: any = [];
|
|
|
|
|
Object.keys(result).forEach((unEvalFunc) => {
|
|
|
|
|
const unEvalValue = result[unEvalFunc];
|
|
|
|
|
if (typeof unEvalValue === "function") {
|
|
|
|
|
const params = getParams(unEvalValue);
|
2021-12-23 14:17:20 +00:00
|
|
|
const functionString = unEvalValue.toString();
|
2021-09-08 17:32:22 +00:00
|
|
|
_.set(
|
|
|
|
|
this.resolvedFunctions,
|
|
|
|
|
`${entityName}.${unEvalFunc}`,
|
2021-11-08 06:49:22 +00:00
|
|
|
unEvalValue,
|
|
|
|
|
);
|
|
|
|
|
_.set(
|
|
|
|
|
this.currentJSCollectionState,
|
|
|
|
|
`${entityName}.${unEvalFunc}`,
|
2021-12-23 14:17:20 +00:00
|
|
|
functionString,
|
2021-11-08 06:49:22 +00:00
|
|
|
);
|
2022-01-25 13:53:53 +00:00
|
|
|
actions.push({
|
|
|
|
|
name: unEvalFunc,
|
|
|
|
|
body: functionString,
|
|
|
|
|
arguments: params,
|
|
|
|
|
value: unEvalValue,
|
|
|
|
|
});
|
2021-11-08 06:49:22 +00:00
|
|
|
} else {
|
|
|
|
|
variables.push({
|
|
|
|
|
name: unEvalFunc,
|
|
|
|
|
value: result[unEvalFunc],
|
|
|
|
|
});
|
|
|
|
|
_.set(
|
|
|
|
|
this.currentJSCollectionState,
|
|
|
|
|
`${entityName}.${unEvalFunc}`,
|
|
|
|
|
unEvalValue,
|
2021-09-08 17:32:22 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
2022-01-25 13:53:53 +00:00
|
|
|
|
|
|
|
|
const modifiedActions = actions.map((action: any) => {
|
|
|
|
|
return {
|
|
|
|
|
name: action.name,
|
|
|
|
|
body: action.body,
|
|
|
|
|
arguments: action.arguments,
|
|
|
|
|
isAsync: isFunctionAsync(
|
|
|
|
|
action.value,
|
|
|
|
|
unEvalDataTree,
|
|
|
|
|
this.resolvedFunctions,
|
|
|
|
|
),
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const parsedBody = {
|
|
|
|
|
body: entity.body,
|
|
|
|
|
actions: modifiedActions,
|
|
|
|
|
variables,
|
|
|
|
|
};
|
2021-11-08 06:49:22 +00:00
|
|
|
_.set(jsUpdates, `${entityName}`, {
|
|
|
|
|
parsedBody,
|
|
|
|
|
id: entity.actionId,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
_.set(jsUpdates, `${entityName}`, {
|
|
|
|
|
parsedBody: undefined,
|
|
|
|
|
id: entity.actionId,
|
|
|
|
|
});
|
2021-09-08 17:32:22 +00:00
|
|
|
}
|
2021-11-08 06:49:22 +00:00
|
|
|
} catch (e) {
|
|
|
|
|
const errors = {
|
|
|
|
|
type: EvalErrorTypes.PARSE_JS_ERROR,
|
|
|
|
|
context: {
|
|
|
|
|
entity: entity,
|
|
|
|
|
propertyPath: entity.name + ".body",
|
|
|
|
|
},
|
|
|
|
|
message: e.message,
|
|
|
|
|
};
|
|
|
|
|
this.errors.push(errors);
|
2021-09-08 17:32:22 +00:00
|
|
|
}
|
2021-11-08 06:49:22 +00:00
|
|
|
} else {
|
|
|
|
|
const errors = {
|
|
|
|
|
type: EvalErrorTypes.PARSE_JS_ERROR,
|
|
|
|
|
context: {
|
|
|
|
|
entity: entity,
|
|
|
|
|
propertyPath: entity.name + ".body",
|
|
|
|
|
},
|
|
|
|
|
message: "Start object with export default",
|
|
|
|
|
};
|
|
|
|
|
this.errors.push(errors);
|
|
|
|
|
}
|
|
|
|
|
return jsUpdates;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
parseJSActions(
|
|
|
|
|
unEvalDataTree: DataTree,
|
|
|
|
|
differences?: DataTreeDiff[],
|
|
|
|
|
oldUnEvalTree?: DataTree,
|
|
|
|
|
) {
|
|
|
|
|
let jsUpdates = {};
|
|
|
|
|
if (!!differences && !!oldUnEvalTree) {
|
|
|
|
|
differences.forEach((diff) => {
|
|
|
|
|
const { entityName, propertyPath } = getEntityNameAndPropertyPath(
|
|
|
|
|
diff.payload.propertyPath,
|
|
|
|
|
);
|
|
|
|
|
const entity = unEvalDataTree[entityName];
|
|
|
|
|
if (diff.event === DataTreeDiffEvent.DELETE) {
|
|
|
|
|
const deletedEntity = oldUnEvalTree[entityName];
|
|
|
|
|
if (!isJSAction(deletedEntity)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (
|
|
|
|
|
this.currentJSCollectionState &&
|
|
|
|
|
this.currentJSCollectionState[diff.payload.propertyPath]
|
|
|
|
|
) {
|
|
|
|
|
delete this.currentJSCollectionState[diff.payload.propertyPath];
|
|
|
|
|
}
|
|
|
|
|
if (
|
|
|
|
|
this.resolvedFunctions &&
|
|
|
|
|
this.resolvedFunctions[diff.payload.propertyPath]
|
|
|
|
|
) {
|
|
|
|
|
delete this.resolvedFunctions[diff.payload.propertyPath];
|
2021-09-08 17:32:22 +00:00
|
|
|
}
|
|
|
|
|
}
|
2021-11-08 06:49:22 +00:00
|
|
|
if (!isJSAction(entity)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (
|
|
|
|
|
(diff.event === DataTreeDiffEvent.EDIT && propertyPath === "body") ||
|
|
|
|
|
(diff.event === DataTreeDiffEvent.NEW && propertyPath === "")
|
|
|
|
|
) {
|
|
|
|
|
jsUpdates = this.saveResolvedFunctionsAndJSUpdates(
|
|
|
|
|
entity,
|
|
|
|
|
jsUpdates,
|
|
|
|
|
unEvalDataTree,
|
|
|
|
|
entityName,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
Object.keys(unEvalDataTree).forEach((entityName) => {
|
|
|
|
|
const entity = unEvalDataTree[entityName];
|
|
|
|
|
if (!isJSAction(entity)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
jsUpdates = this.saveResolvedFunctionsAndJSUpdates(
|
|
|
|
|
entity,
|
|
|
|
|
jsUpdates,
|
|
|
|
|
unEvalDataTree,
|
|
|
|
|
entityName,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return { jsUpdates };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getJSEntities(dataTree: DataTree) {
|
|
|
|
|
const jsCollections: Record<string, DataTreeJSAction> = {};
|
|
|
|
|
Object.keys(dataTree).forEach((key: string) => {
|
|
|
|
|
const entity = dataTree[key];
|
|
|
|
|
if (isJSAction(entity)) {
|
|
|
|
|
jsCollections[entity.name] = entity;
|
2021-09-08 17:32:22 +00:00
|
|
|
}
|
|
|
|
|
});
|
2021-11-08 06:49:22 +00:00
|
|
|
return jsCollections;
|
2021-09-08 17:32:22 +00:00
|
|
|
}
|
|
|
|
|
|
2021-02-22 11:14:08 +00:00
|
|
|
updateDependencyMap(
|
2021-08-04 05:34:44 +00:00
|
|
|
translatedDiffs: Array<DataTreeDiff>,
|
2021-02-22 11:14:08 +00:00
|
|
|
unEvalDataTree: DataTree,
|
|
|
|
|
): {
|
|
|
|
|
dependenciesOfRemovedPaths: Array<string>;
|
|
|
|
|
removedPaths: Array<string>;
|
|
|
|
|
} {
|
|
|
|
|
const diffCalcStart = performance.now();
|
|
|
|
|
let didUpdateDependencyMap = false;
|
|
|
|
|
const dependenciesOfRemovedPaths: Array<string> = [];
|
|
|
|
|
const removedPaths: Array<string> = [];
|
|
|
|
|
|
|
|
|
|
// This is needed for NEW and DELETE events below.
|
|
|
|
|
// 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
|
|
|
|
|
this.allKeys = getAllPaths(unEvalDataTree);
|
|
|
|
|
// Transform the diff library events to Appsmith evaluator events
|
2021-08-04 05:34:44 +00:00
|
|
|
translatedDiffs.forEach((dataTreeDiff) => {
|
2021-06-23 07:29:36 +00:00
|
|
|
const entityName = dataTreeDiff.payload.propertyPath.split(".")[0];
|
|
|
|
|
let entity = unEvalDataTree[entityName];
|
|
|
|
|
if (dataTreeDiff.event === DataTreeDiffEvent.DELETE) {
|
|
|
|
|
entity = this.oldUnEvalTree[entityName];
|
|
|
|
|
}
|
|
|
|
|
const entityType = isValidEntity(entity) ? entity.ENTITY_TYPE : "noop";
|
|
|
|
|
|
|
|
|
|
if (entityType !== "noop") {
|
|
|
|
|
switch (dataTreeDiff.event) {
|
|
|
|
|
case DataTreeDiffEvent.NEW: {
|
|
|
|
|
// If a new entity/property was added, add all the internal bindings for this entity to the global dependency map
|
|
|
|
|
if (
|
2021-09-08 17:32:22 +00:00
|
|
|
(isWidget(entity) || isAction(entity) || isJSAction(entity)) &&
|
2021-07-20 05:04:12 +00:00
|
|
|
!isDynamicLeaf(unEvalDataTree, dataTreeDiff.payload.propertyPath)
|
2021-06-23 07:29:36 +00:00
|
|
|
) {
|
|
|
|
|
const entityDependencyMap: DependencyMap = this.listEntityDependencies(
|
|
|
|
|
entity,
|
|
|
|
|
entityName,
|
2021-02-22 11:14:08 +00:00
|
|
|
);
|
2021-06-23 07:29:36 +00:00
|
|
|
if (Object.keys(entityDependencyMap).length) {
|
2021-02-22 11:14:08 +00:00
|
|
|
didUpdateDependencyMap = true;
|
2021-06-23 07:29:36 +00:00
|
|
|
// The entity might already have some dependencies,
|
|
|
|
|
// so we just want to update those
|
|
|
|
|
Object.entries(entityDependencyMap).forEach(
|
|
|
|
|
([entityDependent, entityDependencies]) => {
|
|
|
|
|
if (this.dependencyMap[entityDependent]) {
|
|
|
|
|
this.dependencyMap[entityDependent] = this.dependencyMap[
|
|
|
|
|
entityDependent
|
|
|
|
|
].concat(entityDependencies);
|
|
|
|
|
} else {
|
|
|
|
|
this.dependencyMap[entityDependent] = entityDependencies;
|
|
|
|
|
}
|
|
|
|
|
},
|
2021-02-22 11:14:08 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-06-23 07:29:36 +00:00
|
|
|
// Either a new entity or a new property path has been added. Go through existing dynamic bindings and
|
|
|
|
|
// find out if a new dependency has to be created because the property path used in the binding just became
|
|
|
|
|
// eligible
|
|
|
|
|
const possibleReferencesInOldBindings: DependencyMap = this.getPropertyPathReferencesInExistingBindings(
|
|
|
|
|
unEvalDataTree,
|
|
|
|
|
dataTreeDiff.payload.propertyPath,
|
|
|
|
|
);
|
|
|
|
|
// We have found some bindings which are related to the new property path and hence should be added to the
|
|
|
|
|
// global dependency map
|
|
|
|
|
if (Object.keys(possibleReferencesInOldBindings).length) {
|
|
|
|
|
didUpdateDependencyMap = true;
|
|
|
|
|
Object.assign(
|
|
|
|
|
this.dependencyMap,
|
|
|
|
|
possibleReferencesInOldBindings,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case DataTreeDiffEvent.DELETE: {
|
|
|
|
|
// Add to removedPaths as they have been deleted from the evalTree
|
|
|
|
|
removedPaths.push(dataTreeDiff.payload.propertyPath);
|
|
|
|
|
// If an existing widget was deleted, remove all the bindings from the global dependency map
|
|
|
|
|
if (
|
2021-09-08 17:32:22 +00:00
|
|
|
(isWidget(entity) || isAction(entity) || isJSAction(entity)) &&
|
2021-06-23 07:29:36 +00:00
|
|
|
dataTreeDiff.payload.propertyPath === entityName
|
|
|
|
|
) {
|
|
|
|
|
const entityDependencies = this.listEntityDependencies(
|
|
|
|
|
entity,
|
|
|
|
|
entityName,
|
|
|
|
|
);
|
|
|
|
|
Object.keys(entityDependencies).forEach((widgetDep) => {
|
|
|
|
|
didUpdateDependencyMap = true;
|
|
|
|
|
delete this.dependencyMap[widgetDep];
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
// Either an existing entity or an existing property path has been deleted. Update the global dependency map
|
|
|
|
|
// by removing the bindings from the same.
|
|
|
|
|
Object.keys(this.dependencyMap).forEach((dependencyPath) => {
|
|
|
|
|
didUpdateDependencyMap = true;
|
2021-02-22 11:14:08 +00:00
|
|
|
if (
|
2021-06-23 07:29:36 +00:00
|
|
|
isChildPropertyPath(
|
|
|
|
|
dataTreeDiff.payload.propertyPath,
|
|
|
|
|
dependencyPath,
|
|
|
|
|
)
|
2021-02-22 11:14:08 +00:00
|
|
|
) {
|
2021-06-23 07:29:36 +00:00
|
|
|
delete this.dependencyMap[dependencyPath];
|
|
|
|
|
} else {
|
|
|
|
|
const toRemove: Array<string> = [];
|
|
|
|
|
this.dependencyMap[dependencyPath].forEach((dependantPath) => {
|
|
|
|
|
if (
|
|
|
|
|
isChildPropertyPath(
|
|
|
|
|
dataTreeDiff.payload.propertyPath,
|
|
|
|
|
dependantPath,
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
dependenciesOfRemovedPaths.push(dependencyPath);
|
|
|
|
|
toRemove.push(dependantPath);
|
|
|
|
|
}
|
2021-02-22 11:14:08 +00:00
|
|
|
});
|
2021-06-23 07:29:36 +00:00
|
|
|
this.dependencyMap[dependencyPath] = _.difference(
|
|
|
|
|
this.dependencyMap[dependencyPath],
|
|
|
|
|
toRemove,
|
|
|
|
|
);
|
2021-02-22 11:14:08 +00:00
|
|
|
}
|
2021-06-23 07:29:36 +00:00
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case DataTreeDiffEvent.EDIT: {
|
|
|
|
|
// We only care if the difference is in dynamic bindings since static values do not need
|
|
|
|
|
// an evaluation.
|
|
|
|
|
if (
|
2021-09-08 17:32:22 +00:00
|
|
|
(isWidget(entity) || isAction(entity) || isJSAction(entity)) &&
|
2021-06-23 07:29:36 +00:00
|
|
|
typeof dataTreeDiff.payload.value === "string"
|
|
|
|
|
) {
|
2021-09-08 17:32:22 +00:00
|
|
|
const entity:
|
|
|
|
|
| DataTreeAction
|
|
|
|
|
| DataTreeWidget
|
|
|
|
|
| DataTreeJSAction = unEvalDataTree[entityName] as
|
|
|
|
|
| DataTreeAction
|
|
|
|
|
| DataTreeWidget
|
|
|
|
|
| DataTreeJSAction;
|
2021-06-23 07:29:36 +00:00
|
|
|
const fullPropertyPath = dataTreeDiff.payload.propertyPath;
|
|
|
|
|
const entityPropertyPath = fullPropertyPath.substring(
|
|
|
|
|
fullPropertyPath.indexOf(".") + 1,
|
|
|
|
|
);
|
|
|
|
|
const isABindingPath = isPathADynamicBinding(
|
|
|
|
|
entity,
|
|
|
|
|
entityPropertyPath,
|
|
|
|
|
);
|
2022-04-07 11:01:31 +00:00
|
|
|
if (isABindingPath) {
|
2021-02-22 11:14:08 +00:00
|
|
|
didUpdateDependencyMap = true;
|
|
|
|
|
|
2021-06-23 07:29:36 +00:00
|
|
|
const { jsSnippets } = getDynamicBindings(
|
|
|
|
|
dataTreeDiff.payload.value,
|
2021-09-08 17:32:22 +00:00
|
|
|
entity,
|
2021-04-26 05:41:32 +00:00
|
|
|
);
|
2021-06-23 07:29:36 +00:00
|
|
|
const correctSnippets = jsSnippets.filter(
|
|
|
|
|
(jsSnippet) => !!jsSnippet,
|
2021-02-22 11:14:08 +00:00
|
|
|
);
|
2021-06-23 07:29:36 +00:00
|
|
|
// We found a new dynamic binding for this property path. We update the dependency map by overwriting the
|
|
|
|
|
// dependencies for this property path with the newly found dependencies
|
|
|
|
|
if (correctSnippets.length) {
|
|
|
|
|
this.dependencyMap[fullPropertyPath] = correctSnippets;
|
|
|
|
|
} else {
|
|
|
|
|
// The dependency on this property path has been removed. Delete this property path from the global
|
|
|
|
|
// dependency map
|
|
|
|
|
delete this.dependencyMap[fullPropertyPath];
|
|
|
|
|
}
|
2021-11-08 06:49:22 +00:00
|
|
|
if (isAction(entity) || isJSAction(entity)) {
|
2021-06-23 07:29:36 +00:00
|
|
|
// Actions have a defined dependency map that should always be maintained
|
|
|
|
|
if (entityPropertyPath in entity.dependencyMap) {
|
|
|
|
|
const entityDependenciesName = entity.dependencyMap[
|
|
|
|
|
entityPropertyPath
|
|
|
|
|
].map((dep) => `${entityName}.${dep}`);
|
|
|
|
|
|
|
|
|
|
// Filter only the paths which exist in the appsmith world to avoid cyclical dependencies
|
|
|
|
|
const filteredEntityDependencies = entityDependenciesName.filter(
|
|
|
|
|
(path) => this.allKeys.hasOwnProperty(path),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Now assign these existing dependent paths to the property path in dependencyMap
|
|
|
|
|
if (fullPropertyPath in this.dependencyMap) {
|
|
|
|
|
this.dependencyMap[fullPropertyPath] = this.dependencyMap[
|
|
|
|
|
fullPropertyPath
|
|
|
|
|
].concat(filteredEntityDependencies);
|
|
|
|
|
} else {
|
|
|
|
|
this.dependencyMap[
|
|
|
|
|
fullPropertyPath
|
|
|
|
|
] = filteredEntityDependencies;
|
2021-04-26 05:41:32 +00:00
|
|
|
}
|
2021-02-22 11:14:08 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-07-01 13:18:46 +00:00
|
|
|
// If the whole binding was removed then the value
|
|
|
|
|
// at this path would be "".
|
|
|
|
|
// In this case if the path exists in the dependency map
|
|
|
|
|
// remove it.
|
|
|
|
|
else if (fullPropertyPath in this.dependencyMap) {
|
|
|
|
|
delete this.dependencyMap[fullPropertyPath];
|
|
|
|
|
}
|
2021-02-22 11:14:08 +00:00
|
|
|
}
|
2021-06-23 07:29:36 +00:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default: {
|
|
|
|
|
break;
|
2021-02-22 11:14:08 +00:00
|
|
|
}
|
|
|
|
|
}
|
2021-06-23 07:29:36 +00:00
|
|
|
}
|
|
|
|
|
});
|
2021-02-22 11:14:08 +00:00
|
|
|
const diffCalcEnd = performance.now();
|
|
|
|
|
const subDepCalcStart = performance.now();
|
|
|
|
|
if (didUpdateDependencyMap) {
|
|
|
|
|
// TODO Optimise
|
|
|
|
|
Object.keys(this.dependencyMap).forEach((key) => {
|
2021-04-26 05:41:32 +00:00
|
|
|
this.dependencyMap[key] = _.uniq(
|
|
|
|
|
_.flatten(
|
2021-12-02 10:03:43 +00:00
|
|
|
this.dependencyMap[key].map((path) => {
|
|
|
|
|
try {
|
|
|
|
|
return extractReferencesFromBinding(path, this.allKeys);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
this.errors.push({
|
|
|
|
|
type: EvalErrorTypes.EXTRACT_DEPENDENCY_ERROR,
|
|
|
|
|
message: e.message,
|
|
|
|
|
context: {
|
|
|
|
|
script: path,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
}),
|
2021-02-22 11:14:08 +00:00
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
});
|
2022-03-03 10:59:52 +00:00
|
|
|
this.dependencyMap = makeParentsDependOnChildren(
|
|
|
|
|
this.dependencyMap,
|
|
|
|
|
this.allKeys,
|
|
|
|
|
);
|
2021-02-22 11:14:08 +00:00
|
|
|
}
|
|
|
|
|
const subDepCalcEnd = performance.now();
|
|
|
|
|
const updateChangedDependenciesStart = performance.now();
|
|
|
|
|
// If the global dependency map has changed, re-calculate the sort order for all entities and the
|
|
|
|
|
// global inverse dependency map
|
|
|
|
|
if (didUpdateDependencyMap) {
|
|
|
|
|
// This is being called purely to test for new circular dependencies that might have been added
|
2021-07-20 10:50:22 +00:00
|
|
|
this.sortedDependencies = this.sortDependencies(
|
|
|
|
|
this.dependencyMap,
|
|
|
|
|
translatedDiffs,
|
|
|
|
|
);
|
2021-02-22 11:14:08 +00:00
|
|
|
this.inverseDependencyMap = this.getInverseDependencyTree();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const updateChangedDependenciesStop = performance.now();
|
|
|
|
|
this.logs.push({
|
|
|
|
|
diffCalcDeps: (diffCalcEnd - diffCalcStart).toFixed(2),
|
|
|
|
|
subDepCalc: (subDepCalcEnd - subDepCalcStart).toFixed(2),
|
|
|
|
|
updateChangedDependencies: (
|
|
|
|
|
updateChangedDependenciesStop - updateChangedDependenciesStart
|
|
|
|
|
).toFixed(2),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return { dependenciesOfRemovedPaths, removedPaths };
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-03 07:42:23 +00:00
|
|
|
applyDifferencesToEvalTree(differences: Diff<any, any>[]) {
|
|
|
|
|
for (const d of differences) {
|
|
|
|
|
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
|
|
|
|
|
applyChange(this.evalTree, undefined, d);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-22 11:14:08 +00:00
|
|
|
calculateSubTreeSortOrder(
|
|
|
|
|
differences: Diff<any, any>[],
|
|
|
|
|
dependenciesOfRemovedPaths: Array<string>,
|
|
|
|
|
removedPaths: Array<string>,
|
|
|
|
|
unEvalTree: DataTree,
|
|
|
|
|
) {
|
|
|
|
|
const changePaths: Set<string> = new Set(dependenciesOfRemovedPaths);
|
|
|
|
|
for (const d of differences) {
|
|
|
|
|
if (!Array.isArray(d.path) || d.path.length === 0) continue; // Null check for typescript
|
|
|
|
|
changePaths.add(convertPathToString(d.path));
|
|
|
|
|
// If this is a property path change, simply add for evaluation and move on
|
2021-07-20 05:04:12 +00:00
|
|
|
if (!isDynamicLeaf(unEvalTree, convertPathToString(d.path))) {
|
2021-02-22 11:14:08 +00:00
|
|
|
// A parent level property has been added or deleted
|
|
|
|
|
/**
|
|
|
|
|
* We want to add all pre-existing dynamic and static bindings in dynamic paths of this entity to get evaluated and validated.
|
|
|
|
|
* Example:
|
|
|
|
|
* - Table1.tableData = {{Api1.data}}
|
|
|
|
|
* - 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.
|
|
|
|
|
*/
|
|
|
|
|
const entityName = d.path[0];
|
|
|
|
|
const entity = unEvalTree[entityName];
|
|
|
|
|
if (!entity) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (!isAction(entity) && !isWidget(entity)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2022-04-01 13:16:59 +00:00
|
|
|
let entityDynamicBindingPaths: string[] = [];
|
|
|
|
|
if (isAction(entity)) {
|
|
|
|
|
const entityDynamicBindingPathList = getEntityDynamicBindingPathList(
|
|
|
|
|
entity,
|
|
|
|
|
);
|
|
|
|
|
entityDynamicBindingPaths = entityDynamicBindingPathList.map(
|
|
|
|
|
(path) => {
|
|
|
|
|
return path.key;
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
2021-02-22 11:14:08 +00:00
|
|
|
const parentPropertyPath = convertPathToString(d.path);
|
|
|
|
|
Object.keys(entity.bindingPaths).forEach((relativePath) => {
|
|
|
|
|
const childPropertyPath = `${entityName}.${relativePath}`;
|
2022-04-01 13:16:59 +00:00
|
|
|
// Check if relative path has dynamic binding
|
|
|
|
|
if (
|
|
|
|
|
entityDynamicBindingPaths &&
|
|
|
|
|
entityDynamicBindingPaths.length &&
|
|
|
|
|
entityDynamicBindingPaths.includes(relativePath)
|
|
|
|
|
) {
|
|
|
|
|
changePaths.add(childPropertyPath);
|
|
|
|
|
}
|
2021-02-22 11:14:08 +00:00
|
|
|
if (isChildPropertyPath(parentPropertyPath, childPropertyPath)) {
|
|
|
|
|
changePaths.add(childPropertyPath);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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:
|
|
|
|
|
// < "Input1.defaultText" : ["Table1.selectedRow.email"] >
|
|
|
|
|
// If Table1.selectedRow has changed, then Input1.defaultText must also be evaluated because Table1.selectedRow.email
|
|
|
|
|
// is a nested property of Table1.selectedRow
|
|
|
|
|
const changePathsWithNestedDependants = addDependantsOfNestedPropertyPaths(
|
|
|
|
|
Array.from(changePaths),
|
|
|
|
|
this.inverseDependencyMap,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const trimmedChangedPaths = trimDependantChangePaths(
|
|
|
|
|
changePathsWithNestedDependants,
|
|
|
|
|
this.dependencyMap,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Now that we have all the root nodes which have to be evaluated, recursively find all the other paths which
|
|
|
|
|
// would get impacted because they are dependent on the said root nodes and add them in order
|
|
|
|
|
const completeSortOrder = this.getCompleteSortOrder(
|
|
|
|
|
trimmedChangedPaths,
|
|
|
|
|
this.inverseDependencyMap,
|
|
|
|
|
);
|
2022-01-28 11:10:05 +00:00
|
|
|
// Remove any paths that do not exist in the data tree anymore
|
2021-02-22 11:14:08 +00:00
|
|
|
return _.difference(completeSortOrder, removedPaths);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getInverseDependencyTree(): DependencyMap {
|
|
|
|
|
const inverseDag: DependencyMap = {};
|
|
|
|
|
this.sortedDependencies.forEach((propertyPath) => {
|
|
|
|
|
const incomingEdges: Array<string> = this.dependencyMap[propertyPath];
|
|
|
|
|
if (incomingEdges) {
|
|
|
|
|
incomingEdges.forEach((edge) => {
|
|
|
|
|
const node = inverseDag[edge];
|
|
|
|
|
if (node) {
|
|
|
|
|
node.push(propertyPath);
|
|
|
|
|
} else {
|
|
|
|
|
inverseDag[edge] = [propertyPath];
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return inverseDag;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: create the lookup dictionary once
|
|
|
|
|
// Response from listEntityDependencies only needs to change if the entity itself changed.
|
|
|
|
|
// Check if it is possible to make a flat structure with O(1) or at least O(m) lookup instead of O(n*m)
|
|
|
|
|
getPropertyPathReferencesInExistingBindings(
|
|
|
|
|
dataTree: DataTree,
|
|
|
|
|
propertyPath: string,
|
|
|
|
|
) {
|
|
|
|
|
const possibleRefs: DependencyMap = {};
|
|
|
|
|
Object.keys(dataTree).forEach((entityName) => {
|
|
|
|
|
const entity = dataTree[entityName];
|
|
|
|
|
if (
|
|
|
|
|
isValidEntity(entity) &&
|
|
|
|
|
(entity.ENTITY_TYPE === ENTITY_TYPE.ACTION ||
|
2021-09-08 17:32:22 +00:00
|
|
|
entity.ENTITY_TYPE === ENTITY_TYPE.JSACTION ||
|
2021-02-22 11:14:08 +00:00
|
|
|
entity.ENTITY_TYPE === ENTITY_TYPE.WIDGET)
|
|
|
|
|
) {
|
|
|
|
|
const entityPropertyBindings = this.listEntityDependencies(
|
|
|
|
|
entity,
|
|
|
|
|
entityName,
|
|
|
|
|
);
|
|
|
|
|
Object.keys(entityPropertyBindings).forEach((path) => {
|
|
|
|
|
const propertyBindings = entityPropertyBindings[path];
|
|
|
|
|
const references = _.flatten(
|
2021-12-02 10:03:43 +00:00
|
|
|
propertyBindings.map((binding) => {
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
return extractReferencesFromBinding(binding, this.allKeys);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
this.errors.push({
|
|
|
|
|
type: EvalErrorTypes.EXTRACT_DEPENDENCY_ERROR,
|
|
|
|
|
message: e.message,
|
|
|
|
|
context: {
|
|
|
|
|
script: binding,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}),
|
2021-02-22 11:14:08 +00:00
|
|
|
);
|
|
|
|
|
references.forEach((value) => {
|
|
|
|
|
if (isChildPropertyPath(propertyPath, value)) {
|
|
|
|
|
possibleRefs[path] = propertyBindings;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return possibleRefs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
evaluateActionBindings(
|
|
|
|
|
bindings: string[],
|
|
|
|
|
executionParams?: Record<string, unknown> | string,
|
|
|
|
|
) {
|
|
|
|
|
// We might get execution params as an object or as a string.
|
|
|
|
|
// If the user has added a proper object (valid case) it will be an object
|
|
|
|
|
// If they have not added any execution params or not an object
|
|
|
|
|
// it would be a string (invalid case)
|
|
|
|
|
let evaluatedExecutionParams: Record<string, any> = {};
|
|
|
|
|
if (executionParams && _.isObject(executionParams)) {
|
|
|
|
|
evaluatedExecutionParams = this.getDynamicValue(
|
|
|
|
|
`{{${JSON.stringify(executionParams)}}}`,
|
|
|
|
|
this.evalTree,
|
2021-09-08 17:32:22 +00:00
|
|
|
this.resolvedFunctions,
|
2021-04-26 05:41:32 +00:00
|
|
|
EvaluationSubstitutionType.TEMPLATE,
|
2021-02-22 11:14:08 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-04 12:28:46 +00:00
|
|
|
return bindings.map((binding) => {
|
|
|
|
|
// Replace any reference of 'this.params' to 'executionParams' (backwards compatibility)
|
|
|
|
|
// also helps with dealing with IIFE which are normal functions (not arrow)
|
|
|
|
|
// because normal functions won't retain 'this' context (when executed elsewhere)
|
|
|
|
|
const replacedBinding = binding.replace(
|
|
|
|
|
EXECUTION_PARAM_REFERENCE_REGEX,
|
|
|
|
|
EXECUTION_PARAM_KEY,
|
|
|
|
|
);
|
|
|
|
|
return this.getDynamicValue(
|
|
|
|
|
`{{${replacedBinding}}}`,
|
|
|
|
|
this.evalTree,
|
2021-09-08 17:32:22 +00:00
|
|
|
this.resolvedFunctions,
|
2021-04-26 05:41:32 +00:00
|
|
|
EvaluationSubstitutionType.TEMPLATE,
|
2022-02-04 12:28:46 +00:00
|
|
|
// params can be accessed via "this.params" or "executionParams"
|
|
|
|
|
{
|
|
|
|
|
thisContext: { [THIS_DOT_PARAMS_KEY]: evaluatedExecutionParams },
|
|
|
|
|
globalContext: { [EXECUTION_PARAM_KEY]: evaluatedExecutionParams },
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
});
|
2021-02-22 11:14:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
clearErrors() {
|
|
|
|
|
this.errors = [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
clearLogs() {
|
|
|
|
|
this.logs = [];
|
|
|
|
|
}
|
2021-10-05 13:52:27 +00:00
|
|
|
|
2021-12-15 05:18:45 +00:00
|
|
|
private lintTriggerPath(
|
|
|
|
|
userScript: string,
|
|
|
|
|
entity: DataTreeEntity,
|
|
|
|
|
currentTree: DataTree,
|
|
|
|
|
) {
|
2021-10-05 13:52:27 +00:00
|
|
|
const { jsSnippets } = getDynamicBindings(userScript, entity);
|
|
|
|
|
const script = getScriptToEval(
|
|
|
|
|
jsSnippets[0],
|
|
|
|
|
EvaluationScriptType.TRIGGERS,
|
|
|
|
|
);
|
2022-02-11 10:52:27 +00:00
|
|
|
const GLOBAL_DATA = createGlobalData(
|
|
|
|
|
currentTree,
|
|
|
|
|
this.resolvedFunctions,
|
|
|
|
|
true,
|
|
|
|
|
);
|
2021-12-15 05:18:45 +00:00
|
|
|
|
2021-10-05 13:52:27 +00:00
|
|
|
return getLintingErrors(
|
|
|
|
|
script,
|
|
|
|
|
GLOBAL_DATA,
|
|
|
|
|
jsSnippets[0],
|
|
|
|
|
EvaluationScriptType.TRIGGERS,
|
|
|
|
|
);
|
|
|
|
|
}
|
2021-02-22 11:14:08 +00:00
|
|
|
}
|
|
|
|
|
|
2021-12-02 10:03:43 +00:00
|
|
|
export const extractReferencesFromBinding = (
|
|
|
|
|
script: string,
|
|
|
|
|
allPaths: Record<string, true>,
|
|
|
|
|
): string[] => {
|
|
|
|
|
const references: Set<string> = new Set<string>();
|
|
|
|
|
const identifiers = extractIdentifiersFromCode(script);
|
|
|
|
|
|
2021-02-22 11:14:08 +00:00
|
|
|
identifiers.forEach((identifier: string) => {
|
|
|
|
|
// If the identifier exists directly, add it and return
|
2021-12-02 10:03:43 +00:00
|
|
|
if (allPaths.hasOwnProperty(identifier)) {
|
|
|
|
|
references.add(identifier);
|
2021-02-22 11:14:08 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const subpaths = _.toPath(identifier);
|
|
|
|
|
let current = "";
|
|
|
|
|
// We want to keep going till we reach top level, but not add top level
|
|
|
|
|
// Eg: Input1.text should not depend on entire Table1 unless it explicitly asked for that.
|
|
|
|
|
// This is mainly to avoid a lot of unnecessary evals, if we feel this is wrong
|
2022-01-28 11:10:05 +00:00
|
|
|
// we can remove the length requirement, and it will still work
|
2021-02-22 11:14:08 +00:00
|
|
|
while (subpaths.length > 1) {
|
|
|
|
|
current = convertPathToString(subpaths);
|
|
|
|
|
// We've found the dep, add it and return
|
2021-12-02 10:03:43 +00:00
|
|
|
if (allPaths.hasOwnProperty(current)) {
|
|
|
|
|
references.add(current);
|
2021-02-22 11:14:08 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
subpaths.pop();
|
|
|
|
|
}
|
|
|
|
|
});
|
2021-12-02 10:03:43 +00:00
|
|
|
return Array.from(references);
|
2021-02-22 11:14:08 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// TODO cryptic comment below. Dont know if we still need this. Duplicate function
|
|
|
|
|
// referencing DATA_BIND_REGEX fails for the value "{{Table1.tableData[Table1.selectedRowIndex]}}" if you run it multiple times and don't recreate
|
|
|
|
|
const isDynamicValue = (value: string): boolean => DATA_BIND_REGEX.test(value);
|
|
|
|
|
|
|
|
|
|
function isValidEntity(entity: DataTreeEntity): entity is DataTreeObjectEntity {
|
|
|
|
|
if (!_.isObject(entity)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return "ENTITY_TYPE" in entity;
|
|
|
|
|
}
|