Store Profiler and Product task - include Subscriptions (https://github.com/woocommerce/woocommerce-admin/pull/7734)

* Add flag

* Add free subscription to Product Types step

* Show copy and install WC-Pay when `Subscriptions` is checked

* Fixed `Start with a template` description

* Added flag to product-types and small refactor

* Add test for subscriptions

* Fixed wc-pay installation in product-types step

* Refactor product-template-modal

# Conflicts:
#	client/task-list/tasks/products/product-template-modal.js
#	client/tasks/fills/products/product-template-modal.js

* Add more tests

* Refactor product types list

* Add wc-pay activation in store profiler

* Add `wc-pay filter in business details step

* Add redirection after selecting subscriptions template option

* Fix prop renamed

* Fix product-types list

* Add changelog

* Fix typo

* Add check to `Free features`

* Refactor `SelectiveExtensionsBundle`

* Add `is_activated` to `EvaluateExtension`

* Add validation to `ProductTemplateModal` component

* Removed useless import

* Add missing flags

* Fix list

* Add promise.all to `product-types`

* Removed useless validation

Co-authored-by: Fernando Marichal <contacto@fernandomarichal.com>
This commit is contained in:
Fernando 2021-10-05 09:27:30 -03:00 committed by GitHub
parent ad0afb4aba
commit 5d7661eeb9
16 changed files with 332 additions and 149 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: Add
Store Profiler and Product task - include Subscriptions #7734

View File

@ -178,22 +178,41 @@ export const SelectiveExtensionsBundle = ( {
const [ showExtensions, setShowExtensions ] = useState( false );
const [ values, setValues ] = useState( baseValues );
const { freeExtensions, isResolving } = useSelect( ( select ) => {
const { getFreeExtensions, hasFinishedResolution } = select(
ONBOARDING_STORE_NAME
);
const { freeExtensions, isResolving, profileItems } = useSelect(
( select ) => {
const {
getFreeExtensions,
getProfileItems,
hasFinishedResolution,
} = select( ONBOARDING_STORE_NAME );
return {
freeExtensions: getFreeExtensions(),
isResolving: ! hasFinishedResolution( 'getFreeExtensions' ),
};
} );
return {
freeExtensions: getFreeExtensions(),
isResolving: ! hasFinishedResolution( 'getFreeExtensions' ),
profileItems: getProfileItems(),
};
}
);
const installableExtensions = useMemo( () => {
const { product_types: productTypes } = profileItems;
return freeExtensions.filter( ( list ) => {
if (
window.wcAdminFeatures &&
window.wcAdminFeatures.subscriptions
) {
if ( productTypes.includes( 'subscriptions' ) ) {
list.plugins = list.plugins.filter(
( extension ) =>
extension.key !== 'woocommerce-payments' ||
( extension.key === 'woocommerce-payments' &&
! extension.is_activated )
);
}
}
return ALLOWED_PLUGIN_LISTS.includes( list.key );
} );
}, [ freeExtensions ] );
}, [ freeExtensions, profileItems ] );
useEffect( () => {
const initialValues = createInitialValues( installableExtensions );

View File

@ -1,7 +1,6 @@
/**
* External dependencies
*/
import { createElement } from '@wordpress/element';
import { render } from '@testing-library/react';
import { useSelect } from '@wordpress/data';
import userEvent from '@testing-library/user-event';
@ -84,11 +83,14 @@ const freeExtensions = [
},
];
const profileItems = { product_types: [] };
describe( 'Selective extensions bundle', () => {
it( 'should list installable free extensions in footer only basics', () => {
useSelect.mockReturnValue( {
freeExtensions,
isResolving: false,
profileItems,
} );
const { getByText, queryByText } = render(
<SelectiveExtensionsBundle isInstallingActivating={ false } />
@ -113,6 +115,7 @@ describe( 'Selective extensions bundle', () => {
useSelect.mockReturnValue( {
freeExtensions,
isResolving: false,
profileItems,
} );
const { getAllByRole, getByText, queryByText } = render(
<SelectiveExtensionsBundle isInstallingActivating={ false } />

View File

@ -16,12 +16,13 @@ import { includes, filter, get } from 'lodash';
import { getSetting } from '@woocommerce/wc-admin-settings';
import { recordEvent } from '@woocommerce/tracks';
import { withDispatch, withSelect } from '@wordpress/data';
import { ONBOARDING_STORE_NAME } from '@woocommerce/data';
import { ONBOARDING_STORE_NAME, PLUGINS_STORE_NAME } from '@woocommerce/data';
import { Text } from '@woocommerce/experimental';
/**
* Internal dependencies
*/
import { createNoticesFromResponse } from '~/lib/notices';
import ProductTypeLabel from './label';
import './style.scss';
@ -57,17 +58,45 @@ export class ProductTypes extends Component {
}
onContinue() {
const { selected } = this.state;
const { installedPlugins = [] } = this.props;
if ( ! this.validateField() ) {
return;
}
const { createNotice, goToNextStep, updateProfileItems } = this.props;
const {
createNotice,
goToNextStep,
installAndActivatePlugins,
updateProfileItems,
} = this.props;
recordEvent( 'storeprofiler_store_product_type_continue', {
product_type: this.state.selected,
product_type: selected,
} );
updateProfileItems( { product_types: this.state.selected } )
const promises = [ updateProfileItems( { product_types: selected } ) ];
if (
window.wcAdminFeatures &&
window.wcAdminFeatures.subscriptions &&
! installedPlugins.includes( 'woocommerce-payments' ) &&
selected.includes( 'subscriptions' )
) {
promises.push(
installAndActivatePlugins( [ 'woocommerce-payments' ] )
.then( ( response ) => {
createNoticesFromResponse( response );
} )
.catch( ( error ) => {
createNoticesFromResponse( error );
throw new Error();
} )
);
}
Promise.all( promises )
.then( () => goToNextStep() )
.catch( () =>
createNotice(
@ -104,7 +133,11 @@ export class ProductTypes extends Component {
render() {
const { productTypes = {} } = getSetting( 'onboarding', {} );
const { error, isMonthlyPricing, selected } = this.state;
const { isProfileItemsRequesting } = this.props;
const {
installedPlugins = [],
isInstallingActivating,
isProfileItemsRequesting,
} = this.props;
return (
<div className="woocommerce-profile-wizard__product-types">
@ -166,9 +199,14 @@ export class ProductTypes extends Component {
<Button
isPrimary
onClick={ this.onContinue }
isBusy={ isProfileItemsRequesting }
isBusy={
isProfileItemsRequesting ||
isInstallingActivating
}
disabled={
! selected.length || isProfileItemsRequesting
! selected.length ||
isProfileItemsRequesting ||
isInstallingActivating
}
>
{ __( 'Continue', 'woocommerce-admin' ) }
@ -201,6 +239,22 @@ export class ProductTypes extends Component {
'woocommerce-admin'
) }
</Text>
{ window.wcAdminFeatures &&
window.wcAdminFeatures.subscriptions &&
! installedPlugins.includes( 'woocommerce-payments' ) &&
selected.includes( 'subscriptions' ) && (
<Text
variant="body"
size="12"
lineHeight="16px"
as="p"
>
{ __(
'The following extensions will be added to your site for free: WooCommerce Payments. An account is required to use this feature.',
'woocommerce-admin'
) }
</Text>
) }
</div>
</div>
);
@ -214,6 +268,9 @@ export default compose(
getOnboardingError,
isOnboardingRequesting,
} = select( ONBOARDING_STORE_NAME );
const { getInstalledPlugins, isPluginsRequesting } = select(
PLUGINS_STORE_NAME
);
return {
isError: Boolean( getOnboardingError( 'updateProfileItems' ) ),
@ -221,14 +278,20 @@ export default compose(
isProfileItemsRequesting: isOnboardingRequesting(
'updateProfileItems'
),
installedPlugins: getInstalledPlugins(),
isInstallingActivating:
isPluginsRequesting( 'installPlugins' ) ||
isPluginsRequesting( 'activatePlugins' ),
};
} ),
withDispatch( ( dispatch ) => {
const { updateProfileItems } = dispatch( ONBOARDING_STORE_NAME );
const { createNotice } = dispatch( 'core/notices' );
const { installAndActivatePlugins } = dispatch( PLUGINS_STORE_NAME );
return {
createNotice,
installAndActivatePlugins,
updateProfileItems,
};
} )

View File

@ -32,6 +32,7 @@ describe( 'ProductTypes', () => {
afterEach( () => {
setSetting( 'onboarding', {} );
window.wcAdminFeatures.subscriptions = false;
} );
test( 'should render product types', () => {
@ -86,4 +87,27 @@ describe( 'ProductTypes', () => {
expect( mockGoToNextStep ).toHaveBeenCalled();
} );
} );
test( 'should show a warning message at the bottom of the step', () => {
setSetting( 'onboarding', {
productTypes: {
subscriptions: {
label: 'Subscriptions',
},
},
} );
window.wcAdminFeatures.subscriptions = true;
render( <ProductTypes /> );
const subscription = screen.getByText( 'Subscriptions', {
selector: 'label',
} );
userEvent.click( subscription );
expect(
screen.queryByText(
'The following extensions will be added to your site for free: WooCommerce Payments. An account is required to use this feature.'
)
).toBeInTheDocument();
} );
} );

View File

@ -353,7 +353,7 @@ export const TaskList = ( {
? () => dismissTask( task )
: undefined
}
remindMeLater={
onSnooze={
task.allowRemindMeLater
? () => remindTaskLater( task )
: undefined

View File

@ -4,9 +4,13 @@
import { __ } from '@wordpress/i18n';
import { Button, Modal, RadioControl } from '@wordpress/components';
import { useState } from '@wordpress/element';
import { useDispatch } from '@wordpress/data';
import { applyFilters } from '@wordpress/hooks';
import { ITEMS_STORE_NAME } from '@woocommerce/data';
import { useDispatch, useSelect } from '@wordpress/data';
import { addFilter, applyFilters } from '@wordpress/hooks';
import {
ITEMS_STORE_NAME,
ONBOARDING_STORE_NAME,
PLUGINS_STORE_NAME,
} from '@woocommerce/data';
import { getAdminLink } from '@woocommerce/wc-admin-settings';
import { recordEvent } from '@woocommerce/tracks';
@ -44,18 +48,46 @@ const PRODUCT_TEMPLATES = [
'woocommerce-admin'
),
},
{
key: 'subscription',
title: __( 'Subscription product', 'woocommerce-admin' ),
subtitle: __(
'Products that customers receive or gain access to regularly by paying in advance',
'woocommerce-admin'
),
},
];
export default function ProductTemplateModal( { onClose } ) {
const [ selectedTemplate, setSelectedTemplate ] = useState( null );
const [ isRedirecting, setIsRedirecting ] = useState( false );
const { createProductFromTemplate } = useDispatch( ITEMS_STORE_NAME );
const { profileItems } = useSelect( ( select ) => {
const { getProfileItems } = select( ONBOARDING_STORE_NAME );
return {
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 = getAdminLink(
'post-new.php?post_type=product&subscription_pointers=true'
);
return;
}
if ( selectedTemplate ) {
createProductFromTemplate(
{
@ -84,6 +116,22 @@ export default function ProductTemplateModal( { onClose } ) {
}
};
if (
( window.wcAdminFeatures && ! window.wcAdminFeatures.subscriptions ) ||
! profileItems.product_types.includes( 'subscriptions' ) ||
! installedPlugins.includes( 'woocommerce-payments' )
) {
addFilter(
ONBOARDING_PRODUCT_TEMPLATES_FILTER,
'woocommerce-admin',
( productTemplates ) => {
return productTemplates.filter(
( template ) => template.key !== 'subscription'
);
}
);
}
const templates = applyFilters(
ONBOARDING_PRODUCT_TEMPLATES_FILTER,
PRODUCT_TEMPLATES

View File

@ -12,6 +12,8 @@ import {
archive,
download,
} from '@wordpress/icons';
import { ONBOARDING_STORE_NAME, PLUGINS_STORE_NAME } from '@woocommerce/data';
import { useSelect } from '@wordpress/data';
import { List, Pill } from '@woocommerce/components';
import { getAdminLink } from '@woocommerce/wc-admin-settings';
import { recordEvent } from '@woocommerce/tracks';
@ -90,6 +92,35 @@ const subTasks = [
export default function Products() {
const [ selectTemplate, setSelectTemplate ] = useState( null );
const { profileItems } = useSelect( ( select ) => {
const { getProfileItems } = select( ONBOARDING_STORE_NAME );
return {
profileItems: getProfileItems(),
};
} );
const { installedPlugins } = useSelect( ( select ) => {
const { getInstalledPlugins } = select( PLUGINS_STORE_NAME );
return {
installedPlugins: getInstalledPlugins(),
};
} );
if (
window.wcAdminFeatures &&
window.wcAdminFeatures.subscriptions &&
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-admin'
);
}
const onTaskClick = ( task ) => {
task.onClick();

View File

@ -1,94 +0,0 @@
/**
* External dependencies
*/
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
/**
* Internal dependencies
*/
import { Products, ProductTemplateModal } from '../tasks/products';
describe( 'products', () => {
describe( 'Products', () => {
afterEach( () => jest.clearAllMocks() );
it( 'should render 4 different options to add products', () => {
render( <Products /> );
expect(
screen.queryByText( 'Start with a template' )
).toBeInTheDocument();
expect( screen.queryByText( 'Add manually' ) ).toBeInTheDocument();
expect(
screen.queryByText( 'Import via CSV' )
).toBeInTheDocument();
expect(
screen.queryByText( 'Import from another service' )
).toBeInTheDocument();
} );
it( 'should not render the product template modal right away', () => {
render( <Products /> );
expect( screen.queryByText( '[ProductTemplateModal]' ) ).toBeNull();
} );
it( 'should render product template modal when start with template task is selected', () => {
render( <Products /> );
fireEvent(
screen.queryByText( 'Start with a template' ),
// eslint-disable-next-line no-undef
new MouseEvent( 'click', { bubbles: true } )
);
expect(
screen.queryByText( 'Physical product' )
).toBeInTheDocument();
expect(
screen.queryByText( 'Digital product' )
).toBeInTheDocument();
expect(
screen.queryByText( 'Variable product' )
).toBeInTheDocument();
} );
it( 'should allow the user to close the template modal', () => {
render( <Products /> );
fireEvent(
screen.queryByText( 'Start with a template' ),
// eslint-disable-next-line no-undef
new MouseEvent( 'click', { bubbles: true } )
);
expect(
screen.queryByText( 'Physical product' )
).toBeInTheDocument();
const closeButton = screen.getByRole( 'button', {
name: 'Close dialog',
} );
fireEvent.click( closeButton );
expect(
screen.queryByText( 'Physical product' )
).not.toBeInTheDocument();
} );
} );
describe( 'ProductWithTemplate', () => {
afterEach( () => jest.clearAllMocks() );
it( 'should render 3 different product types', () => {
render( <ProductTemplateModal /> );
expect(
screen.queryByText( 'Physical product' )
).toBeInTheDocument();
expect(
screen.queryByText( 'Digital product' )
).toBeInTheDocument();
expect(
screen.queryByText( 'Variable product' )
).toBeInTheDocument();
} );
} );
} );

View File

@ -4,9 +4,13 @@
import { __ } from '@wordpress/i18n';
import { Button, Modal, RadioControl } from '@wordpress/components';
import { useState } from '@wordpress/element';
import { useDispatch } from '@wordpress/data';
import { applyFilters } from '@wordpress/hooks';
import { ITEMS_STORE_NAME } from '@woocommerce/data';
import { useDispatch, useSelect } from '@wordpress/data';
import { addFilter, applyFilters } from '@wordpress/hooks';
import {
ITEMS_STORE_NAME,
ONBOARDING_STORE_NAME,
PLUGINS_STORE_NAME,
} from '@woocommerce/data';
import { getAdminLink } from '@woocommerce/wc-admin-settings';
import { recordEvent } from '@woocommerce/tracks';
@ -44,18 +48,46 @@ const PRODUCT_TEMPLATES = [
'woocommerce-admin'
),
},
{
key: 'subscription',
title: __( 'Subscription product', 'woocommerce-admin' ),
subtitle: __(
'Products that customers receive or gain access to regularly by paying in advance',
'woocommerce-admin'
),
},
];
export default function ProductTemplateModal( { onClose } ) {
const [ selectedTemplate, setSelectedTemplate ] = useState( null );
const [ isRedirecting, setIsRedirecting ] = useState( false );
const { createProductFromTemplate } = useDispatch( ITEMS_STORE_NAME );
const { profileItems } = useSelect( ( select ) => {
const { getProfileItems } = select( ONBOARDING_STORE_NAME );
return {
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 = getAdminLink(
'post-new.php?post_type=product&subscription_pointers=true'
);
return;
}
if ( selectedTemplate ) {
createProductFromTemplate(
{
@ -84,6 +116,22 @@ export default function ProductTemplateModal( { onClose } ) {
}
};
if (
( window.wcAdminFeatures && ! window.wcAdminFeatures.subscriptions ) ||
! profileItems.product_types.includes( 'subscriptions' ) ||
! installedPlugins.includes( 'woocommerce-payments' )
) {
addFilter(
ONBOARDING_PRODUCT_TEMPLATES_FILTER,
'woocommerce-admin',
( productTemplates ) => {
return productTemplates.filter(
( template ) => template.key !== 'subscription'
);
}
);
}
const templates = applyFilters(
ONBOARDING_PRODUCT_TEMPLATES_FILTER,
PRODUCT_TEMPLATES

View File

@ -12,6 +12,8 @@ import {
archive,
download,
} from '@wordpress/icons';
import { ONBOARDING_STORE_NAME, PLUGINS_STORE_NAME } from '@woocommerce/data';
import { useSelect } from '@wordpress/data';
import { List, Pill } from '@woocommerce/components';
import { getAdminLink } from '@woocommerce/wc-admin-settings';
import { recordEvent } from '@woocommerce/tracks';
@ -92,6 +94,35 @@ const subTasks = [
const Products = () => {
const [ selectTemplate, setSelectTemplate ] = useState( null );
const { profileItems } = useSelect( ( select ) => {
const { getProfileItems } = select( ONBOARDING_STORE_NAME );
return {
profileItems: getProfileItems(),
};
} );
const { installedPlugins } = useSelect( ( select ) => {
const { getInstalledPlugins } = select( PLUGINS_STORE_NAME );
return {
installedPlugins: getInstalledPlugins(),
};
} );
if (
window.wcAdminFeatures &&
window.wcAdminFeatures.subscriptions &&
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-admin'
);
}
const onTaskClick = ( task ) => {
task.onClick();

View File

@ -15,6 +15,7 @@
"payment-gateway-suggestions": true,
"settings": false,
"shipping-label-banner": true,
"subscriptions": false,
"store-alerts": true,
"tasks": false,
"transient-notices": true,

View File

@ -15,6 +15,7 @@
"remote-free-extensions": true,
"settings": false,
"shipping-label-banner": true,
"subscriptions": true,
"store-alerts": true,
"tasks": true,
"transient-notices": true,

View File

@ -15,6 +15,7 @@
"payment-gateway-suggestions": true,
"settings": false,
"shipping-label-banner": true,
"subscriptions": false,
"store-alerts": true,
"tasks": false,
"transient-notices": true,

View File

@ -433,37 +433,38 @@ class Onboarding {
* @return array
*/
public static function get_allowed_product_types() {
$product_types = self::append_product_data(
array(
'physical' => array(
'label' => __( 'Physical products', 'woocommerce-admin' ),
'default' => true,
),
'downloads' => array(
'label' => __( 'Downloads', 'woocommerce-admin' ),
),
'subscriptions' => array(
'label' => __( 'Subscriptions', 'woocommerce-admin' ),
'product' => 27147,
),
'memberships' => array(
'label' => __( 'Memberships', 'woocommerce-admin' ),
'product' => 958589,
),
'bookings' => array(
'label' => __( 'Bookings', 'woocommerce-admin' ),
'product' => 390890,
),
'product-bundles' => array(
'label' => __( 'Bundles', 'woocommerce-admin' ),
'product' => 18716,
),
'product-add-ons' => array(
'label' => __( 'Customizable products', 'woocommerce-admin' ),
'product' => 18618,
),
)
$products = array(
'physical' => array(
'label' => __( 'Physical products', 'woocommerce-admin' ),
'default' => true,
),
'downloads' => array(
'label' => __( 'Downloads', 'woocommerce-admin' ),
),
'subscriptions' => array(
'label' => __( 'Subscriptions', 'woocommerce-admin' ),
),
'memberships' => array(
'label' => __( 'Memberships', 'woocommerce-admin' ),
'product' => 958589,
),
'bookings' => array(
'label' => __( 'Bookings', 'woocommerce-admin' ),
'product' => 390890,
),
'product-bundles' => array(
'label' => __( 'Bundles', 'woocommerce-admin' ),
'product' => 18716,
),
'product-add-ons' => array(
'label' => __( 'Customizable products', 'woocommerce-admin' ),
'product' => 18618,
),
);
if ( ! Features::is_enabled( 'subscriptions' ) ) {
$products['subscriptions']['product'] = 27147;
}
$product_types = self::append_product_data( $products );
return apply_filters( 'woocommerce_admin_onboarding_product_types', $product_types );
}

View File

@ -31,7 +31,9 @@ class EvaluateExtension {
}
$installed_plugins = PluginsHelper::get_installed_plugin_slugs();
$activated_plugins = PluginsHelper::get_active_plugin_slugs();
$extension->is_installed = in_array( explode( ':', $extension->key )[0], $installed_plugins, true );
$extension->is_activated = in_array( explode( ':', $extension->key )[0], $activated_plugins, true );
return $extension;
}