diff --git a/app/client/package.json b/app/client/package.json index df24c35d3e..663ecba28f 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -55,6 +55,7 @@ "fusioncharts": "^3.15.0-sr.1", "history": "^4.10.1", "husky": "^3.0.5", + "immer": "^7.0.8", "instantsearch.css": "^7.4.2", "instantsearch.js": "^4.4.1", "interweave": "^12.1.1", diff --git a/app/client/src/actions/actionActions.ts b/app/client/src/actions/actionActions.ts index a900f428f3..8ded9afeed 100644 --- a/app/client/src/actions/actionActions.ts +++ b/app/client/src/actions/actionActions.ts @@ -67,6 +67,38 @@ export const runAction = (id: string, paginationField?: PaginationField) => { }; }; +export const runActionInit = ( + id: string, + paginationField?: PaginationField, +) => { + return { + type: ReduxActionTypes.RUN_ACTION_INIT, + payload: { + id, + paginationField, + }, + }; +}; + +export const showRunActionConfirmModal = (show: boolean) => { + return { + type: ReduxActionTypes.SHOW_RUN_ACTION_CONFIRM_MODAL, + payload: show, + }; +}; + +export const cancelRunActionConfirmModal = () => { + return { + type: ReduxActionTypes.CANCEL_RUN_ACTION_CONFIRM_MODAL, + }; +}; + +export const acceptRunActionConfirmModal = () => { + return { + type: ReduxActionTypes.ACCEPT_RUN_ACTION_CONFIRM_MODAL, + }; +}; + export const updateAction = (payload: { id: string }) => { return batchAction({ type: ReduxActionTypes.UPDATE_ACTION_INIT, @@ -196,6 +228,13 @@ export const updateActionProperty = ( }); }; +export const setActionsToExecuteOnPageLoad = (actions: string[]) => { + return { + type: ReduxActionTypes.SET_ACTION_TO_EXECUTE_ON_PAGELOAD, + payload: actions, + }; +}; + export default { createAction: createActionRequest, fetchActions, diff --git a/app/client/src/api/ActionAPI.tsx b/app/client/src/api/ActionAPI.tsx index b5b392326d..c0762c3b37 100644 --- a/app/client/src/api/ActionAPI.tsx +++ b/app/client/src/api/ActionAPI.tsx @@ -182,6 +182,12 @@ class ActionAPI extends API { static executeQuery(executeAction: any): AxiosPromise { return API.post(ActionAPI.url + "/execute", executeAction); } + + static toggleActionExecuteOnLoad(actionId: string, shouldExecute: boolean) { + return API.put(ActionAPI.url + `/executeOnLoad/${actionId}`, undefined, { + flag: shouldExecute.toString(), + }); + } } export default ActionAPI; diff --git a/app/client/src/api/PageApi.tsx b/app/client/src/api/PageApi.tsx index 4344fb22d6..bb5fe88f7e 100644 --- a/app/client/src/api/PageApi.tsx +++ b/app/client/src/api/PageApi.tsx @@ -45,7 +45,11 @@ export type FetchPublishedPageResponse = ApiResponse & { }; export interface SavePageResponse extends ApiResponse { - pageId: string; + data: { + id: string; + layoutOnLoadActions: PageAction[][]; + dsl: Partial>; + }; } export interface CreatePageRequest { diff --git a/app/client/src/components/formControls/DynamicTextFieldControl.tsx b/app/client/src/components/formControls/DynamicTextFieldControl.tsx index 7b31e91d43..a8a423be7d 100644 --- a/app/client/src/components/formControls/DynamicTextFieldControl.tsx +++ b/app/client/src/components/formControls/DynamicTextFieldControl.tsx @@ -24,7 +24,7 @@ import { const Wrapper = styled.div` .dynamic-text-field { border-radius: 4px; - border: 1px solid #d0d7dd; + border: none; font-size: 14px; height: calc(100vh / 4); } diff --git a/app/client/src/components/formControls/SwitchControl.tsx b/app/client/src/components/formControls/SwitchControl.tsx index 4697011339..e9e2cae7d5 100644 --- a/app/client/src/components/formControls/SwitchControl.tsx +++ b/app/client/src/components/formControls/SwitchControl.tsx @@ -21,21 +21,30 @@ const SwitchWrapped = styled.div` } `; +const Info = styled.div` + font-size: 12px; + opacity: 0.7; + margin-top: 8px; +`; + export class SwitchField extends React.Component { render() { - const { label, isRequired, input } = this.props; + const { label, isRequired, input, info } = this.props; return ( - - - {label} {isRequired && "*"} - - input.onChange(value)} - large - /> - +
+ + + {label} {isRequired && "*"} + + input.onChange(value)} + large + /> + + {info && {info}} +
); } } @@ -56,6 +65,8 @@ class SwitchControl extends BaseControl { } } -export type SwitchControlProps = ControlProps; +export interface SwitchControlProps extends ControlProps { + info?: string; +} export default SwitchControl; diff --git a/app/client/src/constants/ReduxActionConstants.tsx b/app/client/src/constants/ReduxActionConstants.tsx index 3dea6e5354..d251925f76 100644 --- a/app/client/src/constants/ReduxActionConstants.tsx +++ b/app/client/src/constants/ReduxActionConstants.tsx @@ -27,6 +27,7 @@ export const ReduxActionTypes: { [key: string]: string } = { REMOVE_PAGE_WIDGET: "REMOVE_PAGE_WIDGET", LOAD_API_RESPONSE: "LOAD_API_RESPONSE", LOAD_QUERY_RESPONSE: "LOAD_QUERY_RESPONSE", + RUN_ACTION_INIT: "RUN_ACTION_INIT", RUN_ACTION_REQUEST: "RUN_ACTION_REQUEST", RUN_ACTION_SUCCESS: "RUN_ACTION_SUCCESS", INIT_API_PANE: "INIT_API_PANE", @@ -59,6 +60,9 @@ export const ReduxActionTypes: { [key: string]: string } = { UPDATE_ACTION_SUCCESS: "UPDATE_ACTION_SUCCESS", DELETE_ACTION_INIT: "DELETE_ACTION_INIT", DELETE_ACTION_SUCCESS: "DELETE_ACTION_SUCCESS", + SHOW_RUN_ACTION_CONFIRM_MODAL: "SHOW_RUN_ACTION_CONFIRM_MODAL", + CANCEL_RUN_ACTION_CONFIRM_MODAL: "CANCEL_RUN_ACTION_CONFIRM_MODAL", + ACCEPT_RUN_ACTION_CONFIRM_MODAL: "ACCEPT_RUN_ACTION_CONFIRM_MODAL", CREATE_QUERY_INIT: "CREATE_QUERY_INIT", FETCH_DATASOURCES_INIT: "FETCH_DATASOURCES_INIT", FETCH_DATASOURCES_SUCCESS: "FETCH_DATASOURCES_SUCCESS", @@ -254,6 +258,10 @@ export const ReduxActionTypes: { [key: string]: string } = { TOGGLE_PROPERTY_PANE_WIDGET_NAME_EDIT: "TOGGLE_PROPERTY_PANE_WIDGET_NAME_EDIT", UPDATE_APP_STORE: "UPDATE_APP_STORE", + SET_ACTION_TO_EXECUTE_ON_PAGELOAD: "SET_ACTION_TO_EXECUTE_ON_PAGELOAD", + TOGGLE_ACTION_EXECUTE_ON_LOAD_SUCCESS: + "TOGGLE_ACTION_EXECUTE_ON_LOAD_SUCCESS", + TOGGLE_ACTION_EXECUTE_ON_LOAD_INIT: "TOGGLE_ACTION_EXECUTE_ON_LOAD_INIT", }; export type ReduxActionType = typeof ReduxActionTypes[keyof typeof ReduxActionTypes]; @@ -344,6 +352,7 @@ export const ReduxActionErrorTypes: { [key: string]: string } = { SAVE_API_NAME_ERROR: "SAVE_API_NAME_ERROR", POPULATE_PAGEDSLS_ERROR: "POPULATE_PAGEDSLS_ERROR", FETCH_PAGE_DSL_ERROR: "FETCH_PAGE_DSL_ERROR", + TOGGLE_ACTION_EXECUTE_ON_LOAD_ERROR: "TOGGLE_ACTION_EXECUTE_ON_LOAD_ERROR", }; export const ReduxFormActionTypes: { [key: string]: string } = { diff --git a/app/client/src/mockResponses/ActionSettings.tsx b/app/client/src/mockResponses/ActionSettings.tsx new file mode 100644 index 0000000000..036ec9cdad --- /dev/null +++ b/app/client/src/mockResponses/ActionSettings.tsx @@ -0,0 +1,41 @@ +export const queryActionSettingsConfig = [ + { + sectionName: "", + id: 1, + children: [ + { + label: "Run query on Page load", + configProperty: "executeOnLoad", + controlType: "SWITCH", + info: "Will refresh data everytime page is reloaded", + }, + // { + // label: "Request confirmation before running query", + // configProperty: "requestConfirmation", + // controlType: "SWITCH", + // info: "Ask confirmation from the user everytime before refreshing data", + // }, + ], + }, +]; + +export const apiActionSettingsConfig = [ + { + sectionName: "", + id: 1, + children: [ + { + label: "Run api on Page load", + configProperty: "executeOnLoad", + controlType: "SWITCH", + info: "Will refresh data everytime page is reloaded", + }, + // { + // label: "Request confirmation before running api", + // configProperty: "requestConfirmation", + // controlType: "SWITCH", + // info: "Ask confirmation from the user everytime before refreshing data", + // }, + ], + }, +]; diff --git a/app/client/src/pages/Editor/APIEditor/Form.tsx b/app/client/src/pages/Editor/APIEditor/Form.tsx index a7a2ef8501..3bab489594 100644 --- a/app/client/src/pages/Editor/APIEditor/Form.tsx +++ b/app/client/src/pages/Editor/APIEditor/Form.tsx @@ -25,6 +25,8 @@ import EmbeddedDatasourcePathField from "components/editorComponents/form/fields import { AppState } from "reducers"; import { getApiName } from "selectors/formSelectors"; import ActionNameEditor from "components/editorComponents/ActionNameEditor"; +import ActionSettings from "pages/Editor/ActionSettings"; +import { apiActionSettingsConfig } from "mockResponses/ActionSettings"; const Form = styled.form` display: flex; @@ -111,6 +113,13 @@ const RequestParamsWrapper = styled.div` padding-right: 10px; `; +const SettingsWrapper = styled.div` + padding-left: 15px; + ${FormLabel} { + padding: 0px; + } +`; + const HeadersSection = styled.div` margin-bottom: 32px; `; @@ -256,6 +265,17 @@ const ApiEditorForm: React.FC = (props: Props) => { /> ), }, + { + key: "settings", + title: "Settings", + panelComponent: ( + + + + ), + }, ]} /> diff --git a/app/client/src/pages/Editor/APIEditor/index.tsx b/app/client/src/pages/Editor/APIEditor/index.tsx index df8c41a9f0..edd5c3895b 100644 --- a/app/client/src/pages/Editor/APIEditor/index.tsx +++ b/app/client/src/pages/Editor/APIEditor/index.tsx @@ -4,7 +4,7 @@ import { submit } from "redux-form"; import ApiEditorForm from "./Form"; import RapidApiEditorForm from "./RapidApiEditorForm"; import ApiHomeScreen from "./ApiHomeScreen"; -import { runAction, deleteAction } from "actions/actionActions"; +import { deleteAction, runActionInit } from "actions/actionActions"; import { PaginationField } from "api/ActionAPI"; import { AppState } from "reducers"; import { RouteComponentProps } from "react-router"; @@ -234,7 +234,7 @@ const mapStateToProps = (state: AppState, props: any): ReduxStateProps => { const mapDispatchToProps = (dispatch: any): ReduxActionProps => ({ submitForm: (name: string) => dispatch(submit(name)), runAction: (id: string, paginationField?: PaginationField) => - dispatch(runAction(id, paginationField)), + dispatch(runActionInit(id, paginationField)), deleteAction: (id: string, name: string) => dispatch(deleteAction({ id, name })), changeAPIPage: (actionId: string) => dispatch(changeApi(actionId)), diff --git a/app/client/src/pages/Editor/ActionSettings.tsx b/app/client/src/pages/Editor/ActionSettings.tsx new file mode 100644 index 0000000000..9c18442013 --- /dev/null +++ b/app/client/src/pages/Editor/ActionSettings.tsx @@ -0,0 +1,37 @@ +import React from "react"; +import FormControlFactory from "utils/FormControlFactory"; +import { ControlProps } from "components/formControls/BaseControl"; + +interface ActionSettingsProps { + actionSettingsConfig: any; +} + +const ActionSettings = (props: ActionSettingsProps): JSX.Element => { + return <>{props.actionSettingsConfig.map(renderEachConfig)}; +}; + +const renderEachConfig = (section: any): any => { + return section.children.map((propertyControlOrSection: ControlProps) => { + if ("children" in propertyControlOrSection) { + return renderEachConfig(propertyControlOrSection); + } else { + try { + const { configProperty } = propertyControlOrSection; + return ( +
+ {FormControlFactory.createControl( + { ...propertyControlOrSection }, + {}, + false, + )} +
+ ); + } catch (e) { + console.log(e); + } + } + return null; + }); +}; + +export default ActionSettings; diff --git a/app/client/src/pages/Editor/ConfirmRunModal.tsx b/app/client/src/pages/Editor/ConfirmRunModal.tsx new file mode 100644 index 0000000000..ce4aa9facf --- /dev/null +++ b/app/client/src/pages/Editor/ConfirmRunModal.tsx @@ -0,0 +1,61 @@ +import React from "react"; +import { connect } from "react-redux"; +import { AppState } from "reducers"; +import { Dialog, Classes } from "@blueprintjs/core"; +import Button from "components/editorComponents/Button"; +import { + showRunActionConfirmModal, + cancelRunActionConfirmModal, + acceptRunActionConfirmModal, +} from "actions/actionActions"; + +type Props = { + isModalOpen: boolean; + dispatch: any; +}; + +class ConfirmRunModal extends React.Component { + render() { + const { dispatch, isModalOpen } = this.props; + const handleClose = () => { + dispatch(showRunActionConfirmModal(false)); + }; + + return ( + +
+ Are you sure you want to refresh your current data +
+
+
+
+
+
+ ); + } +} + +const mapStateToProps = (state: AppState) => ({ + isModalOpen: state.ui.confirmRunAction.modalOpen, +}); + +export default connect(mapStateToProps)(ConfirmRunModal); diff --git a/app/client/src/pages/Editor/QueryEditor/Form.tsx b/app/client/src/pages/Editor/QueryEditor/Form.tsx index 1ef28060c3..a0c62c57e8 100644 --- a/app/client/src/pages/Editor/QueryEditor/Form.tsx +++ b/app/client/src/pages/Editor/QueryEditor/Form.tsx @@ -1,10 +1,5 @@ import React from "react"; -import { - formValueSelector, - InjectedFormProps, - reduxForm, - Field, -} from "redux-form"; +import { formValueSelector, InjectedFormProps, reduxForm } from "redux-form"; import styled, { createGlobalStyle } from "styled-components"; import { Icon, Popover, Spinner, Tag } from "@blueprintjs/core"; import { @@ -22,6 +17,7 @@ import FormRow from "components/editorComponents/FormRow"; import DropdownField from "components/editorComponents/form/fields/DropdownField"; import { BaseButton } from "components/designSystems/blueprint/ButtonComponent"; import { Datasource } from "api/DatasourcesApi"; +import { BaseTabbedView } from "components/designSystems/appsmith/TabbedView"; import { QUERY_EDITOR_FORM_NAME } from "constants/forms"; import { Colors } from "constants/Colors"; import JSONViewer from "./JSONViewer"; @@ -39,7 +35,8 @@ import { import FormControlFactory from "utils/FormControlFactory"; import { ControlProps } from "components/formControls/BaseControl"; import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper"; -import { SwitchField } from "components/formControls/SwitchControl"; +import ActionSettings from "pages/Editor/ActionSettings"; +import { queryActionSettingsConfig } from "mockResponses/ActionSettings"; const QueryFormContainer = styled.div` padding: 20px 32px; @@ -216,6 +213,22 @@ const LoadingContainer = styled(CenteredWrapper)` height: 50%; `; +const TabContainerView = styled.div` + height: calc(100vh / 3); + + .react-tabs__tab-panel { + border: 1px solid #ebeff2; + } + .react-tabs__tab-list { + margin: 0px; + } +`; + +const SettingsWrapper = styled.div` + padding-left: 15px; + padding-top: 8px; +`; + type QueryFormProps = { onDeleteClick: () => void; onRunClick: () => void; @@ -442,29 +455,44 @@ const QueryEditorForm: React.FC = (props: Props) => { )} - {editorConfig && editorConfig.length > 0 ? ( - editorConfig.map(renderEachConfig) - ) : ( - <> - An unexpected error occurred - window.location.reload()} - > - Refresh - - - )} -
- + 0 ? ( + editorConfig.map(renderEachConfig) + ) : ( + <> + An unexpected error occurred + window.location.reload()} + > + Refresh + + + ), + }, + { + key: "settings", + title: "Settings", + panelComponent: ( + + + + ), + }, + ]} /> -
+ {dataSources.length === 0 && ( diff --git a/app/client/src/pages/Editor/QueryEditor/TemplateMenu.tsx b/app/client/src/pages/Editor/QueryEditor/TemplateMenu.tsx index 56b5bf1220..ba3b059b70 100644 --- a/app/client/src/pages/Editor/QueryEditor/TemplateMenu.tsx +++ b/app/client/src/pages/Editor/QueryEditor/TemplateMenu.tsx @@ -6,11 +6,9 @@ import { getPluginTemplates } from "selectors/entitiesSelector"; const Container = styled.div` display: flex; - height: 185px; padding: 16px 24px; flex: 1; border-radius: 4px; - border: 1px solid #d0d7dd; flex-direction: column; color: #4e5d78; `; diff --git a/app/client/src/pages/Editor/QueryEditor/index.tsx b/app/client/src/pages/Editor/QueryEditor/index.tsx index bb33cd5e2a..ac86fcad16 100644 --- a/app/client/src/pages/Editor/QueryEditor/index.tsx +++ b/app/client/src/pages/Editor/QueryEditor/index.tsx @@ -6,7 +6,7 @@ import styled from "styled-components"; import { QueryEditorRouteParams } from "constants/routes"; import QueryEditorForm from "./Form"; import QueryHomeScreen from "./QueryHomeScreen"; -import { deleteAction, runAction } from "actions/actionActions"; +import { deleteAction, runActionInit } from "actions/actionActions"; import { AppState } from "reducers"; import { getIsEditorInitialized } from "selectors/editorSelectors"; import { QUERY_EDITOR_FORM_NAME } from "constants/forms"; @@ -187,7 +187,7 @@ const mapStateToProps = (state: AppState, props: any): ReduxStateProps => { const mapDispatchToProps = (dispatch: any): ReduxDispatchProps => ({ deleteAction: (id: string, name: string) => dispatch(deleteAction({ id, name })), - runAction: (actionId: string) => dispatch(runAction(actionId)), + runAction: (actionId: string) => dispatch(runActionInit(actionId)), changeQueryPage: (queryId: string) => { dispatch(changeQuery(queryId)); }, diff --git a/app/client/src/pages/Editor/index.tsx b/app/client/src/pages/Editor/index.tsx index 247f795972..6f46282c0c 100644 --- a/app/client/src/pages/Editor/index.tsx +++ b/app/client/src/pages/Editor/index.tsx @@ -39,6 +39,7 @@ import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper"; import { getAppsmithConfigs } from "configs"; import { getCurrentUser } from "selectors/usersSelectors"; import { User } from "constants/userConstants"; +import ConfirmRunModal from "pages/Editor/ConfirmRunModal"; const { cloudHosting, intercomAppID } = getAppsmithConfigs(); @@ -192,6 +193,7 @@ class Editor extends Component { + ); } diff --git a/app/client/src/reducers/entityReducers/actionsReducer.tsx b/app/client/src/reducers/entityReducers/actionsReducer.tsx index 34cc07fc26..0871b8d006 100644 --- a/app/client/src/reducers/entityReducers/actionsReducer.tsx +++ b/app/client/src/reducers/entityReducers/actionsReducer.tsx @@ -9,6 +9,8 @@ import { ExecuteErrorPayload } from "constants/ActionConstants"; import _ from "lodash"; import { RapidApiAction, RestAction } from "entities/Action"; import { UpdateActionPropertyActionPayload } from "actions/actionActions"; +import produce from "immer"; + export interface ActionData { isLoading: boolean; config: RestAction | RapidApiAction; @@ -294,6 +296,18 @@ const actionsReducer = createReducer(initialState, { return true; }), + [ReduxActionTypes.SET_ACTION_TO_EXECUTE_ON_PAGELOAD]: ( + state: ActionDataState, + actionIds: ReduxAction, + ) => { + return produce(state, draft => { + draft.forEach((action, index) => { + if (actionIds.payload.indexOf(action.config.id) > -1) { + draft[index].config.executeOnLoad = true; + } + }); + }); + }, }); export default actionsReducer; diff --git a/app/client/src/reducers/index.tsx b/app/client/src/reducers/index.tsx index e6e9a94634..22098afbf7 100644 --- a/app/client/src/reducers/index.tsx +++ b/app/client/src/reducers/index.tsx @@ -30,6 +30,7 @@ import { HelpReduxState } from "./uiReducers/helpReducer"; import { ApiNameReduxState } from "./uiReducers/apiNameReducer"; import { ExplorerReduxState } from "./uiReducers/explorerReducer"; import { PageDSLsReduxState } from "./uiReducers/pageDSLReducer"; +import { ConfirmRunActionReduxState } from "./uiReducers/confirmRunActionReducer"; import { AppDataState } from "@appsmith/reducers/entityReducers/appReducer"; import { DatasourceNameReduxState } from "./uiReducers/datasourceNameReducer"; @@ -63,6 +64,7 @@ export interface AppState { apiName: ApiNameReduxState; explorer: ExplorerReduxState; pageDSLs: PageDSLsReduxState; + confirmRunAction: ConfirmRunActionReduxState; datasourceName: DatasourceNameReduxState; }; entities: { diff --git a/app/client/src/reducers/uiReducers/confirmRunActionReducer.ts b/app/client/src/reducers/uiReducers/confirmRunActionReducer.ts new file mode 100644 index 0000000000..96c4a6bd6b --- /dev/null +++ b/app/client/src/reducers/uiReducers/confirmRunActionReducer.ts @@ -0,0 +1,21 @@ +import { createReducer } from "utils/AppsmithUtils"; +import { ReduxAction, ReduxActionTypes } from "constants/ReduxActionConstants"; + +const initialState: ConfirmRunActionReduxState = { + modalOpen: false, +}; + +const confirmRunActionReducer = createReducer(initialState, { + [ReduxActionTypes.SHOW_RUN_ACTION_CONFIRM_MODAL]: ( + state: ConfirmRunActionReduxState, + action: ReduxAction, + ) => { + return { ...state, modalOpen: action.payload }; + }, +}); + +export interface ConfirmRunActionReduxState { + modalOpen: boolean; +} + +export default confirmRunActionReducer; diff --git a/app/client/src/reducers/uiReducers/index.tsx b/app/client/src/reducers/uiReducers/index.tsx index f3f4926a84..7d29af31d2 100644 --- a/app/client/src/reducers/uiReducers/index.tsx +++ b/app/client/src/reducers/uiReducers/index.tsx @@ -19,6 +19,7 @@ import helpReducer from "./helpReducer"; import apiNameReducer from "./apiNameReducer"; import explorerReducer from "./explorerReducer"; import pageDSLsReducer from "./pageDSLReducer"; +import confirmRunActionReducer from "./confirmRunActionReducer"; import datasourceNameReducer from "./datasourceNameReducer"; const uiReducer = combineReducers({ @@ -43,5 +44,6 @@ const uiReducer = combineReducers({ apiName: apiNameReducer, explorer: explorerReducer, pageDSLs: pageDSLsReducer, + confirmRunAction: confirmRunActionReducer, }); export default uiReducer; diff --git a/app/client/src/sagas/ActionExecutionSagas.ts b/app/client/src/sagas/ActionExecutionSagas.ts index c245dd8716..75436caf73 100644 --- a/app/client/src/sagas/ActionExecutionSagas.ts +++ b/app/client/src/sagas/ActionExecutionSagas.ts @@ -19,6 +19,7 @@ import { take, takeEvery, takeLatest, + race, } from "redux-saga/effects"; import { evaluateDataTreeWithFunctions, @@ -50,6 +51,7 @@ import { executeApiActionRequest, executeApiActionSuccess, updateAction, + showRunActionConfirmModal, } from "actions/actionActions"; import { Action, RestAction } from "entities/Action"; import ActionAPI, { @@ -500,6 +502,35 @@ function* runActionSaga( } } +function* confirmRunActionSaga( + reduxAction: ReduxAction<{ + id: string; + paginationField: PaginationField; + }>, +) { + const action = yield select(getAction, reduxAction.payload.id); + if (action.requestConfirmation) { + yield put(showRunActionConfirmModal(true)); + + const { accept } = yield race({ + cancel: take(ReduxActionTypes.CANCEL_RUN_ACTION_CONFIRM_MODAL), + accept: take(ReduxActionTypes.ACCEPT_RUN_ACTION_CONFIRM_MODAL), + }); + + if (accept) { + yield put({ + type: ReduxActionTypes.RUN_ACTION_REQUEST, + payload: reduxAction.payload, + }); + } + } else { + yield put({ + type: ReduxActionTypes.RUN_ACTION_REQUEST, + payload: reduxAction.payload, + }); + } +} + function* executePageLoadAction(pageAction: PageAction) { yield put(executeApiActionRequest({ id: pageAction.id })); const params: Property[] = yield call( @@ -545,6 +576,7 @@ export function* watchActionExecutionSagas() { yield all([ takeEvery(ReduxActionTypes.EXECUTE_ACTION, executeAppAction), takeLatest(ReduxActionTypes.RUN_ACTION_REQUEST, runActionSaga), + takeLatest(ReduxActionTypes.RUN_ACTION_INIT, confirmRunActionSaga), takeLatest( ReduxActionTypes.EXECUTE_PAGE_LOAD_ACTIONS, executePageLoadActionsSaga, diff --git a/app/client/src/sagas/ActionSagas.ts b/app/client/src/sagas/ActionSagas.ts index 249637b6fd..8773ce6fd1 100644 --- a/app/client/src/sagas/ActionSagas.ts +++ b/app/client/src/sagas/ActionSagas.ts @@ -442,6 +442,7 @@ function* setActionPropertySaga(action: ReduxAction) { const { actionId, value, propertyName } = action.payload; if (!actionId) return; if (propertyName === "name") return; + const actionObj = yield select(getAction, actionId); const effects: Record = {}; // Value change effect @@ -457,9 +458,42 @@ function* setActionPropertySaga(action: ReduxAction) { put(updateActionProperty({ id: actionId, field, value: effects[field] })), ), ); + if (propertyName === "executeOnLoad") { + yield put({ + type: ReduxActionTypes.TOGGLE_ACTION_EXECUTE_ON_LOAD_INIT, + payload: { + actionId, + shouldExecute: value, + }, + }); + return; + } yield put(updateAction({ id: actionId })); } +function* toggleActionExecuteOnLoadSaga( + action: ReduxAction<{ actionId: string; shouldExecute: boolean }>, +) { + try { + const response = yield call( + ActionAPI.toggleActionExecuteOnLoad, + action.payload.actionId, + action.payload.shouldExecute, + ); + const isValidResponse = yield validateResponse(response); + if (isValidResponse) { + yield put({ + type: ReduxActionTypes.TOGGLE_ACTION_EXECUTE_ON_LOAD_SUCCESS, + }); + } + } catch (error) { + yield put({ + type: ReduxActionErrorTypes.TOGGLE_ACTION_EXECUTE_ON_LOAD_ERROR, + payload: error, + }); + } +} + function* handleMoveOrCopySaga(actionPayload: ReduxAction<{ id: string }>) { const { id } = actionPayload.payload; const action: Action = yield select(getAction, id); @@ -499,5 +533,9 @@ export function* watchActionSagas() { takeEvery(ReduxActionTypes.COPY_ACTION_SUCCESS, handleMoveOrCopySaga), takeEvery(ReduxActionErrorTypes.MOVE_ACTION_ERROR, handleMoveOrCopySaga), takeEvery(ReduxActionErrorTypes.COPY_ACTION_ERROR, handleMoveOrCopySaga), + takeLatest( + ReduxActionTypes.TOGGLE_ACTION_EXECUTE_ON_LOAD_INIT, + toggleActionExecuteOnLoadSaga, + ), ]); } diff --git a/app/client/src/sagas/PageSagas.tsx b/app/client/src/sagas/PageSagas.tsx index 6e09af77de..a80fdbce93 100644 --- a/app/client/src/sagas/PageSagas.tsx +++ b/app/client/src/sagas/PageSagas.tsx @@ -65,7 +65,10 @@ import { getCurrentPageId, getCurrentPageName, } from "selectors/editorSelectors"; -import { fetchActionsForPage } from "actions/actionActions"; +import { + fetchActionsForPage, + setActionsToExecuteOnPageLoad, +} from "actions/actionActions"; import { clearCaches } from "utils/DynamicBindingUtils"; import { UrlDataState } from "reducers/entityReducers/appReducer"; import { getQueryParams } from "utils/AppsmithUtils"; @@ -245,6 +248,14 @@ function* savePageSaga() { ); const isValidResponse = yield validateResponse(savePageResponse); if (isValidResponse) { + if ( + savePageResponse.data.layoutOnLoadActions && + savePageResponse.data.layoutOnLoadActions.length > 0 + ) { + for (const actionSet of savePageResponse.data.layoutOnLoadActions) { + yield put(setActionsToExecuteOnPageLoad(actionSet.map(a => a.id))); + } + } yield put(savePageSuccess(savePageResponse)); } } catch (error) { diff --git a/app/client/yarn.lock b/app/client/yarn.lock index 2407202f4b..e465a816c1 100644 --- a/app/client/yarn.lock +++ b/app/client/yarn.lock @@ -9258,6 +9258,11 @@ immer@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/immer/-/immer-1.10.0.tgz#bad67605ba9c810275d91e1c2a47d4582e98286d" +immer@^7.0.8: + version "7.0.8" + resolved "https://registry.yarnpkg.com/immer/-/immer-7.0.8.tgz#41dcbc5669a76500d017bef3ad0d03ce0a1d7c1e" + integrity sha512-XnpIN8PXBBaOD43U8Z17qg6RQiKQYGDGGCIbz1ixmLGwBkSWwmrmx5X7d+hTtXDM8ur7m5OdLE0PiO+y5RB3pw== + immutable@3.8.2: version "3.8.2" resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3"