From efa8b68a47c15ed18cf685055f0f6a0dfcfb63bb Mon Sep 17 00:00:00 2001 From: Apeksha Bhosale <7846888+ApekshaBhosale@users.noreply.github.com> Date: Mon, 12 Dec 2022 19:45:40 +0530 Subject: [PATCH] fix: Parse js actions for view mode changes (#18357) * added new function inside parse js actions for view moe * fixing test cases * added evaluateSync with isTriggered false * Add types Add switch case for diff.event Add function get back app mode * Change foreach loops to for of * Dont return jsUpdates from view mode save resolved since no operation is happening Change viewModeSaveResolvedFunctionsAndJSUpdates -> viewModeSaveResolvedFunctions to reflect what it's functionality is * Refactor JSObject code * Refactor code for JSobject * remove any * export parsed object's type from ast * create function for deleteResolvedFunctionsAndCurrentJSCollectionState * Code review changes * Fix tests * Change returns in for of loops to continue Remove try for evaluate sync Js updates return * Fix bug * Fix bug Co-authored-by: Rimil Dey Co-authored-by: Aishwarya UR --- .../DataTree/dataTreeJSAction.test.ts | 5 +- .../src/entities/DataTree/dataTreeJSAction.ts | 1 + app/client/src/entities/DataTree/types.ts | 1 + .../src/workers/Evaluation/JSObject/index.ts | 427 ++++++++++++------ .../src/workers/Evaluation/JSObject/utils.ts | 18 +- .../Evaluation/__tests__/evaluation.test.ts | 8 + .../workers/common/DataTreeEvaluator/index.ts | 47 +- .../mockData/ArrayAccessorTree.ts | 66 +++ .../mockData/NestedArrayAccessorTree.ts | 48 ++ .../mockData/mockUnEvalTree.ts | 70 +++ .../workers/common/DataTreeEvaluator/test.ts | 26 +- app/shared/ast/index.ts | 5 +- app/shared/ast/src/jsObject/index.ts | 2 +- 13 files changed, 545 insertions(+), 179 deletions(-) diff --git a/app/client/src/entities/DataTree/dataTreeJSAction.test.ts b/app/client/src/entities/DataTree/dataTreeJSAction.test.ts index 4119af5551..2f20eec82f 100644 --- a/app/client/src/entities/DataTree/dataTreeJSAction.test.ts +++ b/app/client/src/entities/DataTree/dataTreeJSAction.test.ts @@ -139,7 +139,6 @@ describe("generateDataTreeJSAction", () => { ENTITY_TYPE: "JSACTION", body: "export default {\n\tmyVar1: [],\n\tmyVar2: {},\n\tmyFun1: () => {\n\t\t//write code here\n\t},\n\tmyFun2: async () => {\n\t\t//use async-await or promises\n\t}\n}", - myFun2: { data: { users: [{ id: 1, name: "John" }], @@ -159,11 +158,13 @@ describe("generateDataTreeJSAction", () => { arguments: [], isAsync: true, confirmBeforeExecute: false, + body: "async () => {\n\t\t//use async-await or promises\n\t}", }, myFun1: { arguments: [], isAsync: false, confirmBeforeExecute: false, + body: "() => {\n\t\t//write code here\n\t}", }, }, bindingPaths: { @@ -350,11 +351,13 @@ describe("generateDataTreeJSAction", () => { arguments: [], isAsync: true, confirmBeforeExecute: false, + body: "async () => {\n\t\t//use async-await or promises\n\t}", }, myFun1: { arguments: [], isAsync: false, confirmBeforeExecute: false, + body: "() => {\n\t\t//write code here\n\t}", }, }, bindingPaths: { diff --git a/app/client/src/entities/DataTree/dataTreeJSAction.ts b/app/client/src/entities/DataTree/dataTreeJSAction.ts index 8e9aeaba94..7e52773c95 100644 --- a/app/client/src/entities/DataTree/dataTreeJSAction.ts +++ b/app/client/src/entities/DataTree/dataTreeJSAction.ts @@ -44,6 +44,7 @@ export const generateDataTreeJSAction = ( arguments: action.actionConfiguration.jsArguments, isAsync: action.actionConfiguration.isAsync, confirmBeforeExecute: !!action.confirmBeforeExecute, + body: action.actionConfiguration.body, }; bindingPaths[action.name] = EvaluationSubstitutionType.SMART_SUBSTITUTE; dynamicBindingPathList.push({ key: action.name }); diff --git a/app/client/src/entities/DataTree/types.ts b/app/client/src/entities/DataTree/types.ts index d613d4ef9a..39547640bf 100644 --- a/app/client/src/entities/DataTree/types.ts +++ b/app/client/src/entities/DataTree/types.ts @@ -66,6 +66,7 @@ export interface MetaArgs { arguments: Variable[]; isAsync: boolean; confirmBeforeExecute: boolean; + body: string; } export interface JSActionEntityConfig { diff --git a/app/client/src/workers/Evaluation/JSObject/index.ts b/app/client/src/workers/Evaluation/JSObject/index.ts index e402817b13..c9eb1d83bf 100644 --- a/app/client/src/workers/Evaluation/JSObject/index.ts +++ b/app/client/src/workers/Evaluation/JSObject/index.ts @@ -2,7 +2,7 @@ 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 { NodeTypes, parseJSObjectWithAST, JsObjectProperty } from "@shared/ast"; import DataTreeEvaluator from "workers/common/DataTreeEvaluator"; import evaluateSync, { isFunctionAsync } from "workers/Evaluation/evaluate"; import { @@ -16,6 +16,19 @@ import { updateJSCollectionInUnEvalTree, } from "workers/Evaluation/JSObject/utils"; +type Actions = { + name: string; + body: string; + arguments: Array<{ key: string; value: unknown }>; + parsedFunction: any; + isAsync: boolean; +}; + +type Variables = { + name: string; + value: string; +}; + /** * Here we update our unEvalTree according to the change in JSObject's body * @@ -33,7 +46,7 @@ export const getUpdatedLocalUnEvalTreeAfterJSUpdates = ( const parsedBody = jsUpdates[jsEntity].parsedBody; if (isJSAction(entity)) { if (!!parsedBody) { - //add/delete/update functions from dataTree + // add/delete/update functions from dataTree localUnEvalTree = updateJSCollectionInUnEvalTree( parsedBody, entity, @@ -54,6 +67,110 @@ export const getUpdatedLocalUnEvalTreeAfterJSUpdates = ( const regex = new RegExp(/^export default[\s]*?({[\s\S]*?})/); +function deleteResolvedFunctionsAndCurrentJSCollectionState( + dataTreeEvalRef: DataTreeEvaluator, + entityName: string, +) { + delete dataTreeEvalRef.resolvedFunctions[entityName]; + delete dataTreeEvalRef.currentJSCollectionState[entityName]; +} + +function parseFunction( + parsedElement: JsObjectProperty, + unEvalDataTree: DataTree, + dataTreeEvalRef: DataTreeEvaluator, + entityName: string, + actions: Actions[], +) { + 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: string = 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, + }); + } +} + +function parseVariables( + variables: Variables[], + parsedElement: JsObjectProperty, + dataTreeEvalRef: DataTreeEvaluator, + entityName: string, +) { + variables.push({ + name: parsedElement.key, + value: parsedElement.value, + }); + set( + dataTreeEvalRef.currentJSCollectionState, + `${entityName}.${parsedElement.key}`, + parsedElement.value, + ); +} + +function getParsedBody( + parsedObject: Array, + unEvalDataTree: DataTree, + dataTreeEvalRef: DataTreeEvaluator, + entityName: string, +) { + const actions: Actions[] = []; + const variables: Variables[] = []; + for (const parsedElement of parsedObject) { + switch (parsedElement.type) { + case "literal": + continue; + case NodeTypes.ArrowFunctionExpression: + case NodeTypes.FunctionExpression: + parseFunction( + parsedElement, + unEvalDataTree, + dataTreeEvalRef, + entityName, + actions, + ); + break; + default: + parseVariables(variables, parsedElement, dataTreeEvalRef, entityName); + } + } + return { + actions, + variables, + }; +} + /** * Here we parse the JSObject and then determine * 1. it's nature : async or sync @@ -62,24 +179,26 @@ const regex = new RegExp(/^export default[\s]*?({[\s\S]*?})/); * * @param dataTreeEvalRef * @param entity - * @param jsUpdates * @param unEvalDataTree * @param entityName + * @param jsUpdates * @returns */ export function saveResolvedFunctionsAndJSUpdates( dataTreeEvalRef: DataTreeEvaluator, entity: DataTreeJSAction, - jsUpdates: Record, unEvalDataTree: DataTree, entityName: string, + jsUpdates: Record, ) { 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}`]; + deleteResolvedFunctionsAndCurrentJSCollectionState( + dataTreeEvalRef, + entityName, + ); const parseStartTime = performance.now(); const parsedObject = parseJSObjectWithAST(body); const parseEndTime = performance.now(); @@ -88,82 +207,23 @@ export function saveResolvedFunctionsAndJSUpdates( 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, - {}, - false, - 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 = !!parsedObject + ? { + body: entity.body, + ...getParsedBody( + parsedObject, + unEvalDataTree, + dataTreeEvalRef, + entityName, + ), } - }); - const parsedBody = { - body: entity.body, - actions: actions, - variables, - }; - set(jsUpdates, `${entityName}`, { - parsedBody, - id: entity.actionId, - }); - } else { - set(jsUpdates, `${entityName}`, { - parsedBody: undefined, - id: entity.actionId, - }); - } + : undefined; + + set(jsUpdates, `${entityName}`, { + parsedBody, + id: entity.actionId, + }); } catch (e) { //if we need to push error as popup in case } @@ -181,74 +241,152 @@ export function saveResolvedFunctionsAndJSUpdates( return jsUpdates; } +export function viewModeSaveResolvedFunctions( + dataTreeEvalRef: DataTreeEvaluator, + entity: DataTreeJSAction, + unEvalDataTree: DataTree, + entityName: string, +) { + try { + deleteResolvedFunctionsAndCurrentJSCollectionState( + dataTreeEvalRef, + entityName, + ); + const jsActions = entity.meta; + const jsActionList = Object.keys(jsActions); + for (const jsAction of jsActionList) { + const { result } = evaluateSync( + jsActions[jsAction].body, + unEvalDataTree, + {}, + false, + undefined, + undefined, + true, + ); + + if (!!result) { + const functionString = jsActions[jsAction].body; + + set( + dataTreeEvalRef.resolvedFunctions, + `${entityName}.${jsAction}`, + result, + ); + set( + dataTreeEvalRef.currentJSCollectionState, + `${entityName}.${jsAction}`, + functionString, + ); + } + } + } catch (e) { + //if we need to push error as popup in case + } +} + +export function parseJSActionsWithDifferences( + dataTreeEvalRef: DataTreeEvaluator, + unEvalDataTree: DataTree, + differences: DataTreeDiff[], +) { + let jsUpdates: Record = {}; + for (const diff of differences) { + const payLoadPropertyPath = diff.payload.propertyPath; + const { entityName, propertyPath } = getEntityNameAndPropertyPath( + payLoadPropertyPath, + ); + const entity = unEvalDataTree[entityName]; + + if (!isJSAction(entity)) { + continue; + } + + switch (diff.event) { + case DataTreeDiffEvent.DELETE: + // when JSObject is deleted, we remove it from currentJSCollectionState & resolvedFunctions + deleteResolvedFunctionsAndCurrentJSCollectionState( + dataTreeEvalRef, + payLoadPropertyPath, + ); + break; + case DataTreeDiffEvent.EDIT: + if (propertyPath === "body") { + jsUpdates = saveResolvedFunctionsAndJSUpdates( + dataTreeEvalRef, + entity, + unEvalDataTree, + entityName, + jsUpdates, + ); + } + break; + case DataTreeDiffEvent.NEW: + if (propertyPath === "") { + jsUpdates = saveResolvedFunctionsAndJSUpdates( + dataTreeEvalRef, + entity, + unEvalDataTree, + entityName, + jsUpdates, + ); + } + break; + } + } + return parseJSUpdates(jsUpdates, unEvalDataTree, dataTreeEvalRef); +} + +export function parseJSActionsForViewMode( + dataTreeEvalRef: DataTreeEvaluator, + unEvalDataTree: DataTree, +) { + const unEvalDataTreeKeys = Object.keys(unEvalDataTree); + for (const entityName of unEvalDataTreeKeys) { + const entity = unEvalDataTree[entityName]; + if (!isJSAction(entity)) { + continue; + } + viewModeSaveResolvedFunctions( + dataTreeEvalRef, + entity, + unEvalDataTree, + entityName, + ); + } +} + export function parseJSActions( dataTreeEvalRef: DataTreeEvaluator, unEvalDataTree: DataTree, - differences?: DataTreeDiff[], - oldUnEvalTree?: DataTree, ) { let jsUpdates: Record = {}; - 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, - ); - }); + const unEvalDataTreeKeys = Object.keys(unEvalDataTree); + for (const entityName of unEvalDataTreeKeys) { + const entity = unEvalDataTree[entityName]; + if (!isJSAction(entity)) { + continue; + } + jsUpdates = saveResolvedFunctionsAndJSUpdates( + dataTreeEvalRef, + entity, + unEvalDataTree, + entityName, + jsUpdates, + ); } + return parseJSUpdates(jsUpdates, unEvalDataTree, dataTreeEvalRef); +} - Object.keys(jsUpdates).forEach((entityName) => { +export function parseJSUpdates( + jsUpdates: Record, + unEvalDataTree: DataTree, + dataTreeEvalRef: DataTreeEvaluator, +) { + const jsUpdateKeys = Object.keys(jsUpdates); + for (const entityName of jsUpdateKeys) { const parsedBody = jsUpdates[entityName].parsedBody; - if (!parsedBody) return; + if (!parsedBody) continue; parsedBody.actions = parsedBody.actions.map((action) => { return { ...action, @@ -262,17 +400,18 @@ export function parseJSActions( parsedFunction: undefined, } as ParsedJSSubAction; }); - }); - return { jsUpdates }; + } + return jsUpdates; } export function getJSEntities(dataTree: DataTree) { const jsCollections: Record = {}; - Object.keys(dataTree).forEach((key: string) => { + const dataTreeKeys = Object.keys(dataTree); + for (const key of dataTreeKeys) { const entity = dataTree[key]; if (isJSAction(entity)) { jsCollections[entity.name] = entity; } - }); + } return jsCollections; } diff --git a/app/client/src/workers/Evaluation/JSObject/utils.ts b/app/client/src/workers/Evaluation/JSObject/utils.ts index c0068d96d1..edb4199fc1 100644 --- a/app/client/src/workers/Evaluation/JSObject/utils.ts +++ b/app/client/src/workers/Evaluation/JSObject/utils.ts @@ -1,20 +1,22 @@ import { DataTree, + DataTreeAppsmith, DataTreeJSAction, EvaluationSubstitutionType, } from "entities/DataTree/dataTreeFactory"; import { ParsedBody, ParsedJSSubAction } from "utils/JSPaneUtils"; import { unset, set, get } from "lodash"; import { isJSAction } from "workers/Evaluation/evaluationUtils"; +import { APP_MODE } from "../../../entities/App"; /** * here we add/remove the properties (variables and actions) which got added/removed from the JSObject parsedBody. NOTE: For other entity below logic is maintained in DataTreeFactory, for JSObject we handle it inside evaluations - * - * @param parsedBody - * @param jsCollection - * @param unEvalTree - * @returns + * + * @param parsedBody + * @param jsCollection + * @param unEvalTree + * @returns */ export const updateJSCollectionInUnEvalTree = ( parsedBody: ParsedBody, @@ -72,6 +74,7 @@ export const updateJSCollectionInUnEvalTree = ( arguments: action.arguments, isAsync: false, confirmBeforeExecute: false, + body: action.body, }; const data = get( @@ -238,3 +241,8 @@ export function isJSObjectFunction( } return false; } + +export function getAppMode(dataTree: DataTree) { + const appsmithObj = dataTree.appsmith as DataTreeAppsmith; + return appsmithObj.mode as APP_MODE; +} diff --git a/app/client/src/workers/Evaluation/__tests__/evaluation.test.ts b/app/client/src/workers/Evaluation/__tests__/evaluation.test.ts index 5e98f07600..73dd3a79c7 100644 --- a/app/client/src/workers/Evaluation/__tests__/evaluation.test.ts +++ b/app/client/src/workers/Evaluation/__tests__/evaluation.test.ts @@ -346,6 +346,14 @@ describe("DataTreeEvaluator", () => { {}, ); const unEvalTree: UnEvalTree = { + appsmith: { + mode: "EDIT", + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + ENTITY_TYPE: ENTITY_TYPE.APPSMITH, + store: {}, + theme: {}, + }, Text1: generateDataTreeWidget( { ...BASE_WIDGET, diff --git a/app/client/src/workers/common/DataTreeEvaluator/index.ts b/app/client/src/workers/common/DataTreeEvaluator/index.ts index b85be2cd1b..4cb235fdd2 100644 --- a/app/client/src/workers/common/DataTreeEvaluator/index.ts +++ b/app/client/src/workers/common/DataTreeEvaluator/index.ts @@ -28,6 +28,7 @@ import { convertPathToString, CrashingError, DataTreeDiff, + getAllPaths, getEntityNameAndPropertyPath, getImmediateParentsOfPropertyPaths, isAction, @@ -38,7 +39,6 @@ import { translateDiffEventToDataTreeDiffEvent, trimDependantChangePaths, overrideWidgetProperties, - getAllPaths, isValidEntity, } from "workers/Evaluation/evaluationUtils"; import { @@ -63,14 +63,14 @@ import { import { DATA_BIND_REGEX } from "constants/BindingsConstants"; import evaluateSync, { EvalResult, - EvaluateContext, evaluateAsync, + EvaluateContext, } from "workers/Evaluation/evaluate"; import { substituteDynamicBindingWithValues } from "workers/Evaluation/evaluationSubstitution"; import { + ENTITY_TYPE as CONSOLE_ENTITY_TYPE, Severity, SourceEntity, - ENTITY_TYPE as CONSOLE_ENTITY_TYPE, UserLogObject, } from "entities/AppsmithConsole"; import { error as logError } from "loglevel"; @@ -83,21 +83,27 @@ import { import { klona } from "klona/full"; import { EvalMetaUpdates } from "./types"; import { - updateDependencyMap, createDependencyMap, + updateDependencyMap, } from "workers/common/DependencyMap"; import { getJSEntities, getUpdatedLocalUnEvalTreeAfterJSUpdates, + parseJSActionsForViewMode, parseJSActions, + parseJSActionsWithDifferences, } from "workers/Evaluation/JSObject"; import { getFixedTimeDifference } from "./utils"; -import { isJSObjectFunction } from "workers/Evaluation/JSObject/utils"; +import { + getAppMode, + isJSObjectFunction, +} from "workers/Evaluation/JSObject/utils"; import { getValidatedTree, validateActionProperty, validateAndParseWidgetProperty, } from "./validationUtils"; +import { APP_MODE } from "../../../entities/App"; type SortedDependencies = Array; @@ -142,6 +148,10 @@ export default class DataTreeEvaluator { sortedValidationDependencies: SortedDependencies = []; inverseValidationDependencyMap: DependencyMap = {}; public hasCyclicalDependency = false; + parseJsActionsConfig = { + [APP_MODE.EDIT]: parseJSActions, + [APP_MODE.PUBLISHED]: parseJSActionsForViewMode, + }; constructor( widgetConfigMap: WidgetTypeConfigMap, allActionValidationConfig?: { @@ -190,8 +200,9 @@ export default class DataTreeEvaluator { //save current state of js collection action and variables to be added to uneval tree //save functions in resolveFunctions (as functions) to be executed as functions are not allowed in evalTree //and functions are saved in dataTree as strings - const parsedCollections = parseJSActions(this, localUnEvalTree); - jsUpdates = parsedCollections.jsUpdates; + const currentAppMode: APP_MODE = getAppMode(localUnEvalTree); + jsUpdates = + this.parseJsActionsConfig[currentAppMode](this, localUnEvalTree) || {}; localUnEvalTree = getUpdatedLocalUnEvalTreeAfterJSUpdates( jsUpdates, localUnEvalTree, @@ -363,18 +374,18 @@ export default class DataTreeEvaluator { translateDiffEventToDataTreeDiffEvent(diff, localUnEvalTree), ), ); - //save parsed functions in resolveJSFunctions, update current state of js collection - const parsedCollections = parseJSActions( - this, - localUnEvalTree, - jsTranslatedDiffs, - this.oldUnEvalTree, - ); - - jsUpdates = parsedCollections.jsUpdates; - //update local data tree if js body has updated (remove/update/add js functions or variables) + // save parsed functions in resolveJSFunctions, update current state of js collection + jsUpdates = + (!!jsTranslatedDiffs && !!this.oldUnEvalTree + ? parseJSActionsWithDifferences( + this, + localUnEvalTree, + jsTranslatedDiffs, + ) + : parseJSActions(this, localUnEvalTree)) || {}; + // update local data tree if js body has updated (remove/update/add js functions or variables) localUnEvalTree = getUpdatedLocalUnEvalTreeAfterJSUpdates( - jsUpdates, + jsUpdates || {}, localUnEvalTree, ); diff --git a/app/client/src/workers/common/DataTreeEvaluator/mockData/ArrayAccessorTree.ts b/app/client/src/workers/common/DataTreeEvaluator/mockData/ArrayAccessorTree.ts index 9663fa75ee..572600e384 100644 --- a/app/client/src/workers/common/DataTreeEvaluator/mockData/ArrayAccessorTree.ts +++ b/app/client/src/workers/common/DataTreeEvaluator/mockData/ArrayAccessorTree.ts @@ -5,6 +5,7 @@ import { DataTreeAction, DataTreeWidget, ENTITY_TYPE, + DataTreeAppsmith, } from "entities/DataTree/dataTreeFactory"; export const arrayAccessorCyclicDependency: Record = { @@ -210,6 +211,71 @@ export const arrayAccessorCyclicDependency: Record = { privateWidgets: {}, meta: {}, } as unknown) as DataTreeWidget, + appsmith: ({ + user: { + email: "anand@appsmith.com", + workspaceIds: [ + "61431979a67ce2289d3c7c6d", + "61431a95a67ce2289d3c7c74", + "5f7add8687af934ed846dd6a", + "5f9fd13993794869fdbb8dcb", + "618b5af5da7cd651ee273112", + "604ef1c5c046f668d7bcc051", + "61b3389cd3e4214454c26bd1", + "61b3389cd3e4214454c26bd2", + "620a0d896b4b1e154a3c057a", + "620b37296b4b1e154a3c1fd7", + "60c1a5273535574772b6377b", + "6066e71a034ece74b1481ad2", + "623b36e34d9aea1b062b15b3", + "623b37de4d9aea1b062b170f", + "624fe51b457aa64da9e02ed3", + "6176537b515e45415cc7fd15", + "6206486d6b4b1e154a3be208", + ], + username: "anand@appsmith.com", + name: "Anand Srinivasan", + enableTelemetry: true, + idToken: { + sub: "109879730040206968321", + email_verified: true, + name: "Anand Srinivasan", + given_name: "Anand", + locale: "en", + hd: "appsmith.com", + family_name: "Srinivasan", + picture: + "https://lh3.googleusercontent.com/a-/AOh14Gi4HfYY0sKhaG93YAHB_E5-dL4BkFxdf8ZfQ2w7=s96-c", + email: "anand@appsmith.com", + }, + accountNonExpired: true, + accountNonLocked: true, + credentialsNonExpired: true, + emptyInstance: false, + isAnonymous: false, + isEnabled: true, + isSuperUser: false, + isConfigurable: true, + }, + URL: { + fullPath: + "https://app.appsmith.com/app/untitled-application-25/page1-6272179d8a368d6f1efcd0d2/edit", + host: "app.appsmith.com", + hostname: "app.appsmith.com", + queryParams: {}, + protocol: "https:", + pathname: + "/app/untitled-application-25/page1-6272179d8a368d6f1efcd0d2/edit", + port: "", + hash: "", + }, + store: {}, + geolocation: { + canBeRequested: true, + }, + mode: "EDIT", + ENTITY_TYPE: ENTITY_TYPE.APPSMITH, + } as unknown) as DataTreeAppsmith, }, apiSuccessUnEvalTree: { // success: response -> [{...}, {...}, {...}] diff --git a/app/client/src/workers/common/DataTreeEvaluator/mockData/NestedArrayAccessorTree.ts b/app/client/src/workers/common/DataTreeEvaluator/mockData/NestedArrayAccessorTree.ts index 9def250bed..b8452e5b5b 100644 --- a/app/client/src/workers/common/DataTreeEvaluator/mockData/NestedArrayAccessorTree.ts +++ b/app/client/src/workers/common/DataTreeEvaluator/mockData/NestedArrayAccessorTree.ts @@ -5,6 +5,7 @@ import { DataTreeAction, DataTreeWidget, ENTITY_TYPE, + DataTreeAppsmith, } from "entities/DataTree/dataTreeFactory"; export const nestedArrayAccessorCyclicDependency: Record = { @@ -210,6 +211,53 @@ export const nestedArrayAccessorCyclicDependency: Record = { privateWidgets: {}, meta: {}, } as unknown) as DataTreeWidget, + appsmith: ({ + user: { + email: "rathod@appsmith.com", + workspaceIds: [ + "6218a61972ccd9145ec78c57", + "621913df0276eb01d22fec44", + "60caf8edb1e47a1315f0c48f", + "609114fe05c4d35a9f6cbbf2", + ], + username: "rathod@appsmith.com", + name: "Rishabh", + commentOnboardingState: "RESOLVED", + role: "engineer", + useCase: "personal project", + enableTelemetry: false, + emptyInstance: false, + accountNonExpired: true, + accountNonLocked: true, + credentialsNonExpired: true, + isAnonymous: false, + isEnabled: true, + isSuperUser: false, + isConfigurable: true, + }, + URL: { + fullPath: + "https://dev.appsmith.com/applications/6200d1a2b5bfc0392b959cab/pages/6220c268c48234070f8ac65a/edit?a=b", + host: "dev.appsmith.com", + hostname: "dev.appsmith.com", + queryParams: { + a: "b", + }, + protocol: "https:", + pathname: + "/applications/6200d1a2b5bfc0392b959cab/pages/6220c268c48234070f8ac65a/edit", + port: "", + hash: "", + }, + store: { + textColor: "#DF7E65", + }, + geolocation: { + canBeRequested: true, + }, + mode: "EDIT", + ENTITY_TYPE: ENTITY_TYPE.APPSMITH, + } as unknown) as DataTreeAppsmith, }, apiSuccessUnEvalTree: { // success: response -> [ [{...}, {...}, {...}], [{...}, {...}, {...}], [{...}, {...}, {...}] ] diff --git a/app/client/src/workers/common/DataTreeEvaluator/mockData/mockUnEvalTree.ts b/app/client/src/workers/common/DataTreeEvaluator/mockData/mockUnEvalTree.ts index 8b923f724b..7cc76e0074 100644 --- a/app/client/src/workers/common/DataTreeEvaluator/mockData/mockUnEvalTree.ts +++ b/app/client/src/workers/common/DataTreeEvaluator/mockData/mockUnEvalTree.ts @@ -388,6 +388,7 @@ export const asyncTagUnevalTree: DataTree = { arguments: [], isAsync: false, confirmBeforeExecute: false, + body: "myFun1: () => {\n\t\treturn JSObject2.callApi();\n\t}", }, }, bindingPaths: { @@ -426,6 +427,7 @@ export const asyncTagUnevalTree: DataTree = { arguments: [], isAsync: false, confirmBeforeExecute: false, + body: "callApi: () => {\n\t\treturn Api1.run()\n\t}", }, }, bindingPaths: { @@ -1419,3 +1421,71 @@ export const unEvalTreeWidgetSelectWidget = { isDirty: false, }, }; + +export const emptyTreeWithAppsmithObject = ({ + appsmith: ({ + user: { + email: "anand@appsmith.com", + workspaceIds: [ + "61431979a67ce2289d3c7c6d", + "61431a95a67ce2289d3c7c74", + "5f7add8687af934ed846dd6a", + "5f9fd13993794869fdbb8dcb", + "618b5af5da7cd651ee273112", + "604ef1c5c046f668d7bcc051", + "61b3389cd3e4214454c26bd1", + "61b3389cd3e4214454c26bd2", + "620a0d896b4b1e154a3c057a", + "620b37296b4b1e154a3c1fd7", + "60c1a5273535574772b6377b", + "6066e71a034ece74b1481ad2", + "623b36e34d9aea1b062b15b3", + "623b37de4d9aea1b062b170f", + "624fe51b457aa64da9e02ed3", + "6176537b515e45415cc7fd15", + "6206486d6b4b1e154a3be208", + ], + username: "anand@appsmith.com", + name: "Anand Srinivasan", + enableTelemetry: true, + idToken: { + sub: "109879730040206968321", + email_verified: true, + name: "Anand Srinivasan", + given_name: "Anand", + locale: "en", + hd: "appsmith.com", + family_name: "Srinivasan", + picture: + "https://lh3.googleusercontent.com/a-/AOh14Gi4HfYY0sKhaG93YAHB_E5-dL4BkFxdf8ZfQ2w7=s96-c", + email: "anand@appsmith.com", + }, + accountNonExpired: true, + accountNonLocked: true, + credentialsNonExpired: true, + emptyInstance: false, + isAnonymous: false, + isEnabled: true, + isSuperUser: false, + isConfigurable: true, + }, + URL: { + fullPath: + "https://app.appsmith.com/app/untitled-application-25/page1-6272179d8a368d6f1efcd0d2/edit", + host: "app.appsmith.com", + hostname: "app.appsmith.com", + queryParams: {}, + protocol: "https:", + pathname: + "/app/untitled-application-25/page1-6272179d8a368d6f1efcd0d2/edit", + port: "", + hash: "", + }, + store: {}, + geolocation: { + canBeRequested: true, + }, + mode: "EDIT", + ENTITY_TYPE: ENTITY_TYPE.APPSMITH, + } as unknown) as DataTreeAppsmith, +} as unknown) as DataTree; diff --git a/app/client/src/workers/common/DataTreeEvaluator/test.ts b/app/client/src/workers/common/DataTreeEvaluator/test.ts index 284b280ae5..bcfa5f46d8 100644 --- a/app/client/src/workers/common/DataTreeEvaluator/test.ts +++ b/app/client/src/workers/common/DataTreeEvaluator/test.ts @@ -1,17 +1,22 @@ import DataTreeEvaluator from "."; import { asyncTagUnevalTree, + emptyTreeWithAppsmithObject, lintingUnEvalTree, unEvalTree, } from "./mockData/mockUnEvalTree"; import { DataTree } from "entities/DataTree/dataTreeFactory"; -import { DataTreeDiff } from "workers/Evaluation/evaluationUtils"; +import { + DataTreeDiff, + DataTreeDiffEvent, +} from "workers/Evaluation/evaluationUtils"; import { ALL_WIDGETS_AND_CONFIG } from "utils/WidgetRegistry"; import { arrayAccessorCyclicDependency } from "./mockData/ArrayAccessorTree"; import { nestedArrayAccessorCyclicDependency } from "./mockData/NestedArrayAccessorTree"; import { updateDependencyMap } from "workers/common/DependencyMap"; import { parseJSActions } from "workers/Evaluation/JSObject"; import { WidgetConfiguration } from "widgets/constants"; +import { JSUpdate, ParsedBody } from "../../../utils/JSPaneUtils"; const widgetConfigMap: Record< string, @@ -21,6 +26,7 @@ const widgetConfigMap: Record< metaProperties: WidgetConfiguration["properties"]["meta"]; } > = {}; + ALL_WIDGETS_AND_CONFIG.map(([, config]) => { if (config.type && config.properties) { widgetConfigMap[config.type] = { @@ -209,17 +215,21 @@ describe("DataTreeEvaluator", () => { describe("parseJsActions", () => { beforeEach(() => { - dataTreeEvaluator.setupFirstTree(({} as unknown) as DataTree); + dataTreeEvaluator.setupFirstTree( + (emptyTreeWithAppsmithObject as unknown) as DataTree, + ); dataTreeEvaluator.evalAndValidateFirstTree(); }); it("set's isAsync tag for cross JsObject references", () => { const result = parseJSActions(dataTreeEvaluator, asyncTagUnevalTree); - expect( - result.jsUpdates["JSObject1"]?.parsedBody?.actions[0].isAsync, - ).toBe(true); - expect( - result.jsUpdates["JSObject2"]?.parsedBody?.actions[0].isAsync, - ).toBe(true); + const jsUpdatesForJsObject1 = (result as Record)[ + "JSObject1" + ].parsedBody as ParsedBody; + const jsUpdatesForJsObject2 = (result as Record)[ + "JSObject2" + ].parsedBody as ParsedBody; + expect(jsUpdatesForJsObject1.actions[0].isAsync).toBe(true); + expect(jsUpdatesForJsObject2.actions[0].isAsync).toBe(true); }); }); diff --git a/app/shared/ast/index.ts b/app/shared/ast/index.ts index 4fb4231cbe..410670d439 100644 --- a/app/shared/ast/index.ts +++ b/app/shared/ast/index.ts @@ -21,14 +21,15 @@ import { import { ECMA_VERSION, SourceType, NodeTypes } from "./src/constants"; // JSObjects -import { parseJSObjectWithAST } from "./src/jsObject"; +import { parseJSObjectWithAST, JsObjectProperty } from "./src/jsObject"; -// types or intefaces should be exported with type keyword, while enums can be exported like normal functions +// types or interfaces should be exported with type keyword, while enums can be exported like normal functions export type { ObjectExpression, PropertyNode, MemberExpressionData, IdentifierInfo, + JsObjectProperty, }; export { diff --git a/app/shared/ast/src/jsObject/index.ts b/app/shared/ast/src/jsObject/index.ts index b3a4feff74..bb3c962728 100644 --- a/app/shared/ast/src/jsObject/index.ts +++ b/app/shared/ast/src/jsObject/index.ts @@ -11,7 +11,7 @@ import { functionParam, } from "../index"; -type JsObjectProperty = { +export type JsObjectProperty = { key: string; value: string; type: string;