2020-10-21 04:25:32 +00:00
|
|
|
/* eslint no-restricted-globals: 0 */
|
|
|
|
|
import {
|
|
|
|
|
ISO_DATE_FORMAT,
|
|
|
|
|
VALIDATION_TYPES,
|
|
|
|
|
ValidationResponse,
|
|
|
|
|
ValidationType,
|
|
|
|
|
Validator,
|
|
|
|
|
} from "../constants/WidgetValidation";
|
|
|
|
|
import {
|
|
|
|
|
ActionDescription,
|
|
|
|
|
DataTree,
|
|
|
|
|
DataTreeAction,
|
|
|
|
|
DataTreeEntity,
|
2020-12-30 12:58:57 +00:00
|
|
|
DataTreeObjectEntity,
|
2020-10-21 04:25:32 +00:00
|
|
|
DataTreeWidget,
|
|
|
|
|
ENTITY_TYPE,
|
|
|
|
|
} from "../entities/DataTree/dataTreeFactory";
|
|
|
|
|
import equal from "fast-deep-equal/es6";
|
|
|
|
|
import _, {
|
|
|
|
|
every,
|
|
|
|
|
isBoolean,
|
|
|
|
|
isNumber,
|
|
|
|
|
isObject,
|
2020-12-30 12:58:57 +00:00
|
|
|
isPlainObject,
|
2020-10-21 04:25:32 +00:00
|
|
|
isString,
|
|
|
|
|
isUndefined,
|
|
|
|
|
toNumber,
|
|
|
|
|
toString,
|
|
|
|
|
} from "lodash";
|
|
|
|
|
import toposort from "toposort";
|
|
|
|
|
import { DATA_BIND_REGEX } from "../constants/BindingsConstants";
|
|
|
|
|
import unescapeJS from "unescape-js";
|
|
|
|
|
import { WidgetTypeConfigMap } from "../utils/WidgetFactory";
|
|
|
|
|
import { WidgetType } from "../constants/WidgetConstants";
|
|
|
|
|
import { WidgetProps } from "../widgets/BaseWidget";
|
|
|
|
|
import { WIDGET_TYPE_VALIDATION_ERROR } from "../constants/messages";
|
|
|
|
|
import moment from "moment";
|
|
|
|
|
import {
|
|
|
|
|
EVAL_WORKER_ACTIONS,
|
|
|
|
|
EvalError,
|
|
|
|
|
EvalErrorTypes,
|
|
|
|
|
extraLibraries,
|
2020-11-12 11:23:32 +00:00
|
|
|
getEntityDynamicBindingPathList,
|
|
|
|
|
getWidgetDynamicTriggerPathList,
|
|
|
|
|
isPathADynamicTrigger,
|
2020-12-30 12:58:57 +00:00
|
|
|
unsafeFunctionForEval,
|
2020-10-21 04:25:32 +00:00
|
|
|
} from "../utils/DynamicBindingUtils";
|
|
|
|
|
|
|
|
|
|
const ctx: Worker = self as any;
|
|
|
|
|
|
|
|
|
|
let ERRORS: EvalError[] = [];
|
2020-12-29 09:25:56 +00:00
|
|
|
let LOGS: any[] = [];
|
2020-10-21 04:25:32 +00:00
|
|
|
let WIDGET_TYPE_CONFIG_MAP: WidgetTypeConfigMap = {};
|
|
|
|
|
|
2020-12-28 08:42:43 +00:00
|
|
|
//TODO: Create a more complete RPC setup in the subtree-eval branch.
|
2020-12-29 09:25:56 +00:00
|
|
|
function messageEventListener(
|
|
|
|
|
fn: (message: EVAL_WORKER_ACTIONS, requestData: any) => void,
|
|
|
|
|
) {
|
2020-12-28 08:42:43 +00:00
|
|
|
return (e: MessageEvent) => {
|
2020-12-29 09:25:56 +00:00
|
|
|
const startTime = performance.now();
|
2020-12-28 08:42:43 +00:00
|
|
|
const { method, requestId, requestData } = e.data;
|
|
|
|
|
const responseData = fn(method, requestData);
|
2020-12-29 09:25:56 +00:00
|
|
|
const endTime = performance.now();
|
|
|
|
|
ctx.postMessage({
|
|
|
|
|
requestId,
|
|
|
|
|
responseData,
|
|
|
|
|
timeTaken: (endTime - startTime).toFixed(2),
|
|
|
|
|
});
|
2020-12-28 08:42:43 +00:00
|
|
|
ERRORS = [];
|
2020-12-29 09:25:56 +00:00
|
|
|
LOGS = [];
|
2020-12-28 08:42:43 +00:00
|
|
|
};
|
|
|
|
|
}
|
2020-10-21 04:25:32 +00:00
|
|
|
|
2020-12-28 08:42:43 +00:00
|
|
|
ctx.addEventListener(
|
|
|
|
|
"message",
|
2020-12-29 09:25:56 +00:00
|
|
|
messageEventListener((method, requestData: any) => {
|
|
|
|
|
switch (method) {
|
2020-12-28 08:42:43 +00:00
|
|
|
case EVAL_WORKER_ACTIONS.EVAL_TREE: {
|
|
|
|
|
const { widgetTypeConfigMap, dataTree } = requestData;
|
|
|
|
|
WIDGET_TYPE_CONFIG_MAP = widgetTypeConfigMap;
|
|
|
|
|
try {
|
2020-12-29 18:27:07 +00:00
|
|
|
const response = getEvaluatedDataTree(dataTree);
|
2020-12-28 08:42:43 +00:00
|
|
|
// We need to clean it to remove any possible functions inside the tree.
|
|
|
|
|
// If functions exist, it will crash the web worker
|
|
|
|
|
const cleanDataTree = JSON.stringify(response);
|
2020-12-29 09:25:56 +00:00
|
|
|
return { dataTree: cleanDataTree, errors: ERRORS, logs: LOGS };
|
2020-12-28 08:42:43 +00:00
|
|
|
} catch (e) {
|
|
|
|
|
const cleanDataTree = JSON.stringify(getValidatedTree(dataTree));
|
2020-12-29 09:25:56 +00:00
|
|
|
return { dataTree: cleanDataTree, errors: ERRORS, logs: LOGS };
|
2020-12-28 08:42:43 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
case EVAL_WORKER_ACTIONS.EVAL_SINGLE: {
|
|
|
|
|
const { binding, dataTree } = requestData;
|
|
|
|
|
const withFunctions = addFunctions(dataTree);
|
|
|
|
|
const value = getDynamicValue(binding, withFunctions, false);
|
|
|
|
|
const cleanedResponse = removeFunctions(value);
|
|
|
|
|
return { value: cleanedResponse, errors: ERRORS };
|
|
|
|
|
}
|
|
|
|
|
case EVAL_WORKER_ACTIONS.EVAL_TRIGGER: {
|
|
|
|
|
const { dynamicTrigger, callbackData, dataTree } = requestData;
|
|
|
|
|
const evalTree = getEvaluatedDataTree(dataTree);
|
|
|
|
|
const withFunctions = addFunctions(evalTree);
|
|
|
|
|
const triggers = getDynamicValue(
|
|
|
|
|
dynamicTrigger,
|
|
|
|
|
withFunctions,
|
|
|
|
|
true,
|
|
|
|
|
callbackData,
|
|
|
|
|
);
|
|
|
|
|
const cleanedResponse = removeFunctions(triggers);
|
|
|
|
|
return { triggers: cleanedResponse, errors: ERRORS };
|
|
|
|
|
}
|
|
|
|
|
case EVAL_WORKER_ACTIONS.CLEAR_CACHE: {
|
|
|
|
|
clearCaches();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
case EVAL_WORKER_ACTIONS.CLEAR_PROPERTY_CACHE: {
|
|
|
|
|
const { propertyPath } = requestData;
|
|
|
|
|
clearPropertyCache(propertyPath);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
case EVAL_WORKER_ACTIONS.CLEAR_PROPERTY_CACHE_OF_WIDGET: {
|
|
|
|
|
const { widgetName } = requestData;
|
|
|
|
|
clearPropertyCacheOfWidget(widgetName);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
case EVAL_WORKER_ACTIONS.VALIDATE_PROPERTY: {
|
|
|
|
|
const { widgetType, property, value, props } = requestData;
|
|
|
|
|
const result = validateWidgetProperty(
|
|
|
|
|
widgetType,
|
|
|
|
|
property,
|
|
|
|
|
value,
|
|
|
|
|
props,
|
|
|
|
|
);
|
|
|
|
|
const cleanedResponse = removeFunctions(result);
|
|
|
|
|
return cleanedResponse;
|
|
|
|
|
}
|
|
|
|
|
default: {
|
|
|
|
|
console.error("Action not registered on worker", method, requestData);
|
2020-12-23 14:18:46 +00:00
|
|
|
}
|
2020-10-24 14:29:53 +00:00
|
|
|
}
|
2020-12-28 08:42:43 +00:00
|
|
|
}),
|
|
|
|
|
);
|
2020-10-21 04:25:32 +00:00
|
|
|
|
|
|
|
|
let dependencyTreeCache: any = {};
|
|
|
|
|
let cachedDataTreeString = "";
|
|
|
|
|
|
|
|
|
|
function getEvaluatedDataTree(dataTree: DataTree): DataTree {
|
|
|
|
|
const totalStart = performance.now();
|
|
|
|
|
// Add functions to the tre
|
|
|
|
|
const withFunctions = addFunctions(dataTree);
|
|
|
|
|
// Create Dependencies DAG
|
|
|
|
|
const createDepsStart = performance.now();
|
|
|
|
|
const dataTreeString = JSON.stringify(dataTree);
|
|
|
|
|
// Stringify before doing a fast equals because the data tree has functions and fast equal will always treat those as changed values
|
|
|
|
|
// Better solve will be to prune functions
|
|
|
|
|
if (!equal(dataTreeString, cachedDataTreeString)) {
|
|
|
|
|
cachedDataTreeString = dataTreeString;
|
|
|
|
|
dependencyTreeCache = createDependencyTree(withFunctions);
|
|
|
|
|
}
|
|
|
|
|
const createDepsEnd = performance.now();
|
|
|
|
|
const {
|
|
|
|
|
dependencyMap,
|
|
|
|
|
sortedDependencies,
|
|
|
|
|
dependencyTree,
|
|
|
|
|
} = dependencyTreeCache;
|
|
|
|
|
|
|
|
|
|
// Evaluate Tree
|
|
|
|
|
const evaluatedTreeStart = performance.now();
|
|
|
|
|
const evaluatedTree = dependencySortedEvaluateDataTree(
|
|
|
|
|
dataTree,
|
|
|
|
|
dependencyMap,
|
|
|
|
|
sortedDependencies,
|
|
|
|
|
);
|
|
|
|
|
const evaluatedTreeEnd = performance.now();
|
|
|
|
|
|
|
|
|
|
// Set Loading Widgets
|
|
|
|
|
const loadingTreeStart = performance.now();
|
|
|
|
|
const treeWithLoading = setTreeLoading(evaluatedTree, dependencyTree);
|
|
|
|
|
const loadingTreeEnd = performance.now();
|
|
|
|
|
|
|
|
|
|
// Validate Widgets
|
2020-12-29 09:25:56 +00:00
|
|
|
const validateTreeStart = performance.now();
|
2020-10-21 04:25:32 +00:00
|
|
|
const validated = getValidatedTree(treeWithLoading);
|
2020-12-29 09:25:56 +00:00
|
|
|
const validateTreeEnd = performance.now();
|
2020-10-21 04:25:32 +00:00
|
|
|
const withoutFunctions = removeFunctionsFromDataTree(validated);
|
|
|
|
|
|
|
|
|
|
// End counting total time
|
|
|
|
|
const endStart = performance.now();
|
|
|
|
|
|
|
|
|
|
// Log time taken and count
|
|
|
|
|
const timeTaken = {
|
|
|
|
|
total: (endStart - totalStart).toFixed(2),
|
|
|
|
|
createDeps: (createDepsEnd - createDepsStart).toFixed(2),
|
|
|
|
|
evaluate: (evaluatedTreeEnd - evaluatedTreeStart).toFixed(2),
|
|
|
|
|
loading: (loadingTreeEnd - loadingTreeStart).toFixed(2),
|
2020-12-29 09:25:56 +00:00
|
|
|
validate: (validateTreeEnd - validateTreeStart).toFixed(2),
|
2020-10-21 04:25:32 +00:00
|
|
|
};
|
2020-12-29 09:25:56 +00:00
|
|
|
LOGS.push({ timeTaken });
|
2020-10-21 04:25:32 +00:00
|
|
|
// dataTreeCache = validated;
|
|
|
|
|
return withoutFunctions;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const addFunctions = (dataTree: DataTree): DataTree => {
|
|
|
|
|
dataTree.actionPaths = [];
|
2020-12-23 14:18:46 +00:00
|
|
|
Object.keys(dataTree).forEach((entityName) => {
|
2020-10-21 04:25:32 +00:00
|
|
|
const entity = dataTree[entityName];
|
2020-12-30 12:58:57 +00:00
|
|
|
if (isValidEntity(entity) && entity.ENTITY_TYPE === ENTITY_TYPE.ACTION) {
|
2020-12-24 04:32:25 +00:00
|
|
|
const runFunction = function(
|
2020-10-21 04:25:32 +00:00
|
|
|
this: DataTreeAction,
|
|
|
|
|
onSuccess: string,
|
|
|
|
|
onError: string,
|
|
|
|
|
params = "",
|
|
|
|
|
) {
|
|
|
|
|
return {
|
|
|
|
|
type: "RUN_ACTION",
|
|
|
|
|
payload: {
|
|
|
|
|
actionId: this.actionId,
|
|
|
|
|
onSuccess: onSuccess ? `{{${onSuccess.toString()}}}` : "",
|
|
|
|
|
onError: onError ? `{{${onError.toString()}}}` : "",
|
|
|
|
|
params,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
_.set(dataTree, `${entityName}.run`, runFunction);
|
|
|
|
|
dataTree.actionPaths && dataTree.actionPaths.push(`${entityName}.run`);
|
|
|
|
|
}
|
|
|
|
|
});
|
2020-12-24 04:32:25 +00:00
|
|
|
dataTree.navigateTo = function(
|
2020-10-21 04:25:32 +00:00
|
|
|
pageNameOrUrl: string,
|
|
|
|
|
params: Record<string, string>,
|
|
|
|
|
) {
|
|
|
|
|
return {
|
|
|
|
|
type: "NAVIGATE_TO",
|
|
|
|
|
payload: { pageNameOrUrl, params },
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
dataTree.actionPaths.push("navigateTo");
|
|
|
|
|
|
2020-12-24 04:32:25 +00:00
|
|
|
dataTree.showAlert = function(message: string, style: string) {
|
2020-10-21 04:25:32 +00:00
|
|
|
return {
|
|
|
|
|
type: "SHOW_ALERT",
|
|
|
|
|
payload: { message, style },
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
dataTree.actionPaths.push("showAlert");
|
|
|
|
|
|
2020-12-24 04:32:25 +00:00
|
|
|
dataTree.showModal = function(modalName: string) {
|
2020-10-21 04:25:32 +00:00
|
|
|
return {
|
|
|
|
|
type: "SHOW_MODAL_BY_NAME",
|
|
|
|
|
payload: { modalName },
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
dataTree.actionPaths.push("showModal");
|
|
|
|
|
|
2020-12-24 04:32:25 +00:00
|
|
|
dataTree.closeModal = function(modalName: string) {
|
2020-10-21 04:25:32 +00:00
|
|
|
return {
|
|
|
|
|
type: "CLOSE_MODAL",
|
|
|
|
|
payload: { modalName },
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
dataTree.actionPaths.push("closeModal");
|
|
|
|
|
|
2020-12-24 04:32:25 +00:00
|
|
|
dataTree.storeValue = function(key: string, value: string) {
|
2020-10-21 04:25:32 +00:00
|
|
|
return {
|
|
|
|
|
type: "STORE_VALUE",
|
|
|
|
|
payload: { key, value },
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
dataTree.actionPaths.push("storeValue");
|
|
|
|
|
|
2020-12-24 04:32:25 +00:00
|
|
|
dataTree.download = function(data: string, name: string, type: string) {
|
2020-10-21 04:25:32 +00:00
|
|
|
return {
|
|
|
|
|
type: "DOWNLOAD",
|
|
|
|
|
payload: { data, name, type },
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
dataTree.actionPaths.push("download");
|
|
|
|
|
return dataTree;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const removeFunctionsFromDataTree = (dataTree: DataTree) => {
|
2020-12-23 14:18:46 +00:00
|
|
|
dataTree.actionPaths?.forEach((functionPath) => {
|
2020-10-21 04:25:32 +00:00
|
|
|
_.set(dataTree, functionPath, {});
|
|
|
|
|
});
|
|
|
|
|
delete dataTree.actionPaths;
|
|
|
|
|
return dataTree;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
const removeFunctions = (value: any) => {
|
|
|
|
|
if (_.isFunction(value)) {
|
|
|
|
|
return "Function call";
|
2020-12-30 12:58:57 +00:00
|
|
|
} else if (_.isObject(value)) {
|
2020-10-21 04:25:32 +00:00
|
|
|
return JSON.parse(JSON.stringify(value));
|
|
|
|
|
} else {
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type DynamicDependencyMap = Record<string, Array<string>>;
|
|
|
|
|
const createDependencyTree = (
|
|
|
|
|
dataTree: DataTree,
|
|
|
|
|
): {
|
|
|
|
|
sortedDependencies: Array<string>;
|
|
|
|
|
dependencyTree: Array<[string, string]>;
|
|
|
|
|
dependencyMap: DynamicDependencyMap;
|
|
|
|
|
} => {
|
2020-12-23 14:18:46 +00:00
|
|
|
let dependencyMap: DynamicDependencyMap = {};
|
2020-10-21 04:25:32 +00:00
|
|
|
const allKeys = getAllPaths(dataTree);
|
2020-12-23 14:18:46 +00:00
|
|
|
Object.keys(dataTree).forEach((entityKey) => {
|
2020-10-21 04:25:32 +00:00
|
|
|
const entity = dataTree[entityKey];
|
2020-12-30 12:58:57 +00:00
|
|
|
if (isValidEntity(entity)) {
|
2020-11-12 11:23:32 +00:00
|
|
|
if (
|
|
|
|
|
entity.ENTITY_TYPE === ENTITY_TYPE.WIDGET ||
|
|
|
|
|
entity.ENTITY_TYPE === ENTITY_TYPE.ACTION
|
|
|
|
|
) {
|
|
|
|
|
const dynamicBindingPathList = getEntityDynamicBindingPathList(entity);
|
|
|
|
|
if (dynamicBindingPathList.length) {
|
2020-12-23 14:18:46 +00:00
|
|
|
dynamicBindingPathList.forEach((dynamicPath) => {
|
2020-11-12 11:23:32 +00:00
|
|
|
const propertyPath = dynamicPath.key;
|
|
|
|
|
const unevalPropValue = _.get(entity, propertyPath);
|
2020-10-21 04:25:32 +00:00
|
|
|
const { jsSnippets } = getDynamicBindings(unevalPropValue);
|
|
|
|
|
const existingDeps =
|
2020-11-12 11:23:32 +00:00
|
|
|
dependencyMap[`${entityKey}.${propertyPath}`] || [];
|
|
|
|
|
dependencyMap[`${entityKey}.${propertyPath}`] = existingDeps.concat(
|
2020-12-23 14:18:46 +00:00
|
|
|
jsSnippets.filter((jsSnippet) => !!jsSnippet),
|
2020-10-21 04:25:32 +00:00
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
}
|
2020-11-12 11:23:32 +00:00
|
|
|
if (entity.ENTITY_TYPE === ENTITY_TYPE.WIDGET) {
|
|
|
|
|
// Set default property dependency
|
|
|
|
|
const defaultProperties =
|
|
|
|
|
WIDGET_TYPE_CONFIG_MAP[entity.type].defaultProperties;
|
2020-12-23 14:18:46 +00:00
|
|
|
Object.keys(defaultProperties).forEach((property) => {
|
2020-11-12 11:23:32 +00:00
|
|
|
dependencyMap[`${entityKey}.${property}`] = [
|
|
|
|
|
`${entityKey}.${defaultProperties[property]}`,
|
|
|
|
|
];
|
2020-10-21 04:25:32 +00:00
|
|
|
});
|
2020-11-12 11:23:32 +00:00
|
|
|
const dynamicTriggerPathList = getWidgetDynamicTriggerPathList(
|
|
|
|
|
entity,
|
|
|
|
|
);
|
|
|
|
|
if (dynamicTriggerPathList.length) {
|
2020-12-23 14:18:46 +00:00
|
|
|
dynamicTriggerPathList.forEach((dynamicPath) => {
|
2020-11-12 11:23:32 +00:00
|
|
|
dependencyMap[`${entityKey}.${dynamicPath.key}`] = [];
|
|
|
|
|
});
|
|
|
|
|
}
|
2020-10-21 04:25:32 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
2020-12-23 14:18:46 +00:00
|
|
|
Object.keys(dependencyMap).forEach((key) => {
|
2020-10-21 04:25:32 +00:00
|
|
|
dependencyMap[key] = _.flatten(
|
2020-12-23 14:18:46 +00:00
|
|
|
dependencyMap[key].map((path) => calculateSubDependencies(path, allKeys)),
|
2020-10-21 04:25:32 +00:00
|
|
|
);
|
|
|
|
|
});
|
2020-12-23 14:18:46 +00:00
|
|
|
dependencyMap = makeParentsDependOnChildren(dependencyMap);
|
2020-10-21 04:25:32 +00:00
|
|
|
const dependencyTree: Array<[string, string]> = [];
|
|
|
|
|
Object.keys(dependencyMap).forEach((key: string) => {
|
|
|
|
|
if (dependencyMap[key].length) {
|
2020-12-23 14:18:46 +00:00
|
|
|
dependencyMap[key].forEach((dep) => dependencyTree.push([key, dep]));
|
2020-10-21 04:25:32 +00:00
|
|
|
} else {
|
|
|
|
|
// Set no dependency
|
|
|
|
|
dependencyTree.push([key, ""]);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// sort dependencies and remove empty dependencies
|
|
|
|
|
const sortedDependencies = toposort(dependencyTree)
|
|
|
|
|
.reverse()
|
2020-12-23 14:18:46 +00:00
|
|
|
.filter((d) => !!d);
|
2020-10-21 04:25:32 +00:00
|
|
|
|
|
|
|
|
return { sortedDependencies, dependencyMap, dependencyTree };
|
|
|
|
|
} catch (e) {
|
|
|
|
|
ERRORS.push({
|
|
|
|
|
type: EvalErrorTypes.DEPENDENCY_ERROR,
|
2020-10-22 13:15:50 +00:00
|
|
|
message: e.message,
|
2020-10-21 04:25:32 +00:00
|
|
|
});
|
2020-12-29 18:27:07 +00:00
|
|
|
throw new Error("Dependency Error");
|
|
|
|
|
//return { sortedDependencies: [], dependencyMap: {}, dependencyTree: [] };
|
2020-10-21 04:25:32 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const calculateSubDependencies = (
|
|
|
|
|
path: string,
|
|
|
|
|
all: Record<string, true>,
|
|
|
|
|
): Array<string> => {
|
|
|
|
|
const subDeps: Array<string> = [];
|
|
|
|
|
const identifiers = path.match(/[a-zA-Z_$][a-zA-Z_$0-9.]*/g) || [path];
|
|
|
|
|
identifiers.forEach((identifier: string) => {
|
|
|
|
|
if (all.hasOwnProperty(identifier)) {
|
|
|
|
|
subDeps.push(identifier);
|
|
|
|
|
} else {
|
|
|
|
|
const subIdentifiers =
|
|
|
|
|
identifier.match(/[a-zA-Z_$][a-zA-Z_$0-9]*/g) || [];
|
|
|
|
|
let current = "";
|
|
|
|
|
for (let i = 0; i < subIdentifiers.length; i++) {
|
|
|
|
|
const key = `${current}${current ? "." : ""}${subIdentifiers[i]}`;
|
|
|
|
|
if (key in all) {
|
|
|
|
|
current = key;
|
|
|
|
|
} else {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (current && current.includes(".")) subDeps.push(current);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return _.uniq(subDeps);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const setTreeLoading = (
|
|
|
|
|
dataTree: DataTree,
|
|
|
|
|
dependencyMap: Array<[string, string]>,
|
|
|
|
|
) => {
|
|
|
|
|
const widgets: string[] = [];
|
|
|
|
|
const isLoadingActions: string[] = [];
|
|
|
|
|
|
|
|
|
|
// Fetch all actions that are in loading state
|
2020-12-23 14:18:46 +00:00
|
|
|
Object.keys(dataTree).forEach((e) => {
|
2020-10-21 04:25:32 +00:00
|
|
|
const entity = dataTree[e];
|
2020-12-30 12:58:57 +00:00
|
|
|
if (isValidEntity(entity)) {
|
2020-10-21 04:25:32 +00:00
|
|
|
if (entity.ENTITY_TYPE === ENTITY_TYPE.WIDGET) {
|
|
|
|
|
widgets.push(e);
|
|
|
|
|
} else if (
|
|
|
|
|
entity.ENTITY_TYPE === ENTITY_TYPE.ACTION &&
|
|
|
|
|
entity.isLoading
|
|
|
|
|
) {
|
|
|
|
|
isLoadingActions.push(e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// get all widget dependencies of those actions
|
|
|
|
|
isLoadingActions
|
|
|
|
|
.reduce(
|
|
|
|
|
(allEntities: string[], curr) =>
|
|
|
|
|
allEntities.concat(getEntityDependencies(dependencyMap, curr, widgets)),
|
|
|
|
|
[],
|
|
|
|
|
)
|
|
|
|
|
// set loading to true for those widgets
|
2020-12-23 14:18:46 +00:00
|
|
|
.forEach((w) => {
|
2020-10-21 04:25:32 +00:00
|
|
|
const entity = dataTree[w] as DataTreeWidget;
|
|
|
|
|
entity.isLoading = true;
|
|
|
|
|
});
|
|
|
|
|
return dataTree;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getEntityDependencies = (
|
|
|
|
|
dependencyMap: Array<[string, string]>,
|
|
|
|
|
entity: string,
|
|
|
|
|
entities: string[],
|
|
|
|
|
): Array<string> => {
|
|
|
|
|
const entityDeps: Record<string, string[]> = dependencyMap
|
2020-12-23 14:18:46 +00:00
|
|
|
.map((d) => [d[1].split(".")[0], d[0].split(".")[0]])
|
|
|
|
|
.filter((d) => d[0] !== d[1])
|
2020-10-21 04:25:32 +00:00
|
|
|
.reduce((deps: Record<string, string[]>, dep) => {
|
|
|
|
|
const key: string = dep[0];
|
|
|
|
|
const value: string = dep[1];
|
|
|
|
|
return {
|
|
|
|
|
...deps,
|
|
|
|
|
[key]: deps[key] ? deps[key].concat(value) : [value],
|
|
|
|
|
};
|
|
|
|
|
}, {});
|
|
|
|
|
|
|
|
|
|
if (entity in entityDeps) {
|
2020-12-23 14:18:46 +00:00
|
|
|
const visited = new Set<string>();
|
2020-10-21 04:25:32 +00:00
|
|
|
const recFind = (
|
|
|
|
|
keys: Array<string>,
|
|
|
|
|
deps: Record<string, string[]>,
|
|
|
|
|
): Array<string> => {
|
|
|
|
|
let allDeps: string[] = [];
|
|
|
|
|
keys
|
2020-12-23 14:18:46 +00:00
|
|
|
.filter((k) => entities.includes(k))
|
|
|
|
|
.forEach((e) => {
|
|
|
|
|
if (visited.has(e)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
visited.add(e);
|
2020-10-21 04:25:32 +00:00
|
|
|
allDeps = allDeps.concat([e]);
|
|
|
|
|
if (e in deps) {
|
|
|
|
|
allDeps = allDeps.concat([...recFind(deps[e], deps)]);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return allDeps;
|
|
|
|
|
};
|
|
|
|
|
return recFind(entityDeps[entity], entityDeps);
|
|
|
|
|
}
|
|
|
|
|
return [];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function dependencySortedEvaluateDataTree(
|
|
|
|
|
dataTree: DataTree,
|
|
|
|
|
dependencyMap: DynamicDependencyMap,
|
|
|
|
|
sortedDependencies: Array<string>,
|
|
|
|
|
): DataTree {
|
|
|
|
|
const tree = _.cloneDeep(dataTree);
|
|
|
|
|
try {
|
|
|
|
|
return sortedDependencies.reduce(
|
|
|
|
|
(currentTree: DataTree, propertyPath: string) => {
|
|
|
|
|
const entityName = propertyPath.split(".")[0];
|
|
|
|
|
const entity: DataTreeEntity = currentTree[entityName];
|
|
|
|
|
const unEvalPropertyValue = _.get(currentTree as any, propertyPath);
|
|
|
|
|
let evalPropertyValue;
|
|
|
|
|
const propertyDependencies = dependencyMap[propertyPath];
|
|
|
|
|
const currentDependencyValues = getCurrentDependencyValues(
|
|
|
|
|
propertyDependencies,
|
|
|
|
|
currentTree,
|
|
|
|
|
propertyPath,
|
|
|
|
|
);
|
|
|
|
|
const cachedDependencyValues = dependencyCache.get(propertyPath);
|
|
|
|
|
const requiresEval = isDynamicValue(unEvalPropertyValue);
|
|
|
|
|
if (requiresEval) {
|
|
|
|
|
try {
|
|
|
|
|
evalPropertyValue = evaluateDynamicProperty(
|
|
|
|
|
propertyPath,
|
|
|
|
|
currentTree,
|
|
|
|
|
unEvalPropertyValue,
|
|
|
|
|
currentDependencyValues,
|
|
|
|
|
cachedDependencyValues,
|
|
|
|
|
);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
ERRORS.push({
|
|
|
|
|
type: EvalErrorTypes.EVAL_PROPERTY_ERROR,
|
2020-10-22 13:15:50 +00:00
|
|
|
message: e.message,
|
2020-10-21 04:25:32 +00:00
|
|
|
context: {
|
|
|
|
|
propertyPath,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
evalPropertyValue = undefined;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
evalPropertyValue = unEvalPropertyValue;
|
|
|
|
|
// If we have stored any previous dependency cache, clear it
|
|
|
|
|
// since it is no longer a binding
|
|
|
|
|
if (cachedDependencyValues && cachedDependencyValues.length) {
|
|
|
|
|
dependencyCache.set(propertyPath, []);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (isWidget(entity)) {
|
|
|
|
|
const widgetEntity: DataTreeWidget = entity as DataTreeWidget;
|
|
|
|
|
const propertyName = propertyPath.split(".")[1];
|
|
|
|
|
if (propertyName) {
|
|
|
|
|
let parsedValue = validateAndParseWidgetProperty(
|
|
|
|
|
propertyPath,
|
|
|
|
|
widgetEntity,
|
|
|
|
|
currentTree,
|
|
|
|
|
evalPropertyValue,
|
|
|
|
|
unEvalPropertyValue,
|
|
|
|
|
currentDependencyValues,
|
|
|
|
|
cachedDependencyValues,
|
|
|
|
|
);
|
|
|
|
|
const defaultPropertyMap =
|
|
|
|
|
WIDGET_TYPE_CONFIG_MAP[widgetEntity.type].defaultProperties;
|
|
|
|
|
const hasDefaultProperty = propertyName in defaultPropertyMap;
|
|
|
|
|
if (hasDefaultProperty) {
|
|
|
|
|
const defaultProperty = defaultPropertyMap[propertyName];
|
|
|
|
|
parsedValue = overwriteDefaultDependentProps(
|
|
|
|
|
defaultProperty,
|
|
|
|
|
parsedValue,
|
|
|
|
|
propertyPath,
|
|
|
|
|
widgetEntity,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return _.set(currentTree, propertyPath, parsedValue);
|
|
|
|
|
}
|
|
|
|
|
return _.set(currentTree, propertyPath, evalPropertyValue);
|
|
|
|
|
} else {
|
|
|
|
|
return _.set(currentTree, propertyPath, evalPropertyValue);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
tree,
|
|
|
|
|
);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
ERRORS.push({
|
|
|
|
|
type: EvalErrorTypes.EVAL_TREE_ERROR,
|
2020-10-22 13:15:50 +00:00
|
|
|
message: e.message,
|
2020-10-21 04:25:32 +00:00
|
|
|
});
|
|
|
|
|
return tree;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const overwriteDefaultDependentProps = (
|
|
|
|
|
defaultProperty: string,
|
|
|
|
|
propertyValue: any,
|
|
|
|
|
propertyPath: string,
|
|
|
|
|
entity: DataTreeWidget,
|
|
|
|
|
) => {
|
|
|
|
|
const defaultPropertyCache = getParsedValueCache(
|
|
|
|
|
`${entity.widgetName}.${defaultProperty}`,
|
|
|
|
|
);
|
|
|
|
|
const propertyCache = getParsedValueCache(propertyPath);
|
2020-12-01 05:26:35 +00:00
|
|
|
|
2020-10-21 04:25:32 +00:00
|
|
|
if (
|
|
|
|
|
propertyValue === undefined ||
|
|
|
|
|
propertyCache.version < defaultPropertyCache.version
|
|
|
|
|
) {
|
|
|
|
|
return defaultPropertyCache.value;
|
|
|
|
|
}
|
|
|
|
|
return propertyValue;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getValidatedTree = (tree: any) => {
|
|
|
|
|
return Object.keys(tree).reduce((tree, entityKey: string) => {
|
|
|
|
|
const entity = tree[entityKey];
|
|
|
|
|
if (entity && entity.type) {
|
|
|
|
|
const parsedEntity = { ...entity };
|
|
|
|
|
Object.keys(entity).forEach((property: string) => {
|
|
|
|
|
const hasEvaluatedValue = _.has(
|
|
|
|
|
parsedEntity,
|
|
|
|
|
`evaluatedValues.${property}`,
|
|
|
|
|
);
|
|
|
|
|
const hasValidation = _.has(parsedEntity, `invalidProps.${property}`);
|
|
|
|
|
const isSpecialField = [
|
2020-11-12 11:23:32 +00:00
|
|
|
"dynamicBindingPathList",
|
|
|
|
|
"dynamicTriggerPathList",
|
|
|
|
|
"dynamicPropertyPathList",
|
2020-10-21 04:25:32 +00:00
|
|
|
"evaluatedValues",
|
|
|
|
|
"invalidProps",
|
|
|
|
|
"validationMessages",
|
|
|
|
|
].includes(property);
|
2020-12-29 18:27:07 +00:00
|
|
|
|
|
|
|
|
if (!isSpecialField && (!hasValidation || !hasEvaluatedValue)) {
|
2020-10-21 04:25:32 +00:00
|
|
|
const value = entity[property];
|
|
|
|
|
// Pass it through parse
|
|
|
|
|
const {
|
|
|
|
|
parsed,
|
|
|
|
|
isValid,
|
|
|
|
|
message,
|
|
|
|
|
transformed,
|
|
|
|
|
} = validateWidgetProperty(
|
|
|
|
|
entity.type,
|
|
|
|
|
property,
|
|
|
|
|
value,
|
|
|
|
|
entity,
|
|
|
|
|
tree,
|
|
|
|
|
);
|
|
|
|
|
parsedEntity[property] = parsed;
|
|
|
|
|
if (!hasEvaluatedValue) {
|
|
|
|
|
const evaluatedValue = isValid
|
|
|
|
|
? parsed
|
|
|
|
|
: _.isUndefined(transformed)
|
|
|
|
|
? value
|
|
|
|
|
: transformed;
|
|
|
|
|
const safeEvaluatedValue = removeFunctions(evaluatedValue);
|
|
|
|
|
_.set(
|
|
|
|
|
parsedEntity,
|
|
|
|
|
`evaluatedValues.${property}`,
|
|
|
|
|
safeEvaluatedValue,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const hasValidation = _.has(parsedEntity, `invalidProps.${property}`);
|
|
|
|
|
if (!hasValidation && !isValid) {
|
|
|
|
|
_.set(parsedEntity, `invalidProps.${property}`, true);
|
|
|
|
|
_.set(parsedEntity, `validationMessages.${property}`, message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return { ...tree, [entityKey]: parsedEntity };
|
|
|
|
|
}
|
|
|
|
|
return tree;
|
|
|
|
|
}, tree);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getAllPaths = (
|
|
|
|
|
tree: Record<string, any>,
|
|
|
|
|
prefix = "",
|
|
|
|
|
): Record<string, true> => {
|
|
|
|
|
return Object.keys(tree).reduce((res: Record<string, true>, el): Record<
|
|
|
|
|
string,
|
|
|
|
|
true
|
|
|
|
|
> => {
|
|
|
|
|
if (Array.isArray(tree[el])) {
|
|
|
|
|
const key = `${prefix}${el}`;
|
|
|
|
|
return { ...res, [key]: true };
|
|
|
|
|
} else if (typeof tree[el] === "object" && tree[el] !== null) {
|
|
|
|
|
const key = `${prefix}${el}`;
|
|
|
|
|
return { ...res, [key]: true, ...getAllPaths(tree[el], `${key}.`) };
|
|
|
|
|
} else {
|
|
|
|
|
const key = `${prefix}${el}`;
|
|
|
|
|
return { ...res, [key]: true };
|
|
|
|
|
}
|
|
|
|
|
}, {});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getDynamicBindings = (
|
|
|
|
|
dynamicString: string,
|
|
|
|
|
): { stringSegments: string[]; jsSnippets: string[] } => {
|
|
|
|
|
// Protect against bad string parse
|
|
|
|
|
if (!dynamicString || !_.isString(dynamicString)) {
|
|
|
|
|
return { stringSegments: [], jsSnippets: [] };
|
|
|
|
|
}
|
|
|
|
|
const sanitisedString = dynamicString.trim();
|
|
|
|
|
// Get the {{binding}} bound values
|
|
|
|
|
const stringSegments = getDynamicStringSegments(sanitisedString);
|
|
|
|
|
// Get the "binding" path values
|
2020-12-23 14:18:46 +00:00
|
|
|
const paths = stringSegments.map((segment) => {
|
2020-10-21 04:25:32 +00:00
|
|
|
const length = segment.length;
|
|
|
|
|
const matches = isDynamicValue(segment);
|
|
|
|
|
if (matches) {
|
|
|
|
|
return segment.substring(2, length - 2);
|
|
|
|
|
}
|
|
|
|
|
return "";
|
|
|
|
|
});
|
|
|
|
|
return { stringSegments: stringSegments, jsSnippets: paths };
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//{{}}{{}}}
|
|
|
|
|
function getDynamicStringSegments(dynamicString: string): string[] {
|
|
|
|
|
let stringSegments = [];
|
|
|
|
|
const indexOfDoubleParanStart = dynamicString.indexOf("{{");
|
|
|
|
|
if (indexOfDoubleParanStart === -1) {
|
|
|
|
|
return [dynamicString];
|
|
|
|
|
}
|
|
|
|
|
//{{}}{{}}}
|
|
|
|
|
const firstString = dynamicString.substring(0, indexOfDoubleParanStart);
|
|
|
|
|
firstString && stringSegments.push(firstString);
|
|
|
|
|
let rest = dynamicString.substring(
|
|
|
|
|
indexOfDoubleParanStart,
|
|
|
|
|
dynamicString.length,
|
|
|
|
|
);
|
|
|
|
|
//{{}}{{}}}
|
|
|
|
|
let sum = 0;
|
|
|
|
|
for (let i = 0; i <= rest.length - 1; i++) {
|
|
|
|
|
const char = rest[i];
|
|
|
|
|
const prevChar = rest[i - 1];
|
|
|
|
|
|
|
|
|
|
if (char === "{") {
|
|
|
|
|
sum++;
|
|
|
|
|
} else if (char === "}") {
|
|
|
|
|
sum--;
|
|
|
|
|
if (prevChar === "}" && sum === 0) {
|
|
|
|
|
stringSegments.push(rest.substring(0, i + 1));
|
|
|
|
|
rest = rest.substring(i + 1, rest.length);
|
|
|
|
|
if (rest) {
|
|
|
|
|
stringSegments = stringSegments.concat(
|
|
|
|
|
getDynamicStringSegments(rest),
|
|
|
|
|
);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (sum !== 0 && dynamicString !== "") {
|
|
|
|
|
return [dynamicString];
|
|
|
|
|
}
|
|
|
|
|
return stringSegments;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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 getCurrentDependencyValues(
|
|
|
|
|
propertyDependencies: Array<string>,
|
|
|
|
|
currentTree: DataTree,
|
|
|
|
|
currentPropertyPath: string,
|
|
|
|
|
): Array<string> {
|
|
|
|
|
return propertyDependencies
|
|
|
|
|
? propertyDependencies
|
|
|
|
|
.map((path: string) => {
|
|
|
|
|
//*** Remove current path from data tree because cached value contains evaluated version while this contains unevaluated version */
|
|
|
|
|
const cleanDataTree = _.omit(currentTree, [currentPropertyPath]);
|
|
|
|
|
return _.get(cleanDataTree, path);
|
|
|
|
|
})
|
|
|
|
|
.filter((data: any) => {
|
|
|
|
|
return data !== undefined;
|
|
|
|
|
})
|
|
|
|
|
: [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const dynamicPropValueCache: Map<
|
|
|
|
|
string,
|
|
|
|
|
{
|
|
|
|
|
unEvaluated: any;
|
|
|
|
|
evaluated: any;
|
|
|
|
|
}
|
|
|
|
|
> = new Map();
|
|
|
|
|
|
|
|
|
|
const parsedValueCache: Map<
|
|
|
|
|
string,
|
|
|
|
|
{
|
|
|
|
|
value: any;
|
|
|
|
|
version: number;
|
|
|
|
|
}
|
|
|
|
|
> = new Map();
|
|
|
|
|
|
|
|
|
|
const getDynamicPropValueCache = (propertyPath: string) =>
|
|
|
|
|
dynamicPropValueCache.get(propertyPath);
|
|
|
|
|
|
|
|
|
|
const getParsedValueCache = (propertyPath: string) =>
|
|
|
|
|
parsedValueCache.get(propertyPath) || {
|
|
|
|
|
value: undefined,
|
|
|
|
|
version: 0,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const clearPropertyCache = (propertyPath: string) =>
|
|
|
|
|
parsedValueCache.delete(propertyPath);
|
|
|
|
|
|
2020-12-01 05:26:35 +00:00
|
|
|
/**
|
|
|
|
|
* delete all values of a particular widget
|
|
|
|
|
*
|
|
|
|
|
* @param propertyPath
|
|
|
|
|
*/
|
|
|
|
|
export const clearPropertyCacheOfWidget = (widgetName: string) => {
|
|
|
|
|
parsedValueCache.forEach((value, key) => {
|
|
|
|
|
const match = key.match(`${widgetName}.`);
|
|
|
|
|
|
|
|
|
|
if (match) return parsedValueCache.delete(key);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2020-10-21 04:25:32 +00:00
|
|
|
const dependencyCache: Map<string, any[]> = new Map();
|
|
|
|
|
|
2020-12-30 12:58:57 +00:00
|
|
|
function isValidEntity(entity: DataTreeEntity): entity is DataTreeObjectEntity {
|
|
|
|
|
if (!_.isObject(entity)) {
|
|
|
|
|
ERRORS.push({
|
|
|
|
|
type: EvalErrorTypes.BAD_UNEVAL_TREE_ERROR,
|
|
|
|
|
message: "Data tree entity is not an object",
|
|
|
|
|
context: entity,
|
|
|
|
|
});
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return "ENTITY_TYPE" in entity;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-21 04:25:32 +00:00
|
|
|
function isWidget(entity: DataTreeEntity): boolean {
|
2020-12-30 12:58:57 +00:00
|
|
|
return isValidEntity(entity) && entity.ENTITY_TYPE === ENTITY_TYPE.WIDGET;
|
2020-10-21 04:25:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function validateAndParseWidgetProperty(
|
|
|
|
|
propertyPath: string,
|
|
|
|
|
widget: DataTreeWidget,
|
|
|
|
|
currentTree: DataTree,
|
|
|
|
|
evalPropertyValue: any,
|
|
|
|
|
unEvalPropertyValue: string,
|
|
|
|
|
currentDependencyValues: Array<string>,
|
|
|
|
|
cachedDependencyValues?: Array<string>,
|
|
|
|
|
): any {
|
2020-11-12 11:23:32 +00:00
|
|
|
const entityPropertyName = _.drop(propertyPath.split(".")).join(".");
|
2020-10-21 04:25:32 +00:00
|
|
|
let valueToValidate = evalPropertyValue;
|
2020-11-12 11:23:32 +00:00
|
|
|
if (isPathADynamicTrigger(widget, propertyPath)) {
|
2020-10-21 04:25:32 +00:00
|
|
|
const { triggers } = getDynamicValue(
|
|
|
|
|
unEvalPropertyValue,
|
|
|
|
|
currentTree,
|
|
|
|
|
true,
|
|
|
|
|
undefined,
|
|
|
|
|
);
|
|
|
|
|
valueToValidate = triggers;
|
|
|
|
|
}
|
|
|
|
|
const { parsed, isValid, message, transformed } = validateWidgetProperty(
|
|
|
|
|
widget.type,
|
2020-11-12 11:23:32 +00:00
|
|
|
entityPropertyName,
|
2020-10-21 04:25:32 +00:00
|
|
|
valueToValidate,
|
|
|
|
|
widget,
|
|
|
|
|
currentTree,
|
|
|
|
|
);
|
|
|
|
|
const evaluatedValue = isValid
|
|
|
|
|
? parsed
|
|
|
|
|
: _.isUndefined(transformed)
|
|
|
|
|
? evalPropertyValue
|
|
|
|
|
: transformed;
|
|
|
|
|
const safeEvaluatedValue = removeFunctions(evaluatedValue);
|
2020-11-12 11:23:32 +00:00
|
|
|
_.set(widget, `evaluatedValues.${entityPropertyName}`, safeEvaluatedValue);
|
2020-10-21 04:25:32 +00:00
|
|
|
if (!isValid) {
|
2020-11-12 11:23:32 +00:00
|
|
|
_.set(widget, `invalidProps.${entityPropertyName}`, true);
|
|
|
|
|
_.set(widget, `validationMessages.${entityPropertyName}`, message);
|
2020-10-21 04:25:32 +00:00
|
|
|
}
|
|
|
|
|
|
2020-11-12 11:23:32 +00:00
|
|
|
if (isPathADynamicTrigger(widget, entityPropertyName)) {
|
2020-10-21 04:25:32 +00:00
|
|
|
return unEvalPropertyValue;
|
|
|
|
|
} else {
|
|
|
|
|
const parsedCache = getParsedValueCache(propertyPath);
|
|
|
|
|
if (
|
|
|
|
|
!equal(parsedCache.value, parsed) ||
|
|
|
|
|
(cachedDependencyValues !== undefined &&
|
|
|
|
|
!equal(currentDependencyValues, cachedDependencyValues))
|
|
|
|
|
) {
|
|
|
|
|
parsedValueCache.set(propertyPath, {
|
|
|
|
|
value: parsed,
|
|
|
|
|
version: Date.now(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return parsed;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function evaluateDynamicProperty(
|
|
|
|
|
propertyPath: string,
|
|
|
|
|
currentTree: DataTree,
|
|
|
|
|
unEvalPropertyValue: any,
|
|
|
|
|
currentDependencyValues: Array<string>,
|
|
|
|
|
cachedDependencyValues?: Array<string>,
|
|
|
|
|
): any {
|
|
|
|
|
const cacheObj = getDynamicPropValueCache(propertyPath);
|
|
|
|
|
const isCacheHit =
|
|
|
|
|
cacheObj &&
|
|
|
|
|
equal(cacheObj.unEvaluated, unEvalPropertyValue) &&
|
|
|
|
|
cachedDependencyValues !== undefined &&
|
|
|
|
|
equal(currentDependencyValues, cachedDependencyValues);
|
|
|
|
|
if (isCacheHit && cacheObj) {
|
|
|
|
|
return cacheObj.evaluated;
|
|
|
|
|
} else {
|
2020-12-29 09:25:56 +00:00
|
|
|
LOGS.push("eval " + propertyPath);
|
2020-10-21 04:25:32 +00:00
|
|
|
const dynamicResult = getDynamicValue(
|
|
|
|
|
unEvalPropertyValue,
|
|
|
|
|
currentTree,
|
|
|
|
|
false,
|
|
|
|
|
);
|
|
|
|
|
dynamicPropValueCache.set(propertyPath, {
|
|
|
|
|
evaluated: dynamicResult,
|
|
|
|
|
unEvaluated: unEvalPropertyValue,
|
|
|
|
|
});
|
|
|
|
|
dependencyCache.set(propertyPath, currentDependencyValues);
|
|
|
|
|
return dynamicResult;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type EvalResult = {
|
|
|
|
|
result: any;
|
|
|
|
|
triggers?: ActionDescription<any>[];
|
|
|
|
|
};
|
|
|
|
|
// Paths are expected to have "{name}.{path}" signature
|
|
|
|
|
// Also returns any action triggers found after evaluating value
|
|
|
|
|
const evaluateDynamicBoundValue = (
|
|
|
|
|
data: DataTree,
|
|
|
|
|
path: string,
|
2020-11-20 09:30:50 +00:00
|
|
|
callbackData?: Array<any>,
|
2020-10-21 04:25:32 +00:00
|
|
|
): EvalResult => {
|
|
|
|
|
try {
|
|
|
|
|
const unescapedJS = unescapeJS(path).replace(/(\r\n|\n|\r)/gm, "");
|
|
|
|
|
return evaluate(unescapedJS, data, callbackData);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
ERRORS.push({
|
|
|
|
|
type: EvalErrorTypes.UNESCAPE_STRING_ERROR,
|
2020-10-22 13:15:50 +00:00
|
|
|
message: e.message,
|
2020-10-21 04:25:32 +00:00
|
|
|
context: {
|
|
|
|
|
path,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
return { result: undefined, triggers: [] };
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const evaluate = (
|
|
|
|
|
js: string,
|
|
|
|
|
data: DataTree,
|
2020-11-20 09:30:50 +00:00
|
|
|
callbackData?: Array<any>,
|
2020-10-21 04:25:32 +00:00
|
|
|
): EvalResult => {
|
|
|
|
|
const scriptToEvaluate = `
|
|
|
|
|
function closedFunction () {
|
|
|
|
|
const result = ${js};
|
|
|
|
|
return { result, triggers: self.triggers }
|
|
|
|
|
}
|
|
|
|
|
closedFunction()
|
|
|
|
|
`;
|
|
|
|
|
const scriptWithCallback = `
|
|
|
|
|
function callback (script) {
|
|
|
|
|
const userFunction = script;
|
2020-11-20 09:30:50 +00:00
|
|
|
const result = userFunction.apply(self, CALLBACK_DATA);
|
2020-10-21 04:25:32 +00:00
|
|
|
return { result, triggers: self.triggers };
|
|
|
|
|
}
|
|
|
|
|
callback(${js});
|
|
|
|
|
`;
|
|
|
|
|
const script = callbackData ? scriptWithCallback : scriptToEvaluate;
|
|
|
|
|
try {
|
2020-12-24 04:32:25 +00:00
|
|
|
const { result, triggers } = (function() {
|
2020-10-21 04:25:32 +00:00
|
|
|
/**** Setting the eval context ****/
|
2020-10-22 13:15:50 +00:00
|
|
|
const GLOBAL_DATA: Record<string, any> = {};
|
2020-10-21 04:25:32 +00:00
|
|
|
///// Adding callback data
|
2020-10-22 13:15:50 +00:00
|
|
|
GLOBAL_DATA.CALLBACK_DATA = callbackData;
|
2020-10-21 04:25:32 +00:00
|
|
|
///// Adding Data tree
|
2020-12-23 14:18:46 +00:00
|
|
|
Object.keys(data).forEach((datum) => {
|
2020-10-22 13:15:50 +00:00
|
|
|
GLOBAL_DATA[datum] = data[datum];
|
2020-10-21 04:25:32 +00:00
|
|
|
});
|
|
|
|
|
///// Fixing action paths and capturing their execution response
|
|
|
|
|
if (data.actionPaths) {
|
2020-10-22 13:15:50 +00:00
|
|
|
GLOBAL_DATA.triggers = [];
|
2020-12-24 04:32:25 +00:00
|
|
|
const pusher = function(
|
2020-10-21 04:25:32 +00:00
|
|
|
this: DataTree,
|
|
|
|
|
action: any,
|
|
|
|
|
...payload: any[]
|
|
|
|
|
) {
|
|
|
|
|
const actionPayload = action(...payload);
|
2020-10-22 13:15:50 +00:00
|
|
|
GLOBAL_DATA.triggers.push(actionPayload);
|
2020-10-21 04:25:32 +00:00
|
|
|
};
|
2020-10-22 13:15:50 +00:00
|
|
|
GLOBAL_DATA.actionPaths.forEach((path: string) => {
|
|
|
|
|
const action = _.get(GLOBAL_DATA, path);
|
|
|
|
|
const entity = _.get(GLOBAL_DATA, path.split(".")[0]);
|
2020-10-21 04:25:32 +00:00
|
|
|
if (action) {
|
2020-10-22 13:15:50 +00:00
|
|
|
_.set(GLOBAL_DATA, path, pusher.bind(data, action.bind(entity)));
|
2020-10-21 04:25:32 +00:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2020-10-22 13:15:50 +00:00
|
|
|
|
|
|
|
|
// Set it to self
|
2020-12-23 14:18:46 +00:00
|
|
|
Object.keys(GLOBAL_DATA).forEach((key) => {
|
2020-11-03 13:05:40 +00:00
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
|
|
|
// @ts-ignore: No types available
|
2020-10-22 13:15:50 +00:00
|
|
|
self[key] = GLOBAL_DATA[key];
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
///// Adding extra libraries separately
|
2020-12-23 14:18:46 +00:00
|
|
|
extraLibraries.forEach((library) => {
|
2020-11-03 13:05:40 +00:00
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
|
|
|
// @ts-ignore: No types available
|
2020-10-21 04:25:32 +00:00
|
|
|
self[library.accessor] = library.lib;
|
|
|
|
|
});
|
2020-10-22 13:15:50 +00:00
|
|
|
|
2020-12-10 08:30:49 +00:00
|
|
|
///// Remove all unsafe functions
|
2020-12-23 14:18:46 +00:00
|
|
|
unsafeFunctionForEval.forEach((func) => {
|
2020-12-10 08:30:49 +00:00
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
|
|
|
// @ts-ignore: No types available
|
|
|
|
|
self[func] = undefined;
|
|
|
|
|
});
|
|
|
|
|
|
2020-10-22 13:15:50 +00:00
|
|
|
const evalResult = eval(script);
|
|
|
|
|
|
|
|
|
|
// Remove it from self
|
|
|
|
|
// This is needed so that next eval can have a clean sheet
|
2020-12-23 14:18:46 +00:00
|
|
|
Object.keys(GLOBAL_DATA).forEach((key) => {
|
2020-11-03 13:05:40 +00:00
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
|
|
|
// @ts-ignore: No types available
|
2020-10-22 13:15:50 +00:00
|
|
|
delete self[key];
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return evalResult;
|
2020-10-21 04:25:32 +00:00
|
|
|
})();
|
|
|
|
|
return { result, triggers };
|
|
|
|
|
} catch (e) {
|
|
|
|
|
ERRORS.push({
|
|
|
|
|
type: EvalErrorTypes.EVAL_ERROR,
|
2020-10-22 13:15:50 +00:00
|
|
|
message: e.message,
|
2020-10-21 04:25:32 +00:00
|
|
|
context: {
|
|
|
|
|
binding: js,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
return { result: undefined, triggers: [] };
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// For creating a final value where bindings could be in a template format
|
|
|
|
|
const createDynamicValueString = (
|
|
|
|
|
binding: string,
|
|
|
|
|
subBindings: string[],
|
|
|
|
|
subValues: string[],
|
|
|
|
|
): string => {
|
|
|
|
|
// Replace the string with the data tree values
|
|
|
|
|
let finalValue = binding;
|
|
|
|
|
subBindings.forEach((b, i) => {
|
|
|
|
|
let value = subValues[i];
|
|
|
|
|
if (Array.isArray(value) || _.isObject(value)) {
|
|
|
|
|
value = JSON.stringify(value);
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
if (JSON.parse(value)) {
|
|
|
|
|
value = value.replace(/\\([\s\S])|(")/g, "\\$1$2");
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
// do nothing
|
|
|
|
|
}
|
|
|
|
|
finalValue = finalValue.replace(b, value);
|
|
|
|
|
});
|
|
|
|
|
return finalValue;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getDynamicValue = (
|
|
|
|
|
dynamicBinding: string,
|
|
|
|
|
data: DataTree,
|
|
|
|
|
returnTriggers: boolean,
|
2020-11-20 09:30:50 +00:00
|
|
|
callBackData?: Array<any>,
|
2020-10-21 04:25:32 +00:00
|
|
|
) => {
|
|
|
|
|
// Get the {{binding}} bound values
|
|
|
|
|
const { stringSegments, jsSnippets } = getDynamicBindings(dynamicBinding);
|
|
|
|
|
if (returnTriggers) {
|
|
|
|
|
const result = evaluateDynamicBoundValue(data, jsSnippets[0], callBackData);
|
|
|
|
|
return result.triggers;
|
|
|
|
|
}
|
|
|
|
|
if (stringSegments.length) {
|
|
|
|
|
// Get the Data Tree value of those "binding "paths
|
|
|
|
|
const values = jsSnippets.map((jsSnippet, index) => {
|
|
|
|
|
if (jsSnippet) {
|
|
|
|
|
const result = evaluateDynamicBoundValue(data, jsSnippet, callBackData);
|
|
|
|
|
return result.result;
|
|
|
|
|
} else {
|
|
|
|
|
return stringSegments[index];
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// if it is just one binding, no need to create template string
|
|
|
|
|
if (stringSegments.length === 1) return values[0];
|
|
|
|
|
// else return a string template with bindings
|
|
|
|
|
return createDynamicValueString(dynamicBinding, stringSegments, values);
|
|
|
|
|
}
|
|
|
|
|
return undefined;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const validateWidgetProperty = (
|
|
|
|
|
widgetType: WidgetType,
|
|
|
|
|
property: string,
|
|
|
|
|
value: any,
|
|
|
|
|
props: WidgetProps,
|
|
|
|
|
dataTree?: DataTree,
|
|
|
|
|
) => {
|
|
|
|
|
const propertyValidationTypes =
|
|
|
|
|
WIDGET_TYPE_CONFIG_MAP[widgetType].validations;
|
|
|
|
|
const validationTypeOrValidator = propertyValidationTypes[property];
|
|
|
|
|
let validator;
|
|
|
|
|
|
|
|
|
|
if (typeof validationTypeOrValidator === "function") {
|
|
|
|
|
validator = validationTypeOrValidator;
|
|
|
|
|
} else {
|
|
|
|
|
validator = VALIDATORS[validationTypeOrValidator];
|
|
|
|
|
}
|
|
|
|
|
if (validator) {
|
|
|
|
|
return validator(value, props, dataTree);
|
|
|
|
|
} else {
|
|
|
|
|
return { isValid: true, parsed: value };
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const clearCaches = () => {
|
|
|
|
|
dynamicPropValueCache.clear();
|
|
|
|
|
dependencyCache.clear();
|
|
|
|
|
parsedValueCache.clear();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const VALIDATORS: Record<ValidationType, Validator> = {
|
|
|
|
|
[VALIDATION_TYPES.TEXT]: (
|
|
|
|
|
value: any,
|
|
|
|
|
props: WidgetProps,
|
|
|
|
|
dataTree?: DataTree,
|
|
|
|
|
): ValidationResponse => {
|
|
|
|
|
let parsed = value;
|
|
|
|
|
if (isUndefined(value) || value === null) {
|
|
|
|
|
return {
|
|
|
|
|
isValid: true,
|
|
|
|
|
parsed: value,
|
|
|
|
|
message: "",
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
if (isObject(value)) {
|
|
|
|
|
return {
|
|
|
|
|
isValid: false,
|
|
|
|
|
parsed: JSON.stringify(value, null, 2),
|
|
|
|
|
message: `${WIDGET_TYPE_VALIDATION_ERROR}: text`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
let isValid = isString(value);
|
|
|
|
|
if (!isValid) {
|
|
|
|
|
try {
|
|
|
|
|
parsed = toString(value);
|
|
|
|
|
isValid = true;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error(`Error when parsing ${value} to string`);
|
|
|
|
|
console.error(e);
|
|
|
|
|
return {
|
|
|
|
|
isValid: false,
|
|
|
|
|
parsed: "",
|
|
|
|
|
message: `${WIDGET_TYPE_VALIDATION_ERROR}: text`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return { isValid, parsed };
|
|
|
|
|
},
|
|
|
|
|
[VALIDATION_TYPES.REGEX]: (
|
|
|
|
|
value: any,
|
|
|
|
|
props: WidgetProps,
|
|
|
|
|
dataTree?: DataTree,
|
|
|
|
|
): ValidationResponse => {
|
|
|
|
|
const { isValid, parsed, message } = VALIDATORS[VALIDATION_TYPES.TEXT](
|
|
|
|
|
value,
|
|
|
|
|
props,
|
|
|
|
|
dataTree,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (isValid) {
|
|
|
|
|
try {
|
|
|
|
|
new RegExp(parsed);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
return {
|
|
|
|
|
isValid: false,
|
|
|
|
|
parsed: parsed,
|
|
|
|
|
message: `${WIDGET_TYPE_VALIDATION_ERROR}: regex`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { isValid, parsed, message };
|
|
|
|
|
},
|
|
|
|
|
[VALIDATION_TYPES.NUMBER]: (
|
|
|
|
|
value: any,
|
|
|
|
|
props: WidgetProps,
|
|
|
|
|
dataTree?: DataTree,
|
|
|
|
|
): ValidationResponse => {
|
|
|
|
|
let parsed = value;
|
|
|
|
|
if (isUndefined(value)) {
|
|
|
|
|
return {
|
|
|
|
|
isValid: false,
|
|
|
|
|
parsed: 0,
|
|
|
|
|
message: `${WIDGET_TYPE_VALIDATION_ERROR}: number`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
let isValid = isNumber(value);
|
|
|
|
|
if (!isValid) {
|
|
|
|
|
try {
|
|
|
|
|
parsed = toNumber(value);
|
|
|
|
|
if (isNaN(parsed)) {
|
|
|
|
|
return {
|
|
|
|
|
isValid: false,
|
|
|
|
|
parsed: 0,
|
|
|
|
|
message: `${WIDGET_TYPE_VALIDATION_ERROR}: number`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
isValid = true;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error(`Error when parsing ${value} to number`);
|
|
|
|
|
console.error(e);
|
|
|
|
|
return {
|
|
|
|
|
isValid: false,
|
|
|
|
|
parsed: 0,
|
|
|
|
|
message: `${WIDGET_TYPE_VALIDATION_ERROR}: number`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return { isValid, parsed };
|
|
|
|
|
},
|
|
|
|
|
[VALIDATION_TYPES.BOOLEAN]: (
|
|
|
|
|
value: any,
|
|
|
|
|
props: WidgetProps,
|
|
|
|
|
dataTree?: DataTree,
|
|
|
|
|
): ValidationResponse => {
|
|
|
|
|
let parsed = value;
|
|
|
|
|
if (isUndefined(value)) {
|
|
|
|
|
return {
|
|
|
|
|
isValid: false,
|
|
|
|
|
parsed: false,
|
|
|
|
|
message: `${WIDGET_TYPE_VALIDATION_ERROR}: boolean`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
const isABoolean = isBoolean(value);
|
|
|
|
|
const isStringTrueFalse = value === "true" || value === "false";
|
|
|
|
|
const isValid = isABoolean || isStringTrueFalse;
|
|
|
|
|
if (isStringTrueFalse) parsed = value !== "false";
|
|
|
|
|
if (!isValid) {
|
|
|
|
|
return {
|
|
|
|
|
isValid: isValid,
|
|
|
|
|
parsed: parsed,
|
|
|
|
|
message: `${WIDGET_TYPE_VALIDATION_ERROR}: boolean`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return { isValid, parsed };
|
|
|
|
|
},
|
|
|
|
|
[VALIDATION_TYPES.OBJECT]: (
|
|
|
|
|
value: any,
|
|
|
|
|
props: WidgetProps,
|
|
|
|
|
dataTree?: DataTree,
|
|
|
|
|
): ValidationResponse => {
|
|
|
|
|
let parsed = value;
|
|
|
|
|
if (isUndefined(value)) {
|
|
|
|
|
return {
|
|
|
|
|
isValid: false,
|
|
|
|
|
parsed: {},
|
|
|
|
|
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Object`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
let isValid = isObject(value);
|
|
|
|
|
if (!isValid) {
|
|
|
|
|
try {
|
|
|
|
|
parsed = JSON.parse(value);
|
|
|
|
|
isValid = true;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error(`Error when parsing ${value} to object`);
|
|
|
|
|
console.error(e);
|
|
|
|
|
return {
|
|
|
|
|
isValid: false,
|
|
|
|
|
parsed: {},
|
|
|
|
|
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Object`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return { isValid, parsed };
|
|
|
|
|
},
|
|
|
|
|
[VALIDATION_TYPES.ARRAY]: (
|
|
|
|
|
value: any,
|
|
|
|
|
props: WidgetProps,
|
|
|
|
|
dataTree?: DataTree,
|
|
|
|
|
): ValidationResponse => {
|
|
|
|
|
let parsed = value;
|
|
|
|
|
try {
|
|
|
|
|
if (isUndefined(value)) {
|
|
|
|
|
return {
|
|
|
|
|
isValid: false,
|
|
|
|
|
parsed: [],
|
|
|
|
|
transformed: undefined,
|
|
|
|
|
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Array/List`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
if (isString(value)) {
|
|
|
|
|
parsed = JSON.parse(parsed as string);
|
|
|
|
|
}
|
|
|
|
|
if (!Array.isArray(parsed)) {
|
|
|
|
|
return {
|
|
|
|
|
isValid: false,
|
|
|
|
|
parsed: [],
|
|
|
|
|
transformed: parsed,
|
|
|
|
|
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Array/List`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return { isValid: true, parsed, transformed: parsed };
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error(e);
|
|
|
|
|
return {
|
|
|
|
|
isValid: false,
|
|
|
|
|
parsed: [],
|
|
|
|
|
transformed: parsed,
|
|
|
|
|
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Array/List`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[VALIDATION_TYPES.TABS_DATA]: (
|
|
|
|
|
value: any,
|
|
|
|
|
props: WidgetProps,
|
|
|
|
|
dataTree?: DataTree,
|
|
|
|
|
): ValidationResponse => {
|
|
|
|
|
const { isValid, parsed } = VALIDATORS[VALIDATION_TYPES.ARRAY](
|
|
|
|
|
value,
|
|
|
|
|
props,
|
|
|
|
|
dataTree,
|
|
|
|
|
);
|
|
|
|
|
if (!isValid) {
|
|
|
|
|
return {
|
|
|
|
|
isValid,
|
|
|
|
|
parsed,
|
|
|
|
|
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Tabs Data`,
|
|
|
|
|
};
|
2020-12-23 14:18:46 +00:00
|
|
|
} else if (!every(parsed, (datum) => isObject(datum))) {
|
2020-10-21 04:25:32 +00:00
|
|
|
return {
|
|
|
|
|
isValid: false,
|
|
|
|
|
parsed: [],
|
|
|
|
|
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Tabs Data`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return { isValid, parsed };
|
|
|
|
|
},
|
|
|
|
|
[VALIDATION_TYPES.TABLE_DATA]: (
|
|
|
|
|
value: any,
|
|
|
|
|
props: WidgetProps,
|
|
|
|
|
dataTree?: DataTree,
|
|
|
|
|
): ValidationResponse => {
|
|
|
|
|
const { isValid, transformed, parsed } = VALIDATORS.ARRAY(
|
|
|
|
|
value,
|
|
|
|
|
props,
|
|
|
|
|
dataTree,
|
|
|
|
|
);
|
|
|
|
|
if (!isValid) {
|
|
|
|
|
return {
|
|
|
|
|
isValid,
|
|
|
|
|
parsed: [],
|
|
|
|
|
transformed,
|
|
|
|
|
message: `${WIDGET_TYPE_VALIDATION_ERROR}: [{ "Col1" : "val1", "Col2" : "val2" }]`,
|
|
|
|
|
};
|
|
|
|
|
}
|
2020-12-23 14:18:46 +00:00
|
|
|
const isValidTableData = every(parsed, (datum) => {
|
2020-10-21 04:25:32 +00:00
|
|
|
return (
|
2020-12-23 04:17:13 +00:00
|
|
|
isPlainObject(datum) &&
|
2020-12-23 14:18:46 +00:00
|
|
|
Object.keys(datum).filter((key) => isString(key) && key.length === 0)
|
2020-10-21 04:25:32 +00:00
|
|
|
.length === 0
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
if (!isValidTableData) {
|
|
|
|
|
return {
|
|
|
|
|
isValid: false,
|
|
|
|
|
parsed: [],
|
|
|
|
|
transformed,
|
|
|
|
|
message: `${WIDGET_TYPE_VALIDATION_ERROR}: [{ "Col1" : "val1", "Col2" : "val2" }]`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return { isValid, parsed };
|
|
|
|
|
},
|
|
|
|
|
[VALIDATION_TYPES.CHART_DATA]: (
|
|
|
|
|
value: any,
|
|
|
|
|
props: WidgetProps,
|
|
|
|
|
dataTree?: DataTree,
|
|
|
|
|
): ValidationResponse => {
|
|
|
|
|
const { isValid, parsed } = VALIDATORS[VALIDATION_TYPES.ARRAY](
|
|
|
|
|
value,
|
|
|
|
|
props,
|
|
|
|
|
dataTree,
|
|
|
|
|
);
|
|
|
|
|
if (!isValid) {
|
|
|
|
|
return {
|
|
|
|
|
isValid,
|
|
|
|
|
parsed,
|
|
|
|
|
transformed: parsed,
|
|
|
|
|
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Chart Data`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
let validationMessage = "";
|
|
|
|
|
let index = 0;
|
|
|
|
|
const isValidChartData = every(
|
|
|
|
|
parsed,
|
|
|
|
|
(datum: { name: string; data: any }) => {
|
|
|
|
|
const validatedResponse: {
|
|
|
|
|
isValid: boolean;
|
|
|
|
|
parsed: Array<unknown>;
|
|
|
|
|
message?: string;
|
|
|
|
|
} = VALIDATORS[VALIDATION_TYPES.ARRAY](datum.data, props, dataTree);
|
|
|
|
|
validationMessage = `${index}##${WIDGET_TYPE_VALIDATION_ERROR}: [{ "x": "val", "y": "val" }]`;
|
|
|
|
|
let isValidChart = validatedResponse.isValid;
|
|
|
|
|
if (validatedResponse.isValid) {
|
|
|
|
|
datum.data = validatedResponse.parsed;
|
|
|
|
|
isValidChart = every(
|
|
|
|
|
datum.data,
|
|
|
|
|
(chartPoint: { x: string; y: any }) => {
|
|
|
|
|
return (
|
|
|
|
|
isObject(chartPoint) &&
|
|
|
|
|
isString(chartPoint.x) &&
|
|
|
|
|
!isUndefined(chartPoint.y)
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
index++;
|
|
|
|
|
return isValidChart;
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
if (!isValidChartData) {
|
|
|
|
|
return {
|
|
|
|
|
isValid: false,
|
|
|
|
|
parsed: [],
|
|
|
|
|
transformed: parsed,
|
|
|
|
|
message: validationMessage,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return { isValid, parsed, transformed: parsed };
|
|
|
|
|
},
|
|
|
|
|
[VALIDATION_TYPES.MARKERS]: (
|
|
|
|
|
value: any,
|
|
|
|
|
props: WidgetProps,
|
|
|
|
|
dataTree?: DataTree,
|
|
|
|
|
): ValidationResponse => {
|
|
|
|
|
const { isValid, parsed } = VALIDATORS[VALIDATION_TYPES.ARRAY](
|
|
|
|
|
value,
|
|
|
|
|
props,
|
|
|
|
|
dataTree,
|
|
|
|
|
);
|
|
|
|
|
if (!isValid) {
|
|
|
|
|
return {
|
|
|
|
|
isValid,
|
|
|
|
|
parsed,
|
|
|
|
|
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Marker Data`,
|
|
|
|
|
};
|
2020-12-23 14:18:46 +00:00
|
|
|
} else if (!every(parsed, (datum) => isObject(datum))) {
|
2020-10-21 04:25:32 +00:00
|
|
|
return {
|
|
|
|
|
isValid: false,
|
|
|
|
|
parsed: [],
|
|
|
|
|
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Marker Data`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return { isValid, parsed };
|
|
|
|
|
},
|
|
|
|
|
[VALIDATION_TYPES.OPTIONS_DATA]: (
|
|
|
|
|
value: any,
|
|
|
|
|
props: WidgetProps,
|
|
|
|
|
dataTree?: DataTree,
|
|
|
|
|
): ValidationResponse => {
|
|
|
|
|
const { isValid, parsed } = VALIDATORS[VALIDATION_TYPES.ARRAY](
|
|
|
|
|
value,
|
|
|
|
|
props,
|
|
|
|
|
dataTree,
|
|
|
|
|
);
|
|
|
|
|
if (!isValid) {
|
|
|
|
|
return {
|
|
|
|
|
isValid,
|
|
|
|
|
parsed,
|
|
|
|
|
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Options Data`,
|
|
|
|
|
};
|
|
|
|
|
}
|
2020-11-11 11:32:14 +00:00
|
|
|
try {
|
|
|
|
|
const isValidOption = (option: { label: any; value: any }) =>
|
|
|
|
|
_.isObject(option) &&
|
|
|
|
|
_.isString(option.label) &&
|
|
|
|
|
_.isString(option.value) &&
|
|
|
|
|
!_.isEmpty(option.label) &&
|
|
|
|
|
!_.isEmpty(option.value);
|
|
|
|
|
|
|
|
|
|
const hasOptions = every(parsed, isValidOption);
|
|
|
|
|
const validOptions = parsed.filter(isValidOption);
|
|
|
|
|
const uniqValidOptions = _.uniqBy(validOptions, "value");
|
|
|
|
|
|
|
|
|
|
if (!hasOptions || uniqValidOptions.length !== validOptions.length) {
|
|
|
|
|
return {
|
|
|
|
|
isValid: false,
|
|
|
|
|
parsed: uniqValidOptions,
|
|
|
|
|
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Options Data`,
|
|
|
|
|
};
|
2020-10-21 04:25:32 +00:00
|
|
|
}
|
2020-11-11 11:32:14 +00:00
|
|
|
return { isValid, parsed };
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error(e);
|
2020-10-21 04:25:32 +00:00
|
|
|
return {
|
|
|
|
|
isValid: false,
|
2020-11-11 11:32:14 +00:00
|
|
|
parsed: [],
|
|
|
|
|
transformed: parsed,
|
2020-10-21 04:25:32 +00:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[VALIDATION_TYPES.DATE]: (
|
|
|
|
|
dateString: string,
|
|
|
|
|
props: WidgetProps,
|
|
|
|
|
dataTree?: DataTree,
|
|
|
|
|
): ValidationResponse => {
|
2020-12-24 04:32:25 +00:00
|
|
|
const today = moment()
|
|
|
|
|
.hour(0)
|
|
|
|
|
.minute(0)
|
|
|
|
|
.second(0)
|
|
|
|
|
.millisecond(0);
|
2020-10-21 04:25:32 +00:00
|
|
|
const dateFormat = props.dateFormat ? props.dateFormat : ISO_DATE_FORMAT;
|
|
|
|
|
|
|
|
|
|
const todayDateString = today.format(dateFormat);
|
|
|
|
|
if (dateString === undefined) {
|
|
|
|
|
return {
|
|
|
|
|
isValid: false,
|
|
|
|
|
parsed: "",
|
|
|
|
|
message:
|
|
|
|
|
`${WIDGET_TYPE_VALIDATION_ERROR}: Date ` + props.dateFormat
|
|
|
|
|
? props.dateFormat
|
|
|
|
|
: "",
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
const isValid = moment(dateString, dateFormat).isValid();
|
|
|
|
|
const parsed = isValid ? dateString : todayDateString;
|
|
|
|
|
return {
|
|
|
|
|
isValid,
|
|
|
|
|
parsed,
|
|
|
|
|
message: isValid ? "" : `${WIDGET_TYPE_VALIDATION_ERROR}: Date`,
|
|
|
|
|
};
|
|
|
|
|
},
|
2020-11-27 08:48:38 +00:00
|
|
|
[VALIDATION_TYPES.DEFAULT_DATE]: (
|
|
|
|
|
dateString: string,
|
|
|
|
|
props: WidgetProps,
|
|
|
|
|
dataTree?: DataTree,
|
|
|
|
|
): ValidationResponse => {
|
2020-12-24 04:32:25 +00:00
|
|
|
const today = moment()
|
|
|
|
|
.hour(0)
|
|
|
|
|
.minute(0)
|
|
|
|
|
.second(0)
|
|
|
|
|
.millisecond(0);
|
2020-11-27 08:48:38 +00:00
|
|
|
const dateFormat = props.dateFormat ? props.dateFormat : ISO_DATE_FORMAT;
|
|
|
|
|
|
|
|
|
|
const todayDateString = today.format(dateFormat);
|
|
|
|
|
if (dateString === undefined) {
|
|
|
|
|
return {
|
|
|
|
|
isValid: false,
|
|
|
|
|
parsed: "",
|
|
|
|
|
message:
|
|
|
|
|
`${WIDGET_TYPE_VALIDATION_ERROR}: Date ` + props.dateFormat
|
|
|
|
|
? props.dateFormat
|
|
|
|
|
: "",
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
const parsedCurrentDate = moment(dateString, dateFormat);
|
|
|
|
|
let isValid = parsedCurrentDate.isValid();
|
|
|
|
|
const parsedMinDate = moment(props.minDate, dateFormat);
|
|
|
|
|
const parsedMaxDate = moment(props.maxDate, dateFormat);
|
|
|
|
|
|
|
|
|
|
// checking for max/min date range
|
|
|
|
|
if (isValid) {
|
|
|
|
|
if (
|
|
|
|
|
parsedMinDate.isValid() &&
|
|
|
|
|
parsedCurrentDate.isBefore(parsedMinDate)
|
|
|
|
|
) {
|
|
|
|
|
isValid = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
isValid &&
|
|
|
|
|
parsedMaxDate.isValid() &&
|
|
|
|
|
parsedCurrentDate.isAfter(parsedMaxDate)
|
|
|
|
|
) {
|
|
|
|
|
isValid = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const parsed = isValid ? dateString : todayDateString;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
isValid,
|
|
|
|
|
parsed,
|
|
|
|
|
message: isValid ? "" : `${WIDGET_TYPE_VALIDATION_ERROR}: Date R`,
|
|
|
|
|
};
|
|
|
|
|
},
|
2020-10-21 04:25:32 +00:00
|
|
|
[VALIDATION_TYPES.ACTION_SELECTOR]: (
|
|
|
|
|
value: any,
|
|
|
|
|
props: WidgetProps,
|
|
|
|
|
dataTree?: DataTree,
|
|
|
|
|
): ValidationResponse => {
|
|
|
|
|
if (Array.isArray(value) && value.length) {
|
|
|
|
|
return {
|
|
|
|
|
isValid: true,
|
|
|
|
|
parsed: undefined,
|
|
|
|
|
transformed: "Function Call",
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
/*
|
|
|
|
|
if (_.isString(value)) {
|
|
|
|
|
if (value.indexOf("navigateTo") !== -1) {
|
|
|
|
|
const pageNameOrUrl = modalGetter(value);
|
|
|
|
|
if (dataTree) {
|
|
|
|
|
if (isDynamicValue(pageNameOrUrl)) {
|
|
|
|
|
return {
|
|
|
|
|
isValid: true,
|
|
|
|
|
parsed: value,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
const isPage =
|
|
|
|
|
(dataTree.pageList as PageListPayload).findIndex(
|
|
|
|
|
page => page.pageName === pageNameOrUrl,
|
|
|
|
|
) !== -1;
|
|
|
|
|
const isValidUrl = URL_REGEX.test(pageNameOrUrl);
|
|
|
|
|
if (!(isValidUrl || isPage)) {
|
|
|
|
|
return {
|
|
|
|
|
isValid: false,
|
|
|
|
|
parsed: value,
|
|
|
|
|
message: `${NAVIGATE_TO_VALIDATION_ERROR}`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
return {
|
|
|
|
|
isValid: false,
|
|
|
|
|
parsed: undefined,
|
|
|
|
|
transformed: "undefined",
|
|
|
|
|
message: "Not a function call",
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
[VALIDATION_TYPES.ARRAY_ACTION_SELECTOR]: (
|
|
|
|
|
value: any,
|
|
|
|
|
props: WidgetProps,
|
|
|
|
|
dataTree?: DataTree,
|
|
|
|
|
): ValidationResponse => {
|
|
|
|
|
const { isValid, parsed, message } = VALIDATORS[VALIDATION_TYPES.ARRAY](
|
|
|
|
|
value,
|
|
|
|
|
props,
|
|
|
|
|
dataTree,
|
|
|
|
|
);
|
|
|
|
|
let isValidFinal = isValid;
|
|
|
|
|
let finalParsed = parsed.slice();
|
|
|
|
|
if (isValid) {
|
|
|
|
|
finalParsed = parsed.map((value: any) => {
|
|
|
|
|
const { isValid, message } = VALIDATORS[
|
|
|
|
|
VALIDATION_TYPES.ACTION_SELECTOR
|
|
|
|
|
](value.dynamicTrigger, props, dataTree);
|
|
|
|
|
|
|
|
|
|
isValidFinal = isValidFinal && isValid;
|
|
|
|
|
return {
|
|
|
|
|
...value,
|
|
|
|
|
message: message,
|
|
|
|
|
isValid: isValid,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
isValid: isValidFinal,
|
|
|
|
|
parsed: finalParsed,
|
|
|
|
|
message: message,
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
[VALIDATION_TYPES.SELECTED_TAB]: (
|
|
|
|
|
value: any,
|
|
|
|
|
props: WidgetProps,
|
|
|
|
|
dataTree?: DataTree,
|
|
|
|
|
): ValidationResponse => {
|
|
|
|
|
const tabs =
|
|
|
|
|
props.tabs && isString(props.tabs)
|
|
|
|
|
? JSON.parse(props.tabs)
|
|
|
|
|
: props.tabs && Array.isArray(props.tabs)
|
|
|
|
|
? props.tabs
|
|
|
|
|
: [];
|
|
|
|
|
const tabNames = tabs.map((i: { label: string; id: string }) => i.label);
|
|
|
|
|
const isValidTabName = tabNames.includes(value);
|
|
|
|
|
return {
|
|
|
|
|
isValid: isValidTabName,
|
|
|
|
|
parsed: value,
|
|
|
|
|
message: isValidTabName
|
|
|
|
|
? ""
|
|
|
|
|
: `${WIDGET_TYPE_VALIDATION_ERROR}: Invalid tab name.`,
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
[VALIDATION_TYPES.DEFAULT_OPTION_VALUE]: (
|
|
|
|
|
value: string | string[],
|
|
|
|
|
props: WidgetProps,
|
|
|
|
|
dataTree?: DataTree,
|
|
|
|
|
) => {
|
|
|
|
|
let values = value;
|
|
|
|
|
|
|
|
|
|
if (props) {
|
|
|
|
|
if (props.selectionType === "SINGLE_SELECT") {
|
|
|
|
|
return VALIDATORS[VALIDATION_TYPES.TEXT](value, props, dataTree);
|
|
|
|
|
} else if (props.selectionType === "MULTI_SELECT") {
|
|
|
|
|
if (typeof value === "string") {
|
|
|
|
|
try {
|
|
|
|
|
values = JSON.parse(value);
|
|
|
|
|
if (!Array.isArray(values)) {
|
|
|
|
|
throw new Error();
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
values = value.length ? value.split(",") : [];
|
|
|
|
|
if (values.length > 0) {
|
2020-12-23 14:18:46 +00:00
|
|
|
values = values.map((value) => value.trim());
|
2020-10-21 04:25:32 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Array.isArray(values)) {
|
|
|
|
|
values = _.uniq(values);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
isValid: true,
|
|
|
|
|
parsed: values,
|
|
|
|
|
};
|
|
|
|
|
},
|
2020-11-20 07:43:39 +00:00
|
|
|
[VALIDATION_TYPES.DEFAULT_SELECTED_ROW]: (
|
|
|
|
|
value: string | string[],
|
|
|
|
|
props: WidgetProps,
|
|
|
|
|
dataTree?: DataTree,
|
|
|
|
|
) => {
|
|
|
|
|
let values = value;
|
|
|
|
|
|
|
|
|
|
if (props) {
|
|
|
|
|
if (props.multiRowSelection) {
|
|
|
|
|
if (typeof value === "string") {
|
|
|
|
|
try {
|
|
|
|
|
values = JSON.parse(value);
|
|
|
|
|
if (!Array.isArray(values)) {
|
|
|
|
|
throw new Error();
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
values = value.length ? value.split(",") : [];
|
|
|
|
|
if (values.length > 0) {
|
2020-12-23 14:18:46 +00:00
|
|
|
let numbericValues = values.map((value) => {
|
2020-11-20 07:43:39 +00:00
|
|
|
return isNumber(value.trim()) ? -1 : Number(value.trim());
|
|
|
|
|
});
|
|
|
|
|
numbericValues = _.uniq(numbericValues);
|
|
|
|
|
return {
|
|
|
|
|
isValid: true,
|
|
|
|
|
parsed: numbericValues,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
try {
|
2020-12-23 03:58:23 +00:00
|
|
|
if (value === "") {
|
|
|
|
|
return {
|
|
|
|
|
isValid: true,
|
|
|
|
|
parsed: -1,
|
|
|
|
|
};
|
|
|
|
|
}
|
2020-11-20 07:43:39 +00:00
|
|
|
const parsed = toNumber(value);
|
|
|
|
|
return {
|
|
|
|
|
isValid: true,
|
|
|
|
|
parsed: parsed,
|
|
|
|
|
};
|
|
|
|
|
} catch (e) {
|
|
|
|
|
return {
|
|
|
|
|
isValid: true,
|
|
|
|
|
parsed: -1,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
isValid: true,
|
|
|
|
|
parsed: values,
|
|
|
|
|
};
|
|
|
|
|
},
|
2020-10-21 04:25:32 +00:00
|
|
|
};
|
2020-12-23 14:18:46 +00:00
|
|
|
|
|
|
|
|
export const makeParentsDependOnChildren = (
|
|
|
|
|
depMap: DynamicDependencyMap,
|
|
|
|
|
): DynamicDependencyMap => {
|
|
|
|
|
//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;
|
|
|
|
|
};
|
|
|
|
|
export const makeParentsDependOnChild = (
|
|
|
|
|
depMap: DynamicDependencyMap,
|
|
|
|
|
child: string,
|
|
|
|
|
): DynamicDependencyMap => {
|
|
|
|
|
const result: DynamicDependencyMap = depMap;
|
|
|
|
|
let curKey = child;
|
|
|
|
|
const rgx = /^(.*)\..*$/;
|
|
|
|
|
let matches: Array<string> | null;
|
|
|
|
|
// Note: The `=` is intentional
|
|
|
|
|
// Stops looping when match is null
|
|
|
|
|
while ((matches = curKey.match(rgx)) !== null) {
|
|
|
|
|
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;
|
|
|
|
|
};
|