Skip to content

Commit

Permalink
Request body rewrite (#106)
Browse files Browse the repository at this point in the history
- Adds request body presets
- Sets the default preset to JSON (#103)
- Persists the user's previously selected preset for
  new requests (#88)
- Sets the request body to be expanded by default (#78)
- Fixes the `Content-Type` header not being visible in the
  headers list (#75)
  • Loading branch information
eliihen committed Jul 25, 2017
1 parent 69e77d3 commit 5f64d83
Show file tree
Hide file tree
Showing 20 changed files with 1,045 additions and 1,545 deletions.
3 changes: 1 addition & 2 deletions test/jest.conf.js → jest.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
module.exports = {
"testRegex": "/test/.*\\.test\\.js$",
"rootDir": "..",
"moduleDirectories": ["node_modules", "src/"],
"testPathIgnorePatterns": ["/node_modules/", "/dist/"],
"setupTestFrameworkScriptFile": "./test/setupTestFramework.js",
Expand Down
16 changes: 7 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@
"multiprocess": true
},
"scripts": {
"test": "jest --coverage --config test/jest.conf.js",
"test:watch": "jest --watchman --watchAll --config test/jest.conf.js",
"test:snapshot": "jest --update-snapshot --config test/jest.conf.js",
"test:debugger": "node --debug-brk --inspect ./node_modules/.bin/jest -i --config test/jest.conf.js",
"load": "web-ext run --no-reload",
"test": "jest --coverage",
"test:watch": "jest --watchman --watchAll",
"test:snapshot": "jest --update-snapshot",
"test:debugger": "node --debug-brk --inspect ./node_modules/.bin/jest -i",
"eslint": "eslint src test",
"eslint:fix": "eslint --fix src test",
"start": "npm run dev",
Expand All @@ -42,7 +41,7 @@
"react-immutable-proptypes": "^2.1.0",
"react-redux": "^5.0.3",
"redux": "^3.6.0",
"redux-form": "^6.6.0",
"redux-form": "^7.0.1",
"redux-saga": "^0.14.3",
"reselect": "^3.0.0",
"styled-components": "^1.4.4",
Expand Down Expand Up @@ -72,15 +71,14 @@
"eslint-plugin-jsx-a11y": "^2.2.3",
"eslint-plugin-react": "^6.6.0",
"husky": "^0.13.3",
"jest": "^19.0.2",
"jest": "^20.0.4",
"react-addons-test-utils": "^15.4.2",
"react-dnd-test-backend": "^2.3.0",
"react-test-renderer": "^15.4.2",
"redux-devtools": "^3.3.2",
"redux-devtools-dock-monitor": "^1.1.1",
"redux-devtools-log-monitor": "^1.2.0",
"web-ext": "^1.9.1",
"webpack": "^2.3.2",
"webpack": "^3.4.0",
"whatwg-fetch": "^2.0.3"
}
}
6 changes: 5 additions & 1 deletion src/components/Request/BodyField.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,12 @@ export function BodyField({ bodyType, changeBodyType }) {
);
}

BodyField.defaultProps = {
bodyType: 'json',
};

BodyField.propTypes = {
bodyType: PropTypes.oneOf(['json', 'multipart', 'urlencoded', 'custom']).isRequired,
bodyType: PropTypes.oneOf(['json', 'multipart', 'urlencoded', 'custom']),
changeBodyType: PropTypes.func.isRequired,
};

Expand Down
2 changes: 1 addition & 1 deletion src/components/Request/Titlebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ Titlebar.propTypes = {
removeModal: PropTypes.func.isRequired,
formPristine: PropTypes.bool.isRequired,
formInvalid: PropTypes.bool.isRequired,
collectionsMinimized: PropTypes.bool.isRequired,
collectionsMinimized: PropTypes.bool,
isEditing: PropTypes.bool.isRequired,
editingRequest: PropTypes.shape({
name: PropTypes.string,
Expand Down
6 changes: 5 additions & 1 deletion src/store/config/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import {
TOGGLE_EDIT,
} from './types';

const initialState = {};
const initialState = {
requestBody: {
expanded: true,
},
};

export default function (state = initialState, action) {
switch (action.type) {
Expand Down
10 changes: 9 additions & 1 deletion src/store/options/sagas.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import Immutable from 'immutable';
import localforage from 'localforage';
import { select, put, call, takeEvery } from 'redux-saga/effects';
import { change } from 'redux-form';

import { requestForm } from 'components/Request';

import { FETCH_REQUESTED, UPDATE_REQUESTED, UPDATE_OPTION } from './types';
import { startFetch, receiveOptions } from './actions';
import { getOptions } from './selectors';
import { getOptions, getBodyType } from './selectors';

function* updateLocalStorage() {
const options = (yield select(getOptions)).toJS();
Expand All @@ -22,6 +25,11 @@ function* fetchOptionsSaga() {

options = Immutable.fromJS(options) || Immutable.Map();
yield put(receiveOptions(options));

// The selected bodyType is persisted across reloads and is put into the form
// when we are done loading the initial options
const bodyType = yield select(getBodyType);
yield put(change(requestForm, 'bodyType', bodyType));
}

function* updateOptionSaga({ option, value }) {
Expand Down
5 changes: 5 additions & 0 deletions src/store/options/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ export const getHistorySize = createSelector(
options => options && options.getIn(['options', 'historySize'], 10),
);

export const getBodyType = createSelector(
[getOptions],
options => options && options.getIn(['options', 'bodyType'], 'json'),
);

export const isDisabledHighlighting = createSelector(
[getOptions],
options => options && options.getIn(['options', 'disableHighlighting'], false),
Expand Down
9 changes: 0 additions & 9 deletions src/store/request/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
PUSH_REDIRECT_CHAIN,
RECEIVE_RESPONSE,
CLEAR_RESPONSE,
CHANGE_BODY_TYPE,
REQUEST_FAILED,
} from './types';

Expand All @@ -19,7 +18,6 @@ const initialState = {
redirectChain: [],
lastRequestTime: null,
loading: false,
useFormData: true,
};

export default function (state = initialState, action) {
Expand Down Expand Up @@ -60,13 +58,6 @@ export default function (state = initialState, action) {
error: undefined,
});

case CHANGE_BODY_TYPE:
// Set Content-Type header to application/x-www-form-urlencoded
// Unset Content-Type when set to application/x-www-form-urlencoded
return Object.assign({}, state, {
bodyType: action.bodyType,
});

case REQUEST_FAILED:
return Object.assign({}, state, {
error: action.error,
Expand Down
69 changes: 39 additions & 30 deletions src/store/request/sagas.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import { prependHttp, mapParameters } from 'utils/request';
import { pushHistory } from 'store/history/actions';
import { getUrlVariables } from 'store/urlVariables/selectors';
import { requestForm } from 'components/Request';
import { updateOption } from 'store/options/actions';

import { getPlaceholderUrl, getUseFormData, getHeaders } from './selectors';
import { getPlaceholderUrl, getHeaders } from './selectors';
import { executeRequest, receiveResponse } from './actions';
import { SEND_REQUEST, REQUEST_FAILED, SELECT_REQUESTED, CHANGE_BODY_TYPE } from './types';

Expand Down Expand Up @@ -42,7 +43,7 @@ export function* createResource(request) {
return yield call(prependHttp, resource);
}

export function* buildHeaders({ headers, basicAuth, bodyType }) {
export function* buildHeaders({ headers, basicAuth }) {
const parameters = yield call(getParameters);
const requestHeaders = new Headers(reMapHeaders(headers, parameters));
if (basicAuth && basicAuth.username) {
Expand All @@ -64,7 +65,6 @@ function buildRequestData({ bodyType, formData }) {
formData.forEach(f => {
body.append(f.name, f.value);
});
console.log('body', body);

return body;
}
Expand Down Expand Up @@ -96,6 +96,7 @@ function buildRequestData({ bodyType, formData }) {
default:
return null;
}
return null;
}

// Needed for unit tests to be consistent
Expand Down Expand Up @@ -133,15 +134,15 @@ function createUUID() {
export function* fetchData({ request }) {
try {
yield put(executeRequest());
const useFormData = yield select(getUseFormData);
const bodyType = request.bodyType;

const resource = yield call(createResource, request);
const headers = yield call(buildHeaders, request);

// Build body for requests that support it
let body;
if (!['GET', 'HEAD'].includes(request.method)) {
body = useFormData
body = bodyType !== 'custom'
? buildRequestData(request)
: request.data;
}
Expand Down Expand Up @@ -189,47 +190,55 @@ function* selectRequest({ request }) {
function setContentType(array, value) {
const index = array.findIndex(item => item.name === 'Content-Type');

// Replace any existing Content-Type headers
if (index > -1) {
return [
...array.slice(0, index),
{ name: 'Content-Type', value },
...array.slice(index + 1)
...array.slice(index + 1),
];
} else {
}

// When the last row is empty, overwrite it instead of pushing
const lastItem = array.length >= 1
? array[array.length - 1]
: null;
if (lastItem && !lastItem.name) {
return [
...array,
...array.slice(0, array.length - 1),
{ name: 'Content-Type', value },
];
}

return [
...array,
{ name: 'Content-Type', value },
];
}

// TODO breaks when switching after request is sent
function* setTypeHeaderSaga({ bodyType }) {
try {
let headers = yield select(getHeaders)
console.log('bodyType', bodyType);
console.log('headers', headers);
switch (bodyType) {
case 'multipart':
headers = setContentType(headers, 'multipart/form-data');
break;
case 'urlencoded':
headers = setContentType(headers, 'application/x-www-urlencoded');
break;
case 'json':
headers = setContentType(headers, 'application/json');
break;
}
console.log('headers', headers);
yield put(change(requestForm, 'headers', headers));
} catch (e) {
console.log('e', e);
function* changeBodyTypeSaga({ bodyType }) {
let headers = yield select(getHeaders);
switch (bodyType) {
case 'multipart':
headers = setContentType(headers, 'multipart/form-data');
break;
case 'urlencoded':
headers = setContentType(headers, 'application/x-www-urlencoded');
break;
case 'json':
headers = setContentType(headers, 'application/json');
break;
default:
throw new Error(`Body type ${bodyType} is not supported`);
}
yield put(change(requestForm, 'headers', headers));
// For persistence on load
yield put(updateOption('bodyType', bodyType));
}

export default function* rootSaga() {
yield takeLatest(SEND_REQUEST, fetchData);
yield takeEvery(SELECT_REQUESTED, selectRequest);
yield takeEvery(CHANGE_BODY_TYPE, setTypeHeaderSaga);
yield takeEvery(CHANGE_BODY_TYPE, changeBodyTypeSaga);
}

1 change: 0 additions & 1 deletion src/store/request/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ export const getResponse = state => state.request.response;
export const getInterceptedResponse = state => state.request.interceptedResponse;
export const getRedirectChain = state => state.request.redirectChain;
export const getLoading = state => state.request.loading;
export const getUseFormData = state => state.request.useFormData;

const getValues = getFormValues('request');
export const getBodyType = state => getValues(state).bodyType;
Expand Down
Loading

0 comments on commit 5f64d83

Please sign in to comment.