AA-287 cta events (#177)
This commit is contained in:
@@ -5,7 +5,8 @@ Object {
|
||||
"courseHome": Object {
|
||||
"courseId": null,
|
||||
"courseStatus": "loading",
|
||||
"displayResetDatesToast": false,
|
||||
"resetDatesToastBody": null,
|
||||
"resetDatesToastHeader": null,
|
||||
},
|
||||
"courseware": Object {
|
||||
"courseId": null,
|
||||
@@ -22,7 +23,8 @@ Object {
|
||||
"courseHome": Object {
|
||||
"courseId": "course-v1:edX+DemoX+Demo_Course_1",
|
||||
"courseStatus": "loaded",
|
||||
"displayResetDatesToast": false,
|
||||
"resetDatesToastBody": null,
|
||||
"resetDatesToastHeader": null,
|
||||
},
|
||||
"courseware": Object {
|
||||
"courseId": null,
|
||||
@@ -106,7 +108,8 @@ Object {
|
||||
"courseHome": Object {
|
||||
"courseId": "course-v1:edX+DemoX+Demo_Course_1",
|
||||
"courseStatus": "loaded",
|
||||
"displayResetDatesToast": false,
|
||||
"resetDatesToastBody": null,
|
||||
"resetDatesToastHeader": null,
|
||||
},
|
||||
"courseware": Object {
|
||||
"courseId": null,
|
||||
|
||||
@@ -93,7 +93,7 @@ export async function getOutlineTabData(courseId) {
|
||||
|
||||
export async function postCourseDeadlines(courseId) {
|
||||
const url = new URL(`${getConfig().LMS_BASE_URL}/api/course_experience/v1/reset_course_deadlines`);
|
||||
await getAuthenticatedHttpClient().post(url.href, { course_key: courseId });
|
||||
return getAuthenticatedHttpClient().post(url.href, { course_key: courseId });
|
||||
}
|
||||
|
||||
export async function postDismissWelcomeMessage(courseId) {
|
||||
@@ -105,3 +105,8 @@ export async function postRequestCert(courseId) {
|
||||
const url = new URL(`${getConfig().LMS_BASE_URL}/courses/${courseId}/generate_user_cert`);
|
||||
await getAuthenticatedHttpClient().post(url.href);
|
||||
}
|
||||
|
||||
export async function executePostFromPostEvent(postData) {
|
||||
const url = new URL(postData.url);
|
||||
return getAuthenticatedHttpClient().post(url.href, { course_key: postData.bodyParams.courseId });
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ describe('Data layer integration tests', () => {
|
||||
describe('Test resetDeadlines', () => {
|
||||
it('Should reset course deadlines', async () => {
|
||||
const resetUrl = `${getConfig().LMS_BASE_URL}/api/course_experience/v1/reset_course_deadlines`;
|
||||
axiosMock.onPost(resetUrl).reply(201);
|
||||
axiosMock.onPost(resetUrl).reply(201, {});
|
||||
|
||||
const getTabDataMock = jest.fn(() => ({
|
||||
type: 'MOCK_ACTION',
|
||||
|
||||
@@ -10,7 +10,8 @@ const slice = createSlice({
|
||||
initialState: {
|
||||
courseStatus: 'loading',
|
||||
courseId: null,
|
||||
displayResetDatesToast: false,
|
||||
resetDatesToastBody: null,
|
||||
resetDatesToastHeader: null,
|
||||
},
|
||||
reducers: {
|
||||
fetchTabRequest: (state, { payload }) => {
|
||||
@@ -25,8 +26,13 @@ const slice = createSlice({
|
||||
state.courseId = payload.courseId;
|
||||
state.courseStatus = FAILED;
|
||||
},
|
||||
toggleResetDatesToast: (state, { payload }) => {
|
||||
state.displayResetDatesToast = payload.displayResetDatesToast;
|
||||
setResetDatesToast: (state, { payload }) => {
|
||||
const {
|
||||
body,
|
||||
header,
|
||||
} = payload;
|
||||
state.resetDatesToastBody = body;
|
||||
state.resetDatesToastHeader = header;
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -35,7 +41,7 @@ export const {
|
||||
fetchTabRequest,
|
||||
fetchTabSuccess,
|
||||
fetchTabFailure,
|
||||
toggleResetDatesToast,
|
||||
setResetDatesToast,
|
||||
} = slice.actions;
|
||||
|
||||
export const {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { logError } from '@edx/frontend-platform/logging';
|
||||
import { camelCaseObject } from '@edx/frontend-platform';
|
||||
import {
|
||||
executePostFromPostEvent,
|
||||
getCourseHomeCourseMetadata,
|
||||
getDatesTabData,
|
||||
getOutlineTabData,
|
||||
@@ -17,9 +19,13 @@ import {
|
||||
fetchTabFailure,
|
||||
fetchTabRequest,
|
||||
fetchTabSuccess,
|
||||
toggleResetDatesToast,
|
||||
setResetDatesToast,
|
||||
} from './slice';
|
||||
|
||||
const eventTypes = {
|
||||
POST_EVENT: 'post_event',
|
||||
};
|
||||
|
||||
export function fetchTab(courseId, tab, getTabData) {
|
||||
return async (dispatch) => {
|
||||
dispatch(fetchTabRequest({ courseId }));
|
||||
@@ -77,9 +83,14 @@ export function fetchOutlineTab(courseId) {
|
||||
|
||||
export function resetDeadlines(courseId, getTabData) {
|
||||
return async (dispatch) => {
|
||||
postCourseDeadlines(courseId).then(() => {
|
||||
postCourseDeadlines(courseId).then(response => {
|
||||
const { data } = response;
|
||||
const {
|
||||
body,
|
||||
header,
|
||||
} = data;
|
||||
dispatch(getTabData(courseId));
|
||||
dispatch(toggleResetDatesToast({ displayResetDatesToast: true }));
|
||||
dispatch(setResetDatesToast({ body, header }));
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -91,3 +102,20 @@ export function dismissWelcomeMessage(courseId) {
|
||||
export function requestCert(courseId) {
|
||||
return async () => postRequestCert(courseId);
|
||||
}
|
||||
|
||||
export function processEvent(eventData, getTabData) {
|
||||
return async (dispatch) => {
|
||||
const event = camelCaseObject(eventData);
|
||||
if (event.eventName === eventTypes.POST_EVENT) {
|
||||
executePostFromPostEvent(event.postData).then(response => {
|
||||
const { data } = response;
|
||||
const {
|
||||
body,
|
||||
header,
|
||||
} = data;
|
||||
dispatch(getTabData(event.postData.bodyParams.courseId));
|
||||
dispatch(setResetDatesToast({ body, header }));
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import messages from './messages';
|
||||
import BookmarkButton from '../bookmark/BookmarkButton';
|
||||
import { useModel } from '../../../generic/model-store';
|
||||
import PageLoading from '../../../generic/PageLoading';
|
||||
import { resetDeadlines } from '../../../course-home/data/thunks';
|
||||
import { processEvent } from '../../../course-home/data/thunks';
|
||||
import { fetchCourse } from '../../data/thunks';
|
||||
|
||||
const LockPaywall = React.lazy(() => import('./lock-paywall'));
|
||||
@@ -138,8 +138,8 @@ function Unit({
|
||||
referrerPolicy="origin"
|
||||
onLoad={() => {
|
||||
window.onmessage = function (e) {
|
||||
if (e.data === 'reset_dates') {
|
||||
dispatch(resetDeadlines(courseId, fetchCourse));
|
||||
if (e.data.event_name) {
|
||||
dispatch(processEvent(e.data, fetchCourse));
|
||||
}
|
||||
};
|
||||
}}
|
||||
|
||||
@@ -9,16 +9,16 @@ import PageLoading from '../generic/PageLoading';
|
||||
import messages from './messages';
|
||||
import LoadedTabPage from './LoadedTabPage';
|
||||
import LearningToast from '../toast/LearningToast';
|
||||
import { toggleResetDatesToast } from '../course-home/data/slice';
|
||||
import { setResetDatesToast } from '../course-home/data/slice';
|
||||
|
||||
function TabPage({
|
||||
intl,
|
||||
courseStatus,
|
||||
...passthroughProps
|
||||
}) {
|
||||
const courseId = useSelector(state => state.courseHome.courseId || state.courseware.courseId);
|
||||
const {
|
||||
displayResetDatesToast,
|
||||
resetDatesToastBody,
|
||||
resetDatesToastHeader,
|
||||
} = useSelector(state => state.courseHome);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
@@ -37,11 +37,10 @@ function TabPage({
|
||||
return (
|
||||
<>
|
||||
<LearningToast
|
||||
body={messages.datesResetSuccessBody}
|
||||
header={messages.datesResetSuccessHeader}
|
||||
link={`/course/${courseId}/dates`}
|
||||
onClose={() => dispatch(toggleResetDatesToast({ displayResetDatesToast: false }))}
|
||||
show={displayResetDatesToast}
|
||||
body={resetDatesToastBody}
|
||||
header={resetDatesToastHeader}
|
||||
onClose={() => dispatch(setResetDatesToast({ body: null, header: null }))}
|
||||
show={!!(resetDatesToastBody && resetDatesToastHeader)}
|
||||
/>
|
||||
<LoadedTabPage {...passthroughProps} />
|
||||
</>
|
||||
|
||||
@@ -9,14 +9,6 @@ const messages = defineMessages({
|
||||
id: 'learning.loading',
|
||||
defaultMessage: 'Loading course page…',
|
||||
},
|
||||
datesResetSuccessBody: {
|
||||
id: 'learning.datesResetSuccessBody',
|
||||
defaultMessage: 'View all dates',
|
||||
},
|
||||
datesResetSuccessHeader: {
|
||||
id: 'learning.datesResetSuccessHeader',
|
||||
defaultMessage: 'Your due dates have been successfully shifted to help you stay on track.',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Toast } from '@edx/paragon';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import './LearningToast.scss';
|
||||
|
||||
function LearningToast({
|
||||
intl,
|
||||
export default function LearningToast({
|
||||
body,
|
||||
header,
|
||||
link,
|
||||
onClose,
|
||||
show,
|
||||
}) {
|
||||
@@ -29,29 +25,23 @@ function LearningToast({
|
||||
}}
|
||||
>
|
||||
<Toast.Header className="bg-gray-700 border-bottom-0 text-light">
|
||||
<div className="mr-auto">{intl.formatMessage(header)}</div>
|
||||
<div dangerouslySetInnerHTML={{ __html: header }} />
|
||||
</Toast.Header>
|
||||
<Toast.Body className="bg-gray-700 text-light">
|
||||
<Link className="text-light" to={link}>{intl.formatMessage(body)}</Link>
|
||||
<div dangerouslySetInnerHTML={{ __html: body }} />
|
||||
</Toast.Body>
|
||||
</Toast>
|
||||
);
|
||||
}
|
||||
|
||||
LearningToast.propTypes = {
|
||||
body: PropTypes.shape({
|
||||
possible: PropTypes.number,
|
||||
earned: PropTypes.number,
|
||||
graded: PropTypes.bool,
|
||||
}).isRequired,
|
||||
header: PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
defaultMessage: PropTypes.string,
|
||||
}).isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
link: PropTypes.string.isRequired,
|
||||
body: PropTypes.string,
|
||||
header: PropTypes.string,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
show: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(LearningToast);
|
||||
LearningToast.defaultProps = {
|
||||
body: null,
|
||||
header: null,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user