AA-790: progress tab handle unenrolled/unauthenticated users (#445)
This commit is contained in:
@@ -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",
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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`;
|
||||
|
||||
@@ -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`]);
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -24,7 +24,7 @@ AssignmentTypeCell.propTypes = {
|
||||
|
||||
AssignmentTypeCell.defaultProps = {
|
||||
footnoteId: '',
|
||||
footnoteMarker: '',
|
||||
footnoteMarker: null,
|
||||
};
|
||||
|
||||
export default AssignmentTypeCell;
|
||||
|
||||
@@ -38,7 +38,7 @@ function GradeSummaryTable({
|
||||
|
||||
const gradeSummaryData = assignmentPolicies.map((assignment) => {
|
||||
let footnoteId = '';
|
||||
let footnoteMarker = '';
|
||||
let footnoteMarker;
|
||||
|
||||
if (assignment.numDroppable > 0) {
|
||||
footnoteId = getFootnoteId(assignment);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user