Skip to content

Commit

Permalink
Stepper: Add a flag to skip the calypso_signup_start on sign up flo…
Browse files Browse the repository at this point in the history
…ws (#91597)

* Add hook to manage the signup_start events

* Use hook to manage the sign up event
  • Loading branch information
gabrielcaires authored Jun 10, 2024
1 parent 84cb354 commit 6f03668
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { SENSEI_FLOW } from '@automattic/onboarding';
import { useEffect, useMemo } from 'react';
import { useQuery } from 'calypso/landing/stepper/hooks/use-query';
import { recordSignupStart } from 'calypso/lib/analytics/signup';
import { type Flow } from '../../types';

/**
* Hook to track the start of a signup flow.
*/
interface Props {
flow: Flow;
currentStepRoute: string;
}

export const useSignUpStartTracking = ( { flow, currentStepRoute }: Props ) => {
const steps = flow.useSteps();
const queryParams = useQuery();
const ref = queryParams.get( 'ref' ) || '';
const signedUp = queryParams.has( 'signed_up' );

// TODO: Check if we can remove the sensei flow reference from here.
const firstStepSlug = ( flow.name === SENSEI_FLOW ? steps[ 1 ] : steps[ 0 ] ).slug;
const isFirstStep = firstStepSlug === currentStepRoute;
const extraProps = useMemo( () => flow.useSignupStartEventProps?.() || {}, [ flow ] );
const flowName = flow.name;
const shouldTrack = flow.isSignupFlow && isFirstStep && ! signedUp;

useEffect( () => {
if ( ! shouldTrack ) {
return;
}

recordSignupStart( flowName, ref, extraProps );
}, [ extraProps, flowName, ref, shouldTrack ] );
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/**
* @jest-environment jsdom
*/

import { recordTracksEvent } from '@automattic/calypso-analytics';
import { SENSEI_FLOW } from '@automattic/onboarding';
import { addQueryArgs } from '@wordpress/url';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { renderHookWithProvider } from 'calypso/test-helpers/testing-library';
import { useSignUpStartTracking } from '../';
import type { Flow, StepperStep } from '../../../types';

const steps = [ { slug: 'step-1' }, { slug: 'step-2' } ] as StepperStep[];

const regularFlow: Flow = {
name: 'regular-flow',
useSteps: () => steps,
useStepNavigation: () => ( {
submit: () => {},
} ),
isSignupFlow: false,
};

const signUpFlow: Flow = {
...regularFlow,
name: 'sign-up-flow',
isSignupFlow: true,
};

const senseiFlow: Flow = {
...regularFlow,
name: SENSEI_FLOW,
// The original sensei flow is missing the isSignupFlow flag as true, it will be addressed by wp-calypso/pull/91593
isSignupFlow: true,
};

jest.mock( '@automattic/calypso-analytics' );

const render = ( { flow, currentStepRoute, queryParams = {} } ) => {
return renderHookWithProvider(
() =>
useSignUpStartTracking( {
flow,
currentStepRoute,
} ),
{
wrapper: ( { children } ) => (
<MemoryRouter initialEntries={ [ addQueryArgs( `/setup/${ flow.name }`, queryParams ) ] }>
{ children }
</MemoryRouter>
),
}
);
};

describe( 'useSignUpTracking', () => {
beforeEach( () => {
jest.clearAllMocks();
} );

it( 'does not track event when the flow is not a isSignupFlow', () => {
render( { flow: regularFlow, currentStepRoute: 'step-1' } );

expect( recordTracksEvent ).not.toHaveBeenCalled();
} );

describe( 'sign-up-flow', () => {
it( 'tracks the event current step is first step', () => {
render( {
flow: signUpFlow,
currentStepRoute: 'step-1',
queryParams: { ref: 'another-flow-or-cta' },
} );

expect( recordTracksEvent ).toHaveBeenCalledWith( 'calypso_signup_start', {
flow: 'sign-up-flow',
ref: 'another-flow-or-cta',
} );
} );

it( 'skips the tracking when the signed up flag is set', () => {
render( {
flow: signUpFlow,
currentStepRoute: 'step-1',
queryParams: { signed_up: 1 },
} );

expect( recordTracksEvent ).not.toHaveBeenCalled();
} );

it( 'tracks the event with extra props from useSighupStartEventProps', () => {
render( {
flow: {
...signUpFlow,
useSignupStartEventProps: () => ( { extra: 'props' } ),
} satisfies Flow,
currentStepRoute: 'step-1',
queryParams: { ref: 'another-flow-or-cta' },
} );

expect( recordTracksEvent ).toHaveBeenCalledWith( 'calypso_signup_start', {
flow: 'sign-up-flow',
ref: 'another-flow-or-cta',
extra: 'props',
} );
} );

it( 'does not track events current step is NOT the first step', () => {
render( { flow: signUpFlow, currentStepRoute: 'step-2' } );

expect( recordTracksEvent ).not.toHaveBeenCalled();
} );

it( 'does not track events current step is the first step AND the user is returning from the sign in flow', () => {
render( { flow: signUpFlow, currentStepRoute: 'step-1', queryParams: { signed_up: true } } );

expect( recordTracksEvent ).not.toHaveBeenCalled();
} );

// Check if sensei is a sign-up flow;
it( "tracks when the user is on the sensei's flow second step", () => {
render( { flow: senseiFlow, currentStepRoute: 'step-2' } );

expect( recordTracksEvent ).toHaveBeenCalledWith( 'calypso_signup_start', {
flow: SENSEI_FLOW,
ref: '',
} );
} );

it( 'does not trigger the event on rerender', () => {
const { rerender } = render( {
flow: { ...signUpFlow, useSignupStartEventProps: () => ( { extra: 'props' } ) },
currentStepRoute: 'step-1',
queryParams: { ref: 'another-flow-or-cta' },
} );

rerender();

expect( recordTracksEvent ).toHaveBeenNthCalledWith( 1, 'calypso_signup_start', {
flow: 'sign-up-flow',
ref: 'another-flow-or-cta',
extra: 'props',
} );
} );
} );
} );
33 changes: 6 additions & 27 deletions client/landing/stepper/declarative-flow/internals/index.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,27 @@
import {
SENSEI_FLOW,
isNewsletterOrLinkInBioFlow,
isSenseiFlow,
isWooExpressFlow,
} from '@automattic/onboarding';
import { useSelect, useDispatch } from '@wordpress/data';
import { useI18n } from '@wordpress/react-i18n';
import React, { useEffect, useCallback, useMemo, Suspense, lazy } from 'react';
import React, { useEffect, useMemo, Suspense, lazy } from 'react';
import Modal from 'react-modal';
import { Navigate, Route, Routes, generatePath, useNavigate, useLocation } from 'react-router-dom';
import DocumentHead from 'calypso/components/data/document-head';
import { STEPPER_INTERNAL_STORE } from 'calypso/landing/stepper/stores';
import { recordSignupStart } from 'calypso/lib/analytics/signup';
import AsyncCheckoutModal from 'calypso/my-sites/checkout/modal/async';
import { useSelector } from 'calypso/state';
import { getSite } from 'calypso/state/sites/selectors';
import { useQuery } from '../../hooks/use-query';
import { useSaveQueryParams } from '../../hooks/use-save-query-params';
import { useSiteData } from '../../hooks/use-site-data';
import useSyncRoute from '../../hooks/use-sync-route';
import { ONBOARD_STORE } from '../../stores';
import { StepRoute, StepperLoader } from './components';
import { useSignUpStartTracking } from './hooks/use-sign-up-start-tracking';
import { AssertConditionState, type Flow, type StepperStep, type StepProps } from './types';
import type { OnboardSelect, StepperInternalSelect } from '@automattic/data-stores';

import './global.scss';

/**
Expand All @@ -40,6 +39,7 @@ export const FlowRenderer: React.FC< { flow: Flow } > = ( { flow } ) => {
Modal.setAppElement( '#wpcom' );
const flowSteps = flow.useSteps();
const stepPaths = flowSteps.map( ( step ) => step.slug );

const stepComponents: Record< string, React.FC< StepProps > > = useMemo(
() =>
flowSteps.reduce(
Expand All @@ -64,7 +64,6 @@ export const FlowRenderer: React.FC< { flow: Flow } > = ( { flow } ) => {
);

useSaveQueryParams();
const ref = useQuery().get( 'ref' ) || '';

const { site, siteSlugOrId } = useSiteData();

Expand All @@ -91,18 +90,6 @@ export const FlowRenderer: React.FC< { flow: Flow } > = ( { flow } ) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ flow, siteSlugOrId, selectedSite ] );

const isFlowStart = useCallback( () => {
if ( ! flow || ! stepPaths.length ) {
return false;
}

if ( flow.name === SENSEI_FLOW ) {
return currentStepRoute === stepPaths[ 1 ];
}

return currentStepRoute === stepPaths[ 0 ];
}, [ flow, currentStepRoute, ...stepPaths ] );

const _navigate = async ( path: string, extraData = {} ) => {
// If any extra data is passed to the navigate() function, store it to the stepper-internal store.
setStepData( {
Expand Down Expand Up @@ -139,16 +126,6 @@ export const FlowRenderer: React.FC< { flow: Flow } > = ( { flow } ) => {
window.scrollTo( 0, 0 );
}, [ location ] );

// Get any flow-specific event props to include in the
// `calypso_signup_start` Tracks event triggerd in the effect below.
const signupStartEventProps = flow.useSignupStartEventProps?.() ?? {};

useEffect( () => {
if ( flow.isSignupFlow && isFlowStart() ) {
recordSignupStart( flow.name, ref, signupStartEventProps );
}
}, [ flow, ref, isFlowStart ] );

const assertCondition = flow.useAssertConditions?.( _navigate ) ?? {
state: AssertConditionState.SUCCESS,
};
Expand Down Expand Up @@ -184,6 +161,8 @@ export const FlowRenderer: React.FC< { flow: Flow } > = ( { flow } ) => {
}
};

useSignUpStartTracking( { flow, currentStepRoute: currentStepRoute } );

return (
<Suspense fallback={ <StepperLoader /> }>
<DocumentHead title={ getDocumentHeadTitle() } />
Expand Down

0 comments on commit 6f03668

Please sign in to comment.