Skip to content

Commit

Permalink
feat: [AXIMST-538] Add errors handling 4xx, 5xx
Browse files Browse the repository at this point in the history
  • Loading branch information
ruzniaievdm committed Feb 29, 2024
1 parent f0efff1 commit d2e8321
Show file tree
Hide file tree
Showing 39 changed files with 295 additions and 98 deletions.
1 change: 1 addition & 0 deletions src/certificates/data/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createSelector } from '@reduxjs/toolkit';

export const getLoadingStatus = (state) => state.certificates.loadingStatus;
export const getSavingStatus = (state) => state.certificates.savingStatus;
export const getErrorMessage = (state) => state.certificates.errorMessage;
export const getSendRequestErrors = (state) => state.certificates.sendRequestErrors.developer_message;
export const getCertificates = state => state.certificates.certificatesData.certificates;
export const getHasCertificateModes = state => state.certificates.certificatesData.hasCertificateModes;
Expand Down
5 changes: 4 additions & 1 deletion src/certificates/data/slice.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ const slice = createSlice({
componentMode: MODE_STATES.noModes,
loadingStatus: RequestStatus.PENDING,
savingStatus: '',
errorMessage: '',
},
reducers: {
updateSavingStatus: (state, { payload }) => {
state.savingStatus = payload.status;
const { status, errorMessage } = payload;
state.savingStatus = status;
state.errorMessage = errorMessage;
},
updateLoadingStatus: (state, { payload }) => {
state.loadingStatus = payload.status;
Expand Down
13 changes: 5 additions & 8 deletions src/certificates/data/thunks.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
hideProcessingNotification,
showProcessingNotification,
} from '../../generic/processing-notification/data/slice';
import { handleResponseErrors } from '../../generic/saving-error-notification';
import { NOTIFICATION_MESSAGES } from '../../constants';
import {
getCertificates,
Expand Down Expand Up @@ -51,8 +52,7 @@ export function createCourseCertificate(courseId, certificate) {
dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL }));
return true;
} catch (error) {
dispatch(updateSavingStatus({ status: RequestStatus.FAILED }));
return false;
return handleResponseErrors(error, dispatch, updateSavingStatus);
} finally {
dispatch(hideProcessingNotification());
}
Expand All @@ -70,8 +70,7 @@ export function updateCourseCertificate(courseId, certificate) {
dispatch(updateCertificateSuccess(certificatesValues));
return true;
} catch (error) {
dispatch(updateSavingStatus({ status: RequestStatus.FAILED }));
return false;
return handleResponseErrors(error, dispatch, updateSavingStatus);
} finally {
dispatch(hideProcessingNotification());
}
Expand All @@ -89,8 +88,7 @@ export function deleteCourseCertificate(courseId, certificateId) {
dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL }));
return true;
} catch (error) {
dispatch(updateSavingStatus({ status: RequestStatus.FAILED }));
return false;
return handleResponseErrors(error, dispatch, updateSavingStatus);
} finally {
dispatch(hideProcessingNotification());
}
Expand All @@ -111,8 +109,7 @@ export function updateCertificateActiveStatus(courseId, path, activationStatus)
dispatch(fetchCertificates(courseId));
return true;
} catch (error) {
dispatch(updateSavingStatus({ status: RequestStatus.FAILED }));
return false;
return handleResponseErrors(error, dispatch, updateSavingStatus);
} finally {
dispatch(hideProcessingNotification());
}
Expand Down
7 changes: 7 additions & 0 deletions src/certificates/layout/MainLayout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import { Container, Layout } from '@edx/paragon';
import { useIntl } from '@edx/frontend-platform/i18n';

import { SavingErrorNotification } from '../../generic/saving-error-notification';
import ProcessingNotification from '../../generic/processing-notification';
import InternetConnectionAlert from '../../generic/internet-connection-alert';
import SubHeader from '../../generic/sub-header/SubHeader';
Expand All @@ -14,6 +15,8 @@ const MainLayout = ({ courseId, showHeaderButtons, children }) => {
const intl = useIntl();

const {
errorMessage,
savingStatus,
isQueryPending,
isQueryFailed,
isShowProcessingNotification,
Expand Down Expand Up @@ -54,6 +57,10 @@ const MainLayout = ({ courseId, showHeaderButtons, children }) => {
isShow={isShowProcessingNotification}
title={processingNotificationTitle}
/>
<SavingErrorNotification
savingStatus={savingStatus}
errorMessage={errorMessage}
/>
<InternetConnectionAlert
isFailed={isQueryFailed}
isQueryPending={isQueryPending}
Expand Down
5 changes: 4 additions & 1 deletion src/certificates/layout/hooks/useLayout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import { useSelector } from 'react-redux';

import { RequestStatus } from '../../../data/constants';
import { getProcessingNotification } from '../../../generic/processing-notification/data/selectors';
import { getSavingStatus } from '../../data/selectors';
import { getSavingStatus, getErrorMessage } from '../../data/selectors';

const useLayout = () => {
const savingStatus = useSelector(getSavingStatus);
const errorMessage = useSelector(getErrorMessage);
const {
isShow: isShowProcessingNotification,
title: processingNotificationTitle,
Expand All @@ -22,6 +23,8 @@ const useLayout = () => {
}, [savingStatus]);

return {
errorMessage,
savingStatus,
isQueryPending,
isQueryFailed,
isShowProcessingNotification,
Expand Down
23 changes: 7 additions & 16 deletions src/course-unit/CourseUnit.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import { Container, Layout, Stack } from '@edx/paragon';
import { useIntl, injectIntl } from '@edx/frontend-platform/i18n';
import { DraggableList, ErrorAlert } from '@edx/frontend-lib-content-components';
import { DraggableList } from '@edx/frontend-lib-content-components';
import { Warning as WarningIcon } from '@edx/paragon/icons';

import { getProcessingNotification } from '../generic/processing-notification/data/selectors';
Expand All @@ -13,7 +13,7 @@ import { RequestStatus } from '../data/constants';
import getPageHeadTitle from '../generic/utils';
import AlertMessage from '../generic/alert-message';
import ProcessingNotification from '../generic/processing-notification';
import InternetConnectionAlert from '../generic/internet-connection-alert';
import { SavingErrorNotification } from '../generic/saving-error-notification';
import ConnectionErrorAlert from '../generic/ConnectionErrorAlert';
import Loading from '../generic/Loading';
import AddComponent from './add-component/AddComponent';
Expand All @@ -34,22 +34,19 @@ const CourseUnit = ({ courseId }) => {
isLoading,
sequenceId,
unitTitle,
isQueryPending,
errorMessage,
sequenceStatus,
savingStatus,
isEditTitleFormOpen,
isErrorAlert,
staticFileNotices,
currentlyVisibleToStudents,
isInternetConnectionAlertFailed,
unitXBlockActions,
sharedClipboardData,
showPasteXBlock,
showPasteUnit,
handleTitleEditSubmit,
headerNavigationsActions,
handleTitleEdit,
handleInternetConnectionFailed,
handleCreateNewCourseXBlock,
handleConfigureSubmit,
courseVerticalChildren,
Expand Down Expand Up @@ -95,9 +92,6 @@ const CourseUnit = ({ courseId }) => {
<>
<Container size="xl" className="course-unit px-4">
<section className="course-unit-container mb-4 mt-5">
<ErrorAlert hideHeading isError={savingStatus === RequestStatus.FAILED && isErrorAlert}>
{intl.formatMessage(messages.alertFailedGeneric, { actionName: 'save', type: 'changes' })}
</ErrorAlert>
<SubHeader
hideBorder
title={(
Expand Down Expand Up @@ -194,13 +188,10 @@ const CourseUnit = ({ courseId }) => {
isShow={isShowProcessingNotification}
title={processingNotificationTitle}
/>
{isQueryPending && (
<InternetConnectionAlert
isFailed={isInternetConnectionAlertFailed}
isQueryPending={savingStatus === RequestStatus.PENDING}
onInternetConnectionFailed={handleInternetConnectionFailed}
/>
)}
<SavingErrorNotification
savingStatus={savingStatus}
errorMessage={errorMessage}
/>
</div>
</>
);
Expand Down
1 change: 1 addition & 0 deletions src/course-unit/data/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export const getCourseUnitData = (state) => state.courseUnit.unit;
export const getCanEdit = (state) => state.courseUnit.canEdit;
export const getStaticFileNotices = (state) => state.courseUnit.staticFileNotices;
export const getSavingStatus = (state) => state.courseUnit.savingStatus;
export const getErrorMessage = (state) => state.courseUnit.errorMessage;
export const getLoadingStatus = (state) => state.courseUnit.loadingStatus;
export const getSequenceStatus = (state) => state.courseUnit.sequenceStatus;
export const getSequenceIds = (state) => state.courseUnit.courseSectionVertical.courseSequenceIds;
Expand Down
5 changes: 4 additions & 1 deletion src/course-unit/data/slice.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const slice = createSlice({
name: 'courseUnit',
initialState: {
savingStatus: '',
errorMessage: '',
isQueryPending: false,
isEditTitleFormOpen: false,
canEdit: true,
Expand Down Expand Up @@ -38,7 +39,9 @@ const slice = createSlice({
state.isEditTitleFormOpen = payload;
},
updateSavingStatus: (state, { payload }) => {
state.savingStatus = payload.status;
const { status, errorMessage } = payload;
state.savingStatus = status;
state.errorMessage = errorMessage;
},
fetchSequenceRequest: (state, { payload }) => {
state.sequenceId = payload.sequenceId;
Expand Down
15 changes: 8 additions & 7 deletions src/course-unit/data/thunk.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
hideProcessingNotification,
showProcessingNotification,
} from '../../generic/processing-notification/data/slice';
import { handleResponseErrors } from '../../generic/saving-error-notification';
import { RequestStatus } from '../../data/constants';
import { NOTIFICATION_MESSAGES } from '../../constants';
import { updateModel, updateModels } from '../../generic/model-store';
Expand Down Expand Up @@ -116,7 +117,7 @@ export function editCourseItemQuery(itemId, displayName, sequenceId) {
});
} catch (error) {
dispatch(hideProcessingNotification());
dispatch(updateSavingStatus({ status: RequestStatus.FAILED }));
handleResponseErrors(error, dispatch, updateSavingStatus);
}
};
}
Expand All @@ -141,7 +142,7 @@ export function editCourseUnitVisibilityAndData(itemId, type, isVisible, groupAc
});
} catch (error) {
dispatch(hideProcessingNotification());
dispatch(updateSavingStatus({ status: RequestStatus.FAILED }));
handleResponseErrors(error, dispatch, updateSavingStatus);
}
};
}
Expand Down Expand Up @@ -184,7 +185,7 @@ export function createNewCourseXBlock(body, callback, blockId) {
} catch (error) {
dispatch(hideProcessingNotification());
dispatch(updateLoadingCourseXblockStatus({ status: RequestStatus.FAILED }));
dispatch(updateSavingStatus({ status: RequestStatus.FAILED }));
handleResponseErrors(error, dispatch, updateSavingStatus);
}
};
}
Expand Down Expand Up @@ -219,7 +220,7 @@ export function deleteUnitItemQuery(itemId, xblockId) {
dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL }));
} catch (error) {
dispatch(hideProcessingNotification());
dispatch(updateSavingStatus({ status: RequestStatus.FAILED }));
handleResponseErrors(error, dispatch, updateSavingStatus);
}
};
}
Expand All @@ -242,7 +243,7 @@ export function duplicateUnitItemQuery(itemId, xblockId) {
dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL }));
} catch (error) {
dispatch(hideProcessingNotification());
dispatch(updateSavingStatus({ status: RequestStatus.FAILED }));
handleResponseErrors(error, dispatch, updateSavingStatus);
}
};
}
Expand Down Expand Up @@ -271,8 +272,8 @@ export function copyToClipboard(usageKey) {
throw new Error(`Unexpected clipboard status "${clipboardData.content?.status}" in successful API response.`);
}
} catch (error) {
dispatch(updateSavingStatus({ status: RequestStatus.FAILED }));
logError('Error copying to clipboard:', error);
handleResponseErrors(error, dispatch, updateSavingStatus);
} finally {
dispatch(hideProcessingNotification());
}
Expand All @@ -295,7 +296,7 @@ export function setXBlockOrderListQuery(blockId, xblockListIds, restoreCallback)
});
} catch (error) {
restoreCallback();
dispatch(updateSavingStatus({ status: RequestStatus.FAILED }));
handleResponseErrors(error, dispatch, updateSavingStatus);
} finally {
dispatch(hideProcessingNotification());
}
Expand Down
18 changes: 4 additions & 14 deletions src/course-unit/hooks.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react';
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useNavigate } from 'react-router-dom';

Expand All @@ -20,6 +20,7 @@ import {
getCourseUnitData,
getLoadingStatus,
getSavingStatus,
getErrorMessage,
getSequenceStatus,
getStaticFileNotices,
getCanEdit,
Expand All @@ -33,18 +34,16 @@ import { PUBLISH_TYPES } from './constants';
export const useCourseUnit = ({ courseId, blockId }) => {
const dispatch = useDispatch();

const [isErrorAlert, toggleErrorAlert] = useState(false);
const [hasInternetConnectionError, setInternetConnectionError] = useState(false);
const courseUnit = useSelector(getCourseUnitData);
const savingStatus = useSelector(getSavingStatus);
const errorMessage = useSelector(getErrorMessage);
const loadingStatus = useSelector(getLoadingStatus);
const sequenceStatus = useSelector(getSequenceStatus);
const { draftPreviewLink, publishedPreviewLink } = useSelector(getCourseSectionVertical);
const courseVerticalChildren = useSelector(getCourseVerticalChildren);
const staticFileNotices = useSelector(getStaticFileNotices);
const navigate = useNavigate();
const isEditTitleFormOpen = useSelector(state => state.courseUnit.isEditTitleFormOpen);
const isQueryPending = useSelector(state => state.courseUnit.isQueryPending);
const canEdit = useSelector(getCanEdit);
const { currentlyVisibleToStudents } = courseUnit;
const { sharedClipboardData, showPasteXBlock, showPasteUnit } = useCopyToClipboard(canEdit);
Expand All @@ -62,10 +61,6 @@ export const useCourseUnit = ({ courseId, blockId }) => {
},
};

const handleInternetConnectionFailed = () => {
setInternetConnectionError(true);
};

const handleTitleEdit = () => {
dispatch(changeEditTitleFormOpen(!isEditTitleFormOpen));
};
Expand Down Expand Up @@ -109,8 +104,6 @@ export const useCourseUnit = ({ courseId, blockId }) => {
useEffect(() => {
if (savingStatus === RequestStatus.SUCCESSFUL) {
dispatch(updateQueryPendingStatus(true));
} else if (savingStatus === RequestStatus.FAILED && !hasInternetConnectionError) {
toggleErrorAlert(true);
}
}, [savingStatus]);

Expand All @@ -126,20 +119,17 @@ export const useCourseUnit = ({ courseId, blockId }) => {
sequenceId,
courseUnit,
unitTitle,
errorMessage,
sequenceStatus,
savingStatus,
isQueryPending,
isErrorAlert,
staticFileNotices,
currentlyVisibleToStudents,
isLoading: loadingStatus.fetchUnitLoadingStatus === RequestStatus.IN_PROGRESS
|| loadingStatus.courseSectionVerticalLoadingStatus === RequestStatus.IN_PROGRESS,
isEditTitleFormOpen,
isInternetConnectionAlertFailed: savingStatus === RequestStatus.FAILED,
sharedClipboardData,
showPasteXBlock,
showPasteUnit,
handleInternetConnectionFailed,
unitXBlockActions,
headerNavigationsActions,
handleTitleEdit,
Expand Down
Loading

0 comments on commit d2e8321

Please sign in to comment.