Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [AXIMST-538] Add errors handling 4xx, 5xx #196

Merged
merged 3 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/certificates/data/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createSelector } from '@reduxjs/toolkit';
export const getLoadingStatus = (state) => state.certificates.loadingStatus;
export const getSavingStatus = (state) => state.certificates.savingStatus;
export const getSavingImageStatus = (state) => state.certificates.savingImageStatus;
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 @@ -12,10 +12,13 @@ const slice = createSlice({
loadingStatus: RequestStatus.PENDING,
savingStatus: '',
savingImageStatus: '',
errorMessage: '',
},
reducers: {
updateSavingStatus: (state, { payload }) => {
state.savingStatus = payload.status;
const { status, errorMessage } = payload;
state.savingStatus = status;
state.errorMessage = errorMessage;
},
updateSavingImageStatus: (state, { payload }) => {
state.savingImageStatus = 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-alert';
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
12 changes: 6 additions & 6 deletions src/certificates/layout/MainLayout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import PropTypes from 'prop-types';
import { Container, Layout } from '@openedx/paragon';
import { useIntl } from '@edx/frontend-platform/i18n';

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

const {
isQueryPending,
isQueryFailed,
errorMessage,
savingStatus,
isShowProcessingNotification,
processingNotificationTitle,
} = useLayout();
Expand Down Expand Up @@ -54,9 +54,9 @@ const MainLayout = ({ courseId, showHeaderButtons, children }) => {
isShow={isShowProcessingNotification}
title={processingNotificationTitle}
/>
<InternetConnectionAlert
isFailed={isQueryFailed}
isQueryPending={isQueryPending}
<SavingErrorAlert
savingStatus={savingStatus}
errorMessage={errorMessage}
/>
</div>
</>
Expand Down
12 changes: 5 additions & 7 deletions src/certificates/layout/hooks/useLayout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,26 @@ import { useSelector } from 'react-redux';

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

const useLayout = () => {
const savingStatus = useSelector(getSavingStatus);
const savingImageStatus = useSelector(getSavingImageStatus);
const errorMessage = useSelector(getErrorMessage);

const {
isShow: isShowProcessingNotification,
title: processingNotificationTitle,
} = useSelector(getProcessingNotification);

const isQueryPending = savingStatus === RequestStatus.PENDING || savingImageStatus === RequestStatus.PENDING;
const isQueryFailed = savingStatus === RequestStatus.FAILED || savingImageStatus === RequestStatus.FAILED;

useEffect(() => {
if (savingStatus === RequestStatus.SUCCESSFUL) {
window.scrollTo({ top: 0, behavior: 'smooth' });
}
}, [savingStatus]);

return {
isQueryPending,
isQueryFailed,
errorMessage,
savingStatus,
isShowProcessingNotification,
processingNotificationTitle,
};
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,16 +4,16 @@ import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import { Container, Layout, Stack } from '@openedx/paragon';
import { useIntl, injectIntl } from '@edx/frontend-platform/i18n';
import { DraggableList, ErrorAlert } from '@edx/frontend-lib-content-components';
import { Warning as WarningIcon } from '@openedx/paragon/icons';
import { DraggableList } from '@edx/frontend-lib-content-components';

import { getProcessingNotification } from '../generic/processing-notification/data/selectors';
import SubHeader from '../generic/sub-header/SubHeader';
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 { SavingErrorAlert } from '../generic/saving-error-alert';
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,
isTitleEditFormOpen,
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 @@ -196,13 +190,10 @@ const CourseUnit = ({ courseId }) => {
isShow={isShowProcessingNotification}
title={processingNotificationTitle}
/>
{isQueryPending && (
<InternetConnectionAlert
isFailed={isInternetConnectionAlertFailed}
isQueryPending={savingStatus === RequestStatus.PENDING}
onInternetConnectionFailed={handleInternetConnectionFailed}
/>
)}
<SavingErrorAlert
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 @@ -5,6 +5,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 getSequenceStatus = (state) => state.courseUnit.sequenceStatus;
export const getSequenceIds = (state) => state.courseUnit.courseSectionVertical.courseSequenceIds;
export const getCourseSectionVertical = (state) => state.courseUnit.courseSectionVertical;
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,
isTitleEditFormOpen: false,
canEdit: true,
Expand Down Expand Up @@ -38,7 +39,9 @@ const slice = createSlice({
state.isTitleEditFormOpen = 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-alert';
import { RequestStatus } from '../../data/constants';
import { NOTIFICATION_MESSAGES } from '../../constants';
import { updateModel, updateModels } from '../../generic/model-store';
Expand Down Expand Up @@ -117,7 +118,7 @@ export function editCourseItemQuery(itemId, displayName, sequenceId) {
});
} catch (error) {
dispatch(hideProcessingNotification());
dispatch(updateSavingStatus({ status: RequestStatus.FAILED }));
handleResponseErrors(error, dispatch, updateSavingStatus);
}
};
}
Expand All @@ -142,7 +143,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 @@ -185,7 +186,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 @@ -220,7 +221,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 @@ -243,7 +244,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 @@ -272,8 +273,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 @@ -296,7 +297,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,
getIsLoading,
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 isLoading = useSelector(getIsLoading);
const errorMessage = useSelector(getErrorMessage);
const sequenceStatus = useSelector(getSequenceStatus);
const { draftPreviewLink, publishedPreviewLink } = useSelector(getCourseSectionVertical);
const courseVerticalChildren = useSelector(getCourseVerticalChildren);
const staticFileNotices = useSelector(getStaticFileNotices);
const navigate = useNavigate();
const isTitleEditFormOpen = useSelector(state => state.courseUnit.isTitleEditFormOpen);
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(!isTitleEditFormOpen));
};
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,19 +119,16 @@ export const useCourseUnit = ({ courseId, blockId }) => {
sequenceId,
courseUnit,
unitTitle,
errorMessage,
sequenceStatus,
savingStatus,
isQueryPending,
isErrorAlert,
staticFileNotices,
currentlyVisibleToStudents,
isLoading,
isTitleEditFormOpen,
isInternetConnectionAlertFailed: savingStatus === RequestStatus.FAILED,
sharedClipboardData,
showPasteXBlock,
showPasteUnit,
handleInternetConnectionFailed,
unitXBlockActions,
headerNavigationsActions,
handleTitleEdit,
Expand Down
Loading
Loading