chore: perf data tree shrink (#18361)

trimming dataTree object size by removing configs to make evaluation faster.
This commit is contained in:
Rishabh Rathod 2022-12-02 18:15:11 +05:30 committed by GitHub
parent ced3b413c3
commit c440343586
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 1209 additions and 617 deletions

View File

@ -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,
};

View File

@ -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: {},
},
};
};

View File

@ -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<unknown> | 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<string, true>;
export interface DataTreeAction
extends Omit<ActionDataWithMeta, "data" | "config"> {
data: ActionResponse["body"];
actionId: string;
config: Partial<ActionConfig>;
pluginType: PluginType;
pluginId: PluginId;
name: string;
run: ActionDispatcher | RunPluginActionDescription | Record<string, unknown>;
clear:
| ActionDispatcher
| ClearPluginActionDescription
| Record<string, unknown>;
dynamicBindingPathList: DynamicPath[];
bindingPaths: Record<string, EvaluationSubstitutionType>;
reactivePaths: Record<string, EvaluationSubstitutionType>;
ENTITY_TYPE: ENTITY_TYPE.ACTION;
dependencyMap: DependencyMap;
logBlackList: Record<string, true>;
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<string, MetaArgs>;
dynamicBindingPathList: DynamicPath[];
bindingPaths: Record<string, EvaluationSubstitutionType>;
reactivePaths: Record<string, EvaluationSubstitutionType>;
variables: Array<string>;
dependencyMap: DependencyMap;
export type DataTreeJSAction = JSActionEvalTree & JSActionEntityConfig;
export interface WidgetEntityConfig
extends Partial<WidgetProps>,
Omit<WidgetConfigProps, "widgetName" | "rows" | "columns">,
WidgetConfig {
defaultMetaProps: Array<string>;
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<string, string[]>;
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<string, EvaluationSubstitutionType>;
reactivePaths: Record<string, EvaluationSubstitutionType>;
triggerPaths: Record<string, boolean>;
validationPaths: Record<string, ValidationConfig>;
ENTITY_TYPE: ENTITY_TYPE.WIDGET;
logBlackList: Record<string, true>;
propertyOverrideDependency: PropertyOverrideDependency;
overridingPropertyPaths: OverridingPropertyPaths;
privateWidgets: PrivateWidgets;
export interface WidgetEvalTree extends WidgetProps {
meta: Record<string, unknown>;
ENTITY_TYPE: ENTITY_TYPE.WIDGET;
}
export interface UnEvalTreeWidget extends WidgetEvalTree {
__config__: WidgetEntityConfig;
}
export interface DataTreeWidget extends WidgetEvalTree, WidgetConfig {}
export interface DataTreeAppsmith extends Omit<AppDataState, "store"> {
ENTITY_TYPE: ENTITY_TYPE.APPSMITH;
store: Record<string, unknown>;
@ -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<string, any[]>;
@ -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 };

View File

@ -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);

View File

@ -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<string, MetaArgs> = {};
const dynamicBindingPathList = [];
const bindingPaths: Record<string, EvaluationSubstitutionType> = {};
@ -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,
},
};
};

View File

@ -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);

View File

@ -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<string, boolean>;
defaultMetaProps: Record<string, unknown>;
entityConfig: WidgetEntityConfig;
} => {
const derivedProps: any = {};
const blockedDerivedProps: Record<string, true> = {};
@ -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<string, unknown> = {};
@ -215,5 +234,7 @@ export const generateDataTreeWidget = (
});
dataTreeWidget["meta"] = meta;
dataTreeWidget["__config__"] = entityConfig;
return dataTreeWidget;
};

View File

@ -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<unknown> | 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<string, unknown>;
clear:
| ActionDispatcher
| ClearPluginActionDescription
| Record<string, unknown>;
responseMeta: {
statusCode?: string;
isExecutionSuccess: boolean;
headers?: unknown;
};
ENTITY_TYPE: ENTITY_TYPE.ACTION;
config: Partial<ActionConfig>;
datasourceUrl: string;
}
export interface ActionEntityConfig {
dynamicBindingPathList: DynamicPath[];
bindingPaths: Record<string, EvaluationSubstitutionType>;
reactivePaths: Record<string, EvaluationSubstitutionType>;
ENTITY_TYPE: ENTITY_TYPE.ACTION;
dependencyMap: DependencyMap;
logBlackList: Record<string, true>;
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<string, MetaArgs>;
dynamicBindingPathList: DynamicPath[];
bindingPaths: Record<string, EvaluationSubstitutionType>;
reactivePaths: Record<string, EvaluationSubstitutionType>;
variables: Array<string>;
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<string, true>;
/**
* Map of overriding property as key and overridden property as values
*/
export type OverridingPropertyPaths = Record<string, string[]>;
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<string, EvaluationSubstitutionType>;
reactivePaths: Record<string, EvaluationSubstitutionType>;
triggerPaths: Record<string, boolean>;
validationPaths: Record<string, ValidationConfig>;
ENTITY_TYPE: ENTITY_TYPE.WIDGET;
logBlackList: Record<string, true>;
propertyOverrideDependency: PropertyOverrideDependency;
overridingPropertyPaths: OverridingPropertyPaths;
privateWidgets: PrivateWidgets;
};

View File

@ -2,7 +2,7 @@ import {
PropertyOverrideDependency,
OverridingPropertyPaths,
OverridingPropertyType,
} from "./dataTreeFactory";
} from "./types";
type SetOverridingPropertyParams = {
key: string;

View File

@ -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;
}

View File

@ -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(

View File

@ -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,

View File

@ -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(

View File

@ -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,

View File

@ -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 {

View File

@ -23,6 +23,7 @@ const JS_object_tree: DataTreeJSAction = {
reactivePaths: {},
variables: [],
dependencyMap: {},
actionId: "",
};
// @ts-expect-error: meta property not provided

View File

@ -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(
{

View File

@ -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,

View File

@ -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";

View File

@ -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<string> = [];
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;
};

View File

@ -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,
);
});
});

View File

@ -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,

View File

@ -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<string, DataTreeWidget> = {
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,

View File

@ -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<string, DataTreeWidget> = {
},
};
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<any, any> = {
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<any, any>[] = [
{
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<any, any>[] = [
{
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<any, any>[] = [
{
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<any, any>[] = [
{
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<any, any>[] = [
{
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");

View File

@ -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;
}

View File

@ -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(

View File

@ -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 = [];
}

View File

@ -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);
}

View File

@ -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<any, EVAL_WORKER_ACTIONS>;
export type EvalWorkerResponse = EvalTreeResponseData | boolean | unknown;
export interface EvalTreeRequestData {
unevalTree: DataTree;
unevalTree: UnEvalTree;
widgetTypeConfigMap: WidgetTypeConfigMap;
widgets: CanvasWidgetsReduxState;
theme: AppTheme;

View File

@ -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<string, JSUpdate>;
}
export type LintWorkerRequest = WorkerRequest<
@ -24,6 +22,5 @@ export type LintWorkerRequest = WorkerRequest<
export type LintTreeSagaRequestData = {
pathsToLint: string[];
jsUpdates: Record<string, JSUpdate>;
unevalTree: DataTree;
};

View File

@ -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<string, JSUpdate>,
unevalTree: DataTree,
requiresLinting: boolean,
) {
@ -470,7 +468,6 @@ export function initiateLinting(
promisified: true,
responseData: {
lintOrder,
jsUpdates,
unevalTree,
type: EVAL_WORKER_ACTIONS.LINT_TREE,
},

View File

@ -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<any, any>[]) {
/**
* 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<any, any>[];
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);
}
}

View File

@ -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",

View File

@ -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);
}