fix: Saving variables as string rather than values in backend (#12999)

* parsing of js object has changed

* added test for parsing

* parse object with literal

* added variable to reactive paths and solved some PR comments

* test fixed

* small fix

* removed restricting functions to be added if object body has errors

Co-authored-by: Aishwarya UR <aishwarya@appsmith.com>
This commit is contained in:
Apeksha Bhosale 2022-06-24 09:36:52 +05:30 committed by GitHub
parent b4347b44e1
commit 7fa4df32a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 261 additions and 50 deletions

View File

@ -158,11 +158,19 @@ describe("generateDataTreeJSAction", () => {
body: "SMART_SUBSTITUTE",
myFun2: "SMART_SUBSTITUTE",
myFun1: "SMART_SUBSTITUTE",
myVar1: "SMART_SUBSTITUTE",
myVar2: "SMART_SUBSTITUTE",
},
dynamicBindingPathList: [
{
key: "body",
},
{
key: "myVar1",
},
{
key: "myVar2",
},
{
key: "myFun2",
},
@ -186,6 +194,8 @@ describe("generateDataTreeJSAction", () => {
body: "SMART_SUBSTITUTE",
myFun1: "SMART_SUBSTITUTE",
myFun2: "SMART_SUBSTITUTE",
myVar1: "SMART_SUBSTITUTE",
myVar2: "SMART_SUBSTITUTE",
},
};
const result = generateDataTreeJSAction(jsCollection);
@ -347,11 +357,19 @@ describe("generateDataTreeJSAction", () => {
body: "SMART_SUBSTITUTE",
myFun2: "SMART_SUBSTITUTE",
myFun1: "SMART_SUBSTITUTE",
myVar1: "SMART_SUBSTITUTE",
myVar2: "SMART_SUBSTITUTE",
},
dynamicBindingPathList: [
{
key: "body",
},
{
key: "myVar1",
},
{
key: "myVar2",
},
{
key: "myFun2",
},
@ -375,6 +393,8 @@ describe("generateDataTreeJSAction", () => {
body: "SMART_SUBSTITUTE",
myFun1: "SMART_SUBSTITUTE",
myFun2: "SMART_SUBSTITUTE",
myVar1: "SMART_SUBSTITUTE",
myVar2: "SMART_SUBSTITUTE",
},
};

View File

@ -28,6 +28,8 @@ export const generateDataTreeJSAction = (
const variable = variables[i];
variableList[variable.name] = variable.value;
listVariables.push(variable.name);
dynamicBindingPathList.push({ key: variable.name });
bindingPaths[variable.name] = EvaluationSubstitutionType.SMART_SUBSTITUTE;
}
}
const dependencyMap: DependencyMap = {};

View File

@ -155,6 +155,21 @@ export const getDifferenceInJSCollection = (
} else {
changedVariables = jsAction.variables;
}
//delete variable
if (varList && varList.length > 0 && parsedBody.variables) {
for (let i = 0; i < varList.length; i++) {
const preVar = varList[i];
const existed = parsedBody.variables.find(
(jsVar: Variable) => jsVar.name === preVar.name,
);
if (!existed) {
const newvarList = varList.filter(
(deletedVar) => deletedVar.name !== preVar.name,
);
changedVariables = changedVariables.concat(newvarList);
}
}
}
return {
newActions: toBeAddedActions,
updateActions: toBeUpdatedActions,

View File

@ -82,9 +82,9 @@ import {
ActionValidationConfigMap,
ValidationConfig,
} from "constants/PropertyControlConstants";
import { parseJSObjectWithAST } from "workers/ast";
import { klona } from "klona/full";
import { EvalMetaUpdates } from "./types";
export default class DataTreeEvaluator {
dependencyMap: DependencyMap = {};
sortedDependencies: Array<string> = [];
@ -573,8 +573,13 @@ export default class DataTreeEvaluator {
Object.keys(entity.reactivePaths).forEach((propertyPath) => {
const existingDeps =
dependencies[`${entityName}.${propertyPath}`] || [];
const unevalPropValue = _.get(entity, propertyPath).toString();
const { jsSnippets } = getDynamicBindings(unevalPropValue, entity);
const unevalPropValue = _.get(entity, propertyPath);
const unevalPropValueString =
!!unevalPropValue && unevalPropValue.toString();
const { jsSnippets } = getDynamicBindings(
unevalPropValueString,
entity,
);
dependencies[`${entityName}.${propertyPath}`] = existingDeps.concat(
jsSnippets.filter((jsSnippet) => !!jsSnippet),
);
@ -730,6 +735,30 @@ export default class DataTreeEvaluator {
_.set(currentTree, fullPropertyPath, evalPropertyValue);
return currentTree;
} else if (isJSAction(entity)) {
const variableList: Array<string> =
_.get(entity, "variables") || [];
if (variableList.indexOf(propertyPath) > -1) {
const currentEvaluatedValue = _.get(
currentTree,
getEvalValuePath(fullPropertyPath, {
isPopulated: true,
fullPath: true,
}),
);
if (!currentEvaluatedValue) {
_.set(
currentTree,
getEvalValuePath(fullPropertyPath, {
isPopulated: true,
fullPath: true,
}),
evalPropertyValue,
);
_.set(currentTree, fullPropertyPath, evalPropertyValue);
} else {
_.set(currentTree, fullPropertyPath, currentEvaluatedValue);
}
}
return currentTree;
} else {
return _.set(currentTree, fullPropertyPath, evalPropertyValue);
@ -1041,47 +1070,60 @@ export default class DataTreeEvaluator {
if (correctFormat) {
const body = entity.body.replace(/export default/g, "");
try {
const { result } = evaluateSync(body, unEvalDataTree, {}, true);
delete this.resolvedFunctions[`${entityName}`];
delete this.currentJSCollectionState[`${entityName}`];
if (result) {
const actions: ParsedJSSubAction[] = [];
const variables: any = [];
Object.keys(result).forEach((unEvalFunc) => {
const unEvalValue = result[unEvalFunc];
if (typeof unEvalValue === "function") {
const params = getParams(unEvalValue);
const functionString = unEvalValue.toString();
_.set(
this.resolvedFunctions,
`${entityName}.${unEvalFunc}`,
unEvalValue,
);
_.set(
this.currentJSCollectionState,
`${entityName}.${unEvalFunc}`,
functionString,
);
actions.push({
name: unEvalFunc,
body: functionString,
arguments: params,
parsedFunction: unEvalValue,
isAsync: false,
});
} else {
const parsedObject = parseJSObjectWithAST(body);
const actions: any = [];
const variables: any = [];
if (!!parsedObject) {
parsedObject.forEach((parsedElement: any) => {
if (
parsedElement.type === "ArrowFunctionExpression" ||
parsedElement.type === "FunctionExpression"
) {
try {
const { result } = evaluateSync(
parsedElement.value,
unEvalDataTree,
{},
true,
);
if (!!result) {
const params = getParams(result);
const functionString = parsedElement.value;
_.set(
this.resolvedFunctions,
`${entityName}.${parsedElement.key}`,
result,
);
_.set(
this.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: unEvalFunc,
value: result[unEvalFunc],
name: parsedElement.key,
value: parsedElement.value,
});
_.set(
this.currentJSCollectionState,
`${entityName}.${unEvalFunc}`,
unEvalValue,
`${entityName}.${parsedElement.key}`,
parsedElement.value,
);
}
});
const parsedBody = {
body: entity.body,
actions: actions,
@ -1097,16 +1139,8 @@ export default class DataTreeEvaluator {
id: entity.actionId,
});
}
} catch (error) {
const errors = {
type: EvalErrorTypes.PARSE_JS_ERROR,
context: {
entity: entity,
propertyPath: entity.name + ".body",
},
message: (error as Error).message,
};
this.errors.push(errors);
} catch (e) {
//if we need to push error as popup in case
}
} else {
const errors = {
@ -1503,7 +1537,7 @@ export default class DataTreeEvaluator {
if (!entity) {
continue;
}
if (!isAction(entity) && !isWidget(entity)) {
if (!isAction(entity) && !isWidget(entity) && !isJSAction(entity)) {
continue;
}
let entityDynamicBindingPaths: string[] = [];

View File

@ -1,4 +1,4 @@
import { extractIdentifiersFromCode } from "workers/ast";
import { extractIdentifiersFromCode, parseJSObjectWithAST } from "workers/ast";
describe("getAllIdentifiers", () => {
it("works properly", () => {
@ -228,3 +228,85 @@ describe("getAllIdentifiers", () => {
});
});
});
describe("parseJSObjectWithAST", () => {
it("parse js object", () => {
const body = `{
myVar1: [],
myVar2: {},
myFun1: () => {
//write code here
},
myFun2: async () => {
//use async-await or promises
}
}`;
const parsedObject = [
{
key: "myVar1",
value: "[]",
type: "ArrayExpression",
},
{
key: "myVar2",
value: "{}",
type: "ObjectExpression",
},
{
key: "myFun1",
value: "() => {}",
type: "ArrowFunctionExpression",
},
{
key: "myFun2",
value: "async () => {}",
type: "ArrowFunctionExpression",
},
];
const resultParsedObject = parseJSObjectWithAST(body);
expect(resultParsedObject).toStrictEqual(parsedObject);
});
it("parse js object with literal", () => {
const body = `{
myVar1: [],
myVar2: {
"a": "app",
},
myFun1: () => {
//write code here
},
myFun2: async () => {
//use async-await or promises
}
}`;
const parsedObject = [
{
key: "myVar1",
value: "[]",
type: "ArrayExpression",
},
{
key: '"a"',
value: '"app"',
type: "Literal",
},
{
key: "myVar2",
value: '{\n "a": "app"\n}',
type: "ObjectExpression",
},
{
key: "myFun1",
value: "() => {}",
type: "ArrowFunctionExpression",
},
{
key: "myFun2",
value: "async () => {}",
type: "ArrowFunctionExpression",
},
];
const resultParsedObject = parseJSObjectWithAST(body);
expect(resultParsedObject).toStrictEqual(parsedObject);
});
});

View File

@ -1,8 +1,9 @@
import { parse, Node } from "acorn";
import { ancestor } from "acorn-walk";
import { ancestor, simple } from "acorn-walk";
import { ECMA_VERSION, NodeTypes } from "constants/ast";
import _ from "lodash";
import { sanitizeScript } from "./evaluate";
import { generate } from "astring";
/*
* Valuable links:
@ -298,3 +299,26 @@ const getPropertyAccessor = (propertyNode: IdentifierNode | LiteralNode) => {
return `[${propertyNode.value}]`;
}
};
export const parseJSObjectWithAST = (jsObjectBody: string) => {
const createString = `var jsObj = ${jsObjectBody}`;
const ast = parse(createString, { ecmaVersion: ECMA_VERSION });
const rawKeyValues: any = [];
const parsedObject: any = [];
simple(ast, {
Property(node: any) {
rawKeyValues.push({
key: node.key,
value: node.value,
});
},
});
rawKeyValues.forEach((rawKeyValue: any) => {
parsedObject.push({
key: generate(rawKeyValue.key),
value: generate(rawKeyValue.value),
type: rawKeyValue.value.type,
});
});
return parsedObject;
};

View File

@ -822,6 +822,22 @@ export const updateJSCollectionInDataTree = (
}
} else {
varList.push(newVar.name);
const reactivePaths = jsCollection.reactivePaths;
reactivePaths[newVar.name] =
EvaluationSubstitutionType.SMART_SUBSTITUTE;
_.set(
modifiedDataTree,
`${jsCollection.name}.reactivePaths`,
reactivePaths,
);
const dynamicBindingPathList = jsCollection.dynamicBindingPathList;
dynamicBindingPathList.push({ key: newVar.name });
_.set(
modifiedDataTree,
`${jsCollection.name}.dynamicBindingPathList`,
dynamicBindingPathList,
);
_.set(modifiedDataTree, `${jsCollection.name}.variables`, varList);
_.set(
modifiedDataTree,
@ -830,15 +846,33 @@ export const updateJSCollectionInDataTree = (
);
}
}
let newVarList: Array<string> = [];
let newVarList: Array<string> = varList;
for (let i = 0; i < varList.length; i++) {
const varListItem = varList[i];
const existsInParsed = parsedBody.variables.find(
(item) => item.name === varListItem,
);
if (!existsInParsed) {
const reactivePaths = jsCollection.reactivePaths;
delete reactivePaths[varListItem];
_.set(
modifiedDataTree,
`${jsCollection.name}.reactivePaths`,
reactivePaths,
);
let dynamicBindingPathList = jsCollection.dynamicBindingPathList;
dynamicBindingPathList = dynamicBindingPathList.filter(
(path) => path["key"] !== varListItem,
);
_.set(
modifiedDataTree,
`${jsCollection.name}.dynamicBindingPathList`,
dynamicBindingPathList,
);
newVarList = newVarList.filter((item) => item !== varListItem);
delete modifiedDataTree[`${jsCollection.name}`][`${varListItem}`];
newVarList = varList.filter((item) => item !== varListItem);
}
}
if (newVarList.length) {