Skip to content

Commit

Permalink
My Jetpack Welcome Flow: Show default recommendations upfront first, …
Browse files Browse the repository at this point in the history
…then offer optional survey (#39485)
  • Loading branch information
elliottprogrammer authored Sep 27, 2024
1 parent 4520ed0 commit c837623
Show file tree
Hide file tree
Showing 13 changed files with 192 additions and 43 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: changed

My Jetpack Welcome Flow: Display default recommendations upfront first, then offer optional survey for customized recommendations.
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ public static function get_option_names( $type = 'compact' ) {
'dismissed_welcome_banner', // (bool) Determines if the welcome banner has been dismissed or not.
'recommendations_evaluation', // (object) Catalog of recommended modules with corresponding score following successful site evaluation in Welcome Banner.
'dismissed_recommendations', // (bool) Determines if the recommendations have been dismissed or not.
'recommendations_first_run', // (bool) Determines if the current recommendations are the initial default auto-loaded ones (without user input).
'historically_active_modules', // (array) List of installed plugins/enabled modules that have at one point in time been active and working
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,52 @@
import { Container, Col, Text } from '@automattic/jetpack-components';
import { DropdownMenu } from '@wordpress/components';
import { Flex } from '@wordpress/components';
import { FlexItem } from '@wordpress/components';
import { Flex, FlexItem, DropdownMenu, Button } from '@wordpress/components';
import { createInterpolateElement } from '@wordpress/element';
import { __, _n } from '@wordpress/i18n';
import { moreHorizontalMobile } from '@wordpress/icons';
import { useEffect } from 'react';
import { useEffect, useCallback } from 'react';
import useEvaluationRecommendations from '../../data/evaluation-recommendations/use-evaluation-recommendations';
import useAnalytics from '../../hooks/use-analytics';
import getPurchasePlanUrl from '../../utils/get-purchase-plan-url';
import { JetpackModuleToProductCard } from '../product-cards-section/all';
import styles from './style.module.scss';
import type { WelcomeFlowExperiment } from '../welcome-flow';
import type { FC } from 'react';

const EvaluationRecommendations: React.FC = () => {
interface Props {
welcomeFlowExperimentVariation: WelcomeFlowExperiment[ 'variation' ];
}

const EvaluationRecommendations: FC< Props > = ( { welcomeFlowExperimentVariation } ) => {
const { recordEvent } = useAnalytics();
const { recommendedModules, redoEvaluation, removeEvaluationResult } =
const { recommendedModules, isFirstRun, redoEvaluation, removeEvaluationResult } =
useEvaluationRecommendations();
const isTreatmentVariation = welcomeFlowExperimentVariation === 'treatment';

const handleExploreAllPlansLinkClick = useCallback( () => {
recordEvent( 'jetpack_myjetpack_evaluation_recommendations_explore_all_plans_click' );
}, [ recordEvent ] );

// We're defining each of these translations in separate variables here, otherwise optimizations in
// the build step end up breaking the translations and causing error.
const recommendationsHeadline = _n(
'Our recommendation for you',
'Our recommendations for you',
recommendedModules.length,
'jetpack-my-jetpack'
);
const recommendationsHeadlineTreatment = __( 'Recommended for your site', 'jetpack-my-jetpack' );
const menuRedoTitle = __( 'Redo', 'jetpack-my-jetpack' );
const menuRedoTitleTreatment = __( 'Customize recommendations', 'jetpack-my-jetpack' );
const menuDismissTitle = __( 'Dismiss', 'jetpack-my-jetpack' );
const menuDismissTitleTreatment = __( 'Close', 'jetpack-my-jetpack' );
const recommendationsRedoLink = __(
'Start over? <link>Analyze again for fresh recommendations</link>!',
'jetpack-my-jetpack'
);
const recommendationsRedoLinkTreatment = __(
'Find your perfect match by <link>letting us know what you’re looking for</link>!',
'jetpack-my-jetpack'
);

useEffect( () => {
recordEvent( 'jetpack_myjetpack_evaluation_recommendations_view', {
Expand All @@ -27,19 +60,18 @@ const EvaluationRecommendations: React.FC = () => {
<Flex>
<FlexItem>
<Text variant="headline-small" className={ styles.title }>
{ _n(
'Our recommendation for you',
'Our recommendations for you',
recommendedModules.length,
'jetpack-my-jetpack'
) }
</Text>
<Text>
{ __(
'Here are the tools that we think will help you reach your website goals:',
'jetpack-my-jetpack'
) }
{ isTreatmentVariation && isFirstRun
? recommendationsHeadlineTreatment
: recommendationsHeadline }
</Text>
{ ! isTreatmentVariation && (
<Text>
{ __(
'Here are the tools that we think will help you reach your website goals:',
'jetpack-my-jetpack'
) }
</Text>
) }
</FlexItem>
<FlexItem>
<DropdownMenu
Expand All @@ -49,11 +81,15 @@ const EvaluationRecommendations: React.FC = () => {
label={ __( 'Recommendations menu', 'jetpack-my-jetpack' ) }
controls={ [
{
title: __( 'Redo', 'jetpack-my-jetpack' ),
title:
isTreatmentVariation && isFirstRun ? menuRedoTitleTreatment : menuRedoTitle,
onClick: redoEvaluation,
},
{
title: __( 'Dismiss', 'jetpack-my-jetpack' ),
title:
isTreatmentVariation && isFirstRun
? menuDismissTitleTreatment
: menuDismissTitle,
onClick: removeEvaluationResult,
},
] }
Expand Down Expand Up @@ -81,6 +117,40 @@ const EvaluationRecommendations: React.FC = () => {
} ) }
</Container>
</Col>
{ isTreatmentVariation && (
<Col>
<Flex>
<FlexItem>
<Text variant="body">
{ createInterpolateElement(
isFirstRun ? recommendationsRedoLinkTreatment : recommendationsRedoLink,
{
link: (
<Button
variant="link"
className={ styles[ 'evaluation-footer-link' ] }
onClick={ redoEvaluation }
/>
),
}
) }
</Text>
</FlexItem>
<FlexItem>
<Text variant="body">
<Button
variant="link"
className={ styles[ 'evaluation-footer-link' ] }
href={ getPurchasePlanUrl() }
onClick={ handleExploreAllPlansLinkClick }
>
{ __( 'Explore all Jetpack plans', 'jetpack-my-jetpack' ) }
</Button>
</Text>
</FlexItem>
</Flex>
</Col>
) }
</Container>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
width: 200px;
}

.evaluation-footer-link {
font-size: var( --font-body );
}

// Setting min-width of recommendation (product) cards to 300px; see: /components/product-cards-section/style.module.scss:62
@media screen and (min-width: 599px) and (max-width: 1290px) {
ul.recommendations-list {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
useBreakpointMatch,
ActionButton,
} from '@automattic/jetpack-components';
import { useExperiment } from '@automattic/jetpack-explat';
import clsx from 'clsx';
import { useContext, useEffect, useLayoutEffect, useState } from 'react';
/*
Expand Down Expand Up @@ -77,7 +76,10 @@ const GlobalNotice = ( { message, title, options } ) => {
* @return {object} The MyJetpackScreen component.
*/
export default function MyJetpackScreen() {
useExperiment( 'explat_test_jetpack_implementation_aa_test' );
const [ welcomeFlowExperiment, setWelcomeFlowExperiment ] = useState( {
isLoading: false,
variation: 'control',
} );
useNotificationWatcher();
const { redBubbleAlerts } = getMyJetpackWindowInitialState();
const { jetpackManage = {}, adminUrl } = getMyJetpackWindowInitialState();
Expand Down Expand Up @@ -143,7 +145,10 @@ export default function MyJetpackScreen() {
</Container>
) }
{ isWelcomeBannerVisible ? (
<WelcomeFlow>
<WelcomeFlow
welcomeFlowExperiment={ welcomeFlowExperiment }
setWelcomeFlowExperiment={ setWelcomeFlowExperiment }
>
{ noticeMessage && siteIsRegistered && (
<GlobalNotice
message={ noticeMessage }
Expand All @@ -165,7 +170,11 @@ export default function MyJetpackScreen() {
</Container>
)
) }
{ ! isWelcomeBannerVisible && isSectionVisible && <EvaluationRecommendations /> }
{ ! isWelcomeBannerVisible && isSectionVisible && (
<EvaluationRecommendations
welcomeFlowExperimentVariation={ welcomeFlowExperiment.variation }
/>
) }

<ProductCardsSection />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import useProductsByOwnership from '../../data/products/use-products-by-ownershi
import useAnalytics from '../../hooks/use-analytics';
import sideloadTracks from '../../utils/side-load-tracks';
import styles from './style.module.scss';
import { WelcomeFlowExperiment } from '.';
import type { WelcomeFlowExperiment } from '.';
import type { Dispatch, SetStateAction } from 'react';

type ConnectionStepProps = {
Expand Down Expand Up @@ -50,12 +50,12 @@ const ConnectionStep = ( {
initializeExPlat();

const { variationName } = await loadExperimentAssignment(
'jetpack_my_jetpack_evaluation_recommendations_202409'
'jetpack_my_jetpack_welcome_flow_display_default_recommendations_upfront_202410'
);

onUpdateWelcomeFlowExperiment( state => ( {
...state,
variation: variationName as WelcomeFlowExperiment[ 'variation' ], // casting to 'control' or 'treatment'
variation: ( variationName ?? 'control' ) as WelcomeFlowExperiment[ 'variation' ], // casting to 'control' or 'treatment'
} ) );
} finally {
resetNotice();
Expand Down
60 changes: 49 additions & 11 deletions projects/packages/my-jetpack/_inc/components/welcome-flow/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,19 @@ export type WelcomeFlowExperiment = {
variation: 'control' | 'treatment';
};

const WelcomeFlow: FC< PropsWithChildren > = ( { children } ) => {
interface Props extends PropsWithChildren {
welcomeFlowExperiment: WelcomeFlowExperiment;
setWelcomeFlowExperiment: React.Dispatch< React.SetStateAction< WelcomeFlowExperiment > >;
}

const WelcomeFlow: FC< Props > = ( {
welcomeFlowExperiment,
setWelcomeFlowExperiment,
children,
} ) => {
const { recordEvent } = useAnalytics();
const { dismissWelcomeBanner } = useWelcomeBanner();
const { recommendedModules, submitEvaluation, saveEvaluationResult } =
const { recommendedModules, isFirstRun, submitEvaluation, saveEvaluationResult } =
useEvaluationRecommendations();
const {
siteIsRegistered,
Expand All @@ -36,24 +45,25 @@ 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 || welcomeFlowExperiment.isLoading ) {
return 'connection';
} else if ( ! isProcessingEvaluation ) {
if (
! recommendedModules &&
( welcomeFlowExperiment.variation === 'treatment' || ! isJetpackUserNew() )
) {
if ( ! recommendedModules && ! isJetpackUserNew() ) {
// If user is not new but doesn't have recommendations, we skip evaluation
// If user has recommendations, it means they redo the evaluation
return null;
}

// For the "treatment" experiment we immediately jump to the 'evaluation-processing' step if
// there are no `recommendedModules` loaded yet.
if (
'treatment' === welcomeFlowExperiment.variation &&
! recommendedModules &&
isJetpackUserNew()
) {
return 'evaluation-processing';
}
// Otherwise, it means user is either new or just repeats the recommendation
return 'evaluation';
}
Expand Down Expand Up @@ -107,6 +117,34 @@ const WelcomeFlow: FC< PropsWithChildren > = ( { children } ) => {
[ dismissWelcomeBanner, recordEvent, saveEvaluationResult, submitEvaluation ]
);

useEffect( () => {
// For the "treatment" experiment, when there are no `recommendedModules` loaded yet,
// we immediately submit some default evaluation data (when we change from connection
// step to evaluation-processing step).
if (
'treatment' === welcomeFlowExperiment.variation &&
! recommendedModules &&
isFirstRun &&
prevStep === 'connection' &&
currentStep === 'evaluation-processing'
) {
handleEvaluation( {
protect: true,
performance: true,
audience: true,
content: true,
unsure: false,
} );
}
}, [
currentStep,
prevStep,
recommendedModules,
welcomeFlowExperiment.variation,
handleEvaluation,
isFirstRun,
] );

useEffect( () => {
if ( ! currentStep ) {
dismissWelcomeBanner();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ type ValueStoreType = {
isLoadingWelcomeFlowExperiment?: boolean;
recommendedModules: JetpackModule[] | null;
recommendedModulesVisible: boolean;
isFirstRun: boolean;
productsOwnership: {
ownedProducts: JetpackModule[];
unownedProducts: JetpackModule[];
Expand Down
Loading

0 comments on commit c837623

Please sign in to comment.