From c4403435860840898c8d76dadf174c6418222e55 Mon Sep 17 00:00:00 2001 From: Rishabh Rathod Date: Fri, 2 Dec 2022 18:15:11 +0530 Subject: [PATCH] chore: perf data tree shrink (#18361) trimming dataTree object size by removing configs to make evaluation faster. --- app/client/src/constants/WidgetConstants.tsx | 14 + .../src/entities/DataTree/dataTreeAction.ts | 33 +- .../src/entities/DataTree/dataTreeFactory.ts | 165 +++---- .../DataTree/dataTreeJSAction.test.ts | 203 ++++---- .../src/entities/DataTree/dataTreeJSAction.ts | 32 +- .../entities/DataTree/dataTreeWidget.test.ts | 125 ++--- .../src/entities/DataTree/dataTreeWidget.ts | 79 ++-- app/client/src/entities/DataTree/types.ts | 126 +++++ app/client/src/entities/DataTree/utils.ts | 2 +- app/client/src/sagas/EvaluationsSaga.ts | 18 +- app/client/src/sagas/LintingSagas.ts | 9 +- app/client/src/sagas/PostEvaluationSagas.ts | 4 +- app/client/src/sagas/selectors.tsx | 18 +- app/client/src/selectors/dataTreeSelectors.ts | 4 +- app/client/src/utils/DSLMigrations.ts | 2 +- .../src/utils/WidgetLoadingStateUtils.test.ts | 1 + .../dataTreeTypeDefCreator.test.ts | 2 +- app/client/src/widgets/BaseWidget.tsx | 19 - .../src/widgets/ListWidget/widget/index.tsx | 2 +- .../src/workers/Evaluation/JSObject/utils.ts | 113 ++--- .../__tests__/dataTreeUtils.test.ts | 444 ++++++++++++++++++ .../Evaluation/__tests__/evaluate.test.ts | 2 +- .../Evaluation/__tests__/evaluation.test.ts | 61 +-- .../__tests__/evaluationUtils.test.ts | 51 +- .../src/workers/Evaluation/dataTreeUtils.ts | 54 +++ app/client/src/workers/Evaluation/evaluate.ts | 12 +- .../workers/Evaluation/evaluation.worker.ts | 53 ++- .../src/workers/Evaluation/evaluationUtils.ts | 7 +- app/client/src/workers/Evaluation/types.ts | 4 +- app/client/src/workers/Linting/types.ts | 3 - app/client/src/workers/Linting/utils.ts | 3 - .../workers/common/DataTreeEvaluator/index.ts | 32 +- .../mockData/mockUnEvalTree.ts | 39 -- .../DataTreeEvaluator/validationUtils.ts | 90 ++-- 34 files changed, 1209 insertions(+), 617 deletions(-) create mode 100644 app/client/src/entities/DataTree/types.ts create mode 100644 app/client/src/workers/Evaluation/__tests__/dataTreeUtils.test.ts create mode 100644 app/client/src/workers/Evaluation/dataTreeUtils.ts diff --git a/app/client/src/constants/WidgetConstants.tsx b/app/client/src/constants/WidgetConstants.tsx index ad57c04fbb..522f69a0f9 100644 --- a/app/client/src/constants/WidgetConstants.tsx +++ b/app/client/src/constants/WidgetConstants.tsx @@ -167,3 +167,17 @@ export const WidgetHeightLimits = { MIN_HEIGHT_IN_ROWS: 4, MIN_CANVAS_HEIGHT_IN_ROWS: 10, }; + +export const WIDGET_PROPS_TO_SKIP_FROM_EVAL = { + children: true, + parentId: true, + renderMode: true, + detachFromLayout: true, + noContainerOffset: false, + hideCard: true, + isDeprecated: true, + searchTags: true, + iconSVG: true, + version: true, + displayName: true, +}; diff --git a/app/client/src/entities/DataTree/dataTreeAction.ts b/app/client/src/entities/DataTree/dataTreeAction.ts index d1f8dc985a..9f7d47fd54 100644 --- a/app/client/src/entities/DataTree/dataTreeAction.ts +++ b/app/client/src/entities/DataTree/dataTreeAction.ts @@ -1,5 +1,8 @@ import { DependencyMap, DynamicPath } from "utils/DynamicBindingUtils"; -import { DataTreeAction, ENTITY_TYPE } from "entities/DataTree/dataTreeFactory"; +import { + ENTITY_TYPE, + UnEvalTreeAction, +} from "entities/DataTree/dataTreeFactory"; import { ActionData } from "reducers/entityReducers/actionsReducer"; import { getBindingAndReactivePathsOfAction, @@ -10,7 +13,7 @@ export const generateDataTreeAction = ( action: ActionData, editorConfig: any[], dependencyConfig: DependencyMap = {}, -): DataTreeAction => { +): UnEvalTreeAction => { let dynamicBindingPathList: DynamicPath[] = []; let datasourceUrl = ""; @@ -45,26 +48,30 @@ export const generateDataTreeAction = ( ); return { + actionId: action.config.id, run: {}, clear: {}, - actionId: action.config.id, - name: action.config.name, - pluginId: action.config.pluginId, - pluginType: action.config.pluginType, - config: action.config.actionConfiguration, - dynamicBindingPathList, data: action.data ? action.data.body : undefined, + isLoading: action.isLoading, responseMeta: { statusCode: action.data?.statusCode, isExecutionSuccess: action.data?.isExecutionSuccess || false, headers: action.data?.headers, }, + config: action.config.actionConfiguration, ENTITY_TYPE: ENTITY_TYPE.ACTION, - isLoading: action.isLoading, - bindingPaths, - reactivePaths, - dependencyMap, - logBlackList: {}, datasourceUrl, + __config__: { + actionId: action.config.id, + name: action.config.name, + pluginId: action.config.pluginId, + pluginType: action.config.pluginType, + dynamicBindingPathList, + ENTITY_TYPE: ENTITY_TYPE.ACTION, + bindingPaths, + reactivePaths, + dependencyMap, + logBlackList: {}, + }, }; }; diff --git a/app/client/src/entities/DataTree/dataTreeFactory.ts b/app/client/src/entities/DataTree/dataTreeFactory.ts index 2571d97e90..03300d50df 100644 --- a/app/client/src/entities/DataTree/dataTreeFactory.ts +++ b/app/client/src/entities/DataTree/dataTreeFactory.ts @@ -1,126 +1,60 @@ -import { - ActionDataState, - ActionDataWithMeta, -} from "reducers/entityReducers/actionsReducer"; +import { ActionDataState } from "reducers/entityReducers/actionsReducer"; import { WidgetProps } from "widgets/BaseWidget"; -import { ActionResponse } from "api/ActionAPI"; import { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer"; import { MetaState } from "reducers/entityReducers/metaReducer"; import { Page } from "@appsmith/constants/ReduxActionConstants"; -import { ActionConfig, PluginType } from "entities/Action"; import { AppDataState } from "reducers/entityReducers/appReducer"; -import { DependencyMap, DynamicPath } from "utils/DynamicBindingUtils"; +import { DependencyMap } from "utils/DynamicBindingUtils"; import { generateDataTreeAction } from "entities/DataTree/dataTreeAction"; import { generateDataTreeJSAction } from "entities/DataTree/dataTreeJSAction"; import { generateDataTreeWidget } from "entities/DataTree/dataTreeWidget"; import { JSCollectionDataState } from "reducers/entityReducers/jsActionsReducer"; -import { ValidationConfig } from "constants/PropertyControlConstants"; -import { Variable } from "entities/JSCollection"; -import { - ActionDescription, - ClearPluginActionDescription, - RunPluginActionDescription, -} from "entities/DataTree/actionTriggers"; import { AppTheme } from "entities/AppTheming"; -import { PluginId } from "api/PluginApi"; import log from "loglevel"; +import { WidgetConfigProps } from "reducers/entityReducers/widgetConfigReducer"; +import { + ActionDispatcher, + ActionEntityConfig, + ActionEntityEvalTree, + ENTITY_TYPE, + JSActionEntityConfig, + JSActionEvalTree, + WidgetConfig, + EvaluationSubstitutionType, +} from "./types"; -export type ActionDispatcher = ( - ...args: any[] -) => Promise | ActionDescription; - -export enum ENTITY_TYPE { - ACTION = "ACTION", - WIDGET = "WIDGET", - APPSMITH = "APPSMITH", - JSACTION = "JSACTION", +export interface UnEvalTreeAction extends ActionEntityEvalTree { + __config__: ActionEntityConfig; } - -export enum EvaluationSubstitutionType { - TEMPLATE = "TEMPLATE", - PARAMETER = "PARAMETER", - SMART_SUBSTITUTE = "SMART_SUBSTITUTE", -} - -// Private widgets do not get evaluated -// For example, for widget Button1 in a List widget List1, List1.template.Button1.text gets evaluated, -// so there is no need to evaluate Button1.text -export type PrivateWidgets = Record; - export interface DataTreeAction - extends Omit { - data: ActionResponse["body"]; - actionId: string; - config: Partial; - pluginType: PluginType; - pluginId: PluginId; - name: string; - run: ActionDispatcher | RunPluginActionDescription | Record; - clear: - | ActionDispatcher - | ClearPluginActionDescription - | Record; - dynamicBindingPathList: DynamicPath[]; - bindingPaths: Record; - reactivePaths: Record; - ENTITY_TYPE: ENTITY_TYPE.ACTION; - dependencyMap: DependencyMap; - logBlackList: Record; - datasourceUrl: string; + extends ActionEntityEvalTree, + ActionEntityConfig {} + +export interface UnEvalTreeJSAction extends JSActionEvalTree { + __config__: JSActionEntityConfig; } -export interface DataTreeJSAction { - pluginType: PluginType.JS; - name: string; - ENTITY_TYPE: ENTITY_TYPE.JSACTION; - body: string; - [propName: string]: any; - meta: Record; - dynamicBindingPathList: DynamicPath[]; - bindingPaths: Record; - reactivePaths: Record; - variables: Array; - dependencyMap: DependencyMap; +export type DataTreeJSAction = JSActionEvalTree & JSActionEntityConfig; + +export interface WidgetEntityConfig + extends Partial, + Omit, + WidgetConfig { + defaultMetaProps: Array; + type: string; } -export interface MetaArgs { - arguments: Variable[]; - isAsync: boolean; - confirmBeforeExecute: boolean; -} -/** - * Map of overriding property as key and overridden property as values - */ -export type OverridingPropertyPaths = Record; - -export enum OverridingPropertyType { - META = "META", - DEFAULT = "DEFAULT", -} -/** - * Map of property name as key and value as object with defaultPropertyName and metaPropertyName which it depends on. - */ -export type PropertyOverrideDependency = Record< - string, - { - DEFAULT: string | undefined; - META: string | undefined; - } ->; - -export interface DataTreeWidget extends WidgetProps { - bindingPaths: Record; - reactivePaths: Record; - triggerPaths: Record; - validationPaths: Record; - ENTITY_TYPE: ENTITY_TYPE.WIDGET; - logBlackList: Record; - propertyOverrideDependency: PropertyOverrideDependency; - overridingPropertyPaths: OverridingPropertyPaths; - privateWidgets: PrivateWidgets; +export interface WidgetEvalTree extends WidgetProps { meta: Record; + ENTITY_TYPE: ENTITY_TYPE.WIDGET; } +export interface UnEvalTreeWidget extends WidgetEvalTree { + __config__: WidgetEntityConfig; +} + +export interface DataTreeWidget extends WidgetEvalTree, WidgetConfig {} + export interface DataTreeAppsmith extends Omit { ENTITY_TYPE: ENTITY_TYPE.APPSMITH; store: Record; @@ -138,6 +72,20 @@ export type DataTree = { [entityName: string]: DataTreeEntity; }; +export type UnEvalTreeEntityObject = + | UnEvalTreeAction + | UnEvalTreeJSAction + | UnEvalTreeWidget; + +export type UnEvalTreeEntity = + | UnEvalTreeEntityObject + | DataTreeAppsmith + | Page[]; + +export type UnEvalTree = { + [entityName: string]: UnEvalTreeEntity; +}; + type DataTreeSeed = { actions: ActionDataState; editorConfigs: Record; @@ -150,6 +98,12 @@ type DataTreeSeed = { theme: AppTheme["properties"]; }; +export type DataTreeEntityConfig = + | WidgetEntityConfig + | ActionEntityConfig + | JSActionEntityConfig + | DataTreeAppsmith; + export class DataTreeFactory { static create({ actions, @@ -161,8 +115,8 @@ export class DataTreeFactory { theme, widgets, widgetsMeta, - }: DataTreeSeed): DataTree { - const dataTree: DataTree = {}; + }: DataTreeSeed): UnEvalTree { + const dataTree: UnEvalTree = {}; const start = performance.now(); const startActions = performance.now(); @@ -195,6 +149,7 @@ export class DataTreeFactory { const endWidgets = performance.now(); dataTree.pageList = pageList; + dataTree.appsmith = { ...appData, // combine both persistent and transient state with the transient state @@ -217,3 +172,5 @@ export class DataTreeFactory { return dataTree; } } + +export { ENTITY_TYPE, EvaluationSubstitutionType }; diff --git a/app/client/src/entities/DataTree/dataTreeJSAction.test.ts b/app/client/src/entities/DataTree/dataTreeJSAction.test.ts index a38d40da84..4119af5551 100644 --- a/app/client/src/entities/DataTree/dataTreeJSAction.test.ts +++ b/app/client/src/entities/DataTree/dataTreeJSAction.test.ts @@ -136,52 +136,10 @@ describe("generateDataTreeJSAction", () => { const expected = { myVar1: [], myVar2: {}, - name: "JSObject2", - actionId: "1234", - pluginType: "JS", 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}", - meta: { - myFun2: { - arguments: [], - isAsync: true, - confirmBeforeExecute: false, - }, - myFun1: { - arguments: [], - isAsync: false, - confirmBeforeExecute: false, - }, - }, - bindingPaths: { - body: "SMART_SUBSTITUTE", - myFun2: "SMART_SUBSTITUTE", - myFun1: "SMART_SUBSTITUTE", - myVar1: "SMART_SUBSTITUTE", - myVar2: "SMART_SUBSTITUTE", - }, - dynamicBindingPathList: [ - { - key: "body", - }, - { - key: "myVar1", - }, - { - key: "myVar2", - }, - { - key: "myFun2", - }, - { - key: "myFun1", - }, - ], - variables: ["myVar1", "myVar2"], - dependencyMap: { - body: ["myFun2", "myFun1"], - }, + myFun2: { data: { users: [{ id: 1, name: "John" }], @@ -190,12 +148,59 @@ describe("generateDataTreeJSAction", () => { myFun1: { data: {}, }, - reactivePaths: { - body: "SMART_SUBSTITUTE", - myFun1: "SMART_SUBSTITUTE", - myFun2: "SMART_SUBSTITUTE", - myVar1: "SMART_SUBSTITUTE", - myVar2: "SMART_SUBSTITUTE", + __config__: { + name: "JSObject2", + actionId: "1234", + pluginType: "JS", + ENTITY_TYPE: "JSACTION", + + meta: { + myFun2: { + arguments: [], + isAsync: true, + confirmBeforeExecute: false, + }, + myFun1: { + arguments: [], + isAsync: false, + confirmBeforeExecute: false, + }, + }, + bindingPaths: { + body: "SMART_SUBSTITUTE", + myFun2: "SMART_SUBSTITUTE", + myFun1: "SMART_SUBSTITUTE", + myVar1: "SMART_SUBSTITUTE", + myVar2: "SMART_SUBSTITUTE", + }, + dynamicBindingPathList: [ + { + key: "body", + }, + { + key: "myVar1", + }, + { + key: "myVar2", + }, + { + key: "myFun2", + }, + { + key: "myFun1", + }, + ], + variables: ["myVar1", "myVar2"], + dependencyMap: { + body: ["myFun2", "myFun1"], + }, + reactivePaths: { + body: "SMART_SUBSTITUTE", + myFun1: "SMART_SUBSTITUTE", + myFun2: "SMART_SUBSTITUTE", + myVar1: "SMART_SUBSTITUTE", + myVar2: "SMART_SUBSTITUTE", + }, }, }; const result = generateDataTreeJSAction(jsCollection); @@ -335,52 +340,63 @@ describe("generateDataTreeJSAction", () => { const expected = { myVar1: [], myVar2: {}, - name: "JSObject2", - actionId: "1234", - pluginType: "JS", - ENTITY_TYPE: "JSACTION", body: "export default {\n\tmyVar1: [],\n\tmyVar2: {},\n\tmyFun1: () => {\n\t\t//write code here\n\t return JSObject2.myFun2},\n\tmyFun2: async () => {\n\t\t//use async-await or promises\n\t}\n}", - meta: { - myFun2: { - arguments: [], - isAsync: true, - confirmBeforeExecute: false, + ENTITY_TYPE: "JSACTION", + __config__: { + ENTITY_TYPE: "JSACTION", + meta: { + myFun2: { + arguments: [], + isAsync: true, + confirmBeforeExecute: false, + }, + myFun1: { + arguments: [], + isAsync: false, + confirmBeforeExecute: false, + }, }, - myFun1: { - arguments: [], - isAsync: false, - confirmBeforeExecute: false, + bindingPaths: { + body: "SMART_SUBSTITUTE", + myFun2: "SMART_SUBSTITUTE", + myFun1: "SMART_SUBSTITUTE", + myVar1: "SMART_SUBSTITUTE", + myVar2: "SMART_SUBSTITUTE", + }, + dynamicBindingPathList: [ + { + key: "body", + }, + { + key: "myVar1", + }, + { + key: "myVar2", + }, + { + key: "myFun2", + }, + { + key: "myFun1", + }, + ], + variables: ["myVar1", "myVar2"], + dependencyMap: { + body: ["myFun2", "myFun1"], + }, + name: "JSObject2", + actionId: "1234", + pluginType: "JS", + reactivePaths: { + body: "SMART_SUBSTITUTE", + myFun1: "SMART_SUBSTITUTE", + myFun2: "SMART_SUBSTITUTE", + myVar1: "SMART_SUBSTITUTE", + myVar2: "SMART_SUBSTITUTE", }, }, - bindingPaths: { - body: "SMART_SUBSTITUTE", - myFun2: "SMART_SUBSTITUTE", - myFun1: "SMART_SUBSTITUTE", - myVar1: "SMART_SUBSTITUTE", - myVar2: "SMART_SUBSTITUTE", - }, - dynamicBindingPathList: [ - { - key: "body", - }, - { - key: "myVar1", - }, - { - key: "myVar2", - }, - { - key: "myFun2", - }, - { - key: "myFun1", - }, - ], - variables: ["myVar1", "myVar2"], - dependencyMap: { - body: ["myFun2", "myFun1"], - }, + myFun2: { data: { users: [{ id: 1, name: "John" }], @@ -389,13 +405,6 @@ describe("generateDataTreeJSAction", () => { myFun1: { data: {}, }, - reactivePaths: { - body: "SMART_SUBSTITUTE", - myFun1: "SMART_SUBSTITUTE", - myFun2: "SMART_SUBSTITUTE", - myVar1: "SMART_SUBSTITUTE", - myVar2: "SMART_SUBSTITUTE", - }, }; const result = generateDataTreeJSAction(jsCollection); diff --git a/app/client/src/entities/DataTree/dataTreeJSAction.ts b/app/client/src/entities/DataTree/dataTreeJSAction.ts index f8e7427d16..8e9aeaba94 100644 --- a/app/client/src/entities/DataTree/dataTreeJSAction.ts +++ b/app/client/src/entities/DataTree/dataTreeJSAction.ts @@ -1,17 +1,18 @@ import { - DataTreeJSAction, ENTITY_TYPE, - MetaArgs, + UnEvalTreeJSAction, } from "entities/DataTree/dataTreeFactory"; + import { JSCollectionData } from "reducers/entityReducers/jsActionsReducer"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; import { DependencyMap } from "utils/DynamicBindingUtils"; +import { MetaArgs } from "./types"; const reg = /this\./g; export const generateDataTreeJSAction = ( js: JSCollectionData, -): DataTreeJSAction => { +): UnEvalTreeJSAction => { const meta: Record = {}; const dynamicBindingPathList = []; const bindingPaths: Record = {}; @@ -54,17 +55,20 @@ export const generateDataTreeJSAction = ( } return { ...variableList, - name: js.config.name, - actionId: js.config.id, - pluginType: js.config.pluginType, - ENTITY_TYPE: ENTITY_TYPE.JSACTION, - body: removeThisReference, - meta: meta, - bindingPaths: bindingPaths, // As all js object function referred to as action is user javascript code, we add them as binding paths. - reactivePaths: { ...bindingPaths }, - dynamicBindingPathList: dynamicBindingPathList, - variables: listVariables, - dependencyMap: dependencyMap, ...actionsData, + body: removeThisReference, + ENTITY_TYPE: ENTITY_TYPE.JSACTION, + __config__: { + meta: meta, + name: js.config.name, + actionId: js.config.id, + pluginType: js.config.pluginType, + ENTITY_TYPE: ENTITY_TYPE.JSACTION, + bindingPaths: bindingPaths, // As all js object function referred to as action is user javascript code, we add them as binding paths. + reactivePaths: { ...bindingPaths }, + dynamicBindingPathList: dynamicBindingPathList, + variables: listVariables, + dependencyMap: dependencyMap, + }, }; }; diff --git a/app/client/src/entities/DataTree/dataTreeWidget.test.ts b/app/client/src/entities/DataTree/dataTreeWidget.test.ts index f935ebadc4..79e3097cee 100644 --- a/app/client/src/entities/DataTree/dataTreeWidget.test.ts +++ b/app/client/src/entities/DataTree/dataTreeWidget.test.ts @@ -1,14 +1,13 @@ import { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer"; import { generateDataTreeWidget } from "entities/DataTree/dataTreeWidget"; import { - DataTreeWidget, ENTITY_TYPE, EvaluationSubstitutionType, } from "entities/DataTree/dataTreeFactory"; -import { RenderModes } from "constants/WidgetConstants"; import WidgetFactory from "utils/WidgetFactory"; import { ValidationTypes } from "constants/WidgetValidation"; +import { RenderModes } from "constants/WidgetConstants"; // const WidgetTypes = WidgetFactory.widgetTypes; @@ -207,51 +206,62 @@ describe("generateDataTreeWidget", () => { errorMessage: EvaluationSubstitutionType.TEMPLATE, }; - const expected: DataTreeWidget = { - bindingPaths, - reactivePaths: { - ...bindingPaths, - isDirty: EvaluationSubstitutionType.TEMPLATE, - isFocused: EvaluationSubstitutionType.TEMPLATE, - isValid: EvaluationSubstitutionType.TEMPLATE, - text: EvaluationSubstitutionType.TEMPLATE, - value: EvaluationSubstitutionType.TEMPLATE, - "meta.text": EvaluationSubstitutionType.TEMPLATE, - }, - meta: { - text: "Tester", - isDirty: true, - deepObj: { - level1: { - metaValue: 10, + const expected = { + __config__: { + ENTITY_TYPE: ENTITY_TYPE.WIDGET, + bindingPaths, + reactivePaths: { + ...bindingPaths, + isDirty: EvaluationSubstitutionType.TEMPLATE, + isFocused: EvaluationSubstitutionType.TEMPLATE, + isValid: EvaluationSubstitutionType.TEMPLATE, + text: EvaluationSubstitutionType.TEMPLATE, + value: EvaluationSubstitutionType.TEMPLATE, + "meta.text": EvaluationSubstitutionType.TEMPLATE, + }, + + triggerPaths: { + onSubmit: true, + onTextChanged: true, + }, + type: "INPUT_WIDGET_V2", + validationPaths: { + defaultText: { type: ValidationTypes.TEXT }, + errorMessage: { type: ValidationTypes.TEXT }, + isDisabled: { type: ValidationTypes.BOOLEAN }, + isRequired: { type: ValidationTypes.BOOLEAN }, + isVisible: { type: ValidationTypes.BOOLEAN }, + placeholderText: { type: ValidationTypes.TEXT }, + regex: { type: ValidationTypes.REGEX }, + resetOnSubmit: { type: ValidationTypes.BOOLEAN }, + }, + dynamicBindingPathList: [ + { + key: "isValid", + }, + { + key: "value", + }, + ], + logBlackList: { + isValid: true, + value: true, + }, + propertyOverrideDependency: { + text: { + DEFAULT: "defaultText", + META: "meta.text", }, }, - }, - triggerPaths: { - onSubmit: true, - onTextChanged: true, - }, - validationPaths: { - defaultText: { type: ValidationTypes.TEXT }, - errorMessage: { type: ValidationTypes.TEXT }, - isDisabled: { type: ValidationTypes.BOOLEAN }, - isRequired: { type: ValidationTypes.BOOLEAN }, - isVisible: { type: ValidationTypes.BOOLEAN }, - placeholderText: { type: ValidationTypes.TEXT }, - regex: { type: ValidationTypes.REGEX }, - resetOnSubmit: { type: ValidationTypes.BOOLEAN }, - }, - dynamicBindingPathList: [ - { - key: "isValid", + defaultMetaProps: ["text", "isDirty", "isFocused"], + defaultProps: { + text: "defaultText", }, - { - key: "value", + overridingPropertyPaths: { + defaultText: ["text", "meta.text"], + "meta.text": ["text"], }, - ], - logBlackList: { - isValid: true, - value: true, + privateWidgets: {}, }, value: "{{Input1.text}}", isDirty: true, @@ -263,35 +273,28 @@ describe("generateDataTreeWidget", () => { leftColumn: 0, parentColumnSpace: 0, parentRowSpace: 0, - propertyOverrideDependency: { - text: { - DEFAULT: "defaultText", - META: "meta.text", - }, - }, - renderMode: RenderModes.CANVAS, rightColumn: 0, - topRow: 0, - type: "INPUT_WIDGET_V2", + renderMode: RenderModes.CANVAS, version: 0, + topRow: 0, widgetId: "123", widgetName: "Input1", ENTITY_TYPE: ENTITY_TYPE.WIDGET, defaultText: "", - defaultMetaProps: ["text", "isDirty", "isFocused"], - defaultProps: { - text: "defaultText", - }, - overridingPropertyPaths: { - defaultText: ["text", "meta.text"], - "meta.text": ["text"], - }, - privateWidgets: {}, deepObj: { level1: { metaValue: 10, }, }, + meta: { + text: "Tester", + isDirty: true, + deepObj: { + level1: { + metaValue: 10, + }, + }, + }, }; const result = generateDataTreeWidget(widget, widgetMetaProps); diff --git a/app/client/src/entities/DataTree/dataTreeWidget.ts b/app/client/src/entities/DataTree/dataTreeWidget.ts index 0df5871628..a2e41a7ec0 100644 --- a/app/client/src/entities/DataTree/dataTreeWidget.ts +++ b/app/client/src/entities/DataTree/dataTreeWidget.ts @@ -2,15 +2,22 @@ import { getAllPathsFromPropertyConfig } from "entities/Widget/utils"; import _ from "lodash"; import memoize from "micro-memoize"; import { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer"; -import { getEntityDynamicBindingPathList } from "utils/DynamicBindingUtils"; +import { + DynamicPath, + getEntityDynamicBindingPathList, +} from "utils/DynamicBindingUtils"; import WidgetFactory from "utils/WidgetFactory"; import { - DataTreeWidget, ENTITY_TYPE, + WidgetEntityConfig, + UnEvalTreeWidget, +} from "./dataTreeFactory"; +import { OverridingPropertyPaths, OverridingPropertyType, PropertyOverrideDependency, -} from "./dataTreeFactory"; +} from "./types"; + import { setOverridingProperty } from "./utils"; // We are splitting generateDataTreeWidget into two parts to memoize better as the widget doesn't change very often. @@ -19,9 +26,10 @@ import { setOverridingProperty } from "./utils"; const generateDataTreeWidgetWithoutMeta = ( widget: FlattenedWidgetProps, ): { - dataTreeWidgetWithoutMetaProps: DataTreeWidget; + dataTreeWidgetWithoutMetaProps: UnEvalTreeWidget; overridingMetaPropsMap: Record; defaultMetaProps: Record; + entityConfig: WidgetEntityConfig; } => { const derivedProps: any = {}; const blockedDerivedProps: Record = {}; @@ -130,14 +138,38 @@ const generateDataTreeWidgetWithoutMeta = ( * * Therefore spread is replaced with "merge" which merges objects recursively. */ + + const widgetPathsToOmit = [ + "dynamicBindingPathList", + "dynamicPropertyPathList", + "dynamicTriggerPathList", + "privateWidgets", + "type", + ]; + const dataTreeWidgetWithoutMetaProps = _.merge( - {}, - widget, - unInitializedDefaultProps, - // defaultMetaProps, - // widgetMetaProps, - derivedProps, { + ENTITY_TYPE: ENTITY_TYPE.WIDGET, + }, + _.omit(widget, widgetPathsToOmit), + unInitializedDefaultProps, + derivedProps, + ); + + const dynamicPathsList: { + dynamicPropertyPathList?: DynamicPath[]; + dynamicTriggerPathList?: DynamicPath[]; + } = {}; + if (widget.dynamicPropertyPathList) + dynamicPathsList.dynamicPropertyPathList = widget.dynamicPropertyPathList; + if (widget.dynamicTriggerPathList) + dynamicPathsList.dynamicTriggerPathList = widget.dynamicTriggerPathList; + + return { + dataTreeWidgetWithoutMetaProps, + overridingMetaPropsMap, + defaultMetaProps, + entityConfig: { defaultProps, defaultMetaProps: Object.keys(defaultMetaProps), dynamicBindingPathList, @@ -145,9 +177,6 @@ const generateDataTreeWidgetWithoutMeta = ( ...widget.logBlackList, ...blockedDerivedProps, }, - meta: {}, // this will be overridden by meta value calculated in generateDataTreeWidget - propertyOverrideDependency, - overridingPropertyPaths, bindingPaths, reactivePaths, triggerPaths, @@ -156,31 +185,20 @@ const generateDataTreeWidgetWithoutMeta = ( privateWidgets: { ...widget.privateWidgets, }, + propertyOverrideDependency, + overridingPropertyPaths, + type: widget.type, + ...dynamicPathsList, }, - ); - return { - dataTreeWidgetWithoutMetaProps, - overridingMetaPropsMap, - defaultMetaProps, }; }; // @todo set the max size dynamically based on number of widgets. (widgets.length) -// Remove the debug statements in July 2022 + const generateDataTreeWidgetWithoutMetaMemoized = memoize( generateDataTreeWidgetWithoutMeta, { maxSize: 1000, - // onCacheHit: (cache, options) => { - // console.log("####### cache was hit: ", cache.keys.length); - // }, - // onCacheAdd: (cache, options) => { - // console.log( - // "####### cache was missed ", - // cache.keys.length, - // cache.keys[0][0].widgetName, - // ); - // }, }, ); @@ -191,6 +209,7 @@ export const generateDataTreeWidget = ( const { dataTreeWidgetWithoutMetaProps: dataTreeWidget, defaultMetaProps, + entityConfig, overridingMetaPropsMap, } = generateDataTreeWidgetWithoutMetaMemoized(widget); const overridingMetaProps: Record = {}; @@ -215,5 +234,7 @@ export const generateDataTreeWidget = ( }); dataTreeWidget["meta"] = meta; + dataTreeWidget["__config__"] = entityConfig; + return dataTreeWidget; }; diff --git a/app/client/src/entities/DataTree/types.ts b/app/client/src/entities/DataTree/types.ts new file mode 100644 index 0000000000..d613d4ef9a --- /dev/null +++ b/app/client/src/entities/DataTree/types.ts @@ -0,0 +1,126 @@ +import { ActionResponse } from "api/ActionAPI"; +import { PluginId } from "api/PluginApi"; +import { ValidationConfig } from "constants/PropertyControlConstants"; +import { ActionConfig, PluginType } from "entities/Action"; +import { + ActionDescription, + ClearPluginActionDescription, + RunPluginActionDescription, +} from "entities/DataTree/actionTriggers"; +import { Variable } from "entities/JSCollection"; +import { DependencyMap, DynamicPath } from "utils/DynamicBindingUtils"; + +export type ActionDispatcher = ( + ...args: any[] +) => Promise | ActionDescription; + +export enum ENTITY_TYPE { + ACTION = "ACTION", + WIDGET = "WIDGET", + APPSMITH = "APPSMITH", + JSACTION = "JSACTION", +} + +export enum EvaluationSubstitutionType { + TEMPLATE = "TEMPLATE", + PARAMETER = "PARAMETER", + SMART_SUBSTITUTE = "SMART_SUBSTITUTE", +} + +// Action entity types +export interface ActionEntityEvalTree { + actionId: string; + isLoading: boolean; + data: ActionResponse["body"]; + run: ActionDispatcher | RunPluginActionDescription | Record; + clear: + | ActionDispatcher + | ClearPluginActionDescription + | Record; + responseMeta: { + statusCode?: string; + isExecutionSuccess: boolean; + headers?: unknown; + }; + ENTITY_TYPE: ENTITY_TYPE.ACTION; + config: Partial; + datasourceUrl: string; +} + +export interface ActionEntityConfig { + dynamicBindingPathList: DynamicPath[]; + bindingPaths: Record; + reactivePaths: Record; + ENTITY_TYPE: ENTITY_TYPE.ACTION; + dependencyMap: DependencyMap; + logBlackList: Record; + pluginType: PluginType; + pluginId: PluginId; + actionId: string; + name: string; +} + +// JSAction (JSObject) entity Types + +export interface MetaArgs { + arguments: Variable[]; + isAsync: boolean; + confirmBeforeExecute: boolean; +} + +export interface JSActionEntityConfig { + meta: Record; + dynamicBindingPathList: DynamicPath[]; + bindingPaths: Record; + reactivePaths: Record; + variables: Array; + dependencyMap: DependencyMap; + pluginType: PluginType.JS; + name: string; + ENTITY_TYPE: ENTITY_TYPE.JSACTION; + actionId: string; +} + +export interface JSActionEvalTree { + [propName: string]: any; + body: string; +} + +// Widget entity Types + +// Private widgets do not get evaluated +// For example, for widget Button1 in a List widget List1, List1.template.Button1.text gets evaluated, +// so there is no need to evaluate Button1.text +export type PrivateWidgets = Record; + +/** + * Map of overriding property as key and overridden property as values + */ +export type OverridingPropertyPaths = Record; + +export enum OverridingPropertyType { + META = "META", + DEFAULT = "DEFAULT", +} +/** + * Map of property name as key and value as object with defaultPropertyName and metaPropertyName which it depends on. + */ +export type PropertyOverrideDependency = Record< + string, + { + DEFAULT: string | undefined; + META: string | undefined; + } +>; + +export type WidgetConfig = { + bindingPaths: Record; + reactivePaths: Record; + triggerPaths: Record; + validationPaths: Record; + ENTITY_TYPE: ENTITY_TYPE.WIDGET; + logBlackList: Record; + propertyOverrideDependency: PropertyOverrideDependency; + overridingPropertyPaths: OverridingPropertyPaths; + privateWidgets: PrivateWidgets; +}; diff --git a/app/client/src/entities/DataTree/utils.ts b/app/client/src/entities/DataTree/utils.ts index a0b96189fc..4989849aaa 100644 --- a/app/client/src/entities/DataTree/utils.ts +++ b/app/client/src/entities/DataTree/utils.ts @@ -2,7 +2,7 @@ import { PropertyOverrideDependency, OverridingPropertyPaths, OverridingPropertyType, -} from "./dataTreeFactory"; +} from "./types"; type SetOverridingPropertyParams = { key: string; diff --git a/app/client/src/sagas/EvaluationsSaga.ts b/app/client/src/sagas/EvaluationsSaga.ts index d1b6494b99..21a90bcd8c 100644 --- a/app/client/src/sagas/EvaluationsSaga.ts +++ b/app/client/src/sagas/EvaluationsSaga.ts @@ -94,7 +94,11 @@ import { FormEvalActionPayload } from "./FormEvaluationSaga"; import { getSelectedAppTheme } from "selectors/appThemingSelectors"; import { updateMetaState } from "actions/metaActions"; import { getAllActionValidationConfig } from "selectors/entitiesSelector"; -import { DataTree } from "entities/DataTree/dataTreeFactory"; +import { + DataTree, + UnEvalTree, + UnEvalTreeWidget, +} from "entities/DataTree/dataTreeFactory"; import { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer"; import { AppTheme } from "entities/AppTheming"; import { ActionValidationConfigMap } from "constants/PropertyControlConstants"; @@ -125,7 +129,7 @@ function* evaluateTreeSaga( const allActionValidationConfig: { [actionId: string]: ActionValidationConfigMap; } = yield select(getAllActionValidationConfig); - const unevalTree: DataTree = yield select(getUnevaluatedDataTree); + const unevalTree: UnEvalTree = yield select(getUnevaluatedDataTree); const widgets: CanvasWidgetsReduxState = yield select(getWidgets); const theme: AppTheme = yield select(getSelectedAppTheme); const appMode: APP_MODE | undefined = yield select(getAppMode); @@ -270,7 +274,7 @@ export function* evaluateAndExecuteDynamicTrigger( evalWorker.duplexRequest, EVAL_WORKER_ACTIONS.EVAL_TRIGGER, { - dataTree: unEvalTree, + unEvalTree, dynamicTrigger, callbackData, globalContext, @@ -383,7 +387,6 @@ export function* executeDynamicTriggerRequest( if (requestData.type === EVAL_WORKER_ACTIONS.LINT_TREE) { yield spawn(lintTreeSaga, { pathsToLint: requestData.lintOrder, - jsUpdates: requestData.jsUpdates, unevalTree: requestData.unevalTree, }); } @@ -526,9 +529,9 @@ export function* validateProperty( value: any, props: WidgetProps, ) { - const unevalTree: DataTree = yield select(getUnevaluatedDataTree); - // @ts-expect-error: We have a typeMismatch for validationPaths - const validation = unevalTree[props.widgetName].validationPaths[property]; + const unevalTree: UnEvalTree = yield select(getUnevaluatedDataTree); + const entity = unevalTree[props.widgetName] as UnEvalTreeWidget; + const validation = entity?.__config__.validationPaths[property]; const response: unknown = yield call( evalWorker.request, EVAL_WORKER_ACTIONS.VALIDATE_PROPERTY, @@ -539,7 +542,6 @@ export function* validateProperty( validation, }, ); - return response; } diff --git a/app/client/src/sagas/LintingSagas.ts b/app/client/src/sagas/LintingSagas.ts index 4373ee8d53..907d671b0a 100644 --- a/app/client/src/sagas/LintingSagas.ts +++ b/app/client/src/sagas/LintingSagas.ts @@ -3,7 +3,6 @@ import { APP_MODE } from "entities/App"; import { call, put, select } from "redux-saga/effects"; import { getAppMode } from "selectors/entitiesSelector"; import { GracefulWorkerService } from "utils/WorkerUtil"; -import { getUpdatedLocalUnEvalTreeAfterJSUpdates } from "workers/Evaluation/JSObject"; import { LintTreeRequest, LintTreeResponse, @@ -20,7 +19,6 @@ export const lintWorker = new GracefulWorkerService( ); export function* lintTreeSaga({ - jsUpdates, pathsToLint, unevalTree, }: LintTreeSagaRequestData) { @@ -28,14 +26,9 @@ export function* lintTreeSaga({ const appMode: APP_MODE = yield select(getAppMode); if (appMode !== APP_MODE.EDIT) return; - const updatedUnevalTree = getUpdatedLocalUnEvalTreeAfterJSUpdates( - jsUpdates, - unevalTree, - ); const lintTreeRequestData: LintTreeRequest = { - jsUpdates, pathsToLint, - unevalTree: updatedUnevalTree, + unevalTree, }; const { errors }: LintTreeResponse = yield call( diff --git a/app/client/src/sagas/PostEvaluationSagas.ts b/app/client/src/sagas/PostEvaluationSagas.ts index 7d26638303..ad8fdf8c39 100644 --- a/app/client/src/sagas/PostEvaluationSagas.ts +++ b/app/client/src/sagas/PostEvaluationSagas.ts @@ -4,7 +4,7 @@ import { PLATFORM_ERROR, Severity, } from "entities/AppsmithConsole"; -import { DataTree } from "entities/DataTree/dataTreeFactory"; +import { DataTree, UnEvalTree } from "entities/DataTree/dataTreeFactory"; import { DataTreeDiff, DataTreeDiffEvent, @@ -281,7 +281,7 @@ export function* evalErrorHandler( } export function* logSuccessfulBindings( - unEvalTree: DataTree, + unEvalTree: UnEvalTree, dataTree: DataTree, evaluationOrder: string[], isCreateFirstTree: boolean, diff --git a/app/client/src/sagas/selectors.tsx b/app/client/src/sagas/selectors.tsx index 27f0f48754..450038843d 100644 --- a/app/client/src/sagas/selectors.tsx +++ b/app/client/src/sagas/selectors.tsx @@ -5,8 +5,11 @@ import { FlattenedWidgetProps, } from "reducers/entityReducers/canvasWidgetsReducer"; import { WidgetProps } from "widgets/BaseWidget"; -import _ from "lodash"; -import { WidgetType } from "constants/WidgetConstants"; +import _, { omit } from "lodash"; +import { + WidgetType, + WIDGET_PROPS_TO_SKIP_FROM_EVAL, +} from "constants/WidgetConstants"; import { ActionData } from "reducers/entityReducers/actionsReducer"; import { Page } from "@appsmith/constants/ReduxActionConstants"; import { getActions, getPlugins } from "selectors/entitiesSelector"; @@ -16,6 +19,17 @@ export const getWidgets = (state: AppState): CanvasWidgetsReduxState => { return state.entities.canvasWidgets; }; +export const getWidgetsForEval = createSelector(getWidgets, (widgets) => { + const widgetForEval: CanvasWidgetsReduxState = {}; + for (const key of Object.keys(widgets)) { + widgetForEval[key] = omit( + widgets[key], + Object.keys(WIDGET_PROPS_TO_SKIP_FROM_EVAL), + ) as FlattenedWidgetProps; + } + return widgetForEval; +}); + export const getWidgetsMeta = (state: AppState) => state.entities.meta; export const getWidgetMetaProps = createSelector( diff --git a/app/client/src/selectors/dataTreeSelectors.ts b/app/client/src/selectors/dataTreeSelectors.ts index 0f4542325a..ee432b4549 100644 --- a/app/client/src/selectors/dataTreeSelectors.ts +++ b/app/client/src/selectors/dataTreeSelectors.ts @@ -11,7 +11,7 @@ import { DataTreeFactory, DataTreeWidget, } from "entities/DataTree/dataTreeFactory"; -import { getWidgets, getWidgetsMeta } from "sagas/selectors"; +import { getWidgetsForEval, getWidgetsMeta } from "sagas/selectors"; import "url-search-params-polyfill"; import { getPageList } from "./appViewSelectors"; import { AppState } from "@appsmith/reducers"; @@ -23,7 +23,7 @@ import { EvaluationError, getEvalErrorPath } from "utils/DynamicBindingUtils"; export const getUnevaluatedDataTree = createSelector( getActionsForCurrentPage, getJSCollectionsForCurrentPage, - getWidgets, + getWidgetsForEval, getWidgetsMeta, getPageList, getAppData, diff --git a/app/client/src/utils/DSLMigrations.ts b/app/client/src/utils/DSLMigrations.ts index 8a777347bd..8e288d1f4c 100644 --- a/app/client/src/utils/DSLMigrations.ts +++ b/app/client/src/utils/DSLMigrations.ts @@ -51,7 +51,7 @@ import { migrateCheckboxGroupWidgetInlineProperty } from "./migrations/CheckboxG import { migrateMapWidgetIsClickedMarkerCentered } from "./migrations/MapWidget"; import { DSLWidget } from "widgets/constants"; import { migrateRecaptchaType } from "./migrations/ButtonWidgetMigrations"; -import { PrivateWidgets } from "entities/DataTree/dataTreeFactory"; +import { PrivateWidgets } from "entities/DataTree/types"; import { migrateStylingPropertiesForTheming } from "./migrations/ThemingMigrations"; import { diff --git a/app/client/src/utils/WidgetLoadingStateUtils.test.ts b/app/client/src/utils/WidgetLoadingStateUtils.test.ts index 2e4937176c..f30d692a1b 100644 --- a/app/client/src/utils/WidgetLoadingStateUtils.test.ts +++ b/app/client/src/utils/WidgetLoadingStateUtils.test.ts @@ -23,6 +23,7 @@ const JS_object_tree: DataTreeJSAction = { reactivePaths: {}, variables: [], dependencyMap: {}, + actionId: "", }; // @ts-expect-error: meta property not provided diff --git a/app/client/src/utils/autocomplete/dataTreeTypeDefCreator.test.ts b/app/client/src/utils/autocomplete/dataTreeTypeDefCreator.test.ts index 09ce377cee..ae9169fa4c 100644 --- a/app/client/src/utils/autocomplete/dataTreeTypeDefCreator.test.ts +++ b/app/client/src/utils/autocomplete/dataTreeTypeDefCreator.test.ts @@ -13,7 +13,6 @@ import { entityDefinitions } from "utils/autocomplete/EntityDefinitions"; describe("dataTreeTypeDefCreator", () => { it("creates the right def for a widget", () => { - // @ts-expect-error: meta property not provided const dataTreeEntity: DataTreeWidget = { widgetId: "yolo", widgetName: "Input1", @@ -44,6 +43,7 @@ describe("dataTreeTypeDefCreator", () => { propertyOverrideDependency: {}, overridingPropertyPaths: {}, privateWidgets: {}, + meta: {}, }; const { def, entityInfo } = dataTreeTypeDefCreator( { diff --git a/app/client/src/widgets/BaseWidget.tsx b/app/client/src/widgets/BaseWidget.tsx index 3d341672cb..7e6865fb53 100644 --- a/app/client/src/widgets/BaseWidget.tsx +++ b/app/client/src/widgets/BaseWidget.tsx @@ -557,25 +557,6 @@ export interface WidgetPositionProps extends WidgetRowCols { noContainerOffset?: boolean; // This won't offset the child in parent } -export const WIDGET_STATIC_PROPS = { - leftColumn: true, - rightColumn: true, - topRow: true, - bottomRow: true, - minHeight: true, - parentColumnSpace: true, - parentRowSpace: true, - children: true, - type: true, - widgetId: true, - widgetName: true, - parentId: true, - renderMode: true, - detachFromLayout: true, - noContainerOffset: false, - height: false, -}; - export const WIDGET_DISPLAY_PROPS = { isVisible: true, isLoading: true, diff --git a/app/client/src/widgets/ListWidget/widget/index.tsx b/app/client/src/widgets/ListWidget/widget/index.tsx index 41cccd90db..e5dd0cc009 100644 --- a/app/client/src/widgets/ListWidget/widget/index.tsx +++ b/app/client/src/widgets/ListWidget/widget/index.tsx @@ -39,7 +39,7 @@ import { ValidationTypes } from "constants/WidgetValidation"; import derivedProperties from "./parseDerivedProperties"; import { DSLWidget } from "widgets/constants"; import { entityDefinitions } from "utils/autocomplete/EntityDefinitions"; -import { PrivateWidgets } from "entities/DataTree/dataTreeFactory"; +import { PrivateWidgets } from "entities/DataTree/types"; import equal from "fast-deep-equal/es6"; import { klona } from "klona/lite"; import { Stylesheet } from "entities/AppTheming"; diff --git a/app/client/src/workers/Evaluation/JSObject/utils.ts b/app/client/src/workers/Evaluation/JSObject/utils.ts index 3b29e551f9..c0068d96d1 100644 --- a/app/client/src/workers/Evaluation/JSObject/utils.ts +++ b/app/client/src/workers/Evaluation/JSObject/utils.ts @@ -29,6 +29,8 @@ export const updateJSCollectionInUnEvalTree = ( functionsList.push(action); }); + const oldConfig = Object.getPrototypeOf(jsCollection) as DataTreeJSAction; + if (parsedBody.actions && parsedBody.actions.length > 0) { for (let i = 0; i < parsedBody.actions.length; i++) { const action = parsedBody.actions[i]; @@ -52,37 +54,26 @@ export const updateJSCollectionInUnEvalTree = ( ); } } else { - const reactivePaths = jsCollection.reactivePaths; + const reactivePaths = oldConfig.reactivePaths; + reactivePaths[action.name] = EvaluationSubstitutionType.SMART_SUBSTITUTE; reactivePaths[`${action.name}.data`] = EvaluationSubstitutionType.TEMPLATE; - set( - modifiedUnEvalTree, - `${jsCollection.name}.reactivePaths`, - reactivePaths, - ); - const dynamicBindingPathList = jsCollection.dynamicBindingPathList; + + const dynamicBindingPathList = oldConfig.dynamicBindingPathList; dynamicBindingPathList.push({ key: action.name }); - set( - modifiedUnEvalTree, - `${jsCollection.name}.dynamicBindingPathList`, - dynamicBindingPathList, - ); - const dependencyMap = jsCollection.dependencyMap; + + const dependencyMap = oldConfig.dependencyMap; dependencyMap["body"].push(action.name); - set( - modifiedUnEvalTree, - `${jsCollection.name}.dependencyMap`, - dependencyMap, - ); - const meta = jsCollection.meta; + + const meta = oldConfig.meta; meta[action.name] = { arguments: action.arguments, isAsync: false, confirmBeforeExecute: false, }; - set(modifiedUnEvalTree, `${jsCollection.name}.meta`, meta); + const data = get( modifiedUnEvalTree, `${jsCollection.name}.${action.name}.data`, @@ -108,37 +99,23 @@ export const updateJSCollectionInUnEvalTree = ( (js: ParsedJSSubAction) => js.name === oldActionName, ); if (!existed) { - const reactivePaths = jsCollection.reactivePaths; + const reactivePaths = oldConfig.reactivePaths; delete reactivePaths[oldActionName]; - set( - modifiedUnEvalTree, - `${jsCollection.name}.reactivePaths`, - reactivePaths, - ); - let dynamicBindingPathList = jsCollection.dynamicBindingPathList; - dynamicBindingPathList = dynamicBindingPathList.filter( + + oldConfig.dynamicBindingPathList = oldConfig.dynamicBindingPathList.filter( (path) => path["key"] !== oldActionName, ); - set( - modifiedUnEvalTree, - `${jsCollection.name}.dynamicBindingPathList`, - dynamicBindingPathList, - ); - const dependencyMap = jsCollection.dependencyMap["body"]; + + const dependencyMap = oldConfig.dependencyMap["body"]; const removeIndex = dependencyMap.indexOf(oldActionName); if (removeIndex > -1) { - const updatedDMap = dependencyMap.filter( + oldConfig.dependencyMap["body"] = dependencyMap.filter( (item) => item !== oldActionName, ); - set( - modifiedUnEvalTree, - `${jsCollection.name}.dependencyMap.body`, - updatedDMap, - ); } - const meta = jsCollection.meta; + const meta = oldConfig.meta; delete meta[oldActionName]; - set(modifiedUnEvalTree, `${jsCollection.name}.meta`, meta); + unset(modifiedUnEvalTree[jsCollection.name], oldActionName); unset(modifiedUnEvalTree[jsCollection.name], `${oldActionName}.data`); } @@ -163,21 +140,12 @@ export const updateJSCollectionInUnEvalTree = ( } } else { varList.push(newVar.name); - const reactivePaths = jsCollection.reactivePaths; + const reactivePaths = oldConfig.reactivePaths; reactivePaths[newVar.name] = EvaluationSubstitutionType.SMART_SUBSTITUTE; - set( - modifiedUnEvalTree, - `${jsCollection.name}.reactivePaths`, - reactivePaths, - ); - const dynamicBindingPathList = jsCollection.dynamicBindingPathList; + + const dynamicBindingPathList = oldConfig.dynamicBindingPathList; dynamicBindingPathList.push({ key: newVar.name }); - set( - modifiedUnEvalTree, - `${jsCollection.name}.dynamicBindingPathList`, - dynamicBindingPathList, - ); set(modifiedUnEvalTree, `${jsCollection.name}.variables`, varList); set( @@ -194,23 +162,12 @@ export const updateJSCollectionInUnEvalTree = ( (item) => item.name === varListItem, ); if (!existsInParsed) { - const reactivePaths = jsCollection.reactivePaths; + const reactivePaths = oldConfig.reactivePaths; delete reactivePaths[varListItem]; - set( - modifiedUnEvalTree, - `${jsCollection.name}.reactivePaths`, - reactivePaths, - ); - let dynamicBindingPathList = jsCollection.dynamicBindingPathList; - dynamicBindingPathList = dynamicBindingPathList.filter( + oldConfig.dynamicBindingPathList = oldConfig.dynamicBindingPathList.filter( (path) => path["key"] !== varListItem, ); - set( - modifiedUnEvalTree, - `${jsCollection.name}.dynamicBindingPathList`, - dynamicBindingPathList, - ); newVarList = newVarList.filter((item) => item !== varListItem); unset(modifiedUnEvalTree[jsCollection.name], varListItem); @@ -234,6 +191,7 @@ export const removeFunctionsAndVariableJSCollection = ( unEvalTree: DataTree, entity: DataTreeJSAction, ) => { + const oldConfig = Object.getPrototypeOf(entity) as DataTreeJSAction; const modifiedDataTree: DataTree = unEvalTree; const functionsList: Array = []; Object.keys(entity.meta).forEach((action) => { @@ -247,28 +205,25 @@ export const removeFunctionsAndVariableJSCollection = ( unset(modifiedDataTree[entity.name], varName); } //remove functions - let dynamicBindingPathList = entity.dynamicBindingPathList; + const reactivePaths = entity.reactivePaths; const meta = entity.meta; - let dependencyMap = entity.dependencyMap["body"]; + for (let i = 0; i < functionsList.length; i++) { const actionName = functionsList[i]; delete reactivePaths[actionName]; delete meta[actionName]; unset(modifiedDataTree[entity.name], actionName); - dynamicBindingPathList = dynamicBindingPathList.filter( + + oldConfig.dynamicBindingPathList = oldConfig.dynamicBindingPathList.filter( (path: any) => path["key"] !== actionName, ); - dependencyMap = dependencyMap.filter((item: any) => item !== actionName); + + entity.dependencyMap["body"] = entity.dependencyMap["body"].filter( + (item: any) => item !== actionName, + ); } - set(modifiedDataTree, `${entity.name}.reactivePaths`, reactivePaths); - set( - modifiedDataTree, - `${entity.name}.dynamicBindingPathList`, - dynamicBindingPathList, - ); - set(modifiedDataTree, `${entity.name}.dependencyMap.body`, dependencyMap); - set(modifiedDataTree, `${entity.name}.meta`, meta); + return modifiedDataTree; }; diff --git a/app/client/src/workers/Evaluation/__tests__/dataTreeUtils.test.ts b/app/client/src/workers/Evaluation/__tests__/dataTreeUtils.test.ts new file mode 100644 index 0000000000..00b91ba47d --- /dev/null +++ b/app/client/src/workers/Evaluation/__tests__/dataTreeUtils.test.ts @@ -0,0 +1,444 @@ +import { + UnEvalTree, + UnEvalTreeAction, +} from "entities/DataTree/dataTreeFactory"; +import { + createNewEntity, + createUnEvalTreeForEval, + makeEntityConfigsAsObjProperties, +} from "../dataTreeUtils"; + +const unevalTreeFromMainThread = { + Api2: { + actionId: "6380b1003a20d922b774eb75", + run: {}, + clear: {}, + isLoading: false, + responseMeta: { + isExecutionSuccess: false, + }, + config: {}, + ENTITY_TYPE: "ACTION", + datasourceUrl: "https://www.facebook.com", + __config__: { + actionId: "6380b1003a20d922b774eb75", + name: "Api2", + pluginId: "5ca385dc81b37f0004b4db85", + pluginType: "API", + dynamicBindingPathList: [ + { + key: "config.path", + }, + ], + ENTITY_TYPE: "ACTION", + bindingPaths: { + "config.path": "TEMPLATE", + "config.body": "SMART_SUBSTITUTE", + "config.pluginSpecifiedTemplates[1].value": "SMART_SUBSTITUTE", + }, + reactivePaths: { + data: "TEMPLATE", + isLoading: "TEMPLATE", + datasourceUrl: "TEMPLATE", + "config.path": "TEMPLATE", + "config.body": "SMART_SUBSTITUTE", + "config.pluginSpecifiedTemplates[1].value": "SMART_SUBSTITUTE", + "config.pluginSpecifiedTemplates[2].value.limitBased.limit.value": + "SMART_SUBSTITUTE", + }, + dependencyMap: { + "config.body": ["config.pluginSpecifiedTemplates[0].value"], + }, + logBlackList: {}, + }, + }, + JSObject1: { + newFunction: { + data: {}, + }, + storeTest2: { + data: {}, + }, + body: + "export default {\n\tstoreTest2: () => {\n\t\tlet values = [\n\t\t\t\t\tstoreValue('val1', 'number 1'),\n\t\t\t\t\tstoreValue('val2', 'number 2'),\n\t\t\t\t\tstoreValue('val3', 'number 3'),\n\t\t\t\t\tstoreValue('val4', 'number 4')\n\t\t\t\t];\n\t\treturn Promise.all(values)\n\t\t\t.then(() => {\n\t\t\tshowAlert(JSON.stringify(appsmith.store))\n\t\t})\n\t\t\t.catch((err) => {\n\t\t\treturn showAlert('Could not store values in store ' + err.toString());\n\t\t})\n\t},\n\tnewFunction: function() {\n\t\tJSObject1.storeTest()\n\t}\n}", + ENTITY_TYPE: "JSACTION", + __config__: { + meta: { + newFunction: { + arguments: [], + isAsync: false, + confirmBeforeExecute: false, + }, + storeTest2: { + arguments: [], + isAsync: true, + confirmBeforeExecute: false, + }, + }, + name: "JSObject1", + actionId: "637cda3b2f8e175c6f5269d5", + pluginType: "JS", + ENTITY_TYPE: "JSACTION", + bindingPaths: { + body: "SMART_SUBSTITUTE", + newFunction: "SMART_SUBSTITUTE", + storeTest2: "SMART_SUBSTITUTE", + }, + reactivePaths: { + body: "SMART_SUBSTITUTE", + newFunction: "SMART_SUBSTITUTE", + storeTest2: "SMART_SUBSTITUTE", + }, + dynamicBindingPathList: [ + { + key: "body", + }, + { + key: "newFunction", + }, + { + key: "storeTest2", + }, + ], + variables: [], + dependencyMap: { + body: ["newFunction", "storeTest2"], + }, + }, + }, + MainContainer: { + ENTITY_TYPE: "WIDGET", + widgetName: "MainContainer", + backgroundColor: "none", + rightColumn: 1224, + snapColumns: 64, + widgetId: "0", + topRow: 0, + bottomRow: 1240, + containerStyle: "none", + snapRows: 124, + parentRowSpace: 1, + canExtend: true, + minHeight: 1250, + parentColumnSpace: 1, + leftColumn: 0, + meta: {}, + __config__: { + defaultProps: {}, + defaultMetaProps: [], + dynamicBindingPathList: [], + logBlackList: {}, + bindingPaths: {}, + reactivePaths: {}, + triggerPaths: {}, + validationPaths: {}, + ENTITY_TYPE: "WIDGET", + privateWidgets: {}, + propertyOverrideDependency: {}, + overridingPropertyPaths: {}, + type: "CANVAS_WIDGET", + }, + }, + Button2: { + ENTITY_TYPE: "WIDGET", + resetFormOnClick: false, + boxShadow: "none", + widgetName: "Button2", + buttonColor: "{{appsmith.theme.colors.primaryColor}}", + topRow: 3, + bottomRow: 7, + parentRowSpace: 10, + animateLoading: true, + parentColumnSpace: 34.5, + leftColumn: 31, + text: "test", + isDisabled: false, + key: "oypcoe6gx4", + rightColumn: 47, + isDefaultClickDisabled: true, + widgetId: "vxpz4ta27g", + isVisible: true, + recaptchaType: "V3", + isLoading: false, + disabledWhenInvalid: false, + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + buttonVariant: "PRIMARY", + placement: "CENTER", + meta: {}, + __config__: { + defaultProps: {}, + defaultMetaProps: ["recaptchaToken"], + dynamicBindingPathList: [ + { + key: "buttonColor", + }, + { + key: "borderRadius", + }, + ], + logBlackList: {}, + bindingPaths: { + text: "TEMPLATE", + tooltip: "TEMPLATE", + isVisible: "TEMPLATE", + isDisabled: "TEMPLATE", + animateLoading: "TEMPLATE", + googleRecaptchaKey: "TEMPLATE", + recaptchaType: "TEMPLATE", + disabledWhenInvalid: "TEMPLATE", + resetFormOnClick: "TEMPLATE", + buttonVariant: "TEMPLATE", + iconName: "TEMPLATE", + placement: "TEMPLATE", + buttonColor: "TEMPLATE", + borderRadius: "TEMPLATE", + boxShadow: "TEMPLATE", + }, + reactivePaths: { + recaptchaToken: "TEMPLATE", + buttonColor: "TEMPLATE", + borderRadius: "TEMPLATE", + text: "TEMPLATE", + tooltip: "TEMPLATE", + isVisible: "TEMPLATE", + isDisabled: "TEMPLATE", + animateLoading: "TEMPLATE", + googleRecaptchaKey: "TEMPLATE", + recaptchaType: "TEMPLATE", + disabledWhenInvalid: "TEMPLATE", + resetFormOnClick: "TEMPLATE", + buttonVariant: "TEMPLATE", + iconName: "TEMPLATE", + placement: "TEMPLATE", + boxShadow: "TEMPLATE", + }, + triggerPaths: { + onClick: true, + }, + validationPaths: { + text: { + type: "TEXT", + }, + tooltip: { + type: "TEXT", + }, + isVisible: { + type: "BOOLEAN", + }, + isDisabled: { + type: "BOOLEAN", + }, + animateLoading: { + type: "BOOLEAN", + }, + googleRecaptchaKey: { + type: "TEXT", + }, + recaptchaType: { + type: "TEXT", + params: { + allowedValues: ["V3", "V2"], + default: "V3", + }, + }, + disabledWhenInvalid: { + type: "BOOLEAN", + }, + resetFormOnClick: { + type: "BOOLEAN", + }, + buttonVariant: { + type: "TEXT", + params: { + allowedValues: ["PRIMARY", "SECONDARY", "TERTIARY"], + default: "PRIMARY", + }, + }, + iconName: { + type: "TEXT", + }, + placement: { + type: "TEXT", + params: { + allowedValues: ["START", "BETWEEN", "CENTER"], + default: "CENTER", + }, + }, + buttonColor: { + type: "TEXT", + }, + borderRadius: { + type: "TEXT", + }, + boxShadow: { + type: "TEXT", + }, + }, + ENTITY_TYPE: "WIDGET", + privateWidgets: {}, + propertyOverrideDependency: {}, + overridingPropertyPaths: {}, + type: "BUTTON_WIDGET", + dynamicTriggerPathList: [], + }, + }, + pageList: [ + { + pageName: "Page1", + pageId: "63349fb5d39f215f89b8245e", + isDefault: false, + isHidden: false, + slug: "page1", + }, + { + pageName: "Page2", + pageId: "637cc6b4a3664a7fe679b7b0", + isDefault: true, + isHidden: false, + slug: "page2", + }, + ], + appsmith: { + user: { + email: "someuser@appsmith.com", + username: "someuser@appsmith.com", + name: "Some name", + enableTelemetry: true, + emptyInstance: false, + accountNonExpired: true, + accountNonLocked: true, + credentialsNonExpired: true, + isAnonymous: false, + isEnabled: true, + isSuperUser: false, + isConfigurable: true, + }, + URL: { + fullPath: "", + host: "dev.appsmith.com", + hostname: "dev.appsmith.com", + queryParams: {}, + protocol: "https:", + pathname: "", + port: "", + hash: "", + }, + store: { + val1: "number 1", + val2: "number 2", + }, + geolocation: { + canBeRequested: true, + currentPosition: {}, + }, + mode: "EDIT", + theme: { + colors: { + primaryColor: "#553DE9", + backgroundColor: "#F6F6F6", + }, + borderRadius: { + appBorderRadius: "0.375rem", + }, + boxShadow: { + appBoxShadow: + "0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)", + }, + fontFamily: { + appFont: "Nunito Sans", + }, + }, + ENTITY_TYPE: "APPSMITH", + }, +}; + +describe("7. Test util methods", () => { + it("1. createUnEvalTree method", () => { + const unEvalTreeForEval = createUnEvalTreeForEval( + (unevalTreeFromMainThread as unknown) as UnEvalTree, + ); + // Action config + expect(unEvalTreeForEval).toHaveProperty( + "Api2.dynamicBindingPathList", + unevalTreeFromMainThread.Api2.__config__.dynamicBindingPathList, + ); + expect(unEvalTreeForEval).toHaveProperty( + "Api2.bindingPaths", + unevalTreeFromMainThread.Api2.__config__.bindingPaths, + ); + expect(unEvalTreeForEval).toHaveProperty( + "Api2.reactivePaths", + unevalTreeFromMainThread.Api2.__config__.reactivePaths, + ); + + // widget config + expect(unEvalTreeForEval).toHaveProperty( + "Button2.dynamicBindingPathList", + unevalTreeFromMainThread.Button2.__config__.dynamicBindingPathList, + ); + expect(unEvalTreeForEval).toHaveProperty( + "Button2.bindingPaths", + unevalTreeFromMainThread.Button2.__config__.bindingPaths, + ); + expect(unEvalTreeForEval).toHaveProperty( + "Button2.reactivePaths", + unevalTreeFromMainThread.Button2.__config__.reactivePaths, + ); + + // appsmith object config + expect(unEvalTreeForEval).toHaveProperty( + "appsmith", + unevalTreeFromMainThread.appsmith, + ); + + // JSObject config + expect(unEvalTreeForEval).toHaveProperty( + "JSObject1.dynamicBindingPathList", + unevalTreeFromMainThread.JSObject1.__config__.dynamicBindingPathList, + ); + expect(unEvalTreeForEval).toHaveProperty( + "JSObject1.bindingPaths", + unevalTreeFromMainThread.JSObject1.__config__.bindingPaths, + ); + expect(unEvalTreeForEval).toHaveProperty( + "JSObject1.reactivePaths", + unevalTreeFromMainThread.JSObject1.__config__.reactivePaths, + ); + }); + + it("2. createNewEntity method", () => { + const actionForEval = createNewEntity( + (unevalTreeFromMainThread.Api2 as unknown) as UnEvalTreeAction, + ); + // Action config + expect(actionForEval).toHaveProperty( + "dynamicBindingPathList", + unevalTreeFromMainThread.Api2.__config__.dynamicBindingPathList, + ); + expect(actionForEval).not.toHaveProperty("__config__"); + + const widgetForEval = createNewEntity( + (unevalTreeFromMainThread.Button2 as unknown) as UnEvalTreeAction, + ); + // widget config + expect(widgetForEval).toHaveProperty( + "dynamicBindingPathList", + unevalTreeFromMainThread.Button2.__config__.dynamicBindingPathList, + ); + expect(widgetForEval).not.toHaveProperty("__config__"); + }); + + it("3. makeDataTreeEntityConfigAsProperty method", () => { + const unEvalTreeForEval = createUnEvalTreeForEval( + (unevalTreeFromMainThread as unknown) as UnEvalTree, + ); + const dataTree = makeEntityConfigsAsObjProperties(unEvalTreeForEval); + + expect(dataTree.Api2).not.toHaveProperty("__config__"); + + expect(dataTree.Api2).toHaveProperty( + "dynamicBindingPathList", + unevalTreeFromMainThread.Api2.__config__.dynamicBindingPathList, + ); + }); +}); diff --git a/app/client/src/workers/Evaluation/__tests__/evaluate.test.ts b/app/client/src/workers/Evaluation/__tests__/evaluate.test.ts index f3c9f2636a..753c9bbf8d 100644 --- a/app/client/src/workers/Evaluation/__tests__/evaluate.test.ts +++ b/app/client/src/workers/Evaluation/__tests__/evaluate.test.ts @@ -11,7 +11,6 @@ import { import { RenderModes } from "constants/WidgetConstants"; describe("evaluateSync", () => { - // @ts-expect-error: meta property not provided const widget: DataTreeWidget = { bottomRow: 0, isLoading: false, @@ -35,6 +34,7 @@ describe("evaluateSync", () => { overridingPropertyPaths: {}, privateWidgets: {}, propertyOverrideDependency: {}, + meta: {}, }; const dataTree: DataTree = { Input1: widget, diff --git a/app/client/src/workers/Evaluation/__tests__/evaluation.test.ts b/app/client/src/workers/Evaluation/__tests__/evaluation.test.ts index 8bd3be144b..5e98f07600 100644 --- a/app/client/src/workers/Evaluation/__tests__/evaluation.test.ts +++ b/app/client/src/workers/Evaluation/__tests__/evaluation.test.ts @@ -3,6 +3,7 @@ import { DataTreeWidget, ENTITY_TYPE, EvaluationSubstitutionType, + UnEvalTree, } from "entities/DataTree/dataTreeFactory"; import { WidgetTypeConfigMap } from "utils/WidgetFactory"; import { RenderModes } from "constants/WidgetConstants"; @@ -12,6 +13,7 @@ import { ValidationTypes } from "constants/WidgetValidation"; import WidgetFactory from "utils/WidgetFactory"; import { generateDataTreeWidget } from "entities/DataTree/dataTreeWidget"; import { sortObjectWithArray } from "../../../utils/treeUtils"; +import { createUnEvalTreeForEval } from "../dataTreeUtils"; const WIDGET_CONFIG_MAP: WidgetTypeConfigMap = { CONTAINER_WIDGET: { @@ -217,8 +219,7 @@ const WIDGET_CONFIG_MAP: WidgetTypeConfigMap = { }, }; -// @ts-expect-error: meta is required -const BASE_WIDGET: DataTreeWidget = { +const BASE_WIDGET = ({ logBlackList: {}, widgetId: "randomID", widgetName: "randomWidgetName", @@ -233,15 +234,9 @@ const BASE_WIDGET: DataTreeWidget = { type: "SKELETON_WIDGET", parentId: "0", version: 1, - bindingPaths: {}, - reactivePaths: {}, - triggerPaths: {}, - validationPaths: {}, ENTITY_TYPE: ENTITY_TYPE.WIDGET, - propertyOverrideDependency: {}, - overridingPropertyPaths: {}, - privateWidgets: {}, -}; + meta: {}, +} as unknown) as DataTreeWidget; export const BASE_ACTION: DataTreeAction = { clear: {}, @@ -350,7 +345,7 @@ describe("DataTreeEvaluator", () => { }, {}, ); - const unEvalTree: Record = { + const unEvalTree: UnEvalTree = { Text1: generateDataTreeWidget( { ...BASE_WIDGET, @@ -424,7 +419,7 @@ describe("DataTreeEvaluator", () => { ), }; const evaluator = new DataTreeEvaluator(WIDGET_CONFIG_MAP); - evaluator.setupFirstTree(unEvalTree); + evaluator.setupFirstTree(createUnEvalTreeForEval(unEvalTree)); evaluator.evalAndValidateFirstTree(); it("Evaluates a binding in first run", () => { const evaluation = evaluator.evalTree; @@ -446,7 +441,8 @@ describe("DataTreeEvaluator", () => { const { evalOrder, nonDynamicFieldValidationOrder, - } = evaluator.setupUpdateTree(updatedUnEvalTree); + } = evaluator.setupUpdateTree(createUnEvalTreeForEval(updatedUnEvalTree)); + evaluator.evalAndValidateSubTree(evalOrder, nonDynamicFieldValidationOrder); const dataTree = evaluator.evalTree; expect(dataTree).toHaveProperty("Text2.text", "Hey there"); @@ -464,7 +460,7 @@ describe("DataTreeEvaluator", () => { const { evalOrder, nonDynamicFieldValidationOrder, - } = evaluator.setupUpdateTree(updatedUnEvalTree); + } = evaluator.setupUpdateTree(createUnEvalTreeForEval(updatedUnEvalTree)); evaluator.evalAndValidateSubTree(evalOrder, nonDynamicFieldValidationOrder); const dataTree = evaluator.evalTree; @@ -482,10 +478,11 @@ describe("DataTreeEvaluator", () => { ...unEvalTree, Input1, }; + const { evalOrder, nonDynamicFieldValidationOrder, - } = evaluator.setupUpdateTree(updatedUnEvalTree); + } = evaluator.setupUpdateTree(createUnEvalTreeForEval(updatedUnEvalTree)); evaluator.evalAndValidateSubTree(evalOrder, nonDynamicFieldValidationOrder); const dataTree = evaluator.evalTree; expect(dataTree).toHaveProperty("Input1.text", "Default value"); @@ -499,7 +496,7 @@ describe("DataTreeEvaluator", () => { isVisible: EvaluationSubstitutionType.TEMPLATE, isDisabled: EvaluationSubstitutionType.TEMPLATE, }; - const updatedUnEvalTree = { + const updatedUnEvalTree = ({ ...unEvalTree, Dropdown2: { ...BASE_WIDGET, @@ -522,19 +519,21 @@ describe("DataTreeEvaluator", () => { selectedOptionValue: EvaluationSubstitutionType.TEMPLATE, selectedOptionLabel: EvaluationSubstitutionType.TEMPLATE, }, + propertyOverrideDependency: {}, + validationPaths: {}, }, - }; + } as unknown) as UnEvalTree; const { evalOrder, nonDynamicFieldValidationOrder, - } = evaluator.setupUpdateTree(updatedUnEvalTree); + } = evaluator.setupUpdateTree(createUnEvalTreeForEval(updatedUnEvalTree)); evaluator.evalAndValidateSubTree(evalOrder, nonDynamicFieldValidationOrder); const dataTree = evaluator.evalTree; expect(dataTree).toHaveProperty("Dropdown2.options.0.label", "newValue"); }); it("Adds an entity with a complicated binding", () => { - const updatedUnEvalTree = { + const updatedUnEvalTree = ({ ...unEvalTree, Api1: { ...BASE_ACTION, @@ -548,11 +547,11 @@ describe("DataTreeEvaluator", () => { }, ], }, - }; + } as unknown) as UnEvalTree; const { evalOrder, nonDynamicFieldValidationOrder, - } = evaluator.setupUpdateTree(updatedUnEvalTree); + } = evaluator.setupUpdateTree(createUnEvalTreeForEval(updatedUnEvalTree)); evaluator.evalAndValidateSubTree(evalOrder, nonDynamicFieldValidationOrder); const dataTree = evaluator.evalTree; const updatedDependencyMap = evaluator.dependencyMap; @@ -576,7 +575,7 @@ describe("DataTreeEvaluator", () => { }); it("Selects a row", () => { - const updatedUnEvalTree = { + const updatedUnEvalTree = ({ ...unEvalTree, Table1: { ...unEvalTree.Table1, @@ -598,11 +597,11 @@ describe("DataTreeEvaluator", () => { }, ], }, - }; + } as unknown) as UnEvalTree; const { evalOrder, nonDynamicFieldValidationOrder, - } = evaluator.setupUpdateTree(updatedUnEvalTree); + } = evaluator.setupUpdateTree(createUnEvalTreeForEval(updatedUnEvalTree)); evaluator.evalAndValidateSubTree(evalOrder, nonDynamicFieldValidationOrder); const dataTree = evaluator.evalTree; const updatedDependencyMap = evaluator.dependencyMap; @@ -629,7 +628,7 @@ describe("DataTreeEvaluator", () => { const updatedTree1 = { ...unEvalTree, Text1: { - ...BASE_WIDGET, + ...unEvalTree.Text1, text: "Test", }, Api2: { @@ -655,7 +654,9 @@ describe("DataTreeEvaluator", () => { const { evalOrder, nonDynamicFieldValidationOrder: nonDynamicFieldValidationOrder2, - } = evaluator.setupUpdateTree(updatedTree1); + } = evaluator.setupUpdateTree( + createUnEvalTreeForEval((updatedTree1 as unknown) as UnEvalTree), + ); evaluator.evalAndValidateSubTree( evalOrder, nonDynamicFieldValidationOrder2, @@ -682,7 +683,9 @@ describe("DataTreeEvaluator", () => { const { evalOrder: newEvalOrder, nonDynamicFieldValidationOrder, - } = evaluator.setupUpdateTree(updatedTree2); + } = evaluator.setupUpdateTree( + createUnEvalTreeForEval((updatedTree2 as unknown) as UnEvalTree), + ); evaluator.evalAndValidateSubTree( newEvalOrder, nonDynamicFieldValidationOrder, @@ -715,7 +718,9 @@ describe("DataTreeEvaluator", () => { const { evalOrder: newEvalOrder2, nonDynamicFieldValidationOrder: nonDynamicFieldValidationOrder3, - } = evaluator.setupUpdateTree(updatedTree3); + } = evaluator.setupUpdateTree( + createUnEvalTreeForEval((updatedTree3 as unknown) as UnEvalTree), + ); evaluator.evalAndValidateSubTree( newEvalOrder2, nonDynamicFieldValidationOrder3, diff --git a/app/client/src/workers/Evaluation/__tests__/evaluationUtils.test.ts b/app/client/src/workers/Evaluation/__tests__/evaluationUtils.test.ts index 1f091b91f1..6e66a127df 100644 --- a/app/client/src/workers/Evaluation/__tests__/evaluationUtils.test.ts +++ b/app/client/src/workers/Evaluation/__tests__/evaluationUtils.test.ts @@ -6,8 +6,8 @@ import { DataTreeWidget, ENTITY_TYPE, EvaluationSubstitutionType, - PrivateWidgets, } from "entities/DataTree/dataTreeFactory"; +import { PrivateWidgets } from "entities/DataTree/types"; import { DataTreeDiff, DataTreeDiffEvent, @@ -30,6 +30,8 @@ import InputWidget, { CONFIG as InputWidgetV2Config, } from "widgets/InputWidgetV2"; import { registerWidget } from "utils/WidgetRegisterHelpers"; +import { WidgetConfiguration } from "widgets/constants"; +import { createNewEntity } from "../dataTreeUtils"; // to check if logWarn was called. // use jest.unmock, if the mock needs to be removed. @@ -137,8 +139,8 @@ const testDataTree: Record = { }, }; -describe("Correctly handle paths", () => { - it("getsAllPaths", () => { +describe("1. Correctly handle paths", () => { + it("1. getsAllPaths", () => { const myTree = { WidgetName: { 1: "yo", @@ -177,8 +179,8 @@ describe("Correctly handle paths", () => { }); }); -describe("privateWidgets", () => { - it("correctly checks if path is a PrivateEntityPath", () => { +describe("2. privateWidgets", () => { + it("1. correctly checks if path is a PrivateEntityPath", () => { const privateWidgets: PrivateWidgets = { Button1: true, Image1: true, @@ -196,7 +198,7 @@ describe("privateWidgets", () => { expect(isPrivateEntityPath(privateWidgets, "Image2.data")).toBeTruthy(); }); - it("Returns list of all privateWidgets", () => { + it("2. Returns list of all privateWidgets", () => { const expectedPrivateWidgetsList = { Text2: true, Text3: true, @@ -209,7 +211,7 @@ describe("privateWidgets", () => { expect(expectedPrivateWidgetsList).toStrictEqual(actualPrivateWidgetsList); }); - it("Returns data tree without privateWidgets", () => { + it("3. Returns data tree without privateWidgets", () => { const expectedDataTreeWithoutPrivateWidgets: Record< string, DataTreeWidget @@ -274,8 +276,8 @@ describe("privateWidgets", () => { }); }); -describe("makeParentsDependOnChildren", () => { - it("makes parent properties depend on child properties", () => { +describe("3. makeParentsDependOnChildren", () => { + it("1. makes parent properties depend on child properties", () => { let depMap: DependencyMap = { Widget1: [], "Widget1.defaultText": [], @@ -294,7 +296,7 @@ describe("makeParentsDependOnChildren", () => { }); }); - it("logs warning for child properties not listed in allKeys", () => { + it("2. logs warning for child properties not listed in allKeys", () => { const depMap: DependencyMap = { Widget1: [], "Widget1.defaultText": [], @@ -310,8 +312,8 @@ describe("makeParentsDependOnChildren", () => { }); }); -describe("translateDiffEvent", () => { - it("noop when diff path does not exist", () => { +describe("4. translateDiffEvent", () => { + it("1. noop when diff path does not exist", () => { const noDiffPath: Diff = { kind: "E", lhs: undefined, @@ -326,7 +328,7 @@ describe("translateDiffEvent", () => { event: DataTreeDiffEvent.NOOP, }); }); - it("translates new and delete events", () => { + it("2. translates new and delete events", () => { const diffs: Diff[] = [ { kind: "N", @@ -397,7 +399,7 @@ describe("translateDiffEvent", () => { expect(expectedTranslations).toStrictEqual(actualTranslations); }); - it("properly categorises the edit events", () => { + it("3. properly categorises the edit events", () => { const diffs: Diff[] = [ { kind: "E", @@ -423,7 +425,7 @@ describe("translateDiffEvent", () => { expect(expectedTranslations).toStrictEqual(actualTranslations); }); - it("handles JsObject function renaming", () => { + it("4. handles JsObject function renaming", () => { // cyclic dependency case const lhs = new String("() => {}"); _.set(lhs, "data", {}); @@ -465,7 +467,7 @@ describe("translateDiffEvent", () => { expect(expectedTranslations).toStrictEqual(actualTranslations); }); - it("lists array accessors when object is replaced by an array", () => { + it("5. lists array accessors when object is replaced by an array", () => { const diffs: Diff[] = [ { kind: "E", @@ -497,7 +499,7 @@ describe("translateDiffEvent", () => { expect(expectedTranslations).toStrictEqual(actualTranslations); }); - it("lists array accessors when array is replaced by an object", () => { + it("6. lists array accessors when array is replaced by an object", () => { const diffs: Diff[] = [ { kind: "E", @@ -529,7 +531,7 @@ describe("translateDiffEvent", () => { expect(expectedTranslations).toStrictEqual(actualTranslations); }); - it("deletes member expressions when Array changes to string", () => { + it("7. deletes member expressions when Array changes to string", () => { const diffs: Diff[] = [ { kind: "E", @@ -569,10 +571,13 @@ describe("translateDiffEvent", () => { }); }); -describe("overrideWidgetProperties", () => { +describe("5. overrideWidgetProperties", () => { beforeAll(() => { registerWidget(TableWidget, TableWidgetConfig); - registerWidget(InputWidget, InputWidgetV2Config); + registerWidget( + InputWidget, + (InputWidgetV2Config as unknown) as WidgetConfiguration, + ); }); describe("1. Input widget ", () => { @@ -596,7 +601,7 @@ describe("overrideWidgetProperties", () => { }, {}, ); - currentTree["Input1"] = inputWidgetDataTree; + currentTree["Input1"] = createNewEntity(inputWidgetDataTree); }); // When default text is re-evaluated it will override values of meta.text and text in InputWidget it("1. defaultText updating meta.text and text", () => { @@ -673,7 +678,7 @@ describe("overrideWidgetProperties", () => { }, {}, ); - currentTree["Table1"] = tableWidgetDataTree; + currentTree["Table1"] = createNewEntity(tableWidgetDataTree); }); // When default defaultSelectedRow is re-evaluated it will override values of meta.selectedRowIndices, selectedRowIndices, meta.selectedRowIndex & selectedRowIndex. it("1. On change of defaultSelectedRow ", () => { @@ -730,7 +735,7 @@ describe("overrideWidgetProperties", () => { }); //A set of test cases to evaluate the logic for finding a given value's datatype -describe("Evaluated Datatype of a given value", () => { +describe("6. Evaluated Datatype of a given value", () => { it("1. Numeric datatypes", () => { expect(findDatatype(37)).toBe("number"); expect(findDatatype(3.14)).toBe("number"); diff --git a/app/client/src/workers/Evaluation/dataTreeUtils.ts b/app/client/src/workers/Evaluation/dataTreeUtils.ts new file mode 100644 index 0000000000..d8ad0cb69d --- /dev/null +++ b/app/client/src/workers/Evaluation/dataTreeUtils.ts @@ -0,0 +1,54 @@ +import { + DataTree, + DataTreeEntity, + UnEvalTree, + UnEvalTreeEntityObject, +} from "entities/DataTree/dataTreeFactory"; + +/** + * This method accept an entity object as input and if it has __config__ property than it moves the __config__ to object's prototype + */ +export function createNewEntity(entity: UnEvalTreeEntityObject) { + if (!entity || !entity.hasOwnProperty("__config__")) return entity; + const { __config__, ...rest } = entity; + const newObj = Object.create(__config__); + Object.assign(newObj, rest) as DataTreeEntity; + return newObj; +} +/** + * This method takes unevaltree received from mainThread as input and return a new unEvalTree with each entity config moved to entity object's prototype. + * Moving configs to prototype skips it from diffing, cloning and getAllPaths calculation. + * Refer: https://github.com/appsmithorg/appsmith/pull/18361 to know more + */ +export function createUnEvalTreeForEval(unevalTree: UnEvalTree) { + const newUnEvalTree: DataTree = {}; + + for (const entityName of Object.keys(unevalTree)) { + const entity = unevalTree[entityName]; + newUnEvalTree[entityName] = createNewEntity( + entity as UnEvalTreeEntityObject, + ); + } + + return newUnEvalTree; +} + +/** + * This method loops through each entity object of dataTree and sets the entity config from prototype as object properties. + * This is done to send back dataTree in the format expected by mainThread. + */ +export function makeEntityConfigsAsObjProperties( + dataTree: DataTree, + option = {} as { sanitizeDataTree: boolean }, +) { + const { sanitizeDataTree = true } = option; + const newDataTree: DataTree = {}; + for (const entityName of Object.keys(dataTree)) { + const entityConfig = Object.getPrototypeOf(dataTree[entityName]) || {}; + const entity = dataTree[entityName]; + newDataTree[entityName] = { ...entityConfig, ...entity }; + } + return sanitizeDataTree + ? JSON.parse(JSON.stringify(newDataTree)) + : newDataTree; +} diff --git a/app/client/src/workers/Evaluation/evaluate.ts b/app/client/src/workers/Evaluation/evaluate.ts index b026dcaa7d..272b4bbbb1 100644 --- a/app/client/src/workers/Evaluation/evaluate.ts +++ b/app/client/src/workers/Evaluation/evaluate.ts @@ -170,13 +170,10 @@ export const createGlobalData = (args: createGlobalDataArgs) => { context?.eventType, ); ///// Adding Data tree with functions - Object.keys(dataTreeWithFunctions).forEach((datum) => { - GLOBAL_DATA[datum] = dataTreeWithFunctions[datum]; - }); + Object.assign(GLOBAL_DATA, dataTreeWithFunctions); } else { - Object.keys(dataTree).forEach((datum) => { - GLOBAL_DATA[datum] = dataTree[datum]; - }); + // Object.assign removes prototypes of the entity object making sure configs are not shown to user. + Object.assign(GLOBAL_DATA, dataTree); } if (!isEmpty(resolvedFunctions)) { Object.keys(resolvedFunctions).forEach((datum: any) => { @@ -187,8 +184,7 @@ export const createGlobalData = (args: createGlobalDataArgs) => { const data = dataTreeKey[key]?.data; //do not remove we will be investigating this //const isAsync = dataTreeKey?.meta[key]?.isAsync || false; - //const confirmBeforeExecute = - dataTreeKey?.meta[key]?.confirmBeforeExecute || false; + //const confirmBeforeExecute = dataTreeKey?.meta[key]?.confirmBeforeExecute || false; dataTreeKey[key] = resolvedObject[key]; // if (isAsync && confirmBeforeExecute) { // dataTreeKey[key] = confirmationPromise.bind( diff --git a/app/client/src/workers/Evaluation/evaluation.worker.ts b/app/client/src/workers/Evaluation/evaluation.worker.ts index e16c48d615..e147e3eb98 100644 --- a/app/client/src/workers/Evaluation/evaluation.worker.ts +++ b/app/client/src/workers/Evaluation/evaluation.worker.ts @@ -35,6 +35,10 @@ import evaluate, { import { JSUpdate } from "utils/JSPaneUtils"; import { validateWidgetProperty } from "workers/common/DataTreeEvaluator/validationUtils"; import { initiateLinting } from "workers/Linting/utils"; +import { + createUnEvalTreeForEval, + makeEntityConfigsAsObjProperties, +} from "./dataTreeUtils"; const CANVAS = "canvas"; @@ -111,19 +115,22 @@ function eventRequestHandler({ case EVAL_WORKER_ACTIONS.EVAL_TRIGGER: { const { callbackData, - dataTree, dynamicTrigger, eventType, globalContext, triggerMeta, + unEvalTree: __unEvalTree__, } = requestData; if (!dataTreeEvaluator) { return { triggers: [], errors: [] }; } + + const unEvalTree = createUnEvalTreeForEval(__unEvalTree__); + const { evalOrder, nonDynamicFieldValidationOrder, - } = dataTreeEvaluator.setupUpdateTree(dataTree); + } = dataTreeEvaluator.setupUpdateTree(unEvalTree); dataTreeEvaluator.evalAndValidateSubTree( evalOrder, nonDynamicFieldValidationOrder, @@ -238,10 +245,13 @@ function eventRequestHandler({ requiresLinting, shouldReplay, theme, - unevalTree, + unevalTree: __unevalTree__, widgets, widgetTypeConfigMap, } = requestData as EvalTreeRequestData; + + const unevalTree = createUnEvalTreeForEval(__unevalTree__); + try { if (!dataTreeEvaluator) { isCreateFirstTree = true; @@ -251,6 +261,7 @@ function eventRequestHandler({ widgetTypeConfigMap, allActionValidationConfig, ); + const setupFirstTreeResponse = dataTreeEvaluator.setupFirstTree( unevalTree, ); @@ -260,14 +271,16 @@ function eventRequestHandler({ initiateLinting( lintOrder, - jsUpdates, - dataTreeEvaluator.oldUnEvalTree, + makeEntityConfigsAsObjProperties(dataTreeEvaluator.oldUnEvalTree, { + sanitizeDataTree: false, + }), requiresLinting, ); const dataTreeResponse = dataTreeEvaluator.evalAndValidateFirstTree(); - dataTree = dataTreeResponse.evalTree; - dataTree = dataTree && JSON.parse(JSON.stringify(dataTree)); + dataTree = makeEntityConfigsAsObjProperties( + dataTreeResponse.evalTree, + ); } else if (dataTreeEvaluator.hasCyclicalDependency) { if (dataTreeEvaluator && !isEmpty(allActionValidationConfig)) { //allActionValidationConfigs may not be set in dataTreeEvaluatior. Therefore, set it explicitly via setter method @@ -297,14 +310,16 @@ function eventRequestHandler({ initiateLinting( lintOrder, - jsUpdates, - dataTreeEvaluator.oldUnEvalTree, + makeEntityConfigsAsObjProperties(dataTreeEvaluator.oldUnEvalTree, { + sanitizeDataTree: false, + }), requiresLinting, ); const dataTreeResponse = dataTreeEvaluator.evalAndValidateFirstTree(); - dataTree = dataTreeResponse.evalTree; - dataTree = dataTree && JSON.parse(JSON.stringify(dataTree)); + dataTree = makeEntityConfigsAsObjProperties( + dataTreeResponse.evalTree, + ); } else { if (dataTreeEvaluator && !isEmpty(allActionValidationConfig)) { dataTreeEvaluator.setAllActionValidationConfig( @@ -325,8 +340,9 @@ function eventRequestHandler({ initiateLinting( lintOrder, - jsUpdates, - dataTreeEvaluator.oldUnEvalTree, + makeEntityConfigsAsObjProperties(dataTreeEvaluator.oldUnEvalTree, { + sanitizeDataTree: false, + }), requiresLinting, ); nonDynamicFieldValidationOrder = @@ -335,7 +351,9 @@ function eventRequestHandler({ evalOrder, nonDynamicFieldValidationOrder, ); - dataTree = JSON.parse(JSON.stringify(dataTreeEvaluator.evalTree)); + dataTree = makeEntityConfigsAsObjProperties( + dataTreeEvaluator.evalTree, + ); evalMetaUpdates = JSON.parse( JSON.stringify(updateResponse.evalMetaUpdates), ); @@ -366,7 +384,12 @@ function eventRequestHandler({ }); console.error(error); } - dataTree = getSafeToRenderDataTree(unevalTree, widgetTypeConfigMap); + + dataTree = getSafeToRenderDataTree( + makeEntityConfigsAsObjProperties(unevalTree), + widgetTypeConfigMap, + ); + unEvalUpdates = []; } diff --git a/app/client/src/workers/Evaluation/evaluationUtils.ts b/app/client/src/workers/Evaluation/evaluationUtils.ts index f36bdebb6a..bfa4c09b6f 100644 --- a/app/client/src/workers/Evaluation/evaluationUtils.ts +++ b/app/client/src/workers/Evaluation/evaluationUtils.ts @@ -16,8 +16,8 @@ import { DataTreeWidget, ENTITY_TYPE, DataTreeJSAction, - PrivateWidgets, } from "entities/DataTree/dataTreeFactory"; + import _, { get, set } from "lodash"; import { WidgetTypeConfigMap } from "utils/WidgetFactory"; import { PluginType } from "entities/Action"; @@ -27,6 +27,7 @@ import { EvalMetaUpdates } from "../common/DataTreeEvaluator/types"; import { isObject } from "lodash"; import { DataTreeObjectEntity } from "entities/DataTree/dataTreeFactory"; import { validateWidgetProperty } from "workers/common/DataTreeEvaluator/validationUtils"; +import { PrivateWidgets } from "entities/DataTree/types"; // Dropdown1.options[1].value -> Dropdown1.options[1] // Dropdown1.options[1] -> Dropdown1.options @@ -496,8 +497,8 @@ export const getAllPaths = ( const tempKey = curKey ? `${curKey}[${i}]` : `${i}`; getAllPaths(records[i], tempKey, result); } - } else if (typeof records === "object") { - for (const key in records) { + } else if (typeof records === "object" && records) { + for (const key of Object.keys(records)) { const tempKey = curKey ? `${curKey}.${key}` : `${key}`; getAllPaths(records[key], tempKey, result); } diff --git a/app/client/src/workers/Evaluation/types.ts b/app/client/src/workers/Evaluation/types.ts index d70eba5a9a..027e673f5e 100644 --- a/app/client/src/workers/Evaluation/types.ts +++ b/app/client/src/workers/Evaluation/types.ts @@ -1,7 +1,7 @@ import { ActionValidationConfigMap } from "constants/PropertyControlConstants"; import { UserLogObject } from "entities/AppsmithConsole"; import { AppTheme } from "entities/AppTheming"; -import { DataTree } from "entities/DataTree/dataTreeFactory"; +import { DataTree, UnEvalTree } from "entities/DataTree/dataTreeFactory"; import { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer"; import { @@ -19,7 +19,7 @@ export type EvalWorkerRequest = WorkerRequest; export type EvalWorkerResponse = EvalTreeResponseData | boolean | unknown; export interface EvalTreeRequestData { - unevalTree: DataTree; + unevalTree: UnEvalTree; widgetTypeConfigMap: WidgetTypeConfigMap; widgets: CanvasWidgetsReduxState; theme: AppTheme; diff --git a/app/client/src/workers/Linting/types.ts b/app/client/src/workers/Linting/types.ts index 445f21a5e6..f8c1dcb141 100644 --- a/app/client/src/workers/Linting/types.ts +++ b/app/client/src/workers/Linting/types.ts @@ -1,6 +1,5 @@ import { DataTree } from "entities/DataTree/dataTreeFactory"; import { LintErrors } from "reducers/lintingReducers/lintErrorsReducers"; -import { JSUpdate } from "utils/JSPaneUtils"; import { WorkerRequest } from "workers/common/types"; export enum LINT_WORKER_ACTIONS { @@ -14,7 +13,6 @@ export interface LintTreeResponse { export interface LintTreeRequest { pathsToLint: string[]; unevalTree: DataTree; - jsUpdates: Record; } export type LintWorkerRequest = WorkerRequest< @@ -24,6 +22,5 @@ export type LintWorkerRequest = WorkerRequest< export type LintTreeSagaRequestData = { pathsToLint: string[]; - jsUpdates: Record; unevalTree: DataTree; }; diff --git a/app/client/src/workers/Linting/utils.ts b/app/client/src/workers/Linting/utils.ts index acb86b5321..d780446cc6 100644 --- a/app/client/src/workers/Linting/utils.ts +++ b/app/client/src/workers/Linting/utils.ts @@ -51,7 +51,6 @@ import { isWidget, } from "workers/Evaluation/evaluationUtils"; import { LintErrors } from "reducers/lintingReducers/lintErrorsReducers"; -import { JSUpdate } from "utils/JSPaneUtils"; import { Severity } from "entities/AppsmithConsole"; export function getlintErrorsFromTree( @@ -461,7 +460,6 @@ function getInvalidPropertyErrorsFromScript( export function initiateLinting( lintOrder: string[], - jsUpdates: Record, unevalTree: DataTree, requiresLinting: boolean, ) { @@ -470,7 +468,6 @@ export function initiateLinting( promisified: true, responseData: { lintOrder, - jsUpdates, unevalTree, type: EVAL_WORKER_ACTIONS.LINT_TREE, }, diff --git a/app/client/src/workers/common/DataTreeEvaluator/index.ts b/app/client/src/workers/common/DataTreeEvaluator/index.ts index d5c88650ad..b85be2cd1b 100644 --- a/app/client/src/workers/common/DataTreeEvaluator/index.ts +++ b/app/client/src/workers/common/DataTreeEvaluator/index.ts @@ -20,8 +20,8 @@ import { DataTreeJSAction, DataTreeWidget, EvaluationSubstitutionType, - PrivateWidgets, } from "entities/DataTree/dataTreeFactory"; +import { PrivateWidgets } from "entities/DataTree/types"; import { addDependantsOfNestedPropertyPaths, addErrorToEntityProperty, @@ -39,6 +39,7 @@ import { trimDependantChangePaths, overrideWidgetProperties, getAllPaths, + isValidEntity, } from "workers/Evaluation/evaluationUtils"; import { difference, @@ -415,7 +416,7 @@ export default class DataTreeEvaluator { }); const updateDependencyEndTime = performance.now(); - this.applyDifferencesToEvalTree(differences); + this.applyDifferencesToEvalTree({ differences, localUnEvalTree }); const calculateSortOrderStartTime = performance.now(); const subTreeSortOrder: string[] = this.calculateSubTreeSortOrder( @@ -1158,11 +1159,36 @@ export default class DataTreeEvaluator { } } - applyDifferencesToEvalTree(differences: Diff[]) { + /** + * Update the entity config set as prototype according to latest unEvalTree changes else code would consume stale configs. + * + * Example scenario: On addition of a JS binding to widget, it's dynamicBindingPathList changes and needs to be updated. + */ + updateConfigForModifiedEntity(unEvalTree: DataTree, entityName: string) { + const unEvalEntity = unEvalTree[entityName]; + // skip entity if entity is not present in the evalTree or is not a valid entity + if (!this.evalTree[entityName] || !isValidEntity(this.evalTree[entityName])) + return; + const entityConfig = Object.getPrototypeOf(unEvalEntity); + const newEntityObject = Object.create(entityConfig); + this.evalTree[entityName] = Object.assign(newEntityObject, { + ...this.evalTree[entityName], + }); + } + + applyDifferencesToEvalTree({ + differences, + localUnEvalTree, + }: { + differences: Diff[]; + localUnEvalTree: DataTree; + }) { for (const d of differences) { if (!Array.isArray(d.path) || d.path.length === 0) continue; // Null check for typescript // Apply the changes into the evalTree so that it gets the latest changes applyChange(this.evalTree, undefined, d); + const { entityName } = getEntityNameAndPropertyPath(d.path.join(".")); + this.updateConfigForModifiedEntity(localUnEvalTree, entityName); } } diff --git a/app/client/src/workers/common/DataTreeEvaluator/mockData/mockUnEvalTree.ts b/app/client/src/workers/common/DataTreeEvaluator/mockData/mockUnEvalTree.ts index 819fb664eb..8b923f724b 100644 --- a/app/client/src/workers/common/DataTreeEvaluator/mockData/mockUnEvalTree.ts +++ b/app/client/src/workers/common/DataTreeEvaluator/mockData/mockUnEvalTree.ts @@ -232,26 +232,6 @@ export const unEvalTree = { ENTITY_TYPE: ENTITY_TYPE.WIDGET, privateWidgets: {}, }, - pageList: [ - { - pageName: "Page1", - pageId: "6200d1a2b5bfc0392b959cae", - isDefault: true, - isHidden: false, - }, - { - pageName: "Page2", - pageId: "621e22cf2b75295c1c165fa6", - isDefault: false, - isHidden: false, - }, - { - pageName: "Page3", - pageId: "6220c268c48234070f8ac65a", - isDefault: false, - isHidden: false, - }, - ], appsmith: { user: { email: "rathod@appsmith.com", @@ -505,15 +485,6 @@ export const asyncTagUnevalTree: DataTree = { ENTITY_TYPE: ENTITY_TYPE.WIDGET, privateWidgets: {}, } as unknown) as DataTreeWidget, - pageList: [ - { - pageName: "Page1", - pageId: "6272179d8a368d6f1efcd0d2", - isDefault: true, - isHidden: false, - slug: "page1", - }, - ], appsmith: ({ user: { email: "anand@appsmith.com", @@ -1126,16 +1097,6 @@ export const lintingUnEvalTree = { ENTITY_TYPE: "WIDGET", privateWidgets: {}, }, - pageList: [ - { - pageName: "Page1", - pageId: "62bf3730174c17103179d18c", - isDefault: true, - isHidden: false, - slug: "page1", - latest: false, - }, - ], appsmith: { user: { email: "favour@appsmith.com", diff --git a/app/client/src/workers/common/DataTreeEvaluator/validationUtils.ts b/app/client/src/workers/common/DataTreeEvaluator/validationUtils.ts index ca31730218..806f63541c 100644 --- a/app/client/src/workers/common/DataTreeEvaluator/validationUtils.ts +++ b/app/client/src/workers/common/DataTreeEvaluator/validationUtils.ts @@ -109,59 +109,55 @@ export function validateActionProperty( export function getValidatedTree(tree: DataTree) { return Object.keys(tree).reduce((tree, entityKey: string) => { - const entity = tree[entityKey] as DataTreeWidget; - if (!isWidget(entity)) { + const parsedEntity = tree[entityKey]; + if (!isWidget(parsedEntity)) { return tree; } - const parsedEntity = { ...entity }; - Object.entries(entity.validationPaths).forEach(([property, validation]) => { - const value = get(entity, property); - // Pass it through parse - const { isValid, messages, parsed, transformed } = validateWidgetProperty( - validation, - value, - entity, - property, - ); - set(parsedEntity, property, parsed); - const evaluatedValue = isValid - ? parsed - : isUndefined(transformed) - ? value - : transformed; - const safeEvaluatedValue = removeFunctions(evaluatedValue); - set( - parsedEntity, - getEvalValuePath(`${entityKey}.${property}`, { - isPopulated: false, - fullPath: false, - }), - safeEvaluatedValue, - ); - if (!isValid) { - const evalErrors: EvaluationError[] = - messages?.map((message) => ({ - errorType: PropertyEvaluationErrorType.VALIDATION, - errorMessage: message, - severity: Severity.ERROR, - raw: value, - })) ?? []; - addErrorToEntityProperty( - evalErrors, - tree, - getEvalErrorPath(`${entityKey}.${property}`, { + + Object.entries(parsedEntity.validationPaths).forEach( + ([property, validation]) => { + const value = get(parsedEntity, property); + // Pass it through parse + const { + isValid, + messages, + parsed, + transformed, + } = validateWidgetProperty(validation, value, parsedEntity, property); + set(parsedEntity, property, parsed); + const evaluatedValue = isValid + ? parsed + : isUndefined(transformed) + ? value + : transformed; + const safeEvaluatedValue = removeFunctions(evaluatedValue); + set( + parsedEntity, + getEvalValuePath(`${entityKey}.${property}`, { isPopulated: false, fullPath: false, }), + safeEvaluatedValue, ); - } - // else { - // resetValidationErrorsForEntityProperty( - // tree, - // `${entityKey}.${property}`, - // ); - // } - }); + if (!isValid) { + const evalErrors: EvaluationError[] = + messages?.map((message) => ({ + errorType: PropertyEvaluationErrorType.VALIDATION, + errorMessage: message, + severity: Severity.ERROR, + raw: value, + })) ?? []; + addErrorToEntityProperty( + evalErrors, + tree, + getEvalErrorPath(`${entityKey}.${property}`, { + isPopulated: false, + fullPath: false, + }), + ); + } + }, + ); return { ...tree, [entityKey]: parsedEntity }; }, tree); }