Fix free features is still rendered when no recommendation (#33923)

* Fix free features is still rendered when there is no recommendation

* Add changelog

* Update BusinessDetails tab to go to next step if no installable extensions

* Update spinner
This commit is contained in:
Chi-Hsuan Huang 2022-07-21 12:57:46 +08:00 committed by GitHub
parent 9620704d42
commit d09d59186d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 148 additions and 139 deletions

View File

@ -13,6 +13,7 @@ import {
__experimentalText as Text, __experimentalText as Text,
FlexItem, FlexItem,
CheckboxControl, CheckboxControl,
Spinner,
} from '@wordpress/components'; } from '@wordpress/components';
import { withDispatch, withSelect } from '@wordpress/data'; import { withDispatch, withSelect } from '@wordpress/data';
import { SelectControl, Form, TextControl } from '@woocommerce/components'; import { SelectControl, Form, TextControl } from '@woocommerce/components';
@ -35,7 +36,10 @@ import { employeeOptions } from '../../data/employee-options';
import { sellingVenueOptions } from '../../data/selling-venue-options'; import { sellingVenueOptions } from '../../data/selling-venue-options';
import { getRevenueOptions } from '../../data/revenue-options'; import { getRevenueOptions } from '../../data/revenue-options';
import { getProductCountOptions } from '../../data/product-options'; import { getProductCountOptions } from '../../data/product-options';
import { SelectiveExtensionsBundle } from './selective-extensions-bundle'; import {
SelectiveExtensionsBundle,
getInstallableExtensions,
} from './selective-extensions-bundle';
import { getPluginSlug, getPluginTrackKey, getTimeFrame } from '~/utils'; import { getPluginSlug, getPluginTrackKey, getTimeFrame } from '~/utils';
import './style.scss'; import './style.scss';
@ -146,6 +150,8 @@ class BusinessDetails extends Component {
this.state.savedValues, this.state.savedValues,
this.persistValues this.persistValues
); );
// Refetch the free extensions data
props.invalidateResolutionForStoreSelector( 'getFreeExtensions' );
} }
async onContinue( async onContinue(
@ -430,13 +436,19 @@ class BusinessDetails extends Component {
<Form <Form
initialValues={ this.state.savedValues.businessDetailsTab } initialValues={ this.state.savedValues.businessDetailsTab }
onSubmit={ ( values ) => { onSubmit={ ( values ) => {
this.setState( { if ( this.props.hasInstallableExtensions ) {
savedValues: { this.setState( {
...this.state.savedValues, savedValues: {
businessDetailsTab: values, ...this.state.savedValues,
}, businessDetailsTab: values,
currentTab: BUSINESS_FEATURES_TAB_NAME, },
} ); currentTab: BUSINESS_FEATURES_TAB_NAME,
} );
} else {
goToNextStep( {
step: BUSINESS_FEATURES_TAB_NAME,
} );
}
this.trackBusinessDetailsStep( values ); this.trackBusinessDetailsStep( values );
recordEvent( 'storeprofiler_step_view', { recordEvent( 'storeprofiler_step_view', {
@ -645,13 +657,10 @@ class BusinessDetails extends Component {
} }
renderFreeFeaturesStep() { renderFreeFeaturesStep() {
const { isInstallingActivating, settings, profileItems } = this.props; const { isInstallingActivating } = this.props;
const { const {
savedValues: { freeFeaturesTab }, savedValues: { freeFeaturesTab },
} = this.state; } = this.state;
const country = settings.woocommerce_default_country
? settings.woocommerce_default_country
: null;
return ( return (
<> <>
@ -681,9 +690,7 @@ class BusinessDetails extends Component {
<SelectiveExtensionsBundle <SelectiveExtensionsBundle
isInstallingActivating={ isInstallingActivating } isInstallingActivating={ isInstallingActivating }
onSubmit={ this.onContinue } onSubmit={ this.onContinue }
country={ country } installableExtensions={ this.props.installableExtensions }
industry={ profileItems.industry }
productTypes={ profileItems.product_types }
installExtensionOptions={ installExtensionOptions={
freeFeaturesTab.installExtensionOptions freeFeaturesTab.installExtensionOptions
} }
@ -700,10 +707,17 @@ class BusinessDetails extends Component {
this.setState( { this.setState( {
savedValues, savedValues,
} ); } );
this.props.updateCurrentStepValues(
this.props.step.key, if (
savedValues // Only update current step values when current state's installExtensionOptions is not undefined.
); this.state.savedValues.freeFeaturesTab
.installExtensionOptions
) {
this.props.updateCurrentStepValues(
this.props.step.key,
savedValues
);
}
} } } }
/> />
</> </>
@ -711,6 +725,36 @@ class BusinessDetails extends Component {
} }
render() { render() {
const tabs = [];
if ( ! this.props.hasFinishedGetFreeExtensionsResolution ) {
return (
<div className="woocommerce-admin__business-details__spinner">
<Spinner />
</div>
);
}
tabs.push( {
name:
this.state.currentTab === BUSINESS_DETAILS_TAB_NAME
? 'current-tab'
: BUSINESS_DETAILS_TAB_NAME,
id: BUSINESS_DETAILS_TAB_NAME,
title: __( 'Business details', 'woocommerce' ),
} );
if ( this.props.hasInstallableExtensions ) {
tabs.push( {
name:
this.state.currentTab === BUSINESS_FEATURES_TAB_NAME
? 'current-tab'
: BUSINESS_FEATURES_TAB_NAME,
id: BUSINESS_FEATURES_TAB_NAME,
title: __( 'Free features', 'woocommerce' ),
className: this.state.isValid ? '' : 'is-disabled',
} );
}
// There is a hack here to help us manage the selected tab programmatically. // There is a hack here to help us manage the selected tab programmatically.
// We set the tab name "current-tab". when its the one we want selected. This tricks // We set the tab name "current-tab". when its the one we want selected. This tricks
// the logic in the TabPanel and allows us to switch which tab has the name "current-tab" // the logic in the TabPanel and allows us to switch which tab has the name "current-tab"
@ -733,25 +777,7 @@ class BusinessDetails extends Component {
} ); } );
} }
} } } }
tabs={ [ tabs={ tabs }
{
name:
this.state.currentTab === BUSINESS_DETAILS_TAB_NAME
? 'current-tab'
: BUSINESS_DETAILS_TAB_NAME,
id: BUSINESS_DETAILS_TAB_NAME,
title: __( 'Business details', 'woocommerce' ),
},
{
name:
this.state.currentTab === BUSINESS_FEATURES_TAB_NAME
? 'current-tab'
: BUSINESS_FEATURES_TAB_NAME,
id: BUSINESS_FEATURES_TAB_NAME,
title: __( 'Free features', 'woocommerce' ),
className: this.state.isValid ? '' : 'is-disabled',
},
] }
> >
{ ( tab ) => <>{ this.getTab( tab.id ) }</> } { ( tab ) => <>{ this.getTab( tab.id ) }</> }
</TabPanel> </TabPanel>
@ -771,29 +797,54 @@ BusinessDetails.contextType = CurrencyContext;
export const BusinessFeaturesList = compose( export const BusinessFeaturesList = compose(
withSelect( ( select ) => { withSelect( ( select ) => {
const { getSettings, getSettingsError } = select( SETTINGS_STORE_NAME ); const { getSettings, getSettingsError } = select( SETTINGS_STORE_NAME );
const { getProfileItems, getOnboardingError } = select( const {
ONBOARDING_STORE_NAME getProfileItems,
); getOnboardingError,
getFreeExtensions,
hasFinishedResolution,
} = select( ONBOARDING_STORE_NAME );
const { getPluginsError, isPluginsRequesting } = const { getPluginsError, isPluginsRequesting } =
select( PLUGINS_STORE_NAME ); select( PLUGINS_STORE_NAME );
const { general: settings = {} } = getSettings( 'general' ); const { general: settings = {} } = getSettings( 'general' );
const freeExtensions = getFreeExtensions();
const profileItems = getProfileItems();
const country = settings.woocommerce_default_country
? settings.woocommerce_default_country
: null;
const installableExtensions = freeExtensions
? getInstallableExtensions( {
freeExtensionBundleByCategory: freeExtensions,
country,
productTypes: profileItems.product_types,
} )
: [];
const hasInstallableExtensions = installableExtensions.some(
( { plugins } ) => plugins.length > 0
);
return { return {
hasInstallActivateError: hasInstallActivateError:
getPluginsError( 'installPlugins' ) || getPluginsError( 'installPlugins' ) ||
getPluginsError( 'activatePlugins' ), getPluginsError( 'activatePlugins' ),
hasInstallableExtensions,
hasFinishedGetFreeExtensionsResolution:
hasFinishedResolution( 'getFreeExtensions' ),
installableExtensions,
isError: Boolean( getOnboardingError( 'updateProfileItems' ) ), isError: Boolean( getOnboardingError( 'updateProfileItems' ) ),
profileItems: getProfileItems(),
isSettingsError: Boolean( getSettingsError( 'general' ) ), isSettingsError: Boolean( getSettingsError( 'general' ) ),
settings,
isInstallingActivating: isInstallingActivating:
isPluginsRequesting( 'installPlugins' ) || isPluginsRequesting( 'installPlugins' ) ||
isPluginsRequesting( 'activatePlugins' ) || isPluginsRequesting( 'activatePlugins' ) ||
isPluginsRequesting( 'getJetpackConnectUrl' ), isPluginsRequesting( 'getJetpackConnectUrl' ),
profileItems,
settings,
}; };
} ), } ),
withDispatch( ( dispatch ) => { withDispatch( ( dispatch ) => {
const { updateProfileItems } = dispatch( ONBOARDING_STORE_NAME ); const { updateProfileItems, invalidateResolutionForStoreSelector } =
dispatch( ONBOARDING_STORE_NAME );
const { installAndActivatePlugins } = dispatch( PLUGINS_STORE_NAME ); const { installAndActivatePlugins } = dispatch( PLUGINS_STORE_NAME );
const { createNotice } = dispatch( 'core/notices' ); const { createNotice } = dispatch( 'core/notices' );
@ -801,6 +852,7 @@ export const BusinessFeaturesList = compose(
createNotice, createNotice,
installAndActivatePlugins, installAndActivatePlugins,
updateProfileItems, updateProfileItems,
invalidateResolutionForStoreSelector,
}; };
} ) } )
)( BusinessDetails ); )( BusinessDetails );

View File

@ -1,16 +1,15 @@
/** /**
* External dependencies * External dependencies
*/ */
import { useEffect, useMemo, useState } from '@wordpress/element'; import { useEffect, useState } from '@wordpress/element';
import { Button, Card, CheckboxControl, Spinner } from '@wordpress/components'; import { Button, Card, CheckboxControl, Spinner } from '@wordpress/components';
import { Text } from '@woocommerce/experimental'; import { Text } from '@woocommerce/experimental';
import { Link } from '@woocommerce/components'; import { Link } from '@woocommerce/components';
import { __, _n, sprintf } from '@wordpress/i18n'; import { __, _n, sprintf } from '@wordpress/i18n';
import { Icon, chevronDown, chevronUp } from '@wordpress/icons'; import { Icon, chevronDown, chevronUp } from '@wordpress/icons';
import interpolateComponents from '@automattic/interpolate-components'; import interpolateComponents from '@automattic/interpolate-components';
import { pluginNames, ONBOARDING_STORE_NAME } from '@woocommerce/data'; import { pluginNames } from '@woocommerce/data';
import { recordEvent } from '@woocommerce/tracks'; import { recordEvent } from '@woocommerce/tracks';
import { useDispatch, useSelect } from '@wordpress/data';
/** /**
* Internal dependencies * Internal dependencies
@ -234,55 +233,38 @@ export const createInstallExtensionOptions = (
}, prevInstallExtensionOptions ); }, prevInstallExtensionOptions );
}; };
export const getInstallableExtensions = ( {
freeExtensionBundleByCategory,
country,
productTypes,
} ) => {
return freeExtensionBundleByCategory.filter( ( extensionBundle ) => {
if (
window.wcAdminFeatures &&
window.wcAdminFeatures.subscriptions &&
getCountryCode( country ) === 'US'
) {
if ( productTypes.includes( 'subscriptions' ) ) {
extensionBundle.plugins = extensionBundle.plugins.filter(
( extension ) =>
extension.key !== 'woocommerce-payments' ||
( extension.key === 'woocommerce-payments' &&
! extension.is_activated )
);
}
}
return ALLOWED_PLUGIN_CATEGORIES.includes( extensionBundle.key );
} );
};
export const SelectiveExtensionsBundle = ( { export const SelectiveExtensionsBundle = ( {
isInstallingActivating, isInstallingActivating,
onSubmit, onSubmit,
country,
productTypes,
industry,
setInstallExtensionOptions, setInstallExtensionOptions,
installableExtensions,
installExtensionOptions = { install_extensions: true }, installExtensionOptions = { install_extensions: true },
} ) => { } ) => {
const [ showExtensions, setShowExtensions ] = useState( false ); const [ showExtensions, setShowExtensions ] = useState( false );
const { freeExtensions: freeExtensionBundleByCategory, isResolving } =
useSelect( ( select ) => {
const { getFreeExtensions, hasFinishedResolution } = select(
ONBOARDING_STORE_NAME
);
return {
freeExtensions: getFreeExtensions(),
isResolving: ! hasFinishedResolution( 'getFreeExtensions' ),
};
} );
const { invalidateResolutionForStoreSelector } = useDispatch(
ONBOARDING_STORE_NAME
);
useEffect( () => {
invalidateResolutionForStoreSelector( 'getFreeExtensions' );
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ country, industry ] );
const installableExtensions = useMemo( () => {
return freeExtensionBundleByCategory.filter( ( extensionBundle ) => {
if (
window.wcAdminFeatures &&
window.wcAdminFeatures.subscriptions &&
getCountryCode( country ) === 'US'
) {
if ( productTypes.includes( 'subscriptions' ) ) {
extensionBundle.plugins = extensionBundle.plugins.filter(
( extension ) =>
extension.key !== 'woocommerce-payments' ||
( extension.key === 'woocommerce-payments' &&
! extension.is_activated )
);
}
}
return ALLOWED_PLUGIN_CATEGORIES.includes( extensionBundle.key );
} );
}, [ freeExtensionBundleByCategory, productTypes, country ] );
useEffect( () => { useEffect( () => {
if ( isInstallingActivating || installableExtensions.length === 0 ) { if ( isInstallingActivating || installableExtensions.length === 0 ) {
@ -401,8 +383,8 @@ export const SelectiveExtensionsBundle = ( {
installableExtensions installableExtensions
); );
} } } }
isBusy={ isInstallingActivating || isResolving } isBusy={ isInstallingActivating }
disabled={ isInstallingActivating || isResolving } disabled={ isInstallingActivating }
isPrimary isPrimary
> >
{ __( 'Continue', 'woocommerce' ) } { __( 'Continue', 'woocommerce' ) }

View File

@ -2,7 +2,6 @@
* External dependencies * External dependencies
*/ */
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import { useSelect, useDispatch } from '@wordpress/data';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { pluginNames } from '@woocommerce/data'; import { pluginNames } from '@woocommerce/data';
@ -15,25 +14,21 @@ jest.mock( '../../app-illustration', () => ( {
AppIllustration: jest.fn().mockReturnValue( '[illustration]' ), AppIllustration: jest.fn().mockReturnValue( '[illustration]' ),
} ) ); } ) );
jest.mock( '@wordpress/data', () => ( {
...jest.requireActual( '@wordpress/data' ),
useSelect: jest.fn(),
useDispatch: jest.fn().mockImplementation( () => ( {
updateOptions: jest.fn(),
installAndActivatePlugins: jest.fn(),
} ) ),
} ) );
jest.mock( '@woocommerce/data', () => ( {
pluginNames: {
'woocommerce-payments': 'WooCommerce Payments',
mailpoet: 'Mailpoet',
random: 'Random',
'google-listings-and-ads': 'Google Listings and Ads',
},
} ) );
const freeExtensions = [ const freeExtensions = [
{
key: 'task-list/reach',
title: 'Reach out to customers',
plugins: [
{
key: 'random',
name: 'Random',
description: 'Random description',
manage_url: 'admin.php?page=mailpoet-newsletters',
is_visible: true,
is_installed: true,
},
],
},
{ {
key: 'obw/basics', key: 'obw/basics',
title: 'Get the basics', title: 'Get the basics',
@ -53,20 +48,6 @@ const freeExtensions = [
}, },
], ],
}, },
{
key: 'task-list/reach',
title: 'Reach out to customers',
plugins: [
{
key: 'random',
name: 'Random',
description: 'Random description',
manage_url: 'admin.php?page=mailpoet-newsletters',
is_visible: true,
is_installed: true,
},
],
},
{ {
key: 'obw/grow', key: 'obw/grow',
title: 'Grow your store', title: 'Grow your store',
@ -84,23 +65,13 @@ const freeExtensions = [
]; ];
describe( 'Selective extensions bundle', () => { describe( 'Selective extensions bundle', () => {
beforeAll( () => {
useSelect.mockReturnValue( {
freeExtensions,
isResolving: false,
} );
useDispatch.mockReturnValue( {
invalidateResolutionForStoreSelector: () => {},
} );
} );
it( 'should list installable free extensions from obw/basics and obw/grow', () => { it( 'should list installable free extensions from obw/basics and obw/grow', () => {
const mockSetInstallExtensionOptions = jest.fn(); const mockSetInstallExtensionOptions = jest.fn();
// Render once to get installExtensionOptions // Render once to get installExtensionOptions
render( render(
<SelectiveExtensionsBundle <SelectiveExtensionsBundle
isInstallingActivating={ false } isInstallingActivating={ false }
installableExtensions={ freeExtensions.slice( 1 ) }
setInstallExtensionOptions={ mockSetInstallExtensionOptions } setInstallExtensionOptions={ mockSetInstallExtensionOptions }
/> />
); );
@ -109,6 +80,7 @@ describe( 'Selective extensions bundle', () => {
<SelectiveExtensionsBundle <SelectiveExtensionsBundle
isInstallingActivating={ false } isInstallingActivating={ false }
setInstallExtensionOptions={ mockSetInstallExtensionOptions } setInstallExtensionOptions={ mockSetInstallExtensionOptions }
installableExtensions={ freeExtensions.slice( 1 ) }
installExtensionOptions={ installExtensionOptions={
mockSetInstallExtensionOptions.mock.calls[ 0 ][ 0 ] mockSetInstallExtensionOptions.mock.calls[ 0 ][ 0 ]
} }
@ -126,15 +98,14 @@ describe( 'Selective extensions bundle', () => {
new RegExp( pluginNames[ 'google-listings-and-ads' ] ) new RegExp( pluginNames[ 'google-listings-and-ads' ] )
) )
).toBeInTheDocument(); ).toBeInTheDocument();
expect( expect( queryByText( new RegExp( 'Random' ) ) ).not.toBeInTheDocument();
queryByText( new RegExp( pluginNames.random ) )
).not.toBeInTheDocument();
} ); } );
it( 'should list installable extensions when dropdown is clicked', () => { it( 'should list installable extensions when dropdown is clicked', () => {
const { getAllByRole, getByText, queryByText } = render( const { getAllByRole, getByText, queryByText } = render(
<SelectiveExtensionsBundle <SelectiveExtensionsBundle
isInstallingActivating={ false } isInstallingActivating={ false }
installableExtensions={ freeExtensions }
setInstallExtensionOptions={ jest.fn() } setInstallExtensionOptions={ jest.fn() }
/> />
); );

View File

@ -3,7 +3,7 @@
*/ */
import { useSelect } from '@wordpress/data'; import { useSelect } from '@wordpress/data';
import { useMemo } from '@wordpress/element'; import { useMemo } from '@wordpress/element';
import { Spinner } from '@woocommerce/components'; import { Spinner } from '@wordpress/components';
import { ONBOARDING_STORE_NAME, SETTINGS_STORE_NAME } from '@woocommerce/data'; import { ONBOARDING_STORE_NAME, SETTINGS_STORE_NAME } from '@woocommerce/data';
/** /**

View File

@ -0,0 +1,4 @@
Significance: patch
Type: fix
Fix free features is still rendered when there is no recommendation