From c9914c424634c0eda1c6f99f6413fe982eecc4b3 Mon Sep 17 00:00:00 2001 From: Hetu Nandu Date: Tue, 12 Nov 2019 09:43:13 +0000 Subject: [PATCH] Api Dry Run --- app/client/src/actions/actionActions.ts | 7 +++ app/client/src/api/ActionAPI.tsx | 23 +++++++-- app/client/src/api/Api.tsx | 9 ++-- .../editorComponents/ApiResponseView.tsx | 4 +- .../src/constants/ReduxActionConstants.tsx | 1 + .../pages/Editor/APIEditor/ApiEditorForm.tsx | 2 +- app/client/src/pages/Editor/ApiEditor.tsx | 10 +++- .../entityReducers/apiDataReducer.tsx | 6 +-- app/client/src/sagas/ActionSagas.ts | 51 ++++++++++++++++--- app/client/src/sagas/DatasourcesSagas.ts | 2 +- 10 files changed, 92 insertions(+), 23 deletions(-) diff --git a/app/client/src/actions/actionActions.ts b/app/client/src/actions/actionActions.ts index 3ec32ee13c..122c3d8681 100644 --- a/app/client/src/actions/actionActions.ts +++ b/app/client/src/actions/actionActions.ts @@ -57,6 +57,13 @@ export const deleteActionSuccess = (payload: { id: string }) => { }; }; +export const dryRunAction = (payload: RestAction) => { + return { + type: ReduxActionTypes.DRY_RUN_ACTION, + payload, + }; +}; + export default { createAction: createActionRequest, fetchActions, diff --git a/app/client/src/api/ActionAPI.tsx b/app/client/src/api/ActionAPI.tsx index f0181fb82b..b9d17e43e8 100644 --- a/app/client/src/api/ActionAPI.tsx +++ b/app/client/src/api/ActionAPI.tsx @@ -2,6 +2,7 @@ import API, { HttpMethod } from "./Api"; import { ApiResponse, GenericApiResponse, ResponseMeta } from "./ApiResponses"; import { APIRequest } from "../constants/ApiConstants"; import { AxiosPromise } from "axios"; +import { Datasource } from "./DatasourcesApi"; export interface CreateActionRequest extends APIRequest { datasourceId: string; @@ -51,14 +52,14 @@ export interface ActionCreateUpdateResponse extends ApiResponse { export interface RestAction { id: string; name: string; - datasourceId: string; + datasource: Pick | Omit; pluginId: string; pageId?: string; actionConfiguration: Partial; } export interface ExecuteActionRequest extends APIRequest { - actionId: string; + action: Pick | Omit; params?: Property[]; } @@ -68,10 +69,22 @@ export interface ExecuteActionResponse extends ApiResponse { } export interface ActionApiResponse { - responseMeta?: ResponseMeta; - body: JSON; + responseMeta: ResponseMeta; + data: { + body: object; + headers: Record; + statusCode: string | number; + }; + clientMeta: { + duration: string; + size: string; + }; +} + +export interface ActionResponse { + body: object; headers: Record; - statusCode: string; + statusCode: string | number; duration: string; size: string; } diff --git a/app/client/src/api/Api.tsx b/app/client/src/api/Api.tsx index 6adef3ae93..036dc81fd6 100644 --- a/app/client/src/api/Api.tsx +++ b/app/client/src/api/Api.tsx @@ -6,6 +6,7 @@ import { REQUEST_HEADERS, AUTH_CREDENTIALS, } from "../constants/ApiConstants"; +import { ActionApiResponse, ActionResponse } from "./ActionAPI"; const axiosInstance = axios.create({ baseURL: BASE_URL, @@ -20,10 +21,12 @@ axiosInstance.interceptors.request.use((config: any) => { return { ...config, timer: performance.now() }; }); -const makeExecuteActionResponse = (response: any) => ({ +const makeExecuteActionResponse = (response: any): ActionApiResponse => ({ ...response.data, - size: response.headers["content-length"], - duration: Number(performance.now() - response.config.timer).toFixed(), + clientMeta: { + size: response.headers["content-length"], + duration: Number(performance.now() - response.config.timer).toFixed(), + }, }); axiosInstance.interceptors.response.use( diff --git a/app/client/src/components/editorComponents/ApiResponseView.tsx b/app/client/src/components/editorComponents/ApiResponseView.tsx index 043dc2c2ff..a497f4d002 100644 --- a/app/client/src/components/editorComponents/ApiResponseView.tsx +++ b/app/client/src/components/editorComponents/ApiResponseView.tsx @@ -7,7 +7,7 @@ import { BaseTabbedView } from "../designSystems/appsmith/TabbedView"; import styled from "styled-components"; import { AppState } from "../../reducers"; import CodeEditor from "./CodeEditor"; -import { ActionApiResponse } from "../../api/ActionAPI"; +import { ActionResponse } from "../../api/ActionAPI"; import { formatBytes } from "../../utils/helpers"; const ResponseWrapper = styled.div` @@ -62,7 +62,7 @@ const LoadingScreen = styled.div` interface ReduxStateProps { responses: { - [id: string]: ActionApiResponse; + [id: string]: ActionResponse; }; isRunning: boolean; } diff --git a/app/client/src/constants/ReduxActionConstants.tsx b/app/client/src/constants/ReduxActionConstants.tsx index 47049c1d14..8fa0ae98bd 100644 --- a/app/client/src/constants/ReduxActionConstants.tsx +++ b/app/client/src/constants/ReduxActionConstants.tsx @@ -53,6 +53,7 @@ export const ReduxActionTypes: { [key: string]: string } = { UPDATE_ACTION_SUCCESS: "UPDATE_ACTION_SUCCESS", DELETE_ACTION_INIT: "DELETE_ACTION_INIT", DELETE_ACTION_SUCCESS: "DELETE_ACTION_SUCCESS", + DRY_RUN_ACTION: "DRY_RUN_ACTION", FETCH_DATASOURCES_INIT: "FETCH_DATASOURCES_INIT", FETCH_DATASOURCES_SUCCESS: "FETCH_DATASOURCES_SUCCESS", CREATE_DATASOURCE_INIT: "CREATE_DATASOURCE_INIT", diff --git a/app/client/src/pages/Editor/APIEditor/ApiEditorForm.tsx b/app/client/src/pages/Editor/APIEditor/ApiEditorForm.tsx index f4665de9f3..10ddecd5aa 100644 --- a/app/client/src/pages/Editor/APIEditor/ApiEditorForm.tsx +++ b/app/client/src/pages/Editor/APIEditor/ApiEditorForm.tsx @@ -137,7 +137,7 @@ const ApiEditorForm: React.FC = (props: Props) => { name="actionConfiguration.httpMethod" options={HTTP_METHOD_OPTIONS} /> - + void; createAction: (values: RestAction) => void; runAction: (id: string) => void; + dryRunAction: (data: RestAction) => void; deleteAction: (id: string) => void; updateAction: (data: RestAction) => void; initialize: (formName: string, data?: Partial) => void; @@ -78,7 +80,12 @@ class ApiEditor extends React.Component { this.props.deleteAction(this.props.match.params.id); }; handleRunClick = () => { - this.props.runAction(this.props.match.params.id); + const { formData } = this.props; + if (formData.id) { + this.props.runAction(this.props.match.params.id); + } else { + this.props.dryRunAction(formData); + } }; render() { @@ -117,6 +124,7 @@ const mapDispatchToProps = (dispatch: any): ReduxActionProps => ({ }, ]), ), + dryRunAction: (data: RestAction) => dispatch(dryRunAction(data)), deleteAction: (id: string) => dispatch(deleteAction({ id })), updateAction: (data: RestAction) => dispatch(updateAction({ data })), initialize: (formName: string, data?: Partial) => diff --git a/app/client/src/reducers/entityReducers/apiDataReducer.tsx b/app/client/src/reducers/entityReducers/apiDataReducer.tsx index bd07199142..24d1b4f05e 100644 --- a/app/client/src/reducers/entityReducers/apiDataReducer.tsx +++ b/app/client/src/reducers/entityReducers/apiDataReducer.tsx @@ -3,19 +3,19 @@ import { ReduxActionTypes, ReduxAction, } from "../../constants/ReduxActionConstants"; -import { ActionApiResponse } from "../../api/ActionAPI"; +import { ActionResponse } from "../../api/ActionAPI"; import { ActionDataState } from "./actionsReducer"; const initialState: APIDataState = {}; export interface APIDataState { - [id: string]: ActionApiResponse; + [id: string]: ActionResponse; } const apiDataReducer = createReducer(initialState, { [ReduxActionTypes.EXECUTE_ACTION_SUCCESS]: ( state: ActionDataState, - action: ReduxAction<{ [id: string]: ActionApiResponse }>, + action: ReduxAction<{ [id: string]: ActionResponse }>, ) => ({ ...state, ...action.payload }), }); diff --git a/app/client/src/sagas/ActionSagas.ts b/app/client/src/sagas/ActionSagas.ts index c8655221f0..51a272fd51 100644 --- a/app/client/src/sagas/ActionSagas.ts +++ b/app/client/src/sagas/ActionSagas.ts @@ -16,6 +16,7 @@ import { ActionPayload, PageAction } from "../constants/ActionConstants"; import ActionAPI, { ActionApiResponse, ActionCreateUpdateResponse, + ActionResponse, ExecuteActionRequest, RestAction, } from "../api/ActionAPI"; @@ -45,6 +46,23 @@ const getAction = ( return _.find(state.entities.actions.data, { id: actionId }); }; +const createActionResponse = (response: ActionApiResponse): ActionResponse => ({ + ...response.data, + ...response.clientMeta, +}); + +const createActionErrorResponse = ( + response: ActionApiResponse, +): ActionResponse => ({ + body: response.responseMeta.error || { error: "Error" }, + statusCode: response.responseMeta.error + ? response.responseMeta.error.code + : "Error", + headers: {}, + duration: "0", + size: "0", +}); + export function* evaluateJSONPathSaga(path: string): any { const dataTree = yield select(getDataTree); return getDynamicBoundValue(dataTree, path); @@ -54,7 +72,9 @@ export function* executeAPIQueryActionSaga(apiAction: ActionPayload) { const api: PageAction = yield select(getAction, apiAction.actionId); const executeActionRequest: ExecuteActionRequest = { - actionId: apiAction.actionId, + action: { + id: apiAction.actionId, + }, }; if (!_.isNil(api.jsonPathKeys)) { const values: any = _.flatten( @@ -73,13 +93,9 @@ export function* executeAPIQueryActionSaga(apiAction: ActionPayload) { const response: ActionApiResponse = yield ActionAPI.executeAction( executeActionRequest, ); - let payload = response; + let payload = createActionResponse(response); if (response.responseMeta && response.responseMeta.error) { - payload = { - body: response.responseMeta.error, - statusCode: response.responseMeta.error.code, - ...response, - }; + payload = createActionErrorResponse(response); if (apiAction.onError) { yield put({ type: ReduxActionTypes.EXECUTE_ACTION, @@ -127,6 +143,26 @@ export function* executeReduxActionSaga(action: ReduxAction) { } } +function* dryRunActionSaga(action: ReduxAction) { + const executeActionRequest: ExecuteActionRequest = { + action: { + ...action.payload, + }, + }; + // TODO(hetu): No support for dynamic bindings in dry runs yet + const response: ActionApiResponse = yield ActionAPI.executeAction( + executeActionRequest, + ); + let payload = createActionResponse(response); + if (response.responseMeta && response.responseMeta.error) { + payload = createActionErrorResponse(response); + } + yield put({ + type: ReduxActionTypes.EXECUTE_ACTION_SUCCESS, + payload: { [action.type]: payload }, + }); +} + export function* createActionSaga(actionPayload: ReduxAction) { const response: ActionCreateUpdateResponse = yield ActionAPI.createAPI( actionPayload.payload, @@ -208,5 +244,6 @@ export function* watchActionSagas() { takeLatest(ReduxActionTypes.CREATE_ACTION_INIT, createActionSaga), takeLatest(ReduxActionTypes.UPDATE_ACTION_INIT, updateActionSaga), takeLatest(ReduxActionTypes.DELETE_ACTION_INIT, deleteActionSaga), + takeLatest(ReduxActionTypes.DRY_RUN_ACTION, dryRunActionSaga), ]); } diff --git a/app/client/src/sagas/DatasourcesSagas.ts b/app/client/src/sagas/DatasourcesSagas.ts index e46d201680..876bf00647 100644 --- a/app/client/src/sagas/DatasourcesSagas.ts +++ b/app/client/src/sagas/DatasourcesSagas.ts @@ -40,7 +40,7 @@ function* createDatasourceSaga( type: ReduxActionTypes.CREATE_DATASOURCE_SUCCESS, payload: response.data, }); - yield put(change(API_EDITOR_FORM_NAME, "datasourceId", response.data.id)); + yield put(change(API_EDITOR_FORM_NAME, "datasource.id", response.data.id)); } else { yield put({ type: ReduxActionTypes.CREATE_DATASOURCES_ERROR,