Photo upload error handling (#57)

* Parse error response for photo upload

* Show profile photo upload errors

* Update test to match new shape of error

* Clearer comments
This commit is contained in:
Adam Butterworth
2019-03-06 12:17:04 -05:00
committed by GitHub
parent f431d41948
commit 7bc2e8f02b
6 changed files with 32 additions and 20 deletions

View File

@@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Container, Row, Col } from 'reactstrap';
import { Container, Row, Col, Alert } from 'reactstrap';
import { connect } from 'react-redux';
import { logEvent } from '../analytics/analytics';
@@ -77,7 +77,7 @@ export class ProfilePage extends React.Component {
profileImage,
username,
dateJoined,
errors,
photoUploadError,
name,
visibilityName,
country,
@@ -99,7 +99,6 @@ export class ProfilePage extends React.Component {
closeHandler: this.handleClose,
submitHandler: this.handleSubmit,
changeHandler: this.handleChange,
errors,
};
return (
@@ -119,6 +118,7 @@ export class ProfilePage extends React.Component {
isEditable={this.props.isCurrentUserProfile}
/>
<div>
{photoUploadError !== null ? <Alert color="danger">{photoUploadError.userMessage}</Alert> : null}
<h2 className="mb-0">{username}</h2>
<DateJoined date={dateJoined} />
</div>
@@ -240,7 +240,7 @@ ProfilePage.propTypes = {
savePhotoState: PropTypes.oneOf([null, 'pending', 'complete', 'error']),
// Page state helpers
errors: PropTypes.objectOf(PropTypes.string),
photoUploadError: PropTypes.objectOf(PropTypes.string),
// Actions
fetchProfile: PropTypes.func.isRequired,
@@ -262,7 +262,7 @@ ProfilePage.propTypes = {
ProfilePage.defaultProps = {
saveState: null,
savePhotoState: null,
errors: {},
photoUploadError: {},
profileImage: {},
name: null,
username: null,

View File

@@ -76,7 +76,7 @@ const profilePage = (state = initialState, action) => {
return {
...state,
savePhotoState: 'error',
errors: Object.assign({}, state.errors, action.payload.errors),
errors: Object.assign({}, state.errors, { photo: action.payload.error }),
};
case SAVE_PROFILE_PHOTO.RESET:
return {

View File

@@ -122,9 +122,12 @@ export function* handleSaveProfile(action) {
yield put(saveProfileReset());
yield put(resetDrafts());
} catch (e) {
// TODO: If this is any other kind of exception than a known validation error from the server,
// this code will fail gracelessly when it can't find fieldErrors on the error.
yield put(saveProfileFailure(e.fieldErrors));
if (e.processedData.fieldErrors) {
yield put(saveProfileFailure(e.processedData.fieldErrors));
} else {
// TODO: Currently failing silently on other kinds of errors
yield put(saveProfileReset());
}
}
}
@@ -141,7 +144,12 @@ export function* handleSaveProfilePhoto(action) {
yield put(saveProfilePhotoSuccess());
yield put(saveProfilePhotoReset());
} catch (e) {
yield put(saveProfilePhotoFailure(e.message));
if (e.processedData) {
yield put(saveProfilePhotoFailure(e.processedData));
} else {
// TODO: Currently failing silently on other kinds of errors
yield put(saveProfilePhotoReset());
}
}
}

View File

@@ -74,8 +74,10 @@ describe('RootSaga', () => {
it('should successfully publish a failure action on exception', () => {
const error = new Error('uhoh');
error.fieldErrors = {
uhoh: 'not good',
error.processedData = {
fieldErrors: {
uhoh: 'not good',
},
};
const action = profileActions.saveProfile(
'my username',

View File

@@ -67,7 +67,7 @@ export const visibilityDraftsFieldSelector = createSelector(
export const formErrorSelector = createSelector(
accountErrorsSelector,
formIdSelector,
(errors, formId) => errors[formId] || null,
(errors, formId) => (errors[formId] ? errors[formId].userMessage : null),
);
export const editableFormSelector = createSelector(
@@ -264,6 +264,7 @@ export const profilePageSelector = createSelector(
savePhotoStateSelector,
isCurrentUserProfileSelector,
draftSocialLinksByPlatformSelector,
accountErrorsSelector,
(
account,
formValues,
@@ -272,6 +273,7 @@ export const profilePageSelector = createSelector(
savePhotoState,
isCurrentUserProfile,
draftSocialLinksByPlatform,
errors,
) => ({
// Account data we need
username: account.username,
@@ -312,5 +314,6 @@ export const profilePageSelector = createSelector(
saveState,
savePhotoState,
isCurrentUserProfile,
photoUploadError: errors.photo || null,
}),
);

View File

@@ -39,12 +39,7 @@ export async function patchProfile(username, params) {
},
).catch((error) => {
const processedError = Object.create(error);
const fieldErrors = Object.entries(processAccountData(error.response.data.field_errors))
.reduce((acc, [fieldKey, messages]) => {
acc[fieldKey] = messages.userMessage;
return acc;
}, {});
processedError.fieldErrors = fieldErrors;
processedError.processedData = processAccountData(error.response.data);
throw processedError;
});
@@ -63,7 +58,11 @@ export async function postProfilePhoto(username, formData) {
'Content-Type': 'multipart/form-data',
},
},
);
).catch((error) => {
const processedError = Object.create(error);
processedError.processedData = camelCaseObject(error.response.data);
throw processedError;
});
return camelCaseObject(data);
}