For nested functions, the trigger meta is not available for subsequent functions. So in cases where the dynamic trigger has the relevant info, we can use that to update the trigger meta and then use that meta to display the source of the logs. Fixes #16515 Fixes #16483 Fixes #16514 Co-authored-by: Aishwarya UR <aishwarya@appsmith.com>
279 lines
8.3 KiB
TypeScript
279 lines
8.3 KiB
TypeScript
import { DataTree, DataTreeJSAction } from "entities/DataTree/dataTreeFactory";
|
|
import { isEmpty, set } from "lodash";
|
|
import { EvalErrorTypes } from "utils/DynamicBindingUtils";
|
|
import { JSUpdate, ParsedJSSubAction } from "utils/JSPaneUtils";
|
|
import { isTypeOfFunction, parseJSObjectWithAST } from "@shared/ast";
|
|
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,
|
|
undefined,
|
|
undefined,
|
|
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;
|
|
}
|