diff --git a/app/client/src/entities/DataTree/dataTreeFactory.ts b/app/client/src/entities/DataTree/dataTreeFactory.ts index 5d68c072be0e..c764177d437a 100644 --- a/app/client/src/entities/DataTree/dataTreeFactory.ts +++ b/app/client/src/entities/DataTree/dataTreeFactory.ts @@ -1,88 +1,99 @@ import { generateDataTreeAction } from "ee/entities/DataTree/dataTreeAction"; import { generateDataTreeJSAction } from "ee/entities/DataTree/dataTreeJSAction"; import { generateDataTreeWidget } from "entities/DataTree/dataTreeWidget"; -import log from "loglevel"; +import type { DependencyMap } from "utils/DynamicBindingUtils"; + import { ENTITY_TYPE, EvaluationSubstitutionType, } from "ee/entities/DataTree/types"; import { generateDataTreeModuleInputs } from "ee/entities/DataTree/utils"; -import type { - DataTreeSeed, - AppsmithEntity, - EntityTypeValue, -} from "ee/entities/DataTree/types"; -import type { - unEvalAndConfigTree, - ConfigTree, - UnEvalTree, -} from "entities/DataTree/dataTreeTypes"; +import type { EntityTypeValue } from "ee/entities/DataTree/types"; +import type { ConfigTree, UnEvalTree } from "entities/DataTree/dataTreeTypes"; import { isEmpty } from "lodash"; import { generateModuleInstance } from "ee/entities/DataTree/dataTreeModuleInstance"; -import { - endSpan, - startNestedSpan, - startRootSpan, -} from "UITelemetry/generateTraces"; +import { endSpan, startRootSpan } from "UITelemetry/generateTraces"; + +import type { LayoutSystemTypes } from "layoutSystems/types"; +import type { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer"; +import type { MetaState } from "reducers/entityReducers/metaReducer"; +import type { LoadingEntitiesState } from "reducers/evaluationReducers/loadingEntitiesReducer"; +import type { MetaWidgetsReduxState } from "reducers/entityReducers/metaWidgetsReducer"; +import type { ActionDataState } from "ee/reducers/entityReducers/actionsReducer"; +import type { JSCollectionDataState } from "ee/reducers/entityReducers/jsActionsReducer"; +import type { ModuleInputSection } from "ee/constants/ModuleConstants"; +import type { ModuleInstance } from "ee/constants/ModuleInstanceConstants"; + export class DataTreeFactory { - static create({ - actions, - appData, - editorConfigs, - isMobile, - jsActions, - layoutSystemType, - loadingEntities, - metaWidgets, - moduleInputs, - moduleInstanceEntities, - moduleInstances, - pluginDependencyConfig, - theme, - widgets, - widgetsMeta, - }: DataTreeSeed): unEvalAndConfigTree { + static metaWidgets( + metaWidgets: MetaWidgetsReduxState, + widgetsMeta: MetaState, + loadingEntities: LoadingEntitiesState, + ) { + const widgetsSpan = startRootSpan("DataTreeFactory.metaWidgets"); + + const res = Object.values(metaWidgets).reduce( + (acc, widget) => { + const { configEntity, unEvalEntity } = generateDataTreeWidget( + widget, + widgetsMeta[widget.metaWidgetId || widget.widgetId], + loadingEntities, + ); + + acc.dataTree[widget.widgetName] = unEvalEntity; + acc.configTree[widget.widgetName] = configEntity; + + return acc; + }, + { dataTree: {} as UnEvalTree, configTree: {} as ConfigTree }, + ); + + endSpan(widgetsSpan); + + return res; + } + + static widgets( + widgets: CanvasWidgetsReduxState, + widgetsMeta: MetaState, + loadingEntities: LoadingEntitiesState, + layoutSystemType: LayoutSystemTypes, + isMobile: boolean, + ) { + const widgetsSpan = startRootSpan("DataTreeFactory.widgets"); + const dataTree: UnEvalTree = {}; const configTree: ConfigTree = {}; - const start = performance.now(); - const startActions = performance.now(); - const rootSpan = startRootSpan("DataTreeFactory.create"); - const actionsSpan = startNestedSpan("DataTreeFactory.actions", rootSpan); - - actions.forEach((action) => { - const editorConfig = editorConfigs[action.config.pluginId]; - const dependencyConfig = pluginDependencyConfig[action.config.pluginId]; - const { configEntity, unEvalEntity } = generateDataTreeAction( - action, - editorConfig, - dependencyConfig, + + Object.values(widgets).forEach((widget) => { + const { configEntity, unEvalEntity } = generateDataTreeWidget( + widget, + widgetsMeta[widget.metaWidgetId || widget.widgetId], + loadingEntities, + layoutSystemType, + isMobile, ); - dataTree[action.config.name] = unEvalEntity; - configTree[action.config.name] = configEntity; + dataTree[widget.widgetName] = unEvalEntity; + configTree[widget.widgetName] = configEntity; }); - const endActions = performance.now(); - endSpan(actionsSpan); - - const startJsActions = performance.now(); - const jsActionsSpan = startNestedSpan( - "DataTreeFactory.jsActions", - rootSpan, - ); - - jsActions.forEach((js) => { - const { configEntity, unEvalEntity } = generateDataTreeJSAction(js); + endSpan(widgetsSpan); - dataTree[js.config.name] = unEvalEntity; - configTree[js.config.name] = configEntity; - }); - const endJsActions = performance.now(); + return { dataTree, configTree }; + } - endSpan(jsActionsSpan); + public static moduleComponents( + moduleInputs: ModuleInputSection[], + moduleInstances: Record | null, + moduleInstanceEntities: unknown, + ) { + const moduleComponentsSpan = startRootSpan( + "DataTreeFactory.moduleComponents", + ); - const startWidgets = performance.now(); - const widgetsSpan = startNestedSpan("DataTreeFactory.widgets", rootSpan); + const dataTree: UnEvalTree = {}; + const configTree: ConfigTree = {}; if (!isEmpty(moduleInputs)) { const { configEntity, unEvalEntity } = @@ -108,66 +119,65 @@ export class DataTreeFactory { }); } - Object.values(widgets).forEach((widget) => { - const { configEntity, unEvalEntity } = generateDataTreeWidget( - widget, - widgetsMeta[widget.metaWidgetId || widget.widgetId], - loadingEntities, - layoutSystemType, - isMobile, - ); + endSpan(moduleComponentsSpan); - dataTree[widget.widgetName] = unEvalEntity; - configTree[widget.widgetName] = configEntity; - }); + return { + dataTree, + configTree, + }; + } - const endWidgets = performance.now(); + static jsActions(jsActions: JSCollectionDataState) { + const actionsSpan = startRootSpan("DataTreeFactory.jsActions"); - endSpan(widgetsSpan); + const res = jsActions.reduce( + (acc, js) => { + const { configEntity, unEvalEntity } = generateDataTreeJSAction(js); - dataTree.appsmith = { - ...appData, - // combine both persistent and transient state with the transient state - // taking precedence in case the key is the same - store: appData.store, - theme, - } as AppsmithEntity; - (dataTree.appsmith as AppsmithEntity).ENTITY_TYPE = ENTITY_TYPE.APPSMITH; - - const startMetaWidgets = performance.now(); - const metaWidgetsSpan = startNestedSpan( - "DataTreeFactory.metaWidgets", - rootSpan, + acc.dataTree[js.config.name] = unEvalEntity; + acc.configTree[js.config.name] = configEntity; + + return acc; + }, + { + dataTree: {} as UnEvalTree, + configTree: {} as ConfigTree, + }, ); - Object.values(metaWidgets).forEach((widget) => { - const { configEntity, unEvalEntity } = generateDataTreeWidget( - widget, - widgetsMeta[widget.metaWidgetId || widget.widgetId], - loadingEntities, - ); + endSpan(actionsSpan); - dataTree[widget.widgetName] = unEvalEntity; - configTree[widget.widgetName] = configEntity; - }); - const endMetaWidgets = performance.now(); + return res; + } - endSpan(metaWidgetsSpan); - endSpan(rootSpan); + public static actions( + actions: ActionDataState, + editorConfigs: Record, + pluginDependencyConfig: Record, + ) { + const actionsSpan = startRootSpan("DataTreeFactory.actions"); + + const res = actions.reduce( + (acc, action) => { + const editorConfig = editorConfigs[action.config.pluginId]; + const dependencyConfig = pluginDependencyConfig[action.config.pluginId]; + const { configEntity, unEvalEntity } = generateDataTreeAction( + action, + editorConfig, + dependencyConfig, + ); - const end = performance.now(); + acc.dataTree[action.config.name] = unEvalEntity; + acc.configTree[action.config.name] = configEntity; - const out = { - total: end - start, - widgets: endWidgets - startWidgets, - actions: endActions - startActions, - jsActions: endJsActions - startJsActions, - metaWidgets: endMetaWidgets - startMetaWidgets, - }; + return acc; + }, + { dataTree: {} as UnEvalTree, configTree: {} as ConfigTree }, + ); - log.debug("### Create unevalTree timing", out); + endSpan(actionsSpan); - return { unEvalTree: dataTree, configTree }; + return res; } } diff --git a/app/client/src/entities/DataTree/dataTreeWidget.test.ts b/app/client/src/entities/DataTree/dataTreeWidget.test.ts index f50444e11a03..36d78901d407 100644 --- a/app/client/src/entities/DataTree/dataTreeWidget.test.ts +++ b/app/client/src/entities/DataTree/dataTreeWidget.test.ts @@ -225,8 +225,6 @@ describe("generateDataTreeWidget", () => { parentColumnSpace: 0, parentRowSpace: 0, rightColumn: 0, - renderMode: RenderModes.CANVAS, - version: 0, topRow: 0, widgetId: "123", widgetName: "Input1", diff --git a/app/client/src/entities/DataTree/dataTreeWidget.ts b/app/client/src/entities/DataTree/dataTreeWidget.ts index a5a9a2fd8c8b..51d99a57f6ba 100644 --- a/app/client/src/entities/DataTree/dataTreeWidget.ts +++ b/app/client/src/entities/DataTree/dataTreeWidget.ts @@ -1,5 +1,5 @@ import { getAllPathsFromPropertyConfig } from "entities/Widget/utils"; -import _, { get, isEmpty } from "lodash"; +import _, { get, isEmpty, omit } from "lodash"; import memoize from "micro-memoize"; import type { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer"; import type { DynamicPath } from "utils/DynamicBindingUtils"; @@ -21,6 +21,7 @@ import WidgetFactory from "WidgetProvider/factory"; import { getComponentDimensions } from "layoutSystems/common/utils/ComponentSizeUtils"; import type { LoadingEntitiesState } from "reducers/evaluationReducers/loadingEntitiesReducer"; import { LayoutSystemTypes } from "layoutSystems/types"; +import { WIDGET_PROPS_TO_SKIP_FROM_EVAL } from "constants/WidgetConstants"; /** * @@ -176,13 +177,17 @@ export function getSetterConfig( // Widget changes only when dynamicBindingPathList changes. // Only meta properties change very often, for example typing in an input or selecting a table row. const generateDataTreeWidgetWithoutMeta = ( - widget: FlattenedWidgetProps, + pureWidget: FlattenedWidgetProps, ): { dataTreeWidgetWithoutMetaProps: WidgetEntity; overridingMetaPropsMap: Record; defaultMetaProps: Record; entityConfig: WidgetEntityConfig; } => { + const widget = omit( + pureWidget, + Object.keys(WIDGET_PROPS_TO_SKIP_FROM_EVAL), + ) as FlattenedWidgetProps; // TODO: Fix this the next time the file is edited // eslint-disable-next-line @typescript-eslint/no-explicit-any const derivedProps: any = {}; diff --git a/app/client/src/entities/Widget/utils.ts b/app/client/src/entities/Widget/utils.ts index 475e070f0bb4..f48f18aae533 100644 --- a/app/client/src/entities/Widget/utils.ts +++ b/app/client/src/entities/Widget/utils.ts @@ -367,6 +367,7 @@ const getAllPathsFromPropertyConfigWithoutMemo = ( export const getAllPathsFromPropertyConfig = memoize( getAllPathsFromPropertyConfigWithoutMemo, + { maxSize: 1000 }, ); diff --git a/app/client/src/reducers/evaluationReducers/loadingEntitiesReducer.ts b/app/client/src/reducers/evaluationReducers/loadingEntitiesReducer.ts index 9a22376cbdfa..05e6da8aea0b 100644 --- a/app/client/src/reducers/evaluationReducers/loadingEntitiesReducer.ts +++ b/app/client/src/reducers/evaluationReducers/loadingEntitiesReducer.ts @@ -1,6 +1,7 @@ import { createReducer } from "utils/ReducerUtils"; import type { ReduxAction } from "ee/constants/ReduxActionConstants"; import { ReduxActionTypes } from "ee/constants/ReduxActionConstants"; +import { isEqual } from "lodash"; export type LoadingEntitiesState = Set; @@ -10,7 +11,16 @@ const loadingEntitiesReducer = createReducer(initialState, { [ReduxActionTypes.SET_LOADING_ENTITIES]: ( state: LoadingEntitiesState, action: ReduxAction>, - ): LoadingEntitiesState => action.payload, + ): LoadingEntitiesState => { + const newLoadingEntities = action.payload; + + // its just a set with string properties time complexity of equal is not too bad + if (isEqual(state, newLoadingEntities)) { + return state; + } + + return newLoadingEntities; + }, [ReduxActionTypes.FETCH_PAGE_INIT]: () => initialState, }); diff --git a/app/client/src/selectors/dataTreeSelectors.ts b/app/client/src/selectors/dataTreeSelectors.ts index 467af24c1a02..a99f0776272e 100644 --- a/app/client/src/selectors/dataTreeSelectors.ts +++ b/app/client/src/selectors/dataTreeSelectors.ts @@ -11,17 +11,20 @@ import { getCurrentModuleActions, getCurrentModuleJSCollections, } from "ee/selectors/entitiesSelector"; -import type { WidgetEntity } from "ee/entities/DataTree/types"; -import type { DataTree } from "entities/DataTree/dataTreeTypes"; +import type { AppsmithEntity, WidgetEntity } from "ee/entities/DataTree/types"; +import type { + ConfigTree, + DataTree, + UnEvalTree, +} from "entities/DataTree/dataTreeTypes"; import { DataTreeFactory } from "entities/DataTree/dataTreeFactory"; import { getIsMobileBreakPoint, getMetaWidgets, - getWidgetsForEval, + getWidgets, getWidgetsMeta, } from "sagas/selectors"; import "url-search-params-polyfill"; -import { getPageList } from "./appViewSelectors"; import type { AppState } from "ee/reducers"; import { getSelectedAppThemeProperties } from "./appThemingSelectors"; import type { LoadingEntitiesState } from "reducers/evaluationReducers/loadingEntitiesReducer"; @@ -35,6 +38,7 @@ import { getCurrentWorkflowActions, getCurrentWorkflowJSActions, } from "ee/selectors/workflowSelectors"; +import { endSpan, startRootSpan } from "UITelemetry/generateTraces"; export const getLoadingEntities = (state: AppState) => state.evaluations.loadingEntities; @@ -55,26 +59,25 @@ const getLayoutSystemPayload = createSelector( }, ); -const getCurrentActionEntities = createSelector( +const getCurrentActionsEntities = createSelector( getCurrentActions, getCurrentModuleActions, getCurrentWorkflowActions, + (actions, moduleActions, workflowActions) => [ + ...actions, + ...moduleActions, + ...workflowActions, + ], +); +const getCurrentJSActionsEntities = createSelector( getCurrentJSCollections, getCurrentModuleJSCollections, getCurrentWorkflowJSActions, - ( - actions, - moduleActions, - workflowActions, - jsActions, - moduleJSActions, - workflowJsActions, - ) => { - return { - actions: [...actions, ...moduleActions, ...workflowActions], - jsActions: [...jsActions, ...moduleJSActions, ...workflowJsActions], - }; - }, + (jsActions, moduleJSActions, workflowJsActions) => [ + ...jsActions, + ...moduleJSActions, + ...workflowJsActions, + ], ); const getModulesData = createSelector( @@ -90,55 +93,126 @@ const getModulesData = createSelector( }, ); -export const getUnevaluatedDataTree = createSelector( - getCurrentActionEntities, - getWidgetsForEval, - getWidgetsMeta, - getPageList, - getAppData, +const getActionsFromUnevaluatedTree = createSelector( + getCurrentActionsEntities, getPluginEditorConfigs, getPluginDependencyConfig, - getSelectedAppThemeProperties, - getMetaWidgets, - getLayoutSystemPayload, - getLoadingEntities, + (actions, editorConfigs, pluginDependencyConfig) => { + const { configTree, dataTree } = DataTreeFactory.actions( + actions, + editorConfigs, + pluginDependencyConfig, + ); + + return { + configTree, + dataTree, + }; + }, +); + +const getJSActionsFromUnevaluatedTree = createSelector( + getCurrentJSActionsEntities, + (jsActions) => { + return DataTreeFactory.jsActions(jsActions); + }, +); + +const getModuleComponentsFromUnEvaluatedTree = createSelector( getModulesData, - ( - currentActionEntities, - widgets, - widgetsMeta, - pageListPayload, - appData, - editorConfigs, - pluginDependencyConfig, - selectedAppThemeProperty, - metaWidgets, - layoutSystemPayload, - loadingEntities, - modulesData, - ) => { - const pageList = pageListPayload || []; - - return DataTreeFactory.create({ - ...currentActionEntities, + + (moduleData) => { + const { moduleInputs, moduleInstanceEntities, moduleInstances } = + moduleData; + + return DataTreeFactory.moduleComponents( + moduleInputs, + moduleInstances, + moduleInstanceEntities, + ); + }, +); + +const getWidgetsFromUnevaluatedTree = createSelector( + getModuleComponentsFromUnEvaluatedTree, + getWidgets, + getWidgetsMeta, + getLoadingEntities, + getLayoutSystemPayload, + (moduleData, widgets, widgetsMeta, loadingEntities, layoutSystemPayload) => { + const { isMobile, layoutSystemType } = layoutSystemPayload; + + const widgetsDataTree = DataTreeFactory.widgets( widgets, widgetsMeta, - pageList, - appData, - editorConfigs, - pluginDependencyConfig, - theme: selectedAppThemeProperty, + loadingEntities, + layoutSystemType, + isMobile, + ); + + return { + configTree: { ...moduleData.configTree, ...widgetsDataTree.configTree }, + dataTree: { ...moduleData.dataTree, ...widgetsDataTree.dataTree }, + }; + }, +); +const getMetaWidgetsFromUnevaluatedTree = createSelector( + getMetaWidgets, + getWidgetsMeta, + getLoadingEntities, + (metaWidgets, widgetsMeta, loadingEntities) => { + return DataTreeFactory.metaWidgets( metaWidgets, + widgetsMeta, loadingEntities, - ...layoutSystemPayload, - ...modulesData, - }); + ); }, ); export const getEvaluationInverseDependencyMap = (state: AppState) => state.evaluations.dependencies.inverseDependencyMap; +export const getUnevaluatedDataTree = createSelector( + getActionsFromUnevaluatedTree, + getJSActionsFromUnevaluatedTree, + getWidgetsFromUnevaluatedTree, + getAppData, + getSelectedAppThemeProperties, + getMetaWidgetsFromUnevaluatedTree, + (actions, jsActions, widgets, appData, theme, metaWidgets) => { + let dataTree: UnEvalTree = {}; + let configTree: ConfigTree = {}; + const rootSpan = startRootSpan("DataTreeFactory.create"); + + configTree = { + ...actions.configTree, + ...jsActions.configTree, + ...widgets.configTree, + }; + dataTree = { + ...actions.dataTree, + ...jsActions.dataTree, + ...widgets.dataTree, + }; + + dataTree.appsmith = { + ...appData, + // combine both persistent and transient state with the transient state + // taking precedence in case the key is the same + store: appData.store, + theme, + } as AppsmithEntity; + configTree = { ...configTree, ...metaWidgets.configTree }; + dataTree = { ...dataTree, ...metaWidgets.dataTree }; + + endSpan(rootSpan); + + return { + configTree, + unEvalTree: dataTree, + }; + }, +); export const getIsWidgetLoading = createSelector( [getLoadingEntities, (_state: AppState, widgetName: string) => widgetName], (loadingEntities: LoadingEntitiesState, widgetName: string) =>