CYS: Fix opt-in flow fonts (#50086)

* CYS: Fix opt-in flow fonts

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

* add E2E test

* fix e2e test

---------

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

View File

@ -14,7 +14,7 @@ import { SidebarNavigationScreenFooter } from './sidebar-navigation-screen-foote
import { SidebarNavigationScreenHeader } from './sidebar-navigation-screen-header/sidebar-navigation-screen-header';
import { SidebarNavigationScreenHomepage } from './sidebar-navigation-screen-homepage/sidebar-navigation-screen-homepage';
import { SidebarNavigationScreenMain } from './sidebar-navigation-screen-main';
import { SidebarNavigationScreenTypography } from './sidebar-navigation-screen-typography';
import { SidebarNavigationScreenTypography } from './sidebar-navigation-screen-typography/sidebar-navigation-screen-typography';
// import { SidebarNavigationScreenPages } from './sidebar-navigation-screen-pages';
import { getNewPath, navigateTo, useQuery } from '@woocommerce/navigation';

View File

@ -12,26 +12,27 @@ import {
import { useSelect } from '@wordpress/data';
import { Link } from '@woocommerce/components';
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
import { Button, Modal, CheckboxControl } from '@wordpress/components';
import { Button, Modal, CheckboxControl, Spinner } from '@wordpress/components';
import interpolateComponents from '@automattic/interpolate-components';
/**
* Internal dependencies
*/
import { SidebarNavigationScreen } from './sidebar-navigation-screen';
import { SidebarNavigationScreen } from '../sidebar-navigation-screen';
import { ADMIN_URL } from '~/utils/admin-settings';
import { FontPairing } from './global-styles';
import { CustomizeStoreContext } from '..';
import { FontPairing } from '../global-styles';
import { CustomizeStoreContext } from '../..';
import { FlowType } from '~/customize-store/types';
import { isIframe, sendMessageToParent } from '~/customize-store/utils';
import { trackEvent } from '~/customize-store/tracking';
import { installFontFamilies } from '../../utils/fonts';
import { enableTracking } from '~/customize-store/design-without-ai/services';
export const SidebarNavigationScreenTypography = ( {
onNavigateBackClick,
}: {
onNavigateBackClick: () => void;
} ) => {
const { context, sendEvent } = useContext( CustomizeStoreContext );
const { context } = useContext( CustomizeStoreContext );
const aiOnline = context.flowType === FlowType.AIOnline;
const isFontLibraryAvailable = context.isFontLibraryAvailable;
@ -90,6 +91,8 @@ export const SidebarNavigationScreenTypography = ( {
const openModal = () => setIsModalOpen( true );
const closeModal = () => setIsModalOpen( false );
const [ isFetchingFonts, setIsFetchingFonts ] = useState( false );
const [ OptInDataSharing, setIsOptInDataSharing ] =
useState< boolean >( true );
@ -207,22 +210,23 @@ export const SidebarNavigationScreenTypography = ( {
{ __( 'Cancel', 'woocommerce' ) }
</Button>
<Button
onClick={ () => {
onClick={ async () => {
optIn();
if ( isIframe( window ) ) {
sendMessageToParent( {
type: 'INSTALL_FONTS',
} );
} else {
sendEvent(
'INSTALL_FONTS'
);
}
setIsFetchingFonts( true );
await enableTracking();
await installFontFamilies();
closeModal();
setIsFetchingFonts( false );
} }
variant="primary"
disabled={ ! OptInDataSharing }
>
{ __( 'Opt in', 'woocommerce' ) }
{ isFetchingFonts ? (
<Spinner />
) : (
__( 'Opt in', 'woocommerce' )
) }
</Button>
</div>
</Modal>

View File

@ -0,0 +1,282 @@
/**
* External dependencies
*/
import apiFetch from '@wordpress/api-fetch';
import { resolveSelect } from '@wordpress/data';
/**
* Internal dependencies
*/
import {
FontFace,
FontFamiliesToInstall,
FontFamily,
} from '~/customize-store/types/font';
import { FONT_FAMILIES_TO_INSTALL } from '../sidebar/global-styles/font-pairing-variations/constants';
export type FontCollectionsResponse = Array< {
slug: string;
description: string;
name: string;
} >;
export type FontCollectionResponse = {
slug: string;
name: string;
font_families: Array< {
font_family_settings: FontFamily;
categories: Array< string >;
} >;
};
const getInstalledFontFamilyByNameFontFamily = (
installedFontFamilies: Array< {
id: number;
font_family_settings: FontFamily;
font_face: Array< FontFace >;
} >,
nameFontFamily: string
) => {
return installedFontFamilies.find(
( { font_family_settings } ) =>
font_family_settings.slug === nameFontFamily
);
};
const getFontFamiliesToInstall = (
fontCollection: FontCollectionResponse,
slugFontFamily: string,
fontFamilyToInstall: FontFamiliesToInstall[ 'slug' ]
) => {
const fontFromCollection = fontCollection.font_families.find(
( { font_family_settings } ) =>
font_family_settings.slug === slugFontFamily
);
if ( ! fontFromCollection ) {
return null;
}
const fontFace = fontFromCollection?.font_family_settings.fontFace.filter(
( { fontWeight } ) =>
fontFamilyToInstall.fontWeights.includes( fontWeight )
);
const fontFamilyWithFontFace = {
...fontFromCollection?.font_family_settings,
fontFace,
};
return fontFamilyWithFontFace;
};
/**
* Retrieves font families and font faces to install based on a provided font collection and a list of installed font families.
* The fontFamilyWithFontFaceToInstall include fontFamilies with font faces that are not installed yet.
* The fontFaceToInstall include font faces that are not installed yet, but already have the font family installed.
*
* @param fontCollection - The complete font collection containing all available font data.
* @param installedFontFamilies - An array of installed font families with associated font faces and settings.
* @return An object containing font families with font faces to install and individual font faces to install.
*/
export const getFontFamiliesAndFontFaceToInstall = (
fontCollection: FontCollectionResponse,
installedFontFamilies: Array< {
id: number;
font_face: Array< FontFace >;
font_family_settings: FontFamily;
} >
) => {
return Object.entries( FONT_FAMILIES_TO_INSTALL ).reduce(
( acc, [ slug, fontData ] ) => {
const fontFamilyWithFontFaceToInstall = getFontFamiliesToInstall(
fontCollection,
slug,
fontData
);
if ( ! fontFamilyWithFontFaceToInstall ) {
return acc;
}
const fontFamily = getInstalledFontFamilyByNameFontFamily(
installedFontFamilies,
fontFamilyWithFontFaceToInstall.slug
);
if ( ! fontFamily ) {
return {
...acc,
fontFamiliesWithFontFacesToInstall: [
...acc.fontFamiliesWithFontFacesToInstall,
fontFamilyWithFontFaceToInstall,
],
};
}
const fontFace = fontFamily.font_face.filter( ( { fontWeight } ) =>
fontData.fontWeights.includes( fontWeight )
);
return {
...acc,
fontFacesToInstall: [
...acc.fontFacesToInstall,
...fontFace.map( ( face ) => ( {
...face,
fontFamilyId: fontFamily.id,
} ) ),
],
};
},
{
fontFamiliesWithFontFacesToInstall: [],
fontFacesToInstall: [],
} as {
fontFamiliesWithFontFacesToInstall: Array< FontFamily >;
fontFacesToInstall: Array<
FontFace & {
fontFamilyId: number;
}
>;
}
);
};
export const installFontFamily = ( data: FontFamily ) => {
const config = {
path: '/wp/v2/font-families',
method: 'POST',
data: {
font_family_settings: JSON.stringify( {
name: data.name,
slug: data.slug,
fontFamily: data.fontFamily,
preview: data.preview,
} ),
},
};
return apiFetch< {
id: number;
font_family_settings: string;
} >( config );
};
async function downloadFontFaceAssets( src: string ) {
try {
const fontBlob = await ( await fetch( new Request( src ) ) ).blob();
const fileName = src.split( '/' ).pop() as string;
return new File( [ fontBlob ], fileName, {
type: fontBlob.type,
} );
} catch ( error ) {
throw new Error( `Error downloading font face asset from ${ src }` );
}
}
function makeFontFacesFormData(
fontFaceFile: File,
formData: FormData,
index: number
) {
const fileId = `file-${ index }`;
formData.append( fileId, fontFaceFile, fontFaceFile.name );
return fileId;
}
export const installFontFace = async (
data: FontFace & {
fontFamilyId: number;
},
index: number
) => {
const { fontFamilyId, ...font } = data;
const fontFaceAssets = await downloadFontFaceAssets(
Array.isArray( font.src ) ? font.src[ 0 ] : font.src
);
const formData = new FormData();
const fontFile = await makeFontFacesFormData(
fontFaceAssets,
formData,
index
);
formData.append(
'font_face_settings',
JSON.stringify( { ...font, src: fontFile } )
);
const config = {
path: `/wp/v2/font-families/${ data.fontFamilyId }/font-faces/`,
method: 'POST',
body: formData,
};
return apiFetch( config );
};
export const installFontFamilies = async () => {
const installedFontFamily = ( await resolveSelect(
'core'
).getEntityRecords( 'postType', 'wp_font_family', {
per_page: -1,
} ) ) as Array< {
id: number;
font_faces: Array< number >;
font_family_settings: FontFamily;
} >;
const installedFontFamiliesWithFontFaces = await Promise.all(
installedFontFamily.map( async ( fontFamily ) => {
const fontFaces = await apiFetch< Array< FontFace > >( {
path: `/wp/v2/font-families/${ fontFamily.id }/font-faces`,
method: 'GET',
} );
return {
...fontFamily,
font_face: fontFaces,
};
} )
);
const fontCollection = await apiFetch< FontCollectionResponse >( {
path: `/wp/v2/font-collections/google-fonts`,
method: 'GET',
} );
const { fontFacesToInstall, fontFamiliesWithFontFacesToInstall } =
getFontFamiliesAndFontFaceToInstall(
fontCollection,
installedFontFamiliesWithFontFaces
);
const fontFamiliesWithFontFaceToInstallPromises =
fontFamiliesWithFontFacesToInstall.map( async ( fontFamily ) => {
const fontFamilyResponse = await installFontFamily( fontFamily );
return Promise.all(
fontFamily.fontFace.map( async ( fontFace, index ) => {
return installFontFace(
{
...fontFace,
fontFamilyId: fontFamilyResponse.id,
},
index
);
} )
);
} );
const fontFacesToInstallPromises =
fontFacesToInstall.map( installFontFace );
return ( await Promise.all( [
...fontFamiliesWithFontFaceToInstallPromises,
...fontFacesToInstallPromises,
] ) ) as Array<
Array< {
font_face_settings: FontFace;
} >
>;
};

View File

@ -1,10 +1,10 @@
/**
* External dependencies
*/
import { Sender } from 'xstate';
import apiFetch from '@wordpress/api-fetch';
import { resolveSelect, dispatch } from '@wordpress/data';
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
import apiFetch from '@wordpress/api-fetch';
import { dispatch, resolveSelect } from '@wordpress/data';
import { Sender } from 'xstate';
// @ts-expect-error -- No types for this exist yet.
// eslint-disable-next-line @woocommerce/dependency-group
import { mergeBaseAndUserConfigs } from '@wordpress/edit-site/build-module/components/global-styles/global-styles-provider';
@ -15,24 +15,18 @@ import { store as coreStore } from '@wordpress/core-data';
/**
* Internal dependencies
*/
import { updateTemplate } from '../data/actions';
import { HOMEPAGE_TEMPLATES } from '../data/homepageTemplates';
import { installAndActivateTheme as setTheme } from '../data/service';
import { THEME_SLUG } from '../data/constants';
import { FontFace, FontFamily } from '../types/font';
import {
FontCollectionResponse,
installFontFace,
installFontFamily,
getFontFamiliesAndFontFaceToInstall,
} from './fonts';
import { COLOR_PALETTES } from '../assembler-hub/sidebar/global-styles/color-palette-variations/constants';
import {
FONT_PAIRINGS_WHEN_AI_IS_OFFLINE,
FONT_PAIRINGS_WHEN_USER_DID_NOT_ALLOW_TRACKING,
} from '../assembler-hub/sidebar/global-styles/font-pairing-variations/constants';
import { DesignWithoutAIStateMachineContext, Theme } from './types';
import { updateTemplate } from '../data/actions';
import { THEME_SLUG } from '../data/constants';
import { HOMEPAGE_TEMPLATES } from '../data/homepageTemplates';
import { installAndActivateTheme as setTheme } from '../data/service';
import { trackEvent } from '../tracking';
import { DesignWithoutAIStateMachineContext, Theme } from './types';
import { installFontFamilies as installDefaultFontFamilies } from '../assembler-hub/utils/fonts';
const assembleSite = async () => {
await updateTemplate( {
@ -179,66 +173,7 @@ const installFontFamilies = async () => {
}
try {
const installedFontFamily = ( await resolveSelect(
'core'
).getEntityRecords( 'postType', 'wp_font_family', {
per_page: -1,
} ) ) as Array< {
id: number;
font_faces: Array< number >;
font_family_settings: FontFamily;
} >;
const installedFontFamiliesWithFontFaces = await Promise.all(
installedFontFamily.map( async ( fontFamily ) => {
const fontFaces = await apiFetch< Array< FontFace > >( {
path: `/wp/v2/font-families/${ fontFamily.id }/font-faces`,
method: 'GET',
} );
return {
...fontFamily,
font_face: fontFaces,
};
} )
);
const fontCollection = await apiFetch< FontCollectionResponse >( {
path: `/wp/v2/font-collections/google-fonts`,
method: 'GET',
} );
const { fontFacesToInstall, fontFamiliesWithFontFacesToInstall } =
getFontFamiliesAndFontFaceToInstall(
fontCollection,
installedFontFamiliesWithFontFaces
);
const fontFamiliesWithFontFaceToInstallPromises =
fontFamiliesWithFontFacesToInstall.map( async ( fontFamily ) => {
const fontFamilyResponse = await installFontFamily(
fontFamily
);
return Promise.all(
fontFamily.fontFace.map( async ( fontFace, index ) => {
installFontFace(
{
...fontFace,
fontFamilyId: fontFamilyResponse.id,
},
index
);
} )
);
} );
const fontFacesToInstallPromises =
fontFacesToInstall.map( installFontFace );
await Promise.all( [
...fontFamiliesWithFontFaceToInstallPromises,
...fontFacesToInstallPromises,
] );
await installDefaultFontFamilies();
} catch ( error ) {
throw error;
}

View File

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

View File

@ -57,6 +57,13 @@ test.describe( 'Assembler -> Font Picker', { 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' );
}
@ -78,6 +85,13 @@ test.describe( 'Assembler -> Font Picker', { tag: '@gutenberg' }, () => {
'no'
);
await setOption(
request,
baseURL,
'woocommerce_allow_tracking',
'no'
);
// Reset theme back to default.
await activateTheme( DEFAULT_THEME );
} catch ( error ) {
@ -209,4 +223,26 @@ test.describe( 'Assembler -> Font Picker', { tag: '@gutenberg' }, () => {
expect( isPrimaryFontUsed ).toBe( true );
expect( isSecondaryFontUsed ).toBe( true );
} );
test( 'Clicking opt-in new fonts should be available', async ( {
pageObject,
} ) => {
const assembler = await pageObject.getAssembler();
await assembler.getByText( 'Usage tracking' ).click();
await expect(
assembler.getByText( 'Access more fonts' )
).toBeVisible();
await assembler.getByRole( 'button', { name: 'Opt in' } ).click();
await assembler
.getByText( 'Access more fonts' )
.waitFor( { state: 'hidden' } );
const fontPickers = assembler.locator(
'.woocommerce-customize-store_global-styles-variations_item'
);
await expect( fontPickers ).toHaveCount( 10 );
} );
} );