diff --git a/src/login/LoginHelpLinks.jsx b/src/login/LoginHelpLinks.jsx index b170074e..98c6bc2f 100644 --- a/src/login/LoginHelpLinks.jsx +++ b/src/login/LoginHelpLinks.jsx @@ -1,5 +1,6 @@ import React, { useState } from 'react'; import PropTypes from 'prop-types'; +import { sendTrackEvent } from '@edx/frontend-platform/analytics'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCaretDown, faCaretRight } from '@fortawesome/free-solid-svg-icons'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; @@ -22,8 +23,13 @@ const LoginHelpLinks = (props) => { setShowLoginHelpValue(!showLoginHelp); }; + const handleForgotPasswordLinkClickEvent = () => { + sendTrackEvent('edx.bi.password-reset_form.toggled', { category: 'user-engagement' }); + }; + const forgotPasswordLink = () => ( - + + {intl.formatMessage(messages['forgot.password.link'])} ); diff --git a/src/login/LoginPage.jsx b/src/login/LoginPage.jsx index 7acda232..06f6a6db 100644 --- a/src/login/LoginPage.jsx +++ b/src/login/LoginPage.jsx @@ -3,6 +3,7 @@ import Skeleton from 'react-loading-skeleton'; import { getConfig } from '@edx/frontend-platform'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics'; import { Form, Hyperlink, Input, StatefulButton, ValidationFormGroup, } from '@edx/paragon'; @@ -58,6 +59,8 @@ class LoginPage extends React.Component { } handleInstitutionLogin = () => { + sendTrackEvent('edx.bi.institution_login_form.toggled', { category: 'user-engagement' }); + sendPageEvent('login_and_registration', 'institution_login'); this.setState(prevState => ({ institutionLogin: !prevState.institutionLogin })); } @@ -123,6 +126,10 @@ class LoginPage extends React.Component { }); } + handleCreateAccountLinkClickEvent() { + sendTrackEvent('edx.bi.register_form.toggled', { category: 'user-engagement' }); + } + renderThirdPartyAuth(providers, secondaryProviders, currentProvider, thirdPartyAuthApiStatus, intl) { let thirdPartyComponent = null; if ((providers.length || secondaryProviders.length) && !currentProvider) { @@ -162,6 +169,8 @@ class LoginPage extends React.Component { /> ); } + + sendPageEvent('login_and_registration', 'login'); return ( <> {intl.formatMessage(messages['first.time.here'])} - + {intl.formatMessage(messages['create.an.account'])}. diff --git a/src/login/tests/LoginHelpLinks.test.jsx b/src/login/tests/LoginHelpLinks.test.jsx index bf550127..e975aa1c 100644 --- a/src/login/tests/LoginHelpLinks.test.jsx +++ b/src/login/tests/LoginHelpLinks.test.jsx @@ -1,5 +1,6 @@ import React from 'react'; import { IntlProvider } from '@edx/frontend-platform/i18n'; +import * as analytics from '@edx/frontend-platform/analytics'; import { mount } from 'enzyme'; import LoginHelpLinks from '../LoginHelpLinks'; @@ -11,6 +12,9 @@ jest.mock('@edx/frontend-platform', () => ({ getConfig: jest.fn().mockReturnValue({ LOGIN_ISSUE_SUPPORT_LINK: otherSignInIssues }), })); +jest.mock('@edx/frontend-platform/analytics'); +analytics.sendTrackEvent = jest.fn(); + describe('LoginHelpLinks', () => { let props = {}; diff --git a/src/login/tests/LoginPage.test.jsx b/src/login/tests/LoginPage.test.jsx index d4b0ca32..c1771f8e 100644 --- a/src/login/tests/LoginPage.test.jsx +++ b/src/login/tests/LoginPage.test.jsx @@ -6,10 +6,16 @@ import configureStore from 'redux-mock-store'; import { getConfig } from '@edx/frontend-platform'; import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n'; +import * as analytics from '@edx/frontend-platform/analytics'; import LoginPage from '../LoginPage'; import { RenderInstitutionButton } from '../../common-components'; import { PENDING_STATE } from '../../data/constants'; +jest.mock('@edx/frontend-platform/analytics'); + +analytics.sendTrackEvent = jest.fn(); +analytics.sendPageEvent = jest.fn(); + const IntlLoginPage = injectIntl(LoginPage); const mockStore = configureStore(); @@ -278,4 +284,34 @@ describe('LoginPage', () => { loginPage.find(RenderInstitutionButton).simulate('click', { institutionLogin: true }); expect(loginPage.text().includes('Test University')).toBe(true); }); + + it('send tracking event when create account link is clicked', () => { + const loginPage = mount(reduxWrapper()); + + loginPage.find('a[href*="/register"]').simulate('click'); + loginPage.update(); + expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.bi.register_form.toggled', { category: 'user-engagement' }); + }); + + it('send page event when login page is rendered', () => { + mount(reduxWrapper()); + expect(analytics.sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'login'); + }); + + it('send tracking and page events when institutional button is clicked', () => { + store = mockStore({ + ...initialState, + commonComponents: { + ...initialState.commonComponents, + thirdPartyAuthContext: { + ...initialState.commonComponents.thirdPartyAuthContext, + secondaryProviders: [secondaryProviders], + }, + }, + }); + const loginPage = mount(reduxWrapper()); + loginPage.find(RenderInstitutionButton).simulate('click', { institutionLogin: true }); + expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.bi.institution_login_form.toggled', { category: 'user-engagement' }); + expect(analytics.sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'institution_login'); + }); }); diff --git a/src/register/RegistrationPage.jsx b/src/register/RegistrationPage.jsx index 3b6d5201..1d544003 100644 --- a/src/register/RegistrationPage.jsx +++ b/src/register/RegistrationPage.jsx @@ -2,6 +2,7 @@ import React from 'react'; import { connect } from 'react-redux'; import Skeleton from 'react-loading-skeleton'; import PropTypes from 'prop-types'; +import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics'; import { Input, StatefulButton, @@ -229,6 +230,7 @@ class RegistrationPage extends React.Component { this.setState({ enableOptionalField: targetValue, }); + sendTrackEvent('edx.bi.user.register.optional_fields_selected', {}); } handleOnClick(e) { @@ -244,6 +246,10 @@ class RegistrationPage extends React.Component { } } + handleLoginLinkClickEvent() { + sendTrackEvent('edx.bi.login_form.toggled', { category: 'user-engagement' }); + } + validateInput(inputName, value) { const { errors, @@ -543,6 +549,7 @@ class RegistrationPage extends React.Component { ); } + sendPageEvent('login_and_registration', 'register'); return ( <> {intl.formatMessage(messages['already.have.an.edx.account'])} - {intl.formatMessage(messages['sign.in.hyperlink'])} + {intl.formatMessage(messages['sign.in.hyperlink'])} {(providers.length || secondaryProviders.length || thirdPartyAuthApiStatus === PENDING_STATE) && !currentProvider ? ( diff --git a/src/register/tests/RegistrationPage.test.jsx b/src/register/tests/RegistrationPage.test.jsx index 3f6b1a00..c120ecc3 100644 --- a/src/register/tests/RegistrationPage.test.jsx +++ b/src/register/tests/RegistrationPage.test.jsx @@ -5,12 +5,18 @@ import { mount } from 'enzyme'; import configureStore from 'redux-mock-store'; import { getConfig } from '@edx/frontend-platform'; import { IntlProvider, injectIntl, configure } from '@edx/frontend-platform/i18n'; +import * as analytics from '@edx/frontend-platform/analytics'; import RegistrationPage from '../RegistrationPage'; import { RenderInstitutionButton } from '../../common-components'; import { PENDING_STATE } from '../../data/constants'; import { fetchRegistrationForm, fetchRealtimeValidations, registerNewUser } from '../data/actions'; +jest.mock('@edx/frontend-platform/analytics'); + +analytics.sendTrackEvent = jest.fn(); +analytics.sendPageEvent = jest.fn(); + const IntlRegistrationPage = injectIntl(RegistrationPage); const mockStore = configureStore(); @@ -141,6 +147,65 @@ describe('./RegistrationPage.js', () => { expect(registrationPage.find('RegistrationPage').state('enableOptionalField')).toEqual(true); }); + it('send tracking event on optional checkbox enabled', () => { + store = mockStore({ + ...initialState, + logistration: { + ...initialState.logistration, + formData: { + fields: [ + { + label: 'Tell us why you\'re interested in edX', + name: 'goals', + type: 'textarea', + required: false, + }, + { + label: 'Highest level of Education completed.', + name: 'level_of_education', + type: 'select', + options: [{ value: '', name: '--' }, { value: 'p', name: 'Doctorate' }], + required: false, + }, + { + label: 'Year of birth.', + name: 'year_of_birth', + type: 'select', + options: [{ value: '', name: '--' }, { value: '2021', name: '2021' }], + required: false, + }, + { + label: 'Gender.', + name: 'gender', + type: 'select', + options: [{ value: '', name: '--' }, { value: 'f', name: 'Female' }], + required: false, + }, + ], + }, + }, + }); + + const registrationPage = mount(reduxWrapper()); + + registrationPage.find('input#optional').simulate('change', { target: { checked: true } }); + registrationPage.update(); + expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.bi.user.register.optional_fields_selected', {}); + }); + + it('send tracking event when login link is clicked', () => { + const registrationPage = mount(reduxWrapper()); + + registrationPage.find('a[href*="/login"]').simulate('click'); + registrationPage.update(); + expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.bi.login_form.toggled', { category: 'user-engagement' }); + }); + + it('send page event when register page is rendered', () => { + mount(reduxWrapper()); + expect(analytics.sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'register'); + }); + it('should show optional fields section on optional check enabled', () => { store = mockStore({ ...initialState, diff --git a/src/register/tests/__snapshots__/RegistrationPage.test.jsx.snap b/src/register/tests/__snapshots__/RegistrationPage.test.jsx.snap index 0ec0da52..b0506a90 100644 --- a/src/register/tests/__snapshots__/RegistrationPage.test.jsx.snap +++ b/src/register/tests/__snapshots__/RegistrationPage.test.jsx.snap @@ -27,6 +27,7 @@ exports[`./RegistrationPage.js should display no password field when current pro Sign in. @@ -218,6 +219,7 @@ exports[`./RegistrationPage.js should match TPA provider snapshot 1`] = ` Sign in. @@ -473,6 +475,7 @@ exports[`./RegistrationPage.js should match default section snapshot 1`] = ` Sign in. @@ -693,6 +696,7 @@ exports[`./RegistrationPage.js should match pending button state snapshot 1`] = Sign in. @@ -948,6 +952,7 @@ exports[`./RegistrationPage.js should show error message on 409 1`] = ` Sign in.
{intl.formatMessage(messages['first.time.here'])} - + {intl.formatMessage(messages['create.an.account'])}.