diff --git a/src/course-home/data/__snapshots__/redux.test.js.snap b/src/course-home/data/__snapshots__/redux.test.js.snap index 7a8a20b0..b6e46238 100644 --- a/src/course-home/data/__snapshots__/redux.test.js.snap +++ b/src/course-home/data/__snapshots__/redux.test.js.snap @@ -6,6 +6,7 @@ Object { "courseId": "course-v1:edX+DemoX+Demo_Course_1", "courseStatus": "loaded", "gradesFeatureIsLocked": false, + "targetUserId": undefined, "toastBodyLink": null, "toastBodyText": null, "toastHeader": "", @@ -302,6 +303,7 @@ Object { "courseId": "course-v1:edX+DemoX+Demo_Course_1", "courseStatus": "loaded", "gradesFeatureIsLocked": false, + "targetUserId": undefined, "toastBodyLink": null, "toastBodyText": null, "toastHeader": "", @@ -479,6 +481,7 @@ Object { "courseId": "course-v1:edX+DemoX+Demo_Course_1", "courseStatus": "loaded", "gradesFeatureIsLocked": false, + "targetUserId": undefined, "toastBodyLink": null, "toastBodyText": null, "toastHeader": "", diff --git a/src/course-home/data/api.js b/src/course-home/data/api.js index 73fa35aa..c06b6804 100644 --- a/src/course-home/data/api.js +++ b/src/course-home/data/api.js @@ -211,8 +211,15 @@ export async function getDatesTabData(courseId) { } } -export async function getProgressTabData(courseId) { - const url = `${getConfig().LMS_BASE_URL}/api/course_home/v1/progress/${courseId}`; +export async function getProgressTabData(courseId, targetUserId) { + let url = `${getConfig().LMS_BASE_URL}/api/course_home/v1/progress/${courseId}`; + + // If targetUserId is passed in, we will get the progress page data + // for the user with the provided id, rather than the requesting user. + if (targetUserId) { + url += `/${targetUserId}/`; + } + try { const { data } = await getAuthenticatedHttpClient().get(url); const camelCasedData = camelCaseObject(data); diff --git a/src/course-home/data/redux.test.js b/src/course-home/data/redux.test.js index 62bd0d6b..c4ae61d7 100644 --- a/src/course-home/data/redux.test.js +++ b/src/course-home/data/redux.test.js @@ -115,6 +115,20 @@ describe('Data layer integration tests', () => { expect(state.courseHome.courseStatus).toEqual('loaded'); expect(state).toMatchSnapshot(); }); + + it('Should handle the url including a targetUserId', async () => { + const progressTabData = Factory.build('progressTabData', { courseId }); + const targetUserId = 2; + const progressUrl = `${progressBaseUrl}/${courseId}/${targetUserId}/`; + + axiosMock.onGet(courseMetadataUrl).reply(200, courseHomeMetadata); + axiosMock.onGet(progressUrl).reply(200, progressTabData); + + await executeThunk(thunks.fetchProgressTab(courseId, 2), store.dispatch); + + const state = store.getState(); + expect(state.courseHome.targetUserId).toEqual(2); + }); }); describe('Test saveCourseGoal', () => { diff --git a/src/course-home/data/slice.js b/src/course-home/data/slice.js index 0cefaae3..4534887e 100644 --- a/src/course-home/data/slice.js +++ b/src/course-home/data/slice.js @@ -22,6 +22,7 @@ const slice = createSlice({ }, fetchTabSuccess: (state, { payload }) => { state.courseId = payload.courseId; + state.targetUserId = payload.targetUserId; state.courseStatus = LOADED; }, fetchTabFailure: (state, { payload }) => { diff --git a/src/course-home/data/thunks.js b/src/course-home/data/thunks.js index 44cabead..f95a7052 100644 --- a/src/course-home/data/thunks.js +++ b/src/course-home/data/thunks.js @@ -27,12 +27,12 @@ const eventTypes = { POST_EVENT: 'post_event', }; -export function fetchTab(courseId, tab, getTabData) { +export function fetchTab(courseId, tab, getTabData, targetUserId) { return async (dispatch) => { dispatch(fetchTabRequest({ courseId })); Promise.allSettled([ getCourseHomeCourseMetadata(courseId), - getTabData(courseId), + getTabData(courseId, targetUserId), ]).then(([courseHomeCourseMetadataResult, tabDataResult]) => { const fetchedCourseHomeCourseMetadata = courseHomeCourseMetadataResult.status === 'fulfilled'; const fetchedTabData = tabDataResult.status === 'fulfilled'; @@ -62,7 +62,7 @@ export function fetchTab(courseId, tab, getTabData) { } if (fetchedCourseHomeCourseMetadata && fetchedTabData) { - dispatch(fetchTabSuccess({ courseId })); + dispatch(fetchTabSuccess({ courseId, targetUserId })); } else { dispatch(fetchTabFailure({ courseId })); } @@ -74,8 +74,8 @@ export function fetchDatesTab(courseId) { return fetchTab(courseId, 'dates', getDatesTabData); } -export function fetchProgressTab(courseId) { - return fetchTab(courseId, 'progress', getProgressTabData); +export function fetchProgressTab(courseId, targetUserId) { + return fetchTab(courseId, 'progress', getProgressTabData, parseInt(targetUserId, 10) || targetUserId); } export function fetchOutlineTab(courseId) { diff --git a/src/course-home/progress-tab/ProgressHeader.jsx b/src/course-home/progress-tab/ProgressHeader.jsx index 2ff8bc32..38f9cfa3 100644 --- a/src/course-home/progress-tab/ProgressHeader.jsx +++ b/src/course-home/progress-tab/ProgressHeader.jsx @@ -12,16 +12,23 @@ import messages from './messages'; function ProgressHeader({ intl }) { const { courseId, + targetUserId, } = useSelector(state => state.courseHome); - const { administrator } = getAuthenticatedUser(); + const { administrator, userId } = getAuthenticatedUser(); - const { studioUrl } = useModel('progress', courseId); + const { studioUrl, username } = useModel('progress', courseId); + + const viewingOtherStudentsProgressPage = (targetUserId && targetUserId !== userId); + + const pageTitle = viewingOtherStudentsProgressPage + ? intl.formatMessage(messages.progressHeaderForTargetUser, { username }) + : intl.formatMessage(messages.progressHeader); return ( <>
-

{intl.formatMessage(messages.progressHeader)}

+

{pageTitle}

{administrator && studioUrl && (