Cleanup product task experiment (#35950)
* Cleanup product task experiment * Rename experimental-products -> products * Add changelog * Clean up product task experiment in woo/onboarding Remove product variant * Rename experimental-import-products -> import-products * Clean up OnboardingTasks.php * Add changelog * Remove experimental-products-task feature flag
This commit is contained in:
parent
b133ad98c7
commit
9f0d718a33
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Cleanup product task experiment
|
|
@ -34,7 +34,6 @@ export const trackView = async ( taskId: string, variant?: string ) => {
|
|||
} );
|
||||
};
|
||||
|
||||
let experimentalVariant: string | undefined;
|
||||
type WooOnboardingTaskProps = {
|
||||
id: string;
|
||||
variant?: string;
|
||||
|
@ -56,37 +55,15 @@ type WooOnboardingTaskSlotProps = Slot.Props & {
|
|||
*/
|
||||
const WooOnboardingTask: React.FC< WooOnboardingTaskProps > & {
|
||||
Slot: React.VFC< WooOnboardingTaskSlotProps >;
|
||||
} = ( { id, variant, ...props } ) => {
|
||||
useEffect( () => {
|
||||
if ( id === 'products' ) {
|
||||
experimentalVariant = variant;
|
||||
}
|
||||
}, [ id, variant ] );
|
||||
|
||||
} = ( { id, ...props } ) => {
|
||||
return <Fill name={ 'woocommerce_onboarding_task_' + id } { ...props } />;
|
||||
};
|
||||
|
||||
// We need this here just in case the experiment assignment takes awhile to load, so that we don't fire trackView with a blank experimentalVariant
|
||||
// Remove all of the code in this file related to experiments and variants when the product task experiment concludes and never speak of the existence of this code to anyone
|
||||
const pollForExperimentalVariant = ( id: string, count: number ) => {
|
||||
if ( count > 20 ) {
|
||||
trackView( id, 'experiment_timed_out' ); // if we can't fetch experiment after 4 seconds, give up
|
||||
} else if ( experimentalVariant ) {
|
||||
trackView( id, experimentalVariant );
|
||||
} else {
|
||||
setTimeout( () => pollForExperimentalVariant( id, count + 1 ), 200 );
|
||||
}
|
||||
};
|
||||
|
||||
WooOnboardingTask.Slot = ( { id, fillProps } ) => {
|
||||
// The Slot is a React component and this hook works as expected.
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
useEffect( () => {
|
||||
if ( id === 'products' ) {
|
||||
pollForExperimentalVariant( id, 0 );
|
||||
} else {
|
||||
trackView( id );
|
||||
}
|
||||
}, [ id ] );
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
.woocommerce-products-card-layout {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.woocommerce-products-card-layout__description {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.woocommerce-products-card-list {
|
||||
margin-top: 32px;
|
||||
|
||||
.woocommerce-list {
|
||||
grid-template-columns: 217px 217px 217px;
|
||||
}
|
||||
|
||||
.woocommerce-list__item {
|
||||
width: 217px;
|
||||
height: 200px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.woocommerce-list__item-after {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.woocommerce-list__item-inner {
|
||||
padding: 40px 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-products-list__item-load-sample-product {
|
||||
border: 1.5px dashed #dcdcde;
|
||||
|
||||
.woocommerce-list__item-before {
|
||||
background-color: $gray-100;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Text } from '@woocommerce/experimental';
|
||||
import { Link } from '@woocommerce/components';
|
||||
import interpolateComponents from '@automattic/interpolate-components';
|
||||
import { getAdminLink } from '@woocommerce/settings';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ProductType } from './constants';
|
||||
import CardList from '../experimental-import-products/CardList';
|
||||
import './card-layout.scss';
|
||||
import useRecordCompletionTime from '../use-record-completion-time';
|
||||
|
||||
type CardProps = {
|
||||
items: ( ProductType & {
|
||||
onClick: () => void;
|
||||
} )[];
|
||||
};
|
||||
|
||||
const CardLayout: React.FC< CardProps > = ( { items } ) => {
|
||||
const { recordCompletionTime } = useRecordCompletionTime( 'products' );
|
||||
|
||||
return (
|
||||
<div className="woocommerce-products-card-layout">
|
||||
<Text className="woocommerce-products-card-layout__description">
|
||||
{ interpolateComponents( {
|
||||
mixedString: __(
|
||||
'{{sbLink}}Start blank{{/sbLink}} or select a product type:',
|
||||
'woocommerce'
|
||||
),
|
||||
components: {
|
||||
sbLink: (
|
||||
<Link
|
||||
onClick={ () => {
|
||||
recordEvent( 'tasklist_add_product', {
|
||||
method: 'manually',
|
||||
} );
|
||||
recordCompletionTime();
|
||||
window.location.href = getAdminLink(
|
||||
'post-new.php?post_type=product&wc_onboarding_active_task=products&tutorial=true'
|
||||
);
|
||||
return false;
|
||||
} }
|
||||
href=""
|
||||
type="wc-admin"
|
||||
>
|
||||
<></>
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
} ) }
|
||||
</Text>
|
||||
<CardList items={ items } />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardLayout;
|
|
@ -1,58 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import CardLayout from '../card-layout';
|
||||
import { productTypes } from '../constants';
|
||||
|
||||
jest.mock( '@woocommerce/tracks', () => ( { recordEvent: jest.fn() } ) );
|
||||
describe( 'CardLayout', () => {
|
||||
beforeEach( () => {
|
||||
( recordEvent as jest.Mock ).mockClear();
|
||||
} );
|
||||
it( 'should render all products types in CardLayout', () => {
|
||||
const { queryByText, queryAllByRole } = render(
|
||||
<CardLayout
|
||||
items={ [
|
||||
{
|
||||
...productTypes[ 0 ],
|
||||
onClick: () => {},
|
||||
},
|
||||
] }
|
||||
/>
|
||||
);
|
||||
|
||||
expect( queryByText( productTypes[ 0 ].title ) ).toBeInTheDocument();
|
||||
|
||||
expect( queryAllByRole( 'link' ) ).toHaveLength( 1 );
|
||||
} );
|
||||
|
||||
it( 'start blank link should fire the tasklist_add_product and completion events', () => {
|
||||
const { getByText } = render(
|
||||
<CardLayout
|
||||
items={ [
|
||||
{
|
||||
...productTypes[ 0 ],
|
||||
onClick: () => {},
|
||||
},
|
||||
] }
|
||||
/>
|
||||
);
|
||||
fireEvent.click( getByText( 'Start blank' ) );
|
||||
expect( recordEvent ).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'tasklist_add_product',
|
||||
{ method: 'manually' }
|
||||
);
|
||||
expect( recordEvent ).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'task_completion_time',
|
||||
{ task_name: 'products', time: '0-2s' }
|
||||
);
|
||||
} );
|
||||
} );
|
|
@ -1,33 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useState, useEffect } from '@wordpress/element';
|
||||
|
||||
type Layout = 'control' | 'card' | 'stacked';
|
||||
|
||||
export const getProductLayoutExperiment = async (): Promise< Layout > => {
|
||||
// Deploy the stacked layout. Todo: cleanup the experiment code.
|
||||
return 'stacked';
|
||||
};
|
||||
|
||||
export const isProductTaskExperimentTreatment =
|
||||
async (): Promise< boolean > => {
|
||||
return ( await getProductLayoutExperiment() ) !== 'control';
|
||||
};
|
||||
|
||||
export const useProductTaskExperiment = () => {
|
||||
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 useProductTaskExperiment;
|
|
@ -13,12 +13,12 @@ import { recordEvent } from '@woocommerce/tracks';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Stacks from '../experimental-products/stack';
|
||||
import Stacks from '../products/stack';
|
||||
import CardList from './CardList';
|
||||
import { importTypes } from './importTypes';
|
||||
import './style.scss';
|
||||
import useProductTypeListItems from '../experimental-products/use-product-types-list-items';
|
||||
import { getProductTypes } from '../experimental-products/utils';
|
||||
import useProductTypeListItems from '../products/use-product-types-list-items';
|
||||
import { getProductTypes } from '../products/utils';
|
||||
import LoadSampleProductModal from '../components/load-sample-product-modal';
|
||||
import useLoadSampleProducts from '../components/use-load-sample-products';
|
||||
import LoadSampleProductConfirmModal from '../components/load-sample-product-confirm-modal';
|
||||
|
@ -114,7 +114,7 @@ registerPlugin( 'wc-admin-onboarding-task-products', {
|
|||
// @ts-expect-error 'scope' does exist. @types/wordpress__plugins is outdated.
|
||||
scope: 'woocommerce-tasks',
|
||||
render: () => (
|
||||
<WooOnboardingTask id="products" variant="import">
|
||||
<WooOnboardingTask id="products">
|
||||
<Products />
|
||||
</WooOnboardingTask>
|
||||
),
|
|
@ -7,7 +7,7 @@ import userEvent from '@testing-library/user-event';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Products } from '../';
|
||||
import { Products } from '..';
|
||||
|
||||
jest.mock( '@woocommerce/tracks', () => ( { recordEvent: jest.fn() } ) );
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { isProductTaskExperimentTreatment } from './experimental-products/use-product-layout-experiment';
|
||||
import { isImportProductExperiment } from './product-task-experiment';
|
||||
import { isImportProduct } from './utils';
|
||||
import './PaymentGatewaySuggestions';
|
||||
import './shipping';
|
||||
import './Marketing';
|
||||
|
@ -12,28 +11,15 @@ import './tax';
|
|||
import './woocommerce-payments';
|
||||
import './purchase';
|
||||
|
||||
const possiblyImportProductTaskExperiment = async () => {
|
||||
const isExperiment = await isProductTaskExperimentTreatment();
|
||||
if ( isExperiment ) {
|
||||
if ( isImportProductExperiment() ) {
|
||||
import( './experimental-import-products' );
|
||||
} else {
|
||||
import( './experimental-products' );
|
||||
}
|
||||
const possiblyImportProductTask = async () => {
|
||||
if ( isImportProduct() ) {
|
||||
import( './import-products' );
|
||||
} else {
|
||||
import( './products' );
|
||||
}
|
||||
};
|
||||
|
||||
if (
|
||||
window.wcAdminFeatures &&
|
||||
( window.wcAdminFeatures[ 'experimental-import-products-task' ] ||
|
||||
window.wcAdminFeatures[ 'experimental-products-task' ] )
|
||||
) {
|
||||
possiblyImportProductTaskExperiment();
|
||||
} else {
|
||||
import( './products' );
|
||||
}
|
||||
possiblyImportProductTask();
|
||||
|
||||
if (
|
||||
window.wcAdminFeatures &&
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './products';
|
||||
import ProductTemplateModal from './product-template-modal';
|
||||
|
||||
export { ProductTemplateModal };
|
|
@ -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, Spinner } from '@wordpress/components';
|
||||
import { Button } from '@wordpress/components';
|
||||
import { getAdminLink } from '@woocommerce/settings';
|
||||
import { Icon, chevronDown, chevronUp } from '@wordpress/icons';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
|
@ -21,14 +21,11 @@ import { getSurfacedProductTypeKeys, getProductTypes } from './utils';
|
|||
import useProductTypeListItems from './use-product-types-list-items';
|
||||
import Stack from './stack';
|
||||
import Footer from './footer';
|
||||
import CardLayout from './card-layout';
|
||||
import { LoadSampleProductType } from './constants';
|
||||
import LoadSampleProductModal from '../components/load-sample-product-modal';
|
||||
import useLoadSampleProducts from '../components/use-load-sample-products';
|
||||
import LoadSampleProductConfirmModal from '../components/load-sample-product-confirm-modal';
|
||||
import useRecordCompletionTime from '../use-record-completion-time';
|
||||
import { getCountryCode } from '~/dashboard/utils';
|
||||
import { useProductTaskExperiment } from './use-product-layout-experiment';
|
||||
|
||||
const getOnboardingProductType = (): string[] => {
|
||||
const onboardingData = getAdminSetting( 'onboarding' );
|
||||
|
@ -59,8 +56,6 @@ export const Products = () => {
|
|||
isConfirmingLoadSampleProducts,
|
||||
setIsConfirmingLoadSampleProducts,
|
||||
] = useState( false );
|
||||
const { isLoading: isLoadingExperiment, experimentLayout } =
|
||||
useProductTaskExperiment();
|
||||
|
||||
const { isStoreInUS } = useSelect( ( select ) => {
|
||||
const { getSettings } = select( SETTINGS_STORE_NAME );
|
||||
|
@ -120,41 +115,21 @@ export const Products = () => {
|
|||
! surfacedProductTypes.includes( productType ) &&
|
||||
surfacedProductTypes.push( productType )
|
||||
);
|
||||
|
||||
if ( experimentLayout === 'card' ) {
|
||||
surfacedProductTypes.push( {
|
||||
...LoadSampleProductType,
|
||||
onClick: () => setIsConfirmingLoadSampleProducts( true ),
|
||||
} );
|
||||
}
|
||||
}
|
||||
return surfacedProductTypes;
|
||||
}, [
|
||||
surfacedProductTypeKeys,
|
||||
isExpanded,
|
||||
productTypesWithTimeRecord,
|
||||
experimentLayout,
|
||||
] );
|
||||
}, [ surfacedProductTypeKeys, isExpanded, productTypesWithTimeRecord ] );
|
||||
|
||||
return (
|
||||
<div className="woocommerce-task-products">
|
||||
{ isLoadingExperiment ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
<>
|
||||
<Text
|
||||
variant="title"
|
||||
as="h2"
|
||||
className="woocommerce-task-products__title"
|
||||
>
|
||||
{ __(
|
||||
'What product do you want to add?',
|
||||
'woocommerce'
|
||||
) }
|
||||
{ __( 'What product do you want to add?', 'woocommerce' ) }
|
||||
</Text>
|
||||
|
||||
<div className="woocommerce-product-content">
|
||||
{ experimentLayout === 'stacked' ? (
|
||||
<Stack
|
||||
items={ visibleProductTypes }
|
||||
onClickLoadSampleProduct={ () =>
|
||||
|
@ -162,9 +137,6 @@ export const Products = () => {
|
|||
}
|
||||
showOtherOptions={ isExpanded }
|
||||
/>
|
||||
) : (
|
||||
<CardLayout items={ visibleProductTypes } />
|
||||
) }
|
||||
<ViewControlButton
|
||||
isExpanded={ isExpanded }
|
||||
onClick={ () => {
|
||||
|
@ -196,17 +168,13 @@ export const Products = () => {
|
|||
/>
|
||||
)
|
||||
) }
|
||||
</>
|
||||
) }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ExperimentalProductsFill = () => {
|
||||
const { isLoading, experimentLayout } = useProductTaskExperiment();
|
||||
|
||||
return isLoading ? null : (
|
||||
<WooOnboardingTask id="products" variant={ experimentLayout }>
|
||||
const ProductsFill = () => {
|
||||
return (
|
||||
<WooOnboardingTask id="products">
|
||||
<Products />
|
||||
</WooOnboardingTask>
|
||||
);
|
||||
|
@ -215,5 +183,5 @@ const ExperimentalProductsFill = () => {
|
|||
registerPlugin( 'wc-admin-onboarding-task-products', {
|
||||
// @ts-expect-error 'scope' does exist. @types/wordpress__plugins is outdated.
|
||||
scope: 'woocommerce-tasks',
|
||||
render: () => <ExperimentalProductsFill />,
|
||||
render: () => <ProductsFill />,
|
||||
} );
|
|
@ -1,198 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Button, Modal, RadioControl } from '@wordpress/components';
|
||||
import { useState } from '@wordpress/element';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { applyFilters } from '@wordpress/hooks';
|
||||
import {
|
||||
ITEMS_STORE_NAME,
|
||||
ONBOARDING_STORE_NAME,
|
||||
PLUGINS_STORE_NAME,
|
||||
SETTINGS_STORE_NAME,
|
||||
} from '@woocommerce/data';
|
||||
import { getAdminLink } from '@woocommerce/settings';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './product-template-modal.scss';
|
||||
import { createNoticesFromResponse } from '../../../lib/notices';
|
||||
import { getCountryCode } from '../../../dashboard/utils';
|
||||
|
||||
export const ONBOARDING_PRODUCT_TEMPLATES_FILTER =
|
||||
'woocommerce_admin_onboarding_product_templates';
|
||||
|
||||
const getProductTemplates = () => [
|
||||
{
|
||||
key: 'physical',
|
||||
title: __( 'Physical product', 'woocommerce' ),
|
||||
subtitle: __(
|
||||
'Tangible items that get delivered to customers',
|
||||
'woocommerce'
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'digital',
|
||||
title: __( 'Digital product', 'woocommerce' ),
|
||||
subtitle: __(
|
||||
'Items that customers download or access through your website',
|
||||
'woocommerce'
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'variable',
|
||||
title: __( 'Variable product', 'woocommerce' ),
|
||||
subtitle: __(
|
||||
'Products with several versions that customers can choose from',
|
||||
'woocommerce'
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'subscription',
|
||||
title: __( 'Subscription product', 'woocommerce' ),
|
||||
subtitle: __(
|
||||
'Products that customers receive or gain access to regularly by paying in advance',
|
||||
'woocommerce'
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
export default function ProductTemplateModal( { onClose } ) {
|
||||
const [ selectedTemplate, setSelectedTemplate ] = useState( null );
|
||||
const [ isRedirecting, setIsRedirecting ] = useState( false );
|
||||
const { createProductFromTemplate } = useDispatch( ITEMS_STORE_NAME );
|
||||
const { countryCode, profileItems } = useSelect( ( select ) => {
|
||||
const { getProfileItems } = select( ONBOARDING_STORE_NAME );
|
||||
const { getSettings } = select( SETTINGS_STORE_NAME );
|
||||
const { general: settings = {} } = getSettings( 'general' );
|
||||
|
||||
return {
|
||||
countryCode: getCountryCode( settings.woocommerce_default_country ),
|
||||
profileItems: getProfileItems(),
|
||||
};
|
||||
} );
|
||||
const { installedPlugins } = useSelect( ( select ) => {
|
||||
const { getInstalledPlugins } = select( PLUGINS_STORE_NAME );
|
||||
|
||||
return {
|
||||
installedPlugins: getInstalledPlugins(),
|
||||
};
|
||||
} );
|
||||
|
||||
const createTemplate = () => {
|
||||
setIsRedirecting( true );
|
||||
recordEvent( 'tasklist_product_template_selection', {
|
||||
product_type: selectedTemplate,
|
||||
} );
|
||||
if ( selectedTemplate === 'subscription' ) {
|
||||
window.location.href = getAdminLink(
|
||||
'post-new.php?post_type=product&subscription_pointers=true'
|
||||
);
|
||||
return;
|
||||
}
|
||||
if ( selectedTemplate ) {
|
||||
createProductFromTemplate(
|
||||
{
|
||||
template_name: selectedTemplate,
|
||||
status: 'draft',
|
||||
},
|
||||
{ _fields: [ 'id' ] }
|
||||
).then(
|
||||
( data ) => {
|
||||
if ( data && data.id ) {
|
||||
const link = getAdminLink(
|
||||
`post.php?post=${ data.id }&action=edit&wc_onboarding_active_task=products&tutorial=true`
|
||||
);
|
||||
window.location = link;
|
||||
}
|
||||
},
|
||||
( error ) => {
|
||||
// failed creating product with template
|
||||
createNoticesFromResponse( error );
|
||||
setIsRedirecting( false );
|
||||
}
|
||||
);
|
||||
} else if ( onClose ) {
|
||||
recordEvent( 'tasklist_product_template_dismiss' );
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
const removeSubscriptions =
|
||||
( window.wcAdminFeatures && ! window.wcAdminFeatures.subscriptions ) ||
|
||||
countryCode !== 'US' ||
|
||||
! profileItems.product_types?.includes( 'subscriptions' ) ||
|
||||
! installedPlugins.includes( 'woocommerce-payments' );
|
||||
|
||||
const productTemplates = removeSubscriptions
|
||||
? getProductTemplates().filter(
|
||||
( template ) => template.key !== 'subscription'
|
||||
)
|
||||
: getProductTemplates();
|
||||
|
||||
/**
|
||||
* An object defining a product template.
|
||||
*
|
||||
* @typedef {Object} template
|
||||
* @property {string} key Icon to render.
|
||||
* @property {string} title Url.
|
||||
* @property {string} subtitle Link title.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Store product templates.
|
||||
*
|
||||
* @filter woocommerce_admin_onboarding_product_templates
|
||||
* @param {Array.<template>} templates Array of product templates.
|
||||
*/
|
||||
const templates = applyFilters(
|
||||
ONBOARDING_PRODUCT_TEMPLATES_FILTER,
|
||||
productTemplates
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={ __( 'Start with a template', 'woocommerce' ) }
|
||||
isDismissible={ true }
|
||||
onRequestClose={ () => onClose() }
|
||||
className="woocommerce-product-template-modal"
|
||||
>
|
||||
<div className="woocommerce-product-template-modal__wrapper">
|
||||
<div className="woocommerce-product-template-modal__list">
|
||||
<RadioControl
|
||||
selected={ selectedTemplate }
|
||||
options={ templates.map( ( item ) => {
|
||||
return {
|
||||
label: (
|
||||
<>
|
||||
<span className="woocommerce-product-template-modal__list-title">
|
||||
{ item.title }
|
||||
</span>
|
||||
<span className="woocommerce-product-template-modal__list-subtitle">
|
||||
{ item.subtitle }
|
||||
</span>
|
||||
</>
|
||||
),
|
||||
value: item.key,
|
||||
};
|
||||
} ) }
|
||||
onChange={ setSelectedTemplate }
|
||||
/>
|
||||
</div>
|
||||
<div className="woocommerce-product-template-modal__actions">
|
||||
<Button
|
||||
isPrimary
|
||||
isBusy={ isRedirecting }
|
||||
disabled={ ! selectedTemplate || isRedirecting }
|
||||
onClick={ createTemplate }
|
||||
>
|
||||
{ __( 'Go', 'woocommerce' ) }
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
$border-color: $gray-100;
|
||||
|
||||
.woocommerce-product-template-modal {
|
||||
@include breakpoint( '>600px' ) {
|
||||
min-width: 565px;
|
||||
}
|
||||
|
||||
.woocommerce-product-template-modal__actions {
|
||||
padding-top: $gap-large;
|
||||
}
|
||||
}
|
||||
.woocommerce-product-template-modal__list {
|
||||
.components-base-control {
|
||||
margin: 0 -#{$gap-large};
|
||||
.components-base-control__field {
|
||||
.components-radio-control__option {
|
||||
display: flex;
|
||||
text-decoration: none;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
padding: $gap $gap-large;
|
||||
margin-bottom: 0;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid $gray-100;
|
||||
&:first-child {
|
||||
border-top: 1px solid $gray-100;
|
||||
}
|
||||
.components-radio-control__input {
|
||||
margin-right: $gap;
|
||||
flex: none;
|
||||
&:checked::before {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.woocommerce-product-template-modal__list-title {
|
||||
color: $gray-900;
|
||||
display: block;
|
||||
}
|
||||
.woocommerce-product-template-modal__list-subtitle {
|
||||
color: $gray-700;
|
||||
margin-top: 4px;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.woocommerce-product-template-modal__actions {
|
||||
text-align: right;
|
||||
}
|
|
@ -1,173 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Fragment, useState } from '@wordpress/element';
|
||||
import { Card, CardBody } from '@wordpress/components';
|
||||
import {
|
||||
Icon,
|
||||
sidebar,
|
||||
chevronRight,
|
||||
plusCircle,
|
||||
archive,
|
||||
download,
|
||||
} from '@wordpress/icons';
|
||||
import {
|
||||
ONBOARDING_STORE_NAME,
|
||||
PLUGINS_STORE_NAME,
|
||||
SETTINGS_STORE_NAME,
|
||||
} from '@woocommerce/data';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { List, Pill } from '@woocommerce/components';
|
||||
import { getAdminLink } from '@woocommerce/settings';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import { registerPlugin } from '@wordpress/plugins';
|
||||
import { WooOnboardingTask } from '@woocommerce/onboarding';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import ProductTemplateModal from './product-template-modal';
|
||||
import { getCountryCode } from '../../../dashboard/utils';
|
||||
|
||||
const getSubTasks = () => [
|
||||
{
|
||||
key: 'addProductTemplate',
|
||||
title: (
|
||||
<>
|
||||
{ __( 'Start with a template', 'woocommerce' ) }
|
||||
<Pill>{ __( 'Recommended', 'woocommerce' ) }</Pill>
|
||||
</>
|
||||
),
|
||||
content: __(
|
||||
'Use a template to add physical, digital, and variable products',
|
||||
'woocommerce'
|
||||
),
|
||||
before: <Icon icon={ sidebar }></Icon>,
|
||||
after: <Icon icon={ chevronRight } />,
|
||||
onClick: () =>
|
||||
recordEvent( 'tasklist_add_product', {
|
||||
method: 'product_template',
|
||||
} ),
|
||||
},
|
||||
{
|
||||
key: 'addProductManually',
|
||||
title: __( 'Add manually', 'woocommerce' ),
|
||||
content: __(
|
||||
'For small stores we recommend adding products manually',
|
||||
'woocommerce'
|
||||
),
|
||||
before: <Icon icon={ plusCircle } />,
|
||||
after: <Icon icon={ chevronRight } />,
|
||||
onClick: () =>
|
||||
recordEvent( 'tasklist_add_product', { method: 'manually' } ),
|
||||
href: getAdminLink(
|
||||
'post-new.php?post_type=product&wc_onboarding_active_task=products&tutorial=true'
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'importProducts',
|
||||
title: __( 'Import via CSV', 'woocommerce' ),
|
||||
content: __(
|
||||
'For larger stores we recommend importing all products at once via CSV file',
|
||||
'woocommerce'
|
||||
),
|
||||
before: <Icon icon={ archive } />,
|
||||
after: <Icon icon={ chevronRight } />,
|
||||
onClick: () =>
|
||||
recordEvent( 'tasklist_add_product', { method: 'import' } ),
|
||||
href: getAdminLink(
|
||||
'edit.php?post_type=product&page=product_importer&wc_onboarding_active_task=products'
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'migrateProducts',
|
||||
title: __( 'Import from another service', 'woocommerce' ),
|
||||
content: __(
|
||||
'For stores currently selling elsewhere we suggest using a product migration service',
|
||||
'woocommerce'
|
||||
),
|
||||
before: <Icon icon={ download } />,
|
||||
after: <Icon icon={ chevronRight } />,
|
||||
onClick: () =>
|
||||
recordEvent( 'tasklist_add_product', { method: 'migrate' } ),
|
||||
// @todo This should be replaced with the in-app purchase iframe when ready.
|
||||
href: 'https://woocommerce.com/products/cart2cart/?utm_medium=product',
|
||||
target: '_blank',
|
||||
},
|
||||
];
|
||||
|
||||
const Products = () => {
|
||||
const [ selectTemplate, setSelectTemplate ] = useState( null );
|
||||
const { countryCode, profileItems } = useSelect( ( select ) => {
|
||||
const { getProfileItems } = select( ONBOARDING_STORE_NAME );
|
||||
const { getSettings } = select( SETTINGS_STORE_NAME );
|
||||
const { general: settings = {} } = getSettings( 'general' );
|
||||
|
||||
return {
|
||||
countryCode: getCountryCode( settings.woocommerce_default_country ),
|
||||
profileItems: getProfileItems(),
|
||||
};
|
||||
} );
|
||||
const { installedPlugins } = useSelect( ( select ) => {
|
||||
const { getInstalledPlugins } = select( PLUGINS_STORE_NAME );
|
||||
|
||||
return {
|
||||
installedPlugins: getInstalledPlugins(),
|
||||
};
|
||||
} );
|
||||
|
||||
const subTasks = getSubTasks();
|
||||
|
||||
if (
|
||||
window.wcAdminFeatures &&
|
||||
window.wcAdminFeatures.subscriptions &&
|
||||
countryCode === 'US' &&
|
||||
profileItems.product_types?.includes( 'subscriptions' ) &&
|
||||
installedPlugins.includes( 'woocommerce-payments' )
|
||||
) {
|
||||
const task = subTasks.find(
|
||||
( { key } ) => key === 'addProductTemplate'
|
||||
);
|
||||
task.content = __(
|
||||
'Use a template to add physical, digital, variable, and subscription products',
|
||||
'woocommerce'
|
||||
);
|
||||
}
|
||||
|
||||
const onTaskClick = ( task ) => {
|
||||
task.onClick();
|
||||
if ( task.key === 'addProductTemplate' ) {
|
||||
setSelectTemplate( true );
|
||||
}
|
||||
};
|
||||
|
||||
const listItems = subTasks.map( ( task ) => ( {
|
||||
...task,
|
||||
onClick: () => onTaskClick( task ),
|
||||
} ) );
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Card className="woocommerce-task-card">
|
||||
<CardBody size={ null }>
|
||||
<List items={ listItems } />
|
||||
</CardBody>
|
||||
</Card>
|
||||
{ selectTemplate ? (
|
||||
<ProductTemplateModal
|
||||
onClose={ () => setSelectTemplate( null ) }
|
||||
/>
|
||||
) : null }
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
registerPlugin( 'wc-admin-onboarding-task-products', {
|
||||
scope: 'woocommerce-tasks',
|
||||
render: () => (
|
||||
<WooOnboardingTask id="products" variant="control">
|
||||
<Products />
|
||||
</WooOnboardingTask>
|
||||
),
|
||||
} );
|
|
@ -9,10 +9,9 @@ import { useSelect } from '@wordpress/data';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Products } from '../';
|
||||
import { Products } from '..';
|
||||
import { defaultSurfacedProductTypes, productTypes } from '../constants';
|
||||
import { getAdminSetting } from '~/utils/admin-settings';
|
||||
import { useProductTaskExperiment } from '../use-product-layout-experiment';
|
||||
|
||||
jest.mock( '@wordpress/data', () => ( {
|
||||
...jest.requireActual( '@wordpress/data' ),
|
||||
|
@ -23,13 +22,6 @@ jest.mock( '~/utils/admin-settings', () => ( {
|
|||
getAdminSetting: jest.fn(),
|
||||
} ) );
|
||||
|
||||
jest.mock( '../use-product-layout-experiment', () => ( {
|
||||
useProductTaskExperiment: jest.fn().mockReturnValue( {
|
||||
isLoading: false,
|
||||
experimentLayout: 'stacked',
|
||||
} ),
|
||||
} ) );
|
||||
|
||||
jest.mock( '../use-create-product-by-type', () => ( {
|
||||
useCreateProductByType: jest
|
||||
.fn()
|
||||
|
@ -306,41 +298,7 @@ describe( 'Products', () => {
|
|||
);
|
||||
} );
|
||||
|
||||
it( 'should show spinner when layout experiment is loading', async () => {
|
||||
( useProductTaskExperiment as jest.Mock ).mockImplementation( () => {
|
||||
return {
|
||||
isLoading: true,
|
||||
experimentLayout: 'card',
|
||||
};
|
||||
} );
|
||||
const { container } = render( <Products /> );
|
||||
expect(
|
||||
container.getElementsByClassName( 'components-spinner' )
|
||||
).toHaveLength( 1 );
|
||||
} );
|
||||
|
||||
it( 'should render card layout when experiment is assigned', async () => {
|
||||
( useProductTaskExperiment as jest.Mock ).mockImplementation( () => {
|
||||
return {
|
||||
isLoading: false,
|
||||
experimentLayout: 'card',
|
||||
};
|
||||
} );
|
||||
const { container } = render( <Products /> );
|
||||
expect(
|
||||
container.getElementsByClassName(
|
||||
'woocommerce-products-card-layout'
|
||||
)
|
||||
).toHaveLength( 1 );
|
||||
} );
|
||||
|
||||
it( 'should render stacked layout when experiment is assigned', async () => {
|
||||
( useProductTaskExperiment as jest.Mock ).mockImplementation( () => {
|
||||
return {
|
||||
isLoading: false,
|
||||
experimentLayout: 'stacked',
|
||||
};
|
||||
} );
|
||||
it( 'should render stacked layout', async () => {
|
||||
const { container } = render( <Products /> );
|
||||
expect(
|
||||
container.getElementsByClassName( 'woocommerce-products-stack' )
|
|
@ -8,9 +8,9 @@ const onboardingData = getAdminSetting( 'onboarding' );
|
|||
/**
|
||||
* Returns true if the merchant has indicated that they have another online shop while filling out the OBW
|
||||
*/
|
||||
export const isImportProductExperiment = () => {
|
||||
export const isImportProduct = () => {
|
||||
return (
|
||||
window?.wcAdminFeatures?.[ 'experimental-import-products-task' ] &&
|
||||
window?.wcAdminFeatures?.[ 'import-products-task' ] &&
|
||||
onboardingData?.profile?.selling_venues &&
|
||||
onboardingData?.profile?.selling_venues !== 'no'
|
||||
);
|
|
@ -11,7 +11,6 @@ declare global {
|
|||
analytics: boolean;
|
||||
coupons: boolean;
|
||||
'customer-effort-score-tracks': boolean;
|
||||
'experimental-products-task': boolean;
|
||||
homescreen: boolean;
|
||||
marketing: boolean;
|
||||
'minified-js': boolean;
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Cleanup product task experiment
|
|
@ -4,8 +4,7 @@
|
|||
"analytics": true,
|
||||
"coupons": true,
|
||||
"customer-effort-score-tracks": true,
|
||||
"experimental-products-task": true,
|
||||
"experimental-import-products-task": true,
|
||||
"import-products-task": true,
|
||||
"experimental-fashion-sample-products": true,
|
||||
"shipping-smart-defaults": true,
|
||||
"shipping-setting-tour": true,
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
"analytics": true,
|
||||
"coupons": true,
|
||||
"customer-effort-score-tracks": true,
|
||||
"experimental-products-task": true,
|
||||
"experimental-import-products-task": true,
|
||||
"import-products-task": true,
|
||||
"experimental-fashion-sample-products": true,
|
||||
"shipping-smart-defaults": true,
|
||||
"shipping-setting-tour": true,
|
||||
|
|
|
@ -340,38 +340,13 @@ class OnboardingTasks extends \WC_REST_Data_Controller {
|
|||
* @return WP_Error|WP_REST_Response
|
||||
*/
|
||||
public static function import_sample_products() {
|
||||
if (
|
||||
( Features::is_enabled( 'experimental-import-products-task' ) || Features::is_enabled( 'experimental-products-task' ) )
|
||||
&& static::is_experiment_product_task()
|
||||
) {
|
||||
|
||||
$sample_csv_file = Features::is_enabled( 'experimental-fashion-sample-products' ) ? WC_ABSPATH . 'sample-data/experimental_fashion_sample_9_products.csv' :
|
||||
WC_ABSPATH . 'sample-data/experimental_sample_9_products.csv';
|
||||
} else {
|
||||
$sample_csv_file = WC_ABSPATH . 'sample-data/sample_products.csv';
|
||||
}
|
||||
|
||||
$import = self::import_sample_products_from_csv( $sample_csv_file );
|
||||
return rest_ensure_response( $import );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if product task experiment is treatment.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_experiment_product_task() {
|
||||
$anon_id = isset( $_COOKIE['tk_ai'] ) ? sanitize_text_field( wp_unslash( $_COOKIE['tk_ai'] ) ) : '';
|
||||
$allow_tracking = get_option( 'woocommerce_allow_tracking' ) === 'yes';
|
||||
$abtest = new \WooCommerce\Admin\Experimental_Abtest(
|
||||
$anon_id,
|
||||
'woocommerce',
|
||||
$allow_tracking
|
||||
);
|
||||
return $abtest->get_variation( 'woocommerce_products_task_layout_stacked_v3' ) === 'treatment' ||
|
||||
$abtest->get_variation( 'woocommerce_products_task_layout_card_v3' ) === 'treatment';
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a product from a template name passed in through the template_name param.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue