diff --git a/src/data/constants.js b/src/data/constants.js index 90fdf75f..5adf4382 100644 --- a/src/data/constants.js +++ b/src/data/constants.js @@ -37,3 +37,4 @@ export const VALID_EMAIL_REGEX = '(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+ // things like auto-enrollment upon login and registration. export const AUTH_PARAMS = ['course_id', 'enrollment_action', 'course_mode', 'email_opt_in', 'purchase_workflow', 'next', 'register_for_free', 'track', 'is_account_recovery', 'variant', 'host', 'cta']; export const REDIRECT = 'redirect'; +export const APP_NAME = 'authn_mfe'; diff --git a/src/data/segment/utils.js b/src/data/segment/utils.js new file mode 100644 index 00000000..fa84443d --- /dev/null +++ b/src/data/segment/utils.js @@ -0,0 +1,37 @@ +/* eslint-disable import/prefer-default-export */ +import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics'; + +import { APP_NAME } from '../constants'; + +export const LINK_TIMEOUT = 300; + +/** + * Creates an event tracker function that sends a tracking event with the given name and options. + * + * @param {string} name - The name of the event to be tracked. + * @param {object} [options={}] - Additional options to be included with the event. + * @returns {function} - A function that, when called, sends the tracking event. + */ +export const createEventTracker = (name, options = {}) => () => sendTrackEvent( + name, + { ...options, app_name: APP_NAME }, +); + +/** + * Creates an event tracker function that sends a tracking event with the given name and options. + * + * @param {string} name - The name of the event to be tracked. + * @param {object} [options={}] - Additional options to be included with the event. + * @returns {function} - A function that, when called, sends the tracking event. + */ +export const createPageEventTracker = (name, options = null) => () => sendPageEvent( + name, + options, + { app_name: APP_NAME }, +); + +export const createLinkTracker = (tracker, href) => (e) => { + e.preventDefault(); + tracker(); + return setTimeout(() => { window.location.href = href; }, LINK_TIMEOUT); +}; diff --git a/src/forgot-password/ForgotPasswordPage.jsx b/src/forgot-password/ForgotPasswordPage.jsx index f4c7b314..fad31300 100644 --- a/src/forgot-password/ForgotPasswordPage.jsx +++ b/src/forgot-password/ForgotPasswordPage.jsx @@ -2,7 +2,6 @@ import React, { useEffect, useState } from 'react'; import { connect } from 'react-redux'; import { getConfig } from '@edx/frontend-platform'; -import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics'; import { useIntl } from '@edx/frontend-platform/i18n'; import { Form, @@ -25,6 +24,10 @@ import BaseContainer from '../base-container'; import { FormGroup } from '../common-components'; import { DEFAULT_STATE, LOGIN_PAGE, VALID_EMAIL_REGEX } from '../data/constants'; import { updatePathWithQueryParams, windowScrollTo } from '../data/utils'; +import { + trackForgotPasswordPageEvent, + trackForgotPasswordPageViewed, +} from '../tracking/trackers/forgotpassword'; const ForgotPasswordPage = (props) => { const platformName = getConfig().SITE_NAME; @@ -41,8 +44,8 @@ const ForgotPasswordPage = (props) => { const navigate = useNavigate(); useEffect(() => { - sendPageEvent('login_and_registration', 'forgot-password'); - sendTrackEvent('edx.bi.password_reset_form.viewed', { category: 'user-engagement' }); + trackForgotPasswordPageEvent(); + trackForgotPasswordPageViewed(); }, []); useEffect(() => { diff --git a/src/login/LoginPage.jsx b/src/login/LoginPage.jsx index 62815726..0ff25258 100644 --- a/src/login/LoginPage.jsx +++ b/src/login/LoginPage.jsx @@ -2,7 +2,6 @@ import React, { useEffect, useMemo, useState } from 'react'; import { connect } from 'react-redux'; import { getConfig } from '@edx/frontend-platform'; -import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics'; import { injectIntl, useIntl } from '@edx/frontend-platform/i18n'; import { Form, StatefulButton, @@ -43,6 +42,9 @@ import { updatePathWithQueryParams, } from '../data/utils'; import ResetPasswordSuccess from '../reset-password/ResetPasswordSuccess'; +import { + trackForgotPasswordLinkClick, trackLoginPageViewed, trackLoginSuccess, +} from '../tracking/trackers/login'; const LoginPage = (props) => { const { @@ -78,9 +80,15 @@ const LoginPage = (props) => { const tpaHint = getTpaHint(); useEffect(() => { - sendPageEvent('login_and_registration', 'login'); + trackLoginPageViewed(); }, []); + useEffect(() => { + if (loginResult.success) { + trackLoginSuccess(); + } + }, [loginResult]); + useEffect(() => { const payload = { ...queryParams }; if (tpaHint) { @@ -170,9 +178,6 @@ const LoginPage = (props) => { const { name } = event.target; setErrors(prevErrors => ({ ...prevErrors, [name]: '' })); }; - const trackForgotPasswordLinkClick = () => { - sendTrackEvent('edx.bi.password-reset_form.toggled', { category: 'user-engagement' }); - }; const { provider, skipHintedLogin } = getTpaProvider(tpaHint, providers, secondaryProviders); diff --git a/src/login/tests/LoginPage.test.jsx b/src/login/tests/LoginPage.test.jsx index 9c337bf2..38778b8c 100644 --- a/src/login/tests/LoginPage.test.jsx +++ b/src/login/tests/LoginPage.test.jsx @@ -11,7 +11,9 @@ import { act } from 'react-dom/test-utils'; import { MemoryRouter } from 'react-router-dom'; import configureStore from 'redux-mock-store'; -import { COMPLETE_STATE, LOGIN_PAGE, PENDING_STATE } from '../../data/constants'; +import { + APP_NAME, COMPLETE_STATE, LOGIN_PAGE, PENDING_STATE, +} from '../../data/constants'; import { backupLoginFormBegin, dismissPasswordResetBanner, loginRequest } from '../data/actions'; import { INTERNAL_SERVER_ERROR } from '../data/constants'; import LoginPage from '../LoginPage'; @@ -751,7 +753,7 @@ describe('LoginPage', () => { it('should send page event when login page is rendered', () => { render(reduxWrapper()); - expect(sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'login'); + expect(sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'login', { app_name: APP_NAME }); }); it('tests that form is in invalid state when it is submitted', () => { @@ -784,7 +786,7 @@ describe('LoginPage', () => { { selector: '#forgot-password' }, )); - expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.password-reset_form.toggled', { category: 'user-engagement' }); + expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.password-reset_form.toggled', { category: 'user-engagement', app_name: APP_NAME }); }); it('should backup the login form state when shouldBackupState is true', () => { diff --git a/src/logistration/Logistration.jsx b/src/logistration/Logistration.jsx index 9451aa27..1a65b259 100644 --- a/src/logistration/Logistration.jsx +++ b/src/logistration/Logistration.jsx @@ -20,7 +20,7 @@ import { tpaProvidersSelector, } from '../common-components/data/selectors'; import messages from '../common-components/messages'; -import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants'; +import { APP_NAME, LOGIN_PAGE, REGISTER_PAGE } from '../data/constants'; import { getTpaHint, getTpaProvider, updatePathWithQueryParams, } from '../data/utils'; @@ -56,11 +56,11 @@ const Logistration = (props) => { }, [navigate, disablePublicAccountCreation]); const handleInstitutionLogin = (e) => { - sendTrackEvent('edx.bi.institution_login_form.toggled', { category: 'user-engagement' }); + sendTrackEvent('edx.bi.institution_login_form.toggled', { category: 'user-engagement', app_name: APP_NAME }); if (typeof e === 'string') { - sendPageEvent('login_and_registration', e === '/login' ? 'login' : 'register'); + sendPageEvent('login_and_registration', e === '/login' ? 'login' : 'register', { app_name: APP_NAME }); } else { - sendPageEvent('login_and_registration', e.target.dataset.eventName); + sendPageEvent('login_and_registration', e.target.dataset.eventName, { app_name: APP_NAME }); } setInstitutionLogin(!institutionLogin); @@ -70,7 +70,7 @@ const Logistration = (props) => { if (tabKey === currentTab) { return; } - sendTrackEvent(`edx.bi.${tabKey.replace('/', '')}_form.toggled`, { category: 'user-engagement' }); + sendTrackEvent(`edx.bi.${tabKey.replace('/', '')}_form.toggled`, { category: 'user-engagement', app_name: APP_NAME }); props.clearThirdPartyAuthContextErrorMessage(); if (tabKey === LOGIN_PAGE) { props.backupRegistrationForm(); diff --git a/src/logistration/Logistration.test.jsx b/src/logistration/Logistration.test.jsx index b16ae216..b4b0d1e9 100644 --- a/src/logistration/Logistration.test.jsx +++ b/src/logistration/Logistration.test.jsx @@ -11,6 +11,7 @@ import configureStore from 'redux-mock-store'; import Logistration from './Logistration'; import { clearThirdPartyAuthContextErrorMessage } from '../common-components/data/actions'; import { + APP_NAME, COMPLETE_STATE, LOGIN_PAGE, REGISTER_PAGE, } from '../data/constants'; import { backupLoginForm } from '../login/data/actions'; @@ -229,8 +230,8 @@ describe('Logistration', () => { render(reduxWrapper()); fireEvent.click(screen.getByText('Institution/campus credentials')); - expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.institution_login_form.toggled', { category: 'user-engagement' }); - expect(sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'institution_login'); + expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.institution_login_form.toggled', { category: 'user-engagement', app_name: APP_NAME }); + expect(sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'institution_login', { app_name: APP_NAME }); mergeConfig({ DISABLE_ENTERPRISE_LOGIN: '', diff --git a/src/progressive-profiling/ProgressiveProfiling.jsx b/src/progressive-profiling/ProgressiveProfiling.jsx index d42e3d52..55b96462 100644 --- a/src/progressive-profiling/ProgressiveProfiling.jsx +++ b/src/progressive-profiling/ProgressiveProfiling.jsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import { connect } from 'react-redux'; import { getConfig, snakeCaseObject } from '@edx/frontend-platform'; -import { identifyAuthenticatedUser, sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics'; +import { identifyAuthenticatedUser } from '@edx/frontend-platform/analytics'; import { AxiosJwtAuthService, configure as configureAuth, @@ -39,6 +39,13 @@ import { import isOneTrustFunctionalCookieEnabled from '../data/oneTrust'; import { getAllPossibleQueryParams, isHostAvailableInQueryParams } from '../data/utils'; import { FormFieldRenderer } from '../field-renderer'; +import { + trackDisablePostRegistrationRecommendations, + trackProgressiveProfilingPageViewed, + trackProgressiveProfilingSkipLinkClick, + trackProgressiveProfilingSubmitClick, + trackProgressiveProfilingSupportLinkCLick, +} from '../tracking/trackers/progressive-profiling'; const ProgressiveProfiling = (props) => { const { formatMessage } = useIntl(); @@ -98,14 +105,13 @@ const ProgressiveProfiling = (props) => { useEffect(() => { if (authenticatedUser?.userId) { identifyAuthenticatedUser(authenticatedUser.userId); - sendPageEvent('login_and_registration', 'welcome'); + trackProgressiveProfilingPageViewed(); } }, [authenticatedUser]); useEffect(() => { if (!enablePostRegistrationRecommendations) { - sendTrackEvent( - 'edx.bi.user.recommendations.not.enabled', + trackDisablePostRegistrationRecommendations( { functionalCookiesConsent, page: 'authn_recommendations' }, ); return; @@ -149,29 +155,23 @@ const ProgressiveProfiling = (props) => { }); } props.saveUserProfile(authenticatedUser.username, snakeCaseObject(payload)); - - sendTrackEvent( - 'edx.bi.welcome.page.submit.clicked', - { - isGenderSelected: !!values.gender, - isYearOfBirthSelected: !!values.year_of_birth, - isLevelOfEducationSelected: !!values.level_of_education, - isWorkExperienceSelected: !!values.work_experience, - host: queryParams?.host || '', - }, - ); + const eventProperties = { + isGenderSelected: !!values.gender, + isYearOfBirthSelected: !!values.year_of_birth, + isLevelOfEducationSelected: !!values.level_of_education, + isWorkExperienceSelected: !!values.work_experience, + host: queryParams?.host || '', + }; + trackProgressiveProfilingSubmitClick(eventProperties); }; const handleSkip = (e) => { e.preventDefault(); window.history.replaceState(location.state, null, ''); setShowModal(true); - sendTrackEvent( - 'edx.bi.welcome.page.skip.link.clicked', - { - host: queryParams?.host || '', - }, - ); + trackProgressiveProfilingSkipLinkClick({ + host: queryParams?.host || '', + }); }; const onChangeHandler = (e) => { @@ -242,7 +242,7 @@ const ProgressiveProfiling = (props) => { destination={getConfig().AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK} target="_blank" showLaunchIcon={false} - onClick={() => (sendTrackEvent('edx.bi.welcome.page.support.link.clicked'))} + onClick={() => (trackProgressiveProfilingSupportLinkCLick())} > {formatMessage(messages['optional.fields.information.link'])} diff --git a/src/progressive-profiling/tests/ProgressiveProfiling.test.jsx b/src/progressive-profiling/tests/ProgressiveProfiling.test.jsx index 9bc4439c..c5786b2e 100644 --- a/src/progressive-profiling/tests/ProgressiveProfiling.test.jsx +++ b/src/progressive-profiling/tests/ProgressiveProfiling.test.jsx @@ -12,6 +12,7 @@ import { MemoryRouter, mockNavigate, useLocation } from 'react-router-dom'; import configureStore from 'redux-mock-store'; import { + APP_NAME, AUTHN_PROGRESSIVE_PROFILING, COMPLETE_STATE, DEFAULT_REDIRECT_URL, EMBEDDED, @@ -143,8 +144,9 @@ describe('ProgressiveProfilingTests', () => { const modalContentContainer = document.getElementsByClassName('.pgn__modal-content-container'); expect(modalContentContainer).toBeTruthy(); + const payload = { host: '', app_name: APP_NAME }; - expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.welcome.page.skip.link.clicked', { host: '' }); + expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.welcome.page.skip.link.clicked', payload); }); // ******** test event functionality ******** @@ -165,7 +167,7 @@ describe('ProgressiveProfilingTests', () => { const supportLink = screen.getByRole('link', { name: /learn more about how we use this information/i }); fireEvent.click(supportLink); - expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.welcome.page.support.link.clicked'); + expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.welcome.page.support.link.clicked', { app_name: APP_NAME }); }); it('should set empty host property value for non-embedded experience', () => { @@ -175,6 +177,7 @@ describe('ProgressiveProfilingTests', () => { isLevelOfEducationSelected: false, isWorkExperienceSelected: false, host: '', + app_name: APP_NAME, }; delete window.location; window.location = { href: getConfig().BASE_URL.concat(AUTHN_PROGRESSIVE_PROFILING) }; @@ -316,7 +319,7 @@ describe('ProgressiveProfilingTests', () => { const skipLinkButton = screen.getByText('Skip for now'); fireEvent.click(skipLinkButton); - expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.welcome.page.skip.link.clicked', { host }); + expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.welcome.page.skip.link.clicked', { host, app_name: APP_NAME }); }); it('should show spinner while fetching the optional fields', () => { @@ -349,6 +352,7 @@ describe('ProgressiveProfilingTests', () => { isLevelOfEducationSelected: false, isWorkExperienceSelected: false, host: 'http://example.com', + app_name: APP_NAME, }; delete window.location; window.location = { diff --git a/src/register/RegistrationPage.jsx b/src/register/RegistrationPage.jsx index 22f12e14..0a0a5b53 100644 --- a/src/register/RegistrationPage.jsx +++ b/src/register/RegistrationPage.jsx @@ -4,7 +4,6 @@ import React, { import { useDispatch, useSelector } from 'react-redux'; import { getConfig } from '@edx/frontend-platform'; -import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics'; import { useIntl } from '@edx/frontend-platform/i18n'; import { Form, Spinner, StatefulButton } from '@openedx/paragon'; import classNames from 'classnames'; @@ -44,11 +43,12 @@ import { getThirdPartyAuthContext as getRegistrationDataFromBackend } from '../c import EnterpriseSSO from '../common-components/EnterpriseSSO'; import ThirdPartyAuth from '../common-components/ThirdPartyAuth'; import { - COMPLETE_STATE, PENDING_STATE, REGISTER_PAGE, + APP_NAME, COMPLETE_STATE, PENDING_STATE, REGISTER_PAGE, } from '../data/constants'; import { getAllPossibleQueryParams, getTpaHint, getTpaProvider, isHostAvailableInQueryParams, setCookie, } from '../data/utils'; +import { trackRegistrationPageViewed, trackRegistrationSuccess } from '../tracking/trackers/register'; /** * Main Registration Page component @@ -138,7 +138,7 @@ const RegistrationPage = (props) => { useEffect(() => { if (!formStartTime) { - sendPageEvent('login_and_registration', 'register'); + trackRegistrationPageViewed(); const payload = { ...queryParams, is_register_page: true }; if (tpaHint) { payload.tpa_hint = tpaHint; @@ -183,7 +183,7 @@ const RegistrationPage = (props) => { useEffect(() => { if (registrationResult.success) { // This event is used by GTM - sendTrackEvent('edx.bi.user.account.registered.client', {}); + trackRegistrationSuccess(); // This is used by the "User Retention Rate Event" on GTM setCookie(getConfig().USER_RETENTION_COOKIE_NAME, true); @@ -222,7 +222,7 @@ const RegistrationPage = (props) => { const registerUser = () => { const totalRegistrationTime = (Date.now() - formStartTime) / 1000; - let payload = { ...formFields }; + let payload = { ...formFields, app_name: APP_NAME }; if (currentProvider) { delete payload.password; diff --git a/src/register/RegistrationPage.test.jsx b/src/register/RegistrationPage.test.jsx index 9773bf03..5633c753 100644 --- a/src/register/RegistrationPage.test.jsx +++ b/src/register/RegistrationPage.test.jsx @@ -22,7 +22,7 @@ import useAutoGeneratedUsernameExperimentVariation from './data/optimizelyExperiment/useAutoGeneratedUsernameExperimentVariation'; import RegistrationPage from './RegistrationPage'; import { - AUTHN_PROGRESSIVE_PROFILING, COMPLETE_STATE, PENDING_STATE, REGISTER_PAGE, + APP_NAME, AUTHN_PROGRESSIVE_PROFILING, COMPLETE_STATE, PENDING_STATE, REGISTER_PAGE, } from '../data/constants'; jest.mock('@edx/frontend-platform/analytics', () => ({ @@ -190,6 +190,7 @@ describe('RegistrationPage', () => { honor_code: true, totalRegistrationTime: 0, next: '/course/demo-course-url', + app_name: APP_NAME, }; store.dispatch = jest.fn(store.dispatch); @@ -212,6 +213,7 @@ describe('RegistrationPage', () => { honor_code: true, social_auth_provider: 'Apple', totalRegistrationTime: 0, + app_name: APP_NAME, }; store = mockStore({ @@ -297,6 +299,7 @@ describe('RegistrationPage', () => { honor_code: true, totalRegistrationTime: 0, marketing_emails_opt_in: true, + app_name: APP_NAME, }; store.dispatch = jest.fn(store.dispatch); @@ -323,6 +326,7 @@ describe('RegistrationPage', () => { country: 'Pakistan', honor_code: true, totalRegistrationTime: 0, + app_name: APP_NAME, }; store.dispatch = jest.fn(store.dispatch); @@ -597,7 +601,7 @@ describe('RegistrationPage', () => { it('should send page event when register page is rendered', () => { render(routerWrapper(reduxWrapper())); - expect(sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'register'); + expect(sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'register', { app_name: APP_NAME }); }); it('should send track event when user has successfully registered', () => { @@ -615,7 +619,7 @@ describe('RegistrationPage', () => { delete window.location; window.location = { href: getConfig().BASE_URL }; render(routerWrapper(reduxWrapper())); - expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.user.account.registered.client', {}); + expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.user.account.registered.client', { app_name: APP_NAME }); }); it('should populate form with pipeline user details', () => { @@ -888,6 +892,7 @@ describe('RegistrationPage', () => { country: 'PK', social_auth_provider: 'Apple', totalRegistrationTime: 0, + app_name: APP_NAME, })); }); }); diff --git a/src/register/components/tests/ConfigurableRegistrationForm.test.jsx b/src/register/components/tests/ConfigurableRegistrationForm.test.jsx index 93978509..670415f7 100644 --- a/src/register/components/tests/ConfigurableRegistrationForm.test.jsx +++ b/src/register/components/tests/ConfigurableRegistrationForm.test.jsx @@ -9,6 +9,7 @@ import { fireEvent, render } from '@testing-library/react'; import { BrowserRouter as Router } from 'react-router-dom'; import configureStore from 'redux-mock-store'; +import { APP_NAME } from '../../../data/constants'; import { registerNewUser } from '../../data/actions'; import { FIELDS } from '../../data/constants'; import { NOT_INITIALIZED } from '../../data/optimizelyExperiment/helper'; @@ -265,7 +266,7 @@ describe('ConfigurableRegistrationForm', () => { fireEvent.click(submitButton); - expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({ ...payload, country: 'PK' })); + expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({ ...payload, country: 'PK', app_name: APP_NAME })); }); it('should show error messages for required fields on empty form submission', () => { diff --git a/src/reset-password/ResetPasswordPage.jsx b/src/reset-password/ResetPasswordPage.jsx index 0393d279..ceb37e8c 100644 --- a/src/reset-password/ResetPasswordPage.jsx +++ b/src/reset-password/ResetPasswordPage.jsx @@ -2,7 +2,6 @@ import React, { useEffect, useState } from 'react'; import { connect } from 'react-redux'; import { getConfig } from '@edx/frontend-platform'; -import { sendPageEvent } from '@edx/frontend-platform/analytics'; import { useIntl } from '@edx/frontend-platform/i18n'; import { Form, @@ -19,7 +18,7 @@ import { useNavigate, useParams } from 'react-router-dom'; import { resetPassword, validateToken } from './data/actions'; import { - FORM_SUBMISSION_ERROR, PASSWORD_RESET_ERROR, PASSWORD_VALIDATION_ERROR, TOKEN_STATE, + FORM_SUBMISSION_ERROR, PASSWORD_RESET_ERROR, PASSWORD_VALIDATION_ERROR, SUCCESS, TOKEN_STATE, } from './data/constants'; import { resetPasswordResultSelector } from './data/selectors'; import { validatePassword } from './data/service'; @@ -31,6 +30,7 @@ import { LETTER_REGEX, LOGIN_PAGE, NUMBER_REGEX, RESET_PAGE, } from '../data/constants'; import { getAllPossibleQueryParams, updatePathWithQueryParams, windowScrollTo } from '../data/utils'; +import { trackPasswordResetSuccess, trackResetPasswordPageViewed } from '../tracking/trackers/reset-password'; const ResetPasswordPage = (props) => { const { formatMessage } = useIntl(); @@ -44,8 +44,13 @@ const ResetPasswordPage = (props) => { const navigate = useNavigate(); useEffect(() => { - sendPageEvent('login_and_registration', 'reset-password'); - }, []); + if (props.status === TOKEN_STATE.VALID) { + trackResetPasswordPageViewed(); + } + if (props.status === SUCCESS) { + trackPasswordResetSuccess(); + } + }, [props.status]); useEffect(() => { if (props.status !== TOKEN_STATE.PENDING && props.status !== PASSWORD_RESET_ERROR) { @@ -144,7 +149,7 @@ const ResetPasswordPage = (props) => { } } else if (props.status === PASSWORD_RESET_ERROR) { navigate(updatePathWithQueryParams(RESET_PAGE)); - } else if (props.status === 'success') { + } else if (props.status === SUCCESS) { navigate(updatePathWithQueryParams(LOGIN_PAGE)); } else { return ( diff --git a/src/reset-password/tests/ResetPasswordPage.test.jsx b/src/reset-password/tests/ResetPasswordPage.test.jsx index 0c9ba1a2..f3619be7 100644 --- a/src/reset-password/tests/ResetPasswordPage.test.jsx +++ b/src/reset-password/tests/ResetPasswordPage.test.jsx @@ -21,6 +21,7 @@ const token = '1c-bmjdkc-5e60e084cf8113048ca7'; jest.mock('@edx/frontend-platform/analytics', () => ({ sendPageEvent: jest.fn(), + sendTrackEvent: jest.fn(), })); jest.mock('@edx/frontend-platform/auth'); diff --git a/src/tracking/trackers/forgotpassword.js b/src/tracking/trackers/forgotpassword.js new file mode 100644 index 00000000..3918478d --- /dev/null +++ b/src/tracking/trackers/forgotpassword.js @@ -0,0 +1,22 @@ +import { createEventTracker, createPageEventTracker } from '../../data/segment/utils'; + +export const eventNames = { + loginAndRegistration: 'login_and_registration', + forgotPasswordPageViewed: 'edx.bi.password_reset_form.viewed', +}; + +export const categories = { + userEngagement: 'user-engagement', +}; + +// Event tracker for forgot password page viewed +export const trackForgotPasswordPageViewed = () => createEventTracker( + eventNames.forgotPasswordPageViewed, + { + category: categories.userEngagement, + }, +)(); + +export const trackForgotPasswordPageEvent = () => { + createPageEventTracker(eventNames.loginAndRegistration, 'forgot-password')(); +}; diff --git a/src/tracking/trackers/login.js b/src/tracking/trackers/login.js new file mode 100644 index 00000000..3c6a4a2a --- /dev/null +++ b/src/tracking/trackers/login.js @@ -0,0 +1,29 @@ +import { createEventTracker, createPageEventTracker } from '../../data/segment/utils'; + +export const eventNames = { + forgotPasswordLinkClicked: 'edx.bi.password-reset_form.toggled', + loginAndRegistration: 'login_and_registration', + registerFormToggled: 'edx.bi.register_form.toggled', + loginSuccess: 'edx.bi.user.account.authenticated.client', +}; + +export const categories = { + userEngagement: 'user-engagement', +}; + +// Event tracker for Forgot Password link click +export const trackForgotPasswordLinkClick = () => createEventTracker( + eventNames.forgotPasswordLinkClicked, + { category: categories.userEngagement }, +)(); + +// Tracks the login page event. +export const trackLoginPageViewed = () => { + createPageEventTracker(eventNames.loginAndRegistration, 'login')(); +}; + +// Tracks the login sucess event. +export const trackLoginSuccess = () => createEventTracker( + eventNames.loginSuccess, + {}, +)(); diff --git a/src/tracking/trackers/progressive-profiling.js b/src/tracking/trackers/progressive-profiling.js new file mode 100644 index 00000000..11a6f53e --- /dev/null +++ b/src/tracking/trackers/progressive-profiling.js @@ -0,0 +1,37 @@ +import { createEventTracker, createPageEventTracker } from '../../data/segment/utils'; + +export const eventNames = { + progressiveProfilingSubmitClick: 'edx.bi.welcome.page.submit.clicked', + progressiveProfilingSkipLinkClick: 'edx.bi.welcome.page.skip.link.clicked', + disablePostRegistrationRecommendations: 'edx.bi.user.recommendations.not.enabled', + progressiveProfilingSupportLinkCLick: 'edx.bi.welcome.page.support.link.clicked', + loginAndRegistration: 'login_and_registration', +}; + +// Event link tracker for Progressive profiling skip button click +export const trackProgressiveProfilingSkipLinkClick = evenProperties => createEventTracker( + eventNames.progressiveProfilingSkipLinkClick, { ...evenProperties }, +)(); + +// Event tracker for progressive profiling submit button click +export const trackProgressiveProfilingSubmitClick = (evenProperties) => createEventTracker( + eventNames.progressiveProfilingSubmitClick, + { ...evenProperties }, +)(); + +// Event tracker for progressive profiling submit button click +export const trackDisablePostRegistrationRecommendations = (evenProperties) => createEventTracker( + eventNames.disablePostRegistrationRecommendations, + { ...evenProperties }, +)(); + +// Tracks the progressive profiling page event. +export const trackProgressiveProfilingPageViewed = () => { + createPageEventTracker(eventNames.loginAndRegistration, 'welcome')(); +}; + +// Tracks the progressive profiling spport link click. +export const trackProgressiveProfilingSupportLinkCLick = () => createEventTracker( + eventNames.progressiveProfilingSupportLinkCLick, + {}, +)(); diff --git a/src/tracking/trackers/register.js b/src/tracking/trackers/register.js new file mode 100644 index 00000000..3a860856 --- /dev/null +++ b/src/tracking/trackers/register.js @@ -0,0 +1,22 @@ +import { createEventTracker, createPageEventTracker } from '../../data/segment/utils'; + +export const eventNames = { + loginAndRegistration: 'login_and_registration', + registrationSuccess: 'edx.bi.user.account.registered.client', + loginFormToggled: 'edx.bi.login_form.toggled', +}; + +export const categories = { + userEngagement: 'user-engagement', +}; + +// Event tracker for successful registration +export const trackRegistrationSuccess = () => createEventTracker( + eventNames.registrationSuccess, + {}, +)(); + +// Tracks the progressive profiling page event. +export const trackRegistrationPageViewed = () => { + createPageEventTracker(eventNames.loginAndRegistration, 'register')(); +}; diff --git a/src/tracking/trackers/reset-password.js b/src/tracking/trackers/reset-password.js new file mode 100644 index 00000000..cb79340f --- /dev/null +++ b/src/tracking/trackers/reset-password.js @@ -0,0 +1,14 @@ +import { createEventTracker, createPageEventTracker } from '../../data/segment/utils'; + +export const eventNames = { + loginAndRegistration: 'login_and_registration', + resetPasswordSuccess: 'edx.bi.user.password.reset.success', +}; + +export const trackResetPasswordPageViewed = () => { + createPageEventTracker(eventNames.loginAndRegistration, 'reset-password')(); +}; + +export const trackPasswordResetSuccess = () => { + createEventTracker(eventNames.resetPasswordSuccess, {})(); +}; diff --git a/src/tracking/trackers/tests/forgot-password.test.jsx b/src/tracking/trackers/tests/forgot-password.test.jsx new file mode 100644 index 00000000..6a9b87c4 --- /dev/null +++ b/src/tracking/trackers/tests/forgot-password.test.jsx @@ -0,0 +1,37 @@ +import { createEventTracker, createPageEventTracker } from '../../../data/segment/utils'; +import { + categories, + eventNames, + trackForgotPasswordPageEvent, + trackForgotPasswordPageViewed, +} from '../forgotpassword'; + +// Mock createEventTracker function +jest.mock('../../../data/segment/utils', () => ({ + createEventTracker: jest.fn().mockImplementation(() => jest.fn()), + createPageEventTracker: jest.fn().mockImplementation(() => jest.fn()), +})); + +describe('Tracking Functions', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should fire trackForgotPasswordPageEvent', () => { + trackForgotPasswordPageEvent(); + + expect(createPageEventTracker).toHaveBeenCalledWith( + eventNames.loginAndRegistration, + 'forgot-password', + ); + }); + + it('should fire forgotPasswordPageViewedEvent', () => { + trackForgotPasswordPageViewed(); + + expect(createEventTracker).toHaveBeenCalledWith( + eventNames.forgotPasswordPageViewed, + { category: categories.userEngagement }, + ); + }); +}); diff --git a/src/tracking/trackers/tests/login.test.jsx b/src/tracking/trackers/tests/login.test.jsx new file mode 100644 index 00000000..fac7d082 --- /dev/null +++ b/src/tracking/trackers/tests/login.test.jsx @@ -0,0 +1,37 @@ +import { createEventTracker, createPageEventTracker } from '../../../data/segment/utils'; +import { + categories, + eventNames, + trackForgotPasswordLinkClick, + trackLoginPageViewed, +} from '../login'; + +// Mock createEventTracker function +jest.mock('../../../data/segment/utils', () => ({ + createEventTracker: jest.fn().mockImplementation(() => jest.fn()), + createPageEventTracker: jest.fn().mockImplementation(() => jest.fn()), +})); + +describe('Tracking Functions', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('trackForgotPasswordLinkClick function', () => { + trackForgotPasswordLinkClick(); + + expect(createEventTracker).toHaveBeenCalledWith( + eventNames.forgotPasswordLinkClicked, + { category: categories.userEngagement }, + ); + }); + + it('trackLoginPageEvent function', () => { + trackLoginPageViewed(); + + expect(createPageEventTracker).toHaveBeenCalledWith( + eventNames.loginAndRegistration, + 'login', + ); + }); +}); diff --git a/src/tracking/trackers/tests/progressive-profiling.test.jsx b/src/tracking/trackers/tests/progressive-profiling.test.jsx new file mode 100644 index 00000000..4cda593d --- /dev/null +++ b/src/tracking/trackers/tests/progressive-profiling.test.jsx @@ -0,0 +1,37 @@ +import { createEventTracker, createPageEventTracker } from '../../../data/segment/utils'; +import { + eventNames, + trackProgressiveProfilingPageViewed, + trackProgressiveProfilingSkipLinkClick, +} from '../progressive-profiling'; + +// Mock createEventTracker function +jest.mock('../../../data/segment/utils', () => ({ + createEventTracker: jest.fn().mockImplementation(() => jest.fn()), + createPageEventTracker: jest.fn().mockImplementation(() => jest.fn()), + createLinkTracker: jest.fn().mockImplementation(() => jest.fn()), +})); + +describe('Tracking Functions', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should fire trackProgressiveProfilingSkipLinkClickEvent', () => { + trackProgressiveProfilingSkipLinkClick(); + + expect(createEventTracker).toHaveBeenCalledWith( + eventNames.progressiveProfilingSkipLinkClick, + {}, + ); + }); + + it('should fire trackProgressiveProfilingPageEvent', () => { + trackProgressiveProfilingPageViewed(); + + expect(createPageEventTracker).toHaveBeenCalledWith( + eventNames.loginAndRegistration, + 'welcome', + ); + }); +}); diff --git a/src/tracking/trackers/tests/register.test.jsx b/src/tracking/trackers/tests/register.test.jsx new file mode 100644 index 00000000..d058908f --- /dev/null +++ b/src/tracking/trackers/tests/register.test.jsx @@ -0,0 +1,36 @@ +import { createEventTracker, createPageEventTracker } from '../../../data/segment/utils'; +import { + eventNames, + trackRegistrationPageViewed, + trackRegistrationSuccess, +} from '../register'; + +// Mock createEventTracker function +jest.mock('../../../data/segment/utils', () => ({ + createEventTracker: jest.fn().mockImplementation(() => jest.fn()), + createPageEventTracker: jest.fn().mockImplementation(() => jest.fn()), +})); + +describe('Tracking Functions', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should fire registrationSuccessEvent', () => { + trackRegistrationSuccess(); + + expect(createEventTracker).toHaveBeenCalledWith( + eventNames.registrationSuccess, + {}, + ); + }); + + it('should fire trackRegistrationPageEvent', () => { + trackRegistrationPageViewed(); + + expect(createPageEventTracker).toHaveBeenCalledWith( + eventNames.loginAndRegistration, + 'register', + ); + }); +}); diff --git a/src/tracking/trackers/tests/reset-password.test.jsx b/src/tracking/trackers/tests/reset-password.test.jsx new file mode 100644 index 00000000..15d6ed79 --- /dev/null +++ b/src/tracking/trackers/tests/reset-password.test.jsx @@ -0,0 +1,26 @@ +import { createPageEventTracker } from '../../../data/segment/utils'; +import { + eventNames, + trackResetPasswordPageViewed, +} from '../reset-password'; + +// Mock createEventTracker function +jest.mock('../../../data/segment/utils', () => ({ + createEventTracker: jest.fn().mockImplementation(() => jest.fn()), + createPageEventTracker: jest.fn().mockImplementation(() => jest.fn()), +})); + +describe('Tracking Functions', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should fire trackResettPasswordPageEvent', () => { + trackResetPasswordPageViewed(); + + expect(createPageEventTracker).toHaveBeenCalledWith( + eventNames.loginAndRegistration, + 'reset-password', + ); + }); +});