Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

My Jetpack: add A/B experiment to welcome flow #38664

Merged
merged 12 commits into from
Aug 5, 2024
Merged
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { Col, Button, Text, TermsOfService } from '@automattic/jetpack-components';
import { initializeExPlat, loadExperimentAssignment } from '@automattic/jetpack-explat';
import { __ } from '@wordpress/i18n';
import { useCallback, useContext } from 'react';
import { NoticeContext } from '../../context/notices/noticeContext';
import { NOTICE_SITE_CONNECTED } from '../../context/notices/noticeTemplates';
import useAnalytics from '../../hooks/use-analytics';
import sideloadTracks from '../../utils/side-load-tracks';
import styles from './style.module.scss';
import { WelcomeFlowExperiment } from '.';
import type { Dispatch, SetStateAction } from 'react';

type ConnectionStepProps = {
onActivateSite: ( e?: Event ) => Promise< void >;
onUpdateWelcomeFlowExperiment: Dispatch< SetStateAction< WelcomeFlowExperiment > >;
isActivating: boolean;
};

Expand All @@ -16,23 +21,45 @@ type ConnectionStepProps = {
*
* @param {object} props - ConnectioStepProps
* @param {Function} props.onActivateSite - Alias for handleRegisterSite
* @param {Function} props.onUpdateWelcomeFlowExperiment - Updating the welcomeFlowExperiment state
* @param {boolean} props.isActivating - Alias for siteIsRegistering
* @returns {object} The ConnectionStep component.
*/
const ConnectionStep = ( { onActivateSite, isActivating }: ConnectionStepProps ) => {
const ConnectionStep = ( {
onActivateSite,
onUpdateWelcomeFlowExperiment,
isActivating,
}: ConnectionStepProps ) => {
const { recordEvent } = useAnalytics();
const { setNotice, resetNotice } = useContext( NoticeContext );

const activationButtonLabel = __( 'Activate Jetpack in one click', 'jetpack-my-jetpack' );

const onConnectSiteClick = useCallback( () => {
const onConnectSiteClick = useCallback( async () => {
recordEvent( 'jetpack_myjetpack_welcome_banner_connect_site_click' );
onActivateSite().then( () => {
recordEvent( 'jetpack_myjetpack_welcome_banner_connect_site_success' );
resetNotice();
setNotice( NOTICE_SITE_CONNECTED, resetNotice );
} );
}, [ onActivateSite, recordEvent, resetNotice, setNotice ] );
onUpdateWelcomeFlowExperiment( state => ( { ...state, isLoading: true } ) );
await onActivateSite();

recordEvent( 'jetpack_myjetpack_welcome_banner_connect_site_success' );

await sideloadTracks();
robertsreberski marked this conversation as resolved.
Show resolved Hide resolved

initializeExPlat();

const { variationName } = await loadExperimentAssignment(
robertsreberski marked this conversation as resolved.
Show resolved Hide resolved
'jetpack_my_jetpack_post_connection_flow_202408'
);

onUpdateWelcomeFlowExperiment( state => ( {
...state,
variation: variationName as WelcomeFlowExperiment[ 'variation' ], // casting to 'control' or 'treatment'
} ) );

resetNotice();
setNotice( NOTICE_SITE_CONNECTED, resetNotice );

onUpdateWelcomeFlowExperiment( state => ( { ...state, isLoading: false } ) );
}, [ onActivateSite, onUpdateWelcomeFlowExperiment, recordEvent, resetNotice, setNotice ] );

return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ import EvaluationStep, { EvaluationAreas } from './EvaluationStep';
import styles from './style.module.scss';
import type { FC, PropsWithChildren } from 'react';

export type WelcomeFlowExperiment = {
isLoading: boolean;
variation: 'control' | 'treatment';
};

const WelcomeFlow: FC< PropsWithChildren > = ( { children } ) => {
const { recordEvent } = useAnalytics();
const { dismissWelcomeBanner } = useWelcomeBanner();
Expand All @@ -30,11 +35,19 @@ const WelcomeFlow: FC< PropsWithChildren > = ( { children } ) => {
} );
const [ isProcessingEvaluation, setIsProcessingEvaluation ] = useState( false );
const [ prevStep, setPrevStep ] = useState( '' );
const [ welcomeFlowExperiment, setWelcomeFlowExperiment ] = useState< WelcomeFlowExperiment >( {
isLoading: false,
variation: 'control',
} );

const currentStep = useMemo( () => {
if ( ! siteIsRegistered ) {
if ( ! siteIsRegistered || welcomeFlowExperiment.isLoading ) {
return 'connection';
} else if ( ! isProcessingEvaluation ) {
if ( welcomeFlowExperiment.variation !== 'treatment' ) {
// For control or default, we redirect to the connection page as described in the experiment.
window.location.href = 'admin.php?page=my-jetpack#/connection';
}
if ( ! isJetpackUserNew() ) {
// If the user is not new, we don't show the evaluation step
return null;
Expand All @@ -43,7 +56,7 @@ const WelcomeFlow: FC< PropsWithChildren > = ( { children } ) => {
}

return 'evaluation-processing';
}, [ isProcessingEvaluation, siteIsRegistered ] );
}, [ isProcessingEvaluation, siteIsRegistered, welcomeFlowExperiment ] );

useEffect( () => {
if ( prevStep !== currentStep ) {
Expand Down Expand Up @@ -108,7 +121,8 @@ const WelcomeFlow: FC< PropsWithChildren > = ( { children } ) => {
{ 'connection' === currentStep && (
<ConnectionStep
onActivateSite={ handleRegisterSite }
isActivating={ siteIsRegistering }
onUpdateWelcomeFlowExperiment={ setWelcomeFlowExperiment }
isActivating={ siteIsRegistering || welcomeFlowExperiment.isLoading }
/>
) }
{ 'evaluation' === currentStep && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { Dispatch, SetStateAction } from 'react';

type ValueStoreType = {
isWelcomeBannerVisible: boolean;
isLoadingWelcomeFlowExperiment?: boolean;
recommendedModules: JetpackModule[] | null;
recommendedModulesVisible: boolean;
};
Expand Down
58 changes: 58 additions & 0 deletions projects/packages/my-jetpack/_inc/utils/side-load-tracks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
declare global {
interface Window {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_tkq: Array< Array< any > >;
}
}

/**
* Function to get the current year and week number.
*
* @returns {string} The current year and week number.
*/
function getCurrentYearAndWeek() {
const date = new Date();
const year = date.getFullYear();

const firstDayOfYear = new Date( year, 0, 1 );
const daysSinceFirstDay = Math.floor(
( date.getTime() - firstDayOfYear.getTime() ) / ( 24 * 60 * 60 * 1000 )
);

// Calculate the current week number (assuming week starts on Sunday)
const weekNumber = Math.ceil( ( daysSinceFirstDay + firstDayOfYear.getDay() + 1 ) / 7 );
const formattedWeekNumber = weekNumber.toString().padStart( 2, '0' );

return `${ year }${ formattedWeekNumber }`;
}

/**
* Function to dynamically load a script into the document.
* It creates a new script element, sets its source to the provided URL,
* and appends it to the document's head.
*
* @param {string} src - The URL of the script to load.
* @returns {Promise<void>} A promise that resolves once the script has loaded.
*/
function loadScript( src: string ): Promise< void > {
return new Promise( resolve => {
const script = document.createElement( 'script' );
script.src = src;
script.onload = () => resolve();
document.head.appendChild( script );
} );
}

/**
* Function to sideload Tracks script.
*
* It initializes the _tkq array on the window object if it doesn't exist,
* and then loads the tracking script from the specified URL. Once the script has loaded,
* the provided callback function is called.
*
* @returns {Promise<void>} A promise that resolves once the Tracks has been side loaded.
*/
export default function sideloadTracks(): Promise< void > {
window._tkq = window._tkq || [];
return loadScript( `//stats.wp.com/w.js?${ getCurrentYearAndWeek() }` );
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Significance: patch
Type: changed
Comment: Added a simple A/B experimento to the Welcome Flow on My Jetpack.


2 changes: 1 addition & 1 deletion projects/packages/my-jetpack/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"private": true,
"name": "@automattic/jetpack-my-jetpack",
"version": "4.31.0",
"version": "4.31.1-alpha",
"description": "WP Admin page with information and configuration shared among all Jetpack stand-alone plugins",
"homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/packages/my-jetpack/#readme",
"bugs": {
Expand Down
2 changes: 1 addition & 1 deletion projects/packages/my-jetpack/src/class-initializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class Initializer {
*
* @var string
*/
const PACKAGE_VERSION = '4.31.0';
const PACKAGE_VERSION = '4.31.1-alpha';

/**
* HTML container ID for the IDC screen on My Jetpack page.
Expand Down
Loading