CYS: Fix opt-in flow patterns (#50080)

* CYS: Fix opt-in flow patterns

* clean up not necessary state

* Add changefile(s) from automation for the following project(s): woocommerce

* show spinner

* improve e2e test

* fix e2e test

* fix lint error

---------

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Luigi Teschio 2024-07-30 12:10:16 +02:00 committed by GitHub
parent d4696f0437
commit d4e686a063
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 136 additions and 67 deletions

View File

@ -3,7 +3,7 @@
/**
* External dependencies
*/
import { useSelect } from '@wordpress/data';
import { dispatch, useSelect } from '@wordpress/data';
// @ts-ignore No types for this exist yet.
import { store as coreStore } from '@wordpress/core-data';
import { useEffect, useMemo, useState } from '@wordpress/element';
@ -17,24 +17,29 @@ import { Pattern } from '~/customize-store/types/pattern';
import { THEME_SLUG } from '~/customize-store/data/constants';
export const usePatterns = () => {
const { blockPatterns, isLoading } = useSelect(
const { blockPatterns, isLoading, invalidateCache } = useSelect(
( select ) => ( {
blockPatterns: select(
coreStore
// @ts-ignore - This is valid.
// @ts-expect-error -- No types for this exist yet.
).getBlockPatterns() as Pattern[],
isLoading:
// @ts-ignore - This is valid.
// @ts-expect-error -- No types for this exist yet.
! select( coreStore ).hasFinishedResolution(
'getBlockPatterns'
),
} ),
[]
invalidateCache: () =>
// @ts-expect-error -- No types for this exist yet.
dispatch( coreStore ).invalidateResolutionForStoreSelector(
'getBlockPatterns'
),
} )
);
return {
blockPatterns,
isLoading,
invalidateCache,
};
};

View File

@ -11,6 +11,7 @@ import {
Modal,
// @ts-ignore No types for this exist yet.
__experimentalNavigatorButton as NavigatorButton,
Spinner,
// @ts-ignore No types for this exist yet.
} from '@wordpress/components';
import {
@ -41,13 +42,14 @@ import { capitalize } from 'lodash';
import { getNewPath, navigateTo, useQuery } from '@woocommerce/navigation';
import { useSelect } from '@wordpress/data';
import { useNetworkStatus } from '~/utils/react-hooks/use-network-status';
import { isIframe, sendMessageToParent } from '~/customize-store/utils';
import { useEditorBlocks } from '../../hooks/use-editor-blocks';
import { isTrackingAllowed } from '../../utils/is-tracking-allowed';
import clsx from 'clsx';
import './style.scss';
import { usePatterns } from '~/customize-store/assembler-hub/hooks/use-patterns';
import { THEME_SLUG } from '~/customize-store/data/constants';
import apiFetch from '@wordpress/api-fetch';
import { enableTracking } from '~/customize-store/design-without-ai/services';
const isActiveElement = ( path: string | undefined, category: string ) => {
if ( path?.includes( category ) ) {
@ -60,7 +62,7 @@ export const SidebarNavigationScreenHomepagePTK = ( {
}: {
onNavigateBackClick: () => void;
} ) => {
const { context, sendEvent } = useContext( CustomizeStoreContext );
const { context } = useContext( CustomizeStoreContext );
const isNetworkOffline = useNetworkStatus();
const isPTKPatternsAPIAvailable = context.isPTKPatternsAPIAvailable;
@ -107,7 +109,12 @@ export const SidebarNavigationScreenHomepagePTK = ( {
}, initialAccumulator );
}, [ blocks ] );
const { blockPatterns, isLoading: isLoadingPatterns } = usePatterns();
const {
blockPatterns,
isLoading: isLoadingPatterns,
invalidateCache,
} = usePatterns();
const patternsFromPTK = blockPatterns.filter(
( pattern ) =>
! pattern.name.includes( THEME_SLUG ) &&
@ -118,28 +125,36 @@ export const SidebarNavigationScreenHomepagePTK = ( {
pattern.source !== 'pattern-directory/core'
);
let notice;
if ( isNetworkOffline ) {
notice = __(
"Looks like we can't detect your network. Please double-check your internet connection and refresh the page.",
'woocommerce'
);
} else if ( ! isPTKPatternsAPIAvailable ) {
notice = __(
"Unfortunately, we're experiencing some technical issues — please come back later to access more patterns.",
'woocommerce'
);
} else if ( ! isTrackingAllowed() ) {
notice = __(
'Opt in to <OptInModal>usage tracking</OptInModal> to get access to more patterns.',
'woocommerce'
);
} else if ( ! isLoadingPatterns && patternsFromPTK.length === 0 ) {
notice = __(
'Unfortunately, a technical issue is preventing more patterns from being displayed. Please <FetchPatterns>try again</FetchPatterns> later.',
'woocommerce'
);
}
const notice = useMemo( () => {
let noticeText;
if ( isNetworkOffline ) {
noticeText = __(
"Looks like we can't detect your network. Please double-check your internet connection and refresh the page.",
'woocommerce'
);
} else if ( ! isPTKPatternsAPIAvailable ) {
noticeText = __(
"Unfortunately, we're experiencing some technical issues — please come back later to access more patterns.",
'woocommerce'
);
} else if ( ! isTrackingAllowed() ) {
noticeText = __(
'Opt in to <OptInModal>usage tracking</OptInModal> to get access to more patterns.',
'woocommerce'
);
} else if ( ! isLoadingPatterns && patternsFromPTK.length === 0 ) {
noticeText = __(
'Unfortunately, a technical issue is preventing more patterns from being displayed. Please <FetchPatterns>try again</FetchPatterns> later.',
'woocommerce'
);
}
return noticeText;
}, [
isNetworkOffline,
isPTKPatternsAPIAvailable,
isLoadingPatterns,
patternsFromPTK.length,
] );
const [ isModalOpen, setIsModalOpen ] = useState( false );
@ -149,6 +164,8 @@ export const SidebarNavigationScreenHomepagePTK = ( {
const [ optInDataSharing, setIsOptInDataSharing ] =
useState< boolean >( true );
const [ isFetchingPatterns, setIsFetchingPatterns ] = useState( false );
const optIn = () => {
trackEvent(
'customize_your_store_assembler_hub_opt_in_usage_tracking'
@ -265,16 +282,13 @@ export const SidebarNavigationScreenHomepagePTK = ( {
),
FetchPatterns: (
<Button
onClick={ () => {
if ( isIframe( window ) ) {
sendMessageToParent( {
type: 'INSTALL_PATTERNS',
} );
} else {
sendEvent(
'INSTALL_PATTERNS'
);
}
onClick={ async () => {
await apiFetch( {
path: `/wc/private/patterns`,
method: 'POST',
} );
invalidateCache();
} }
variant="link"
/>
@ -327,24 +341,34 @@ export const SidebarNavigationScreenHomepagePTK = ( {
) }
</Button>
<Button
onClick={ () => {
onClick={ async () => {
optIn();
if ( isIframe( window ) ) {
sendMessageToParent( {
type: 'INSTALL_PATTERNS',
} );
} else {
sendEvent(
'INSTALL_PATTERNS'
);
}
await enableTracking();
setIsFetchingPatterns(
true
);
await apiFetch< {
success: boolean;
} >( {
path: `/wc/private/patterns`,
method: 'POST',
} );
invalidateCache();
closeModal();
setIsFetchingPatterns(
false
);
} }
variant="primary"
disabled={ ! optInDataSharing }
>
{ __(
'Opt in',
'woocommerce'
{ isFetchingPatterns ? (
<Spinner />
) : (
__(
'Opt in',
'woocommerce'
)
) }
</Button>
</div>

View File

@ -116,10 +116,6 @@ const AssemblerHub = ( { sendEvent }: { sendEvent: SendEventFn } ) => {
if ( event.data?.type === 'INSTALL_FONTS' ) {
sendEvent( { type: 'INSTALL_FONTS' } );
}
if ( event.data?.type === 'INSTALL_PATTERNS' ) {
sendEvent( { type: 'INSTALL_PATTERNS' } );
}
} );
}, [ sendEvent ] );

View File

@ -81,7 +81,6 @@ const installFontFamiliesState = {
export type DesignWithoutAIStateMachineEvents =
| { type: 'EXTERNAL_URL_UPDATE' }
| { type: 'INSTALL_FONTS' }
| { type: 'INSTALL_PATTERNS' }
| { type: 'NO_AI_FLOW_ERROR'; payload: { hasError: boolean } };
export const designWithNoAiStateMachineDefinition = createMachine(
@ -103,9 +102,6 @@ export const designWithNoAiStateMachineDefinition = createMachine(
INSTALL_FONTS: {
target: 'installFontFamilies',
},
INSTALL_PATTERNS: {
target: 'installPatterns',
},
},
context: {
startLoadingTime: null,

View File

@ -62,7 +62,6 @@ export type customizeStoreStateMachineEvents =
| { type: 'AI_WIZARD_CLOSED_BEFORE_COMPLETION'; payload: { step: string } }
| { type: 'EXTERNAL_URL_UPDATE' }
| { type: 'INSTALL_FONTS' }
| { type: 'INSTALL_PATTERNS' }
| { type: 'NO_AI_FLOW_ERROR'; payload: { hasError: boolean } }
| { type: 'IS_FONT_LIBRARY_AVAILABLE'; payload: boolean };
@ -242,9 +241,6 @@ export const customizeStoreStateMachineDefinition = createMachine( {
INSTALL_FONTS: {
target: 'designWithoutAi.installFonts',
},
INSTALL_PATTERNS: {
target: 'designWithoutAi.installPatterns',
},
},
states: {
setFlags: {

View File

@ -0,0 +1,4 @@
Significance: minor
Type: update
CYS: Improve opt-in flow patterns.

View File

@ -2,6 +2,7 @@
namespace Automattic\WooCommerce\StoreApi\Routes\V1;
use Automattic\WooCommerce\Blocks\BlockPatterns;
use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Patterns\PTKClient;
use Automattic\WooCommerce\Blocks\Patterns\PTKPatternsStore;
@ -114,7 +115,11 @@ class Patterns extends AbstractRoute {
protected function get_route_post_response( WP_REST_Request $request ) {
$ptk_patterns_store = Package::container()->get( PTKPatternsStore::class );
$ptk_patterns_store->fetch_patterns();
$patterns = $ptk_patterns_store->fetch_patterns();
$block_patterns = Package::container()->get( BlockPatterns::class );
$block_patterns->register_ptk_patterns( $patterns );
return rest_ensure_response(
array(

View File

@ -47,6 +47,13 @@ test.describe( 'Assembler -> Full composability', { tag: '@gutenberg' }, () => {
'woocommerce_customize_store_onboarding_tour_hidden',
'yes'
);
await setOption(
request,
baseURL,
'woocommerce_allow_tracking',
'no'
);
} catch ( error ) {
console.log( 'Store completed option not updated' );
}
@ -76,6 +83,13 @@ test.describe( 'Assembler -> Full composability', { tag: '@gutenberg' }, () => {
'no'
);
await setOption(
request,
baseURL,
'woocommerce_allow_tracking',
'no'
);
await activateTheme( DEFAULT_THEME );
} catch ( error ) {
console.log( 'Store completed option not updated' );
@ -300,4 +314,33 @@ test.describe( 'Assembler -> Full composability', { tag: '@gutenberg' }, () => {
await expect( emptyPatternsBlock ).toBeHidden();
await expect( defaultPattern ).toBeVisible();
} );
test( 'Clicking opt-in new patterns should be available', async ( {
pageObject,
baseURL,
} ) => {
await prepareAssembler( pageObject, baseURL );
const assembler = await pageObject.getAssembler();
await assembler.getByText( 'Usage tracking' ).click();
await expect(
assembler.getByText( 'Access more patterns' )
).toBeVisible();
await assembler.getByRole( 'button', { name: 'Opt in' } ).click();
await assembler
.getByText( 'Access more patterns' )
.waitFor( { state: 'hidden' } );
const sidebarPattern = assembler.locator(
'.block-editor-block-patterns-list'
);
await sidebarPattern.waitFor( { state: 'visible' } );
await expect(
assembler.locator( '.block-editor-block-patterns-list__list-item' )
).toHaveCount( 10 );
} );
} );