Api Dry Run

This commit is contained in:
Hetu Nandu 2019-11-12 09:43:13 +00:00
parent 3dd7713aac
commit c9914c4246
10 changed files with 92 additions and 23 deletions

View File

@ -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,

View File

@ -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<T> extends APIRequest {
datasourceId: string;
@ -51,14 +52,14 @@ export interface ActionCreateUpdateResponse extends ApiResponse {
export interface RestAction {
id: string;
name: string;
datasourceId: string;
datasource: Pick<Datasource, "id"> | Omit<Datasource, "id">;
pluginId: string;
pageId?: string;
actionConfiguration: Partial<APIConfigRequest>;
}
export interface ExecuteActionRequest extends APIRequest {
actionId: string;
action: Pick<RestAction, "id"> | Omit<RestAction, "id">;
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<string, string[]>;
statusCode: string | number;
};
clientMeta: {
duration: string;
size: string;
};
}
export interface ActionResponse {
body: object;
headers: Record<string, string[]>;
statusCode: string;
statusCode: string | number;
duration: string;
size: string;
}

View File

@ -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(

View File

@ -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;
}

View File

@ -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",

View File

@ -137,7 +137,7 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
name="actionConfiguration.httpMethod"
options={HTTP_METHOD_OPTIONS}
/>
<DatasourcesField name="datasourceId" />
<DatasourcesField name="datasource.id" />
<ForwardSlash />
<TextField
placeholderMessage="API Path"

View File

@ -7,6 +7,7 @@ import {
executeAction,
deleteAction,
updateAction,
dryRunAction,
} from "../../actions/actionActions";
import { RestAction } from "../../api/ActionAPI";
import { AppState } from "../../reducers";
@ -24,6 +25,7 @@ interface ReduxActionProps {
submitForm: (name: string) => 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<RestAction>) => void;
@ -78,7 +80,12 @@ class ApiEditor extends React.Component<Props> {
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<RestAction>) =>

View File

@ -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 }),
});

View File

@ -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<ActionPayload[]>) {
}
}
function* dryRunActionSaga(action: ReduxAction<RestAction>) {
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<RestAction>) {
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),
]);
}

View File

@ -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,