2022-07-07 09:30:35 +00:00
|
|
|
import { DataTree, DataTreeJSAction } from "entities/DataTree/dataTreeFactory";
|
|
|
|
|
import { isEmpty, set } from "lodash";
|
|
|
|
|
import { EvalErrorTypes } from "utils/DynamicBindingUtils";
|
|
|
|
|
import { JSUpdate, ParsedJSSubAction } from "utils/JSPaneUtils";
|
2022-08-23 11:09:42 +00:00
|
|
|
import { isTypeOfFunction, parseJSObjectWithAST } from "@shared/ast";
|
2022-07-07 09:30:35 +00:00
|
|
|
import DataTreeEvaluator from "workers/DataTreeEvaluator";
|
|
|
|
|
import evaluateSync, { isFunctionAsync } from "workers/evaluate";
|
|
|
|
|
import {
|
|
|
|
|
DataTreeDiff,
|
|
|
|
|
DataTreeDiffEvent,
|
|
|
|
|
getEntityNameAndPropertyPath,
|
|
|
|
|
isJSAction,
|
|
|
|
|
} from "workers/evaluationUtils";
|
|
|
|
|
import {
|
|
|
|
|
removeFunctionsAndVariableJSCollection,
|
|
|
|
|
updateJSCollectionInUnEvalTree,
|
|
|
|
|
} from "workers/JSObject/utils";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Here we update our unEvalTree according to the change in JSObject's body
|
|
|
|
|
*
|
|
|
|
|
* @param jsUpdates
|
|
|
|
|
* @param localUnEvalTree
|
|
|
|
|
* @returns
|
|
|
|
|
*/
|
|
|
|
|
export const 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 = updateJSCollectionInUnEvalTree(
|
|
|
|
|
parsedBody,
|
|
|
|
|
entity,
|
|
|
|
|
localUnEvalTree,
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
//if parse error remove functions and variables from dataTree
|
|
|
|
|
localUnEvalTree = removeFunctionsAndVariableJSCollection(
|
|
|
|
|
localUnEvalTree,
|
|
|
|
|
entity,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return localUnEvalTree;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const regex = new RegExp(/^export default[\s]*?({[\s\S]*?})/);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Here we parse the JSObject and then determine
|
|
|
|
|
* 1. it's nature : async or sync
|
|
|
|
|
* 2. Find arguments of JS Actions
|
|
|
|
|
* 3. set variables and actions in currentJSCollectionState and resolvedFunctions
|
|
|
|
|
*
|
|
|
|
|
* @param dataTreeEvalRef
|
|
|
|
|
* @param entity
|
|
|
|
|
* @param jsUpdates
|
|
|
|
|
* @param unEvalDataTree
|
|
|
|
|
* @param entityName
|
|
|
|
|
* @returns
|
|
|
|
|
*/
|
|
|
|
|
export function saveResolvedFunctionsAndJSUpdates(
|
|
|
|
|
dataTreeEvalRef: DataTreeEvaluator,
|
|
|
|
|
entity: DataTreeJSAction,
|
|
|
|
|
jsUpdates: Record<string, JSUpdate>,
|
|
|
|
|
unEvalDataTree: DataTree,
|
|
|
|
|
entityName: string,
|
|
|
|
|
) {
|
|
|
|
|
const correctFormat = regex.test(entity.body);
|
|
|
|
|
if (correctFormat) {
|
|
|
|
|
const body = entity.body.replace(/export default/g, "");
|
|
|
|
|
try {
|
|
|
|
|
delete dataTreeEvalRef.resolvedFunctions[`${entityName}`];
|
|
|
|
|
delete dataTreeEvalRef.currentJSCollectionState[`${entityName}`];
|
|
|
|
|
const parseStartTime = performance.now();
|
|
|
|
|
const parsedObject = parseJSObjectWithAST(body);
|
|
|
|
|
const parseEndTime = performance.now();
|
|
|
|
|
const JSObjectASTParseTime = parseEndTime - parseStartTime;
|
|
|
|
|
dataTreeEvalRef.logs.push({
|
|
|
|
|
JSObjectName: entityName,
|
|
|
|
|
JSObjectASTParseTime,
|
|
|
|
|
});
|
|
|
|
|
const actions: any = [];
|
|
|
|
|
const variables: any = [];
|
|
|
|
|
if (!!parsedObject) {
|
|
|
|
|
parsedObject.forEach((parsedElement) => {
|
|
|
|
|
if (isTypeOfFunction(parsedElement.type)) {
|
|
|
|
|
try {
|
|
|
|
|
const { result } = evaluateSync(
|
|
|
|
|
parsedElement.value,
|
|
|
|
|
unEvalDataTree,
|
|
|
|
|
{},
|
|
|
|
|
true,
|
|
|
|
|
);
|
|
|
|
|
if (!!result) {
|
|
|
|
|
let params: Array<{ key: string; value: unknown }> = [];
|
|
|
|
|
|
|
|
|
|
if (parsedElement.arguments) {
|
|
|
|
|
params = parsedElement.arguments.map(
|
|
|
|
|
({ defaultValue, paramName }) => ({
|
|
|
|
|
key: paramName,
|
|
|
|
|
value: defaultValue,
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const functionString = parsedElement.value;
|
|
|
|
|
set(
|
|
|
|
|
dataTreeEvalRef.resolvedFunctions,
|
|
|
|
|
`${entityName}.${parsedElement.key}`,
|
|
|
|
|
result,
|
|
|
|
|
);
|
|
|
|
|
set(
|
|
|
|
|
dataTreeEvalRef.currentJSCollectionState,
|
|
|
|
|
`${entityName}.${parsedElement.key}`,
|
|
|
|
|
functionString,
|
|
|
|
|
);
|
|
|
|
|
actions.push({
|
|
|
|
|
name: parsedElement.key,
|
|
|
|
|
body: functionString,
|
|
|
|
|
arguments: params,
|
|
|
|
|
parsedFunction: result,
|
|
|
|
|
isAsync: false,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
// in case we need to handle error state
|
|
|
|
|
}
|
|
|
|
|
} else if (parsedElement.type !== "literal") {
|
|
|
|
|
variables.push({
|
|
|
|
|
name: parsedElement.key,
|
|
|
|
|
value: parsedElement.value,
|
|
|
|
|
});
|
|
|
|
|
set(
|
|
|
|
|
dataTreeEvalRef.currentJSCollectionState,
|
|
|
|
|
`${entityName}.${parsedElement.key}`,
|
|
|
|
|
parsedElement.value,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
const parsedBody = {
|
|
|
|
|
body: entity.body,
|
|
|
|
|
actions: actions,
|
|
|
|
|
variables,
|
|
|
|
|
};
|
|
|
|
|
set(jsUpdates, `${entityName}`, {
|
|
|
|
|
parsedBody,
|
|
|
|
|
id: entity.actionId,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
set(jsUpdates, `${entityName}`, {
|
|
|
|
|
parsedBody: undefined,
|
|
|
|
|
id: entity.actionId,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
//if we need to push error as popup in case
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
const errors = {
|
|
|
|
|
type: EvalErrorTypes.PARSE_JS_ERROR,
|
|
|
|
|
context: {
|
|
|
|
|
entity: entity,
|
|
|
|
|
propertyPath: entity.name + ".body",
|
|
|
|
|
},
|
|
|
|
|
message: "Start object with export default",
|
|
|
|
|
};
|
|
|
|
|
dataTreeEvalRef.errors.push(errors);
|
|
|
|
|
}
|
|
|
|
|
return jsUpdates;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function parseJSActions(
|
|
|
|
|
dataTreeEvalRef: DataTreeEvaluator,
|
|
|
|
|
unEvalDataTree: DataTree,
|
|
|
|
|
differences?: DataTreeDiff[],
|
|
|
|
|
oldUnEvalTree?: DataTree,
|
|
|
|
|
) {
|
|
|
|
|
let jsUpdates: Record<string, JSUpdate> = {};
|
|
|
|
|
if (!!differences && !!oldUnEvalTree) {
|
|
|
|
|
differences.forEach((diff) => {
|
|
|
|
|
const { entityName, propertyPath } = getEntityNameAndPropertyPath(
|
|
|
|
|
diff.payload.propertyPath,
|
|
|
|
|
);
|
|
|
|
|
const entity = unEvalDataTree[entityName];
|
|
|
|
|
|
|
|
|
|
if (!isJSAction(entity)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (diff.event === DataTreeDiffEvent.DELETE) {
|
|
|
|
|
// when JSObject is deleted, we remove it from currentJSCollectionState & resolvedFunctions
|
|
|
|
|
if (
|
|
|
|
|
dataTreeEvalRef.currentJSCollectionState &&
|
|
|
|
|
dataTreeEvalRef.currentJSCollectionState[diff.payload.propertyPath]
|
|
|
|
|
) {
|
|
|
|
|
delete dataTreeEvalRef.currentJSCollectionState[
|
|
|
|
|
diff.payload.propertyPath
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
if (
|
|
|
|
|
dataTreeEvalRef.resolvedFunctions &&
|
|
|
|
|
dataTreeEvalRef.resolvedFunctions[diff.payload.propertyPath]
|
|
|
|
|
) {
|
|
|
|
|
delete dataTreeEvalRef.resolvedFunctions[diff.payload.propertyPath];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
(diff.event === DataTreeDiffEvent.EDIT && propertyPath === "body") ||
|
|
|
|
|
(diff.event === DataTreeDiffEvent.NEW && propertyPath === "")
|
|
|
|
|
) {
|
|
|
|
|
jsUpdates = saveResolvedFunctionsAndJSUpdates(
|
|
|
|
|
dataTreeEvalRef,
|
|
|
|
|
entity,
|
|
|
|
|
jsUpdates,
|
|
|
|
|
unEvalDataTree,
|
|
|
|
|
entityName,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
Object.keys(unEvalDataTree).forEach((entityName) => {
|
|
|
|
|
const entity = unEvalDataTree[entityName];
|
|
|
|
|
if (!isJSAction(entity)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
jsUpdates = saveResolvedFunctionsAndJSUpdates(
|
|
|
|
|
dataTreeEvalRef,
|
|
|
|
|
entity,
|
|
|
|
|
jsUpdates,
|
|
|
|
|
unEvalDataTree,
|
|
|
|
|
entityName,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Object.keys(jsUpdates).forEach((entityName) => {
|
|
|
|
|
const parsedBody = jsUpdates[entityName].parsedBody;
|
|
|
|
|
if (!parsedBody) return;
|
|
|
|
|
parsedBody.actions = parsedBody.actions.map((action) => {
|
|
|
|
|
return {
|
|
|
|
|
...action,
|
|
|
|
|
isAsync: isFunctionAsync(
|
|
|
|
|
action.parsedFunction,
|
|
|
|
|
unEvalDataTree,
|
|
|
|
|
dataTreeEvalRef.resolvedFunctions,
|
|
|
|
|
dataTreeEvalRef.logs,
|
|
|
|
|
),
|
|
|
|
|
// parsedFunction - used only to determine if function is async
|
|
|
|
|
parsedFunction: undefined,
|
|
|
|
|
} as ParsedJSSubAction;
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
return { jsUpdates };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function 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;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return jsCollections;
|
|
|
|
|
}
|