From 79e165af96889e7073d2e0b4ecebd407785659c7 Mon Sep 17 00:00:00 2001 From: Apeksha Bhosale <7846888+ApekshaBhosale@users.noreply.github.com> Date: Thu, 17 Mar 2022 17:35:17 +0530 Subject: [PATCH] feat: Settings js editor (#9984) * POC * Closing channels * WIP * v1 * get working with JS editor * autocomplete * added comments * try removing an import * different way of import * dependency map added to body * triggers can be part of js editor functions hence * removed unwanted lines * new flow chnages * Resolve conflicts * small css changes for empty state * Fix prettier * Fixes * flow changes part 2 * Mock web worker for testing * Throw errors during evaluation * Action execution should be non blocking on the main thread to evaluation of further actions * WIP * Fix build issue * Fix warnings * Rename * Refactor and add tests for worker util * Fix response flow post refactor * added settings icon for js editor * WIP * WIP * WIP * Tests for promises * settings for each function of js object added * Error handling * Error handing action validation * Update test * Passing callback data in the eval trigger flow * log triggers to be executed * WIP * confirm before execution * Remove debugging * Fix backwards compatibility * Avoid passing trigger meta around * fix button loading * handle error callbacks * fix tests * tests * fix console error when checking for async * Fix async function check * Fix async function check again * fix bad commit * Add some comments * added clientSideExecution flag for js functions * css changes for settings icon * unsued code removed * on page load PART 1 * onPageLoad rest iof changes * corrected async badge * removed duplicate test cases * added confirm modal for js functions * removed unused code * small chnage * dependency was not getting created * Fix confirmation modal * unused code removed * replaced new confirmsaga * confirmaton box changes * Fixing JSEditor Run butn locator * corrected property * dependency map was failing * changed key for confirmation box Co-authored-by: hetunandu Co-authored-by: Hetu Nandu Co-authored-by: Arpit Mohan Co-authored-by: Aishwarya UR --- app/client/cypress/support/Pages/JSEditor.ts | 8 +- app/client/src/actions/evaluationActions.ts | 1 + app/client/src/actions/jsPaneActions.ts | 16 +- app/client/src/actions/pluginActionActions.ts | 14 ++ app/client/src/api/JSActionAPI.tsx | 5 + app/client/src/api/PageApi.tsx | 1 + app/client/src/assets/icons/menu/settings.svg | 3 + app/client/src/ce/constants/messages.ts | 9 + .../editorComponents/JSResponseView.tsx | 66 ++++++- app/client/src/components/utils/FlagBadge.tsx | 1 + .../ActionConstants.tsx | 5 + .../src/constants/ReduxActionConstants.tsx | 8 + .../src/entities/DataTree/actionTriggers.ts | 10 +- .../src/entities/DataTree/dataTreeFactory.ts | 2 + .../src/entities/DataTree/dataTreeJSAction.ts | 7 + app/client/src/entities/JSCollection/index.ts | 3 +- .../Editor/JSEditor/JSFunctionSettings.tsx | 79 ++++++++ .../entityReducers/jsActionsReducer.tsx | 75 +++++++- .../ActionExecution/ActionExecutionSagas.ts | 14 ++ .../sagas/ActionExecution/PluginActionSaga.ts | 173 ++++++++++-------- app/client/src/sagas/EvaluationsSaga.ts | 11 +- app/client/src/sagas/JSPaneSagas.ts | 149 ++++++++++++++- app/client/src/sagas/PageSagas.tsx | 14 +- app/client/src/utils/DynamicBindingUtils.ts | 11 +- app/client/src/utils/JSPaneUtils.tsx | 10 +- app/client/src/workers/DataTreeEvaluator.ts | 19 +- app/client/src/workers/PromisifyAction.ts | 20 +- app/client/src/workers/evaluate.ts | 39 +++- app/client/src/workers/evaluationUtils.ts | 48 ++++- 29 files changed, 700 insertions(+), 121 deletions(-) create mode 100644 app/client/src/assets/icons/menu/settings.svg create mode 100644 app/client/src/pages/Editor/JSEditor/JSFunctionSettings.tsx diff --git a/app/client/cypress/support/Pages/JSEditor.ts b/app/client/cypress/support/Pages/JSEditor.ts index 89a9fe9d8f..43be9a7178 100644 --- a/app/client/cypress/support/Pages/JSEditor.ts +++ b/app/client/cypress/support/Pages/JSEditor.ts @@ -6,10 +6,10 @@ const agHelper = new AggregateHelper(); const locator = new CommonLocators(); export class JSEditor { - private _runButton = "//li//*[local-name() = 'svg' and @class='run-button']/parent::li" - private _outputConsole = ".CodeEditorTarget" - private _jsObjName = ".t--js-action-name-edit-field span" - private _jsObjTxt = ".t--js-action-name-edit-field input" + private _runButton = "//li//*[local-name() = 'svg' and @class='run-button']"; + private _outputConsole = ".CodeEditorTarget"; + private _jsObjName = ".t--js-action-name-edit-field span"; + private _jsObjTxt = ".t--js-action-name-edit-field input"; private _newJSobj = "span:contains('New JS Object')" private _bindingsClose = ".t--entity-property-close" diff --git a/app/client/src/actions/evaluationActions.ts b/app/client/src/actions/evaluationActions.ts index 0e57aa5b4b..3d0931ac16 100644 --- a/app/client/src/actions/evaluationActions.ts +++ b/app/client/src/actions/evaluationActions.ts @@ -45,6 +45,7 @@ export const EVALUATE_REDUX_ACTIONS = [ ReduxActionTypes.FETCH_JS_ACTIONS_VIEW_MODE_SUCCESS, ReduxActionErrorTypes.FETCH_JS_ACTIONS_VIEW_MODE_ERROR, ReduxActionTypes.UPDATE_JS_ACTION_BODY_SUCCESS, + ReduxActionTypes.EXECUTE_JS_FUNCTION_SUCCESS, // App Data ReduxActionTypes.SET_APP_MODE, ReduxActionTypes.FETCH_USER_DETAILS_SUCCESS, diff --git a/app/client/src/actions/jsPaneActions.ts b/app/client/src/actions/jsPaneActions.ts index 7341e19e21..8968dff147 100644 --- a/app/client/src/actions/jsPaneActions.ts +++ b/app/client/src/actions/jsPaneActions.ts @@ -1,6 +1,6 @@ import { ReduxAction, ReduxActionTypes } from "constants/ReduxActionConstants"; import { JSCollection, JSAction } from "entities/JSCollection"; -import { RefactorAction } from "api/JSActionAPI"; +import { RefactorAction, SetFunctionPropertyPayload } from "api/JSActionAPI"; export const createNewJSCollection = ( pageId: string, ): ReduxAction<{ pageId: string }> => ({ @@ -61,3 +61,17 @@ export const executeJSFunction = (payload: { payload, }; }; + +export const updateFunctionProperty = (payload: SetFunctionPropertyPayload) => { + return { + type: ReduxActionTypes.SET_FUNCTION_PROPERTY, + payload, + }; +}; + +export const updateJSFunction = (payload: SetFunctionPropertyPayload) => { + return { + type: ReduxActionTypes.UPDATE_JS_FUNCTION_PROPERTY_INIT, + payload, + }; +}; diff --git a/app/client/src/actions/pluginActionActions.ts b/app/client/src/actions/pluginActionActions.ts index 5a40184d29..d448c25afe 100644 --- a/app/client/src/actions/pluginActionActions.ts +++ b/app/client/src/actions/pluginActionActions.ts @@ -279,6 +279,20 @@ export const setActionsToExecuteOnPageLoad = ( }; }; +export const setJSActionsToExecuteOnPageLoad = ( + actions: Array<{ + executeOnLoad: boolean; + id: string; + name: string; + collectionId?: string; + }>, +) => { + return { + type: ReduxActionTypes.SET_JS_ACTION_TO_EXECUTE_ON_PAGELOAD, + payload: actions, + }; +}; + export const bindDataOnCanvas = (payload: { queryId: string; applicationId: string; diff --git a/app/client/src/api/JSActionAPI.tsx b/app/client/src/api/JSActionAPI.tsx index 6fb1011d49..bf2ee37e13 100644 --- a/app/client/src/api/JSActionAPI.tsx +++ b/app/client/src/api/JSActionAPI.tsx @@ -32,6 +32,11 @@ export interface CreateJSCollectionRequest { pluginType: PluginType; } +export type SetFunctionPropertyPayload = { + action: JSAction; + propertyName: string; + value: any; +}; export interface RefactorAction { pageId: string; actionId: string; diff --git a/app/client/src/api/PageApi.tsx b/app/client/src/api/PageApi.tsx index 89b08f39c4..0194d3e6b6 100644 --- a/app/client/src/api/PageApi.tsx +++ b/app/client/src/api/PageApi.tsx @@ -58,6 +58,7 @@ export interface SavePageResponse extends ApiResponse { executeOnLoad: boolean; id: string; name: string; + collectionId?: string; }>; }; } diff --git a/app/client/src/assets/icons/menu/settings.svg b/app/client/src/assets/icons/menu/settings.svg new file mode 100644 index 0000000000..7361abf4b9 --- /dev/null +++ b/app/client/src/assets/icons/menu/settings.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/client/src/ce/constants/messages.ts b/app/client/src/ce/constants/messages.ts index bcade5423a..aca97146e2 100644 --- a/app/client/src/ce/constants/messages.ts +++ b/app/client/src/ce/constants/messages.ts @@ -450,6 +450,15 @@ export const JS_EXECUTION_SUCCESS = () => "JS Function executed successfully"; export const JS_EXECUTION_FAILURE = () => "JS Function execution failed"; export const JS_EXECUTION_FAILURE_TOASTER = () => "There was an error while executing function"; +export const JS_SETTINGS_ONPAGELOAD = () => "Run Function on Page load"; +export const JS_SETTINGS_ONPAGELOAD_SUBTEXT = () => + "Will refresh data every time page is reloaded"; +export const JS_SETTINGS_CONFIRM_EXECUTION = () => + "Request confirmation before calling Function?"; +export const JS_SETTINGS_CONFIRM_EXECUTION_SUBTEXT = () => + "Ask confirmation from the user every time before refreshing data"; +export const JS_SETTINGS_EXECUTE_TIMEOUT = () => + "Function Timeout (in milliseconds)"; // Import/Export Application features export const IMPORT_APPLICATION_MODAL_TITLE = () => "Import application"; diff --git a/app/client/src/components/editorComponents/JSResponseView.tsx b/app/client/src/components/editorComponents/JSResponseView.tsx index 5299258ac8..bfced16e48 100644 --- a/app/client/src/components/editorComponents/JSResponseView.tsx +++ b/app/client/src/components/editorComponents/JSResponseView.tsx @@ -40,6 +40,8 @@ import { setCurrentTab } from "actions/debuggerActions"; import { DEBUGGER_TAB_KEYS } from "./Debugger/helpers"; import EntityBottomTabs from "./EntityBottomTabs"; import Icon from "components/ads/Icon"; +import { ReactComponent as FunctionSettings } from "assets/icons/menu/settings.svg"; +import JSFunctionSettings from "pages/Editor/JSEditor/JSFunctionSettings"; import FlagBadge from "components/utils/FlagBadge"; const ResponseContainer = styled.div` @@ -94,9 +96,16 @@ const ResponseTabAction = styled.li` display: inline-block; flex: 1; } + .function-actions { + margin-left: auto; + order: 2; + svg { + display: inline-block; + } + } .run-button { + margin: 0 15px; margin-left: 10px; - margin-right: 15px; } &.active { background-color: #f0f0f0; @@ -192,6 +201,18 @@ function JSResponseView(props: Props) { dispatch(setCurrentTab(DEBUGGER_TAB_KEYS.ERROR_TAB)); }, []); + const [openSettings, setOpenSettings] = useState(false); + const [selectedFunction, setSelectedFunction] = useState< + undefined | JSAction + >(undefined); + const isSelectedFunctionAsync = (id: string) => { + const jsAction = jsObject.actions.find((action) => action.id === id); + if (!!jsAction) { + return jsAction?.actionConfiguration.isAsync; + } + return false; + }; + const tabs = [ { key: "body", @@ -232,17 +253,35 @@ function JSResponseView(props: Props) { } key={action.id} onClick={() => { - runAction(action); + setSelectActionId(action.id); }} > {" "}
{action.name}
- {action.actionConfiguration.isAsync ? ( - - ) : ( - "" - )} - +
+ {action.actionConfiguration.isAsync ? ( + + ) : ( + "" + )} + {isSelectedFunctionAsync(action.id) ? ( + { + setSelectedFunction(action); + setOpenSettings(true); + }} + /> + ) : ( + "" + )} + + { + runAction(action); + }} + /> +
); })} @@ -271,6 +310,17 @@ function JSResponseView(props: Props) { /> )} + {openSettings && + !!selectedFunction && + isSelectedFunctionAsync(selectedFunction.id) && ( + { + setOpenSettings(!openSettings); + }} + /> + )} )} diff --git a/app/client/src/components/utils/FlagBadge.tsx b/app/client/src/components/utils/FlagBadge.tsx index 171113d859..b003290052 100644 --- a/app/client/src/components/utils/FlagBadge.tsx +++ b/app/client/src/components/utils/FlagBadge.tsx @@ -8,6 +8,7 @@ const Flag = styled.span` text-transform: uppercase; font-size: 10px; font-weight: 600; + margin-right: 10px; `; function FlagBadge(props: { name: string }) { diff --git a/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx b/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx index 4f3408a05d..efa5f50188 100644 --- a/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx +++ b/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx @@ -19,6 +19,9 @@ export type ExecutionResult = { export type TriggerSource = { id: string; name: string; + collectionId?: string; + isJSAction?: boolean; + actionId?: string; }; export type ExecuteTriggerPayload = { @@ -107,6 +110,8 @@ export interface PageAction { name: string; jsonPathKeys: string[]; timeoutInMillisecond: number; + clientSideExecution?: boolean; + collectionId?: string; } export interface ExecuteErrorPayload extends ErrorActionPayload { diff --git a/app/client/src/constants/ReduxActionConstants.tsx b/app/client/src/constants/ReduxActionConstants.tsx index 013b0093e9..eaa4f53669 100644 --- a/app/client/src/constants/ReduxActionConstants.tsx +++ b/app/client/src/constants/ReduxActionConstants.tsx @@ -640,6 +640,13 @@ export const ReduxActionTypes = { UPDATE_JS_ACTION_BODY_INIT: "UPDATE_JS_ACTION_BODY_INIT", UPDATE_JS_ACTION_BODY_SUCCESS: "UPDATE_JS_ACTION_BODY_SUCCESS", SEND_TEST_EMAIL: "SEND_TEST_EMAIL", + SET_FUNCTION_PROPERTY: "SET_FUNCTION_PROPERTY", + UPDATE_JS_FUNCTION_PROPERTY_INIT: "UPDATE_JS_FUNCTION_PROPERTY_INIT", + UPDATE_JS_FUNCTION_PROPERTY_SUCCESS: "UPDATE_JS_FUNCTION_PROPERTY_SUCCESS", + TOGGLE_FUNCTION_EXECUTE_ON_LOAD_INIT: "TOGGLE_FUNCTION_EXECUTE_ON_LOAD_INIT", + TOGGLE_FUNCTION_EXECUTE_ON_LOAD_SUCCESS: + "TOGGLE_FUNCTION_EXECUTE_ON_LOAD_SUCCESS", + SET_JS_ACTION_TO_EXECUTE_ON_PAGELOAD: "SET_JS_ACTION_TO_EXECUTE_ON_PAGELOAD", ENABLE_GUIDED_TOUR: "ENABLE_GUIDED_TOUR", GUIDED_TOUR_MARK_STEP_COMPLETED: "GUIDED_TOUR_MARK_STEP_COMPLETED", SET_CURRENT_STEP: "SET_CURRENT_STEP", @@ -838,6 +845,7 @@ export const ReduxActionErrorTypes = { FETCH_RELEASES_ERROR: "FETCH_RELEASES_ERROR", RESTART_SERVER_ERROR: "RESTART_SERVER_ERROR", UPDATE_JS_ACTION_BODY_ERROR: "UPDATE_JS_ACTION_BODY_ERROR", + UPDATE_JS_FUNCTION_PROPERTY_ERROR: "UPDATE_JS_FUNCTION_PROPERTY_ERROR", DELETE_ORG_ERROR: "DELETE_ORG_ERROR", REFLOW_BETA_FLAGS_INIT_ERROR: "REFLOW_BETA_FLAGS_INIT_ERROR", GET_ALL_TEMPLATES_ERROR: "GET_ALL_TEMPLATES_ERROR", diff --git a/app/client/src/entities/DataTree/actionTriggers.ts b/app/client/src/entities/DataTree/actionTriggers.ts index e6eba816a0..6ad9701684 100644 --- a/app/client/src/entities/DataTree/actionTriggers.ts +++ b/app/client/src/entities/DataTree/actionTriggers.ts @@ -17,6 +17,7 @@ export enum ActionTriggerType { GET_CURRENT_LOCATION = "GET_CURRENT_LOCATION", WATCH_CURRENT_LOCATION = "WATCH_CURRENT_LOCATION", STOP_WATCHING_CURRENT_LOCATION = "STOP_WATCHING_CURRENT_LOCATION", + CONFIRMATION_MODAL = "CONFIRMATION_MODAL", } export const ActionTriggerFunctionNames: Record = { @@ -35,6 +36,7 @@ export const ActionTriggerFunctionNames: Record = { [ActionTriggerType.GET_CURRENT_LOCATION]: "getCurrentLocation", [ActionTriggerType.WATCH_CURRENT_LOCATION]: "watchLocation", [ActionTriggerType.STOP_WATCHING_CURRENT_LOCATION]: "stopWatch", + [ActionTriggerType.CONFIRMATION_MODAL]: "ConfirmationModal", }; export type RunPluginActionDescription = { @@ -158,6 +160,11 @@ export type StopWatchingCurrentLocationDescription = { payload?: Record; }; +export type ConfirmationModal = { + type: ActionTriggerType.CONFIRMATION_MODAL; + payload?: Record; +}; + export type ActionDescription = | RunPluginActionDescription | ClearPluginActionDescription @@ -173,4 +180,5 @@ export type ActionDescription = | ClearIntervalDescription | GetCurrentLocationDescription | WatchCurrentLocationDescription - | StopWatchingCurrentLocationDescription; + | StopWatchingCurrentLocationDescription + | ConfirmationModal; diff --git a/app/client/src/entities/DataTree/dataTreeFactory.ts b/app/client/src/entities/DataTree/dataTreeFactory.ts index 3a904a252a..f6744255ba 100644 --- a/app/client/src/entities/DataTree/dataTreeFactory.ts +++ b/app/client/src/entities/DataTree/dataTreeFactory.ts @@ -81,6 +81,8 @@ export interface DataTreeJSAction { export interface MetaArgs { arguments: Variable[]; + isAsync: boolean; + confirmBeforeExecute: boolean; } /** * Map of overriding property as key and overridden property as values diff --git a/app/client/src/entities/DataTree/dataTreeJSAction.ts b/app/client/src/entities/DataTree/dataTreeJSAction.ts index f61329175c..886cbfa111 100644 --- a/app/client/src/entities/DataTree/dataTreeJSAction.ts +++ b/app/client/src/entities/DataTree/dataTreeJSAction.ts @@ -31,15 +31,21 @@ export const generateDataTreeJSAction = ( const dependencyMap: DependencyMap = {}; dependencyMap["body"] = []; const actions = js.config.actions; + const actionsData: Record = {}; if (actions) { for (let i = 0; i < actions.length; i++) { const action = actions[i]; meta[action.name] = { arguments: action.actionConfiguration.jsArguments, + isAsync: action.actionConfiguration.isAsync, + confirmBeforeExecute: !!action.confirmBeforeExecute, }; bindingPaths[action.name] = EvaluationSubstitutionType.SMART_SUBSTITUTE; dynamicBindingPathList.push({ key: action.name }); dependencyMap["body"].push(action.name); + actionsData[action.name] = { + data: (js.data && js.data[`${action.id}`]) || {}, + }; } } return { @@ -54,5 +60,6 @@ export const generateDataTreeJSAction = ( dynamicBindingPathList: dynamicBindingPathList, variables: listVariables, dependencyMap: dependencyMap, + ...actionsData, }; }; diff --git a/app/client/src/entities/JSCollection/index.ts b/app/client/src/entities/JSCollection/index.ts index ff0cdcd59b..a74975f206 100644 --- a/app/client/src/entities/JSCollection/index.ts +++ b/app/client/src/entities/JSCollection/index.ts @@ -21,9 +21,10 @@ export interface JSCollection { export interface JSActionConfig { body: string; isAsync: boolean; - timeoutInMilliseconds: number; + timeoutInMillisecond: number; jsArguments: Array; } export interface JSAction extends BaseAction { actionConfiguration: JSActionConfig; + clientSideExecution: boolean; } diff --git a/app/client/src/pages/Editor/JSEditor/JSFunctionSettings.tsx b/app/client/src/pages/Editor/JSEditor/JSFunctionSettings.tsx new file mode 100644 index 0000000000..a5315f4571 --- /dev/null +++ b/app/client/src/pages/Editor/JSEditor/JSFunctionSettings.tsx @@ -0,0 +1,79 @@ +import React from "react"; +import styled from "styled-components"; +import Checkbox from "components/ads/Checkbox"; +import Dialog from "components/ads/DialogComponent"; +import { JSAction } from "entities/JSCollection"; +import { updateFunctionProperty } from "actions/jsPaneActions"; +import { useDispatch } from "react-redux"; +import { + createMessage, + JS_SETTINGS_ONPAGELOAD, + JS_SETTINGS_ONPAGELOAD_SUBTEXT, + JS_SETTINGS_CONFIRM_EXECUTION, + JS_SETTINGS_CONFIRM_EXECUTION_SUBTEXT, +} from "@appsmith/constants/messages"; + +const FormRow = styled.div` + margin-bottom: ${(props) => props.theme.spaces[10] + 1}px; + &.flex { + display: flex; + align-items: center; + .cs-text { + margin-right: 30px; + color: rgb(9, 7, 7); + } + } +`; + +interface JSFunctionSettingsProps { + action: JSAction; + openSettings: boolean; + toggleSettings: () => void; +} + +function JSFunctionSettings(props: JSFunctionSettingsProps) { + const { action } = props; + const dispatch = useDispatch(); + const updateProperty = (value: boolean | number, propertyName: string) => { + dispatch( + updateFunctionProperty({ + action: props.action, + propertyName: propertyName, + value: value, + }), + ); + }; + + return ( + + + + updateProperty(value, "executeOnLoad") + } + /> + + + + updateProperty(value, "confirmBeforeExecute") + } + /> + + + ); +} +export default JSFunctionSettings; diff --git a/app/client/src/reducers/entityReducers/jsActionsReducer.tsx b/app/client/src/reducers/entityReducers/jsActionsReducer.tsx index 1b8bf1f56a..96657ffdb3 100644 --- a/app/client/src/reducers/entityReducers/jsActionsReducer.tsx +++ b/app/client/src/reducers/entityReducers/jsActionsReducer.tsx @@ -5,10 +5,10 @@ import { ReduxAction, ReduxActionErrorTypes, } from "constants/ReduxActionConstants"; -import { keyBy } from "lodash"; +import { set, keyBy } from "lodash"; +import produce from "immer"; const initialState: JSCollectionDataState = []; - export interface JSCollectionData { isLoading: boolean; config: JSCollection; @@ -64,8 +64,9 @@ const jsActionsReducer = createReducer(initialState, { action: ReduxAction<{ data: JSCollection }>, ): JSCollectionDataState => state.map((a) => { - if (a.config.id === action.payload.data.id) - return { isLoading: false, config: action.payload.data }; + if (a.config.id === action.payload.data.id) { + return { ...a, isLoading: false, config: action.payload.data }; + } return a; }), [ReduxActionTypes.UPDATE_JS_ACTION_BODY_SUCCESS]: ( @@ -75,6 +76,7 @@ const jsActionsReducer = createReducer(initialState, { state.map((a) => { if (a.config.id === action.payload.data.id) return { + ...a, isLoading: false, config: action.payload.data, }; @@ -286,6 +288,71 @@ const jsActionsReducer = createReducer(initialState, { } return a; }), + [ReduxActionTypes.UPDATE_JS_FUNCTION_PROPERTY_SUCCESS]: ( + state: JSCollectionDataState, + action: ReduxAction<{ collection: JSCollection }>, + ): JSCollectionDataState => + state.map((a) => { + if (a.config.id === action.payload.collection.id) { + return { + ...a, + data: action.payload, + }; + } + return a; + }), + [ReduxActionTypes.TOGGLE_FUNCTION_EXECUTE_ON_LOAD_SUCCESS]: ( + state: JSCollectionDataState, + action: ReduxAction<{ + actionId: string; + collectionId: string; + executeOnLoad: boolean; + }>, + ): JSCollectionDataState => + state.map((a) => { + if (a.config.id === action.payload.collectionId) { + const updatedActions = a.config.actions.map((jsAction) => { + if (jsAction.id === action.payload.actionId) { + set(jsAction, `executeOnLoad`, action.payload.executeOnLoad); + } + return jsAction; + }); + return { + ...a, + config: { + ...a.config, + actions: updatedActions, + }, + }; + } + return a; + }), + [ReduxActionTypes.SET_JS_ACTION_TO_EXECUTE_ON_PAGELOAD]: ( + state: JSCollectionDataState, + action: ReduxAction< + Array<{ + executeOnLoad: boolean; + id: string; + name: string; + collectionId: string; + }> + >, + ) => { + return produce(state, (draft) => { + const CollectionUpdateSearch = keyBy(action.payload, "collectionId"); + const actionUpdateSearch = keyBy(action.payload, "id"); + draft.forEach((action, index) => { + if (action.config.id in CollectionUpdateSearch) { + const allActions = draft[index].config.actions; + allActions.forEach((js) => { + if (js.id in actionUpdateSearch) { + js.executeOnLoad = actionUpdateSearch[js.id].executeOnLoad; + } + }); + } + }); + }); + }, }); export default jsActionsReducer; diff --git a/app/client/src/sagas/ActionExecution/ActionExecutionSagas.ts b/app/client/src/sagas/ActionExecution/ActionExecutionSagas.ts index 53ed741935..731feee4ff 100644 --- a/app/client/src/sagas/ActionExecution/ActionExecutionSagas.ts +++ b/app/client/src/sagas/ActionExecution/ActionExecutionSagas.ts @@ -38,11 +38,14 @@ import { clearIntervalSaga, setIntervalSaga, } from "sagas/ActionExecution/SetIntervalSaga"; +import { UserCancelledActionExecutionError } from "sagas/ActionExecution/errorUtils"; import { getCurrentLocationSaga, stopWatchCurrentLocation, watchCurrentLocation, } from "sagas/ActionExecution/GetCurrentLocationSaga"; +import { requestModalConfirmationSaga } from "sagas/UtilSagas"; +import { ModalType } from "reducers/uiReducers/modalActionReducer"; export type TriggerMeta = { source?: TriggerSource; @@ -125,6 +128,17 @@ export function* executeActionTriggers( case ActionTriggerType.STOP_WATCHING_CURRENT_LOCATION: response = yield call(stopWatchCurrentLocation, eventType, triggerMeta); break; + case ActionTriggerType.CONFIRMATION_MODAL: + const payloadInfo = { + name: trigger?.payload?.funName, + modalOpen: true, + modalType: ModalType.RUN_ACTION, + }; + const flag = yield call(requestModalConfirmationSaga, payloadInfo); + if (!flag) { + throw new UserCancelledActionExecutionError(); + } + break; default: log.error("Trigger type unknown", trigger); throw Error("Trigger type unknown"); diff --git a/app/client/src/sagas/ActionExecution/PluginActionSaga.ts b/app/client/src/sagas/ActionExecution/PluginActionSaga.ts index 628002e7dd..a61c690207 100644 --- a/app/client/src/sagas/ActionExecution/PluginActionSaga.ts +++ b/app/client/src/sagas/ActionExecution/PluginActionSaga.ts @@ -23,6 +23,7 @@ import { getCurrentPageNameByActionId, isActionDirty, isActionSaving, + getJSCollection, } from "selectors/entitiesSelector"; import { getAppMode, @@ -89,6 +90,8 @@ import { UserCancelledActionExecutionError, } from "sagas/ActionExecution/errorUtils"; import { trimQueryString } from "utils/helpers"; +import { JSCollection } from "entities/JSCollection"; +import { executeJSFunction } from "actions/jsPaneActions"; import { executeAppAction, TriggerMeta, @@ -587,85 +590,109 @@ function* runActionSaga( } } -function* executePageLoadAction(pageAction: PageAction) { - const pageId = yield select(getCurrentPageId); - let currentApp: ApplicationPayload = yield select(getCurrentApplication); - currentApp = currentApp || {}; - const appMode = yield select(getAppMode); - AnalyticsUtil.logEvent("EXECUTE_ACTION", { - type: pageAction.pluginType, - name: pageAction.name, - pageId: pageId, - appMode: appMode, - appId: currentApp.id, - onPageLoad: true, - appName: currentApp.name, - isExampleApp: currentApp.appIsExample, - }); - - let payload = EMPTY_RESPONSE; - let isError = true; - const error = `The action "${pageAction.name}" has failed.`; - try { - const executePluginActionResponse: ExecutePluginActionResponse = yield call( - executePluginActionSaga, - pageAction, +function* executeOnPageLoadJSAction(pageAction: PageAction) { + const collectionId = pageAction.collectionId; + if (collectionId) { + const collection: JSCollection = yield select( + getJSCollection, + collectionId, ); - payload = executePluginActionResponse.payload; - isError = executePluginActionResponse.isError; - } catch (e) { - log.error(e); + const jsAction = collection.actions.find((d) => d.id === pageAction.id); + if (!!jsAction) { + yield put( + executeJSFunction({ + collectionName: collection.name, + action: jsAction, + collectionId: collectionId, + }), + ); + } } +} - if (isError) { - AppsmithConsole.addError({ - id: pageAction.id, - logType: LOG_TYPE.ACTION_EXECUTION_ERROR, - text: `Execution failed with status ${payload.statusCode}`, - source: { - type: ENTITY_TYPE.ACTION, - name: pageAction.name, - id: pageAction.id, - }, - state: payload.request, - messages: [ - { - message: error, - type: PLATFORM_ERROR.PLUGIN_EXECUTION, - subType: payload.errorType, - }, - ], +function* executePageLoadAction(pageAction: PageAction) { + if (pageAction.hasOwnProperty("collectionId")) { + yield call(executeOnPageLoadJSAction, pageAction); + } else { + const pageId = yield select(getCurrentPageId); + let currentApp: ApplicationPayload = yield select(getCurrentApplication); + currentApp = currentApp || {}; + const appMode = yield select(getAppMode); + AnalyticsUtil.logEvent("EXECUTE_ACTION", { + type: pageAction.pluginType, + name: pageAction.name, + pageId: pageId, + appMode: appMode, + appId: currentApp.id, + onPageLoad: true, + appName: currentApp.name, + isExampleApp: currentApp.appIsExample, }); - yield put( - executePluginActionError({ - actionId: pageAction.id, - isPageLoad: true, - error: { message: error }, - data: payload, - }), - ); - PerformanceTracker.stopAsyncTracking( - PerformanceTransactionName.EXECUTE_ACTION, - { - failed: true, - }, - pageAction.id, - ); - } else { - PerformanceTracker.stopAsyncTracking( - PerformanceTransactionName.EXECUTE_ACTION, - undefined, - pageAction.id, - ); - yield put( - executePluginActionSuccess({ + let payload = EMPTY_RESPONSE; + let isError = true; + const error = `The action "${pageAction.name}" has failed.`; + try { + const executePluginActionResponse: ExecutePluginActionResponse = yield call( + executePluginActionSaga, + pageAction, + ); + payload = executePluginActionResponse.payload; + isError = executePluginActionResponse.isError; + } catch (e) { + log.error(e); + } + + if (isError) { + AppsmithConsole.addError({ id: pageAction.id, - response: payload, - isPageLoad: true, - }), - ); - yield take(ReduxActionTypes.SET_EVALUATED_TREE); + logType: LOG_TYPE.ACTION_EXECUTION_ERROR, + text: `Execution failed with status ${payload.statusCode}`, + source: { + type: ENTITY_TYPE.ACTION, + name: pageAction.name, + id: pageAction.id, + }, + state: payload.request, + messages: [ + { + message: error, + type: PLATFORM_ERROR.PLUGIN_EXECUTION, + subType: payload.errorType, + }, + ], + }); + + yield put( + executePluginActionError({ + actionId: pageAction.id, + isPageLoad: true, + error: { message: error }, + data: payload, + }), + ); + PerformanceTracker.stopAsyncTracking( + PerformanceTransactionName.EXECUTE_ACTION, + { + failed: true, + }, + pageAction.id, + ); + } else { + PerformanceTracker.stopAsyncTracking( + PerformanceTransactionName.EXECUTE_ACTION, + undefined, + pageAction.id, + ); + yield put( + executePluginActionSuccess({ + id: pageAction.id, + response: payload, + isPageLoad: true, + }), + ); + yield take(ReduxActionTypes.SET_EVALUATED_TREE); + } } } diff --git a/app/client/src/sagas/EvaluationsSaga.ts b/app/client/src/sagas/EvaluationsSaga.ts index 9307b879eb..4567313707 100644 --- a/app/client/src/sagas/EvaluationsSaga.ts +++ b/app/client/src/sagas/EvaluationsSaga.ts @@ -220,9 +220,14 @@ export function* evaluateAndExecuteDynamicTrigger( * We raise an error telling the user that an uncaught error has occurred * */ if (requestData.result.errors.length) { - throw new UncaughtPromiseError( - requestData.result.errors[0].errorMessage, - ); + if ( + requestData.result.errors[0].errorMessage !== + "UncaughtPromiseRejection: User cancelled action execution" + ) { + throw new UncaughtPromiseError( + requestData.result.errors[0].errorMessage, + ); + } } // It is possible to get a few triggers here if the user // still uses the old way of action runs and not promises. For that we diff --git a/app/client/src/sagas/JSPaneSagas.ts b/app/client/src/sagas/JSPaneSagas.ts index 1a03a6ffd7..ac3dc1a490 100644 --- a/app/client/src/sagas/JSPaneSagas.ts +++ b/app/client/src/sagas/JSPaneSagas.ts @@ -6,6 +6,7 @@ import { debounce, call, take, + takeLatest, } from "redux-saga/effects"; import { ReduxAction, @@ -31,11 +32,16 @@ import { pushLogsForObjectUpdate, createDummyJSCollectionActions, } from "../utils/JSPaneUtils"; -import JSActionAPI, { RefactorAction } from "../api/JSActionAPI"; +import JSActionAPI, { + RefactorAction, + SetFunctionPropertyPayload, +} from "../api/JSActionAPI"; +import ActionAPI from "api/ActionAPI"; import { updateJSCollectionSuccess, refactorJSCollectionAction, updateJSCollectionBodySuccess, + updateJSFunction, } from "actions/jsPaneActions"; import { getCurrentOrgId } from "selectors/organizationSelectors"; import { getPluginIdOfPackageName } from "sagas/selectors"; @@ -59,6 +65,7 @@ import LOG_TYPE from "entities/AppsmithConsole/logtype"; import PageApi from "api/PageApi"; import { updateCanvasWithDSL } from "sagas/PageSagas"; export const JS_PLUGIN_PACKAGE_NAME = "js-plugin"; +import { set } from "lodash"; import { updateReplayEntity } from "actions/pageActions"; function* handleCreateNewJsActionSaga(action: ReduxAction<{ pageId: string }>) { @@ -280,7 +287,7 @@ function* handleJSObjectNameChangeSuccessSaga( } } -function* handleExecuteJSFunctionSaga( +export function* handleExecuteJSFunctionSaga( data: ReduxAction<{ collectionName: string; action: JSAction; @@ -291,7 +298,6 @@ function* handleExecuteJSFunctionSaga( const actionId = action.id; try { const result = yield call(executeFunction, collectionName, action); - yield put({ type: ReduxActionTypes.EXECUTE_JS_FUNCTION_SUCCESS, payload: { @@ -338,7 +344,7 @@ function* handleUpdateJSCollectionBody( actionPayload: ReduxAction<{ body: string; id: string; isReplay: boolean }>, ) { const jsCollection = yield select(getJSCollection, actionPayload.payload.id); - jsCollection.body = actionPayload.payload.body; + jsCollection["body"] = actionPayload.payload.body; try { if (jsCollection) { const response = yield JSActionAPI.updateJSCollection(jsCollection); @@ -418,6 +424,132 @@ function* handleRefactorJSActionNameSaga( } } +function* setFunctionPropertySaga( + data: ReduxAction, +) { + const { action, propertyName, value } = data.payload; + if (!action.id) return; + const actionId = action.id; + if (propertyName === "executeOnLoad") { + yield put({ + type: ReduxActionTypes.TOGGLE_FUNCTION_EXECUTE_ON_LOAD_INIT, + payload: { + collectionId: action.collectionId, + actionId, + shouldExecute: value, + }, + }); + return; + } + yield put(updateJSFunction({ ...data.payload })); +} + +function* handleUpdateJSFunctionPropertySaga( + data: ReduxAction, +) { + const { action, propertyName, value } = data.payload; + if (!action.id) return; + const actionId = action.id; + let collection: JSCollection; + if (action.collectionId) { + collection = yield select(getJSCollection, action.collectionId); + + try { + const actions: JSAction[] = collection.actions; + const updatedActions = actions.map((jsAction: JSAction) => { + if (jsAction.id === actionId) { + set(jsAction, propertyName, value); + return jsAction; + } + return jsAction; + }); + collection.actions = updatedActions; + const response = yield JSActionAPI.updateJSCollection(collection); + const isValidResponse = yield validateResponse(response); + if (isValidResponse) { + const fieldToBeUpdated = propertyName.replace( + "actionConfiguration", + "config", + ); + AppsmithConsole.info({ + logType: LOG_TYPE.ACTION_UPDATE, + text: "Configuration updated", + source: { + type: ENTITY_TYPE.JSACTION, + name: collection.name + "." + action.name, + id: actionId, + propertyPath: fieldToBeUpdated, + }, + state: { + [fieldToBeUpdated]: value, + }, + }); + yield put({ + type: ReduxActionTypes.UPDATE_JS_FUNCTION_PROPERTY_SUCCESS, + payload: { + collection, + }, + }); + } + } catch (e) { + yield put({ + type: ReduxActionErrorTypes.UPDATE_JS_FUNCTION_PROPERTY_ERROR, + payload: collection, + }); + } + } +} + +function* toggleFunctionExecuteOnLoadSaga( + action: ReduxAction<{ + collectionId: string; + actionId: string; + shouldExecute: boolean; + }>, +) { + try { + const { actionId, collectionId, shouldExecute } = action.payload; + const collection = yield select(getJSCollection, collectionId); + const jsAction = collection.actions.find( + (action: JSAction) => actionId === action.id, + ); + const response = yield call( + ActionAPI.toggleActionExecuteOnLoad, + actionId, + shouldExecute, + ); + const isValidResponse = yield validateResponse(response); + if (isValidResponse) { + AppsmithConsole.info({ + logType: LOG_TYPE.ACTION_UPDATE, + text: "Configuration updated", + source: { + type: ENTITY_TYPE.JSACTION, + name: collection.name + "." + jsAction.name, + id: actionId, + propertyPath: "executeOnLoad", + }, + state: { + ["executeOnLoad"]: shouldExecute, + }, + }); + yield put({ + type: ReduxActionTypes.TOGGLE_FUNCTION_EXECUTE_ON_LOAD_SUCCESS, + payload: { + actionId: actionId, + collectionId: collectionId, + executeOnLoad: shouldExecute, + }, + }); + } + } catch (error) { + yield put({ + type: ReduxActionErrorTypes.TOGGLE_ACTION_EXECUTE_ON_LOAD_ERROR, + payload: error, + }); + } +} + export default function* root() { yield all([ takeEvery( @@ -445,5 +577,14 @@ export default function* root() { ReduxActionTypes.UPDATE_JS_ACTION_BODY_INIT, handleUpdateJSCollectionBody, ), + takeEvery(ReduxActionTypes.SET_FUNCTION_PROPERTY, setFunctionPropertySaga), + takeLatest( + ReduxActionTypes.UPDATE_JS_FUNCTION_PROPERTY_INIT, + handleUpdateJSFunctionPropertySaga, + ), + takeLatest( + ReduxActionTypes.TOGGLE_FUNCTION_EXECUTE_ON_LOAD_INIT, + toggleFunctionExecuteOnLoadSaga, + ), ]); } diff --git a/app/client/src/sagas/PageSagas.tsx b/app/client/src/sagas/PageSagas.tsx index 5f76277cb5..ebbbbfe290 100644 --- a/app/client/src/sagas/PageSagas.tsx +++ b/app/client/src/sagas/PageSagas.tsx @@ -75,6 +75,7 @@ import { executePageLoadActions, fetchActionsForPage, setActionsToExecuteOnPageLoad, + setJSActionsToExecuteOnPageLoad, } from "actions/pluginActionActions"; import { UrlDataState } from "reducers/entityReducers/appReducer"; import { APP_MODE } from "entities/App"; @@ -403,7 +404,18 @@ function* savePageSaga(action: ReduxAction<{ isRetry?: boolean }>) { } // Update actions if (actionUpdates && actionUpdates.length > 0) { - yield put(setActionsToExecuteOnPageLoad(actionUpdates)); + const actions = actionUpdates.filter( + (d) => !d.hasOwnProperty("collectionId"), + ); + if (actions && actions.length) { + yield put(setActionsToExecuteOnPageLoad(actions)); + } + const jsActions = actionUpdates.filter((d) => + d.hasOwnProperty("collectionId"), + ); + if (jsActions && jsActions.length) { + yield put(setJSActionsToExecuteOnPageLoad(jsActions)); + } } yield put(setLastUpdatedTime(Date.now() / 1000)); yield put(savePageSuccess(savePageResponse)); diff --git a/app/client/src/utils/DynamicBindingUtils.ts b/app/client/src/utils/DynamicBindingUtils.ts index 68955665ad..9633da8ab4 100644 --- a/app/client/src/utils/DynamicBindingUtils.ts +++ b/app/client/src/utils/DynamicBindingUtils.ts @@ -307,10 +307,13 @@ export const unsafeFunctionForEval = [ export const isChildPropertyPath = ( parentPropertyPath: string, childPropertyPath: string, -): boolean => - parentPropertyPath === childPropertyPath || - childPropertyPath.startsWith(`${parentPropertyPath}.`) || - childPropertyPath.startsWith(`${parentPropertyPath}[`); +): boolean => { + return ( + parentPropertyPath === childPropertyPath || + childPropertyPath.startsWith(`${parentPropertyPath}.`) || + childPropertyPath.startsWith(`${parentPropertyPath}[`) + ); +}; /** * Paths set via evaluator on entities diff --git a/app/client/src/utils/JSPaneUtils.tsx b/app/client/src/utils/JSPaneUtils.tsx index 0ec48bbfa4..f421d2f9c1 100644 --- a/app/client/src/utils/JSPaneUtils.tsx +++ b/app/client/src/utils/JSPaneUtils.tsx @@ -113,7 +113,7 @@ export const getDifferenceInJSCollection = ( actionConfiguration: { body: action.body, isAsync: action.isAsync, - timeoutInMilliseconds: 0, + timeoutInMillisecond: 0, jsArguments: [], }, }; @@ -196,9 +196,10 @@ export const createDummyJSCollectionActions = ( actionConfiguration: { body: "() => {\n\t\t//write code here\n\t}", isAsync: false, - timeoutInMilliseconds: 0, + timeoutInMillisecond: 0, jsArguments: [], }, + clientSideExecution: true, }, { name: "myFun2", @@ -206,11 +207,12 @@ export const createDummyJSCollectionActions = ( organizationId, executeOnLoad: false, actionConfiguration: { - body: "async () => {\n\t\t//write code here\n\t}", + body: "async () => {\n\t\t//use async-await or promises\n\t}", isAsync: true, - timeoutInMilliseconds: 0, + timeoutInMillisecond: 0, jsArguments: [], }, + clientSideExecution: true, }, ]; return { diff --git a/app/client/src/workers/DataTreeEvaluator.ts b/app/client/src/workers/DataTreeEvaluator.ts index 069c305638..6f79f09676 100644 --- a/app/client/src/workers/DataTreeEvaluator.ts +++ b/app/client/src/workers/DataTreeEvaluator.ts @@ -178,13 +178,27 @@ export default class DataTreeEvaluator { return { evalTree: this.evalTree, jsUpdates: jsUpdates }; } + isJSObjectFunction(dataTree: DataTree, jsObjectName: string, key: string) { + const entity = dataTree[jsObjectName]; + if (isJSAction(entity)) { + return entity.meta.hasOwnProperty(key); + } + return false; + } + updateLocalUnEvalTree(dataTree: DataTree) { //add functions and variables to unevalTree Object.keys(this.currentJSCollectionState).forEach((update) => { const updates = this.currentJSCollectionState[update]; if (!!dataTree[update]) { Object.keys(updates).forEach((key) => { - _.set(dataTree, `${update}.${key}`, updates[key]); + const data = _.get(dataTree, `${update}.${key}.data`, undefined); + if (this.isJSObjectFunction(dataTree, update, key)) { + _.set(dataTree, `${update}.${key}`, new String(updates[key])); + _.set(dataTree, `${update}.${key}.data`, data); + } else { + _.set(dataTree, `${update}.${key}`, updates[key]); + } }); } }); @@ -548,7 +562,8 @@ export default class DataTreeEvaluator { Object.keys(entity.bindingPaths).forEach((propertyPath) => { const existingDeps = dependencies[`${entityName}.${propertyPath}`] || []; - const jsSnippets = [_.get(entity, propertyPath)]; + const unevalPropValue = _.get(entity, propertyPath).toString(); + const { jsSnippets } = getDynamicBindings(unevalPropValue, entity); dependencies[`${entityName}.${propertyPath}`] = existingDeps.concat( jsSnippets.filter((jsSnippet) => !!jsSnippet), ); diff --git a/app/client/src/workers/PromisifyAction.ts b/app/client/src/workers/PromisifyAction.ts index 23ea5176f8..7c693e120a 100644 --- a/app/client/src/workers/PromisifyAction.ts +++ b/app/client/src/workers/PromisifyAction.ts @@ -9,7 +9,10 @@ const ctx: Worker = self as any; * needs a REQUEST_ID to be passed in to know which request is going on right now */ import { EVAL_WORKER_ACTIONS } from "utils/DynamicBindingUtils"; -import { ActionDescription } from "entities/DataTree/actionTriggers"; +import { + ActionDescription, + ActionTriggerType, +} from "entities/DataTree/actionTriggers"; import _ from "lodash"; import { dataTreeEvaluator } from "workers/evaluation.worker"; @@ -99,3 +102,18 @@ export const completePromise = (requestId: string, result: EvalResult) => { requestId, }); }; + +export const confirmationPromise = function( + requestId: string, + func: any, + name: string, + ...args: any[] +) { + const payload: ActionDescription = { + type: ActionTriggerType.CONFIRMATION_MODAL, + payload: { + funName: name, + }, + }; + return promisifyAction(requestId, payload).then(() => func(...args)); +}; diff --git a/app/client/src/workers/evaluate.ts b/app/client/src/workers/evaluate.ts index ece0f63e59..e9a7b2c30c 100644 --- a/app/client/src/workers/evaluate.ts +++ b/app/client/src/workers/evaluate.ts @@ -11,7 +11,7 @@ import { Severity } from "entities/AppsmithConsole"; import { enhanceDataTreeWithFunctions } from "./Actions"; import { isEmpty } from "lodash"; import { getLintingErrors } from "workers/lint"; -import { completePromise } from "workers/PromisifyAction"; +import { completePromise, confirmationPromise } from "workers/PromisifyAction"; import { ActionDescription } from "entities/DataTree/actionTriggers"; export type EvalResult = { @@ -147,7 +147,23 @@ export const createGlobalData = ( Object.keys(resolvedObject).forEach((key: any) => { const dataTreeKey = GLOBAL_DATA[datum]; if (dataTreeKey) { - dataTreeKey[key] = resolvedObject[key]; + const data = dataTreeKey[key]?.data; + const isAsync = dataTreeKey?.meta[key]?.isAsync || false; + const confirmBeforeExecute = + dataTreeKey?.meta[key]?.confirmBeforeExecute || false; + if (isAsync && confirmBeforeExecute) { + dataTreeKey[key] = confirmationPromise.bind( + {}, + context?.requestId, + resolvedObject[key], + dataTreeKey.name + "." + key, + ); + } else { + dataTreeKey[key] = resolvedObject[key]; + } + if (!!data) { + dataTreeKey[key]["data"] = data; + } } }); }); @@ -355,7 +371,23 @@ export function isFunctionAsync( Object.keys(resolvedObject).forEach((key: any) => { const dataTreeKey = GLOBAL_DATA[datum]; if (dataTreeKey) { - dataTreeKey[key] = resolvedObject[key]; + const data = dataTreeKey[key]?.data; + const isAsync = dataTreeKey.meta[key]?.isAsync || false; + const confirmBeforeExecute = + dataTreeKey.meta[key]?.confirmBeforeExecute || false; + if (isAsync && confirmBeforeExecute) { + dataTreeKey[key] = confirmationPromise.bind( + {}, + "", + resolvedObject[key], + key, + ); + } else { + dataTreeKey[key] = resolvedObject[key]; + } + if (!!data) { + dataTreeKey[key].data = data; + } } }); }); @@ -368,7 +400,6 @@ export function isFunctionAsync( // @ts-ignore: No types available self[key] = GLOBAL_DATA[key]; }); - try { if (typeof userFunction === "function") { const returnValue = userFunction(); diff --git a/app/client/src/workers/evaluationUtils.ts b/app/client/src/workers/evaluationUtils.ts index 5f7ee4fd85..28f18cf450 100644 --- a/app/client/src/workers/evaluationUtils.ts +++ b/app/client/src/workers/evaluationUtils.ts @@ -605,33 +605,68 @@ export const updateJSCollectionInDataTree = ( const action = parsedBody.actions[i]; if (jsCollection.hasOwnProperty(action.name)) { if (jsCollection[action.name] !== action.body) { + const data = _.get( + modifiedDataTree, + `${jsCollection.name}.${action.name}.data`, + {}, + ); _.set( modifiedDataTree, `${jsCollection.name}.${action.name}`, - action.body, + new String(action.body), + ); + + _.set( + modifiedDataTree, + `${jsCollection.name}.${action.name}.data`, + data, ); } } else { const bindingPaths = jsCollection.bindingPaths; bindingPaths[action.name] = EvaluationSubstitutionType.SMART_SUBSTITUTE; - _.set(modifiedDataTree, `${jsCollection}.bindingPaths`, bindingPaths); + bindingPaths[`${action.name}.data`] = + EvaluationSubstitutionType.TEMPLATE; + _.set( + modifiedDataTree, + `${jsCollection.name}.bindingPaths`, + bindingPaths, + ); const dynamicBindingPathList = jsCollection.dynamicBindingPathList; dynamicBindingPathList.push({ key: action.name }); _.set( modifiedDataTree, - `${jsCollection}.dynamicBindingPathList`, + `${jsCollection.name}.dynamicBindingPathList`, dynamicBindingPathList, ); const dependencyMap = jsCollection.dependencyMap; dependencyMap["body"].push(action.name); - _.set(modifiedDataTree, `${jsCollection}.dependencyMap`, dependencyMap); + _.set( + modifiedDataTree, + `${jsCollection.name}.dependencyMap`, + dependencyMap, + ); const meta = jsCollection.meta; - meta[action.name] = { arguments: action.arguments }; + meta[action.name] = { + arguments: action.arguments, + isAsync: false, + confirmBeforeExecute: false, + }; _.set(modifiedDataTree, `${jsCollection.name}.meta`, meta); + const data = _.get( + modifiedDataTree, + `${jsCollection.name}.${action.name}.data`, + {}, + ); _.set( modifiedDataTree, `${jsCollection.name}.${action.name}`, - action.body, + new String(action.body.toString()), + ); + _.set( + modifiedDataTree, + `${jsCollection.name}.${action.name}.data`, + data, ); } } @@ -675,6 +710,7 @@ export const updateJSCollectionInDataTree = ( delete meta[preAction]; _.set(modifiedDataTree, `${jsCollection.name}.meta`, meta); delete modifiedDataTree[`${jsCollection.name}`][`${preAction}`]; + delete modifiedDataTree[`${jsCollection.name}`][`${preAction}.data`]; } } }