diff --git a/includes/RestApi/EventsController.php b/includes/RestApi/EventsController.php index c5cffb52b..7f89ef5da 100644 --- a/includes/RestApi/EventsController.php +++ b/includes/RestApi/EventsController.php @@ -71,7 +71,7 @@ public function get_send_args() { 'validate_callback' => array( EventService::class, 'validate_action' ), ), 'category' => array( - 'default' => Events::get_category(), + 'default' => Events::get_category()[0], 'description' => __( 'Event category', 'wp-module-onboarding' ), 'type' => 'string', 'sanitize_callback' => 'sanitize_title', diff --git a/includes/Services/EventService.php b/includes/Services/EventService.php index d8b215677..c91c27d8f 100644 --- a/includes/Services/EventService.php +++ b/includes/Services/EventService.php @@ -45,7 +45,13 @@ public static function send( $event ) { * @return boolean */ public static function validate_category( $category ) { - return Events::get_category() === $category; + $default_categories = Events::get_category(); + foreach ( $default_categories as $event_category ) { + if ( $event_category === $category ) { + return true; + } + } + return false; } /** diff --git a/src/OnboardingSPA/components/StartOptions/contents.js b/src/OnboardingSPA/components/StartOptions/contents.js new file mode 100644 index 000000000..28a4b8916 --- /dev/null +++ b/src/OnboardingSPA/components/StartOptions/contents.js @@ -0,0 +1,9 @@ +import { __ } from '@wordpress/i18n'; + +const getContents = () => { + return { + badge: __( 'Fastest', 'wp-module-onboarding' ), + }; +}; + +export default getContents; diff --git a/src/OnboardingSPA/components/StartOptions/index.js b/src/OnboardingSPA/components/StartOptions/index.js index 3d400e248..7c1a5118d 100644 --- a/src/OnboardingSPA/components/StartOptions/index.js +++ b/src/OnboardingSPA/components/StartOptions/index.js @@ -1,18 +1,34 @@ -import { SITEGEN_FLOW } from '../../data/flows/constants'; -import { resolveGetDataForFlow } from '../../data/flows'; +// WordPress import { useSelect, useDispatch } from '@wordpress/data'; -import { validateFlow } from '../../data/flows/utils'; +import { memo, useEffect, useState } from '@wordpress/element'; + +// Classes and functions import { useNavigate } from 'react-router-dom'; -import { memo } from '@wordpress/element'; -import { store as nfdOnboardingStore } from '../../store'; +import { validateFlow } from '../../data/flows/utils'; +import { resolveGetDataForFlow } from '../../data/flows'; + +// Misc import { OnboardingEvent, trackOnboardingEvent, } from '../../utils/analytics/hiive'; +import { SITEGEN_FLOW } from '../../data/flows/constants'; +import { store as nfdOnboardingStore } from '../../store'; import { ACTION_SITEGEN_FORK_OPTION_SELECTED } from '../../utils/analytics/hiive/constants'; +import getContents from './contents'; -const StartOptions = ( { questionnaire, oldFlow, options } ) => { +const StartOptions = ( { + experimentVersion, + questionnaire, + oldFlow, + options, +} ) => { + const content = getContents(); const navigate = useNavigate(); + const [ forkOptions, setForkOptions ] = useState( [] ); + const [ showAIRecommendedBadge, setShowAIRecommendedBadge ] = + useState( false ); + const { brandConfig, hireProUrl, currentData } = useSelect( ( select ) => { return { brandConfig: select( nfdOnboardingStore ).getNewfoldBrandConfig(), @@ -31,6 +47,25 @@ const StartOptions = ( { questionnaire, oldFlow, options } ) => { setCurrentOnboardingData, } = useDispatch( nfdOnboardingStore ); + useEffect( () => { + if ( + experimentVersion && + ( experimentVersion === 2 || experimentVersion === 4 ) + ) { + // Swap the DIY flow with the AI Flow + [ options[ 0 ], options[ 1 ] ] = [ options[ 1 ], options[ 0 ] ]; + } + + if ( + experimentVersion && + ( experimentVersion === 3 || experimentVersion === 4 ) + ) { + // Show a Badge in the AI Option + setShowAIRecommendedBadge( true ); + } + setForkOptions( options ); + }, [ experimentVersion ] ); + const switchFlow = ( newFlow ) => { if ( ! validateFlow( brandConfig, newFlow ) ) { return false; @@ -81,51 +116,67 @@ const StartOptions = ( { questionnaire, oldFlow, options } ) => { ); } }; + return ( -
-

- { questionnaire } -

-
- { options.map( ( tab, idx ) => { - if ( - tab.flow === SITEGEN_FLOW && - ! validateFlow( brandConfig, tab.flow ) - ) { - // Do not show the Sitegen AI option if not enabled for the customer - return false; - } - return ( -
{ - selectFlow( tab.flow ); - } } - onKeyDown={ () => { - { - selectFlow( tab.flow ); + experimentVersion && + forkOptions && ( +
+

+ { questionnaire } +

+
+ { forkOptions.map( ( tab, idx ) => { + if ( + tab.flow === SITEGEN_FLOW && + ! validateFlow( brandConfig, tab.flow ) + ) { + // Do not show the Sitegen AI option if not enabled for the customer + return false; + } + return ( +
-

- { tab.span && ( - - { tab.span } - - ) } - { tab.title } -

-

- { tab.subtitle } -

-
- ); - } ) } + key={ idx } + role="button" + tabIndex={ 0 } + onClick={ () => { + selectFlow( tab.flow ); + } } + onKeyDown={ () => { + { + selectFlow( tab.flow ); + } + } } + > + { tab.flow === SITEGEN_FLOW && + showAIRecommendedBadge && ( +
+ { content.badge } +
+ ) } +

+ { tab.span && ( + + { tab.span } + + ) } + { tab.title } +

+

+ { tab.subtitle } +

+
+ ); + } ) } +
-
+ ) ); }; diff --git a/src/OnboardingSPA/components/StartOptions/stylesheet.scss b/src/OnboardingSPA/components/StartOptions/stylesheet.scss index b57c58d58..021bde287 100644 --- a/src/OnboardingSPA/components/StartOptions/stylesheet.scss +++ b/src/OnboardingSPA/components/StartOptions/stylesheet.scss @@ -23,6 +23,7 @@ &__options { flex: 1; + position: relative; min-width: 310px; height: 130px; border: 1px solid #9ca2a7; @@ -58,6 +59,16 @@ padding-top: 3px; padding-bottom: 2px; } + + &--badge { + top: -12px; + font-size: 16px; + padding: 8px 12px; + border-radius: 4px; + position: absolute; + background: linear-gradient(0deg, #f36, #f36); + + } } &__options:hover { diff --git a/src/OnboardingSPA/steps/TheFork/contents.js b/src/OnboardingSPA/steps/TheFork/contents.js index fd39793ff..38f301ca6 100644 --- a/src/OnboardingSPA/steps/TheFork/contents.js +++ b/src/OnboardingSPA/steps/TheFork/contents.js @@ -12,7 +12,7 @@ const getContents = () => { { title: __( 'Guided Configuration', 'wp-module-onboarding' ), subtitle: __( - 'A few questions & settings to get you a jumpstart.', + 'Robust configuration guide to help you build your site', 'wp-module-onboarding' ), flow: 'sitebuild', diff --git a/src/OnboardingSPA/steps/TheFork/index.js b/src/OnboardingSPA/steps/TheFork/index.js index 1bb4cf42b..2133c0ed0 100644 --- a/src/OnboardingSPA/steps/TheFork/index.js +++ b/src/OnboardingSPA/steps/TheFork/index.js @@ -1,29 +1,41 @@ -import CommonLayout from '../../components/Layouts/Common'; - -import { useEffect } from '@wordpress/element'; +// WordPress +import { useEffect, useState } from '@wordpress/element'; import { useSelect, useDispatch } from '@wordpress/data'; -import { store as nfdOnboardingStore } from '../../store'; +// Classes and functions +import getContents from './contents'; +import { setFlow } from '../../utils/api/flow'; + +// Components +import StartOptions from '../../components/StartOptions'; +import CommonLayout from '../../components/Layouts/Common'; +import HeadingWithSubHeading from '../../components/HeadingWithSubHeading/SiteGen/index'; + +// Misc import { FOOTER_SITEGEN, HEADER_SITEGEN, pluginDashboardPage, } from '../../../constants'; - -import { DEFAULT_FLOW } from '../../data/flows/constants'; -import HeadingWithSubHeading from '../../components/HeadingWithSubHeading/SiteGen/index'; -import StartOptions from '../../components/StartOptions'; -import getContents from './contents'; import { OnboardingEvent, sendOnboardingEvent, trackOnboardingEvent, } from '../../utils/analytics/hiive'; -import { ACTION_SITEGEN_FORK_OPTION_SELECTED } from '../../utils/analytics/hiive/constants'; +import { + ACTION_SITEGEN_FORK_AI_EXPERIMENT, + ACTION_SITEGEN_FORK_OPTION_SELECTED, + CATEGORY_EXPERIMENT, +} from '../../utils/analytics/hiive/constants'; +import { store as nfdOnboardingStore } from '../../store'; +import { DEFAULT_FLOW } from '../../data/flows/constants'; const TheFork = () => { - const { migrationUrl } = useSelect( ( select ) => { + const [ experimentVersion, setExperimentVersion ] = useState(); + const { currentData, migrationUrl } = useSelect( ( select ) => { return { + currentData: + select( nfdOnboardingStore ).getCurrentOnboardingData(), migrationUrl: select( nfdOnboardingStore ).getMigrationUrl(), }; } ); @@ -36,6 +48,7 @@ const TheFork = () => { setIsHeaderNavigationEnabled, setFooterActiveView, setHideFooterNav, + setCurrentOnboardingData, } = useDispatch( nfdOnboardingStore ); useEffect( () => { @@ -46,8 +59,48 @@ const TheFork = () => { setHeaderActiveView( HEADER_SITEGEN ); setDrawerActiveView( false ); setFooterActiveView( FOOTER_SITEGEN ); + handleExperimentVersion(); } ); + const handleExperimentVersion = async () => { + let theForkExperimentVersion = 0; + if ( currentData.sitegen.theForkExperimentVersion !== 0 ) { + // Use an existing experiment version if it exists + setExperimentVersion( + currentData.sitegen.theForkExperimentVersion + ); + theForkExperimentVersion = + currentData.sitegen.theForkExperimentVersion; + } else { + // Generate a random experiment version from 1 to 4 + theForkExperimentVersion = Math.floor( Math.random() * 5 ); + setExperimentVersion( theForkExperimentVersion ); + + // Sync that to the store and DB for same version on refresh + currentData.sitegen.theForkExperimentVersion = + theForkExperimentVersion; + setCurrentOnboardingData( currentData ); + await setFlow( currentData ); + const experimentVersionNames = { + 1: 'control', + 2: 'position', + 3: 'badge', + 4: 'position_badge', + }; + + // Send an event for the experiment version shown to the user. + sendOnboardingEvent( + new OnboardingEvent( + ACTION_SITEGEN_FORK_AI_EXPERIMENT, + experimentVersionNames[ theForkExperimentVersion ], + null, + null, + CATEGORY_EXPERIMENT + ) + ); + } + }; + const oldFlow = window.nfdOnboarding?.oldFlow ? window.nfdOnboarding.oldFlow : DEFAULT_FLOW; @@ -73,6 +126,7 @@ const TheFork = () => { subtitle={ content.subheading } /> { @@ -24,6 +27,19 @@ if ( runtimeDataExists ) { }, } ); + HiiveAnalytics.initialize( { + namespace: CATEGORY_EXPERIMENT, + urls: { + single: onboardingRestURL( 'events' ), + batch: onboardingRestURL( 'events/batch' ), + }, + settings: { + debounce: { + time: 3000, + }, + }, + } ); + initializeNFDOnboarding( NFD_ONBOARDING_ELEMENT_ID, window.nfdOnboarding diff --git a/tests/cypress/integration/5-AI-SiteGen-onboarding-flow/1-fork.cy.js b/tests/cypress/integration/5-AI-SiteGen-onboarding-flow/1-fork.cy.js index 97a2e3f65..b66cf91d5 100644 --- a/tests/cypress/integration/5-AI-SiteGen-onboarding-flow/1-fork.cy.js +++ b/tests/cypress/integration/5-AI-SiteGen-onboarding-flow/1-fork.cy.js @@ -43,7 +43,7 @@ describe( 'SiteGen Fork Step', function () { .should('have.length', 3); } ); - it( 'Check for selection of different container options', () => { + it.skip( 'Check for selection of different container options', () => { let options = 0; const className = '.nfd-onboarding-sitegen-options__container__options'; const arr = cy.get( className );