AA-712: upsell link click tracking (#393)

* AA-712: course_home_audit_access_expires and in_course_audit_access_expires

* AA-712: course_home_welcome and in_course_welcome

* AA-712: course_home_dates

* AA-712: course_home_course_tools

* AA-712: course_home_upgrade_shift_dates and dates_upgrade

* AA-712: fixing up PR comments
This commit is contained in:
Carla Duarte
2021-03-19 12:36:36 -04:00
committed by GitHub
parent 45a68973b7
commit b12f184d18
15 changed files with 533 additions and 45 deletions

View File

@@ -1,5 +1,6 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import {
FormattedMessage, FormattedDate, injectIntl, intlShape,
} from '@edx/frontend-platform/i18n';
@@ -21,7 +22,10 @@ function AccessExpirationAlert({ intl, payload }) {
const {
accessExpiration,
courseId,
org,
userTimezone,
analyticsPageName,
} = payload;
const timezoneFormatArgs = userTimezone ? { timeZone: userTimezone } : {};
@@ -66,6 +70,17 @@ function AccessExpirationAlert({ intl, payload }) {
);
}
const logClick = () => {
sendTrackEvent('edx.bi.ecommerce.upsell_links_clicked', {
org_key: org,
courserun_key: courseId,
linkCategory: 'FBE_banner',
linkName: `${analyticsPageName}_audit_access_expires`,
linkType: 'link',
pageName: analyticsPageName,
});
};
let deadlineMessage = null;
if (upgradeDeadline && upgradeUrl) {
deadlineMessage = (
@@ -92,6 +107,7 @@ function AccessExpirationAlert({ intl, payload }) {
className="font-weight-bold"
style={{ textDecoration: 'underline' }}
destination={upgradeUrl}
onClick={logClick}
>
{intl.formatMessage(messages.upgradeNow)}
</Hyperlink>
@@ -150,7 +166,10 @@ AccessExpirationAlert.propTypes = {
upgradeDeadline: PropTypes.string,
upgradeUrl: PropTypes.string,
}).isRequired,
courseId: PropTypes.string.isRequired,
org: PropTypes.string.isRequired,
userTimezone: PropTypes.string.isRequired,
analyticsPageName: PropTypes.string.isRequired,
}).isRequired,
};

View File

@@ -3,11 +3,14 @@ import { useAlert } from '../../generic/user-messages';
const AccessExpirationAlert = React.lazy(() => import('./AccessExpirationAlert'));
function useAccessExpirationAlert(accessExpiration, userTimezone, topic) {
function useAccessExpirationAlert(accessExpiration, courseId, org, userTimezone, topic, analyticsPageName) {
const isVisible = !!accessExpiration; // If it exists, show it.
const payload = {
accessExpiration,
courseId,
org,
userTimezone,
analyticsPageName,
};
useAlert(isVisible, {

View File

@@ -1,5 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import {
FormattedMessage, FormattedDate, injectIntl, intlShape,
} from '@edx/frontend-platform/i18n';
@@ -11,7 +12,10 @@ import messages from './messages';
function OfferAlert({ intl, payload }) {
const {
analyticsPageName,
courseId,
offer,
org,
userTimezone,
} = payload;
@@ -27,6 +31,17 @@ function OfferAlert({ intl, payload }) {
} = offer;
const timezoneFormatArgs = userTimezone ? { timeZone: userTimezone } : {};
const logClick = () => {
sendTrackEvent('edx.bi.ecommerce.upsell_links_clicked', {
org_key: org,
courserun_key: courseId,
linkCategory: 'welcome',
linkName: `${analyticsPageName}_welcome`,
linkType: 'link',
pageName: analyticsPageName,
});
};
return (
<Alert type={ALERT_TYPES.INFO}>
<span className="font-weight-bold">
@@ -61,6 +76,7 @@ function OfferAlert({ intl, payload }) {
className="font-weight-bold"
style={{ textDecoration: 'underline' }}
destination={upgradeUrl}
onClick={logClick}
>
{intl.formatMessage(messages.upgradeNow)}
</Hyperlink>
@@ -71,6 +87,7 @@ function OfferAlert({ intl, payload }) {
OfferAlert.propTypes = {
intl: intlShape.isRequired,
payload: PropTypes.shape({
courseId: PropTypes.string.isRequired,
offer: PropTypes.shape({
code: PropTypes.string.isRequired,
discountedPrice: PropTypes.string.isRequired,
@@ -79,7 +96,9 @@ OfferAlert.propTypes = {
percentage: PropTypes.number.isRequired,
upgradeUrl: PropTypes.string.isRequired,
}).isRequired,
org: PropTypes.string.isRequired,
userTimezone: PropTypes.string.isRequired,
analyticsPageName: PropTypes.string.isRequired,
}).isRequired,
};

View File

@@ -3,10 +3,13 @@ import { useAlert } from '../../generic/user-messages';
const OfferAlert = React.lazy(() => import('./OfferAlert'));
export function useOfferAlert(offer, userTimezone, topic) {
export function useOfferAlert(courseId, offer, org, userTimezone, topic, analyticsPageName) {
const isVisible = !!offer; // if it exists, show it.
const payload = {
analyticsPageName,
courseId,
offer,
org,
userTimezone,
};

View File

@@ -5,19 +5,14 @@ import buildSimpleCourseBlocks from './courseBlocks.factory';
Factory.define('outlineTabData')
.option('courseId', 'course-v1:edX+DemoX+Demo_Course')
.option('host', 'http://localhost:18000')
.option('dateBlocks', [])
.attr('course_tools', ['host', 'courseId'], (host, courseId) => ([{
analytics_id: 'edx.bookmarks',
title: 'Bookmarks',
url: `${host}/courses/${courseId}/bookmarks/`,
}]))
.option('date_blocks', [])
.attr('course_blocks', ['courseId'], courseId => {
const { courseBlocks } = buildSimpleCourseBlocks(courseId);
return {
blocks: courseBlocks.blocks,
};
})
.attr('dates_widget', ['dateBlocks'], (dateBlocks) => ({
.attr('dates_widget', ['date_blocks'], (dateBlocks) => ({
course_date_blocks: dateBlocks,
user_timezone: 'UTC',
}))
@@ -40,6 +35,18 @@ Factory.define('outlineTabData')
goal_options: [],
selected_goal: null,
},
course_tools: [
{
analytics_id: 'edx.bookmarks',
title: 'Bookmarks',
url: 'https://example.com/bookmarks',
},
{
analytics_id: 'edx.tool.verified_upgrade',
title: 'Upgrade to Verified',
url: 'https://example.com/upgrade',
},
],
dates_banner_info: {
content_type_gating_enabled: false,
missed_gated_content: false,

View File

@@ -405,7 +405,12 @@ Object {
Object {
"analyticsId": "edx.bookmarks",
"title": "Bookmarks",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/bookmarks/",
"url": "https://example.com/bookmarks",
},
Object {
"analyticsId": "edx.tool.verified_upgrade",
"title": "Upgrade to Verified",
"url": "https://example.com/upgrade",
},
],
"datesBannerInfo": Object {

View File

@@ -11,6 +11,7 @@ function DatesBannerContainer({
courseDateBlocks,
datesBannerInfo,
hasEnded,
logUpgradeLinkClick,
model,
tabFetch,
}) {
@@ -43,13 +44,19 @@ function DatesBannerContainer({
name: 'upgradeToCompleteGradedBanner',
// verifiedUpgradeLink can be null if we've passed the upgrade deadline
shouldDisplay: upgradeToCompleteGraded && verifiedUpgradeLink,
clickHandler: () => global.location.replace(verifiedUpgradeLink),
clickHandler: () => {
logUpgradeLinkClick();
global.location.replace(verifiedUpgradeLink);
},
},
{
name: 'upgradeToResetBanner',
// verifiedUpgradeLink can be null if we've passed the upgrade deadline
shouldDisplay: upgradeToReset && verifiedUpgradeLink,
clickHandler: () => global.location.replace(verifiedUpgradeLink),
clickHandler: () => {
logUpgradeLinkClick();
global.location.replace(verifiedUpgradeLink);
},
},
{
name: 'resetDatesBanner',
@@ -80,12 +87,14 @@ DatesBannerContainer.propTypes = {
verifiedUpgradeLink: PropTypes.string,
}).isRequired,
hasEnded: PropTypes.bool,
logUpgradeLinkClick: PropTypes.func,
model: PropTypes.string.isRequired,
tabFetch: PropTypes.func.isRequired,
};
DatesBannerContainer.defaultProps = {
hasEnded: false,
logUpgradeLinkClick: () => {},
};
export default DatesBannerContainer;

View File

@@ -1,5 +1,6 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import messages from './messages';
@@ -17,6 +18,10 @@ function DatesTab({ intl }) {
courseId,
} = useSelector(state => state.courseHome);
const {
org,
} = useModel('courseHomeMeta', courseId);
const {
courseDateBlocks,
datesBannerInfo,
@@ -26,6 +31,17 @@ function DatesTab({ intl }) {
/** [MM-P2P] Experiment */
const mmp2p = initDatesMMP2P(courseId);
const logUpgradeLinkClick = () => {
sendTrackEvent('edx.bi.ecommerce.upsell_links_clicked', {
org_key: org,
courserun_key: courseId,
linkCategory: 'personalized_learner_schedules',
linkName: 'dates_upgrade',
linkType: 'button',
pageName: 'dates_tab',
});
};
return (
<>
<div role="heading" aria-level="1" className="h2 my-3">
@@ -37,6 +53,7 @@ function DatesTab({ intl }) {
courseDateBlocks={courseDateBlocks}
datesBannerInfo={datesBannerInfo}
hasEnded={hasEnded}
logUpgradeLinkClick={logUpgradeLinkClick}
model="dates"
tabFetch={fetchDatesTab}
/>

View File

@@ -3,6 +3,7 @@ import { Route } from 'react-router';
import MockAdapter from 'axios-mock-adapter';
import { Factory } from 'rosie';
import { getConfig, history } from '@edx/frontend-platform';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { AppProvider } from '@edx/frontend-platform/react';
import { waitForElementToBeRemoved } from '@testing-library/dom';
@@ -18,6 +19,7 @@ import { appendBrowserTimezoneToUrl } from '../../utils';
import { UserMessagesProvider } from '../../generic/user-messages';
initializeMockApp();
jest.mock('@edx/frontend-platform/analytics');
describe('DatesTab', () => {
let axiosMock;
@@ -241,5 +243,57 @@ describe('DatesTab', () => {
// confirm "Shift due dates" button has not rendered
expect(screen.queryByRole('button', { name: 'Shift due dates' })).not.toBeInTheDocument();
});
it('sends analytics event onClick of upgrade button in upgradeToCompleteGradedBanner', async () => {
sendTrackEvent.mockClear();
datesTabData.datesBannerInfo = {
contentTypeGatingEnabled: true,
missedDeadlines: false,
missedGatedContent: false,
verifiedUpgradeLink: 'http://localhost:18130/basket/add/?sku=8CF08E5',
};
axiosMock.onGet(`${getConfig().LMS_BASE_URL}/api/course_home/v1/dates/${courseId}`).reply(200, datesTabData);
render(component);
const upgradeButton = await waitFor(() => screen.getByRole('button', { name: 'Upgrade now' }));
fireEvent.click(upgradeButton);
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.ecommerce.upsell_links_clicked', {
org_key: 'edX',
courserun_key: courseId,
linkCategory: 'personalized_learner_schedules',
linkName: 'dates_upgrade',
linkType: 'button',
pageName: 'dates_tab',
});
});
it('sends analytics event onClick of upgrade button in upgradeToResetBanner', async () => {
sendTrackEvent.mockClear();
datesTabData.datesBannerInfo = {
contentTypeGatingEnabled: true,
missedDeadlines: true,
missedGatedContent: true,
verifiedUpgradeLink: 'http://localhost:18130/basket/add/?sku=8CF08E5',
};
axiosMock.onGet(`${getConfig().LMS_BASE_URL}/api/course_home/v1/dates/${courseId}`).reply(200, datesTabData);
render(component);
const upgradeButton = await waitFor(() => screen.getByRole('button', { name: 'Upgrade to shift due dates' }));
fireEvent.click(upgradeButton);
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.ecommerce.upsell_links_clicked', {
org_key: 'edX',
courserun_key: courseId,
linkCategory: 'personalized_learner_schedules',
linkName: 'dates_upgrade',
linkType: 'button',
pageName: 'dates_tab',
});
});
});
});

View File

@@ -1,8 +1,11 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCalendarAlt } from '@fortawesome/free-regular-svg-icons';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { FormattedDate } from '@edx/frontend-platform/i18n';
import React from 'react';
import { useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import { useModel } from '../../generic/model-store';
import { isLearnerAssignment } from '../dates-tab/utils';
import './DateSummary.scss';
@@ -12,12 +15,30 @@ export default function DateSummary({
/** [MM-P2P] Experiment */
mmp2p,
}) {
const {
courseId,
} = useSelector(state => state.courseHome);
const {
org,
} = useModel('courseHomeMeta', courseId);
const linkedTitle = dateBlock.link && isLearnerAssignment(dateBlock);
const timezoneFormatArgs = userTimezone ? { timeZone: userTimezone } : {};
/** [MM-P2P] Experiment */
const showMMP2P = mmp2p.state.isEnabled && (dateBlock.dateType === 'verified-upgrade-deadline');
const logVerifiedUpgradeClick = () => {
sendTrackEvent('edx.bi.ecommerce.upsell_links_clicked', {
org_key: org,
courserun_key: courseId,
linkCategory: '(none)',
linkName: 'course_home_dates',
linkType: 'link',
pageName: 'course_home',
});
};
return (
<li className="container p-0 mb-3 small text-dark-500">
<div className="row">
@@ -50,15 +71,27 @@ export default function DateSummary({
) : (
<div className="row ml-4 pr-2">
<div className="date-summary-text">
{linkedTitle
&& <div className="font-weight-bold mt-2"><a href={dateBlock.link}>{dateBlock.title}</a></div>}
{!linkedTitle
&& <div className="font-weight-bold mt-2">{dateBlock.title}</div>}
{linkedTitle && (
<div className="font-weight-bold mt-2">
<a href={dateBlock.link}>{dateBlock.title}</a>
</div>
)}
{!linkedTitle && (
<div className="font-weight-bold mt-2">{dateBlock.title}</div>
)}
</div>
{dateBlock.description
&& <div className="date-summary-text mt-1">{dateBlock.description}</div>}
{!linkedTitle && dateBlock.link
&& <a href={dateBlock.link} className="description-link">{dateBlock.linkText}</a>}
{dateBlock.description && (
<div className="date-summary-text mt-1">{dateBlock.description}</div>
)}
{!linkedTitle && dateBlock.link && (
<a
href={dateBlock.link}
onClick={dateBlock.dateType === 'verified-upgrade-deadline' ? logVerifiedUpgradeClick : () => {}}
className="description-link"
>
{dateBlock.linkText}
</a>
)}
</div>
)}
</li>

View File

@@ -1,6 +1,6 @@
import React, { useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { sendTrackingLogEvent } from '@edx/frontend-platform/analytics';
import { sendTrackEvent, sendTrackingLogEvent } from '@edx/frontend-platform/analytics';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Button, Toast } from '@edx/paragon';
@@ -70,18 +70,22 @@ function OutlineTab({ intl }) {
const [goalToastHeader, setGoalToastHeader] = useState('');
const [expandAll, setExpandAll] = useState(false);
const eventProperties = {
org_key: org,
courserun_key: courseId,
};
const logResumeCourseClick = () => {
sendTrackingLogEvent('edx.course.home.resume_course.clicked', {
courserun_key: courseId,
...eventProperties,
event_type: hasVisitedCourse ? 'resume' : 'start',
org_key: org,
url: resumeCourseUrl,
});
};
// Below the course title alerts (appearing in the order listed here)
const offerAlert = useOfferAlert(offer, userTimezone, 'outline-course-alerts');
const accessExpirationAlert = useAccessExpirationAlert(accessExpiration, userTimezone, 'outline-course-alerts');
const offerAlert = useOfferAlert(courseId, offer, org, userTimezone, 'outline-course-alerts', 'course_home');
const accessExpirationAlert = useAccessExpirationAlert(accessExpiration, courseId, org, userTimezone, 'outline-course-alerts', 'course_home');
const courseStartAlert = useCourseStartAlert(courseId);
const courseEndAlert = useCourseEndAlert(courseId);
const certificateAvailableAlert = useCertificateAvailableAlert(courseId);
@@ -91,6 +95,16 @@ function OutlineTab({ intl }) {
const courseSock = useRef(null);
const logUpgradeLinkClick = () => {
sendTrackEvent('edx.bi.ecommerce.upsell_links_clicked', {
...eventProperties,
linkCategory: 'personalized_learner_schedules',
linkName: 'course_home_upgrade_shift_dates',
linkType: 'button',
pageName: 'course_home',
});
};
/** [[MM-P2P] Experiment */
const MMP2P = initHomeMMP2P(courseId);
@@ -146,6 +160,7 @@ function OutlineTab({ intl }) {
courseDateBlocks={courseDateBlocks}
datesBannerInfo={datesBannerInfo}
hasEnded={hasEnded}
logUpgradeLinkClick={logUpgradeLinkClick}
model="outline"
tabFetch={fetchOutlineTab}
/** [MM-P2P] Experiment */

View File

@@ -158,6 +158,58 @@ describe('Outline Tab', () => {
});
});
describe('Dates Banner', () => {
beforeEach(() => {
setMetadata({ is_enrolled: true });
setTabData({
dates_banner_info: {
content_type_gating_enabled: true,
missed_deadlines: true,
missed_gated_content: true,
verified_upgrade_link: 'http://localhost:18130/basket/add/?sku=8CF08E5',
},
}, {
date_blocks: [
{
assignment_type: 'Homework',
date: '2010-08-20T05:59:40.942669Z',
date_type: 'assignment-due-date',
description: '',
learner_has_access: true,
title: 'Missed assignment',
extra_info: null,
},
],
});
});
it('renders upgradeToReset', async () => {
await fetchAndRender();
expect(screen.getByText('You are auditing this course,')).toBeInTheDocument();
expect(screen.getByText('which means that you are unable to participate in graded assignments. It looks like you missed some important deadlines based on our suggested schedule. To complete graded assignments as part of this course and shift the past due assignments into the future, you can upgrade today.')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Upgrade to shift due dates' })).toBeInTheDocument();
});
it('sends analytics event onClick of upgrade button in banner', async () => {
await fetchAndRender();
sendTrackEvent.mockClear();
const upgradeButton = screen.getByRole('button', { name: 'Upgrade to shift due dates' });
fireEvent.click(upgradeButton);
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.ecommerce.upsell_links_clicked', {
org_key: 'edX',
courserun_key: courseId,
linkCategory: 'personalized_learner_schedules',
linkName: 'course_home_upgrade_shift_dates',
linkType: 'button',
pageName: 'course_home',
});
});
});
describe('Welcome Message', () => {
beforeEach(() => {
setMetadata({ is_enrolled: true });
@@ -214,6 +266,63 @@ describe('Outline Tab', () => {
});
});
describe('Course Dates', () => {
it('renders when course date blocks are populated', async () => {
const startDate = new Date();
startDate.setHours(startDate.getHours() + 1);
setMetadata({ is_enrolled: true });
setTabData({}, {
date_blocks: [
{
date_type: 'course-start-date',
date: startDate.toISOString(),
title: 'Start',
},
],
});
await fetchAndRender();
expect(screen.getByRole('heading', { name: 'Upcoming Dates' })).toBeInTheDocument();
});
it('does not render when course date blocks are not populated', async () => {
setMetadata({ is_enrolled: true });
await fetchAndRender();
expect(screen.queryByRole('heading', { name: 'Upcoming Dates' })).not.toBeInTheDocument();
});
it('sends analytics event onClick of upgrade link', async () => {
const now = new Date();
const tomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
setMetadata({ is_enrolled: true });
setTabData({}, {
date_blocks: [
{
date_type: 'verified-upgrade-deadline',
date: tomorrow.toISOString(),
link: 'https://example.com/upgrade',
link_text: 'Upgrade to Verified Certificate',
title: 'Verification Upgrade Deadline',
},
],
});
await fetchAndRender();
sendTrackEvent.mockClear();
const upgradeLink = screen.getByRole('link', { name: 'Upgrade to Verified Certificate' });
fireEvent.click(upgradeLink);
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.ecommerce.upsell_links_clicked', {
org_key: 'edX',
courserun_key: courseId,
linkCategory: '(none)',
linkName: 'course_home_dates',
linkType: 'link',
pageName: 'course_home',
});
});
});
describe('Course Goals', () => {
const goalOptions = [
['certify', 'Earn a certificate'],
@@ -312,6 +421,51 @@ describe('Outline Tab', () => {
});
});
describe('Course Tools', () => {
it('renders title when tools are available', async () => {
await fetchAndRender();
expect(screen.getByRole('heading', { name: 'Course Tools' })).toBeInTheDocument();
expect(screen.getByRole('link', { name: 'Bookmarks' })).toBeInTheDocument();
});
it('does not render title when tools are not available', async () => {
setTabData({
course_tools: [],
});
await fetchAndRender();
expect(screen.queryByRole('heading', { name: 'Course Tools' })).not.toBeInTheDocument();
});
it('analytics sent when upgrade link clicked', async () => {
await fetchAndRender();
expect(screen.getByRole('heading', { name: 'Course Tools' })).toBeInTheDocument();
sendTrackEvent.mockClear();
sendTrackingLogEvent.mockClear();
const upgradeLink = screen.getByRole('link', { name: 'Upgrade to Verified' });
fireEvent.click(upgradeLink);
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.ecommerce.upsell_links_clicked', {
org_key: 'edX',
courserun_key: courseId,
linkCategory: '(none)',
linkName: 'course_home_course_tools',
linkType: 'link',
pageName: 'course_home',
});
expect(sendTrackingLogEvent).toHaveBeenCalledTimes(1);
expect(sendTrackingLogEvent).toHaveBeenCalledWith('edx.course.tool.accessed', {
org_key: 'edX',
courserun_key: courseId,
course_id: courseId,
is_staff: false,
tool_name: 'edx.tool.verified_upgrade',
});
});
});
describe('Alert List', () => {
describe('Private Course Alert', () => {
it('does not display alert for enrolled user', async () => {
@@ -403,6 +557,33 @@ describe('Outline Tab', () => {
await fetchAndRender();
await screen.findByText('to get unlimited access to the course as long as it exists on the site.', { exact: false });
});
it('sends analytics event onClick of upgrade link', async () => {
setTabData({
access_expiration: {
expiration_date: '2080-01-01T12:00:00Z',
masquerading_expired_course: false,
upgrade_deadline: '2070-01-01T12:00:00Z',
upgrade_url: 'https://example.com/upgrade',
},
});
await fetchAndRender();
// Clearing after render to remove any events sent on view (ex. 'Promotion Viewed')
sendTrackEvent.mockClear();
const upgradeLink = screen.getByRole('link', { name: 'Upgrade now' });
fireEvent.click(upgradeLink);
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.ecommerce.upsell_links_clicked', {
org_key: 'edX',
courserun_key: courseId,
linkCategory: 'FBE_banner',
linkName: 'course_home_audit_access_expires',
linkType: 'link',
pageName: 'course_home',
});
});
});
describe('Course Start Alert', () => {
@@ -412,7 +593,7 @@ describe('Outline Tab', () => {
startDate.setDate(startDate.getDate() + 100);
setMetadata({ is_enrolled: true });
setTabData({}, {
dateBlocks: [
date_blocks: [
{
date_type: 'course-start-date',
date: startDate.toISOString(),
@@ -430,7 +611,7 @@ describe('Outline Tab', () => {
startDate.setHours(startDate.getHours() + 1);
setMetadata({ is_enrolled: true });
setTabData({}, {
dateBlocks: [
date_blocks: [
{
date_type: 'course-start-date',
date: startDate.toISOString(),
@@ -451,7 +632,7 @@ describe('Outline Tab', () => {
endDate.setDate(endDate.getDate() + 13);
setMetadata({ is_enrolled: true });
setTabData({}, {
dateBlocks: [
date_blocks: [
{
date_type: 'course-end-date',
date: endDate.toISOString(),
@@ -469,7 +650,7 @@ describe('Outline Tab', () => {
endDate.setHours(endDate.getHours() + 1);
setMetadata({ is_enrolled: true });
setTabData({}, {
dateBlocks: [
date_blocks: [
{
date_type: 'course-end-date',
date: endDate.toISOString(),
@@ -491,7 +672,7 @@ describe('Outline Tab', () => {
const tomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
setMetadata({ is_enrolled: true });
setTabData({}, {
dateBlocks: [
date_blocks: [
{
date_type: 'course-end-date',
date: yesterday.toISOString(),
@@ -508,6 +689,53 @@ describe('Outline Tab', () => {
await screen.findByText('We are working on generating course certificates.');
});
});
describe('Offer Alert', () => {
it('sends analytics event onClick of upgrade link', async () => {
setTabData({
offer: {
code: 'EDXWELCOME',
expiration_date: '2070-01-01T12:00:00Z',
original_price: '$100',
discounted_price: '$85',
percentage: 15,
upgrade_url: 'https://example.com/upgrade',
},
});
await fetchAndRender();
expect(screen.getByRole('link', { name: 'Upgrade now' })).toBeInTheDocument();
});
it('sends analytics event onClick of upgrade link', async () => {
setTabData({
offer: {
code: 'EDXWELCOME',
expiration_date: '2070-01-01T12:00:00Z',
original_price: '$100',
discounted_price: '$85',
percentage: 15,
upgrade_url: 'https://example.com/upgrade',
},
});
await fetchAndRender();
// Clearing after render to remove any events sent on view (ex. 'Promotion Viewed')
sendTrackEvent.mockClear();
const upgradeLink = screen.getByRole('link', { name: 'Upgrade now' });
fireEvent.click(upgradeLink);
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.ecommerce.upsell_links_clicked', {
org_key: 'edX',
courserun_key: courseId,
linkCategory: 'welcome',
linkName: 'course_home_welcome',
linkType: 'link',
pageName: 'course_home',
});
});
});
});
describe('Proctoring Info Panel', () => {
@@ -697,17 +925,17 @@ describe('Outline Tab', () => {
});
it('clicking upgrade link sends analytics', async () => {
await fetchAndRender();
// Clearing after render to remove any events sent on view (ex. 'Promotion Viewed')
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', {
expect(sendTrackEvent).toHaveBeenCalledTimes(2);
expect(sendTrackEvent).toHaveBeenNthCalledWith(1, 'Promotion Clicked', {
org_key: 'edX',
courserun_key: courseId,
creative: 'sidebarupsell',
@@ -715,7 +943,7 @@ describe('Outline Tab', () => {
position: 'sidebar-message',
promotion_id: 'courseware_verified_certificate_upsell',
});
expect(sendTrackEvent).toHaveBeenNthCalledWith(3, 'edx.bi.ecommerce.upsell_links_clicked', {
expect(sendTrackEvent).toHaveBeenNthCalledWith(2, 'edx.bi.ecommerce.upsell_links_clicked', {
org_key: 'edX',
courserun_key: courseId,
linkCategory: 'green_upgrade',
@@ -724,13 +952,12 @@ describe('Outline Tab', () => {
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', {
expect(sendTrackingLogEvent).toHaveBeenCalledTimes(2);
expect(sendTrackingLogEvent).toHaveBeenNthCalledWith(1, 'edx.bi.course.upgrade.sidebarupsell.clicked', {
org_key: 'edX',
courserun_key: courseId,
});
expect(sendTrackingLogEvent).toHaveBeenNthCalledWith(3, 'edx.course.enrollment.upgrade.clicked', {
expect(sendTrackingLogEvent).toHaveBeenNthCalledWith(2, 'edx.course.enrollment.upgrade.clicked', {
org_key: 'edX',
courserun_key: courseId,
location: 'sidebar-message',

View File

@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { sendTrackingLogEvent } from '@edx/frontend-platform/analytics';
import { sendTrackEvent, sendTrackingLogEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -23,15 +23,29 @@ function CourseTools({ courseId, intl }) {
return null;
}
const eventProperties = {
org_key: org,
courserun_key: courseId,
};
const logClick = (analyticsId) => {
const { administrator } = getAuthenticatedUser();
sendTrackingLogEvent('edx.course.tool.accessed', {
org_key: org,
courserun_key: courseId,
...eventProperties,
course_id: courseId, // should only be courserun_key, but left as-is for historical reasons
is_staff: administrator,
tool_name: analyticsId,
});
if (analyticsId === 'edx.tool.verified_upgrade') {
sendTrackEvent('edx.bi.ecommerce.upsell_links_clicked', {
...eventProperties,
linkCategory: '(none)',
linkName: 'course_home_course_tools',
linkType: 'link',
pageName: 'course_home',
});
}
};
const renderIcon = (iconClasses) => {

View File

@@ -48,8 +48,8 @@ function Course({
} = course;
// Below the tabs, above the breadcrumbs alerts (appearing in the order listed here)
const offerAlert = useOfferAlert(offer, userTimezone, 'course');
const accessExpirationAlert = useAccessExpirationAlert(accessExpiration, userTimezone, 'course');
const offerAlert = useOfferAlert(courseId, offer, org, userTimezone, 'course', 'in_course');
const accessExpirationAlert = useAccessExpirationAlert(accessExpiration, courseId, org, userTimezone, 'course', 'in_course');
const dispatch = useDispatch();
const celebrateFirstSection = celebrations && celebrations.firstSection;

View File

@@ -1,5 +1,6 @@
import React from 'react';
import { Factory } from 'rosie';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import {
loadUnit, render, screen, waitFor, getByRole, initializeTestStore, fireEvent,
} from '../../setupTest';
@@ -108,6 +109,68 @@ describe('Course', () => {
await screen.findByText('Audit Access Expires');
});
it('sends analytics event onClick of access expiration upgrade link', async () => {
sendTrackEvent.mockClear();
const courseMetadata = Factory.build('courseMetadata', {
access_expiration: {
expiration_date: '2080-01-01T12:00:00Z',
masquerading_expired_course: false,
upgrade_deadline: '2070-01-01T12:00:00Z',
upgrade_url: 'https://example.com/upgrade',
},
user_timezone: 'UTC',
});
const testStore = await initializeTestStore({ courseMetadata, excludeFetchSequence: true }, false);
render(<Course {...mockData} courseId={courseMetadata.id} />, { store: testStore });
await screen.findByText('Audit Access Expires');
const upgradeLink = screen.getByRole('link', { name: 'Upgrade now' });
fireEvent.click(upgradeLink);
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.ecommerce.upsell_links_clicked', {
org_key: 'edX',
courserun_key: courseMetadata.id,
linkCategory: 'FBE_banner',
linkName: 'in_course_audit_access_expires',
linkType: 'link',
pageName: 'in_course',
});
});
it('sends analytics event onClick of offer alert link', async () => {
sendTrackEvent.mockClear();
const courseMetadata = Factory.build('courseMetadata', {
offer: {
code: 'EDXWELCOME',
expiration_date: '2070-01-01T12:00:00Z',
original_price: '$100',
discounted_price: '$85',
percentage: 15,
upgrade_url: 'https://example.com/upgrade',
},
user_timezone: 'UTC',
});
const testStore = await initializeTestStore({ courseMetadata, excludeFetchSequence: true }, false);
render(<Course {...mockData} courseId={courseMetadata.id} />, { store: testStore });
await screen.findByText('EDXWELCOME');
const upgradeLink = screen.getByRole('link', { name: 'Upgrade now' });
fireEvent.click(upgradeLink);
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.ecommerce.upsell_links_clicked', {
org_key: 'edX',
courserun_key: courseMetadata.id,
linkCategory: 'welcome',
linkName: 'in_course_welcome',
linkType: 'link',
pageName: 'in_course',
});
});
it('passes handlers to the sequence', async () => {
const nextSequenceHandler = jest.fn();
const previousSequenceHandler = jest.fn();