From 2e676dbf38f9e1b3c9f1acf72a288d9ee92e676a Mon Sep 17 00:00:00 2001 From: PKulkoRaccoonGang Date: Sun, 9 Jun 2024 12:21:38 +0300 Subject: [PATCH] fix: error handling --- src/course-checklist/CourseChecklist.jsx | 16 ++++++++++--- src/course-checklist/data/thunks.js | 6 ++++- src/course-outline/CourseOutline.jsx | 22 +++++++++++++++++ src/course-outline/data/thunk.js | 15 ++++++++---- src/course-outline/hooks.jsx | 1 + src/course-team/CourseTeam.jsx | 10 ++++++++ src/course-team/data/thunk.js | 6 ++++- src/course-team/hooks.jsx | 1 + src/course-updates/CourseUpdates.jsx | 10 ++++++++ src/course-updates/data/thunk.js | 30 +++++++++++++++++------- src/export-page/CourseExportPage.jsx | 10 ++++++++ src/export-page/data/thunks.js | 6 ++++- src/grading-settings/GradingSettings.jsx | 10 ++++++++ src/grading-settings/data/thunks.js | 12 ++++++++-- src/group-configurations/data/thunk.js | 6 ++++- src/group-configurations/hooks.jsx | 1 + src/group-configurations/index.jsx | 10 ++++++++ src/import-page/CourseImportPage.jsx | 10 ++++++++ src/textbooks/Textbooks.jsx | 10 ++++++++ src/textbooks/hooks.jsx | 1 + 20 files changed, 172 insertions(+), 21 deletions(-) diff --git a/src/course-checklist/CourseChecklist.jsx b/src/course-checklist/CourseChecklist.jsx index 5766bfe45e..b71817f168 100644 --- a/src/course-checklist/CourseChecklist.jsx +++ b/src/course-checklist/CourseChecklist.jsx @@ -13,6 +13,7 @@ import AriaLiveRegion from './AriaLiveRegion'; import { RequestStatus } from '../data/constants'; import ChecklistSection from './ChecklistSection'; import { fetchCourseLaunchQuery, fetchCourseBestPracticesQuery } from './data/thunks'; +import ConnectionErrorAlert from '../generic/ConnectionErrorAlert'; import getUpdateLinks from './utils'; const CourseChecklist = ({ @@ -36,10 +37,19 @@ const CourseChecklist = ({ bestPracticeData, } = useSelector(state => state.courseChecklist); - const { bestPracticeChecklistLoadingStatus, launchChecklistLoadingStatus } = loadingStatus; + const { bestPracticeChecklistStatus, launchChecklistStatus } = loadingStatus; - const isCourseLaunchChecklistLoading = bestPracticeChecklistLoadingStatus === RequestStatus.IN_PROGRESS; - const isCourseBestPracticeChecklistLoading = launchChecklistLoadingStatus === RequestStatus.IN_PROGRESS; + const isCourseLaunchChecklistLoading = bestPracticeChecklistStatus === RequestStatus.IN_PROGRESS; + const isCourseBestPracticeChecklistLoading = launchChecklistStatus === RequestStatus.IN_PROGRESS; + const isLoadingDenied = launchChecklistStatus === RequestStatus.DENIED; + + if (isLoadingDenied) { + return ( + + + + ); + } return ( <> diff --git a/src/course-checklist/data/thunks.js b/src/course-checklist/data/thunks.js index 20be7648a1..74547dab7b 100644 --- a/src/course-checklist/data/thunks.js +++ b/src/course-checklist/data/thunks.js @@ -24,7 +24,11 @@ export function fetchCourseLaunchQuery({ dispatch(fetchLaunchChecklistSuccess({ data })); dispatch(updateLaunchChecklistStatus({ status: RequestStatus.SUCCESSFUL })); } catch (error) { - dispatch(updateLaunchChecklistStatus({ status: RequestStatus.FAILED })); + if (error.response && error.response.status === 403) { + dispatch(updateLaunchChecklistStatus({ status: RequestStatus.DENIED })); + } else { + dispatch(updateLaunchChecklistStatus({ status: RequestStatus.FAILED })); + } } }; } diff --git a/src/course-outline/CourseOutline.jsx b/src/course-outline/CourseOutline.jsx index e7468e68fb..95ba56acf0 100644 --- a/src/course-outline/CourseOutline.jsx +++ b/src/course-outline/CourseOutline.jsx @@ -68,6 +68,7 @@ const CourseOutline = ({ courseId }) => { sectionsList, isCustomRelativeDatesActive, isLoading, + isLoadingDenied, isReIndexShow, showSuccessAlert, isSectionsExpanded, @@ -232,6 +233,27 @@ const CourseOutline = ({ courseId }) => { ); } + if (isLoadingDenied) { + return ( + + + + ); + } + return ( <> diff --git a/src/course-outline/data/thunk.js b/src/course-outline/data/thunk.js index 315c5846c0..18b7eea2e2 100644 --- a/src/course-outline/data/thunk.js +++ b/src/course-outline/data/thunk.js @@ -99,10 +99,17 @@ export function fetchCourseOutlineIndexQuery(courseId) { dispatch(updateOutlineIndexLoadingStatus({ status: RequestStatus.SUCCESSFUL })); } catch (error) { - dispatch(updateOutlineIndexLoadingStatus({ - status: RequestStatus.FAILED, - errors: getErrorDetails(error, false), - })); + if (error.response && error.response.status === 403) { + dispatch(updateOutlineIndexLoadingStatus({ + status: RequestStatus.DENIED, + errors: getErrorDetails(error, false), + })); + } else { + dispatch(updateOutlineIndexLoadingStatus({ + status: RequestStatus.FAILED, + errors: getErrorDetails(error, false), + })); + } } }; } diff --git a/src/course-outline/hooks.jsx b/src/course-outline/hooks.jsx index f820f3d0ed..0ae09dea08 100644 --- a/src/course-outline/hooks.jsx +++ b/src/course-outline/hooks.jsx @@ -300,6 +300,7 @@ const useCourseOutline = ({ courseId }) => { sectionsList, isCustomRelativeDatesActive, isLoading: outlineIndexLoadingStatus === RequestStatus.IN_PROGRESS, + isLoadingDenied: outlineIndexLoadingStatus === RequestStatus.DENIED, isReIndexShow: Boolean(reindexLink), showSuccessAlert, isDisabledReindexButton, diff --git a/src/course-team/CourseTeam.jsx b/src/course-team/CourseTeam.jsx index 10360460a3..29ad5b80f9 100644 --- a/src/course-team/CourseTeam.jsx +++ b/src/course-team/CourseTeam.jsx @@ -20,6 +20,7 @@ import CourseTeamMember from './course-team-member/CourseTeamMember'; import InfoModal from './info-modal/InfoModal'; import { useCourseTeam } from './hooks'; import getPageHeadTitle from '../generic/utils'; +import ConnectionErrorAlert from '../generic/ConnectionErrorAlert'; const CourseTeam = ({ courseId }) => { const intl = useIntl(); @@ -35,6 +36,7 @@ const CourseTeam = ({ courseId }) => { courseTeamUsers, currentUserEmail, isLoading, + isLoadingDenied, isSingleAdmin, isFormVisible, isQueryPending, @@ -55,6 +57,14 @@ const CourseTeam = ({ courseId }) => { handleInternetConnectionFailed, } = useCourseTeam({ intl, courseId }); + if (isLoadingDenied) { + return ( + + + + ); + } + if (isLoading) { // eslint-disable-next-line react/jsx-no-useless-fragment return <>; diff --git a/src/course-team/data/thunk.js b/src/course-team/data/thunk.js index 78012870f6..bfc3db193e 100644 --- a/src/course-team/data/thunk.js +++ b/src/course-team/data/thunk.js @@ -24,7 +24,11 @@ export function fetchCourseTeamQuery(courseId) { dispatch(updateLoadingCourseTeamStatus({ status: RequestStatus.SUCCESSFUL })); return true; } catch (error) { - dispatch(updateLoadingCourseTeamStatus({ status: RequestStatus.FAILED })); + if (error.response && error.response.status === 403) { + dispatch(updateLoadingCourseTeamStatus({ status: RequestStatus.DENIED })); + } else { + dispatch(updateLoadingCourseTeamStatus({ status: RequestStatus.FAILED })); + } return false; } }; diff --git a/src/course-team/hooks.jsx b/src/course-team/hooks.jsx index 1b3778fecf..7c6bc09a48 100644 --- a/src/course-team/hooks.jsx +++ b/src/course-team/hooks.jsx @@ -113,6 +113,7 @@ const useCourseTeam = ({ courseId }) => { courseTeamUsers, currentUserEmail, isLoading: loadingCourseTeamStatus === RequestStatus.IN_PROGRESS, + isLoadingDenied: loadingCourseTeamStatus === RequestStatus.DENIED, isSingleAdmin, isFormVisible, isAllowActions, diff --git a/src/course-updates/CourseUpdates.jsx b/src/course-updates/CourseUpdates.jsx index a6b7af677b..0d82f3bf8d 100644 --- a/src/course-updates/CourseUpdates.jsx +++ b/src/course-updates/CourseUpdates.jsx @@ -16,6 +16,7 @@ import { getProcessingNotification } from '../generic/processing-notification/da import ProcessingNotification from '../generic/processing-notification'; import SubHeader from '../generic/sub-header/SubHeader'; import InternetConnectionAlert from '../generic/internet-connection-alert'; +import ConnectionErrorAlert from '../generic/ConnectionErrorAlert'; import { RequestStatus } from '../data/constants'; import CourseHandouts from './course-handouts/CourseHandouts'; import CourseUpdate from './course-update/CourseUpdate'; @@ -64,9 +65,18 @@ const CourseUpdates = ({ courseId }) => { const errors = useSelector(getErrors); const anyStatusFailed = matchesAnyStatus({ ...loadingStatuses, ...savingStatuses }, RequestStatus.FAILED); + const anyStatusDenied = matchesAnyStatus({ ...loadingStatuses, ...savingStatuses }, RequestStatus.DENIED); const anyStatusInProgress = matchesAnyStatus({ ...loadingStatuses, ...savingStatuses }, RequestStatus.IN_PROGRESS); const anyStatusPending = matchesAnyStatus({ ...loadingStatuses, ...savingStatuses }, RequestStatus.PENDING); + if (anyStatusDenied) { + return ( + + + + ); + } + return ( <> diff --git a/src/course-updates/data/thunk.js b/src/course-updates/data/thunk.js index 88b3a0578a..225ca9e461 100644 --- a/src/course-updates/data/thunk.js +++ b/src/course-updates/data/thunk.js @@ -31,10 +31,17 @@ export function fetchCourseUpdatesQuery(courseId) { error: { loadingUpdates: false }, })); } catch (error) { - dispatch(updateLoadingStatuses({ - status: { fetchCourseUpdatesQuery: RequestStatus.FAILED }, - error: { loadingUpdates: true }, - })); + if (error.response && error.response.status === 403) { + dispatch(updateLoadingStatuses({ + status: { fetchCourseUpdatesQuery: RequestStatus.DENIED }, + error: { loadingUpdates: true }, + })); + } else { + dispatch(updateLoadingStatuses({ + status: { fetchCourseUpdatesQuery: RequestStatus.FAILED }, + error: { loadingUpdates: true }, + })); + } } }; } @@ -116,10 +123,17 @@ export function fetchCourseHandoutsQuery(courseId) { error: { loadingHandouts: false }, })); } catch (error) { - dispatch(updateLoadingStatuses({ - status: { fetchCourseHandoutsQuery: RequestStatus.FAILED }, - error: { loadingHandouts: true }, - })); + if (error.response && error.response.status === 403) { + dispatch(updateLoadingStatuses({ + status: { fetchCourseHandoutsQuery: RequestStatus.DENIED }, + error: { loadingHandouts: true }, + })); + } else { + dispatch(updateLoadingStatuses({ + status: { fetchCourseHandoutsQuery: RequestStatus.FAILED }, + error: { loadingHandouts: true }, + })); + } } }; } diff --git a/src/export-page/CourseExportPage.jsx b/src/export-page/CourseExportPage.jsx index b332499732..36f96f2513 100644 --- a/src/export-page/CourseExportPage.jsx +++ b/src/export-page/CourseExportPage.jsx @@ -11,6 +11,7 @@ import { getConfig } from '@edx/frontend-platform'; import { Helmet } from 'react-helmet'; import InternetConnectionAlert from '../generic/internet-connection-alert'; +import ConnectionErrorAlert from '../generic/ConnectionErrorAlert'; import SubHeader from '../generic/sub-header/SubHeader'; import { RequestStatus } from '../data/constants'; import { useModel } from '../generic/model-store'; @@ -37,6 +38,7 @@ const CourseExportPage = ({ intl, courseId }) => { const cookies = new Cookies(); const isShowExportButton = !exportTriggered || errorMessage || currentStage === EXPORT_STAGES.SUCCESS; const anyRequestFailed = savingStatus === RequestStatus.FAILED || loadingStatus === RequestStatus.FAILED; + const isLoadingDenied = loadingStatus === RequestStatus.DENIED; const anyRequestInProgress = savingStatus === RequestStatus.PENDING || loadingStatus === RequestStatus.IN_PROGRESS; useEffect(() => { @@ -48,6 +50,14 @@ const CourseExportPage = ({ intl, courseId }) => { } }, []); + if (isLoadingDenied) { + return ( + + + + ); + } + return ( <> diff --git a/src/export-page/data/thunks.js b/src/export-page/data/thunks.js index f5fdcae07c..dd86ba84ef 100644 --- a/src/export-page/data/thunks.js +++ b/src/export-page/data/thunks.js @@ -89,7 +89,11 @@ export function fetchExportStatus(courseId) { dispatch(updateLoadingStatus({ status: RequestStatus.SUCCESSFUL })); return true; } catch (error) { - dispatch(updateLoadingStatus({ status: RequestStatus.FAILED })); + if (error.response && error.response.status === 403) { + dispatch(updateLoadingStatus({ status: RequestStatus.DENIED })); + } else { + dispatch(updateLoadingStatus({ courseId, status: RequestStatus.FAILED })); + } return false; } }; diff --git a/src/grading-settings/GradingSettings.jsx b/src/grading-settings/GradingSettings.jsx index 79234b603e..6c15fdbc9e 100644 --- a/src/grading-settings/GradingSettings.jsx +++ b/src/grading-settings/GradingSettings.jsx @@ -12,6 +12,7 @@ import AlertMessage from '../generic/alert-message'; import { RequestStatus } from '../data/constants'; import InternetConnectionAlert from '../generic/internet-connection-alert'; import SubHeader from '../generic/sub-header/SubHeader'; +import ConnectionErrorAlert from '../generic/ConnectionErrorAlert'; import SectionSubHeader from '../generic/section-sub-header'; import { STATEFUL_BUTTON_STATES } from '../constants'; import { @@ -37,6 +38,7 @@ const GradingSettings = ({ intl, courseId }) => { const courseAssignmentLists = useSelector(getCourseAssignmentLists); const savingStatus = useSelector(getSavingStatus); const loadingStatus = useSelector(getLoadingStatus); + const isLoadingDenied = loadingStatus === RequestStatus.DENIED; const [showSuccessAlert, setShowSuccessAlert] = useState(false); const dispatch = useDispatch(); const isLoading = loadingStatus === RequestStatus.IN_PROGRESS; @@ -83,6 +85,14 @@ const GradingSettings = ({ intl, courseId }) => { dispatch(fetchCourseSettingsQuery(courseId)); }, [courseId]); + if (isLoadingDenied) { + return ( + + + + ); + } + if (isLoading) { // eslint-disable-next-line react/jsx-no-useless-fragment return <>; diff --git a/src/grading-settings/data/thunks.js b/src/grading-settings/data/thunks.js index b7106eb390..39094ffe55 100644 --- a/src/grading-settings/data/thunks.js +++ b/src/grading-settings/data/thunks.js @@ -20,7 +20,11 @@ export function fetchGradingSettings(courseId) { dispatch(fetchGradingSettingsSuccess(settingValues)); dispatch(updateLoadingStatus({ status: RequestStatus.SUCCESSFUL })); } catch (error) { - dispatch(updateLoadingStatus({ status: RequestStatus.FAILED })); + if (error.response && error.response.status === 403) { + dispatch(updateLoadingStatus({ status: RequestStatus.DENIED })); + } else { + dispatch(updateLoadingStatus({ courseId, status: RequestStatus.FAILED })); + } } }; } @@ -48,7 +52,11 @@ export function fetchCourseSettingsQuery(courseId) { dispatch(updateLoadingStatus({ status: RequestStatus.SUCCESSFUL })); return true; } catch (error) { - dispatch(updateLoadingStatus({ status: RequestStatus.FAILED })); + if (error.response && error.response.status === 403) { + dispatch(updateLoadingStatus({ status: RequestStatus.DENIED })); + } else { + dispatch(updateLoadingStatus({ courseId, status: RequestStatus.FAILED })); + } return false; } }; diff --git a/src/group-configurations/data/thunk.js b/src/group-configurations/data/thunk.js index 16b961d96d..30ae354730 100644 --- a/src/group-configurations/data/thunk.js +++ b/src/group-configurations/data/thunk.js @@ -33,7 +33,11 @@ export function fetchGroupConfigurationsQuery(courseId) { dispatch(fetchGroupConfigurations({ groupConfigurations })); dispatch(updateLoadingStatus({ status: RequestStatus.SUCCESSFUL })); } catch (error) { - dispatch(updateLoadingStatus({ status: RequestStatus.FAILED })); + if (error.response && error.response.status === 403) { + dispatch(updateLoadingStatus({ status: RequestStatus.DENIED })); + } else { + dispatch(updateLoadingStatus({ courseId, status: RequestStatus.FAILED })); + } } }; } diff --git a/src/group-configurations/hooks.jsx b/src/group-configurations/hooks.jsx index 01187f1d2c..cbc3cb7b0e 100644 --- a/src/group-configurations/hooks.jsx +++ b/src/group-configurations/hooks.jsx @@ -85,6 +85,7 @@ const useGroupConfigurations = (courseId) => { return { isLoading: loadingStatus === RequestStatus.IN_PROGRESS, + isLoadingDenied: loadingStatus === RequestStatus.DENIED, savingStatus, contentGroupActions, experimentConfigurationActions, diff --git a/src/group-configurations/index.jsx b/src/group-configurations/index.jsx index 49df275b86..cd4797f4fb 100644 --- a/src/group-configurations/index.jsx +++ b/src/group-configurations/index.jsx @@ -16,6 +16,7 @@ import ExperimentConfigurationsSection from './experiment-configurations-section import EnrollmentTrackGroupsSection from './enrollment-track-groups-section'; import GroupConfigurationSidebar from './group-configuration-sidebar'; import { useGroupConfigurations } from './hooks'; +import ConnectionErrorAlert from '../generic/ConnectionErrorAlert'; const GroupConfigurations = ({ courseId }) => { const { formatMessage } = useIntl(); @@ -34,6 +35,7 @@ const GroupConfigurations = ({ courseId }) => { shouldShowExperimentGroups, experimentGroupConfigurations, }, + isLoadingDenied, } = useGroupConfigurations(courseId); document.title = getPageHeadTitle( @@ -41,6 +43,14 @@ const GroupConfigurations = ({ courseId }) => { formatMessage(messages.headingTitle), ); + if (isLoadingDenied) { + return ( + + + + ); + } + if (isLoading) { return ( diff --git a/src/import-page/CourseImportPage.jsx b/src/import-page/CourseImportPage.jsx index 368d44f741..2daea3fdb2 100644 --- a/src/import-page/CourseImportPage.jsx +++ b/src/import-page/CourseImportPage.jsx @@ -13,6 +13,7 @@ import SubHeader from '../generic/sub-header/SubHeader'; import InternetConnectionAlert from '../generic/internet-connection-alert'; import { RequestStatus } from '../data/constants'; import { useModel } from '../generic/model-store'; +import ConnectionErrorAlert from '../generic/ConnectionErrorAlert'; import { updateFileName, updateImportTriggered, updateSavingStatus, updateSuccessDate, } from './data/slice'; @@ -31,6 +32,7 @@ const CourseImportPage = ({ intl, courseId }) => { const savingStatus = useSelector(getSavingStatus); const loadingStatus = useSelector(getLoadingStatus); const anyRequestFailed = savingStatus === RequestStatus.FAILED || loadingStatus === RequestStatus.FAILED; + const isLoadingDenied = loadingStatus === RequestStatus.DENIED; const anyRequestInProgress = savingStatus === RequestStatus.PENDING || loadingStatus === RequestStatus.IN_PROGRESS; useEffect(() => { @@ -43,6 +45,14 @@ const CourseImportPage = ({ intl, courseId }) => { } }, []); + if (isLoadingDenied) { + return ( + + + + ); + } + return ( <> diff --git a/src/textbooks/Textbooks.jsx b/src/textbooks/Textbooks.jsx index 905a1ec5f4..71b380224a 100644 --- a/src/textbooks/Textbooks.jsx +++ b/src/textbooks/Textbooks.jsx @@ -16,6 +16,7 @@ import { getProcessingNotification } from '../generic/processing-notification/da import { useModel } from '../generic/model-store'; import { LoadingSpinner } from '../generic/Loading'; import SubHeader from '../generic/sub-header/SubHeader'; +import ConnectionErrorAlert from '../generic/ConnectionErrorAlert'; import ProcessingNotification from '../generic/processing-notification'; import EmptyPlaceholder from './empty-placeholder/EmptyPlaceholder'; import TextbookCard from './textbook-card/TextbooksCard'; @@ -33,6 +34,7 @@ const Textbooks = ({ courseId }) => { const { textbooks, isLoading, + isLoadingFailed, breadcrumbs, errorMessage, savingStatus, @@ -50,6 +52,14 @@ const Textbooks = ({ courseId }) => { title: processingNotificationTitle, } = useSelector(getProcessingNotification); + if (isLoadingFailed) { + return ( + + + + ); + } + if (isLoading) { return ( diff --git a/src/textbooks/hooks.jsx b/src/textbooks/hooks.jsx index 10f2d5b9be..b287fc284a 100644 --- a/src/textbooks/hooks.jsx +++ b/src/textbooks/hooks.jsx @@ -77,6 +77,7 @@ const useTextbooks = (courseId) => { return { isLoading: loadingStatus === RequestStatus.IN_PROGRESS, + isLoadingFailed: loadingStatus === RequestStatus.FAILED, savingStatus, errorMessage, textbooks,