+
{(modalState === modalStates.confirm) && (
)}
diff --git a/src/containers/UnenrollConfirmModal/index.test.jsx b/src/containers/UnenrollConfirmModal/index.test.jsx
index 96acd12..7008a4c 100644
--- a/src/containers/UnenrollConfirmModal/index.test.jsx
+++ b/src/containers/UnenrollConfirmModal/index.test.jsx
@@ -51,7 +51,7 @@ describe('UnenrollConfirmModal component', () => {
});
expect(shallow()).toMatchSnapshot();
});
- test('snapshot: modalStates.reason', () => {
+ test('snapshot: modalStates.reason, should be fullscreen with no shadow', () => {
hooks.useUnenrollData.mockReturnValueOnce({ ...hookProps, modalState: hooks.modalStates.reason });
expect(shallow()).toMatchSnapshot();
});
diff --git a/src/data/constants/requests.js b/src/data/constants/requests.js
index fc9d6d1..d02ba56 100644
--- a/src/data/constants/requests.js
+++ b/src/data/constants/requests.js
@@ -10,8 +10,11 @@ export const RequestStates = StrictDict({
export const RequestKeys = StrictDict({
initialize: 'initialize',
refreshList: 'refreshList',
- enrollEntitlementSession: 'enrollEntitlementSession',
- leaveEntitlementSession: 'leaveEntitlementSession',
+ newEntitlementEnrollment: 'newEntitlementEnrollment',
+ leaveEntitlementEnrollment: 'leaveEntitlementEnrollment',
+ switchEntitlementSession: 'switchEntitlementSession',
+ unenrollFromCourse: 'unenrollFromCourse',
+ updateEmailSettings: 'updateEmailSettings',
});
export const ErrorCodes = StrictDict({
diff --git a/src/data/redux/thunkActions/app.js b/src/data/redux/thunkActions/app.js
index 8452a50..b916025 100644
--- a/src/data/redux/thunkActions/app.js
+++ b/src/data/redux/thunkActions/app.js
@@ -1,4 +1,6 @@
import { StrictDict } from 'utils';
+import { handleEvent } from 'data/services/segment/utils';
+import { eventNames } from 'data/services/segment/constants';
import { actions, selectors } from 'data/redux';
import { post } from 'data/services/lms/utils';
@@ -36,25 +38,54 @@ export const sendConfirmEmail = () => (dispatch, getState) => post(
selectors.app.emailConfirmation(getState()).sendEmailUrl,
);
-export const updateEntitlementSession = (cardId, selection) => (dispatch, getState) => {
- const entitlement = selectors.app.courseCard.entitlement(getState(), cardId);
- const { uuid } = entitlement;
- console.log({
- cardId,
- selection,
- entitlement,
- uuid,
+export const newEntitlementEnrollment = (cardId, selection) => (dispatch, getState) => {
+ const { uuid } = selectors.app.courseCard.entitlement(getState(), cardId);
+ handleEvent(eventNames.sessionChange({ action: 'new' }), {
+ fromCourseRun: null,
+ toCourseRun: selection,
});
+ return dispatch(requests.newEntitlementEnrollment({ uuid, courseId: selection }));
};
-export const unenroll = (courseId) => (dispatch, getState) => post(
- selectors.app.courseCard.courseRun(getState(), courseId),
-).then(() => dispatch(module.refreshList()));
+export const switchEntitlementEnrollment = (cardId, selection) => (dispatch, getState) => {
+ const { courseId } = selectors.app.courseCard.courseRun(getState(), cardId);
+ const { uuid } = selectors.app.courseCard.entitlement(getState(), cardId);
+ handleEvent(eventNames.sessionChange({ action: 'switch' }), {
+ fromCourseRun: courseId,
+ toCourseRun: selection,
+ });
+ return dispatch(requests.switchEntitlementEnrollment({ uuid, courseId: selection }));
+};
+
+export const leaveEntitlementSession = (cardId) => (dispatch, getState) => {
+ const { courseId } = selectors.app.courseCard.courseRun(getState(), cardId);
+ const { uuid } = selectors.app.courseCard.entitlement(getState(), cardId);
+ handleEvent(eventNames.entitlementUnenroll({ action: 'leave' }), {
+ fromCourseRun: courseId,
+ toCourseRun: null,
+ });
+ return dispatch(requests.leaveEntitlementSession({ uuid }));
+};
+
+export const unenrollFromCourse = (courseId, reason) => (dispatch) => {
+ handleEvent(eventNames.unenrollReason, {
+ category: 'user-engagement',
+ displayName: 'v1',
+ label: reason,
+ course_id: courseId,
+ });
+ dispatch(requests.unenrollFromCourse({
+ courseId,
+ onSuccess: () => dispatch(module.refreshList()),
+ }));
+};
export default StrictDict({
initialize,
refreshList,
sendConfirmEmail,
- updateEntitlementSession,
- unenroll,
+ newEntitlementEnrollment,
+ switchEntitlementEnrollment,
+ leaveEntitlementSession,
+ unenrollFromCourse,
});
diff --git a/src/data/redux/thunkActions/requests.js b/src/data/redux/thunkActions/requests.js
index 4a10f09..92ba438 100644
--- a/src/data/redux/thunkActions/requests.js
+++ b/src/data/redux/thunkActions/requests.js
@@ -4,7 +4,7 @@ import { RequestKeys } from 'data/constants/requests';
import { actions } from 'data/redux';
import api from 'data/services/lms/api';
-// import * as module from './requests';
+import * as module from './requests';
/**
* Wrapper around a network request promise, that sends actions to the redux store to
@@ -33,44 +33,62 @@ export const networkRequest = ({
});
};
-export const initializeList = ({ onSuccess, onFailure }) => (dispatch) => {
- dispatch(networkRequest({
- requestKey: RequestKeys.initialize,
- onFailure,
- onSuccess,
- promise: api.initializeList(),
- }));
-};
+export const networkAction = (requestKey, promise, options) => (dispatch) => (
+ dispatch(module.networkRequest({
+ requestKey,
+ promise,
+ ...options,
+ })));
-export const updateEntitlementEnrollment = ({
+export const initializeList = (options) => module.networkAction(
+ RequestKeys.initialize,
+ api.initializeList(),
+ options,
+);
+
+export const newEntitlementEnrollment = ({
uuid,
courseId,
- onSuccess,
- onFailure,
-}) => (dispatch) => {
- dispatch(networkRequest({
- requestKey: RequestKeys.enrollEntitlementSession,
- onFailure,
- onSuccess,
- promise: api.updateEntitlementEnrollment({ uuid, courseId }),
- }));
-};
+ ...options
+}) => module.networkAction(
+ RequestKeys.newEntitlementEnrollment,
+ api.updateEntitlementEnrollment({ uuid, courseId }),
+ options,
+);
-export const leaveEntitlementSession = ({
+export const switchEntitlementEnrollment = ({
uuid,
- onSuccess,
- onFailure,
-}) => (dispatch) => {
- dispatch(networkRequest({
- requestKey: RequestKeys.leaveEntitlementSession,
- onFailure,
- onSuccess,
- promise: api.leaveEntitlementEnrollment({ uuid }),
- }));
-};
+ courseId,
+ ...options
+}) => module.networkAction(
+ RequestKeys.switchEntitlementSession,
+ api.updateEntitlementEnrollment({ uuid, courseId }),
+ options,
+);
+
+export const leaveEntitlementSession = ({ uuid, ...options }) => module.networkAction(
+ RequestKeys.leaveEntitlementSession,
+ api.leaveEntitlementEnrollment({ uuid }),
+ options,
+);
+
+export const unenrollFromCourse = ({ courseId, ...options }) => module.networkAction(
+ RequestKeys.unenrollFromCourse,
+ api.unenrollFromCourse({ courseId }),
+ options,
+);
+
+export const updateEmailSettings = ({ courseId, enable, ...options }) => module.networkAction(
+ RequestKeys.updateEmailSettings,
+ api.updateEmailSettings({ courseId, enable }),
+ options,
+);
export default StrictDict({
initializeList,
- updateEntitlementEnrollment,
leaveEntitlementSession,
+ newEntitlementEnrollment,
+ switchEntitlementEnrollment,
+ unenrollFromCourse,
+ updateEmailSettings,
});
diff --git a/src/data/services/lms/api.js b/src/data/services/lms/api.js
index f4fc78a..a21b386 100644
--- a/src/data/services/lms/api.js
+++ b/src/data/services/lms/api.js
@@ -21,8 +21,20 @@ const deleteEntitlementEnrollment = ({ uuid }) => client().delete(stringifyUrl(
{ course_run_id: null },
));
+const updateEmailSettings = ({ courseId, enable }) => post(stringifyUrl(
+ urls.updateEmailSettings,
+ { course_id: courseId, ...(enable && { receive_emails: 'on' }) },
+));
+
+const unenrollFromCourse = ({ courseId }) => post(stringifyUrl(
+ urls.unenrollFromCourse,
+ { course_id: courseId, enrollment_action: 'unenroll' },
+));
+
export default {
initializeList,
+ unenrollFromCourse,
+ updateEmailSettings,
updateEntitlementEnrollment,
deleteEntitlementEnrollment,
};
diff --git a/src/data/services/lms/urls.js b/src/data/services/lms/urls.js
index fb6b587..1463f78 100644
--- a/src/data/services/lms/urls.js
+++ b/src/data/services/lms/urls.js
@@ -6,10 +6,14 @@ const baseUrl = `${configuration.LMS_BASE_URL}`;
const api = `${baseUrl}/api/`;
const init = `${api}learner_home/mock/init`;
+const courseUnenroll = `${api}/courses/unenroll`; // TODO: Fix
+const updateEmailSettings = `${api}/change_email_settings`;
const entitlementEnrollment = (uuid) => `${api}/entitlements/v1/entitlements/${uuid}/enrollments`;
export default StrictDict({
api,
init,
+ courseUnenroll,
+ updateEmailSettings,
entitlementEnrollment,
});
diff --git a/src/data/services/segment/constants.js b/src/data/services/segment/constants.js
new file mode 100644
index 0000000..a6a39c3
--- /dev/null
+++ b/src/data/services/segment/constants.js
@@ -0,0 +1,19 @@
+import { StrictDict } from 'utils';
+
+export const events = StrictDict({
+ courseEnroll: 'courseEnroll',
+ entitlementUnenroll: 'entitlementUnenroll',
+ sessionChange: 'sessionChange',
+ unenrollReason: 'unenrollReason',
+});
+
+export const eventNames = StrictDict({
+ [events.courseEnroll]: 'edx.bi.user.program-details.enrollment',
+ [events.entitlementUnenroll]: 'entitlement_unenrollment_reason.selected',
+ [events.sessionChange]: ({ action }) => `course-dashboard.${action}-session`, // 'switch', 'new', 'leave'
+ [events.unenrollReason]: 'unenrollment_reason.selected',
+});
+
+export const trackingCategory = 'learner-home';
+
+export const pageViewEvent = { category: trackingCategory };
diff --git a/src/data/services/segment/utils.js b/src/data/services/segment/utils.js
new file mode 100755
index 0000000..6237c70
--- /dev/null
+++ b/src/data/services/segment/utils.js
@@ -0,0 +1,18 @@
+/* eslint-disable import/prefer-default-export */
+import { trackEvent } from '@redux-beacon/segment';
+import { trackingCategory as category } from './constants';
+
+export const handleEvent = (name, options = {}) => trackEvent(
+ (event = {}) => {
+ const { payload } = event;
+ const { propsFn, extrasFn } = options;
+ return {
+ name,
+ ...(extrasFn && extrasFn(payload)),
+ properties: {
+ category,
+ ...(propsFn && propsFn(payload)),
+ },
+ };
+ },
+);
diff --git a/src/data/services/segment/utils.test.js b/src/data/services/segment/utils.test.js
new file mode 100644
index 0000000..58ebfe2
--- /dev/null
+++ b/src/data/services/segment/utils.test.js
@@ -0,0 +1,49 @@
+import * as constants from './constants';
+import { handleEvent } from './utils';
+
+jest.mock('@redux-beacon/segment', () => ({
+ trackEvent: (handleFn) => ({ trackEvent: handleFn }),
+}));
+
+const category = 'AFakeCategory';
+describe('segment service utils', () => {
+ beforeAll(() => {
+ global.window = Object.create(window);
+ constants.trackingCategory = category;
+ });
+
+ describe('handleEvent', () => {
+ const name = 'aName';
+ const payload = { field1: 'some data', field2: 'other data' };
+ describe('when called with just a name', () => {
+ it('returns a TrackEvent call with the name and tracking category', () => {
+ const handler = handleEvent(name).trackEvent;
+ expect(handler(payload)).toEqual({
+ name,
+ properties: { category },
+ });
+ });
+ });
+ describe('when a propsFn is provided', () => {
+ it('adds the output of propsFn(event.payload) to properties', () => {
+ const propsFn = ({ field1 }) => ({ field1 });
+ const handler = handleEvent(name, { propsFn }).trackEvent;
+ expect(handler({ payload })).toEqual({
+ name,
+ properties: { category, field1: payload.field1 },
+ });
+ });
+ });
+ describe('when an extrasFn object is provided', () => {
+ it('adds the output of extrasFn(event.payload) to top-level object', () => {
+ const extrasFn = ({ field2 }) => ({ field2 });
+ const handler = handleEvent(name, { extrasFn }).trackEvent;
+ expect(handler({ payload })).toEqual({
+ name,
+ field2: payload.field2,
+ properties: { category },
+ });
+ });
+ });
+ });
+});