Skip to content

Commit

Permalink
Stepper: Framework tracking for remaining step-navigation controls (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
chriskmnds authored Sep 3, 2024
1 parent 8b21a6e commit 5cb03ba
Show file tree
Hide file tree
Showing 4 changed files with 263 additions and 27 deletions.
4 changes: 3 additions & 1 deletion client/landing/stepper/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ export const STEPPER_TRACKS_EVENT_STEP_NAV_GO_NEXT = 'calypso_signup_step_nav_ne
export const STEPPER_TRACKS_EVENT_STEP_NAV_GO_TO = 'calypso_signup_step_nav_go_to';
export const STEPPER_TRACKS_EVENT_STEP_NAV_EXIT_FLOW = 'calypso_signup_step_nav_exit_flow';

export const STEPPER_TRACKS_EVENTS = < const >[
export const STEPPER_TRACKS_EVENTS_STEP_NAV = < const >[
STEPPER_TRACKS_EVENT_STEP_NAV_SUBMIT,
STEPPER_TRACKS_EVENT_STEP_NAV_GO_BACK,
STEPPER_TRACKS_EVENT_STEP_NAV_GO_NEXT,
STEPPER_TRACKS_EVENT_STEP_NAV_GO_TO,
STEPPER_TRACKS_EVENT_STEP_NAV_EXIT_FLOW,
];

export const STEPPER_TRACKS_EVENTS = < const >[ ...STEPPER_TRACKS_EVENTS_STEP_NAV ];
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
import { resolveDeviceTypeByViewPort } from '@automattic/viewport';
import { reduce, snakeCase } from 'lodash';
import { STEPPER_TRACKS_EVENTS_STEP_NAV } from 'calypso/landing/stepper/constants';
import { getStepOldSlug } from 'calypso/landing/stepper/declarative-flow/helpers/get-step-old-slug';
import { recordTracksEvent } from 'calypso/lib/analytics/tracks';
import { ProvidedDependencies } from '../types';

export function recordSubmitStep(
providedDependencies: ProvidedDependencies = {},
intent: string,
flow: string,
step: string,
variant?: string,
additionalProps: ProvidedDependencies = {}
) {
export interface RecordStepNavigationParams {
event: ( typeof STEPPER_TRACKS_EVENTS_STEP_NAV )[ number ];
intent: string;
flow: string;
step: string;
variant?: string;
providedDependencies?: ProvidedDependencies;
additionalProps?: ProvidedDependencies;
}

export function recordStepNavigation( {
event,
intent,
flow,
step,
variant,
providedDependencies = {},
additionalProps = {},
}: RecordStepNavigationParams ) {
const device = resolveDeviceTypeByViewPort();
const inputs = reduce(
providedDependencies,
Expand Down Expand Up @@ -58,7 +70,7 @@ export function recordSubmitStep(
{}
);

recordTracksEvent( 'calypso_signup_actions_submit_step', {
recordTracksEvent( event, {
device,
flow,
variant,
Expand All @@ -70,7 +82,7 @@ export function recordSubmitStep(

const stepOldSlug = getStepOldSlug( step );
if ( stepOldSlug ) {
recordTracksEvent( 'calypso_signup_actions_submit_step', {
recordTracksEvent( event, {
device,
flow,
variant,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import { OnboardSelect } from '@automattic/data-stores';
import { useSelect } from '@wordpress/data';
import { useMemo } from '@wordpress/element';
import { STEPPER_TRACKS_EVENT_STEP_NAV_SUBMIT } from 'calypso/landing/stepper/constants';
import { useCallback, useMemo } from '@wordpress/element';
import {
STEPPER_TRACKS_EVENT_STEP_NAV_EXIT_FLOW,
STEPPER_TRACKS_EVENT_STEP_NAV_GO_BACK,
STEPPER_TRACKS_EVENT_STEP_NAV_GO_NEXT,
STEPPER_TRACKS_EVENT_STEP_NAV_GO_TO,
STEPPER_TRACKS_EVENT_STEP_NAV_SUBMIT,
} from 'calypso/landing/stepper/constants';
import { ONBOARD_STORE } from 'calypso/landing/stepper/stores';
import { recordSubmitStep } from '../../analytics/record-submit-step';
import {
recordStepNavigation,
type RecordStepNavigationParams,
} from '../../analytics/record-step-navigation';
import type { Flow, Navigate, ProvidedDependencies, StepperStep } from '../../types';

interface Params< FlowSteps extends StepperStep[] > {
Expand All @@ -28,21 +37,80 @@ export const useStepNavigationWithTracking = ( {
useSelect( ( select ) => ( select( ONBOARD_STORE ) as OnboardSelect ).getIntent(), [] ) ?? '';
const tracksEventPropsFromFlow = flow.useTracksEventProps?.();

const handleRecordStepNavigation = useCallback(
( {
event,
providedDependencies,
additionalProps,
}: Omit< RecordStepNavigationParams, 'step' | 'intent' | 'flow' | 'variant' > ) => {
recordStepNavigation( {
event,
intent,
flow: flow.name,
step: currentStepRoute,
variant: flow.variantSlug,
providedDependencies,
additionalProps,
} );
},
[ intent, currentStepRoute, flow ]
);

return useMemo(
() => ( {
...stepNavigation,
submit: ( providedDependencies: ProvidedDependencies = {}, ...params: string[] ) => {
recordSubmitStep(
providedDependencies,
intent,
flow.name,
currentStepRoute,
flow.variantSlug,
tracksEventPropsFromFlow?.[ STEPPER_TRACKS_EVENT_STEP_NAV_SUBMIT ]
);
stepNavigation.submit?.( providedDependencies, ...params );
},
...( stepNavigation.submit && {
submit: ( providedDependencies: ProvidedDependencies = {}, ...params: string[] ) => {
handleRecordStepNavigation( {
event: STEPPER_TRACKS_EVENT_STEP_NAV_SUBMIT,
providedDependencies,
additionalProps: tracksEventPropsFromFlow?.[ STEPPER_TRACKS_EVENT_STEP_NAV_SUBMIT ],
} );
stepNavigation.submit?.( providedDependencies, ...params );
},
} ),
...( stepNavigation.exitFlow && {
exitFlow: ( to: string ) => {
handleRecordStepNavigation( {
event: STEPPER_TRACKS_EVENT_STEP_NAV_EXIT_FLOW,
additionalProps: {
to,
...( tracksEventPropsFromFlow?.[ STEPPER_TRACKS_EVENT_STEP_NAV_EXIT_FLOW ] ?? {} ),
},
} );
stepNavigation.exitFlow?.( to );
},
} ),
...( stepNavigation.goBack && {
goBack: () => {
handleRecordStepNavigation( {
event: STEPPER_TRACKS_EVENT_STEP_NAV_GO_BACK,
additionalProps: tracksEventPropsFromFlow?.[ STEPPER_TRACKS_EVENT_STEP_NAV_GO_BACK ],
} );
stepNavigation.goBack?.();
},
} ),
...( stepNavigation.goNext && {
goNext: () => {
handleRecordStepNavigation( {
event: STEPPER_TRACKS_EVENT_STEP_NAV_GO_NEXT,
additionalProps: tracksEventPropsFromFlow?.[ STEPPER_TRACKS_EVENT_STEP_NAV_GO_NEXT ],
} );
stepNavigation.goNext?.();
},
} ),
...( stepNavigation.goToStep && {
goToStep: ( step: string ) => {
handleRecordStepNavigation( {
event: STEPPER_TRACKS_EVENT_STEP_NAV_GO_TO,
additionalProps: {
to: step,
...( tracksEventPropsFromFlow?.[ STEPPER_TRACKS_EVENT_STEP_NAV_GO_TO ] ?? {} ),
},
} );
stepNavigation.goToStep?.( step );
},
} ),
} ),
[ stepNavigation, intent, flow, currentStepRoute, tracksEventPropsFromFlow ]
[ handleRecordStepNavigation, tracksEventPropsFromFlow, stepNavigation ]
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/**
* @jest-environment jsdom
*/
import { renderHook, act } from '@testing-library/react';
import {
STEPPER_TRACKS_EVENT_STEP_NAV_EXIT_FLOW,
STEPPER_TRACKS_EVENT_STEP_NAV_GO_BACK,
STEPPER_TRACKS_EVENT_STEP_NAV_GO_NEXT,
STEPPER_TRACKS_EVENT_STEP_NAV_GO_TO,
STEPPER_TRACKS_EVENT_STEP_NAV_SUBMIT,
} from '../../../../../constants';
import { recordStepNavigation } from '../../../analytics/record-step-navigation';
import { useStepNavigationWithTracking } from '../index';

jest.mock( '@wordpress/data', () => ( {
useSelect: jest.fn(),
} ) );
jest.mock( 'calypso/landing/stepper/stores', () => ( {
ONBOARD_STORE: {},
} ) );
jest.mock( '../../../analytics/record-step-navigation', () => ( {
recordStepNavigation: jest.fn(),
} ) );

describe( 'useStepNavigationWithTracking', () => {
const stepNavControls = {
submit: jest.fn(),
exitFlow: jest.fn(),
goBack: jest.fn(),
goNext: jest.fn(),
goToStep: jest.fn(),
};

const mockParams = {
flow: {
name: 'mock-flow',
isSignupFlow: false,
useSteps: () => [],
useStepNavigation: () => stepNavControls,
},
currentStepRoute: 'mock-step',
navigate: () => {},
steps: [],
};

beforeEach( () => {
jest.clearAllMocks();
} );

it( 'returns callbacks for all known navigation controls', () => {
const { result } = renderHook( () => useStepNavigationWithTracking( mockParams ) );

expect( result.current ).toHaveProperty( 'submit' );
expect( result.current ).toHaveProperty( 'exitFlow' );
expect( result.current ).toHaveProperty( 'goBack' );
expect( result.current ).toHaveProperty( 'goNext' );
expect( result.current ).toHaveProperty( 'goToStep' );
} );

it( 'calls the wrapped submit control with correct parameters and records the respective event', () => {
const { result } = renderHook( () => useStepNavigationWithTracking( mockParams ) );
const providedDependencies = { foo: 'foo' };
act( () => {
result.current.submit?.( providedDependencies, 'bar', 'baz' );
} );

expect( stepNavControls.submit ).toHaveBeenCalledWith( providedDependencies, 'bar', 'baz' );
expect( recordStepNavigation ).toHaveBeenCalledWith( {
event: STEPPER_TRACKS_EVENT_STEP_NAV_SUBMIT,
intent: '',
flow: 'mock-flow',
step: 'mock-step',
variant: undefined,
providedDependencies,
additionalProps: undefined,
} );
} );

it( 'calls the wrapped goBack control with correct parameters and records the respective event', () => {
const { result } = renderHook( () => useStepNavigationWithTracking( mockParams ) );

act( () => {
result.current.goBack?.();
} );

expect( stepNavControls.goBack ).toHaveBeenCalled();
expect( recordStepNavigation ).toHaveBeenCalledWith( {
event: STEPPER_TRACKS_EVENT_STEP_NAV_GO_BACK,
intent: '',
flow: 'mock-flow',
step: 'mock-step',
variant: undefined,
providedDependencies: undefined,
additionalProps: undefined,
} );
} );

it( 'calls the wrapped goNext control with correct parameters and records the respective event', () => {
const { result } = renderHook( () => useStepNavigationWithTracking( mockParams ) );

act( () => {
result.current.goNext?.();
} );

expect( stepNavControls.goNext ).toHaveBeenCalled();
expect( recordStepNavigation ).toHaveBeenCalledWith( {
event: STEPPER_TRACKS_EVENT_STEP_NAV_GO_NEXT,
intent: '',
flow: 'mock-flow',
step: 'mock-step',
variant: undefined,
providedDependencies: undefined,
additionalProps: undefined,
} );
} );

it( 'calls the wrapped exitFlow control with correct parameters and records the respective event', () => {
const { result } = renderHook( () => useStepNavigationWithTracking( mockParams ) );

act( () => {
result.current.exitFlow?.( 'to' );
} );

expect( stepNavControls.exitFlow ).toHaveBeenCalledWith( 'to' );
expect( recordStepNavigation ).toHaveBeenCalledWith( {
event: STEPPER_TRACKS_EVENT_STEP_NAV_EXIT_FLOW,
intent: '',
flow: 'mock-flow',
step: 'mock-step',
variant: undefined,
providedDependencies: undefined,
additionalProps: { to: 'to' },
} );
} );

it( 'calls the wrapped goToStep control with correct parameters and records the respective event', () => {
const { result } = renderHook( () => useStepNavigationWithTracking( mockParams ) );

act( () => {
result.current.goToStep?.( 'to' );
} );

expect( stepNavControls.goToStep ).toHaveBeenCalledWith( 'to' );
expect( recordStepNavigation ).toHaveBeenCalledWith( {
event: STEPPER_TRACKS_EVENT_STEP_NAV_GO_TO,
intent: '',
flow: 'mock-flow',
step: 'mock-step',
variant: undefined,
providedDependencies: undefined,
additionalProps: { to: 'to' },
} );
} );
} );

0 comments on commit 5cb03ba

Please sign in to comment.