Add analytics logging to join flow. Adding page views for each step in the flow.

This commit is contained in:
picklesrus 2019-11-04 10:47:04 -05:00
parent 77fca39bbf
commit e4b79c1bd2
12 changed files with 194 additions and 26 deletions

View file

@ -54,6 +54,10 @@ class BirthDateStep extends React.Component {
'validateSelect'
]);
}
componentDidMount () {
this.props.sendAnalytics('join-birthdate');
}
validateSelect (selection) {
if (selection === 'null') {
return this.props.intl.formatMessage({id: 'general.required'});
@ -162,7 +166,8 @@ class BirthDateStep extends React.Component {
BirthDateStep.propTypes = {
intl: intlShape,
onNextStep: PropTypes.func
onNextStep: PropTypes.func,
sendAnalytics: PropTypes.func
};
const IntlBirthDateStep = injectIntl(BirthDateStep);

View file

@ -23,6 +23,7 @@ class CountryStep extends React.Component {
this.countryOptions = [];
}
componentDidMount () {
this.props.sendAnalytics('join-country');
this.setCountryOptions();
}
setCountryOptions () {
@ -120,7 +121,8 @@ class CountryStep extends React.Component {
CountryStep.propTypes = {
intl: intlShape,
onNextStep: PropTypes.func
onNextStep: PropTypes.func,
sendAnalytics: PropTypes.func
};
const IntlCountryStep = injectIntl(CountryStep);

View file

@ -35,6 +35,7 @@ class EmailStep extends React.Component {
this.emailRemoteCache = {};
}
componentDidMount () {
this.props.sendAnalytics('join-email');
// automatically start with focus on username field
if (this.emailInput) this.emailInput.focus();
@ -231,6 +232,11 @@ EmailStep.propTypes = {
intl: intlShape,
onCaptchaError: PropTypes.func,
onNextStep: PropTypes.func,
<<<<<<< HEAD
=======
onRegistrationError: PropTypes.func,
sendAnalytics: PropTypes.func,
>>>>>>> Add analytics logging to join flow. Adding page views for each step in the flow.
waiting: PropTypes.bool
};

View file

@ -61,6 +61,9 @@ class GenderStep extends React.Component {
'handleValidSubmit'
]);
}
componentDidMount () {
this.props.sendAnalytics('join-gender');
}
handleSetCustomRef (customInputRef) {
this.customInput = customInputRef;
}
@ -180,7 +183,8 @@ class GenderStep extends React.Component {
GenderStep.propTypes = {
intl: intlShape,
onNextStep: PropTypes.func
onNextStep: PropTypes.func,
sendAnalytics: PropTypes.func
};
module.exports = injectIntl(GenderStep);

View file

@ -215,6 +215,18 @@ class JoinFlow extends React.Component {
resetState () {
this.setState(this.initialState);
}
sendAnalytics (path) {
const gaID = window.GA_ID;
if (!window.ga) {
return;
}
window.ga('send', {
hitType: 'pageview',
page: path,
tid: gaID
});
}
render () {
return (
<React.Fragment>
@ -228,11 +240,26 @@ class JoinFlow extends React.Component {
/>
) : (
<Progression step={this.state.step}>
<UsernameStep onNextStep={this.handleAdvanceStep} />
<CountryStep onNextStep={this.handleAdvanceStep} />
<BirthDateStep onNextStep={this.handleAdvanceStep} />
<GenderStep onNextStep={this.handleAdvanceStep} />
<UsernameStep
sendAnalytics={this.sendAnalytics}
onNextStep={this.handleAdvanceStep}
/>
<CountryStep
sendAnalytics={this.sendAnalytics}
onNextStep={this.handleAdvanceStep}
/>
<BirthDateStep
sendAnalytics={this.sendAnalytics}
onNextStep={this.handleAdvanceStep}
/>
<GenderStep
sendAnalytics={this.sendAnalytics}
onNextStep={this.handleAdvanceStep}
/>
<EmailStep
sendAnalytics={this.sendAnalytics}
waiting={this.state.waiting}
onCaptchaError={this.handleCaptchaError}
onNextStep={this.handlePrepareToRegister}
@ -240,6 +267,7 @@ class JoinFlow extends React.Component {
<WelcomeStep
createProjectOnComplete={this.props.createProjectOnComplete}
email={this.state.formData.email}
sendAnalytics={this.sendAnalytics}
username={this.state.formData.username}
onNextStep={this.props.onCompleteRegistration}
/>

View file

@ -15,6 +15,9 @@ class RegistrationErrorStep extends React.Component {
'handleSubmit'
]);
}
componentDidMount () {
this.props.sendAnalytics('join-error');
}
handleSubmit (e) {
// JoinFlowStep includes a <form> that handles a submit action.
// But here, we're not really submitting, so we need to prevent
@ -60,7 +63,8 @@ RegistrationErrorStep.propTypes = {
canTryAgain: PropTypes.bool.isRequired,
errorMsg: PropTypes.string,
intl: intlShape,
onSubmit: PropTypes.func.isRequired
onSubmit: PropTypes.func.isRequired,
sendAnalytics: PropTypes.func.isRequired
};
RegistrationErrorStep.defaultProps = {

View file

@ -37,6 +37,12 @@ class UsernameStep extends React.Component {
this.usernameRemoteCache = {};
}
componentDidMount () {
// Send info to analytics when we aren't on the standalone page.
// If we are on the standalone join page, the page load will take care of this.
if (!window.location.pathname.includes('/join')) {
this.props.sendAnalytics('join-email');
}
// automatically start with focus on username field
if (this.usernameInput) this.usernameInput.focus();
}
@ -282,7 +288,8 @@ class UsernameStep extends React.Component {
UsernameStep.propTypes = {
intl: intlShape,
onNextStep: PropTypes.func
onNextStep: PropTypes.func,
sendAnalytics: PropTypes.func
};
const IntlUsernameStep = injectIntl(UsernameStep);

View file

@ -17,6 +17,12 @@ class WelcomeStep extends React.Component {
'validateForm'
]);
}
componentDidMount () {
if (this.props.sendAnalytics) {
this.props.sendAnalytics('join-welcome');
}
}
validateForm () {
return {};
}
@ -87,6 +93,7 @@ WelcomeStep.propTypes = {
email: PropTypes.string,
intl: intlShape,
onNextStep: PropTypes.func,
sendAnalytics: PropTypes.func,
username: PropTypes.string
};

View file

@ -1,5 +1,6 @@
const React = require('react');
const {shallowWithIntl} = require('../../helpers/intl-helpers.jsx');
const {mountWithIntl} = require('../../helpers/intl-helpers.jsx');
const JoinFlowStep = require('../../../src/components/join-flow/join-flow-step.jsx');
const FormikInput = require('../../../src/components/formik-forms/formik-input.jsx');
const FormikCheckbox = require('../../../src/components/formik-forms/formik-checkbox.jsx');
@ -36,13 +37,17 @@ jest.mock('../../../src/lib/validate.js', () => (
const EmailStep = require('../../../src/components/join-flow/email-step.jsx');
describe('EmailStep test', () => {
const defaultProps = () => ({
sendAnalytics: jest.fn()
});
afterEach(() => {
jest.clearAllMocks();
});
test('send correct props to formik', () => {
const intlWrapper = shallowWithIntl(<EmailStep />);
const intlWrapper = shallowWithIntl(<EmailStep
{...defaultProps()}
/>);
const emailStepWrapper = intlWrapper.dive();
expect(emailStepWrapper.props().initialValues.subscribe).toBe(false);
expect(emailStepWrapper.props().initialValues.email).toBe('');
@ -53,7 +58,9 @@ describe('EmailStep test', () => {
});
test('props sent to JoinFlowStep', () => {
const intlWrapper = shallowWithIntl(<EmailStep />);
const intlWrapper = shallowWithIntl(<EmailStep
{...defaultProps()}
/>);
// Dive to get past the intl wrapper
const emailStepWrapper = intlWrapper.dive();
// Dive to get past the anonymous component.
@ -69,7 +76,9 @@ describe('EmailStep test', () => {
});
test('props sent to FormikInput for email', () => {
const intlWrapper = shallowWithIntl(<EmailStep />);
const intlWrapper = shallowWithIntl(<EmailStep
{...defaultProps()}
/>);
// Dive to get past the intl wrapper
const emailStepWrapper = intlWrapper.dive();
// Dive to get past the anonymous component.
@ -86,7 +95,10 @@ describe('EmailStep test', () => {
});
test('props sent to FormikCheckbox for subscribe', () => {
const intlWrapper = shallowWithIntl(<EmailStep />);
const intlWrapper = shallowWithIntl(<EmailStep
{...defaultProps()}
/>);
// Dive to get past the intl wrapper
const emailStepWrapper = intlWrapper.dive();
// Dive to get past the anonymous component.
@ -109,7 +121,10 @@ describe('EmailStep test', () => {
};
const formData = {item1: 'thing', item2: 'otherthing'};
const intlWrapper = shallowWithIntl(
<EmailStep />);
<EmailStep
{...defaultProps()}
/>);
const emailStepWrapper = intlWrapper.dive();
emailStepWrapper.instance().onCaptchaLoad(); // to setup catpcha state
@ -133,6 +148,7 @@ describe('EmailStep test', () => {
const formData = {item1: 'thing', item2: 'otherthing'};
const intlWrapper = shallowWithIntl(
<EmailStep
{...defaultProps()}
{...props}
/>);
@ -162,6 +178,7 @@ describe('EmailStep test', () => {
global.grecaptcha = null;
const intlWrapper = shallowWithIntl(
<EmailStep
{...defaultProps()}
{...props}
/>
);
@ -171,9 +188,20 @@ describe('EmailStep test', () => {
expect(props.onCaptchaError).toHaveBeenCalled();
});
test('Component logs analytics', () => {
const sendAnalyticsFn = jest.fn();
mountWithIntl(
<EmailStep
sendAnalytics={sendAnalyticsFn}
/>);
expect(sendAnalyticsFn).toHaveBeenCalledWith('join-email');
});
test('validateEmail test email empty', () => {
const intlWrapper = shallowWithIntl(
<EmailStep />);
<EmailStep
{...defaultProps()}
/>);
const emailStepWrapper = intlWrapper.dive();
const val = emailStepWrapper.instance().validateEmail('');
expect(val).toBe('general.required');
@ -181,7 +209,9 @@ describe('EmailStep test', () => {
test('validateEmail test email null', () => {
const intlWrapper = shallowWithIntl(
<EmailStep />);
<EmailStep
{...defaultProps()}
/>);
const emailStepWrapper = intlWrapper.dive();
const val = emailStepWrapper.instance().validateEmail(null);
expect(val).toBe('general.required');
@ -189,7 +219,9 @@ describe('EmailStep test', () => {
test('validateEmail test email undefined', () => {
const intlWrapper = shallowWithIntl(
<EmailStep />);
<EmailStep
{...defaultProps()}
/>);
const emailStepWrapper = intlWrapper.dive();
const val = emailStepWrapper.instance().validateEmail();
expect(val).toBe('general.required');
@ -204,7 +236,9 @@ describe('validateEmailRemotelyWithCache test with successful requests', () => {
test('validateEmailRemotelyWithCache calls validate.validateEmailRemotely', done => {
const intlWrapper = shallowWithIntl(
<EmailStep />);
<EmailStep
{...defaultProps()}
/>);
const instance = intlWrapper.dive().instance();
instance.validateEmailRemotelyWithCache('some-email@some-domain.com')
@ -218,8 +252,10 @@ describe('validateEmailRemotelyWithCache test with successful requests', () => {
test('validateEmailRemotelyWithCache, called twice with different data, makes two remote requests', done => {
const intlWrapper = shallowWithIntl(
<EmailStep />
);
<EmailStep
{...defaultProps()}
/>);
const instance = intlWrapper.dive().instance();
instance.validateEmailRemotelyWithCache('some-email@some-domain.com')
@ -242,8 +278,10 @@ describe('validateEmailRemotelyWithCache test with successful requests', () => {
test('validateEmailRemotelyWithCache, called twice with same data, only makes one remote request', done => {
const intlWrapper = shallowWithIntl(
<EmailStep />
);
<EmailStep
{...defaultProps()}
/>);
const instance = intlWrapper.dive().instance();
instance.validateEmailRemotelyWithCache('some-email@some-domain.com')

View file

@ -67,6 +67,19 @@ describe('JoinFlow', () => {
});
});
test('sendAnalytics calls google analytics with correct params', () => {
const joinFlowInstance = getJoinFlowWrapper().instance();
global.window.ga = jest.fn();
global.window.GA_ID = '1234';
joinFlowInstance.sendAnalytics('page-path');
const obj = {
hitType: 'pageview',
page: 'page-path',
tid: '1234'
};
expect(global.window.ga).toHaveBeenCalledWith('send', obj);
});
test('handleAdvanceStep', () => {
const joinFlowInstance = getJoinFlowWrapper().instance();
joinFlowInstance.setState({formData: {username: 'ScratchCat123'}, step: 2});

View file

@ -9,6 +9,12 @@ describe('RegistrationErrorStep', () => {
const getRegistrationErrorStepWrapper = props => {
const wrapper = shallowWithIntl(
<RegistrationErrorStep
<<<<<<< HEAD
=======
errorMsg={'error message'}
sendAnalytics={jest.fn()}
onSubmit={onSubmit}
>>>>>>> Add analytics logging to join flow. Adding page views for each step in the flow.
{...props}
/>
);

View file

@ -1,5 +1,6 @@
const React = require('react');
const {shallowWithIntl} = require('../../helpers/intl-helpers.jsx');
const {mountWithIntl} = require('../../helpers/intl-helpers.jsx');
const requestSuccessResponse = {
requestSucceeded: true,
@ -22,6 +23,7 @@ let mockedValidateUsernameRemotely = jest.fn(() => (
/* eslint-enable no-undef */
));
jest.mock('../../../src/lib/validate.js', () => (
{
...(jest.requireActual('../../../src/lib/validate.js')),
@ -32,10 +34,22 @@ jest.mock('../../../src/lib/validate.js', () => (
// must come after validation mocks, so validate.js will be mocked before it is required
const UsernameStep = require('../../../src/components/join-flow/username-step.jsx');
<<<<<<< HEAD
describe('UsernameStep tests', () => {
=======
describe('UsernameStep test', () => {
const defaultProps = () => ({
sendAnalytics: jest.fn()
});
afterEach(() => {
jest.clearAllMocks();
});
>>>>>>> Add analytics logging to join flow. Adding page views for each step in the flow.
test('send correct props to formik', () => {
const wrapper = shallowWithIntl(<UsernameStep />);
const wrapper = shallowWithIntl(<UsernameStep
{...defaultProps()}
/>);
const formikWrapper = wrapper.dive();
expect(formikWrapper.props().initialValues.username).toBe('');
expect(formikWrapper.props().initialValues.password).toBe('');
@ -47,6 +61,28 @@ describe('UsernameStep tests', () => {
expect(formikWrapper.props().onSubmit).toBe(formikWrapper.instance().handleValidSubmit);
});
test('Component does not log if path is /join', () => {
const sendAnalyticsFn = jest.fn();
global.window.history.pushState({}, '', '/join');
mountWithIntl(
<UsernameStep
sendAnalytics={sendAnalyticsFn}
/>);
expect(sendAnalyticsFn).not.toHaveBeenCalled();
});
test('Component logs analytics', () => {
// Make sure '/join' is NOT in the path
global.window.history.pushState({}, '', '/');
const sendAnalyticsFn = jest.fn();
mountWithIntl(
<UsernameStep
sendAnalytics={sendAnalyticsFn}
/>);
expect(sendAnalyticsFn).toHaveBeenCalledWith('join-email');
});
test('handleValidSubmit passes formData to next step', () => {
const formikBag = {
setSubmitting: jest.fn()
@ -55,6 +91,7 @@ describe('UsernameStep tests', () => {
const mockedOnNextStep = jest.fn();
const wrapper = shallowWithIntl(
<UsernameStep
{...defaultProps()}
onNextStep={mockedOnNextStep}
/>
);
@ -74,7 +111,14 @@ describe('validateUsernameRemotelyWithCache test with successful requests', () =
});
test('validateUsernameRemotelyWithCache calls validate.validateUsernameRemotely', done => {
<<<<<<< HEAD
const wrapper = shallowWithIntl(<UsernameStep />);
=======
const wrapper = shallowWithIntl(
<UsernameStep
{...defaultProps()}
/>);
>>>>>>> Add analytics logging to join flow. Adding page views for each step in the flow.
const instance = wrapper.dive().instance();
instance.validateUsernameRemotelyWithCache('newUniqueUsername55')
@ -89,7 +133,9 @@ describe('validateUsernameRemotelyWithCache test with successful requests', () =
test('validateUsernameRemotelyWithCache, called twice with different data, makes two remote requests', done => {
const wrapper = shallowWithIntl(
<UsernameStep />
<UsernameStep
{...defaultProps()}
/>
);
const instance = wrapper.dive().instance();
@ -115,7 +161,9 @@ describe('validateUsernameRemotelyWithCache test with successful requests', () =
test('validateUsernameRemotelyWithCache, called twice with same data, only makes one remote request', done => {
const wrapper = shallowWithIntl(
<UsernameStep />
<UsernameStep
{...defaultProps()}
/>
);
const instance = wrapper.dive().instance();