From 826029ca7b39a0447f8a8972b7e0c0c29675e49d Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Tue, 10 May 2022 16:58:02 +0800 Subject: [PATCH 1/4] Add ExPlat call for product task experiment --- .../fills/experimental-products/index.tsx | 65 +++++++++++-------- .../experimental-products/test/index.tsx | 5 ++ .../client/tasks/fills/index.js | 12 +++- .../fills/use-product-layout-experiment.ts | 44 +++++++++++++ 4 files changed, 97 insertions(+), 29 deletions(-) create mode 100644 plugins/woocommerce-admin/client/tasks/fills/use-product-layout-experiment.ts diff --git a/plugins/woocommerce-admin/client/tasks/fills/experimental-products/index.tsx b/plugins/woocommerce-admin/client/tasks/fills/experimental-products/index.tsx index 34ab388d276..1c5dadde5f8 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/experimental-products/index.tsx +++ b/plugins/woocommerce-admin/client/tasks/fills/experimental-products/index.tsx @@ -6,7 +6,7 @@ import { WooOnboardingTask } from '@woocommerce/onboarding'; import { Text } from '@woocommerce/experimental'; import { registerPlugin } from '@wordpress/plugins'; import { useMemo, useState } from '@wordpress/element'; -import { Button } from '@wordpress/components'; +import { Button, Spinner } from '@wordpress/components'; import { getAdminLink } from '@woocommerce/settings'; import { Icon, chevronDown, chevronUp } from '@wordpress/icons'; @@ -17,6 +17,7 @@ import './index.scss'; import { getAdminSetting } from '~/utils/admin-settings'; import { getSurfacedProductTypeKeys, getProductTypes } from './utils'; import useProductTypeListItems from './use-product-types-list-items'; +import useLayoutExperiment from '../use-product-layout-experiment'; import Stack from './stack'; import Footer from './footer'; import CardLayout from './card-layout'; @@ -24,9 +25,6 @@ import { LoadSampleProductType } from './constants'; import LoadSampleProductModal from '../components/load-sample-product-modal'; import useLoadSampleProducts from '../components/use-load-sample-products'; -// TODO: Use experiment data from the API, not hardcoded. -const SHOW_STACK_LAYOUT = true; - const getOnboardingProductType = (): string[] => { const onboardingData = getAdminSetting( 'onboarding' ); return ( @@ -52,6 +50,7 @@ const ViewControlButton: React.FC< { export const Products = () => { const [ isExpanded, setIsExpanded ] = useState< boolean >( false ); + const [ isLoadingExperiment, experimentLayout ] = useLayoutExperiment(); const productTypes = useProductTypeListItems( getProductTypes() ); const surfacedProductTypeKeys = getSurfacedProductTypeKeys( @@ -79,7 +78,7 @@ export const Products = () => { surfacedProductTypes.push( productType ) ); - if ( ! SHOW_STACK_LAYOUT ) { + if ( experimentLayout === 'card' ) { surfacedProductTypes.push( { ...LoadSampleProductType, onClick: loadSampleProduct, @@ -91,35 +90,45 @@ export const Products = () => { surfacedProductTypeKeys, isExpanded, productTypes, + experimentLayout, loadSampleProduct, ] ); return (
- - { __( 'What product do you want to add?', 'woocommerce' ) } - + { isLoadingExperiment ? ( + + ) : ( + <> + + { __( + 'What product do you want to add?', + 'woocommerce' + ) } + -
- { SHOW_STACK_LAYOUT ? ( - - ) : ( - - ) } - setIsExpanded( ! isExpanded ) } - /> -
-
- { isLoadingSampleProducts && } +
+ { experimentLayout === 'stacked' ? ( + + ) : ( + + ) } + setIsExpanded( ! isExpanded ) } + /> +
+
+ { isLoadingSampleProducts && } + + ) }
); }; diff --git a/plugins/woocommerce-admin/client/tasks/fills/experimental-products/test/index.tsx b/plugins/woocommerce-admin/client/tasks/fills/experimental-products/test/index.tsx index a1e44f684e8..2edf943462f 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/experimental-products/test/index.tsx +++ b/plugins/woocommerce-admin/client/tasks/fills/experimental-products/test/index.tsx @@ -20,6 +20,11 @@ jest.mock( '~/utils/admin-settings', () => ( { getAdminSetting: jest.fn(), } ) ); +jest.mock( '../../use-product-layout-experiment', () => ( { + default: () => [ false, 'stacked' ], + __esModule: true, +} ) ); + global.fetch = jest.fn().mockImplementation( () => Promise.resolve( { json: () => Promise.resolve( {} ), diff --git a/plugins/woocommerce-admin/client/tasks/fills/index.js b/plugins/woocommerce-admin/client/tasks/fills/index.js index bfb0521f95e..6abd22a44b1 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/index.js +++ b/plugins/woocommerce-admin/client/tasks/fills/index.js @@ -2,6 +2,7 @@ * Internal dependencies */ import { getAdminSetting } from '~/utils/admin-settings'; +import { isExperimentProductTask } from './use-product-layout-experiment'; import './PaymentGatewaySuggestions'; import './shipping'; @@ -14,6 +15,15 @@ import './purchase'; const onboardingData = getAdminSetting( 'onboarding' ); +const importProductTask = async () => { + const isExperiment = await isExperimentProductTask(); + if ( isExperiment ) { + import( './experimental-products' ); + } else { + import( './products' ); + } +}; + if ( window.wcAdminFeatures && window.wcAdminFeatures[ 'experimental-import-products-task' ] && @@ -25,7 +35,7 @@ if ( window.wcAdminFeatures && window.wcAdminFeatures[ 'experimental-products-task' ] ) { - import( './experimental-products' ); + importProductTask(); } else { import( './products' ); } diff --git a/plugins/woocommerce-admin/client/tasks/fills/use-product-layout-experiment.ts b/plugins/woocommerce-admin/client/tasks/fills/use-product-layout-experiment.ts new file mode 100644 index 00000000000..31212677aa4 --- /dev/null +++ b/plugins/woocommerce-admin/client/tasks/fills/use-product-layout-experiment.ts @@ -0,0 +1,44 @@ +/** + * External dependencies + */ +import { useState, useEffect } from '@wordpress/element'; +import { loadExperimentAssignment } from '@woocommerce/explat'; + +type Layout = 'control' | 'card' | 'stacked'; + +export const getProductLayoutExperiment = async (): Promise< Layout > => { + const [ cardAssignment, stackedAssignment ] = await Promise.all( [ + loadExperimentAssignment( `woocommerce_products_task_layout_card` ), + loadExperimentAssignment( `woocommerce_products_task_layout_stacked` ), + ] ); + // This logic may look flawed as in both looks like they can be assigned treatment at the same time, + // but in backend we segment the experiments by store country, so it will never be. + if ( cardAssignment?.variationName === 'treatment' ) { + return 'card'; + } else if ( stackedAssignment?.variationName === 'treatment' ) { + return 'stacked'; + } + return 'control'; +}; + +export const isExperimentProductTask = async (): Promise< boolean > => { + return ( await getProductLayoutExperiment() ) !== 'control'; +}; + +export const useLayoutExperiment = () => { + const [ isLoading, setIsLoading ] = useState< boolean >( true ); + const [ experimentLayout, setExperimentLayout ] = useState< Layout >( + 'control' + ); + + useEffect( () => { + getProductLayoutExperiment().then( ( layout ) => { + setExperimentLayout( layout ); + setIsLoading( false ); + } ); + }, [ setExperimentLayout ] ); + + return [ isLoading, experimentLayout ]; +}; + +export default useLayoutExperiment; From 933fe9f449c1fab1267cb6f236dda224c80e688e Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Tue, 10 May 2022 18:18:45 +0800 Subject: [PATCH 2/4] Add tests --- .../experimental-products/test/index.tsx | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/plugins/woocommerce-admin/client/tasks/fills/experimental-products/test/index.tsx b/plugins/woocommerce-admin/client/tasks/fills/experimental-products/test/index.tsx index 2edf943462f..933aa0abb2c 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/experimental-products/test/index.tsx +++ b/plugins/woocommerce-admin/client/tasks/fills/experimental-products/test/index.tsx @@ -10,6 +10,7 @@ import userEvent from '@testing-library/user-event'; import { Products } from '../'; import { defaultSurfacedProductTypes, productTypes } from '../constants'; import { getAdminSetting } from '~/utils/admin-settings'; +import useLayoutExperiment from '../../use-product-layout-experiment'; jest.mock( '@wordpress/data', () => ( { ...jest.requireActual( '@wordpress/data' ), @@ -21,7 +22,7 @@ jest.mock( '~/utils/admin-settings', () => ( { } ) ); jest.mock( '../../use-product-layout-experiment', () => ( { - default: () => [ false, 'stacked' ], + default: jest.fn().mockReturnValue( [ false, 'stacked' ] ), __esModule: true, } ) ); @@ -110,4 +111,39 @@ describe( 'Products', () => { ) ); } ); + + it( 'should show spinner when layout experiment is loading', async () => { + ( useLayoutExperiment as jest.Mock ).mockImplementation( () => [ + true, + 'card', + ] ); + const { container } = render( ); + expect( + container.getElementsByClassName( 'components-spinner' ) + ).toHaveLength( 1 ); + } ); + + it( 'should render card layout when experiment is assigned', async () => { + ( useLayoutExperiment as jest.Mock ).mockImplementation( () => [ + false, + 'card', + ] ); + const { container } = render( ); + expect( + container.getElementsByClassName( + 'woocommerce-products-card-layout' + ) + ).toHaveLength( 1 ); + } ); + + it( 'should render stacked layout when experiment is assigned', async () => { + ( useLayoutExperiment as jest.Mock ).mockImplementation( () => [ + false, + 'stacked', + ] ); + const { container } = render( ); + expect( + container.getElementsByClassName( 'woocommerce-products-stack' ) + ).toHaveLength( 1 ); + } ); } ); From 71e0d3d27d3167a231d2ed96834e9ade0cc696b6 Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Tue, 10 May 2022 18:42:07 +0800 Subject: [PATCH 3/4] Changelog --- .../changelog/dev-32635-implement-product-task-experiment | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/dev-32635-implement-product-task-experiment diff --git a/plugins/woocommerce/changelog/dev-32635-implement-product-task-experiment b/plugins/woocommerce/changelog/dev-32635-implement-product-task-experiment new file mode 100644 index 00000000000..612c5967986 --- /dev/null +++ b/plugins/woocommerce/changelog/dev-32635-implement-product-task-experiment @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Add ExPlat call for product task experiment From 367db18bc4570c513dfc902c9d9c9dd9b108c3b7 Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Tue, 10 May 2022 20:57:11 +0800 Subject: [PATCH 4/4] Rename function --- plugins/woocommerce-admin/client/tasks/fills/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce-admin/client/tasks/fills/index.js b/plugins/woocommerce-admin/client/tasks/fills/index.js index 6abd22a44b1..1d04f492a50 100644 --- a/plugins/woocommerce-admin/client/tasks/fills/index.js +++ b/plugins/woocommerce-admin/client/tasks/fills/index.js @@ -15,7 +15,7 @@ import './purchase'; const onboardingData = getAdminSetting( 'onboarding' ); -const importProductTask = async () => { +const possiblyImportProductTaskExperiment = async () => { const isExperiment = await isExperimentProductTask(); if ( isExperiment ) { import( './experimental-products' ); @@ -35,7 +35,7 @@ if ( window.wcAdminFeatures && window.wcAdminFeatures[ 'experimental-products-task' ] ) { - importProductTask(); + possiblyImportProductTaskExperiment(); } else { import( './products' ); }