diff --git a/src/course-home/data/__factories__/outlineTabData.factory.js b/src/course-home/data/__factories__/outlineTabData.factory.js index cf19b16f..d7a8b977 100644 --- a/src/course-home/data/__factories__/outlineTabData.factory.js +++ b/src/course-home/data/__factories__/outlineTabData.factory.js @@ -35,7 +35,7 @@ Factory.define('outlineTabData') })) .attrs({ access_expiration: null, - can_show_upgrade_sock: true, + can_show_upgrade_sock: false, course_goals: { goal_options: [], selected_goal: null, diff --git a/src/course-home/data/__snapshots__/redux.test.js.snap b/src/course-home/data/__snapshots__/redux.test.js.snap index 130b3ead..6e66374c 100644 --- a/src/course-home/data/__snapshots__/redux.test.js.snap +++ b/src/course-home/data/__snapshots__/redux.test.js.snap @@ -354,7 +354,7 @@ Object { "outline": Object { "course-v1:edX+DemoX+Demo_Course_1": Object { "accessExpiration": null, - "canShowUpgradeSock": true, + "canShowUpgradeSock": false, "courseBlocks": Object { "courses": Object { "block-v1:edX+DemoX+Demo_Course+type@course+block@bcdabcdabcdabcdabcdabcdabcdabcd3": Object { diff --git a/src/course-home/outline-tab/OutlineTab.test.jsx b/src/course-home/outline-tab/OutlineTab.test.jsx index dcd00790..ea46922d 100644 --- a/src/course-home/outline-tab/OutlineTab.test.jsx +++ b/src/course-home/outline-tab/OutlineTab.test.jsx @@ -1,6 +1,7 @@ import React from 'react'; import { Factory } from 'rosie'; import { getConfig } from '@edx/frontend-platform'; +import { sendTrackEvent, sendTrackingLogEvent } from '@edx/frontend-platform/analytics'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import MockAdapter from 'axios-mock-adapter'; import userEvent from '@testing-library/user-event'; @@ -661,4 +662,79 @@ describe('Outline Tab', () => { expect(screen.queryByText('Onboarding profile review, including identity verification, can take 2+ business days.')).not.toBeInTheDocument(); }); }); + + describe('Upgrade Card', () => { + it('renders title when upgrade is available', async () => { + await fetchAndRender(); + expect(screen.queryByRole('heading', { name: 'Pursue a verified certificate' })).toBeInTheDocument(); + }); + + it('displays link to upgrade', async () => { + await fetchAndRender(); + expect(screen.getByRole('link', { name: 'Upgrade ($149)' })).toBeInTheDocument(); + }); + + it('viewing upgrade card sends analytics', async () => { + sendTrackEvent.mockClear(); + sendTrackingLogEvent.mockClear(); + await fetchAndRender(); + + expect(sendTrackEvent).toHaveBeenCalledTimes(1); + expect(sendTrackEvent).toHaveBeenCalledWith('Promotion Viewed', { + org_key: 'edX', + courserun_key: courseId, + creative: 'sidebarupsell', + name: 'In-Course Verification Prompt', + position: 'sidebar-message', + promotion_id: 'courseware_verified_certificate_upsell', + }); + + expect(sendTrackingLogEvent).toHaveBeenCalledTimes(1); + expect(sendTrackingLogEvent).toHaveBeenCalledWith('edx.bi.course.upgrade.sidebarupsell.displayed', { + org_key: 'edX', + courserun_key: courseId, + }); + }); + + it('clicking upgrade link sends analytics', async () => { + sendTrackEvent.mockClear(); + sendTrackingLogEvent.mockClear(); + + await fetchAndRender(); + const upgradeButton = screen.getByRole('link', { name: 'Upgrade ($149)' }); + + fireEvent.click(upgradeButton); + + // 3 sendTrackEvent calls are expected because 1 happens on render, and 2 happen onClick + expect(sendTrackEvent).toHaveBeenCalledTimes(3); + expect(sendTrackEvent).toHaveBeenNthCalledWith(2, 'Promotion Clicked', { + org_key: 'edX', + courserun_key: courseId, + creative: 'sidebarupsell', + name: 'In-Course Verification Prompt', + position: 'sidebar-message', + promotion_id: 'courseware_verified_certificate_upsell', + }); + expect(sendTrackEvent).toHaveBeenNthCalledWith(3, 'edx.bi.ecommerce.upsell_links_clicked', { + org_key: 'edX', + courserun_key: courseId, + linkCategory: 'green_upgrade', + linkName: 'course_home_green', + linkType: 'button', + pageName: 'course_home', + }); + + // 3 sendTrackingLogEvent calls are expected because 1 happens on render, and 2 happen onClick + expect(sendTrackingLogEvent).toHaveBeenCalledTimes(3); + expect(sendTrackingLogEvent).toHaveBeenNthCalledWith(2, 'edx.bi.course.upgrade.sidebarupsell.clicked', { + org_key: 'edX', + courserun_key: courseId, + }); + expect(sendTrackingLogEvent).toHaveBeenNthCalledWith(3, 'edx.course.enrollment.upgrade.clicked', { + org_key: 'edX', + courserun_key: courseId, + location: 'sidebar-message', + }); + }); + }); }); diff --git a/src/course-home/outline-tab/widgets/UpgradeCard.jsx b/src/course-home/outline-tab/widgets/UpgradeCard.jsx index d6826f57..fd753fd8 100644 --- a/src/course-home/outline-tab/widgets/UpgradeCard.jsx +++ b/src/course-home/outline-tab/widgets/UpgradeCard.jsx @@ -37,7 +37,7 @@ function UpgradeCard({ courseId, intl, onLearnMore }) { useEffect(() => { sendTrackingLogEvent('edx.bi.course.upgrade.sidebarupsell.displayed', eventProperties); sendTrackEvent('Promotion Viewed', promotionEventProperties); - }); + }, []); const logClick = () => { sendTrackingLogEvent('edx.bi.course.upgrade.sidebarupsell.clicked', eventProperties); @@ -46,6 +46,13 @@ function UpgradeCard({ courseId, intl, onLearnMore }) { location: 'sidebar-message', }); sendTrackEvent('Promotion Clicked', promotionEventProperties); + sendTrackEvent('edx.bi.ecommerce.upsell_links_clicked', { + ...eventProperties, + linkCategory: 'green_upgrade', + linkName: 'course_home_green', + linkType: 'button', + pageName: 'course_home', + }); }; return ( diff --git a/src/courseware/course/sequence/lock-paywall/LockPaywall.jsx b/src/courseware/course/sequence/lock-paywall/LockPaywall.jsx index 31565c20..53bcb631 100644 --- a/src/courseware/course/sequence/lock-paywall/LockPaywall.jsx +++ b/src/courseware/course/sequence/lock-paywall/LockPaywall.jsx @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faLock } from '@fortawesome/free-solid-svg-icons'; +import { sendTrackEvent } from '@edx/frontend-platform/analytics'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import messages from './messages'; @@ -14,6 +15,7 @@ function LockPaywall({ }) { const course = useModel('coursewareMeta', courseId); const { + org, verifiedMode, } = course; @@ -25,6 +27,21 @@ function LockPaywall({ price, upgradeUrl, } = verifiedMode; + + const eventProperties = { + org_key: org, + courserun_key: courseId, + }; + + const logClick = () => { + sendTrackEvent('edx.bi.ecommerce.upsell_links_clicked', { + ...eventProperties, + linkCategory: '(none)', + linkName: 'in_course_upgrade', + linkType: 'link', + pageName: 'in_course', + }); + }; return (
{intl.formatMessage(messages['learn.lockPaywall.content'])}
-
+
{intl.formatMessage(messages['learn.lockPaywall.upgrade.link'], {
currencySymbol,
price,
diff --git a/src/courseware/course/sequence/lock-paywall/LockPaywall.test.jsx b/src/courseware/course/sequence/lock-paywall/LockPaywall.test.jsx
index 3f855041..65da4ca5 100644
--- a/src/courseware/course/sequence/lock-paywall/LockPaywall.test.jsx
+++ b/src/courseware/course/sequence/lock-paywall/LockPaywall.test.jsx
@@ -1,8 +1,14 @@
import React from 'react';
import { Factory } from 'rosie';
-import { initializeTestStore, render, screen } from '../../../../setupTest';
+import { sendTrackEvent } from '@edx/frontend-platform/analytics';
+
+import {
+ fireEvent, initializeTestStore, render, screen,
+} from '../../../../setupTest';
import LockPaywall from './LockPaywall';
+jest.mock('@edx/frontend-platform/analytics');
+
describe('Lock Paywall', () => {
let store;
const mockData = {};
@@ -33,6 +39,29 @@ describe('Lock Paywall', () => {
expect(upgradeLink).toHaveAttribute('href', `${upgradeUrl}`);
});
+ it('sends analytics event onClick of unlock link', () => {
+ sendTrackEvent.mockClear();
+
+ const {
+ currencySymbol,
+ price,
+ } = store.getState().models.coursewareMeta[mockData.courseId].verifiedMode;
+ render(