AA-790: progress tab handle unenrolled/unauthenticated users (#445)

This commit is contained in:
Carla Duarte
2021-05-13 15:07:19 -04:00
committed by GitHub
parent 00205d4b1f
commit b1fdbcccf3
9 changed files with 206 additions and 22 deletions

View File

@@ -460,3 +460,151 @@ Object {
},
}
`;
exports[`Data layer integration tests Test fetchProgressTab Should fetch, normalize, and save metadata 1`] = `
Object {
"courseHome": Object {
"courseId": "course-v1:edX+DemoX+Demo_Course_1",
"courseStatus": "loaded",
"toastBodyLink": null,
"toastBodyText": null,
"toastHeader": "",
},
"courseware": Object {
"courseId": null,
"courseStatus": "loading",
"sequenceId": null,
"sequenceStatus": "loading",
},
"models": Object {
"courseHomeMeta": Object {
"course-v1:edX+DemoX+Demo_Course_1": Object {
"canLoadCourseware": false,
"id": "course-v1:edX+DemoX+Demo_Course_1",
"isEnrolled": false,
"isSelfPaced": false,
"isStaff": false,
"number": "DemoX",
"org": "edX",
"originalUserIsStaff": false,
"tabs": Array [
Object {
"slug": "outline",
"title": "Course",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course_1/course/",
},
Object {
"slug": "discussion",
"title": "Discussion",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course_1/discussion/forum/",
},
Object {
"slug": "wiki",
"title": "Wiki",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course_1/course_wiki",
},
Object {
"slug": "progress",
"title": "Progress",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course_1/progress",
},
Object {
"slug": "instructor",
"title": "Instructor",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course_1/instructor",
},
Object {
"slug": "dates",
"title": "Dates",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course_1/dates",
},
],
"title": "Demonstration Course",
"verifiedMode": Object {
"currencySymbol": "$",
"price": 10,
"upgradeUrl": "test",
},
},
},
"progress": Object {
"course-v1:edX+DemoX+Demo_Course_1": Object {
"certificateData": Object {},
"completionSummary": Object {
"completeCount": 1,
"incompleteCount": 1,
"lockedCount": 0,
},
"courseGrade": Object {
"isPassing": false,
"letterGrade": null,
"percent": 0,
},
"courseId": "course-v1:edX+DemoX+Demo_Course_1",
"end": "3027-03-31T00:00:00Z",
"enrollmentMode": "audit",
"gradingPolicy": Object {
"assignmentPolicies": Array [
Object {
"numDroppable": 1,
"shortLabel": "HW",
"type": "Homework",
"weight": 1,
},
],
"gradeRange": Object {
"pass": 0.75,
},
},
"hasScheduledContent": false,
"id": "course-v1:edX+DemoX+Demo_Course_1",
"sectionScores": Array [
Object {
"displayName": "First section",
"subsections": Array [
Object {
"assignmentType": "Homework",
"displayName": "First subsection",
"hasGradedAssignment": true,
"numPointsEarned": 0,
"numPointsPossible": 1,
"percentGraded": 0,
"showCorrectness": "always",
"showGrades": true,
"url": "http://learning.edx.org/course/course-v1:edX+Test+run/first_subsection",
},
],
},
Object {
"displayName": "Second section",
"subsections": Array [
Object {
"assignmentType": "Homework",
"displayName": "Second subsection",
"hasGradedAssignment": true,
"numPointsEarned": 1,
"numPointsPossible": 1,
"percentGraded": 1,
"showCorrectness": "always",
"showGrades": true,
"url": "http://learning.edx.org/course/course-v1:edX+Test+run/second_subsection",
},
],
},
],
"studioUrl": "http://studio.edx.org/settings/grading/course-v1:edX+Test+run",
"userHasPassingGrade": false,
"verificationData": Object {
"link": null,
"status": "none",
"statusDate": null,
},
"verifiedMode": null,
},
},
},
"recommendations": Object {
"recommendationsStatus": "loading",
},
}
`;

View File

@@ -118,8 +118,8 @@ export async function getDatesTabData(courseId) {
const { httpErrorStatus } = error && error.customAttributes;
if (httpErrorStatus === 404) {
global.location.replace(`${getConfig().LMS_BASE_URL}/courses/${courseId}/dates`);
return {};
}
// 401 can be returned for unauthenticated users or users who are not enrolled
if (httpErrorStatus === 401) {
global.location.replace(`${getConfig().BASE_URL}/course/${courseId}/home`);
}
@@ -139,6 +139,10 @@ export async function getProgressTabData(courseId) {
if (httpErrorStatus === 404) {
global.location.replace(`${getConfig().LMS_BASE_URL}/courses/${courseId}/progress`);
}
// 401 can be returned for unauthenticated users or users who are not enrolled
if (httpErrorStatus === 401) {
global.location.replace(`${getConfig().BASE_URL}/course/${courseId}/home`);
}
throw error;
}
}

View File

@@ -88,6 +88,35 @@ describe('Data layer integration tests', () => {
});
});
describe('Test fetchProgressTab', () => {
const progressBaseUrl = `${getConfig().LMS_BASE_URL}/api/course_home/v1/progress`;
it('Should result in fetch failure if error occurs', async () => {
axiosMock.onGet(courseMetadataUrl).networkError();
axiosMock.onGet(`${progressBaseUrl}/${courseId}`).networkError();
await executeThunk(thunks.fetchProgressTab(courseId), store.dispatch);
expect(loggingService.logError).toHaveBeenCalled();
expect(store.getState().courseHome.courseStatus).toEqual('failed');
});
it('Should fetch, normalize, and save metadata', async () => {
const progressTabData = Factory.build('progressTabData', { courseId });
const progressUrl = `${progressBaseUrl}/${courseId}`;
axiosMock.onGet(courseMetadataUrl).reply(200, courseHomeMetadata);
axiosMock.onGet(progressUrl).reply(200, progressTabData);
await executeThunk(thunks.fetchProgressTab(courseId), store.dispatch);
const state = store.getState();
expect(state.courseHome.courseStatus).toEqual('loaded');
expect(state).toMatchSnapshot();
});
});
describe('Test saveCourseGoal', () => {
it('Should save course goal', async () => {
const goalUrl = `${getConfig().LMS_BASE_URL}/api/course_home/v1/save_course_goal`;

View File

@@ -23,8 +23,11 @@ function CertificateStatus({ intl }) {
const {
certificateData,
end,
hasScheduledContent,
userHasPassingGrade,
verificationData,
verifiedMode,
} = useModel('progress', courseId);
const mode = getCourseExitMode(
@@ -35,16 +38,15 @@ function CertificateStatus({ intl }) {
);
const dispatch = useDispatch();
const {
end,
verificationData,
certificateData: {
certStatus,
certWebViewUrl,
downloadUrl,
},
verifiedMode,
} = useModel('progress', courseId);
let certStatus;
let certWebViewUrl;
let downloadUrl;
if (certificateData) {
certStatus = certificateData.certStatus;
certWebViewUrl = certificateData.certWebViewUrl;
downloadUrl = certificateData.downloadUrl;
}
let certCase;
let body;
@@ -66,7 +68,6 @@ function CertificateStatus({ intl }) {
} else if (mode === COURSE_EXIT_MODES.celebration) {
switch (certStatus) {
case 'requesting':
// Requestable
certCase = 'requestable';
buttonAction = () => { dispatch(requestCert(courseId)); };
body = intl.formatMessage(messages[`${certCase}Body`]);

View File

@@ -22,8 +22,8 @@ function CompletionDonutChart({ intl }) {
} = useModel('progress', courseId);
const numTotalUnits = completeCount + incompleteCount + lockedCount;
const completePercentage = Number(((completeCount / numTotalUnits) * 100).toFixed(0));
const lockedPercentage = Number(((lockedCount / numTotalUnits) * 100).toFixed(0));
const completePercentage = completeCount ? Number(((completeCount / numTotalUnits) * 100).toFixed(0)) : 0;
const lockedPercentage = lockedCount ? Number(((lockedCount / numTotalUnits) * 100).toFixed(0)) : 0;
const incompletePercentage = 100 - completePercentage - lockedPercentage;
return (

View File

@@ -24,7 +24,7 @@ AssignmentTypeCell.propTypes = {
AssignmentTypeCell.defaultProps = {
footnoteId: '',
footnoteMarker: '',
footnoteMarker: null,
};
export default AssignmentTypeCell;

View File

@@ -38,7 +38,7 @@ function GradeSummaryTable({
const gradeSummaryData = assignmentPolicies.map((assignment) => {
let footnoteId = '';
let footnoteMarker = '';
let footnoteMarker;
if (assignment.numDroppable > 0) {
footnoteId = getFootnoteId(assignment);

View File

@@ -188,22 +188,24 @@ function StreakModal({
StreakModal.defaultProps = {
isStreakCelebrationOpen: false,
streakLengthToCelebrate: -1,
verifiedMode: {},
AA759ExperimentEnabled: false,
};
StreakModal.propTypes = {
courseId: PropTypes.string.isRequired,
metadataModel: PropTypes.string.isRequired,
streakLengthToCelebrate: PropTypes.number.isRequired,
streakLengthToCelebrate: PropTypes.number,
intl: intlShape.isRequired,
isStreakCelebrationOpen: PropTypes.bool,
closeStreakCelebration: PropTypes.func.isRequired,
AA759ExperimentEnabled: PropTypes.bool,
verifiedMode: PropTypes.shape({
currencySymbol: PropTypes.string.isRequired,
price: PropTypes.number.isRequired,
upgradeUrl: PropTypes.string.isRequired,
}).isRequired,
currencySymbol: PropTypes.string,
price: PropTypes.number,
upgradeUrl: PropTypes.string,
}),
};
export default injectIntl(StreakModal);

View File

@@ -45,7 +45,7 @@ function LoadedTabPage({
return (
<>
<Helmet>
<title>{`${activeTab.title} | ${title} | ${getConfig().SITE_NAME}`}</title>
<title>{`${activeTab ? `${activeTab.title} | ` : ''}${title} | ${getConfig().SITE_NAME}`}</title>
</Helmet>
<Header
courseOrg={org}