From dbfd986d0e11df3b40d9be4cb73d39689849ed27 Mon Sep 17 00:00:00 2001 From: akash-codemonk <67054171+akash-codemonk@users.noreply.github.com> Date: Thu, 27 Aug 2020 21:09:16 +0530 Subject: [PATCH] Add action settings tab to api and query pane (#434) * Add action settings tab to api and query pane - Ask for confirmation before running an action * Update property of actions basedon the updateLayout response Prevent confirmation dialog for Action run, until property of action is true Send an API Request when the user toggles the property of an Action * update http method to toggle executeOnLoad for an action to PUT * Fix save layout response type * Remove console.log * If updating executeOnLoad, avoid calling update action API Co-authored-by: Abhinav Jha --- app/client/package.json | 1 + app/client/src/actions/actionActions.ts | 39 +++++++++ app/client/src/api/ActionAPI.tsx | 6 ++ app/client/src/api/PageApi.tsx | 6 +- .../formControls/DynamicTextFieldControl.tsx | 2 +- .../components/formControls/SwitchControl.tsx | 35 +++++--- .../src/constants/ReduxActionConstants.tsx | 9 ++ .../src/mockResponses/ActionSettings.tsx | 41 +++++++++ .../src/pages/Editor/APIEditor/Form.tsx | 20 +++++ .../src/pages/Editor/APIEditor/index.tsx | 4 +- .../src/pages/Editor/ActionSettings.tsx | 37 ++++++++ .../src/pages/Editor/ConfirmRunModal.tsx | 61 +++++++++++++ .../src/pages/Editor/QueryEditor/Form.tsx | 86 ++++++++++++------- .../pages/Editor/QueryEditor/TemplateMenu.tsx | 2 - .../src/pages/Editor/QueryEditor/index.tsx | 4 +- app/client/src/pages/Editor/index.tsx | 2 + .../entityReducers/actionsReducer.tsx | 14 +++ app/client/src/reducers/index.tsx | 2 + .../uiReducers/confirmRunActionReducer.ts | 21 +++++ app/client/src/reducers/uiReducers/index.tsx | 2 + app/client/src/sagas/ActionExecutionSagas.ts | 32 +++++++ app/client/src/sagas/ActionSagas.ts | 38 ++++++++ app/client/src/sagas/PageSagas.tsx | 13 ++- app/client/yarn.lock | 5 ++ 24 files changed, 432 insertions(+), 50 deletions(-) create mode 100644 app/client/src/mockResponses/ActionSettings.tsx create mode 100644 app/client/src/pages/Editor/ActionSettings.tsx create mode 100644 app/client/src/pages/Editor/ConfirmRunModal.tsx create mode 100644 app/client/src/reducers/uiReducers/confirmRunActionReducer.ts 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"