2021-01-29 06:04:28 +00:00
|
|
|
import {
|
|
|
|
|
DependencyMap,
|
2021-06-21 11:09:51 +00:00
|
|
|
EVAL_ERROR_PATH,
|
|
|
|
|
EvaluationError,
|
|
|
|
|
getEvalErrorPath,
|
|
|
|
|
getEvalValuePath,
|
2021-01-29 06:04:28 +00:00
|
|
|
isChildPropertyPath,
|
2021-02-16 10:29:08 +00:00
|
|
|
isDynamicValue,
|
2021-06-21 11:09:51 +00:00
|
|
|
PropertyEvaluationErrorType,
|
2021-02-22 11:14:08 +00:00
|
|
|
} from "utils/DynamicBindingUtils";
|
2021-07-26 05:50:46 +00:00
|
|
|
import { validate } from "./validations";
|
2021-01-04 10:16:08 +00:00
|
|
|
import { Diff } from "deep-diff";
|
|
|
|
|
import {
|
|
|
|
|
DataTree,
|
2021-01-29 06:04:28 +00:00
|
|
|
DataTreeAction,
|
2021-07-20 10:02:56 +00:00
|
|
|
DataTreeAppsmith,
|
2021-01-04 10:16:08 +00:00
|
|
|
DataTreeEntity,
|
|
|
|
|
DataTreeWidget,
|
|
|
|
|
ENTITY_TYPE,
|
2021-09-08 17:32:22 +00:00
|
|
|
DataTreeJSAction,
|
2021-02-22 11:14:08 +00:00
|
|
|
} from "entities/DataTree/dataTreeFactory";
|
2021-01-04 10:16:08 +00:00
|
|
|
import _ from "lodash";
|
2021-04-27 13:39:52 +00:00
|
|
|
import { WidgetTypeConfigMap } from "utils/WidgetFactory";
|
2021-07-26 05:50:46 +00:00
|
|
|
import { ValidationConfig } from "constants/PropertyControlConstants";
|
2021-06-21 11:09:51 +00:00
|
|
|
import { Severity } from "entities/AppsmithConsole";
|
2021-09-21 06:02:45 +00:00
|
|
|
import { JSCollection, Variable } from "entities/JSCollection";
|
|
|
|
|
import evaluate from "workers/evaluate";
|
2021-01-29 06:04:28 +00:00
|
|
|
// Dropdown1.options[1].value -> Dropdown1.options[1]
|
|
|
|
|
// Dropdown1.options[1] -> Dropdown1.options
|
|
|
|
|
// Dropdown1.options -> Dropdown1
|
|
|
|
|
export const IMMEDIATE_PARENT_REGEX = /^(.*)(\..*|\[.*\])$/;
|
|
|
|
|
|
2021-01-04 10:16:08 +00:00
|
|
|
export enum DataTreeDiffEvent {
|
|
|
|
|
NEW = "NEW",
|
|
|
|
|
DELETE = "DELETE",
|
|
|
|
|
EDIT = "EDIT",
|
|
|
|
|
NOOP = "NOOP",
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-20 10:50:22 +00:00
|
|
|
export type DataTreeDiff = {
|
2021-01-04 10:16:08 +00:00
|
|
|
payload: {
|
|
|
|
|
propertyPath: string;
|
|
|
|
|
value?: string;
|
|
|
|
|
};
|
|
|
|
|
event: DataTreeDiffEvent;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export class CrashingError extends Error {}
|
|
|
|
|
|
|
|
|
|
export const convertPathToString = (arrPath: Array<string | number>) => {
|
|
|
|
|
let string = "";
|
|
|
|
|
arrPath.forEach((segment) => {
|
2021-02-16 10:29:08 +00:00
|
|
|
if (isInt(segment)) {
|
|
|
|
|
string = string + "[" + segment + "]";
|
|
|
|
|
} else {
|
2021-01-04 10:16:08 +00:00
|
|
|
if (string.length !== 0) {
|
|
|
|
|
string = string + ".";
|
|
|
|
|
}
|
|
|
|
|
string = string + segment;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return string;
|
|
|
|
|
};
|
|
|
|
|
|
2021-02-16 10:29:08 +00:00
|
|
|
// Todo: improve the logic here
|
|
|
|
|
// Right now NaN, Infinity, floats, everything works
|
|
|
|
|
function isInt(val: string | number): boolean {
|
2021-05-05 11:20:59 +00:00
|
|
|
return Number.isInteger(val) || (_.isString(val) && /^\d+$/.test(val));
|
2021-02-16 10:29:08 +00:00
|
|
|
}
|
|
|
|
|
|
2021-04-21 14:34:25 +00:00
|
|
|
// Removes the entity name from the property path
|
|
|
|
|
export function getEntityNameAndPropertyPath(
|
|
|
|
|
fullPath: string,
|
2021-06-21 11:09:51 +00:00
|
|
|
): {
|
|
|
|
|
entityName: string;
|
|
|
|
|
propertyPath: string;
|
|
|
|
|
} {
|
2021-04-21 14:34:25 +00:00
|
|
|
const indexOfFirstDot = fullPath.indexOf(".");
|
2021-06-21 11:09:51 +00:00
|
|
|
if (indexOfFirstDot === -1) {
|
|
|
|
|
// No dot was found so path is the entity name itself
|
|
|
|
|
return {
|
|
|
|
|
entityName: fullPath,
|
|
|
|
|
propertyPath: "",
|
|
|
|
|
};
|
|
|
|
|
}
|
2021-04-21 14:34:25 +00:00
|
|
|
const entityName = fullPath.substring(0, indexOfFirstDot);
|
2021-06-21 11:09:51 +00:00
|
|
|
const propertyPath = fullPath.substring(indexOfFirstDot + 1);
|
2021-04-21 14:34:25 +00:00
|
|
|
return { entityName, propertyPath };
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-04 10:16:08 +00:00
|
|
|
export const translateDiffEventToDataTreeDiffEvent = (
|
|
|
|
|
difference: Diff<any, any>,
|
2021-09-08 17:32:22 +00:00
|
|
|
unEvalDataTree: DataTree,
|
2021-06-23 07:29:36 +00:00
|
|
|
): DataTreeDiff | DataTreeDiff[] => {
|
|
|
|
|
let result: DataTreeDiff | DataTreeDiff[] = {
|
2021-01-04 10:16:08 +00:00
|
|
|
payload: {
|
|
|
|
|
propertyPath: "",
|
|
|
|
|
value: "",
|
|
|
|
|
},
|
|
|
|
|
event: DataTreeDiffEvent.NOOP,
|
|
|
|
|
};
|
|
|
|
|
if (!difference.path) {
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
const propertyPath = convertPathToString(difference.path);
|
2021-09-08 17:32:22 +00:00
|
|
|
const { entityName } = getEntityNameAndPropertyPath(propertyPath);
|
|
|
|
|
const entity = unEvalDataTree[entityName];
|
|
|
|
|
const isJsAction = isJSAction(entity);
|
2021-01-04 10:16:08 +00:00
|
|
|
switch (difference.kind) {
|
|
|
|
|
case "N": {
|
|
|
|
|
result.event = DataTreeDiffEvent.NEW;
|
|
|
|
|
result.payload = {
|
|
|
|
|
propertyPath,
|
|
|
|
|
};
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "D": {
|
|
|
|
|
result.event = DataTreeDiffEvent.DELETE;
|
|
|
|
|
result.payload = { propertyPath };
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "E": {
|
2021-09-08 17:32:22 +00:00
|
|
|
let rhsChange, lhsChange;
|
|
|
|
|
if (isJsAction) {
|
|
|
|
|
rhsChange = typeof difference.rhs === "string";
|
|
|
|
|
lhsChange = typeof difference.lhs === "string";
|
|
|
|
|
} else {
|
|
|
|
|
rhsChange =
|
|
|
|
|
typeof difference.rhs === "string" && isDynamicValue(difference.rhs);
|
|
|
|
|
|
|
|
|
|
lhsChange =
|
|
|
|
|
typeof difference.lhs === "string" && isDynamicValue(difference.lhs);
|
|
|
|
|
}
|
2021-01-04 10:16:08 +00:00
|
|
|
|
|
|
|
|
if (rhsChange || lhsChange) {
|
|
|
|
|
result.event = DataTreeDiffEvent.EDIT;
|
|
|
|
|
result.payload = {
|
|
|
|
|
propertyPath,
|
|
|
|
|
value: difference.rhs,
|
|
|
|
|
};
|
2021-06-23 07:29:36 +00:00
|
|
|
} else if (difference.lhs === undefined || difference.rhs === undefined) {
|
2021-01-04 10:16:08 +00:00
|
|
|
// Handle static value changes that change structure that can lead to
|
|
|
|
|
// old bindings being eligible
|
2021-06-23 07:29:36 +00:00
|
|
|
if (difference.lhs === undefined && isTrueObject(difference.rhs)) {
|
2021-01-04 10:16:08 +00:00
|
|
|
result.event = DataTreeDiffEvent.NEW;
|
|
|
|
|
result.payload = { propertyPath };
|
|
|
|
|
}
|
2021-06-23 07:29:36 +00:00
|
|
|
if (difference.rhs === undefined && isTrueObject(difference.lhs)) {
|
2021-01-04 10:16:08 +00:00
|
|
|
result.event = DataTreeDiffEvent.DELETE;
|
|
|
|
|
result.payload = { propertyPath };
|
|
|
|
|
}
|
2021-06-23 07:29:36 +00:00
|
|
|
} else if (
|
|
|
|
|
isTrueObject(difference.lhs) &&
|
|
|
|
|
!isTrueObject(difference.rhs)
|
|
|
|
|
) {
|
|
|
|
|
// This will happen for static value changes where a property went
|
|
|
|
|
// from being an object to any other type like string or number
|
|
|
|
|
// in such a case we want to delete all nested paths of the
|
|
|
|
|
// original lhs object
|
|
|
|
|
|
|
|
|
|
result = Object.keys(difference.lhs).map((diffKey) => {
|
|
|
|
|
const path = `${propertyPath}.${diffKey}`;
|
|
|
|
|
return {
|
|
|
|
|
event: DataTreeDiffEvent.DELETE,
|
|
|
|
|
payload: {
|
|
|
|
|
propertyPath: path,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
} else if (
|
|
|
|
|
!isTrueObject(difference.lhs) &&
|
|
|
|
|
isTrueObject(difference.rhs)
|
|
|
|
|
) {
|
|
|
|
|
// This will happen for static value changes where a property went
|
|
|
|
|
// from being any other type like string or number to an object
|
|
|
|
|
// in such a case we want to add all nested paths of the
|
|
|
|
|
// new rhs object
|
|
|
|
|
result = Object.keys(difference.rhs).map((diffKey) => {
|
|
|
|
|
const path = `${propertyPath}.${diffKey}`;
|
|
|
|
|
return {
|
|
|
|
|
event: DataTreeDiffEvent.NEW,
|
|
|
|
|
payload: {
|
|
|
|
|
propertyPath: path,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
});
|
2021-01-04 10:16:08 +00:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "A": {
|
2021-09-08 17:32:22 +00:00
|
|
|
return translateDiffEventToDataTreeDiffEvent(
|
|
|
|
|
{
|
|
|
|
|
...difference.item,
|
|
|
|
|
path: [...difference.path, difference.index],
|
|
|
|
|
},
|
|
|
|
|
unEvalDataTree,
|
|
|
|
|
);
|
2021-01-04 10:16:08 +00:00
|
|
|
}
|
|
|
|
|
default: {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
Table1.selectedRow
|
|
|
|
|
Table1.selectedRow.email: ["Input1.defaultText"]
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
export const addDependantsOfNestedPropertyPaths = (
|
|
|
|
|
parentPaths: Array<string>,
|
|
|
|
|
inverseMap: DependencyMap,
|
2021-02-16 10:29:08 +00:00
|
|
|
): Set<string> => {
|
2021-01-04 10:16:08 +00:00
|
|
|
const withNestedPaths: Set<string> = new Set();
|
|
|
|
|
const dependantNodes = Object.keys(inverseMap);
|
|
|
|
|
parentPaths.forEach((propertyPath) => {
|
|
|
|
|
withNestedPaths.add(propertyPath);
|
|
|
|
|
dependantNodes
|
|
|
|
|
.filter((dependantNodePath) =>
|
2021-01-29 06:04:28 +00:00
|
|
|
isChildPropertyPath(propertyPath, dependantNodePath),
|
2021-01-04 10:16:08 +00:00
|
|
|
)
|
|
|
|
|
.forEach((dependantNodePath) => {
|
|
|
|
|
inverseMap[dependantNodePath].forEach((path) => {
|
|
|
|
|
withNestedPaths.add(path);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
2021-02-16 10:29:08 +00:00
|
|
|
return withNestedPaths;
|
2021-01-04 10:16:08 +00:00
|
|
|
};
|
|
|
|
|
|
2021-01-29 06:04:28 +00:00
|
|
|
export function isWidget(entity: DataTreeEntity): entity is DataTreeWidget {
|
2021-01-04 10:16:08 +00:00
|
|
|
return (
|
|
|
|
|
typeof entity === "object" &&
|
|
|
|
|
"ENTITY_TYPE" in entity &&
|
|
|
|
|
entity.ENTITY_TYPE === ENTITY_TYPE.WIDGET
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-29 06:04:28 +00:00
|
|
|
export function isAction(entity: DataTreeEntity): entity is DataTreeAction {
|
2021-01-04 10:16:08 +00:00
|
|
|
return (
|
|
|
|
|
typeof entity === "object" &&
|
|
|
|
|
"ENTITY_TYPE" in entity &&
|
|
|
|
|
entity.ENTITY_TYPE === ENTITY_TYPE.ACTION
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-20 10:02:56 +00:00
|
|
|
export function isAppsmithEntity(
|
|
|
|
|
entity: DataTreeEntity,
|
|
|
|
|
): entity is DataTreeAppsmith {
|
|
|
|
|
return (
|
|
|
|
|
typeof entity === "object" &&
|
|
|
|
|
"ENTITY_TYPE" in entity &&
|
|
|
|
|
entity.ENTITY_TYPE === ENTITY_TYPE.APPSMITH
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-08 17:32:22 +00:00
|
|
|
export function isJSAction(entity: DataTreeEntity): entity is DataTreeJSAction {
|
|
|
|
|
return (
|
|
|
|
|
typeof entity === "object" &&
|
|
|
|
|
"ENTITY_TYPE" in entity &&
|
|
|
|
|
entity.ENTITY_TYPE === ENTITY_TYPE.JSACTION
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-04 10:16:08 +00:00
|
|
|
// We need to remove functions from data tree to avoid any unexpected identifier while JSON parsing
|
|
|
|
|
// Check issue https://github.com/appsmithorg/appsmith/issues/719
|
|
|
|
|
export const removeFunctions = (value: any) => {
|
|
|
|
|
if (_.isFunction(value)) {
|
|
|
|
|
return "Function call";
|
2021-01-25 11:19:31 +00:00
|
|
|
} else if (_.isObject(value)) {
|
2021-05-28 16:00:21 +00:00
|
|
|
return JSON.parse(
|
|
|
|
|
JSON.stringify(value, (_, v) =>
|
|
|
|
|
typeof v === "bigint" ? v.toString() : v,
|
|
|
|
|
),
|
|
|
|
|
);
|
2021-01-04 10:16:08 +00:00
|
|
|
} else {
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const makeParentsDependOnChildren = (
|
|
|
|
|
depMap: DependencyMap,
|
|
|
|
|
): DependencyMap => {
|
|
|
|
|
//return depMap;
|
|
|
|
|
// Make all parents depend on child
|
|
|
|
|
Object.keys(depMap).forEach((key) => {
|
|
|
|
|
depMap = makeParentsDependOnChild(depMap, key);
|
|
|
|
|
depMap[key].forEach((path) => {
|
|
|
|
|
depMap = makeParentsDependOnChild(depMap, path);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
return depMap;
|
|
|
|
|
};
|
2021-01-29 06:04:28 +00:00
|
|
|
|
2021-01-04 10:16:08 +00:00
|
|
|
export const makeParentsDependOnChild = (
|
|
|
|
|
depMap: DependencyMap,
|
|
|
|
|
child: string,
|
|
|
|
|
): DependencyMap => {
|
|
|
|
|
const result: DependencyMap = depMap;
|
|
|
|
|
let curKey = child;
|
2021-01-29 06:04:28 +00:00
|
|
|
|
2021-01-04 10:16:08 +00:00
|
|
|
let matches: Array<string> | null;
|
|
|
|
|
// Note: The `=` is intentional
|
|
|
|
|
// Stops looping when match is null
|
2021-01-29 06:04:28 +00:00
|
|
|
while ((matches = curKey.match(IMMEDIATE_PARENT_REGEX)) !== null) {
|
2021-01-04 10:16:08 +00:00
|
|
|
const parentKey = matches[1];
|
|
|
|
|
// Todo: switch everything to set.
|
|
|
|
|
const existing = new Set(result[parentKey] || []);
|
|
|
|
|
existing.add(curKey);
|
|
|
|
|
result[parentKey] = Array.from(existing);
|
|
|
|
|
curKey = parentKey;
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
};
|
|
|
|
|
|
2021-01-29 06:04:28 +00:00
|
|
|
// 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);
|
|
|
|
|
};
|
|
|
|
|
|
2021-01-04 10:16:08 +00:00
|
|
|
export function validateWidgetProperty(
|
2021-07-26 05:50:46 +00:00
|
|
|
config: ValidationConfig,
|
|
|
|
|
value: unknown,
|
|
|
|
|
props: Record<string, unknown>,
|
2021-01-04 10:16:08 +00:00
|
|
|
) {
|
2021-07-26 05:50:46 +00:00
|
|
|
if (!config) {
|
|
|
|
|
return {
|
|
|
|
|
isValid: true,
|
|
|
|
|
parsed: value,
|
|
|
|
|
};
|
2021-01-04 10:16:08 +00:00
|
|
|
}
|
2021-07-26 05:50:46 +00:00
|
|
|
return validate(config, value, props);
|
2021-01-04 10:16:08 +00:00
|
|
|
}
|
|
|
|
|
|
2021-04-21 14:34:25 +00:00
|
|
|
export function getValidatedTree(tree: DataTree) {
|
2021-06-21 11:09:51 +00:00
|
|
|
return Object.keys(tree).reduce((tree, entityKey: string) => {
|
2021-01-04 10:16:08 +00:00
|
|
|
const entity = tree[entityKey] as DataTreeWidget;
|
|
|
|
|
if (!isWidget(entity)) {
|
|
|
|
|
return tree;
|
|
|
|
|
}
|
|
|
|
|
const parsedEntity = { ...entity };
|
2021-04-21 14:34:25 +00:00
|
|
|
Object.entries(entity.validationPaths).forEach(([property, validation]) => {
|
|
|
|
|
const value = _.get(entity, property);
|
|
|
|
|
// Pass it through parse
|
2021-09-29 12:03:11 +00:00
|
|
|
const { isValid, messages, parsed, transformed } = validateWidgetProperty(
|
2021-07-26 05:50:46 +00:00
|
|
|
validation,
|
2021-04-21 14:34:25 +00:00
|
|
|
value,
|
|
|
|
|
entity,
|
|
|
|
|
);
|
|
|
|
|
_.set(parsedEntity, property, parsed);
|
|
|
|
|
const evaluatedValue = isValid
|
|
|
|
|
? parsed
|
|
|
|
|
: _.isUndefined(transformed)
|
|
|
|
|
? value
|
|
|
|
|
: transformed;
|
|
|
|
|
const safeEvaluatedValue = removeFunctions(evaluatedValue);
|
2021-06-21 11:09:51 +00:00
|
|
|
_.set(
|
|
|
|
|
parsedEntity,
|
|
|
|
|
getEvalValuePath(`${entityKey}.${property}`, false),
|
|
|
|
|
safeEvaluatedValue,
|
|
|
|
|
);
|
2021-04-21 14:34:25 +00:00
|
|
|
if (!isValid) {
|
2021-09-29 12:03:11 +00:00
|
|
|
const evalErrors: EvaluationError[] =
|
|
|
|
|
messages?.map((message) => ({
|
|
|
|
|
errorType: PropertyEvaluationErrorType.VALIDATION,
|
|
|
|
|
errorMessage: message,
|
|
|
|
|
severity: Severity.ERROR,
|
|
|
|
|
raw: value,
|
|
|
|
|
})) ?? [];
|
2021-06-21 11:09:51 +00:00
|
|
|
addErrorToEntityProperty(
|
2021-09-29 12:03:11 +00:00
|
|
|
evalErrors,
|
2021-06-21 11:09:51 +00:00
|
|
|
tree,
|
|
|
|
|
getEvalErrorPath(`${entityKey}.${property}`, false),
|
|
|
|
|
);
|
2021-01-04 10:16:08 +00:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return { ...tree, [entityKey]: parsedEntity };
|
|
|
|
|
}, tree);
|
|
|
|
|
}
|
2021-01-29 17:59:23 +00:00
|
|
|
|
|
|
|
|
export const getAllPaths = (
|
|
|
|
|
records: any,
|
|
|
|
|
curKey = "",
|
|
|
|
|
result: Record<string, true> = {},
|
|
|
|
|
): Record<string, true> => {
|
|
|
|
|
// Add the key if it exists
|
|
|
|
|
if (curKey) result[curKey] = true;
|
|
|
|
|
if (Array.isArray(records)) {
|
|
|
|
|
for (let i = 0; i < records.length; i++) {
|
|
|
|
|
const tempKey = curKey ? `${curKey}[${i}]` : `${i}`;
|
|
|
|
|
getAllPaths(records[i], tempKey, result);
|
|
|
|
|
}
|
|
|
|
|
} else if (typeof records === "object") {
|
|
|
|
|
for (const key in records) {
|
|
|
|
|
const tempKey = curKey ? `${curKey}.${key}` : `${key}`;
|
|
|
|
|
getAllPaths(records[key], tempKey, result);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
};
|
2021-02-16 10:29:08 +00:00
|
|
|
export const trimDependantChangePaths = (
|
|
|
|
|
changePaths: Set<string>,
|
|
|
|
|
dependencyMap: DependencyMap,
|
|
|
|
|
): Array<string> => {
|
|
|
|
|
const trimmedPaths = [];
|
|
|
|
|
for (const path of changePaths) {
|
|
|
|
|
let foundADependant = false;
|
|
|
|
|
if (path in dependencyMap) {
|
|
|
|
|
const dependants = dependencyMap[path];
|
|
|
|
|
for (const dependantPath of dependants) {
|
|
|
|
|
if (changePaths.has(dependantPath)) {
|
|
|
|
|
foundADependant = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!foundADependant) {
|
|
|
|
|
trimmedPaths.push(path);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return trimmedPaths;
|
|
|
|
|
};
|
2021-02-22 11:14:08 +00:00
|
|
|
|
2021-04-27 13:39:52 +00:00
|
|
|
export function getSafeToRenderDataTree(
|
|
|
|
|
tree: DataTree,
|
|
|
|
|
widgetTypeConfigMap: WidgetTypeConfigMap,
|
|
|
|
|
) {
|
|
|
|
|
return Object.keys(tree).reduce((tree, entityKey: string) => {
|
|
|
|
|
const entity = tree[entityKey] as DataTreeWidget;
|
|
|
|
|
if (!isWidget(entity)) {
|
|
|
|
|
return tree;
|
|
|
|
|
}
|
|
|
|
|
const safeToRenderEntity = { ...entity };
|
|
|
|
|
// Set user input values to their parsed values
|
|
|
|
|
Object.entries(entity.validationPaths).forEach(([property, validation]) => {
|
|
|
|
|
const value = _.get(entity, property);
|
|
|
|
|
// Pass it through parse
|
2021-07-26 05:50:46 +00:00
|
|
|
const { parsed } = validateWidgetProperty(validation, value, entity);
|
2021-04-27 13:39:52 +00:00
|
|
|
_.set(safeToRenderEntity, property, parsed);
|
|
|
|
|
});
|
|
|
|
|
// Set derived values to undefined or else they would go as bindings
|
|
|
|
|
Object.keys(widgetTypeConfigMap[entity.type].derivedProperties).forEach(
|
|
|
|
|
(property) => {
|
|
|
|
|
_.set(safeToRenderEntity, property, undefined);
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
return { ...tree, [entityKey]: safeToRenderEntity };
|
|
|
|
|
}, tree);
|
|
|
|
|
}
|
2021-06-21 11:09:51 +00:00
|
|
|
|
2021-09-08 17:32:22 +00:00
|
|
|
export const STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm;
|
|
|
|
|
export const ARGUMENT_NAMES = /([^\s,]+)/g;
|
|
|
|
|
|
|
|
|
|
export function getParams(func: any) {
|
|
|
|
|
const fnStr = func.toString().replace(STRIP_COMMENTS, "");
|
|
|
|
|
const args: Array<Variable> = [];
|
|
|
|
|
let result = fnStr
|
|
|
|
|
.slice(fnStr.indexOf("(") + 1, fnStr.indexOf(")"))
|
|
|
|
|
.match(ARGUMENT_NAMES);
|
|
|
|
|
if (result === null) result = [];
|
|
|
|
|
if (result && result.length) {
|
|
|
|
|
result.forEach((arg: string) => {
|
|
|
|
|
const element = arg.split("=");
|
|
|
|
|
args.push({
|
|
|
|
|
name: element[0],
|
|
|
|
|
value: element[1],
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return args;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-21 11:09:51 +00:00
|
|
|
export const addErrorToEntityProperty = (
|
|
|
|
|
errors: EvaluationError[],
|
|
|
|
|
dataTree: DataTree,
|
|
|
|
|
path: string,
|
|
|
|
|
) => {
|
|
|
|
|
const { entityName, propertyPath } = getEntityNameAndPropertyPath(path);
|
|
|
|
|
const logBlackList = _.get(dataTree, `${entityName}.logBlackList`, {});
|
|
|
|
|
if (propertyPath && !(propertyPath in logBlackList)) {
|
|
|
|
|
const existingErrors = _.get(
|
|
|
|
|
dataTree,
|
|
|
|
|
`${entityName}.${EVAL_ERROR_PATH}['${propertyPath}']`,
|
|
|
|
|
[],
|
|
|
|
|
) as EvaluationError[];
|
|
|
|
|
_.set(
|
|
|
|
|
dataTree,
|
|
|
|
|
`${entityName}.${EVAL_ERROR_PATH}['${propertyPath}']`,
|
|
|
|
|
existingErrors.concat(errors),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return dataTree;
|
|
|
|
|
};
|
2021-06-23 07:29:36 +00:00
|
|
|
|
|
|
|
|
// For the times when you need to know if something truly an object like { a: 1, b: 2}
|
|
|
|
|
// typeof, lodash.isObject and others will return false positives for things like array, null, etc
|
2021-07-20 10:02:56 +00:00
|
|
|
export const isTrueObject = (
|
|
|
|
|
item: unknown,
|
|
|
|
|
): item is Record<string, unknown> => {
|
2021-06-23 07:29:36 +00:00
|
|
|
return Object.prototype.toString.call(item) === "[object Object]";
|
|
|
|
|
};
|
2021-07-20 05:04:12 +00:00
|
|
|
|
|
|
|
|
export const isDynamicLeaf = (unEvalTree: DataTree, propertyPath: string) => {
|
|
|
|
|
const [entityName, ...propPathEls] = _.toPath(propertyPath);
|
|
|
|
|
// Framework feature: Top level items are never leaves
|
|
|
|
|
if (entityName === propertyPath) return false;
|
|
|
|
|
// Ignore if this was a delete op
|
|
|
|
|
if (!(entityName in unEvalTree)) return false;
|
|
|
|
|
|
|
|
|
|
const entity = unEvalTree[entityName];
|
2021-09-08 17:32:22 +00:00
|
|
|
if (!isAction(entity) && !isWidget(entity) && !isJSAction(entity))
|
|
|
|
|
return false;
|
2021-07-20 05:04:12 +00:00
|
|
|
const relativePropertyPath = convertPathToString(propPathEls);
|
|
|
|
|
return relativePropertyPath in entity.bindingPaths;
|
|
|
|
|
};
|
2021-09-21 06:02:45 +00:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
after every update get js object body to parse into actions and variables
|
|
|
|
|
*/
|
|
|
|
|
export const parseJSCollection = (
|
|
|
|
|
body: string,
|
|
|
|
|
jsCollection: JSCollection,
|
|
|
|
|
evalTree: DataTree,
|
|
|
|
|
): Record<string, any> => {
|
|
|
|
|
const regex = new RegExp(/^export default[\s]*?({[\s\S]*?})/);
|
|
|
|
|
const correctFormat = regex.test(body);
|
|
|
|
|
if (correctFormat) {
|
|
|
|
|
const toBeParsedBody = body.replace(/export default/g, "");
|
|
|
|
|
const { errors, result } = evaluate(
|
|
|
|
|
toBeParsedBody,
|
|
|
|
|
evalTree,
|
|
|
|
|
{},
|
|
|
|
|
undefined,
|
|
|
|
|
true,
|
|
|
|
|
);
|
|
|
|
|
const errorsList = errors && errors.length ? errors : [];
|
|
|
|
|
_.set(evalTree, `${jsCollection.name}.${EVAL_ERROR_PATH}.body`, errorsList);
|
|
|
|
|
const parsedLength = Object.keys(result).length;
|
|
|
|
|
const actions = [];
|
|
|
|
|
const variables = [];
|
|
|
|
|
if (parsedLength > 0) {
|
|
|
|
|
for (const key in result) {
|
|
|
|
|
if (result.hasOwnProperty(key)) {
|
|
|
|
|
if (typeof result[key] === "function") {
|
|
|
|
|
const value = result[key];
|
|
|
|
|
const params = getParams(value);
|
|
|
|
|
actions.push({
|
|
|
|
|
name: key,
|
|
|
|
|
body: result[key].toString(),
|
|
|
|
|
arguments: params,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
variables.push({
|
|
|
|
|
name: key,
|
|
|
|
|
value: result[key],
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
evalTree,
|
|
|
|
|
result: {
|
|
|
|
|
actions: actions,
|
|
|
|
|
variables: variables,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
const errors = [
|
|
|
|
|
{
|
|
|
|
|
errorType: PropertyEvaluationErrorType.PARSE,
|
|
|
|
|
raw: "",
|
|
|
|
|
severity: Severity.ERROR,
|
|
|
|
|
errorMessage: "Start object with export default",
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
_.set(evalTree, `${jsCollection.name}.${EVAL_ERROR_PATH}.body`, errors);
|
|
|
|
|
return {
|
|
|
|
|
evalTree,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
};
|