diff --git a/app/client/src/ce/constants/ReduxActionConstants.tsx b/app/client/src/ce/constants/ReduxActionConstants.tsx index 80d94e61bd3a..a91ae9623898 100644 --- a/app/client/src/ce/constants/ReduxActionConstants.tsx +++ b/app/client/src/ce/constants/ReduxActionConstants.tsx @@ -618,6 +618,8 @@ const PageActionTypes = { SET_PAGE_ORDER_INIT: "SET_PAGE_ORDER_INIT", SET_PAGE_ORDER_SUCCESS: "SET_PAGE_ORDER_SUCCESS", RESET_PAGE_LIST: "RESET_PAGE_LIST", + SET_ONLOAD_ACTION_EXECUTED: "SET_ONLOAD_ACTION_EXECUTED", + EXECUTE_REACTIVE_QUERIES: "EXECUTE_REACTIVE_QUERIES", }; const PageActionErrorTypes = { diff --git a/app/client/src/ce/entities/DataTree/dataTreeAction.ts b/app/client/src/ce/entities/DataTree/dataTreeAction.ts index 6bd3dc3a0e8e..dc593369a8ca 100644 --- a/app/client/src/ce/entities/DataTree/dataTreeAction.ts +++ b/app/client/src/ce/entities/DataTree/dataTreeAction.ts @@ -9,6 +9,7 @@ import type { ActionEntity, ActionEntityConfig, } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; export const generateDataTreeAction = ( action: ActionData, @@ -55,6 +56,30 @@ export const generateDataTreeAction = ( dynamicBindingPathList, ); + //this is for view mode + if (action.config.jsonPathKeys && !action.config?.datasource) { + dependencyMap["config.body"] = action.config.jsonPathKeys; + const result = action.config.jsonPathKeys + .map((item) => `{{${item}}}`) + .join(" "); + + action.config.actionConfiguration = { + ...action.config.actionConfiguration, + body: result, + }; + dynamicBindingPathList.push({ + key: "config.body", + }); + bindingPaths["config.body"] = EvaluationSubstitutionType.TEMPLATE; + reactivePaths["config.body"] = EvaluationSubstitutionType.TEMPLATE; + } + + dependencyMap["run"] = dynamicBindingPathList.map( + (path: { key: string }) => path.key, + ); + + const dynamicTriggerPathList = [{ key: "run" }, { key: "clear" }]; + return { unEvalEntity: { actionId: action.config.id, @@ -84,6 +109,8 @@ export const generateDataTreeAction = ( reactivePaths, dependencyMap, logBlackList: {}, + dynamicTriggerPathList, + runBehaviour: action.config.runBehaviour, }, }; }; diff --git a/app/client/src/ce/entities/DataTree/dataTreeJSAction.test.ts b/app/client/src/ce/entities/DataTree/dataTreeJSAction.test.ts index 704b083d5f52..1b4bcdb4efac 100644 --- a/app/client/src/ce/entities/DataTree/dataTreeJSAction.test.ts +++ b/app/client/src/ce/entities/DataTree/dataTreeJSAction.test.ts @@ -150,10 +150,12 @@ describe("generateDataTreeJSAction", () => { myFun2: { arguments: [], confirmBeforeExecute: false, + runBehaviour: "MANUAL", }, myFun1: { arguments: [], confirmBeforeExecute: false, + runBehaviour: "MANUAL", }, }, bindingPaths: { @@ -184,10 +186,20 @@ describe("generateDataTreeJSAction", () => { dependencyMap: { body: ["myFun2", "myFun1"], }, + dynamicTriggerPathList: [ + { + key: "myFun2", + }, + { + key: "myFun1", + }, + ], reactivePaths: { body: "SMART_SUBSTITUTE", myFun1: "SMART_SUBSTITUTE", + "myFun1.data": "SMART_SUBSTITUTE", myFun2: "SMART_SUBSTITUTE", + "myFun2.data": "SMART_SUBSTITUTE", myVar1: "SMART_SUBSTITUTE", myVar2: "SMART_SUBSTITUTE", }, @@ -347,10 +359,12 @@ describe("generateDataTreeJSAction", () => { myFun2: { arguments: [], confirmBeforeExecute: false, + runBehaviour: "MANUAL", }, myFun1: { arguments: [], confirmBeforeExecute: false, + runBehaviour: "MANUAL", }, }, bindingPaths: { @@ -381,12 +395,22 @@ describe("generateDataTreeJSAction", () => { dependencyMap: { body: ["myFun2", "myFun1"], }, + dynamicTriggerPathList: [ + { + key: "myFun2", + }, + { + key: "myFun1", + }, + ], name: "JSObject2", pluginType: "JS", reactivePaths: { body: "SMART_SUBSTITUTE", myFun1: "SMART_SUBSTITUTE", + "myFun1.data": "SMART_SUBSTITUTE", myFun2: "SMART_SUBSTITUTE", + "myFun2.data": "SMART_SUBSTITUTE", myVar1: "SMART_SUBSTITUTE", myVar2: "SMART_SUBSTITUTE", }, diff --git a/app/client/src/ce/entities/DataTree/dataTreeJSAction.ts b/app/client/src/ce/entities/DataTree/dataTreeJSAction.ts index 105ac925153b..3532ac51a19b 100644 --- a/app/client/src/ce/entities/DataTree/dataTreeJSAction.ts +++ b/app/client/src/ce/entities/DataTree/dataTreeJSAction.ts @@ -1,6 +1,6 @@ import { ENTITY_TYPE } from "ee/entities/DataTree/types"; import type { JSCollectionData } from "ee/reducers/entityReducers/jsActionsReducer"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import type { DependencyMap } from "utils/DynamicBindingUtils"; import type { JSActionEntity, @@ -19,6 +19,7 @@ export const generateDataTreeJSAction = ( const meta: Record = {}; const dynamicBindingPathList = []; const bindingPaths: Record = {}; + const reactivePaths: Record = {}; // TODO: Fix this the next time the file is edited // eslint-disable-next-line @typescript-eslint/no-explicit-any const variableList: Record = {}; @@ -50,6 +51,7 @@ export const generateDataTreeJSAction = ( // TODO: Fix this the next time the file is edited // eslint-disable-next-line @typescript-eslint/no-explicit-any const actionsData: Record = {}; + const dynamicTriggerPathList = []; if (actions) { for (let i = 0; i < actions.length; i++) { @@ -58,6 +60,7 @@ export const generateDataTreeJSAction = ( meta[action.name] = { arguments: action.actionConfiguration?.jsArguments || [], confirmBeforeExecute: !!action.confirmBeforeExecute, + runBehaviour: action.runBehaviour, }; bindingPaths[action.name] = EvaluationSubstitutionType.SMART_SUBSTITUTE; dynamicBindingPathList.push({ key: action.name }); @@ -67,6 +70,9 @@ export const generateDataTreeJSAction = ( // Action data is updated directly to the dataTree (see updateActionData.ts) data: {}, }; + dynamicTriggerPathList.push({ key: action.name }); + reactivePaths[`${action.name}.data`] = + EvaluationSubstitutionType.SMART_SUBSTITUTE; } } @@ -85,11 +91,12 @@ export const generateDataTreeJSAction = ( 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 }, + reactivePaths: { ...reactivePaths, ...bindingPaths }, dynamicBindingPathList: dynamicBindingPathList, variables: listVariables, dependencyMap: dependencyMap, actionNames: new Set(actions.map((action) => action.name)), + dynamicTriggerPathList: dynamicTriggerPathList, }, }; }; diff --git a/app/client/src/ce/entities/DataTree/types.ts b/app/client/src/ce/entities/DataTree/types.ts index 905c5324df02..84258e6721e5 100644 --- a/app/client/src/ce/entities/DataTree/types.ts +++ b/app/client/src/ce/entities/DataTree/types.ts @@ -6,19 +6,12 @@ import type { ActionDescription } from "ee/workers/Evaluation/fns"; import type { Variable } from "entities/JSCollection"; import type { DependencyMap, DynamicPath } from "utils/DynamicBindingUtils"; import type { Page } from "entities/Page"; -import type { MetaWidgetsReduxState } from "reducers/entityReducers/metaWidgetsReducer"; import type { WidgetConfigProps } from "WidgetProvider/types"; -import type { ActionDataState } from "ee/reducers/entityReducers/actionsReducer"; import type { WidgetProps } from "widgets/BaseWidget"; -import type { CanvasWidgetsReduxState } from "ee/reducers/entityReducers/canvasWidgetsReducer"; -import type { MetaState } from "reducers/entityReducers/metaReducer"; import type { AppDataState } from "reducers/entityReducers/appReducer"; -import type { JSCollectionDataState } from "ee/reducers/entityReducers/jsActionsReducer"; import type { AppTheme } from "entities/AppTheming"; -import type { LoadingEntitiesState } from "reducers/evaluationReducers/loadingEntitiesReducer"; -import type { LayoutSystemTypes } from "layoutSystems/types"; -import type { Module } from "ee/constants/ModuleConstants"; -import type { ModuleInstance } from "ee/constants/ModuleInstanceConstants"; +import type { ActionRunBehaviourType } from "PluginActionEditor/types/PluginActionTypes"; +import type { EvaluationSubstitutionType } from "constants/EvaluationConstants"; // TODO: Fix this the next time the file is edited // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -36,12 +29,6 @@ export const ACTION_TYPE = ENTITY_TYPE.ACTION; type ValueOf = T[keyof T]; export type EntityTypeValue = ValueOf; -export enum EvaluationSubstitutionType { - TEMPLATE = "TEMPLATE", - PARAMETER = "PARAMETER", - SMART_SUBSTITUTE = "SMART_SUBSTITUTE", -} - // Action entity types export interface ActionEntity { actionId: string; @@ -73,6 +60,8 @@ export interface ActionEntityConfig extends EntityConfig { moduleId?: string; moduleInstanceId?: string; isPublic?: boolean; + dynamicTriggerPathList: DynamicPath[]; + runBehaviour: ActionRunBehaviourType; } // JSAction (JSObject) entity Types @@ -80,6 +69,7 @@ export interface ActionEntityConfig extends EntityConfig { export interface MetaArgs { arguments: Variable[]; confirmBeforeExecute: boolean; + runBehaviour: ActionRunBehaviourType; } export interface JSActionEntityConfig extends EntityConfig { @@ -97,6 +87,7 @@ export interface JSActionEntityConfig extends EntityConfig { moduleInstanceId?: string; isPublic?: boolean; actionNames: Set; + dynamicTriggerPathList: DynamicPath[]; } export interface JSActionEntity { @@ -196,28 +187,6 @@ export interface AppsmithEntity extends Omit { currentEnvironmentName: string; } -export interface DataTreeSeed { - actions: ActionDataState; - // TODO: Fix this the next time the file is edited - // eslint-disable-next-line @typescript-eslint/no-explicit-any - editorConfigs: Record; - pluginDependencyConfig: Record; - widgets: CanvasWidgetsReduxState; - widgetsMeta: MetaState; - appData: AppDataState; - jsActions: JSCollectionDataState; - theme: AppTheme["properties"]; - metaWidgets: MetaWidgetsReduxState; - isMobile: boolean; - moduleInputs: Module["inputsForm"]; - moduleInstances: Record | null; - // TODO: Fix this the next time the file is edited - // eslint-disable-next-line @typescript-eslint/no-explicit-any - moduleInstanceEntities: any; - layoutSystemType: LayoutSystemTypes; - loadingEntities: LoadingEntitiesState; -} - export type DataTreeEntityConfig = | WidgetEntityConfig | ActionEntityConfig diff --git a/app/client/src/ce/entities/DataTree/types/DataTreeSeed.ts b/app/client/src/ce/entities/DataTree/types/DataTreeSeed.ts new file mode 100644 index 000000000000..e893fcf3f794 --- /dev/null +++ b/app/client/src/ce/entities/DataTree/types/DataTreeSeed.ts @@ -0,0 +1,34 @@ +import type { CanvasWidgetsReduxState } from "ee/reducers/entityReducers/canvasWidgetsReducer"; +import type { ActionDataState } from "ee/reducers/entityReducers/actionsReducer"; +import type DependencyMap from "entities/DependencyMap"; +import type { MetaState } from "reducers/entityReducers/metaReducer"; +import type { AppDataState } from "reducers/entityReducers/appReducer"; +import type { JSCollectionDataState } from "ee/reducers/entityReducers/jsActionsReducer"; +import type { MetaWidgetsReduxState } from "reducers/entityReducers/metaWidgetsReducer"; +import type { AppTheme } from "entities/AppTheming"; +import type { Module } from "ee/constants/ModuleConstants"; +import type { ModuleInstance } from "ee/constants/ModuleInstanceConstants"; +import type { LayoutSystemTypes } from "layoutSystems/types"; +import type { LoadingEntitiesState } from "reducers/evaluationReducers/loadingEntitiesReducer"; + +export interface DataTreeSeed { + actions: ActionDataState; + // TODO: Fix this the next time the file is edited + // eslint-disable-next-line @typescript-eslint/no-explicit-any + editorConfigs: Record; + pluginDependencyConfig: Record; + widgets: CanvasWidgetsReduxState; + widgetsMeta: MetaState; + appData: AppDataState; + jsActions: JSCollectionDataState; + theme: AppTheme["properties"]; + metaWidgets: MetaWidgetsReduxState; + isMobile: boolean; + moduleInputs: Module["inputsForm"]; + moduleInstances: Record | null; + // TODO: Fix this the next time the file is edited + // eslint-disable-next-line @typescript-eslint/no-explicit-any + moduleInstanceEntities: any; + layoutSystemType: LayoutSystemTypes; + loadingEntities: LoadingEntitiesState; +} diff --git a/app/client/src/ce/reducers/uiReducers/editorReducer.tsx b/app/client/src/ce/reducers/uiReducers/editorReducer.tsx index 573b00b1ac9c..6d0ecacb47c8 100644 --- a/app/client/src/ce/reducers/uiReducers/editorReducer.tsx +++ b/app/client/src/ce/reducers/uiReducers/editorReducer.tsx @@ -9,7 +9,7 @@ import type { LayoutOnLoadActionErrors, PageAction, } from "constants/AppsmithActionConstants/ActionConstants"; -import type { UpdatePageResponse } from "api/PageApi"; +import type { SavePageResponse, UpdatePageResponse } from "api/PageApi"; import type { UpdateCanvasPayload } from "actions/pageActions"; export const initialState: EditorReduxState = { @@ -38,6 +38,7 @@ export const initialState: EditorReduxState = { isPreviewMode: false, isProtectedMode: true, zoomLevel: 1, + onLoadActionExecution: {}, }; export const handlers = { @@ -51,6 +52,7 @@ export const handlers = { pageWidgetId: undefined, pageActions: undefined, layoutOnLoadActionErrors: undefined, + onLoadActionExecution: undefined, loadingStates: { ...state.loadingStates, saving: false, @@ -122,8 +124,25 @@ export const handlers = { return { ...state }; }, - [ReduxActionTypes.SAVE_PAGE_SUCCESS]: (state: EditorReduxState) => { + [ReduxActionTypes.SAVE_PAGE_SUCCESS]: ( + state: EditorReduxState, + action: ReduxAction, + ) => { + const layoutOnLoadActions = action.payload.data.layoutOnLoadActions; + const newlyBindedActions: Record = {}; + + layoutOnLoadActions.forEach((actionsPerPage) => { + actionsPerPage.forEach((action) => { + newlyBindedActions[action.id] = true; + }); + }); + state.loadingStates.saving = false; + state.pageActions = layoutOnLoadActions; + state.onLoadActionExecution = { + ...state.onLoadActionExecution, + ...newlyBindedActions, + }; return { ...state }; }, @@ -156,6 +175,14 @@ export const handlers = { state.loadingStates.publishing = false; state.loadingStates.publishingError = false; + const newMap: Record = {}; + + pageActions?.forEach((action) => { + if (action.length > 0) { + newMap[action[0].id] = false; + } + }); + return { ...state, currentPageName, @@ -165,6 +192,7 @@ export const handlers = { currentPageId, pageActions, layoutOnLoadActionErrors, + onLoadActionExecution: newMap, }; }, [ReduxActionTypes.CLONE_PAGE_INIT]: (state: EditorReduxState) => { @@ -295,6 +323,18 @@ export const handlers = { loadingStates: { ...state.loadingStates, isSettingUpPage: false }, }; }, + [ReduxActionTypes.SET_ONLOAD_ACTION_EXECUTED]: ( + state: EditorReduxState, + action: ReduxAction<{ id: string; isExecuted: boolean }>, + ) => { + return { + ...state, + onLoadActionExecution: { + ...state.onLoadActionExecution, + [action.payload.id]: action.payload.isExecuted, + }, + }; + }, }; const editorReducer = createReducer(initialState, handlers); @@ -314,6 +354,7 @@ export interface EditorReduxState { isProtectedMode: boolean; zoomLevel: number; layoutOnLoadActionErrors?: LayoutOnLoadActionErrors[]; + onLoadActionExecution?: Record; loadingStates: { saving: boolean; savingError: boolean; diff --git a/app/client/src/ce/sagas/ActionExecution/ActionExecutionSagas.ts b/app/client/src/ce/sagas/ActionExecution/ActionExecutionSagas.ts index b750f3af68f2..5379d9588d24 100644 --- a/app/client/src/ce/sagas/ActionExecution/ActionExecutionSagas.ts +++ b/app/client/src/ce/sagas/ActionExecution/ActionExecutionSagas.ts @@ -155,8 +155,12 @@ export function* executeAppAction(payload: ExecuteTriggerPayload): Generator { log.debug({ dynamicString, callbackData, globalContext }); - if (dynamicString === undefined) { - throw new Error("Executing undefined action"); + if (!dynamicString || typeof dynamicString !== "string") { + log.warn("Attempted to execute action with invalid dynamicString:", { + payload, + }); + + return; } return yield call( diff --git a/app/client/src/ce/sagas/index.tsx b/app/client/src/ce/sagas/index.tsx index 577711a4f01c..985260fb14a9 100644 --- a/app/client/src/ce/sagas/index.tsx +++ b/app/client/src/ce/sagas/index.tsx @@ -52,6 +52,7 @@ import anvilSagas from "layoutSystems/anvil/integrations/sagas"; import ideSagas from "sagas/IDESaga"; import sendSideBySideWidgetHoverAnalyticsEventSaga from "sagas/AnalyticsSaga"; import gitSagas from "git/sagas"; +import PostEvaluationSagas from "sagas/PostEvaluationSagas"; /* Sagas that are registered by a module that is designed to be independent of the core platform */ import ternSagas from "sagas/TernSaga"; @@ -113,4 +114,5 @@ export const sagas = [ sendSideBySideWidgetHoverAnalyticsEventSaga, gitSagas, gitApplicationSagas, + PostEvaluationSagas, ]; diff --git a/app/client/src/ce/sagas/moduleInstanceSagaUtils.ts b/app/client/src/ce/sagas/moduleInstanceSagaUtils.ts new file mode 100644 index 000000000000..a1def360b252 --- /dev/null +++ b/app/client/src/ce/sagas/moduleInstanceSagaUtils.ts @@ -0,0 +1,20 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import type { DataTreeEntityObject } from "ee/entities/DataTree/types"; +import type { ConfigTree, DataTree } from "entities/DataTree/dataTreeTypes"; + +export interface ParamsForJSModuleInstance { + configTree: ConfigTree; + dataTree: DataTree; + entity: DataTreeEntityObject; + entityName: string; + propertyPath: string; +} + +export function* executionForJSModuleInstance({ + configTree, + dataTree, + entity, + entityName, + propertyPath, +}: ParamsForJSModuleInstance) {} diff --git a/app/client/src/ce/selectors/moduleInstanceSelectors.ts b/app/client/src/ce/selectors/moduleInstanceSelectors.ts index 4e0d74eb515a..f26879d7744b 100644 --- a/app/client/src/ce/selectors/moduleInstanceSelectors.ts +++ b/app/client/src/ce/selectors/moduleInstanceSelectors.ts @@ -1,8 +1,16 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import type { DefaultRootState } from "react-redux"; import type { ModuleInstance } from "ee/constants/ModuleInstanceConstants"; +import type { JSCollection } from "entities/JSCollection"; export const getModuleInstanceById = ( state: DefaultRootState, id: string, ): ModuleInstance | undefined => undefined; + +export const getModuleInstanceJSCollectionById = ( + state: DefaultRootState, + jsCollectionId: string, +): JSCollection | undefined => { + return undefined; +}; diff --git a/app/client/src/ce/utils/actionExecutionUtils.ts b/app/client/src/ce/utils/actionExecutionUtils.ts index 524ff167d052..618e00792f53 100644 --- a/app/client/src/ce/utils/actionExecutionUtils.ts +++ b/app/client/src/ce/utils/actionExecutionUtils.ts @@ -72,6 +72,7 @@ export function getActionExecutionAnalytics( actionId: action?.id, inputParams: objectKeys(params).length, source: ActionExecutionContext.EVALUATION_ACTION_TRIGGER, // Used in analytic events to understand who triggered action execution + runBehaviour: action?.runBehaviour, }; if (!!currentApp) { diff --git a/app/client/src/ce/workers/Evaluation/evaluationUtils.test.ts b/app/client/src/ce/workers/Evaluation/evaluationUtils.test.ts index bcd0bbbb9d79..9c8c7756295e 100644 --- a/app/client/src/ce/workers/Evaluation/evaluationUtils.test.ts +++ b/app/client/src/ce/workers/Evaluation/evaluationUtils.test.ts @@ -10,10 +10,7 @@ import type { PrivateWidgets, JSActionEntity, } from "ee/entities/DataTree/types"; -import { - ENTITY_TYPE, - EvaluationSubstitutionType, -} from "ee/entities/DataTree/types"; +import { ENTITY_TYPE } from "ee/entities/DataTree/types"; import type { ConfigTree, DataTreeEntity, @@ -47,6 +44,7 @@ import DataTreeEvaluator from "workers/common/DataTreeEvaluator"; import { Severity } from "entities/AppsmithConsole"; import { PluginType } from "entities/Plugin"; import { registerWidgets } from "WidgetProvider/factory/registrationHelper"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; // to check if logWarn was called. // use jest.unmock, if the mock needs to be removed. diff --git a/app/client/src/ce/workers/Evaluation/evaluationUtils.ts b/app/client/src/ce/workers/Evaluation/evaluationUtils.ts index 141fac8e90f6..293fe0a221ae 100644 --- a/app/client/src/ce/workers/Evaluation/evaluationUtils.ts +++ b/app/client/src/ce/workers/Evaluation/evaluationUtils.ts @@ -1144,3 +1144,80 @@ export const convertMicroDiffToDeepDiff = ( rhs: microDifference.value, }; }); + +export function getExternalChangedDependencies( + property: string, + dependencies: Record, + valuechanged: Record, + entityName: string, + visited = new Set(), +): boolean { + if (visited.has(property)) return false; + + visited.add(property); + + const deps = dependencies[property]; + + if (!deps || deps.length === 0) return false; + + for (const dep of deps) { + if (!dep.startsWith(entityName + ".")) { + // External dependency + if (valuechanged[dep]) return true; + } else { + // Internal dependency, recurse + if ( + getExternalChangedDependencies( + dep, + dependencies, + valuechanged, + entityName, + visited, + ) + ) { + return true; + } + } + } + + return false; +} + +export const isDataPath = ( + entity: DataTreeEntity, + fullPropertyPath: string, +) => { + if (isWidget(entity)) { + return false; + } + + const { propertyPath } = getEntityNameAndPropertyPath(fullPropertyPath); + + if (isAction(entity) && propertyPath === "data") { + return true; + } + + if (isJSAction(entity)) { + // Check if propertyPath matches .data (not just 'data') + if (propertyPath.endsWith(".data") && propertyPath !== "data") { + return true; + } + } + + return false; +}; + +/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ +export function isJSModuleInstance(entity: DataTreeEntity) { + return false; +} + +export const entityTypeCheckForPathDynamicTrigger = ( + entityConfig: DataTreeEntityConfig, +) => { + return ( + "ENTITY_TYPE" in entityConfig && + (entityConfig.ENTITY_TYPE === "ACTION" || + entityConfig.ENTITY_TYPE === "JSACTION") + ); +}; diff --git a/app/client/src/ce/workers/common/DependencyMap/utils/getEntityDependenciesByType.ts b/app/client/src/ce/workers/common/DependencyMap/utils/getEntityDependenciesByType.ts index bb7060eecf17..6e4aa697241e 100644 --- a/app/client/src/ce/workers/common/DependencyMap/utils/getEntityDependenciesByType.ts +++ b/app/client/src/ce/workers/common/DependencyMap/utils/getEntityDependenciesByType.ts @@ -132,6 +132,16 @@ export function getJSDependencies( dependencies = { ...dependencies, [fullPropertyPath]: newDeps }; } + for (const funcName of jsActionConfig.actionNames) { + const func = jsEntity[funcName]; + + if (func) { + dependencies[`${jsObjectName}.${funcName}.data`] = [ + `${jsObjectName}.${funcName}`, + ]; + } + } + return dependencies; } export function getActionDependencies( diff --git a/app/client/src/components/editorComponents/CodeEditor/EvaluatedValuePopup.tsx b/app/client/src/components/editorComponents/CodeEditor/EvaluatedValuePopup.tsx index 70a6df2aaa9c..14cd399d11a0 100644 --- a/app/client/src/components/editorComponents/CodeEditor/EvaluatedValuePopup.tsx +++ b/app/client/src/components/editorComponents/CodeEditor/EvaluatedValuePopup.tsx @@ -8,7 +8,7 @@ import type { FieldEntityInformation } from "components/editorComponents/CodeEdi import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig"; import type { Placement } from "popper.js"; import { EvaluatedValueDebugButton } from "components/editorComponents/Debugger/DebugCTA"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import type { IPopoverSharedProps } from "@blueprintjs/core"; import { Classes, Collapse } from "@blueprintjs/core"; import { UNDEFINED_VALIDATION } from "utils/validation/common"; diff --git a/app/client/src/components/editorComponents/CodeEditor/index.tsx b/app/client/src/components/editorComponents/CodeEditor/index.tsx index c621d571a0e2..00ef8774a642 100644 --- a/app/client/src/components/editorComponents/CodeEditor/index.tsx +++ b/app/client/src/components/editorComponents/CodeEditor/index.tsx @@ -34,7 +34,6 @@ import _, { debounce, isEqual, isNumber } from "lodash"; import scrollIntoView from "scroll-into-view-if-needed"; import { ENTITY_TYPE } from "ee/entities/DataTree/types"; -import type { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; import type { DataTree } from "entities/DataTree/dataTreeTypes"; import { Skin } from "constants/DefaultTheme"; import AnalyticsUtil from "ee/utils/AnalyticsUtil"; @@ -162,6 +161,7 @@ import { getCurrentPageId } from "selectors/editorSelectors"; import { executeCommandAction } from "actions/pluginActionActions"; import { PEEK_OVERLAY_DELAY } from "./PeekOverlayPopup/constants"; import { SAVE_TRIGGER_DELAY_MS } from "./debounceConstants"; +import type { EvaluationSubstitutionType } from "constants/EvaluationConstants"; type ReduxStateProps = ReturnType; type ReduxDispatchProps = ReturnType; diff --git a/app/client/src/components/formControls/DynamicTextFieldControl.tsx b/app/client/src/components/formControls/DynamicTextFieldControl.tsx index c96595b29ce9..5e2f728e60dd 100644 --- a/app/client/src/components/formControls/DynamicTextFieldControl.tsx +++ b/app/client/src/components/formControls/DynamicTextFieldControl.tsx @@ -18,9 +18,9 @@ import { getPluginNameFromId, } from "ee/selectors/entitiesSelector"; import { actionPathFromName } from "components/formControls/utils"; -import type { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; import { getSqlEditorModeFromPluginName } from "components/editorComponents/CodeEditor/sql/config"; import { Flex } from "@appsmith/ads"; +import type { EvaluationSubstitutionType } from "constants/EvaluationConstants"; const Wrapper = styled.div` min-width: 260px; diff --git a/app/client/src/constants/EvaluationConstants.ts b/app/client/src/constants/EvaluationConstants.ts new file mode 100644 index 000000000000..b5b7aa08cecb --- /dev/null +++ b/app/client/src/constants/EvaluationConstants.ts @@ -0,0 +1,5 @@ +export enum EvaluationSubstitutionType { + TEMPLATE = "TEMPLATE", + PARAMETER = "PARAMETER", + SMART_SUBSTITUTE = "SMART_SUBSTITUTE", +} diff --git a/app/client/src/constants/PropertyControlConstants.tsx b/app/client/src/constants/PropertyControlConstants.tsx index 38f6efeac1aa..f77cc6f4f34b 100644 --- a/app/client/src/constants/PropertyControlConstants.tsx +++ b/app/client/src/constants/PropertyControlConstants.tsx @@ -3,7 +3,6 @@ import type { ValidationResponse, ValidationTypes, } from "constants/WidgetValidation"; -import type { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; import type { CodeEditorExpected } from "components/editorComponents/CodeEditor"; import type { UpdateWidgetPropertyPayload } from "actions/controlActions"; import type { AdditionalDynamicDataTree } from "utils/autocomplete/customTreeTypeDefCreator"; @@ -11,6 +10,7 @@ import type { Stylesheet } from "entities/AppTheming"; import type { ReduxActionType } from "actions/ReduxActionTypes"; import type { PropertyUpdates } from "WidgetProvider/types"; import type { WidgetProps } from "widgets/BaseWidget"; +import type { EvaluationSubstitutionType } from "constants/EvaluationConstants"; const ControlTypes = getPropertyControlTypes(); diff --git a/app/client/src/ee/entities/DataTree/types/DataTreeSeed.ts b/app/client/src/ee/entities/DataTree/types/DataTreeSeed.ts new file mode 100644 index 000000000000..d733ebe980e3 --- /dev/null +++ b/app/client/src/ee/entities/DataTree/types/DataTreeSeed.ts @@ -0,0 +1 @@ +export * from "ce/entities/DataTree/types/DataTreeSeed"; diff --git a/app/client/src/ee/sagas/moduleInstanceSagaUtils.ts b/app/client/src/ee/sagas/moduleInstanceSagaUtils.ts new file mode 100644 index 000000000000..4c69420a01fb --- /dev/null +++ b/app/client/src/ee/sagas/moduleInstanceSagaUtils.ts @@ -0,0 +1 @@ +export * from "ce/sagas/moduleInstanceSagaUtils"; diff --git a/app/client/src/entities/Action/actionProperties.test.ts b/app/client/src/entities/Action/actionProperties.test.ts index 0ee379536d59..2204334eb010 100644 --- a/app/client/src/entities/Action/actionProperties.test.ts +++ b/app/client/src/entities/Action/actionProperties.test.ts @@ -1,7 +1,7 @@ import type { Action } from "entities/Action"; import { PluginType } from "entities/Plugin"; import { getBindingAndReactivePathsOfAction } from "entities/Action/actionProperties"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; const DEFAULT_ACTION: Action = { actionConfiguration: {}, @@ -40,6 +40,7 @@ describe("getReactivePathsOfAction", () => { isLoading: EvaluationSubstitutionType.TEMPLATE, config: EvaluationSubstitutionType.TEMPLATE, datasourceUrl: EvaluationSubstitutionType.TEMPLATE, + run: EvaluationSubstitutionType.TEMPLATE, }); }); @@ -93,6 +94,7 @@ describe("getReactivePathsOfAction", () => { data: EvaluationSubstitutionType.TEMPLATE, isLoading: EvaluationSubstitutionType.TEMPLATE, datasourceUrl: EvaluationSubstitutionType.TEMPLATE, + run: EvaluationSubstitutionType.TEMPLATE, "config.body": EvaluationSubstitutionType.TEMPLATE, "config.body2": EvaluationSubstitutionType.TEMPLATE, "config.field1": EvaluationSubstitutionType.SMART_SUBSTITUTE, @@ -162,6 +164,7 @@ describe("getReactivePathsOfAction", () => { data: EvaluationSubstitutionType.TEMPLATE, isLoading: EvaluationSubstitutionType.TEMPLATE, datasourceUrl: EvaluationSubstitutionType.TEMPLATE, + run: EvaluationSubstitutionType.TEMPLATE, "config.params[0].key": EvaluationSubstitutionType.TEMPLATE, "config.params[0].value": EvaluationSubstitutionType.TEMPLATE, "config.params[1].key": EvaluationSubstitutionType.TEMPLATE, @@ -221,6 +224,7 @@ describe("getReactivePathsOfAction", () => { data: EvaluationSubstitutionType.TEMPLATE, isLoading: EvaluationSubstitutionType.TEMPLATE, datasourceUrl: EvaluationSubstitutionType.TEMPLATE, + run: EvaluationSubstitutionType.TEMPLATE, "config.key": EvaluationSubstitutionType.TEMPLATE, "config.value": EvaluationSubstitutionType.TEMPLATE, }); @@ -282,6 +286,7 @@ describe("getReactivePathsOfAction", () => { data: EvaluationSubstitutionType.TEMPLATE, isLoading: EvaluationSubstitutionType.TEMPLATE, datasourceUrl: EvaluationSubstitutionType.TEMPLATE, + run: EvaluationSubstitutionType.TEMPLATE, "config.body": EvaluationSubstitutionType.TEMPLATE, "config.field1": EvaluationSubstitutionType.SMART_SUBSTITUTE, }); @@ -297,6 +302,7 @@ describe("getReactivePathsOfAction", () => { data: EvaluationSubstitutionType.TEMPLATE, isLoading: EvaluationSubstitutionType.TEMPLATE, datasourceUrl: EvaluationSubstitutionType.TEMPLATE, + run: EvaluationSubstitutionType.TEMPLATE, "config.body": EvaluationSubstitutionType.TEMPLATE, "config.body2": EvaluationSubstitutionType.TEMPLATE, }); diff --git a/app/client/src/entities/Action/actionProperties.ts b/app/client/src/entities/Action/actionProperties.ts index 34b6bb8d58f6..db20c559837d 100644 --- a/app/client/src/entities/Action/actionProperties.ts +++ b/app/client/src/entities/Action/actionProperties.ts @@ -1,6 +1,6 @@ import type { Action } from "entities/Action/index"; import _ from "lodash"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import { alternateViewTypeInputConfig, isHidden, @@ -48,6 +48,7 @@ export const getBindingAndReactivePathsOfAction = ( data: EvaluationSubstitutionType.TEMPLATE, isLoading: EvaluationSubstitutionType.TEMPLATE, datasourceUrl: EvaluationSubstitutionType.TEMPLATE, + run: EvaluationSubstitutionType.TEMPLATE, }; const bindingPaths: BindingPaths = {}; diff --git a/app/client/src/entities/DataTree/dataTreeFactory.ts b/app/client/src/entities/DataTree/dataTreeFactory.ts index 95ea698c728e..facc5cce9a0a 100644 --- a/app/client/src/entities/DataTree/dataTreeFactory.ts +++ b/app/client/src/entities/DataTree/dataTreeFactory.ts @@ -19,7 +19,7 @@ import type { DependencyMap, FormEditorConfigs, } from "utils/DynamicBindingUtils"; -import type { DataTreeSeed } from "ee/entities/DataTree/types"; +import type { DataTreeSeed } from "ee/entities/DataTree/types/DataTreeSeed"; export class DataTreeFactory { public static metaWidgets( diff --git a/app/client/src/entities/DataTree/dataTreeWidget.test.ts b/app/client/src/entities/DataTree/dataTreeWidget.test.ts index a97c1953d0d4..f3c357586141 100644 --- a/app/client/src/entities/DataTree/dataTreeWidget.test.ts +++ b/app/client/src/entities/DataTree/dataTreeWidget.test.ts @@ -3,14 +3,12 @@ import { generateDataTreeWidget, getSetterConfig, } from "entities/DataTree/dataTreeWidget"; -import { - ENTITY_TYPE, - EvaluationSubstitutionType, -} from "ee/entities/DataTree/types"; +import { ENTITY_TYPE } from "ee/entities/DataTree/types"; import WidgetFactory from "WidgetProvider/factory"; import { ValidationTypes } from "constants/WidgetValidation"; import { RenderModes } from "constants/WidgetConstants"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; // const WidgetTypes = WidgetFactory.widgetTypes; diff --git a/app/client/src/entities/DependencyMap/DependencyMapUtils.ts b/app/client/src/entities/DependencyMap/DependencyMapUtils.ts index d85cf34d8dbc..9e4cb175ad78 100644 --- a/app/client/src/entities/DependencyMap/DependencyMapUtils.ts +++ b/app/client/src/entities/DependencyMap/DependencyMapUtils.ts @@ -1,6 +1,12 @@ import toposort from "toposort"; import type DependencyMap from "."; -import { IMMEDIATE_PARENT_REGEX } from "ee/workers/Evaluation/evaluationUtils"; +import { + entityTypeCheckForPathDynamicTrigger, + getEntityNameAndPropertyPath, + IMMEDIATE_PARENT_REGEX, +} from "ee/workers/Evaluation/evaluationUtils"; +import type { ConfigTree } from "entities/DataTree/dataTreeTypes"; +import { isPathDynamicTrigger } from "utils/DynamicBindingUtils"; type SortDependencies = | { @@ -11,7 +17,10 @@ type SortDependencies = export class DependencyMapUtils { // inspired by https://www.npmjs.com/package/toposort#sorting-dependencies - static sortDependencies(dependencyMap: DependencyMap): SortDependencies { + static sortDependencies( + dependencyMap: DependencyMap, + configTree?: ConfigTree, + ): SortDependencies { const dependencyTree: Array<[string, string | undefined]> = []; const dependencies = dependencyMap.rawDependencies; @@ -29,8 +38,27 @@ export class DependencyMapUtils { .reverse() .filter((edge) => !!edge); + if (configTree) { + this.detectReactiveDependencyMisuse(dependencyMap, configTree); + } + return { success: true, sortedDependencies }; } catch (error) { + // Add 1 second delay using setTimeout + if ( + error instanceof Error && + error.message.includes("Reactive dependency misuse") + ) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + return { + success: false, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + cyclicNode: (error as any).node, + error, + }; + } + // Cyclic dependency found. Extract node const cyclicNodes = (error as Error).message.match( new RegExp('Cyclic dependency, node was:"(.*)"'), @@ -80,6 +108,11 @@ export class DependencyMapUtils { dependencyMap: DependencyMap, child: string, ) => { + // Skip adding dependencies for paths containing .data + if (child.includes(".data")) { + return; + } + let curKey = child; let matches: string[] | null; @@ -104,4 +137,107 @@ export class DependencyMapUtils { curKey = immediateParent; } }; + + static isTriggerPath(path: string, configTree: ConfigTree) { + const { entityName, propertyPath } = getEntityNameAndPropertyPath(path); + const entityConfig = configTree[entityName]; + + if (!entityConfig) { + return false; + } + + if (entityTypeCheckForPathDynamicTrigger(entityConfig)) { + return isPathDynamicTrigger(entityConfig, propertyPath); + } + + return false; + } + + static isDataPath(path: string) { + return path.endsWith(".data"); + } + + static detectReactiveDependencyMisuse( + dependencyMap: DependencyMap, + configTree: ConfigTree, + ) { + const dependencies = dependencyMap.rawDependencies; + + // Helper function to get all transitive dependencies + const getAllTransitiveDependencies = (node: string): Set => { + const allDeps = new Set(); + const queue = [node]; + + while (queue.length > 0) { + const current = queue.shift()!; + const deps = dependencyMap.getDirectDependencies(current) || []; + + for (const dep of deps) { + if (!allDeps.has(dep)) { + allDeps.add(dep); + queue.push(dep); + } + } + } + + return allDeps; + }; + + for (const [node, deps] of dependencies.entries()) { + // Get all dependencies including transitive ones + const allDeps = new Set(); + const queue = Array.from(deps); + + while (queue.length > 0) { + const dep = queue.shift()!; + + if (!allDeps.has(dep)) { + allDeps.add(dep); + const depDeps = dependencyMap.getDirectDependencies(dep) || []; + + queue.push(...depDeps); + } + } + + // Separate dependencies into trigger paths and data paths + const triggerPaths = Array.from(deps).filter((dep) => + this.isTriggerPath(dep, configTree), + ); + const dataPaths = Array.from(deps).filter((dep) => this.isDataPath(dep)); + + // For each trigger path, check if there's a data path from the same entity + for (const triggerPath of triggerPaths) { + const triggerEntity = triggerPath.split(".")[0]; + + // Find data paths from the same entity + const sameEntityDataPaths = dataPaths.filter((dataPath) => { + const dataEntity = dataPath.split(".")[0]; + + return dataEntity === triggerEntity; + }); + + if (sameEntityDataPaths.length > 0) { + // Check if any of these data paths depend on the trigger path (directly or indirectly) + for (const dataPath of sameEntityDataPaths) { + const dataPathTransitiveDeps = + getAllTransitiveDependencies(dataPath); + + if (dataPathTransitiveDeps.has(triggerPath)) { + const error = new Error( + `Reactive dependency misuse: '${node}' depends on both trigger path '${triggerPath}' and data path '${dataPath}' from the same entity, and '${dataPath}' depends on '${triggerPath}' (directly or indirectly). This can cause unexpected reactivity.`, + ); + + // Add custom properties + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (error as any).node = node; + + throw error; + } + } + } + } + } + } } diff --git a/app/client/src/entities/DependencyMap/__tests__/index.test.ts b/app/client/src/entities/DependencyMap/__tests__/index.test.ts index e1eaa63e81f6..93185d1b2678 100644 --- a/app/client/src/entities/DependencyMap/__tests__/index.test.ts +++ b/app/client/src/entities/DependencyMap/__tests__/index.test.ts @@ -2743,9 +2743,7 @@ describe("Tests for DependencyMapUtils", () => { affectedNodes, ); // after linkAffectedChildNodesToParent execution "apiData" does get linked to "apiData.data" - expect(dependencyMap.rawDependencies.get("apiData")).toEqual( - new Set(["apiData.data"]), - ); + expect(dependencyMap.rawDependencies.get("apiData")).toEqual(undefined); }); test("should not link child node to its parentNode as a dependant when the child node is not affected ", () => { const dependencyMap = createSomeDependencyMap(); @@ -2815,10 +2813,8 @@ describe("Tests for DependencyMapUtils", () => { "apiData.data", ); // addDependency is called - expect(spy).toHaveBeenCalledTimes(1); - expect(dependencyMap.getDirectDependencies("apiData")).toEqual([ - "apiData.data", - ]); + expect(spy).toHaveBeenCalledTimes(0); + expect(dependencyMap.getDirectDependencies("apiData")).toEqual([]); }); test("should not trigger addDependency when the child node is there ", () => { const dependencyMap = createSomeDependencyMap(); diff --git a/app/client/src/entities/Widget/utils.test.ts b/app/client/src/entities/Widget/utils.test.ts index 934872978ef0..182b5c451268 100644 --- a/app/client/src/entities/Widget/utils.test.ts +++ b/app/client/src/entities/Widget/utils.test.ts @@ -5,7 +5,7 @@ import { contentConfig, styleConfig, } from "widgets/ChartWidget/widget/propertyConfig"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import { ValidationTypes } from "constants/WidgetValidation"; describe("getAllPathsFromPropertyConfig", () => { diff --git a/app/client/src/entities/Widget/utils.ts b/app/client/src/entities/Widget/utils.ts index 1d6b7417c5fb..87c8f2138b58 100644 --- a/app/client/src/entities/Widget/utils.ts +++ b/app/client/src/entities/Widget/utils.ts @@ -2,7 +2,7 @@ import type { PropertyPaneConfig, ValidationConfig, } from "constants/PropertyControlConstants"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import { get, isObject, isUndefined, omitBy } from "lodash"; import memoize from "micro-memoize"; import type { FlattenedWidgetProps } from "ee/reducers/entityReducers/canvasWidgetsReducer"; diff --git a/app/client/src/layoutSystems/common/DSLConversions/fixedToAutoLayout.ts b/app/client/src/layoutSystems/common/DSLConversions/fixedToAutoLayout.ts index 2f6041ff791e..447da9811471 100644 --- a/app/client/src/layoutSystems/common/DSLConversions/fixedToAutoLayout.ts +++ b/app/client/src/layoutSystems/common/DSLConversions/fixedToAutoLayout.ts @@ -236,8 +236,7 @@ function getNextLayer(currWidgets: DSLWidget[]): { alignment = alignmentMap[currWidget.widgetId] || FlexLayerAlignment.Start; const flexVerticalAlignment = getWidgetVerticalAlignment(currWidget); - const modifiedCurrentWidget = - removeNullValuesFromObject(currWidget); + const modifiedCurrentWidget = removeNullValuesFromObject(currWidget); modifiedWidgetsInLayer.push( verifyDynamicPathBindingList( @@ -810,12 +809,8 @@ function handleSpecialCaseWidgets(dsl: DSLWidget): DSLWidget { * @param object * @returns */ -// TODO: Fix this the next time the file is edited -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function removeNullValuesFromObject( - object: T, -): T { - const copiedObject: T = { ...object }; +function removeNullValuesFromObject(object: DSLWidget): DSLWidget { + const copiedObject: DSLWidget = { ...object }; //remove null values and dynamic trigger paths which have "null" values Object.keys(copiedObject).forEach( diff --git a/app/client/src/plugins/Linting/utils/entityParser.ts b/app/client/src/plugins/Linting/utils/entityParser.ts index cf1c612e48ca..1e7bec196f0b 100644 --- a/app/client/src/plugins/Linting/utils/entityParser.ts +++ b/app/client/src/plugins/Linting/utils/entityParser.ts @@ -4,7 +4,7 @@ import type { JSActionEntity as TJSActionEntity, } from "ee/entities/DataTree/types"; import type { DataTreeEntity } from "entities/DataTree/dataTreeTypes"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import type { TParsedJSProperty } from "@shared/ast"; import { isJSFunctionProperty } from "@shared/ast"; import { parseJSObject } from "@shared/ast"; diff --git a/app/client/src/sagas/ActionExecution/PluginActionSaga.ts b/app/client/src/sagas/ActionExecution/PluginActionSaga.ts index b9375c913eee..8aeab2bc56bf 100644 --- a/app/client/src/sagas/ActionExecution/PluginActionSaga.ts +++ b/app/client/src/sagas/ActionExecution/PluginActionSaga.ts @@ -870,7 +870,7 @@ export function* runActionSaga( }; const allowedActionAnalyticsKeys = getAllowedActionAnalyticsKeys( - plugin.packageName, + plugin?.packageName, ); const actionAnalyticsPayload = getActionProperties( actionObject, @@ -964,6 +964,7 @@ export function* runActionSaga( isMock: !!datasource?.isMock, actionConfig: actionAnalyticsPayload, source: reduxAction.payload.actionExecutionContext, + runBehaviour: actionObject?.runBehaviour, }); yield put({ @@ -1125,6 +1126,7 @@ function* executePageLoadAction( source: !!actionExecutionContext ? actionExecutionContext : ActionExecutionContext.PAGE_LOAD, + runBehaviour: action?.runBehaviour, }); const actionName = getPluginActionNameToDisplay( @@ -1162,6 +1164,11 @@ function* executePageLoadAction( } } + yield put({ + type: ReduxActionTypes.SET_ONLOAD_ACTION_EXECUTED, + payload: { id: pageAction.id, isExecuted: true }, + }); + // open response tab in debugger on exection of action on page load. // Only if current page is the page on which the action is executed. if ( diff --git a/app/client/src/sagas/EvaluationsSaga.ts b/app/client/src/sagas/EvaluationsSaga.ts index 7af5831a76c5..49dae0c20078 100644 --- a/app/client/src/sagas/EvaluationsSaga.ts +++ b/app/client/src/sagas/EvaluationsSaga.ts @@ -147,6 +147,7 @@ export function* updateDataTreeHandler( errors, evalMetaUpdates = [], evaluationOrder, + executeReactiveActions, isCreateFirstTree = false, isNewWidgetAdded, jsUpdates, @@ -159,6 +160,10 @@ export function* updateDataTreeHandler( updates, } = evalTreeResponse; + const featureFlags: Record = + yield select(selectFeatureFlags); + const isReactiveActionsEnabled = + featureFlags.release_reactive_actions_enabled; const appMode: ReturnType = yield select(getAppMode); if (!isEmpty(staleMetaIds)) { @@ -230,6 +235,21 @@ export function* updateDataTreeHandler( yield call(postEvalActionDispatcher, postEvalActionsToDispatch); } + if ( + executeReactiveActions && + executeReactiveActions.length && + isReactiveActionsEnabled + ) { + yield put({ + type: ReduxActionTypes.EXECUTE_REACTIVE_QUERIES, + payload: { + executeReactiveActions, + dataTree: updatedDataTree, + configTree, + }, + }); + } + yield call(logJSVarCreatedEvent, jsVarsCreatedEvent); } diff --git a/app/client/src/sagas/JSPaneSagas.ts b/app/client/src/sagas/JSPaneSagas.ts index 085d47d8bb84..9401bd260685 100644 --- a/app/client/src/sagas/JSPaneSagas.ts +++ b/app/client/src/sagas/JSPaneSagas.ts @@ -650,6 +650,7 @@ export function* handleStartExecuteJSFunctionSaga( consoleStatements: action.actionConfiguration?.body?.match(CONSOLE_DOT_LOG_INVOCATION_REGEX) ?.length || 0, + runBehaviour: action.runBehaviour, }); yield call(handleExecuteJSFunctionSaga, { diff --git a/app/client/src/sagas/PostEvaluationSagas.ts b/app/client/src/sagas/PostEvaluationSagas.ts index 9d705214da76..0171e364cf1e 100644 --- a/app/client/src/sagas/PostEvaluationSagas.ts +++ b/app/client/src/sagas/PostEvaluationSagas.ts @@ -1,5 +1,6 @@ import { ENTITY_TYPE, PLATFORM_ERROR } from "ee/entities/AppsmithConsole/utils"; import type { + JSActionEntityConfig, WidgetEntity, WidgetEntityConfig, } from "ee/entities/DataTree/types"; @@ -14,14 +15,23 @@ import { getDataTreeForAutocomplete, getEntityNameAndPropertyPath, isAction, + isJSAction, isWidget, } from "ee/workers/Evaluation/evaluationUtils"; import type { EvaluationError } from "utils/DynamicBindingUtils"; import { getEvalErrorPath } from "utils/DynamicBindingUtils"; import { find, get, some } from "lodash"; import LOG_TYPE from "entities/AppsmithConsole/logtype"; -import { call, put, select } from "redux-saga/effects"; -import type { AnyReduxAction } from "actions/ReduxActionTypes"; +import { + call, + put, + select, + take, + race, + all, + debounce, +} from "redux-saga/effects"; +import type { AnyReduxAction, ReduxAction } from "actions/ReduxActionTypes"; import AppsmithConsole from "utils/AppsmithConsole"; import AnalyticsUtil from "ee/utils/AnalyticsUtil"; import { createMessage, JS_EXECUTION_FAILURE } from "ee/constants/messages"; @@ -42,9 +52,29 @@ import { endSpan, startRootSpan } from "instrumentation/generateTraces"; import { getJSActionPathNameToDisplay } from "ee/utils/actionExecutionUtils"; import { showToastOnExecutionError } from "./ActionExecution/errorUtils"; import { waitForFetchEnvironments } from "ee/sagas/EnvironmentSagas"; +import { startExecutingJSFunction } from "actions/jsPaneActions"; +import { getJSCollection } from "ee/selectors/entitiesSelector"; +import { + ReduxActionErrorTypes, + ReduxActionTypes, +} from "ee/constants/ReduxActionConstants"; +import { getModuleInstanceJSCollectionById } from "ee/selectors/moduleInstanceSelectors"; +import { ActionRunBehaviour } from "PluginActionEditor/types/PluginActionTypes"; +import { getOnLoadActionsWithExecutionStatus } from "selectors/editorSelectors"; +import { executionForJSModuleInstance } from "ee/sagas/moduleInstanceSagaUtils"; +import { runAction } from "actions/pluginActionActions"; let successfulBindingsMap: SuccessfulBindingMap | undefined; +export function* runSingleAction(actionId: string) { + yield put(runAction(actionId)); + yield race([ + take(ReduxActionTypes.RUN_ACTION_SUCCESS), + take(ReduxActionTypes.RUN_ACTION_CANCELLED), + take(ReduxActionErrorTypes.RUN_ACTION_ERROR), + ]); +} + export function* logJSVarCreatedEvent( jsVarsCreatedEvent: EvalTreeResponseData["jsVarsCreatedEvent"], ) { @@ -314,3 +344,122 @@ export function* handleJSFunctionExecutionErrorLog( ]) : AppsmithConsole.deleteErrors([{ id: `${collectionId}-${action.id}` }]); } + +export function* executeReactiveQueries( + action: ReduxAction<{ + executeReactiveActions: string[]; + dataTree: DataTree; + configTree: ConfigTree; + }>, +) { + const { configTree, dataTree, executeReactiveActions } = action.payload; + + // Track executed actions to prevent infinite loops + const executedActions = new Set(); + const pendingActions = new Set(executeReactiveActions); + + const onLoadActionExecutions: Record[] = + yield select(getOnLoadActionsWithExecutionStatus); + + while (pendingActions.size > 0) { + // Get next action to execute + const action = Array.from(pendingActions)[0]; + + pendingActions.delete(action); + + // Skip if already executed in this cycle + if (executedActions.has(action)) { + continue; + } + + const { entityName, propertyPath } = getEntityNameAndPropertyPath(action); + const entity = dataTree[entityName]; + + // Skip if entity doesn't exist + if (!entity) { + //eslint-disable-next-line no-console + log.warn(`Entity ${entityName} not found in dataTree`); + continue; + } + + // Mark action as executed + executedActions.add(action); + + if (isJSAction(entity)) { + const entityConfig = configTree[entityName] as JSActionEntityConfig; + let collection: JSCollection = yield select( + getJSCollection, + entity.actionId, + ); + + if (!!entityConfig.moduleInstanceId) { + collection = yield select( + getModuleInstanceJSCollectionById, + entity.actionId, + ); + } + + const action = collection?.actions.find( + (action) => action.name === propertyPath, + ); + + if (collection && action) { + const runBehaviour = + entityConfig?.meta && entityConfig.meta[action.name]?.runBehaviour; + + if (runBehaviour === ActionRunBehaviour.AUTOMATIC) { + yield put( + startExecutingJSFunction({ + action, + collection: collection, + from: "KEYBOARD_SHORTCUT", + openDebugger: false, + }), + ); + // Wait for the JS function execution to complete + yield race([ + take( + (action: AnyReduxAction) => + action.type === ReduxActionTypes.EXECUTE_JS_FUNCTION_SUCCESS || + action.type === + ReduxActionTypes.SET_JS_FUNCTION_EXECUTION_ERRORS || + action.type === ReduxActionTypes.SET_JS_FUNCTION_EXECUTION_DATA, + ), + ]); + } + } + } + + if (isAction(entity)) { + const entityConfig = configTree[entityName] as ActionEntityConfig; + const onLoadAction = onLoadActionExecutions.find( + (action) => action.id == entity.actionId, + ); + + if ( + entityConfig.runBehaviour === ActionRunBehaviour.AUTOMATIC && + onLoadAction?.isExecuted + ) { + yield runSingleAction(entityConfig.actionId); + } + } + + yield call(executionForJSModuleInstance, { + entity, + entityName, + dataTree, + configTree, + propertyPath, + }); + } +} + +export default function* PostEvaluationSagas() { + yield all([ + debounce( + 1000, + ReduxActionTypes.EXECUTE_REACTIVE_QUERIES, + executeReactiveQueries, + ), + ]); +} diff --git a/app/client/src/selectors/editorSelectors.tsx b/app/client/src/selectors/editorSelectors.tsx index bb4f32268084..c4e0e67b2580 100644 --- a/app/client/src/selectors/editorSelectors.tsx +++ b/app/client/src/selectors/editorSelectors.tsx @@ -130,6 +130,19 @@ export const getLayoutOnLoadIssues = (state: DefaultRootState) => { return state.ui.editor.layoutOnLoadActionErrors || []; }; +export const getOnLoadActionsWithExecutionStatus = createSelector( + getLayoutOnLoadActions, + (state: DefaultRootState) => state.ui.editor.onLoadActionExecution, + (onLoadActions, onLoadActionExecution) => { + // onLoadActions is PageAction[][] + // Flatten and map to { id, isExecuted } + return (onLoadActions.flat() || []).map((action) => ({ + id: action.id, + isExecuted: !!onLoadActionExecution?.[action.id], + })); + }, +); + export const getIsPublishingApplication = (state: DefaultRootState) => state.ui.editor.loadingStates.publishing; diff --git a/app/client/src/utils/DynamicBindingUtils.ts b/app/client/src/utils/DynamicBindingUtils.ts index 669bcf546b20..74c2a27bcca8 100644 --- a/app/client/src/utils/DynamicBindingUtils.ts +++ b/app/client/src/utils/DynamicBindingUtils.ts @@ -240,15 +240,16 @@ export const getWidgetDynamicTriggerPathList = ( return []; }; -// TODO: Fix this the next time the file is edited -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const isPathDynamicTrigger = (widget: any, path: string): boolean => { +export const isPathDynamicTrigger = ( + entity: DataTreeEntityConfig | WidgetProps, + path: string, +): boolean => { if ( - widget && - widget.dynamicTriggerPathList && - Array.isArray(widget.dynamicTriggerPathList) + entity && + entity.dynamicTriggerPathList && + Array.isArray(entity.dynamicTriggerPathList) ) { - return _.find(widget.dynamicTriggerPathList, { key: path }) !== undefined; + return _.find(entity.dynamicTriggerPathList, { key: path }) !== undefined; } return false; diff --git a/app/client/src/utils/FilterInternalProperties/__tests__/index.test.ts b/app/client/src/utils/FilterInternalProperties/__tests__/index.test.ts index 40c5c8e6f6f5..f93daefc6de0 100644 --- a/app/client/src/utils/FilterInternalProperties/__tests__/index.test.ts +++ b/app/client/src/utils/FilterInternalProperties/__tests__/index.test.ts @@ -1,8 +1,5 @@ import { filterInternalProperties } from ".."; -import { - ENTITY_TYPE, - EvaluationSubstitutionType, -} from "ee/entities/DataTree/types"; +import { ENTITY_TYPE } from "ee/entities/DataTree/types"; import type { DataTreeEntityConfig, DataTreeEntityObject, @@ -11,6 +8,7 @@ import type { import { registerWidgets } from "WidgetProvider/factory/registrationHelper"; import InputWidget from "widgets/InputWidgetV2"; import type { JSCollectionData } from "ee/reducers/entityReducers/jsActionsReducer"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; describe("filterInternalProperties tests", () => { beforeAll(() => { diff --git a/app/client/src/utils/autocomplete/__tests__/dataTreeTypeDefCreator.test.ts b/app/client/src/utils/autocomplete/__tests__/dataTreeTypeDefCreator.test.ts index 3e88d0e260d8..3482cc7f4a5b 100644 --- a/app/client/src/utils/autocomplete/__tests__/dataTreeTypeDefCreator.test.ts +++ b/app/client/src/utils/autocomplete/__tests__/dataTreeTypeDefCreator.test.ts @@ -3,10 +3,7 @@ import type { WidgetEntity, WidgetEntityConfig, } from "ee/entities/DataTree/types"; -import { - ENTITY_TYPE, - EvaluationSubstitutionType, -} from "ee/entities/DataTree/types"; +import { ENTITY_TYPE } from "ee/entities/DataTree/types"; import InputWidget from "widgets/InputWidgetV2"; import { registerWidgets } from "WidgetProvider/factory/registrationHelper"; @@ -15,6 +12,7 @@ import { generateTypeDef, getFunctionsArgsType, } from "../defCreatorUtils"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; describe("dataTreeTypeDefCreator", () => { it("creates the right def for a widget", () => { diff --git a/app/client/src/utils/editor/EditorBindingPaths.ts b/app/client/src/utils/editor/EditorBindingPaths.ts index ea2e012dab53..6bc815cec3b1 100644 --- a/app/client/src/utils/editor/EditorBindingPaths.ts +++ b/app/client/src/utils/editor/EditorBindingPaths.ts @@ -1,5 +1,5 @@ import { PaginationSubComponent } from "components/formControls/utils"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; /* We kept the graphql constants in this file because we were facing an error : Uncaught TypeError: Cannot read properties of undefined (reading 'register'). So we kept these variables at one place in this file to make sure this is not present in different files. diff --git a/app/client/src/utils/helpers.test.ts b/app/client/src/utils/helpers.test.ts index 6d0c3eecd71b..dcf34c19075b 100644 --- a/app/client/src/utils/helpers.test.ts +++ b/app/client/src/utils/helpers.test.ts @@ -1,6 +1,6 @@ import { RenderModes } from "constants/WidgetConstants"; import { ValidationTypes } from "constants/WidgetValidation"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import type { CanvasWidgetsReduxState } from "ee/reducers/entityReducers/canvasWidgetsReducer"; import { AutocompleteDataType } from "./autocomplete/AutocompleteDataType"; import { diff --git a/app/client/src/widgets/CategorySliderWidget/widget/propertyConfig/contentConfig.ts b/app/client/src/widgets/CategorySliderWidget/widget/propertyConfig/contentConfig.ts index 2a4f3c60df75..df4a77c723b3 100644 --- a/app/client/src/widgets/CategorySliderWidget/widget/propertyConfig/contentConfig.ts +++ b/app/client/src/widgets/CategorySliderWidget/widget/propertyConfig/contentConfig.ts @@ -1,7 +1,7 @@ import { Alignment } from "@blueprintjs/core"; import { LabelPosition } from "components/constants"; import { ValidationTypes } from "constants/WidgetValidation"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; import { isAutoLayout } from "layoutSystems/autolayout/utils/flexWidgetUtils"; import type { CategorySliderWidgetProps } from ".."; diff --git a/app/client/src/widgets/ChartWidget/widget/propertyConfig.ts b/app/client/src/widgets/ChartWidget/widget/propertyConfig.ts index ce0021e76fd6..b82293646a95 100644 --- a/app/client/src/widgets/ChartWidget/widget/propertyConfig.ts +++ b/app/client/src/widgets/ChartWidget/widget/propertyConfig.ts @@ -1,5 +1,5 @@ import { ValidationTypes } from "constants/WidgetValidation"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import type { ChartWidgetProps } from "widgets/ChartWidget/widget"; import { CUSTOM_CHART_TYPES, diff --git a/app/client/src/widgets/CheckboxGroupWidget/widget/index.tsx b/app/client/src/widgets/CheckboxGroupWidget/widget/index.tsx index 81a0af263fe7..e55b5f6529a5 100644 --- a/app/client/src/widgets/CheckboxGroupWidget/widget/index.tsx +++ b/app/client/src/widgets/CheckboxGroupWidget/widget/index.tsx @@ -8,7 +8,7 @@ import type { TextSize } from "constants/WidgetConstants"; import type { ValidationResponse } from "constants/WidgetValidation"; import { ValidationTypes } from "constants/WidgetValidation"; import type { SetterConfig, Stylesheet } from "entities/AppTheming"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import { compact, xor } from "lodash"; import { default as React } from "react"; import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; diff --git a/app/client/src/widgets/DropdownWidget/widget/index.tsx b/app/client/src/widgets/DropdownWidget/widget/index.tsx index bed8055fd594..226004b8084e 100644 --- a/app/client/src/widgets/DropdownWidget/widget/index.tsx +++ b/app/client/src/widgets/DropdownWidget/widget/index.tsx @@ -12,7 +12,7 @@ import { WIDGET_TAGS, layoutConfigurations } from "constants/WidgetConstants"; import type { ValidationResponse } from "constants/WidgetValidation"; import { ValidationTypes } from "constants/WidgetValidation"; import type { Stylesheet } from "entities/AppTheming"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import _ from "lodash"; import { buildDeprecationWidgetMessage } from "pages/Editor/utils"; import React from "react"; diff --git a/app/client/src/widgets/FilePickerWidgetV2/widget/index.tsx b/app/client/src/widgets/FilePickerWidgetV2/widget/index.tsx index 442c4b760e34..6aa5447b6946 100644 --- a/app/client/src/widgets/FilePickerWidgetV2/widget/index.tsx +++ b/app/client/src/widgets/FilePickerWidgetV2/widget/index.tsx @@ -5,7 +5,7 @@ import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import { FILE_SIZE_LIMIT_FOR_BLOBS } from "constants/WidgetConstants"; import { ValidationTypes } from "constants/WidgetValidation"; import type { SetterConfig, Stylesheet } from "entities/AppTheming"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import _, { findIndex } from "lodash"; import log from "loglevel"; diff --git a/app/client/src/widgets/FilepickerWidget/widget/index.tsx b/app/client/src/widgets/FilepickerWidget/widget/index.tsx index 752b7fd3d206..9b9919950ab8 100644 --- a/app/client/src/widgets/FilepickerWidget/widget/index.tsx +++ b/app/client/src/widgets/FilepickerWidget/widget/index.tsx @@ -11,7 +11,7 @@ import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import { WIDGET_TAGS } from "constants/WidgetConstants"; import { ValidationTypes } from "constants/WidgetValidation"; import type { SetterConfig } from "entities/AppTheming"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import _ from "lodash"; import log from "loglevel"; import { buildDeprecationWidgetMessage } from "pages/Editor/utils"; diff --git a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig.ts b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig.ts index b526a6789689..d4e4d47d873d 100644 --- a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig.ts +++ b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig.ts @@ -3,7 +3,7 @@ import { ButtonPlacementTypes, ButtonVariantTypes } from "components/constants"; import type { OnButtonClickProps } from "components/propertyControls/ButtonControl"; import type { ValidationResponse } from "constants/WidgetValidation"; import { ValidationTypes } from "constants/WidgetValidation"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import { EVALUATION_PATH } from "utils/DynamicBindingUtils"; import type { ButtonWidgetProps } from "widgets/ButtonWidget/widget"; import type { JSONFormWidgetProps } from "."; diff --git a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/common.ts b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/common.ts index ab0d1b96160c..be0198be711a 100644 --- a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/common.ts +++ b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/common.ts @@ -1,6 +1,6 @@ import type { ValidationResponse } from "constants/WidgetValidation"; import { ValidationTypes } from "constants/WidgetValidation"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import { get } from "lodash"; import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; import type { SchemaItem } from "widgets/JSONFormWidget/constants"; diff --git a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/multiSelect.ts b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/multiSelect.ts index 7c561c7264a6..3c2b419ac0f6 100644 --- a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/multiSelect.ts +++ b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/multiSelect.ts @@ -1,4 +1,4 @@ -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import { FieldType } from "widgets/JSONFormWidget/constants"; import type { HiddenFnParams } from "../helper"; import { getSchemaItem, getAutocompleteProperties } from "../helper"; diff --git a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/radioGroup.ts b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/radioGroup.ts index b1ab2662ce91..11ab44fac750 100644 --- a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/radioGroup.ts +++ b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/radioGroup.ts @@ -1,6 +1,6 @@ import type { ValidationResponse } from "constants/WidgetValidation"; import { ValidationTypes } from "constants/WidgetValidation"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; import { FieldType } from "widgets/JSONFormWidget/constants"; import { optionsCustomValidation } from "widgets/RadioGroupWidget/widget"; diff --git a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/select.ts b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/select.ts index 525e8dfd66b4..771be36983e9 100644 --- a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/select.ts +++ b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/select.ts @@ -5,7 +5,7 @@ import type { JSONFormWidgetProps } from "../.."; import type { SelectFieldProps } from "widgets/JSONFormWidget/fields/SelectField"; import type { ValidationResponse } from "constants/WidgetValidation"; import { ValidationTypes } from "constants/WidgetValidation"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; import type { LoDashStatic } from "lodash"; diff --git a/app/client/src/widgets/ListWidget/widget/propertyConfig.ts b/app/client/src/widgets/ListWidget/widget/propertyConfig.ts index 96ccb54e18bd..94b5310eb711 100644 --- a/app/client/src/widgets/ListWidget/widget/propertyConfig.ts +++ b/app/client/src/widgets/ListWidget/widget/propertyConfig.ts @@ -3,7 +3,7 @@ import type { WidgetProps } from "widgets/BaseWidget"; import type { ListWidgetProps } from "../constants"; import { ValidationTypes } from "constants/WidgetValidation"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; import { EVAL_VALUE_PATH } from "utils/DynamicBindingUtils"; diff --git a/app/client/src/widgets/ListWidgetV2/widget/propertyConfig.ts b/app/client/src/widgets/ListWidgetV2/widget/propertyConfig.ts index ecb90d10ef09..e82c72ab76ae 100644 --- a/app/client/src/widgets/ListWidgetV2/widget/propertyConfig.ts +++ b/app/client/src/widgets/ListWidgetV2/widget/propertyConfig.ts @@ -2,7 +2,7 @@ import { get, isPlainObject } from "lodash"; import log from "loglevel"; import { EVALUATION_PATH, EVAL_VALUE_PATH } from "utils/DynamicBindingUtils"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import type { ValidationResponse } from "constants/WidgetValidation"; import { ValidationTypes } from "constants/WidgetValidation"; import type { WidgetProps } from "widgets/BaseWidget"; diff --git a/app/client/src/widgets/MapChartWidget/widget/index.tsx b/app/client/src/widgets/MapChartWidget/widget/index.tsx index 7ca9215bb0c5..55ecf07716b1 100644 --- a/app/client/src/widgets/MapChartWidget/widget/index.tsx +++ b/app/client/src/widgets/MapChartWidget/widget/index.tsx @@ -2,7 +2,7 @@ import React, { Suspense, lazy } from "react"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import { ValidationTypes } from "constants/WidgetValidation"; import type { SetterConfig, Stylesheet } from "entities/AppTheming"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import { retryPromise } from "utils/AppsmithUtils"; import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; import type { WidgetProps, WidgetState } from "widgets/BaseWidget"; diff --git a/app/client/src/widgets/MapWidget/widget/index.tsx b/app/client/src/widgets/MapWidget/widget/index.tsx index deb3d6e8a302..465b53a637bd 100644 --- a/app/client/src/widgets/MapWidget/widget/index.tsx +++ b/app/client/src/widgets/MapWidget/widget/index.tsx @@ -7,7 +7,7 @@ import MapComponent from "../component"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import { ValidationTypes } from "constants/WidgetValidation"; import type { SetterConfig, Stylesheet } from "entities/AppTheming"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import styled from "styled-components"; import type { DerivedPropertiesMap } from "WidgetProvider/factory/types"; import type { MarkerProps } from "../constants"; diff --git a/app/client/src/widgets/MenuButtonWidget/widget/propertyConfig/contentConfig.ts b/app/client/src/widgets/MenuButtonWidget/widget/propertyConfig/contentConfig.ts index 004fa9b4c3ca..a4ace1cbbfad 100644 --- a/app/client/src/widgets/MenuButtonWidget/widget/propertyConfig/contentConfig.ts +++ b/app/client/src/widgets/MenuButtonWidget/widget/propertyConfig/contentConfig.ts @@ -1,6 +1,6 @@ import type { PropertyPaneConfig } from "constants/PropertyControlConstants"; import { ValidationTypes } from "constants/WidgetValidation"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; import { sourceDataArrayValidation } from "widgets/MenuButtonWidget/validations"; import type { MenuButtonWidgetProps } from "../../constants"; diff --git a/app/client/src/widgets/MetaHOC.tsx b/app/client/src/widgets/MetaHOC.tsx index bdc9eed3327a..28de167a69ec 100644 --- a/app/client/src/widgets/MetaHOC.tsx +++ b/app/client/src/widgets/MetaHOC.tsx @@ -91,6 +91,8 @@ function withMeta(WrappedWidget: typeof BaseWidget) { const { executeAction } = this.context; const batchActionsToRun = Object.entries(this.actionsToExecute); + if (batchActionsToRun.length === 0) return; + batchActionsToRun.map(([propertyName, actionExecution]) => { if (actionExecution && actionExecution.dynamicString && executeAction) { executeAction({ diff --git a/app/client/src/widgets/MultiSelectTreeWidget/widget/index.tsx b/app/client/src/widgets/MultiSelectTreeWidget/widget/index.tsx index 30a485eba500..adbca4de45ef 100644 --- a/app/client/src/widgets/MultiSelectTreeWidget/widget/index.tsx +++ b/app/client/src/widgets/MultiSelectTreeWidget/widget/index.tsx @@ -6,7 +6,7 @@ import type { TextSize } from "constants/WidgetConstants"; import type { ValidationResponse } from "constants/WidgetValidation"; import { ValidationTypes } from "constants/WidgetValidation"; import type { SetterConfig, Stylesheet } from "entities/AppTheming"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import { isArray, xor } from "lodash"; import type { DefaultValueType } from "rc-tree-select/lib/interface"; import type { CheckedStrategy } from "rc-tree-select/lib/utils/strategyUtil"; diff --git a/app/client/src/widgets/MultiSelectWidget/widget/index.tsx b/app/client/src/widgets/MultiSelectWidget/widget/index.tsx index 9fc9d56ab641..708a9f1b5536 100644 --- a/app/client/src/widgets/MultiSelectWidget/widget/index.tsx +++ b/app/client/src/widgets/MultiSelectWidget/widget/index.tsx @@ -17,7 +17,7 @@ import { Layers } from "constants/Layers"; import { WIDGET_TAGS, layoutConfigurations } from "constants/WidgetConstants"; import { FILL_WIDGET_MIN_WIDTH } from "constants/minWidthConstants"; import type { SetterConfig, Stylesheet } from "entities/AppTheming"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import { ResponsiveBehavior } from "layoutSystems/common/utils/constants"; import { buildDeprecationWidgetMessage } from "pages/Editor/utils"; import type { DraftValueType } from "rc-select/lib/Select"; diff --git a/app/client/src/widgets/MultiSelectWidgetV2/widget/index.tsx b/app/client/src/widgets/MultiSelectWidgetV2/widget/index.tsx index eb95972c1fa8..390121289a2e 100644 --- a/app/client/src/widgets/MultiSelectWidgetV2/widget/index.tsx +++ b/app/client/src/widgets/MultiSelectWidgetV2/widget/index.tsx @@ -4,7 +4,7 @@ import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import { Layers } from "constants/Layers"; import { ValidationTypes } from "constants/WidgetValidation"; import type { SetterConfig, Stylesheet } from "entities/AppTheming"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import equal from "fast-deep-equal/es6"; import { isArray, isFinite, isString, xorWith } from "lodash"; import type { DraftValueType, LabelInValueType } from "rc-select/lib/Select"; diff --git a/app/client/src/widgets/RadioGroupWidget/widget/index.tsx b/app/client/src/widgets/RadioGroupWidget/widget/index.tsx index ad86d9d80733..b857d5918181 100644 --- a/app/client/src/widgets/RadioGroupWidget/widget/index.tsx +++ b/app/client/src/widgets/RadioGroupWidget/widget/index.tsx @@ -8,7 +8,7 @@ import type { TextSize } from "constants/WidgetConstants"; import type { ValidationResponse } from "constants/WidgetValidation"; import { ValidationTypes } from "constants/WidgetValidation"; import type { SetterConfig, Stylesheet } from "entities/AppTheming"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; import { isAutoHeightEnabledForWidget, diff --git a/app/client/src/widgets/SelectWidget/widget/index.tsx b/app/client/src/widgets/SelectWidget/widget/index.tsx index f9afd4b9d8df..6d713f30e997 100644 --- a/app/client/src/widgets/SelectWidget/widget/index.tsx +++ b/app/client/src/widgets/SelectWidget/widget/index.tsx @@ -3,7 +3,7 @@ import { LabelPosition } from "components/constants"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import { ValidationTypes } from "constants/WidgetValidation"; import type { SetterConfig, Stylesheet } from "entities/AppTheming"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import equal from "fast-deep-equal/es6"; import { findIndex, isArray, isNil, isNumber, isString } from "lodash"; import React from "react"; diff --git a/app/client/src/widgets/SingleSelectTreeWidget/widget/index.tsx b/app/client/src/widgets/SingleSelectTreeWidget/widget/index.tsx index f35e5bc460aa..c65fb6c995c7 100644 --- a/app/client/src/widgets/SingleSelectTreeWidget/widget/index.tsx +++ b/app/client/src/widgets/SingleSelectTreeWidget/widget/index.tsx @@ -6,7 +6,7 @@ import type { TextSize } from "constants/WidgetConstants"; import type { ValidationResponse } from "constants/WidgetValidation"; import { ValidationTypes } from "constants/WidgetValidation"; import type { SetterConfig, Stylesheet } from "entities/AppTheming"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import { isArray } from "lodash"; import type { DefaultValueType } from "rc-tree-select/lib/interface"; import type { ReactNode } from "react"; diff --git a/app/client/src/widgets/SwitchGroupWidget/widget/index.tsx b/app/client/src/widgets/SwitchGroupWidget/widget/index.tsx index 47139b5beb66..0ff76dbed838 100644 --- a/app/client/src/widgets/SwitchGroupWidget/widget/index.tsx +++ b/app/client/src/widgets/SwitchGroupWidget/widget/index.tsx @@ -2,7 +2,7 @@ import React from "react"; import { Alignment } from "@blueprintjs/core"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import { ValidationTypes } from "constants/WidgetValidation"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import { isString, xor } from "lodash"; import type { DerivedPropertiesMap } from "WidgetProvider/factory/types"; import type { WidgetProps, WidgetState } from "widgets/BaseWidget"; diff --git a/app/client/src/widgets/TableWidget/widget/propertyConfig.ts b/app/client/src/widgets/TableWidget/widget/propertyConfig.ts index 1fbe63cc2a6a..d3c2a4571f88 100644 --- a/app/client/src/widgets/TableWidget/widget/propertyConfig.ts +++ b/app/client/src/widgets/TableWidget/widget/propertyConfig.ts @@ -1,7 +1,7 @@ import { get } from "lodash"; import type { TableWidgetProps } from "../constants"; import { ValidationTypes } from "constants/WidgetValidation"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; import type { PropertyPaneConfig } from "constants/PropertyControlConstants"; import { ButtonVariantTypes } from "components/constants"; diff --git a/app/client/src/widgets/TableWidgetV2/widget/index.tsx b/app/client/src/widgets/TableWidgetV2/widget/index.tsx index fa9574c09561..78a21afa3f49 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/index.tsx +++ b/app/client/src/widgets/TableWidgetV2/widget/index.tsx @@ -987,7 +987,11 @@ class TableWidgetV2 extends BaseWidget { //check if necessary we are batching now updates // Check if tableData is modifed - const isTableDataModified = this.props.tableData !== prevProps.tableData; + // const isTableDataModified = this.props.tableData !== prevProps.tableData; + const isTableDataModified = !equal( + this.props.tableData, + prevProps.tableData, + ); // If the user has changed the tableData OR // The binding has returned a new value diff --git a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Basic.ts b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Basic.ts index 1cbe7b3fa600..f9cdc4cf9de2 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Basic.ts +++ b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Basic.ts @@ -10,7 +10,7 @@ import { } from "../../propertyUtils"; import { IconNames } from "@blueprintjs/icons"; import { MenuItemsSource } from "widgets/MenuButtonWidget/constants"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import configureMenuItemsConfig from "./childPanels/configureMenuItemsConfig"; export default { diff --git a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/contentConfig.ts b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/contentConfig.ts index 278554f7f119..02de96fffcc8 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/contentConfig.ts +++ b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/contentConfig.ts @@ -4,7 +4,7 @@ import { createMessage, TABLE_WIDGET_TOTAL_RECORD_TOOLTIP, } from "ee/constants/messages"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; import type { TableWidgetProps } from "widgets/TableWidgetV2/constants"; import { diff --git a/app/client/src/widgets/wds/WDSCheckboxGroupWidget/config/propertyPaneConfig/contentConfig.ts b/app/client/src/widgets/wds/WDSCheckboxGroupWidget/config/propertyPaneConfig/contentConfig.ts index 9de9c11a83b7..e69ae66f466a 100644 --- a/app/client/src/widgets/wds/WDSCheckboxGroupWidget/config/propertyPaneConfig/contentConfig.ts +++ b/app/client/src/widgets/wds/WDSCheckboxGroupWidget/config/propertyPaneConfig/contentConfig.ts @@ -1,5 +1,5 @@ import { ValidationTypes } from "constants/WidgetValidation"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import { defaultSelectedValuesValidation } from "./validations"; import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; diff --git a/app/client/src/widgets/wds/WDSMenuButtonWidget/config/propertyPaneConfig/contentConfig.ts b/app/client/src/widgets/wds/WDSMenuButtonWidget/config/propertyPaneConfig/contentConfig.ts index dd5b5d1fbc6c..87242249f926 100644 --- a/app/client/src/widgets/wds/WDSMenuButtonWidget/config/propertyPaneConfig/contentConfig.ts +++ b/app/client/src/widgets/wds/WDSMenuButtonWidget/config/propertyPaneConfig/contentConfig.ts @@ -1,6 +1,6 @@ import { ValidationTypes } from "constants/WidgetValidation"; import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import { sourceDataArrayValidation } from "./validations"; import { configureMenuItemsConfig, menuItemsConfig } from "./childPanels"; import type { MenuButtonWidgetProps } from "../../widget/types"; diff --git a/app/client/src/widgets/wds/WDSMultiSelectWidget/config/propertyPaneConfig/contentConfig.tsx b/app/client/src/widgets/wds/WDSMultiSelectWidget/config/propertyPaneConfig/contentConfig.tsx index 43fdf69c8aff..e593933f6911 100644 --- a/app/client/src/widgets/wds/WDSMultiSelectWidget/config/propertyPaneConfig/contentConfig.tsx +++ b/app/client/src/widgets/wds/WDSMultiSelectWidget/config/propertyPaneConfig/contentConfig.tsx @@ -15,7 +15,7 @@ import { import { labelKeyValidation } from "./validations/labelKeyValidation"; import { Flex } from "@appsmith/ads"; import { SAMPLE_DATA } from "../../widget/constants"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; export const propertyPaneContentConfig = [ { diff --git a/app/client/src/widgets/wds/WDSRadioGroupWidget/config/propertyPaneConfig/contentConfig.ts b/app/client/src/widgets/wds/WDSRadioGroupWidget/config/propertyPaneConfig/contentConfig.ts index cb70b888d948..65c70b0c57ec 100644 --- a/app/client/src/widgets/wds/WDSRadioGroupWidget/config/propertyPaneConfig/contentConfig.ts +++ b/app/client/src/widgets/wds/WDSRadioGroupWidget/config/propertyPaneConfig/contentConfig.ts @@ -5,7 +5,7 @@ import { defaultOptionValidation, optionsCustomValidation, } from "./validations"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; export const propertyPaneContentConfig = [ { diff --git a/app/client/src/widgets/wds/WDSSelectWidget/config/propertyPaneConfig/contentConfig.tsx b/app/client/src/widgets/wds/WDSSelectWidget/config/propertyPaneConfig/contentConfig.tsx index 23cfe804d23a..f32f2593c01c 100644 --- a/app/client/src/widgets/wds/WDSSelectWidget/config/propertyPaneConfig/contentConfig.tsx +++ b/app/client/src/widgets/wds/WDSSelectWidget/config/propertyPaneConfig/contentConfig.tsx @@ -1,6 +1,6 @@ import React from "react"; import { ValidationTypes } from "constants/WidgetValidation"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; import type { WDSSelectWidgetProps } from "../../widget/types"; import { defaultOptionValueValidation } from "./validations"; diff --git a/app/client/src/widgets/wds/WDSSwitchGroupWidget/config/propertyPaneConfig/contentConfig.ts b/app/client/src/widgets/wds/WDSSwitchGroupWidget/config/propertyPaneConfig/contentConfig.ts index 485ffb2f72b8..eeb079b09680 100644 --- a/app/client/src/widgets/wds/WDSSwitchGroupWidget/config/propertyPaneConfig/contentConfig.ts +++ b/app/client/src/widgets/wds/WDSSwitchGroupWidget/config/propertyPaneConfig/contentConfig.ts @@ -1,5 +1,5 @@ import { ValidationTypes } from "constants/WidgetValidation"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; export const propertyPaneContentConfig = [ { diff --git a/app/client/src/widgets/wds/WDSTableWidget/config/propertyPaneConfig/contentConfig.ts b/app/client/src/widgets/wds/WDSTableWidget/config/propertyPaneConfig/contentConfig.ts index 67c5454addbf..2176f86d568c 100644 --- a/app/client/src/widgets/wds/WDSTableWidget/config/propertyPaneConfig/contentConfig.ts +++ b/app/client/src/widgets/wds/WDSTableWidget/config/propertyPaneConfig/contentConfig.ts @@ -1,6 +1,6 @@ import type { PropertyPaneConfig } from "constants/PropertyControlConstants"; import { ValidationTypes } from "constants/WidgetValidation"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; import type { TableWidgetProps } from "widgets/wds/WDSTableWidget/constants"; import { InlineEditingSaveOptions } from "widgets/wds/WDSTableWidget/constants"; diff --git a/app/client/src/workers/Evaluation/JSObject/JSObject.test.ts b/app/client/src/workers/Evaluation/JSObject/JSObject.test.ts index 960e790a4b17..436dd9dab711 100644 --- a/app/client/src/workers/Evaluation/JSObject/JSObject.test.ts +++ b/app/client/src/workers/Evaluation/JSObject/JSObject.test.ts @@ -9,6 +9,7 @@ import type { JSActionEntityConfig, } from "ee/entities/DataTree/types"; import DataTreeEvaluator from "workers/common/DataTreeEvaluator"; +import { ActionRunBehaviour } from "PluginActionEditor/types/PluginActionTypes"; describe("updateJSCollectionInUnEvalTree", function () { it("updates async value of jsAction", () => { @@ -95,6 +96,14 @@ describe("updateJSCollectionInUnEvalTree", function () { dependencyMap: { body: ["myFun1", "myFun2"], }, + dynamicTriggerPathList: [ + { + key: "myFun1", + }, + { + key: "myFun2", + }, + ], }, }; const JSObject1 = { @@ -170,10 +179,12 @@ describe("saveResolvedFunctionsAndJSUpdates", function () { myFun1: { arguments: [], confirmBeforeExecute: false, + runBehaviour: ActionRunBehaviour.MANUAL, }, myFun2: { arguments: [], confirmBeforeExecute: false, + runBehaviour: ActionRunBehaviour.MANUAL, }, }, dependencyMap: { @@ -214,6 +225,14 @@ describe("saveResolvedFunctionsAndJSUpdates", function () { name: "JSObject1", actionId: "64013546b956c26882acc587", actionNames: new Set(["myFun1", "myFun2"]), + dynamicTriggerPathList: [ + { + key: "myFun1", + }, + { + key: "myFun2", + }, + ], } as JSActionEntityConfig, }; const entityName = "JSObject1"; diff --git a/app/client/src/workers/Evaluation/JSObject/utils.ts b/app/client/src/workers/Evaluation/JSObject/utils.ts index 1277fe6f1a06..629604aa8817 100644 --- a/app/client/src/workers/Evaluation/JSObject/utils.ts +++ b/app/client/src/workers/Evaluation/JSObject/utils.ts @@ -7,7 +7,7 @@ import type { DataTree, DataTreeEntity, } from "entities/DataTree/dataTreeTypes"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import type { ParsedBody, ParsedJSSubAction } from "utils/JSPaneUtils"; import { unset, set, get, find } from "lodash"; import type { @@ -25,6 +25,7 @@ import { isJSAction, } from "ee/workers/Evaluation/evaluationUtils"; import JSObjectCollection from "./Collection"; +import { ActionRunBehaviour } from "PluginActionEditor/types/PluginActionTypes"; /** * here we add/remove the properties (variables and actions) which got added/removed from the JSObject parsedBody. @@ -94,6 +95,9 @@ export const updateJSCollectionInUnEvalTree = ( meta[action.name] = { arguments: action.arguments, confirmBeforeExecute: false, + runBehaviour: + jsEntityConfig.meta[action.name]?.runBehaviour || + ActionRunBehaviour.MANUAL, }; const data = get( diff --git a/app/client/src/workers/Evaluation/__tests__/evaluation.test.ts b/app/client/src/workers/Evaluation/__tests__/evaluation.test.ts index 505b0b8cb14b..bde5259b8786 100644 --- a/app/client/src/workers/Evaluation/__tests__/evaluation.test.ts +++ b/app/client/src/workers/Evaluation/__tests__/evaluation.test.ts @@ -5,10 +5,7 @@ import type { ActionEntity, } from "ee/entities/DataTree/types"; import type { UnEvalTree, ConfigTree } from "entities/DataTree/dataTreeTypes"; -import { - ENTITY_TYPE, - EvaluationSubstitutionType, -} from "ee/entities/DataTree/types"; +import { ENTITY_TYPE } from "ee/entities/DataTree/types"; import type { WidgetTypeConfigMap } from "WidgetProvider/factory/types"; import { RenderModes } from "constants/WidgetConstants"; import { PluginType } from "entities/Plugin"; @@ -19,6 +16,8 @@ import { generateDataTreeWidget } from "entities/DataTree/dataTreeWidget"; import { sortObjectWithArray } from "../../../utils/treeUtils"; import klona from "klona"; import { APP_MODE } from "entities/App"; +import { ActionRunBehaviour } from "PluginActionEditor/types/PluginActionTypes"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; const klonaFullSpy = jest.fn(); @@ -299,6 +298,8 @@ export const BASE_ACTION_CONFIG: ActionEntityConfig = { data: EvaluationSubstitutionType.TEMPLATE, }, dependencyMap: {}, + dynamicTriggerPathList: [], + runBehaviour: ActionRunBehaviour.MANUAL, }; const metaMock = jest.spyOn(WidgetFactory, "getWidgetMetaPropertiesMap"); @@ -632,6 +633,8 @@ describe("DataTreeEvaluator", () => { evalOrder, updatedConfigTree, unEvalUpdates, + [], + evaluator.evalTree, ); const dataTree = evaluator.evalTree; @@ -670,6 +673,8 @@ describe("DataTreeEvaluator", () => { evalOrder, updatedConfigTree, unEvalUpdates, + [], + evaluator.evalTree, ); const dataTree = evaluator.evalTree; @@ -716,6 +721,8 @@ describe("DataTreeEvaluator", () => { evalOrder, updatedConfigTree, unEvalUpdates, + [], + evaluator.evalTree, ); const dataTree = evaluator.evalTree; @@ -779,6 +786,8 @@ describe("DataTreeEvaluator", () => { evalOrder, updatedConfigTree, unEvalUpdates, + [], + evaluator.evalTree, ); const dataTree = evaluator.evalTree; const updatedDependencies = evaluator.dependencies; @@ -822,6 +831,8 @@ describe("DataTreeEvaluator", () => { evalOrder, updatedConfigTree, unEvalUpdates, + [], + evaluator.evalTree, ); const dataTree = evaluator.evalTree; const updatedDependencies = evaluator.dependencies; @@ -837,7 +848,6 @@ describe("DataTreeEvaluator", () => { }, ]); expect(sortObjectWithArray(updatedDependencies)).toStrictEqual({ - Api1: ["Api1.data"], ...initialdependencies, "Table1.tableData": ["Api1.data", "Text1.text"], "Text3.text": ["Text1.text"], @@ -887,6 +897,8 @@ describe("DataTreeEvaluator", () => { evalOrder, updatedConfigTree, unEvalUpdates, + [], + evaluator.evalTree, ); const dataTree = evaluator.evalTree; const updatedDependencies = evaluator.dependencies; @@ -904,7 +916,6 @@ describe("DataTreeEvaluator", () => { expect(dataTree).toHaveProperty("Text4.text", "Hey"); expect(sortObjectWithArray(updatedDependencies)).toStrictEqual({ - Api1: ["Api1.data"], ...initialdependencies, "Table1.selectedRow": [], "Table1.tableData": ["Api1.data", "Text1.text"], @@ -959,6 +970,8 @@ describe("DataTreeEvaluator", () => { evalOrder, updatedConfigTree1, unEvalUpdates, + [], + evaluator.evalTree, ); expect(evaluator.dependencies["Api2.config.body"]).toStrictEqual([ "Api2.config.pluginSpecifiedTemplates[0].value", @@ -993,6 +1006,8 @@ describe("DataTreeEvaluator", () => { newEvalOrder, updatedConfigTree2, unEvalUpdates2, + [], + evaluator.evalTree, ); const dataTree = evaluator.evalTree; @@ -1034,6 +1049,8 @@ describe("DataTreeEvaluator", () => { newEvalOrder2, updatedConfigTree3, unEvalUpdates3, + [], + evaluator.evalTree, ); const dataTree3 = evaluator.evalTree; @@ -1044,6 +1061,7 @@ describe("DataTreeEvaluator", () => { // @ts-expect-error: Types are not available expect(dataTree3.Api2.config.body).toBe("{ 'name': \"Test\" }"); }); + it("Prevents data mutation in eval cycle", () => { const { configEntity, unEvalEntity } = generateDataTreeWidget( { @@ -1075,12 +1093,15 @@ describe("DataTreeEvaluator", () => { evalOrder, updatedConfigTree, unEvalUpdates, + [], + evaluator.evalTree, ); const dataTree = evaluator.evalTree; expect(dataTree).toHaveProperty("TextX.text", 123); expect(dataTree).toHaveProperty("Text1.text", "Label"); }); + it("Checks the number of clone operations performed in update tree flow", () => { const { configEntity, unEvalEntity } = generateDataTreeWidget( { @@ -1113,6 +1134,8 @@ describe("DataTreeEvaluator", () => { evalOrder, updatedConfigTree, unEvalUpdates, + [], + evaluator.evalTree, ); // Hard check to not regress on the number of clone operations. Try to improve this number. // Not a good assertion because in one piece of code im cloning multiple times, however the value im cloning is very small. diff --git a/app/client/src/workers/Evaluation/__tests__/evaluationSubstitution.test.ts b/app/client/src/workers/Evaluation/__tests__/evaluationSubstitution.test.ts index c5b8b1f856b8..06d382c126c8 100644 --- a/app/client/src/workers/Evaluation/__tests__/evaluationSubstitution.test.ts +++ b/app/client/src/workers/Evaluation/__tests__/evaluationSubstitution.test.ts @@ -1,5 +1,5 @@ import { substituteDynamicBindingWithValues } from "workers/Evaluation/evaluationSubstitution"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; describe("substituteDynamicBindingWithValues", () => { describe("template substitution", () => { diff --git a/app/client/src/workers/Evaluation/evalTreeWithChanges.test.ts b/app/client/src/workers/Evaluation/evalTreeWithChanges.test.ts index dee35e321843..791e64071b27 100644 --- a/app/client/src/workers/Evaluation/evalTreeWithChanges.test.ts +++ b/app/client/src/workers/Evaluation/evalTreeWithChanges.test.ts @@ -130,6 +130,7 @@ describe("evaluateAndPushResponse", () => { }, [], [], + {}, ); // check if push response has been called expect(pushResponseToMainThreadMock).toHaveBeenCalled(); @@ -172,6 +173,7 @@ describe("getAffectedNodesInTheDataTree", () => { expect(result).toEqual(["Text1.text"]); }); }); + describe("evaluateAndGenerateResponse", () => { let evaluator: DataTreeEvaluator; const UPDATED_LABEL = "updated Label"; @@ -224,6 +226,7 @@ describe("evaluateAndGenerateResponse", () => { }, [], [], + {}, ); const parsedUpdates = getParsedUpdatesFromWebWorkerResp(webworkerResponse); @@ -234,6 +237,7 @@ describe("evaluateAndGenerateResponse", () => { errors: [], evalMetaUpdates: [], evaluationOrder: [], + executeReactiveActions: [], isCreateFirstTree: false, isNewWidgetAdded: false, jsUpdates: {}, @@ -257,6 +261,7 @@ describe("evaluateAndGenerateResponse", () => { }, [], [], + {}, ); const parsedUpdates = getParsedUpdatesFromWebWorkerResp(webworkerResponse); @@ -296,6 +301,7 @@ describe("evaluateAndGenerateResponse", () => { updateTreeResponse, [], [], + {}, ); expect(webworkerResponse.workerResponse.dependencies).toEqual({ @@ -336,6 +342,7 @@ describe("evaluateAndGenerateResponse", () => { updateTreeResponse, [], [], + {}, ); const parsedUpdates = getParsedUpdatesFromWebWorkerResp(webworkerResponse); @@ -369,6 +376,7 @@ describe("evaluateAndGenerateResponse", () => { updateTreeResponse, [], [], + {}, ); const parsedUpdates = @@ -412,6 +420,7 @@ describe("evaluateAndGenerateResponse", () => { updateTreeResponse, [], ["Text1.text"], + {}, ); const parsedUpdates = getParsedUpdatesFromWebWorkerResp(webworkerResponse); @@ -457,6 +466,7 @@ describe("evaluateAndGenerateResponse", () => { response, metaUpdates, [], + {}, ); expect(workerResponse.evalMetaUpdates).toEqual(metaUpdates); @@ -487,6 +497,7 @@ describe("evaluateAndGenerateResponse", () => { response, metaUpdates, [], + {}, ); // the function properties should be stripped out @@ -518,6 +529,7 @@ describe("evaluateAndGenerateResponse", () => { updateTreeResponse, [], [], + {}, ); const parsedUpdates = @@ -556,6 +568,7 @@ describe("evaluateAndGenerateResponse", () => { updateTreeResponse, [], [], + {}, ); const parsedUpdates = getParsedUpdatesFromWebWorkerResp(webworkerResponse); diff --git a/app/client/src/workers/Evaluation/evalTreeWithChanges.ts b/app/client/src/workers/Evaluation/evalTreeWithChanges.ts index 89b4c819d2cc..f0a0a562612b 100644 --- a/app/client/src/workers/Evaluation/evalTreeWithChanges.ts +++ b/app/client/src/workers/Evaluation/evalTreeWithChanges.ts @@ -14,10 +14,14 @@ import { uniqueOrderUpdatePaths, updateEvalProps, } from "./helpers"; -import type { DataTreeDiff } from "ee/workers/Evaluation/evaluationUtils"; +import { + isDataPath, + type DataTreeDiff, +} from "ee/workers/Evaluation/evaluationUtils"; import type DataTreeEvaluator from "workers/common/DataTreeEvaluator"; import type { Diff } from "deep-diff"; import type { DataTree } from "entities/DataTree/dataTreeTypes"; +import { klona as klonaJson } from "klona/json"; const getDefaultEvalResponse = (): EvalTreeResponseData => ({ updates: "[]", @@ -34,6 +38,7 @@ const getDefaultEvalResponse = (): EvalTreeResponseData => ({ isNewWidgetAdded: false, undefinedEvalValuesMap: {}, jsVarsCreatedEvent: [], + executeReactiveActions: [], }); export function evalTreeWithChanges( @@ -45,11 +50,31 @@ export function evalTreeWithChanges( const { data } = request; const { metaUpdates = [], updatedValuePaths } = data; - const pathsToSkipFromEval = updatedValuePaths.map((path) => path.join(".")); + const unEvalTree = dataTreeEvaluator?.getEvalTree(); + const filteredUpdatedValuePaths = updatedValuePaths.filter((pathArr) => { + if (pathArr[pathArr.length - 1] !== "data") return true; + + const fullPath = pathArr.join("."); + + const entityName = pathArr[0]; + const entity = unEvalTree?.[entityName]; + + if (entity && isDataPath(entity, fullPath)) { + return false; // filter out + } + + return true; // keep for other entity types + }); + + const pathsToSkipFromEval = filteredUpdatedValuePaths.map((path) => + path.join("."), + ); let setupUpdateTreeResponse = {} as UpdateTreeResponse; + let oldEvalTree: DataTree = {}; if (dataTreeEvaluator) { + oldEvalTree = klonaJson(dataTreeEvaluator.getEvalTree()); setupUpdateTreeResponse = dataTreeEvaluator.setupUpdateTreeWithDifferences( updatedValuePaths, pathsToSkipFromEval, @@ -61,6 +86,7 @@ export function evalTreeWithChanges( setupUpdateTreeResponse, metaUpdates, pathsToSkipFromEval, + oldEvalTree, ); } @@ -81,12 +107,14 @@ export const evaluateAndPushResponse = ( setupUpdateTreeResponse: UpdateTreeResponse, metaUpdates: EvalMetaUpdates, additionalPathsAddedAsUpdates: string[], + oldEvalTree: DataTree, ) => { const response = evaluateAndGenerateResponse( dataTreeEvaluator, setupUpdateTreeResponse, metaUpdates, additionalPathsAddedAsUpdates, + oldEvalTree, ); return pushResponseToMainThread(response); @@ -97,6 +125,7 @@ export const evaluateAndGenerateResponse = ( setupUpdateTreeResponse: UpdateTreeResponse, metaUpdates: EvalMetaUpdates, additionalPathsAddedAsUpdates: string[], + oldEvalTree: DataTree, ): UpdateDataTreeMessageData => { // generate default response first and later add updates to it const defaultResponse = getDefaultEvalResponse(); @@ -128,6 +157,8 @@ export const evaluateAndGenerateResponse = ( evalOrder, dataTreeEvaluator.oldConfigTree, unEvalUpdates, + [], + oldEvalTree, ); const dataTree = updateEvalProps(dataTreeEvaluator) || {}; @@ -139,6 +170,8 @@ export const evaluateAndGenerateResponse = ( defaultResponse.staleMetaIds = updateResponse.staleMetaIds; defaultResponse.dependencies = dataTreeEvaluator.inverseDependencies; + defaultResponse.executeReactiveActions = + updateResponse.executeReactiveActions; // when additional paths are required to be added as updates, we extract the updates from the data tree using these paths. const additionalUpdates = getNewDataTreeUpdates( diff --git a/app/client/src/workers/Evaluation/evaluationSubstitution.ts b/app/client/src/workers/Evaluation/evaluationSubstitution.ts index 10dc81e03ee9..25cf33355c96 100644 --- a/app/client/src/workers/Evaluation/evaluationSubstitution.ts +++ b/app/client/src/workers/Evaluation/evaluationSubstitution.ts @@ -1,6 +1,6 @@ import { getType, Types } from "utils/TypeHelpers"; import _ from "lodash"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; import { isDynamicValue } from "utils/DynamicBindingUtils"; import { QUOTED_BINDING_REGEX } from "constants/BindingsConstants"; diff --git a/app/client/src/workers/Evaluation/handlers/evalTree.ts b/app/client/src/workers/Evaluation/handlers/evalTree.ts index cf37832c95a6..1d12dde5887d 100644 --- a/app/client/src/workers/Evaluation/handlers/evalTree.ts +++ b/app/client/src/workers/Evaluation/handlers/evalTree.ts @@ -33,6 +33,7 @@ import type { CanvasWidgetsReduxState } from "ee/reducers/entityReducers/canvasW import type { MetaWidgetsReduxState } from "reducers/entityReducers/metaWidgetsReducer"; import type { Attributes } from "instrumentation/types"; import { updateActionsToEvalTree } from "./updateActionData"; +import { klona as klonaJSON } from "klona/json"; // TODO: Fix this the next time the file is edited // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -67,6 +68,7 @@ export async function evalTree( let staleMetaIds: string[] = []; let removedPaths: Array<{ entityId: string; fullpath: string }> = []; let isNewWidgetAdded = false; + let executeReactiveActions: string[] = []; const { actionDataPayloadConsolidated, @@ -135,6 +137,7 @@ export async function evalTree( dataTree = updateEvalProps(dataTreeEvaluator) || {}; staleMetaIds = dataTreeResponse.staleMetaIds; + executeReactiveActions = dataTreeResponse.executeReactiveActions; isNewTree = true; } else if (dataTreeEvaluator.hasCyclicalDependency || forceEvaluation) { if (dataTreeEvaluator && !isEmpty(allActionValidationConfig)) { @@ -187,9 +190,11 @@ export async function evalTree( dataTree = updateEvalProps(dataTreeEvaluator) || {}; staleMetaIds = dataTreeResponse.staleMetaIds; + executeReactiveActions = dataTreeResponse.executeReactiveActions; isNewTree = true; } else { const tree = dataTreeEvaluator.getEvalTree(); + const oldDataTree = klonaJSON(tree); // during update cycles update actions to the dataTree directly // this is useful in cases where we have debounced updateActionData and a regular evaluation @@ -238,6 +243,7 @@ export async function evalTree( configTree, unEvalUpdates, Object.keys(metaWidgets), + oldDataTree, ), ); @@ -247,6 +253,7 @@ export async function evalTree( JSON.stringify(updateResponse.evalMetaUpdates), ); staleMetaIds = updateResponse.staleMetaIds; + executeReactiveActions = updateResponse.executeReactiveActions; isNewTree = false; } @@ -356,6 +363,7 @@ export async function evalTree( isNewWidgetAdded, undefinedEvalValuesMap: dataTreeEvaluator?.undefinedEvalValuesMap || {}, jsVarsCreatedEvent, + executeReactiveActions, }; webworkerTelemetry["transferDataToMainThread"] = newWebWorkerSpanData( diff --git a/app/client/src/workers/Evaluation/handlers/evalTrigger.ts b/app/client/src/workers/Evaluation/handlers/evalTrigger.ts index 992730da7b55..4da86703b395 100644 --- a/app/client/src/workers/Evaluation/handlers/evalTrigger.ts +++ b/app/client/src/workers/Evaluation/handlers/evalTrigger.ts @@ -2,6 +2,7 @@ import { dataTreeEvaluator } from "./evalTree"; import type { EvalWorkerASyncRequest } from "../types"; import ExecutionMetaData from "../fns/utils/ExecutionMetaData"; import { evaluateAndPushResponse } from "../evalTreeWithChanges"; +import { klona as klonaJson } from "klona/json"; export default async function (request: EvalWorkerASyncRequest) { const { data } = request; @@ -20,6 +21,8 @@ export default async function (request: EvalWorkerASyncRequest) { ExecutionMetaData.setExecutionMetaData({ triggerMeta, eventType }); + const oldEvalTree = klonaJson(dataTreeEvaluator.getEvalTree()); + if (!triggerMeta.onPageLoad) { const { evalOrder, unEvalUpdates } = dataTreeEvaluator.setupUpdateTree( unEvalTree.unEvalTree, @@ -34,6 +37,7 @@ export default async function (request: EvalWorkerASyncRequest) { { evalOrder, unEvalUpdates, jsUpdates: {} }, [], [], + oldEvalTree, ); } diff --git a/app/client/src/workers/Evaluation/types.ts b/app/client/src/workers/Evaluation/types.ts index 9bff8b878174..9e64e60c45bf 100644 --- a/app/client/src/workers/Evaluation/types.ts +++ b/app/client/src/workers/Evaluation/types.ts @@ -71,6 +71,7 @@ export interface EvalTreeResponseData { jsVarsCreatedEvent?: { path: string; type: string }[]; webworkerTelemetry?: Record; updates: string; + executeReactiveActions: string[]; } export interface UpdateTreeResponse { diff --git a/app/client/src/workers/common/DataTreeEvaluator/dataTreeEvaluator.test.ts b/app/client/src/workers/common/DataTreeEvaluator/dataTreeEvaluator.test.ts index aeb575f07196..e0ad57986d3a 100644 --- a/app/client/src/workers/common/DataTreeEvaluator/dataTreeEvaluator.test.ts +++ b/app/client/src/workers/common/DataTreeEvaluator/dataTreeEvaluator.test.ts @@ -16,10 +16,7 @@ import { replaceThisDotParams } from "./utils"; import { isDataField } from "./utils"; import widgets from "widgets"; import type { WidgetConfiguration } from "WidgetProvider/types"; -import { - EvaluationSubstitutionType, - type WidgetEntity, -} from "ee/entities/DataTree/types"; +import { type WidgetEntity } from "ee/entities/DataTree/types"; import { EXECUTION_PARAM_KEY, EXECUTION_PARAM_REFERENCE_REGEX, @@ -27,6 +24,7 @@ import { import generateOverrideContext from "ee/workers/Evaluation/generateOverrideContext"; import { klona } from "klona"; import { APP_MODE } from "entities/App"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; const widgetConfigMap: Record< string, @@ -306,6 +304,8 @@ describe("DataTreeEvaluator", () => { evalOrder, configTree as unknown as ConfigTree, unEvalUpdates, + [], + dataTreeEvaluator.evalTree, ); expect(dataTreeEvaluator.dependencies).toStrictEqual({ @@ -477,16 +477,16 @@ describe("DataTreeEvaluator", () => { evalOrder, arrayAccessorCyclicDependencyConfig.apiSuccessConfigTree, unEvalUpdates, + [], + dataTreeEvaluator.evalTree, + ); + expect(dataTreeEvaluator.dependencies["Api1"]).toStrictEqual( + undefined, + ); + expect(dataTreeEvaluator.dependencies["Api1.data"]).toStrictEqual([]); + expect(dataTreeEvaluator.dependencies["Api1.data[2]"]).toStrictEqual( + undefined, ); - expect(dataTreeEvaluator.dependencies["Api1"]).toStrictEqual([ - "Api1.data", - ]); - expect(dataTreeEvaluator.dependencies["Api1.data"]).toStrictEqual([ - "Api1.data[2]", - ]); - expect(dataTreeEvaluator.dependencies["Api1.data[2]"]).toStrictEqual([ - "Api1.data[2].id", - ]); expect(dataTreeEvaluator.dependencies["Text1.text"]).toStrictEqual([ "Api1.data[2].id", ]); @@ -502,11 +502,13 @@ describe("DataTreeEvaluator", () => { order, arrayAccessorCyclicDependencyConfig.apiFailureConfigTree, unEvalUpdates2, + [], + dataTreeEvaluator.evalTree, ); - expect(dataTreeEvaluator.dependencies["Api1"]).toStrictEqual([ - "Api1.data", - ]); + expect(dataTreeEvaluator.dependencies["Api1"]).toStrictEqual( + undefined, + ); expect(dataTreeEvaluator.dependencies["Api1.data"]).toStrictEqual([]); expect(dataTreeEvaluator.dependencies["Api1.data[2]"]).toStrictEqual( undefined, @@ -530,6 +532,8 @@ describe("DataTreeEvaluator", () => { order1, arrayAccessorCyclicDependencyConfig.apiSuccessConfigTree, unEvalUpdates, + [], + dataTreeEvaluator.evalTree, ); // success: response -> [{...}, {...}] @@ -543,11 +547,11 @@ describe("DataTreeEvaluator", () => { order2, arrayAccessorCyclicDependencyConfig.apiSuccessConfigTree2, unEvalUpdates2, + [], + dataTreeEvaluator.evalTree, ); - expect(dataTreeEvaluator.dependencies["Api1"]).toStrictEqual([ - "Api1.data", - ]); + expect(dataTreeEvaluator.dependencies["Api1"]).toStrictEqual(undefined); expect(dataTreeEvaluator.dependencies["Api1.data"]).toStrictEqual([]); expect(dataTreeEvaluator.dependencies["Api1.data[2]"]).toStrictEqual( undefined, @@ -572,19 +576,19 @@ describe("DataTreeEvaluator", () => { order, nestedArrayAccessorCyclicDependencyConfig.apiSuccessConfigTree, unEvalUpdates, + [], + dataTreeEvaluator.evalTree, + ); + expect(dataTreeEvaluator.dependencies["Api1"]).toStrictEqual( + undefined, + ); + expect(dataTreeEvaluator.dependencies["Api1.data"]).toStrictEqual([]); + expect(dataTreeEvaluator.dependencies["Api1.data[2]"]).toStrictEqual( + undefined, ); - expect(dataTreeEvaluator.dependencies["Api1"]).toStrictEqual([ - "Api1.data", - ]); - expect(dataTreeEvaluator.dependencies["Api1.data"]).toStrictEqual([ - "Api1.data[2]", - ]); - expect(dataTreeEvaluator.dependencies["Api1.data[2]"]).toStrictEqual([ - "Api1.data[2][2]", - ]); expect( dataTreeEvaluator.dependencies["Api1.data[2][2]"], - ).toStrictEqual(["Api1.data[2][2].id"]); + ).toStrictEqual(undefined); expect(dataTreeEvaluator.dependencies["Text1.text"]).toStrictEqual([ "Api1.data[2][2].id", ]); @@ -600,10 +604,12 @@ describe("DataTreeEvaluator", () => { order1, nestedArrayAccessorCyclicDependencyConfig.apiFailureConfigTree, unEvalUpdates2, + [], + dataTreeEvaluator.evalTree, + ); + expect(dataTreeEvaluator.dependencies["Api1"]).toStrictEqual( + undefined, ); - expect(dataTreeEvaluator.dependencies["Api1"]).toStrictEqual([ - "Api1.data", - ]); expect(dataTreeEvaluator.dependencies["Api1.data"]).toStrictEqual([]); expect(dataTreeEvaluator.dependencies["Api1.data[2]"]).toStrictEqual( undefined, @@ -630,6 +636,8 @@ describe("DataTreeEvaluator", () => { order, nestedArrayAccessorCyclicDependencyConfig.apiSuccessConfigTree, unEvalUpdates, + [], + dataTreeEvaluator.evalTree, ); // success: response -> [ [{...}, {...}, {...}], [{...}, {...}, {...}] ] @@ -643,11 +651,11 @@ describe("DataTreeEvaluator", () => { order1, nestedArrayAccessorCyclicDependencyConfig.apiSuccessConfigTree2, unEvalUpdates2, + [], + dataTreeEvaluator.evalTree, ); - expect(dataTreeEvaluator.dependencies["Api1"]).toStrictEqual([ - "Api1.data", - ]); + expect(dataTreeEvaluator.dependencies["Api1"]).toStrictEqual(undefined); expect(dataTreeEvaluator.dependencies["Api1.data"]).toStrictEqual([]); expect(dataTreeEvaluator.dependencies["Api1.data[2]"]).toStrictEqual( undefined, @@ -671,6 +679,8 @@ describe("DataTreeEvaluator", () => { order, nestedArrayAccessorCyclicDependencyConfig.apiSuccessConfigTree, unEvalUpdates, + [], + dataTreeEvaluator.evalTree, ); // success: response -> [ [{...}, {...}, {...}], [{...}, {...}, {...}], [] ] @@ -684,15 +694,13 @@ describe("DataTreeEvaluator", () => { order1, nestedArrayAccessorCyclicDependencyConfig.apiSuccessConfigTree3, unEvalUpdates2, + [], + dataTreeEvaluator.evalTree, ); - expect(dataTreeEvaluator.dependencies["Api1"]).toStrictEqual([ - "Api1.data", - ]); - expect(dataTreeEvaluator.dependencies["Api1.data"]).toStrictEqual([ - "Api1.data[2]", - ]); + expect(dataTreeEvaluator.dependencies["Api1"]).toStrictEqual(undefined); + expect(dataTreeEvaluator.dependencies["Api1.data"]).toStrictEqual([]); expect(dataTreeEvaluator.dependencies["Api1.data[2]"]).toStrictEqual( - [], + undefined, ); expect(dataTreeEvaluator.dependencies["Api1.data[2][2]"]).toStrictEqual( undefined, @@ -788,10 +796,12 @@ describe("isDataField", () => { myFun2: { arguments: [], confirmBeforeExecute: false, + runBehaviour: "MANUAL", }, myFun1: { arguments: [], confirmBeforeExecute: false, + runBehaviour: "MANUAL", }, }, name: "JSObject1", @@ -833,19 +843,27 @@ describe("isDataField", () => { body: ["myFun2", "myFun1"], }, actionNames: new Set(["myFun1", "myFun2"]), + dynamicTriggerPathList: [ + { + key: "myFun1", + }, + { + key: "myFun2", + }, + ], }, JSObject2: { actionId: "644242aeadc0936a9b0e71cc", meta: { myFun2: { arguments: [], - confirmBeforeExecute: false, + runBehaviour: "MANUAL", }, myFun1: { arguments: [], - confirmBeforeExecute: false, + runBehaviour: "MANUAL", }, }, name: "JSObject2", @@ -887,6 +905,14 @@ describe("isDataField", () => { body: ["myFun2", "myFun1"], }, actionNames: new Set(["myFun1", "myFun2"]), + dynamicTriggerPathList: [ + { + key: "myFun1", + }, + { + key: "myFun2", + }, + ], }, MainContainer: { defaultProps: {}, diff --git a/app/client/src/workers/common/DataTreeEvaluator/index.ts b/app/client/src/workers/common/DataTreeEvaluator/index.ts index 27e7cdae1985..820d5dab3b9c 100644 --- a/app/client/src/workers/common/DataTreeEvaluator/index.ts +++ b/app/client/src/workers/common/DataTreeEvaluator/index.ts @@ -32,15 +32,13 @@ import type { ConfigTree, UnEvalTree, } from "entities/DataTree/dataTreeTypes"; -import { - EvaluationSubstitutionType, - ENTITY_TYPE, -} from "ee/entities/DataTree/types"; +import { ENTITY_TYPE } from "ee/entities/DataTree/types"; import type { DataTreeDiff } from "ee/workers/Evaluation/evaluationUtils"; import { convertMicroDiffToDeepDiff, getAllPathsBasedOnDiffPaths, - isPropertyAnEntityAction, + isDataPath, + isJSModuleInstance, } from "ee/workers/Evaluation/evaluationUtils"; import { @@ -66,6 +64,7 @@ import { isAPathDynamicBindingPath, isAnyJSAction, isNotEntity, + getExternalChangedDependencies, } from "ee/workers/Evaluation/evaluationUtils"; import { difference, @@ -156,6 +155,7 @@ import { WorkerEnv } from "workers/Evaluation/handlers/workerEnv"; import type { WebworkerSpanData, Attributes } from "instrumentation/types"; import type { AffectedJSObjects } from "actions/EvaluationReduxActionTypes"; import type { UpdateActionProps } from "workers/Evaluation/handlers/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; type SortedDependencies = Array; export interface EvalProps { @@ -342,7 +342,11 @@ export default class DataTreeEvaluator { const sortDependenciesStartTime = performance.now(); - this.sortedDependencies = this.sortDependencies(this.dependencyMap); + this.sortedDependencies = this.sortDependencies( + this.dependencyMap, + [], + configTree, + ); const sortDependenciesEndTime = performance.now(); const secondCloneStartTime = performance.now(); @@ -465,6 +469,7 @@ export default class DataTreeEvaluator { evalTree: DataTree; evalMetaUpdates: EvalMetaUpdates; staleMetaIds: string[]; + executeReactiveActions: string[]; } { const evaluationStartTime = performance.now(); @@ -472,11 +477,17 @@ export default class DataTreeEvaluator { const unEvalTreeClone = klonaJSON(this.oldUnEvalTree); // Evaluate - const { evalMetaUpdates, evaluatedTree, staleMetaIds } = this.evaluateTree( + const { + evalMetaUpdates, + evaluatedTree, + executeReactiveActions, + staleMetaIds, + } = this.evaluateTree( unEvalTreeClone, evaluationOrder, undefined, this.oldConfigTree, + {}, ); /** @@ -509,6 +520,7 @@ export default class DataTreeEvaluator { evalTree: this.getEvalTree(), evalMetaUpdates, staleMetaIds, + executeReactiveActions, }; } @@ -762,13 +774,27 @@ export default class DataTreeEvaluator { undefined, webworkerTelemetry, () => { - const pathsToSkipFromEval = + let pathsToSkipFromEval = actionDataPayloadConsolidated ?.map(({ dataPath, entityName }) => { return [entityName, dataPath]; }) .map((path: string[]) => path.join(".")) || []; + // Remove all .data paths + pathsToSkipFromEval = pathsToSkipFromEval.filter((path) => { + if (!path.endsWith(".data")) return true; + + const { entityName } = getEntityNameAndPropertyPath(path); + const entity = unEvalTree[entityName]; + + if (entity && isDataPath(entity, path)) { + return false; // filter out + } + + return true; // keep for other entity types + }); + return this.setupTree(updatedUnEvalTreeJSObjects, updatedValuePaths, { dependenciesOfRemovedPaths, removedPaths, @@ -931,10 +957,12 @@ export default class DataTreeEvaluator { configTree: ConfigTree, unevalUpdates: DataTreeDiff[], metaWidgetIds: string[] = [], + oldEvalTree: DataTree, ): { evalMetaUpdates: EvalMetaUpdates; staleMetaIds: string[]; contextTree: DataTree; + executeReactiveActions: string[]; } { const evaluationStartTime = performance.now(); @@ -942,6 +970,7 @@ export default class DataTreeEvaluator { contextTree, evalMetaUpdates, evaluatedTree: newEvalTree, + executeReactiveActions, staleMetaIds, } = this.evaluateTree( this.evalTree, @@ -952,6 +981,7 @@ export default class DataTreeEvaluator { metaWidgets: metaWidgetIds, }, configTree, + oldEvalTree, ); const evaluationEndTime = performance.now(); @@ -970,6 +1000,7 @@ export default class DataTreeEvaluator { evalMetaUpdates, staleMetaIds, contextTree, + executeReactiveActions, }; } @@ -1082,7 +1113,7 @@ export default class DataTreeEvaluator { evaluateTree( unEvalTree: DataTree, - evaluationOrder: Array, + evaluationOrder: string[], options: { isFirstTree: boolean; unevalUpdates: DataTreeDiff[]; @@ -1093,17 +1124,20 @@ export default class DataTreeEvaluator { metaWidgets: [], }, oldConfigTree: ConfigTree, + oldEvalTree: DataTree, ): { evaluatedTree: DataTree; evalMetaUpdates: EvalMetaUpdates; staleMetaIds: string[]; contextTree: DataTree; + executeReactiveActions: string[]; } { resetWorkerGlobalScope(); const safeTree = klonaJSON(unEvalTree); const dataStore = DataStore.getDataStore(); const dataStoreClone = klonaJSON(dataStore); + const executeReactiveActions: string[] = []; updateTreeWithData(safeTree, dataStoreClone); updateTreeWithData(unEvalTree, dataStore); @@ -1116,6 +1150,8 @@ export default class DataTreeEvaluator { */ const contextTree = unEvalTree; + const valuechanged: Record = {}; + errorModifier.updateAsyncFunctions( contextTree, this.getConfigTree(), @@ -1141,6 +1177,8 @@ export default class DataTreeEvaluator { }); } + const dependencies = this.dependencyMap.dependencies; + try { for (const fullPropertyPath of evaluationOrder) { const { entityName, propertyPath } = @@ -1150,10 +1188,11 @@ export default class DataTreeEvaluator { if (!isWidgetActionOrJsObject(entity)) continue; - // Skip evaluations for actions in JSObjects - if (isPropertyAnEntityAction(entity, propertyPath, entityConfig)) { + if (isDataPath(entity, fullPropertyPath)) { + valuechanged[fullPropertyPath] = true; continue; } + // Skip evaluations for actions in JSObjects // TODO: Fix this the next time the file is edited // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -1165,12 +1204,41 @@ export default class DataTreeEvaluator { propertyPath, ); - const isATriggerPath = - isWidget(entity) && - isPathDynamicTrigger( - entityConfig as WidgetEntityConfig, - propertyPath, - ); + const isATriggerPath = isPathDynamicTrigger(entityConfig, propertyPath); + + if (isATriggerPath && !isFirstTree) { + if (isJSAction(entity)) { + //check if any direct dependency is changed + const dependenciesForPath = dependencies[fullPropertyPath]; + + if (dependenciesForPath) { + // If this is a .data property and is a direct dependency, always push + const hasEntityDirectDependencyChanged = Object.keys( + valuechanged, + ).some((path) => dependenciesForPath.includes(path)); + + if (hasEntityDirectDependencyChanged) { + executeReactiveActions.push(fullPropertyPath); + } + } + // executeReactiveActions.push(fullPropertyPath); + } else { + if (isAction(entity) || isJSModuleInstance(entity)) { + const hasExternalDependencyChanged = + getExternalChangedDependencies( + fullPropertyPath, + dependencies, + valuechanged, + entityName, + ); + + if (hasExternalDependencyChanged) { + executeReactiveActions.push(fullPropertyPath); + } + } + } + } + let evalPropertyValue; const requiresEval = isADynamicBindingPath && @@ -1233,6 +1301,9 @@ export default class DataTreeEvaluator { if (!propertyPath) continue; + // Get old value from oldEvalTree for comparison + const oldValue = get(oldEvalTree, fullPropertyPath); + switch (entityType) { case ENTITY_TYPE.WIDGET: { if (isATriggerPath) continue; @@ -1271,6 +1342,12 @@ export default class DataTreeEvaluator { ); set(contextTree, fullPropertyPath, parsedValue); + + // Only set in safeTree if value has changed from both oldEvalTree and new evaluated value + if (parsedValue !== oldValue) { + valuechanged[fullPropertyPath] = true; + } + set(safeTree, fullPropertyPath, klonaJSON(parsedValue)); if ( @@ -1324,6 +1401,12 @@ export default class DataTreeEvaluator { if (!requiresEval) continue; set(contextTree, fullPropertyPath, evalPropertyValue); + + // Only set in safeTree if value has changed from both oldEvalTree and new evaluated value + if (evalPropertyValue !== oldValue) { + valuechanged[fullPropertyPath] = true; + } + set(safeTree, fullPropertyPath, klonaJSON(evalPropertyValue)); if ( @@ -1373,33 +1456,47 @@ export default class DataTreeEvaluator { * Their evaluated values need to be reset only when the variable is modified by the user. * When uneval value of a js variable hasn't changed, it means that the previously evaluated values are in both trees already */ if (!skipVariableValueAssignment) { - const valueForSafeTree = klonaJSON(evalValue); - - set(contextTree, fullPropertyPath, evalValue); - set(safeTree, fullPropertyPath, valueForSafeTree); - - if ( - WorkerEnv.flags.release_evaluation_scope_cache && - evalContextCache - ) { - set( - evalContextCache, + // Only set in safeTree if value has changed from both oldEvalTree and new evaluated value + if (evalValue !== oldValue) { + const valueForSafeTree = klonaJSON(evalValue); + + valuechanged[fullPropertyPath] = true; + + set(contextTree, fullPropertyPath, evalValue); + set(safeTree, fullPropertyPath, valueForSafeTree); + + if ( + WorkerEnv.flags.release_evaluation_scope_cache && + evalContextCache + ) { + set( + evalContextCache, + fullPropertyPath, + klonaJSON(evalPropertyValue), + ); + } + + JSObjectCollection.setVariableValue( + evalValue, fullPropertyPath, - klonaJSON(evalPropertyValue), ); + JSObjectCollection.setPrevUnEvalState({ + fullPath: fullPropertyPath, + unEvalValue: unEvalPropertyValue, + }); } - - JSObjectCollection.setVariableValue(evalValue, fullPropertyPath); - JSObjectCollection.setPrevUnEvalState({ - fullPath: fullPropertyPath, - unEvalValue: unEvalPropertyValue, - }); } break; } default: set(contextTree, fullPropertyPath, evalPropertyValue); + + // Only set in safeTree if value has changed from both oldEvalTree and new evaluated value + if (evalPropertyValue !== oldValue) { + valuechanged[fullPropertyPath] = true; + } + set(safeTree, fullPropertyPath, klonaJSON(evalPropertyValue)); if ( @@ -1421,6 +1518,14 @@ export default class DataTreeEvaluator { message: (error as Error).message, stack: (error as Error).stack, }); + + return { + evaluatedTree: safeTree, + contextTree: contextTree, + evalMetaUpdates, + staleMetaIds, + executeReactiveActions, + }; } finally { // Restore the dataStore since it was a part of contextTree and prone to mutation. DataStore.replaceDataStore(dataStoreClone); @@ -1438,6 +1543,7 @@ export default class DataTreeEvaluator { contextTree: contextTree, evalMetaUpdates, staleMetaIds, + executeReactiveActions, }; } } @@ -1466,8 +1572,12 @@ export default class DataTreeEvaluator { sortDependencies( dependencyMap: DependencyMap, diffs?: DataTreeDiff[], + configTree?: ConfigTree, ): Array { - const result = DependencyMapUtils.sortDependencies(dependencyMap); + const result = DependencyMapUtils.sortDependencies( + dependencyMap, + configTree, + ); if (result.success) { return result.sortedDependencies; @@ -1499,7 +1609,9 @@ export default class DataTreeEvaluator { this.errors.push({ type: EvalErrorTypes.CYCLICAL_DEPENDENCY_ERROR, - message: "Cyclic dependency found while evaluating.", + message: (result.error as Error).message + ? (result.error as Error).message + : "Cyclic dependency found while evaluating.", context: { node, entityType, diff --git a/app/client/src/workers/common/DataTreeEvaluator/mockData/ArrayAccessorTree.ts b/app/client/src/workers/common/DataTreeEvaluator/mockData/ArrayAccessorTree.ts index 344763cdc058..9bc9f59807f4 100644 --- a/app/client/src/workers/common/DataTreeEvaluator/mockData/ArrayAccessorTree.ts +++ b/app/client/src/workers/common/DataTreeEvaluator/mockData/ArrayAccessorTree.ts @@ -7,10 +7,8 @@ import type { ActionEntity, } from "ee/entities/DataTree/types"; import type { DataTree } from "entities/DataTree/dataTreeTypes"; -import { - EvaluationSubstitutionType, - ENTITY_TYPE, -} from "ee/entities/DataTree/types"; +import { ENTITY_TYPE } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; export const arrayAccessorCyclicDependency: Record = { initUnEvalTree: { diff --git a/app/client/src/workers/common/DataTreeEvaluator/mockData/NestedArrayAccessorTree.ts b/app/client/src/workers/common/DataTreeEvaluator/mockData/NestedArrayAccessorTree.ts index 46d77cc4f21c..4d54ac11ab76 100644 --- a/app/client/src/workers/common/DataTreeEvaluator/mockData/NestedArrayAccessorTree.ts +++ b/app/client/src/workers/common/DataTreeEvaluator/mockData/NestedArrayAccessorTree.ts @@ -7,10 +7,8 @@ import type { ActionEntity, } from "ee/entities/DataTree/types"; import type { DataTree } from "entities/DataTree/dataTreeTypes"; -import { - EvaluationSubstitutionType, - ENTITY_TYPE, -} from "ee/entities/DataTree/types"; +import { ENTITY_TYPE } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; export const nestedArrayAccessorCyclicDependency: Record = { initUnEvalTree: { diff --git a/app/client/src/workers/common/DataTreeEvaluator/mockData/mockConfigTree.ts b/app/client/src/workers/common/DataTreeEvaluator/mockData/mockConfigTree.ts index 460917c15e3a..b5de22e67145 100644 --- a/app/client/src/workers/common/DataTreeEvaluator/mockData/mockConfigTree.ts +++ b/app/client/src/workers/common/DataTreeEvaluator/mockData/mockConfigTree.ts @@ -1,5 +1,5 @@ import { ENTITY_TYPE } from "ee/entities/DataTree/types"; -import { EvaluationSubstitutionType } from "ee/entities/DataTree/types"; +import { EvaluationSubstitutionType } from "constants/EvaluationConstants"; export const configTree = { MainContainer: { diff --git a/app/client/src/workers/common/DependencyMap/index.ts b/app/client/src/workers/common/DependencyMap/index.ts index 4674d5dc2fdf..bcd5a1847499 100644 --- a/app/client/src/workers/common/DependencyMap/index.ts +++ b/app/client/src/workers/common/DependencyMap/index.ts @@ -328,6 +328,30 @@ export const updateDependencyMap = ({ const entityConfig = configTree[entityName]; const fullPropertyPath = dataTreeDiff.payload.propertyPath; + const entityDependencyMap = getEntityDependencies( + entity, + configTree[entityName], + allKeys, + ); + + if (!isEmpty(entityDependencyMap)) { + // The entity might already have some dependencies, + // so we just want to update those + Object.entries(entityDependencyMap).forEach( + ([path, pathDependencies]) => { + const { errors: extractDependencyErrors, references } = + extractInfoFromBindings(pathDependencies, allKeys); + + setDependenciesToDepedencyMapFn(path, references); + + didUpdateDependencyMap = true; + dataTreeEvalErrors = dataTreeEvalErrors.concat( + extractDependencyErrors, + ); + }, + ); + } + const entityPathDependencies = getEntityPathDependencies( entity, entityConfig, @@ -365,6 +389,7 @@ export const updateDependencyMap = ({ dataTreeEvalRef.sortedDependencies = dataTreeEvalRef.sortDependencies( dependencyMap, translatedDiffs, + configTree, ); }