Merge branch 'trunk' into e2e/update-order-to-cancelled
This commit is contained in:
commit
4c97b1bf17
|
@ -12,7 +12,7 @@ import { parse } from '@wordpress/blocks';
|
|||
import { usePatterns, Pattern, PatternWithBlocks } from './use-patterns';
|
||||
|
||||
// TODO: It might be better to create an API endpoint to get the templates.
|
||||
const LARGE_BUSINESS_TEMPLATES = {
|
||||
export const LARGE_BUSINESS_TEMPLATES = {
|
||||
template1: [
|
||||
'a8c/cover-image-with-left-aligned-call-to-action',
|
||||
'woocommerce-blocks/featured-products-5-item-grid',
|
||||
|
@ -42,7 +42,7 @@ const LARGE_BUSINESS_TEMPLATES = {
|
|||
],
|
||||
};
|
||||
|
||||
const SMALL_MEDIUM_BUSINESS_TEMPLATES = {
|
||||
export const SMALL_MEDIUM_BUSINESS_TEMPLATES = {
|
||||
template1: [
|
||||
'woocommerce-blocks/featured-products-fresh-and-tasty',
|
||||
'woocommerce-blocks/testimonials-single',
|
||||
|
@ -68,7 +68,16 @@ const SMALL_MEDIUM_BUSINESS_TEMPLATES = {
|
|||
],
|
||||
};
|
||||
|
||||
const getTemplatePatterns = (
|
||||
const TEMPLATES = {
|
||||
template1: LARGE_BUSINESS_TEMPLATES.template1,
|
||||
template2: LARGE_BUSINESS_TEMPLATES.template2,
|
||||
template3: LARGE_BUSINESS_TEMPLATES.template3,
|
||||
template4: SMALL_MEDIUM_BUSINESS_TEMPLATES.template1,
|
||||
template5: SMALL_MEDIUM_BUSINESS_TEMPLATES.template2,
|
||||
template6: SMALL_MEDIUM_BUSINESS_TEMPLATES.template3,
|
||||
};
|
||||
|
||||
export const getTemplatePatterns = (
|
||||
template: string[],
|
||||
patternsByName: Record< string, Pattern >
|
||||
) =>
|
||||
|
@ -88,27 +97,26 @@ const getTemplatePatterns = (
|
|||
} )
|
||||
.filter( ( pattern ) => pattern !== null ) as PatternWithBlocks[];
|
||||
|
||||
export const patternsToNameMap = ( blockPatterns: Pattern[] ) =>
|
||||
blockPatterns.reduce(
|
||||
( acc: Record< string, Pattern >, pattern: Pattern ) => {
|
||||
acc[ pattern.name ] = pattern;
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
export const useHomeTemplates = () => {
|
||||
// TODO: Get businessType from option
|
||||
const businessType = 'SMB' as string;
|
||||
const { blockPatterns, isLoading } = usePatterns();
|
||||
|
||||
const patternsByName = useMemo( () => {
|
||||
return blockPatterns.reduce(
|
||||
( acc: Record< string, Pattern >, pattern: Pattern ) => {
|
||||
acc[ pattern.name ] = pattern;
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
}, [ blockPatterns ] );
|
||||
const patternsByName = useMemo(
|
||||
() => patternsToNameMap( blockPatterns ),
|
||||
[ blockPatterns ]
|
||||
);
|
||||
|
||||
const homeTemplates = useMemo( () => {
|
||||
if ( isLoading ) return {};
|
||||
const recommendedTemplates =
|
||||
businessType === 'SMB'
|
||||
? SMALL_MEDIUM_BUSINESS_TEMPLATES
|
||||
: LARGE_BUSINESS_TEMPLATES;
|
||||
const recommendedTemplates = TEMPLATES;
|
||||
|
||||
return Object.entries( recommendedTemplates ).reduce(
|
||||
(
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -21,7 +21,8 @@ export const ColorPalette = () => {
|
|||
gap={ 4 }
|
||||
className="woocommerce-customize-store_color-palette-container"
|
||||
>
|
||||
{ COLOR_PALETTES.map( ( variation, index ) => (
|
||||
{ /* TODO: Show 9 colors based on the AI recommendation */ }
|
||||
{ COLOR_PALETTES.slice( 0, 9 ).map( ( variation, index ) => (
|
||||
<VariationContainer key={ index } variation={ variation }>
|
||||
<ColorPaletteVariationPreview title={ variation?.title } />
|
||||
</VariationContainer>
|
||||
|
|
|
@ -14,6 +14,8 @@ import {
|
|||
designWithAiStateMachineEvents,
|
||||
FontPairing,
|
||||
LookAndToneCompletionResponse,
|
||||
Header,
|
||||
Footer,
|
||||
} from './types';
|
||||
import { aiWizardClosedBeforeCompletionEvent } from './events';
|
||||
import {
|
||||
|
@ -110,6 +112,42 @@ const assignFontPairing = assign<
|
|||
},
|
||||
} );
|
||||
|
||||
const assignHeader = assign<
|
||||
designWithAiStateMachineContext,
|
||||
designWithAiStateMachineEvents
|
||||
>( {
|
||||
aiSuggestions: ( context, event: unknown ) => {
|
||||
return {
|
||||
...context.aiSuggestions,
|
||||
header: (
|
||||
event as {
|
||||
data: {
|
||||
response: Header;
|
||||
};
|
||||
}
|
||||
).data.response.slug,
|
||||
};
|
||||
},
|
||||
} );
|
||||
|
||||
const assignFooter = assign<
|
||||
designWithAiStateMachineContext,
|
||||
designWithAiStateMachineEvents
|
||||
>( {
|
||||
aiSuggestions: ( context, event: unknown ) => {
|
||||
return {
|
||||
...context.aiSuggestions,
|
||||
footer: (
|
||||
event as {
|
||||
data: {
|
||||
response: Footer;
|
||||
};
|
||||
}
|
||||
).data.response.slug,
|
||||
};
|
||||
},
|
||||
} );
|
||||
|
||||
const logAIAPIRequestError = () => {
|
||||
// log AI API request error
|
||||
// eslint-disable-next-line no-console
|
||||
|
@ -178,6 +216,8 @@ export const actions = {
|
|||
assignLookAndTone,
|
||||
assignDefaultColorPalette,
|
||||
assignFontPairing,
|
||||
assignHeader,
|
||||
assignFooter,
|
||||
logAIAPIRequestError,
|
||||
updateQueryStep,
|
||||
recordTracksStepViewed,
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { z } from 'zod';
|
||||
|
||||
const footerChoices = [
|
||||
{
|
||||
slug: 'woocommerce-blocks/footer-simple-menu-and-cart',
|
||||
label: 'Footer with Simple Menu and Cart',
|
||||
},
|
||||
{
|
||||
slug: 'woocommerce-blocks/footer-with-3-menus',
|
||||
label: 'Footer with 3 Menus',
|
||||
},
|
||||
{
|
||||
slug: 'woocommerce-blocks/footer-large',
|
||||
label: 'Large Footer',
|
||||
},
|
||||
];
|
||||
|
||||
const allowedFooter: string[] = footerChoices.map( ( footer ) => footer.slug );
|
||||
|
||||
export const footerValidator = z.object( {
|
||||
slug: z.string().refine( ( slug ) => allowedFooter.includes( slug ), {
|
||||
message: 'Footer not part of allowed list',
|
||||
} ),
|
||||
} );
|
||||
|
||||
export const defaultFooter = {
|
||||
queryId: 'default_footer',
|
||||
|
||||
// make sure version is updated every time the prompt is changed
|
||||
version: '2023-09-19',
|
||||
prompt: ( businessDescription: string, look: string, tone: string ) => {
|
||||
return `
|
||||
You are a WordPress theme expert. Analyse the following store description, merchant's chosen look and tone, and determine the most appropriate footer.
|
||||
Respond only with one footer and only its JSON.
|
||||
|
||||
Chosen look and tone: ${ look } look, ${ tone } tone.
|
||||
Business description: ${ businessDescription }
|
||||
|
||||
Footer to choose from:
|
||||
${ JSON.stringify( footerChoices ) }
|
||||
`;
|
||||
},
|
||||
responseValidation: footerValidator.parse,
|
||||
};
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { z } from 'zod';
|
||||
|
||||
const headerChoices = [
|
||||
{
|
||||
slug: 'woocommerce-blocks/header-essential',
|
||||
label: 'Essential Header',
|
||||
},
|
||||
{
|
||||
slug: 'woocommerce-blocks/header-centered-menu-with-search',
|
||||
label: 'Centered Menu with search Header',
|
||||
},
|
||||
{
|
||||
slug: 'woocommerce-blocks/header-minimal',
|
||||
label: 'Minimal Header',
|
||||
},
|
||||
{
|
||||
slug: 'woocommerce-blocks/header-large',
|
||||
label: 'Large Header',
|
||||
},
|
||||
];
|
||||
|
||||
const allowedHeaders: string[] = headerChoices.map( ( header ) => header.slug );
|
||||
|
||||
export const headerValidator = z.object( {
|
||||
slug: z.string().refine( ( slug ) => allowedHeaders.includes( slug ), {
|
||||
message: 'Header not part of allowed list',
|
||||
} ),
|
||||
} );
|
||||
|
||||
export const defaultHeader = {
|
||||
queryId: 'default_header',
|
||||
|
||||
// make sure version is updated every time the prompt is changed
|
||||
version: '2023-09-19',
|
||||
prompt: ( businessDescription: string, look: string, tone: string ) => {
|
||||
return `
|
||||
You are a WordPress theme expert. Analyse the following store description, merchant's chosen look and tone, and determine the most appropriate header.
|
||||
Respond only with one header and only its JSON.
|
||||
|
||||
Chosen look and tone: ${ look } look, ${ tone } tone.
|
||||
Business description: ${ businessDescription }
|
||||
|
||||
Headers to choose from:
|
||||
${ JSON.stringify( headerChoices ) }
|
||||
`;
|
||||
},
|
||||
responseValidation: headerValidator.parse,
|
||||
};
|
|
@ -1,3 +1,5 @@
|
|||
export * from './colorChoices';
|
||||
export * from './lookAndTone';
|
||||
export * from './fontPairings';
|
||||
export * from './header';
|
||||
export * from './footer';
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { footerValidator } from '..';
|
||||
|
||||
describe( 'footerValidator', () => {
|
||||
it( 'should validate when footer is part of the allowed list', () => {
|
||||
const validFooter = { slug: 'woocommerce-blocks/footer-large' };
|
||||
expect( () => footerValidator.parse( validFooter ) ).not.toThrow();
|
||||
} );
|
||||
|
||||
it( 'should not validate when footer is not part of the allowed list', () => {
|
||||
const invalidFooter = {
|
||||
slug: 'woocommerce-blocks/footer-large-invalid',
|
||||
};
|
||||
expect( () => footerValidator.parse( invalidFooter ) )
|
||||
.toThrowErrorMatchingInlineSnapshot( `
|
||||
"[
|
||||
{
|
||||
\\"code\\": \\"custom\\",
|
||||
\\"message\\": \\"Footer not part of allowed list\\",
|
||||
\\"path\\": [
|
||||
\\"slug\\"
|
||||
]
|
||||
}
|
||||
]"
|
||||
` );
|
||||
} );
|
||||
|
||||
it( 'should not validate when slug is not a string', () => {
|
||||
const invalidType = { slug: 123 };
|
||||
expect( () => footerValidator.parse( invalidType ) )
|
||||
.toThrowErrorMatchingInlineSnapshot( `
|
||||
"[
|
||||
{
|
||||
\\"code\\": \\"invalid_type\\",
|
||||
\\"expected\\": \\"string\\",
|
||||
\\"received\\": \\"number\\",
|
||||
\\"path\\": [
|
||||
\\"slug\\"
|
||||
],
|
||||
\\"message\\": \\"Expected string, received number\\"
|
||||
}
|
||||
]"
|
||||
` );
|
||||
} );
|
||||
} );
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { headerValidator } from '..';
|
||||
|
||||
describe( 'headerValidator', () => {
|
||||
it( 'should validate when header is part of the allowed list', () => {
|
||||
const validHeader = { slug: 'woocommerce-blocks/header-large' };
|
||||
expect( () => headerValidator.parse( validHeader ) ).not.toThrow();
|
||||
} );
|
||||
|
||||
it( 'should not validate when header is not part of the allowed list', () => {
|
||||
const invalidHeader = {
|
||||
slug: 'woocommerce-blocks/header-large-invalid',
|
||||
};
|
||||
expect( () => headerValidator.parse( invalidHeader ) )
|
||||
.toThrowErrorMatchingInlineSnapshot( `
|
||||
"[
|
||||
{
|
||||
\\"code\\": \\"custom\\",
|
||||
\\"message\\": \\"Header not part of allowed list\\",
|
||||
\\"path\\": [
|
||||
\\"slug\\"
|
||||
]
|
||||
}
|
||||
]"
|
||||
` );
|
||||
} );
|
||||
|
||||
it( 'should not validate when slug is not a string', () => {
|
||||
const invalidType = { slug: 123 };
|
||||
expect( () => headerValidator.parse( invalidType ) )
|
||||
.toThrowErrorMatchingInlineSnapshot( `
|
||||
"[
|
||||
{
|
||||
\\"code\\": \\"invalid_type\\",
|
||||
\\"expected\\": \\"string\\",
|
||||
\\"received\\": \\"number\\",
|
||||
\\"path\\": [
|
||||
\\"slug\\"
|
||||
],
|
||||
\\"message\\": \\"Expected string, received number\\"
|
||||
}
|
||||
]"
|
||||
` );
|
||||
} );
|
||||
} );
|
|
@ -1,16 +1,32 @@
|
|||
/* eslint-disable @woocommerce/dependency-group */
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __experimentalRequestJetpackToken as requestJetpackToken } from '@woocommerce/ai';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
|
||||
import { Sender, assign, createMachine } from 'xstate';
|
||||
import { dispatch, resolveSelect } from '@wordpress/data';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { store as coreStore } from '@wordpress/core-data';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { mergeBaseAndUserConfigs } from '@wordpress/edit-site/build-module/components/global-styles/global-styles-provider';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { designWithAiStateMachineContext } from './types';
|
||||
import { lookAndTone } from './prompts';
|
||||
import { FONT_PAIRINGS } from '../assembler-hub/sidebar/global-styles/font-pairing-variations/constants';
|
||||
import { COLOR_PALETTES } from '../assembler-hub/sidebar/global-styles/color-palette-variations/constants';
|
||||
import {
|
||||
patternsToNameMap,
|
||||
getTemplatePatterns,
|
||||
LARGE_BUSINESS_TEMPLATES,
|
||||
SMALL_MEDIUM_BUSINESS_TEMPLATES,
|
||||
} from '../assembler-hub/hooks/use-home-templates';
|
||||
|
||||
const browserPopstateHandler =
|
||||
() => ( sendBack: Sender< { type: 'EXTERNAL_URL_UPDATE' } > ) => {
|
||||
|
@ -192,8 +208,185 @@ export const queryAiEndpoint = createMachine(
|
|||
}
|
||||
);
|
||||
|
||||
export const updateStorePatterns = async (
|
||||
context: designWithAiStateMachineContext
|
||||
) => {
|
||||
try {
|
||||
// TODO: Probably move this to a more appropriate place with a check. We should set this when the user granted permissions during the onboarding phase.
|
||||
await dispatch( OPTIONS_STORE_NAME ).updateOptions( {
|
||||
woocommerce_blocks_allow_ai_connection: true,
|
||||
} );
|
||||
|
||||
await apiFetch( {
|
||||
path: '/wc/store/patterns',
|
||||
method: 'POST',
|
||||
data: {
|
||||
business_description:
|
||||
context.businessInfoDescription.descriptionText,
|
||||
},
|
||||
} );
|
||||
} catch ( error ) {
|
||||
recordEvent( 'customize_your_store_update_store_pattern_api_error', {
|
||||
error: error instanceof Error ? error.message : 'unknown',
|
||||
} );
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Update the current global styles of theme
|
||||
const updateGlobalStyles = async ( {
|
||||
colorPaletteName = COLOR_PALETTES[ 0 ].title,
|
||||
fontPairingName = FONT_PAIRINGS[ 0 ].title,
|
||||
}: {
|
||||
colorPaletteName: string;
|
||||
fontPairingName: string;
|
||||
} ) => {
|
||||
const colorPalette = COLOR_PALETTES.find(
|
||||
( palette ) => palette.title === colorPaletteName
|
||||
);
|
||||
const fontPairing = FONT_PAIRINGS.find(
|
||||
( pairing ) => pairing.title === fontPairingName
|
||||
);
|
||||
|
||||
const globalStylesId = await resolveSelect(
|
||||
coreStore
|
||||
// @ts-ignore No types for this exist yet.
|
||||
).__experimentalGetCurrentGlobalStylesId();
|
||||
|
||||
// @ts-ignore No types for this exist yet.
|
||||
const { saveEntityRecord } = dispatch( coreStore );
|
||||
|
||||
await saveEntityRecord(
|
||||
'root',
|
||||
'globalStyles',
|
||||
{
|
||||
id: globalStylesId,
|
||||
styles: mergeBaseAndUserConfigs(
|
||||
colorPalette?.styles || {},
|
||||
fontPairing?.styles || {}
|
||||
),
|
||||
settings: mergeBaseAndUserConfigs(
|
||||
colorPalette?.settings || {},
|
||||
fontPairing?.settings || {}
|
||||
),
|
||||
},
|
||||
{
|
||||
throwOnError: true,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Update the current theme template
|
||||
const updateTemplate = async ( {
|
||||
headerSlug,
|
||||
businessSize,
|
||||
homepageTemplateId,
|
||||
footerSlug,
|
||||
}: {
|
||||
headerSlug: string;
|
||||
businessSize: 'SMB' | 'LB';
|
||||
homepageTemplateId:
|
||||
| keyof typeof SMALL_MEDIUM_BUSINESS_TEMPLATES
|
||||
| keyof typeof LARGE_BUSINESS_TEMPLATES;
|
||||
footerSlug: string;
|
||||
} ) => {
|
||||
const patterns = ( await resolveSelect(
|
||||
coreStore
|
||||
// @ts-ignore No types for this exist yet.
|
||||
).getBlockPatterns() ) as Pattern[];
|
||||
|
||||
const patternsByName = patternsToNameMap( patterns );
|
||||
|
||||
const headerPattern = patternsByName[ headerSlug ];
|
||||
const footerPattern = patternsByName[ footerSlug ];
|
||||
|
||||
const homepageTemplate = getTemplatePatterns(
|
||||
businessSize === 'SMB'
|
||||
? SMALL_MEDIUM_BUSINESS_TEMPLATES[ homepageTemplateId ]
|
||||
: LARGE_BUSINESS_TEMPLATES[ homepageTemplateId ],
|
||||
patternsByName
|
||||
);
|
||||
|
||||
const content = [ headerPattern, ...homepageTemplate, footerPattern ]
|
||||
.filter( Boolean )
|
||||
.map( ( pattern ) => pattern.content )
|
||||
.join( '\n\n' );
|
||||
|
||||
const currentTemplate = await resolveSelect(
|
||||
coreStore
|
||||
// @ts-ignore No types for this exist yet.
|
||||
).__experimentalGetTemplateForLink( '/' );
|
||||
|
||||
// @ts-ignore No types for this exist yet.
|
||||
const { saveEntityRecord } = dispatch( coreStore );
|
||||
|
||||
await saveEntityRecord(
|
||||
'postType',
|
||||
currentTemplate.type,
|
||||
{
|
||||
id: currentTemplate.id,
|
||||
content,
|
||||
},
|
||||
{
|
||||
throwOnError: true,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const assembleSite = async (
|
||||
context: designWithAiStateMachineContext
|
||||
) => {
|
||||
try {
|
||||
await updateGlobalStyles( {
|
||||
colorPaletteName: context.aiSuggestions.defaultColorPalette.default,
|
||||
fontPairingName: context.aiSuggestions.fontPairing,
|
||||
} );
|
||||
recordEvent( 'customize_your_store_ai_update_global_styles_success' );
|
||||
} catch ( error ) {
|
||||
// TODO handle error
|
||||
// eslint-disable-next-line no-console
|
||||
console.error( error );
|
||||
recordEvent(
|
||||
'customize_your_store_ai_update_global_styles_response_error',
|
||||
{
|
||||
error: error instanceof Error ? error.message : 'unknown',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await updateTemplate( {
|
||||
headerSlug: context.aiSuggestions.header,
|
||||
// TODO: Get from context
|
||||
businessSize: 'SMB',
|
||||
homepageTemplateId: 'template1',
|
||||
footerSlug: context.aiSuggestions.footer,
|
||||
} );
|
||||
recordEvent( 'customize_your_store_ai_update_template_success' );
|
||||
} catch ( error ) {
|
||||
// TODO handle error
|
||||
// eslint-disable-next-line no-console
|
||||
console.error( error );
|
||||
recordEvent( 'customize_your_store_ai_update_template_response_error', {
|
||||
error: error instanceof Error ? error.message : 'unknown',
|
||||
} );
|
||||
}
|
||||
|
||||
// @ts-ignore No types for this exist yet.
|
||||
const { invalidateResolutionForStoreSelector } = dispatch( coreStore );
|
||||
|
||||
// Invalid the selectors so that the new template/style are used in assembler hub.
|
||||
invalidateResolutionForStoreSelector( 'getEntityRecord' );
|
||||
invalidateResolutionForStoreSelector(
|
||||
'__experimentalGetCurrentGlobalStylesId'
|
||||
);
|
||||
invalidateResolutionForStoreSelector( '__experimentalGetTemplateForLink' );
|
||||
};
|
||||
|
||||
export const services = {
|
||||
getLookAndTone,
|
||||
browserPopstateHandler,
|
||||
queryAiEndpoint,
|
||||
assembleSite,
|
||||
updateStorePatterns,
|
||||
};
|
||||
|
|
|
@ -11,6 +11,8 @@ import {
|
|||
designWithAiStateMachineContext,
|
||||
designWithAiStateMachineEvents,
|
||||
FontPairing,
|
||||
Header,
|
||||
Footer,
|
||||
ColorPaletteResponse,
|
||||
} from './types';
|
||||
import {
|
||||
|
@ -21,7 +23,12 @@ import {
|
|||
} from './pages';
|
||||
import { actions } from './actions';
|
||||
import { services } from './services';
|
||||
import { defaultColorPalette, fontPairings } from './prompts';
|
||||
import {
|
||||
defaultColorPalette,
|
||||
fontPairings,
|
||||
defaultHeader,
|
||||
defaultFooter,
|
||||
} from './prompts';
|
||||
|
||||
export const hasStepInUrl = (
|
||||
_ctx: unknown,
|
||||
|
@ -72,6 +79,8 @@ export const designWithAiStateMachineDefinition = createMachine(
|
|||
aiSuggestions: {
|
||||
defaultColorPalette: {} as ColorPaletteResponse,
|
||||
fontPairing: '' as FontPairing[ 'pair_name' ],
|
||||
header: '' as Header[ 'slug' ],
|
||||
footer: '' as Footer[ 'slug' ],
|
||||
},
|
||||
},
|
||||
initial: 'navigate',
|
||||
|
@ -273,48 +282,158 @@ export const designWithAiStateMachineDefinition = createMachine(
|
|||
type: 'parallel',
|
||||
states: {
|
||||
chooseColorPairing: {
|
||||
invoke: {
|
||||
src: 'queryAiEndpoint',
|
||||
data: ( context ) => {
|
||||
return {
|
||||
...defaultColorPalette,
|
||||
prompt: defaultColorPalette.prompt(
|
||||
context.businessInfoDescription
|
||||
.descriptionText,
|
||||
context.lookAndFeel.choice,
|
||||
context.toneOfVoice.choice
|
||||
),
|
||||
};
|
||||
},
|
||||
onDone: {
|
||||
actions: [
|
||||
'assignDefaultColorPalette',
|
||||
],
|
||||
initial: 'pending',
|
||||
states: {
|
||||
pending: {
|
||||
invoke: {
|
||||
src: 'queryAiEndpoint',
|
||||
data: ( context ) => {
|
||||
return {
|
||||
...defaultColorPalette,
|
||||
prompt: defaultColorPalette.prompt(
|
||||
context
|
||||
.businessInfoDescription
|
||||
.descriptionText,
|
||||
context.lookAndFeel
|
||||
.choice,
|
||||
context.toneOfVoice
|
||||
.choice
|
||||
),
|
||||
};
|
||||
},
|
||||
onDone: {
|
||||
actions: [
|
||||
'assignDefaultColorPalette',
|
||||
],
|
||||
target: 'success',
|
||||
},
|
||||
},
|
||||
},
|
||||
success: { type: 'final' },
|
||||
},
|
||||
},
|
||||
chooseFontPairing: {
|
||||
invoke: {
|
||||
src: 'queryAiEndpoint',
|
||||
data: ( context ) => {
|
||||
return {
|
||||
...fontPairings,
|
||||
prompt: fontPairings.prompt(
|
||||
context.businessInfoDescription
|
||||
.descriptionText,
|
||||
context.lookAndFeel.choice,
|
||||
context.toneOfVoice.choice
|
||||
),
|
||||
};
|
||||
initial: 'pending',
|
||||
states: {
|
||||
pending: {
|
||||
invoke: {
|
||||
src: 'queryAiEndpoint',
|
||||
data: ( context ) => {
|
||||
return {
|
||||
...fontPairings,
|
||||
prompt: fontPairings.prompt(
|
||||
context
|
||||
.businessInfoDescription
|
||||
.descriptionText,
|
||||
context.lookAndFeel
|
||||
.choice,
|
||||
context.toneOfVoice
|
||||
.choice
|
||||
),
|
||||
};
|
||||
},
|
||||
onDone: {
|
||||
actions: [
|
||||
'assignFontPairing',
|
||||
],
|
||||
target: 'success',
|
||||
},
|
||||
},
|
||||
},
|
||||
onDone: {
|
||||
actions: [ 'assignFontPairing' ],
|
||||
success: { type: 'final' },
|
||||
},
|
||||
},
|
||||
chooseHeader: {
|
||||
initial: 'pending',
|
||||
states: {
|
||||
pending: {
|
||||
invoke: {
|
||||
src: 'queryAiEndpoint',
|
||||
data: ( context ) => {
|
||||
return {
|
||||
...defaultHeader,
|
||||
prompt: defaultHeader.prompt(
|
||||
context
|
||||
.businessInfoDescription
|
||||
.descriptionText,
|
||||
context.lookAndFeel
|
||||
.choice,
|
||||
context.toneOfVoice
|
||||
.choice
|
||||
),
|
||||
};
|
||||
},
|
||||
onDone: {
|
||||
actions: [ 'assignHeader' ],
|
||||
target: 'success',
|
||||
},
|
||||
},
|
||||
},
|
||||
success: { type: 'final' },
|
||||
},
|
||||
},
|
||||
chooseFooter: {
|
||||
initial: 'pending',
|
||||
states: {
|
||||
pending: {
|
||||
invoke: {
|
||||
src: 'queryAiEndpoint',
|
||||
data: ( context ) => {
|
||||
return {
|
||||
...defaultFooter,
|
||||
prompt: defaultFooter.prompt(
|
||||
context
|
||||
.businessInfoDescription
|
||||
.descriptionText,
|
||||
context.lookAndFeel
|
||||
.choice,
|
||||
context.toneOfVoice
|
||||
.choice
|
||||
),
|
||||
};
|
||||
},
|
||||
onDone: {
|
||||
actions: [ 'assignFooter' ],
|
||||
target: 'success',
|
||||
},
|
||||
},
|
||||
},
|
||||
success: { type: 'final' },
|
||||
},
|
||||
},
|
||||
updateStorePatterns: {
|
||||
initial: 'pending',
|
||||
states: {
|
||||
pending: {
|
||||
invoke: {
|
||||
src: 'updateStorePatterns',
|
||||
onDone: {
|
||||
target: 'success',
|
||||
},
|
||||
onError: {
|
||||
// TODO: handle error
|
||||
target: 'success',
|
||||
},
|
||||
},
|
||||
},
|
||||
success: { type: 'final' },
|
||||
},
|
||||
},
|
||||
},
|
||||
onDone: 'postApiCallLoader',
|
||||
},
|
||||
postApiCallLoader: {
|
||||
invoke: {
|
||||
src: 'assembleSite',
|
||||
onDone: {
|
||||
actions: [
|
||||
sendParent( () => ( {
|
||||
type: 'THEME_SUGGESTED',
|
||||
} ) ),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
postApiCallLoader: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -19,6 +19,13 @@ jest.mock( '@woocommerce/ai', () => ( {
|
|||
|
||||
jest.mock( '@wordpress/api-fetch', () => jest.fn() );
|
||||
|
||||
jest.mock(
|
||||
'@wordpress/edit-site/build-module/components/global-styles/global-styles-provider',
|
||||
() => ( {
|
||||
mergeBaseAndUserConfigs: jest.fn(),
|
||||
} )
|
||||
);
|
||||
|
||||
describe( 'getCompletion', () => {
|
||||
beforeEach( () => {
|
||||
jest.clearAllMocks();
|
||||
|
|
|
@ -7,8 +7,10 @@ import { z } from 'zod';
|
|||
*/
|
||||
import {
|
||||
colorPaletteValidator,
|
||||
colorPaletteResponseValidator,
|
||||
fontChoiceValidator,
|
||||
headerValidator,
|
||||
footerValidator,
|
||||
colorPaletteResponseValidator,
|
||||
} from './prompts';
|
||||
|
||||
export type designWithAiStateMachineContext = {
|
||||
|
@ -24,6 +26,8 @@ export type designWithAiStateMachineContext = {
|
|||
aiSuggestions: {
|
||||
defaultColorPalette: ColorPaletteResponse;
|
||||
fontPairing: FontPairing[ 'pair_name' ];
|
||||
header: Header[ 'slug' ];
|
||||
footer: Footer[ 'slug' ];
|
||||
};
|
||||
// If we require more data from options, previously provided core profiler details,
|
||||
// we can retrieve them in preBusinessInfoDescription and then assign them here
|
||||
|
@ -60,3 +64,7 @@ export type ColorPaletteResponse = z.infer<
|
|||
>;
|
||||
|
||||
export type FontPairing = z.infer< typeof fontChoiceValidator >;
|
||||
|
||||
export type Header = z.infer< typeof headerValidator >;
|
||||
|
||||
export type Footer = z.infer< typeof footerValidator >;
|
||||
|
|
|
@ -4,7 +4,11 @@
|
|||
import { Sender, createMachine } from 'xstate';
|
||||
import { useEffect, useMemo, useState } from '@wordpress/element';
|
||||
import { useMachine, useSelector } from '@xstate/react';
|
||||
import { getQuery, updateQueryString } from '@woocommerce/navigation';
|
||||
import {
|
||||
getNewPath,
|
||||
getQuery,
|
||||
updateQueryString,
|
||||
} from '@woocommerce/navigation';
|
||||
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
|
||||
import { dispatch } from '@wordpress/data';
|
||||
|
||||
|
@ -59,6 +63,10 @@ const updateQueryStep = (
|
|||
}
|
||||
};
|
||||
|
||||
const redirectToWooHome = () => {
|
||||
window.location.href = getNewPath( {}, '/', {} );
|
||||
};
|
||||
|
||||
const markTaskComplete = async () => {
|
||||
return dispatch( OPTIONS_STORE_NAME ).updateOptions( {
|
||||
woocommerce_admin_customize_store_completed: 'yes',
|
||||
|
@ -78,6 +86,7 @@ const browserPopstateHandler =
|
|||
|
||||
export const machineActions = {
|
||||
updateQueryStep,
|
||||
redirectToWooHome,
|
||||
};
|
||||
|
||||
export const customizeStoreStateMachineActions = {
|
||||
|
@ -184,7 +193,7 @@ export const customizeStoreStateMachineDefinition = createMachine( {
|
|||
target: 'assemblerHub',
|
||||
},
|
||||
CLICKED_ON_BREADCRUMB: {
|
||||
target: 'backToHomescreen',
|
||||
actions: 'redirectToWooHome',
|
||||
},
|
||||
SELECTED_NEW_THEME: {
|
||||
target: 'appearanceTask',
|
||||
|
@ -263,11 +272,10 @@ export const customizeStoreStateMachineDefinition = createMachine( {
|
|||
},
|
||||
on: {
|
||||
GO_BACK_TO_HOME: {
|
||||
target: 'backToHomescreen',
|
||||
actions: 'redirectToWooHome',
|
||||
},
|
||||
},
|
||||
},
|
||||
backToHomescreen: {},
|
||||
appearanceTask: {},
|
||||
},
|
||||
} );
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: fix
|
||||
|
||||
Avoid a fatal error on the order received page if the order ID is not for a valid order.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add tags (or general taxonomy ) block
|
|
@ -1,4 +0,0 @@
|
|||
Significance: major
|
||||
Type: update
|
||||
|
||||
Update the simple product template implementation to use the product form template API.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add support for slug auto generation to the create attribute endpoint
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Update intro screen for the new Customize Your Store task
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add AI wizard business info step for Customize Your Store task
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add sidebar to customize your store task.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add component to Customize Your Store task.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: fix
|
||||
|
||||
Add Variation options section back to the product blocks template
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Create a plugin to enable Variations feature #40027
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add has_price param to the variations REST API query.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add variable product experiment
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Update Remote Inbox Notifications to add in and !in comparison operators for comparing values against arrays
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add Tracks events to Appearance > Themes screen
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Persist CYS AI assembled site
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add after_add_block and after_remove block hooks to the block template API.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add woocommerce_block_template_register action.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Call wc store patterns API to update patterns for CYS
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add customize store assembler hub
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Implement customize store assembler hub - logo feature
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add customize store assembler hub onboarding tour
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add customize store AI wizard call for best colour palette suggestions.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add customize store AI wizard call for color palette suggestion
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Implemented loader design for Customize your store - Design with AI
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add customize store AI wizard call for font pairing suggestion
|
|
@ -1,4 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add customize store - fonts
|
||||
Add cys ai header/footer
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: add
|
||||
|
||||
Add track events to customize store AI wizard
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Added xstate scaffolding for AI Wizard in customize your store feature
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: add
|
||||
|
||||
Add tracks to CYS assembler-hub and hide pages sidebar screen
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add customize store color palettes
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add customize store transitional screen
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: add
|
||||
|
||||
Add track events to customize store transitional page
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Added URL navigation support to customize-store feature
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add a filter to OrdersTableQuery to allow overriding of HPOS queries.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: add
|
||||
|
||||
Add filter woocommerce_hpos_enable_sync_on_read to disable sync on read with HPOS sync enabled.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: major
|
||||
Type: enhancement
|
||||
|
||||
Enable HPOS by default for new installs.
|
|
@ -1,5 +0,0 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
Comment: Update CONTRIBUTING.md file to include instructions on how adding unit, API and E2E tests when applicable.
|
||||
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Made ai completion for look and tone more robust and added tracks
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: add
|
||||
|
||||
Add new e2e test to cover My Account Addresses section
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: add
|
||||
|
||||
Add new e2e test for Shopper My Account Downloads section
|
|
@ -1,5 +0,0 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
Comment: Adds regression test for https://github.com/woocommerce/woocommerce/pull/40221.
|
||||
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: tweak
|
||||
|
||||
Add order property to every block in SimpleProductTemplate
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add ability to remove blocks from templates.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Adds new action hook `woocommerce_pay_order_before_payment` to the `checkout/form-pay.php` template.
|
|
@ -1,5 +0,0 @@
|
|||
Significance: patch
|
||||
Type: add
|
||||
Comment: Updated perf test and workflow for extra site
|
||||
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Fix for a couple of flaky API tests on daily runs
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Run a full reset on API daily test site
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Fixes and enables API test suite to run on daily CI run against alternate host
|
|
@ -1,5 +0,0 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
Comment: Bump PHP version where it was missed in #39820.
|
||||
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Bump required PHP version to 7.4
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: dev
|
||||
|
||||
Add notice to "track inventory" toggle #40011
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: dev
|
||||
|
||||
Add some basic E2E tests for Assembler Hub
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add header customization to the Assembler Hub
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Added documentation for the Core Profiler
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: dev
|
||||
|
||||
Refactored core profiler loader to be more generalizable and moved to @woocommerce/onboarding
|
|
@ -1,4 +0,0 @@
|
|||
Significance: minor
|
||||
Type: dev
|
||||
|
||||
Cleanup: remove the unused is_feature_visible and show_feature methods.
|
|
@ -1,5 +0,0 @@
|
|||
Significance: patch
|
||||
Type: tweak
|
||||
Comment: This change only tweaks a few comments.
|
||||
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Add new E2E test covering shopper product page and make Product-related tests granular (separated test files)
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Add job to post Slack summary of plugin test results in "Smoke test daily" workflow.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Fix flakiness around the `Turn off the new product form` menu item.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Remove "WP Latest-2" from release tests.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Adds test to check required fields on checkout
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Updates Playwright from 1.33 to 1.37.1
|
|
@ -1,4 +0,0 @@
|
|||
Significance: major
|
||||
Type: update
|
||||
|
||||
We have completely redesigned the In-app Marketplace.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Eliminate an unnecessary redirect when the geo hash isalready set to the correct value.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Display search results subtitle in HPOS list table view.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
[HPOS] Modify query to have less characters before the `FROM` keyword.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Fix minor layout shift in the core profiler.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: major
|
||||
Type: fix
|
||||
|
||||
Remove use of woocommerce-page class within WooCommerce Admin pages, replaced with woocommerce-admin-page.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Properly convert local time date queries to UTC in the HPOS datastore.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Fix a bug where updating store location doesn't update store currency.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: update
|
||||
|
||||
Update use of preventLeavingProductForm with new function changes.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
FIx WC Admin pages are empty for WP 6.2 and below.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: tweak
|
||||
|
||||
Adds an informative tooltip to the Account Details section of the Direct Bank Transfer settings.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Fix cached refund not deleted when the refund is deleted with HPOS active
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: enhancement
|
||||
|
||||
Add CLI commands to enable or disable HPOS.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Remove COT enable requirement from sync and verify command.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Avoid string<>int comparison in products bought query to avoid results with customer_id = 0.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Fix CYS `__experimentalReapplyBlockTypeFilters` is not a function
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Fix customize your store task header button
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Addressed visual tweaks for CYS in response to feedback from 12th Sept
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Fix customize store white screen bug in WP 6.3
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
[HPOS] Support deleting metadata just by meta id.
|
|
@ -1,5 +0,0 @@
|
|||
Significance: patch
|
||||
Type: tweak
|
||||
Comment: Just a typo fix.
|
||||
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
[HPOS]Fix duplicate meta handling by passing meta_value|unique in post calls
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Restore moving to trash functionality within HPOS order edit screen.
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue