Add context to errors (#49242)

* Add context to errors

* Add changelog

* Fix product manager hook

* Add useBlocksHelper

# Conflicts:
#	packages/js/product-editor/src/blocks/product-fields/shipping-dimensions/edit.tsx

* Send clientId as context

* Add validatorId when validating

* Small refactor errorHandler

* Improve method
This commit is contained in:
Fernando Marichal 2024-07-13 10:39:13 -03:00 committed by GitHub
parent 9fa32b4f2b
commit 64ce7bd732
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 175 additions and 76 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: dev
Add context to errors #49242

View File

@ -17,6 +17,7 @@ import { useProductEdits } from '../../../hooks/use-product-edits';
export function Edit( { export function Edit( {
attributes, attributes,
clientId,
context: { postType }, context: { postType },
}: ProductEditorBlockEditProps< NumberBlockAttributes > ) { }: ProductEditorBlockEditProps< NumberBlockAttributes > ) {
const blockProps = useWooBlockProps( attributes ); const blockProps = useWooBlockProps( attributes );
@ -47,31 +48,40 @@ export function Edit( {
value && value &&
parseFloat( value ) < min parseFloat( value ) < min
) { ) {
return sprintf( return {
message: sprintf(
// translators: %d is the minimum value of the number input. // translators: %d is the minimum value of the number input.
__( __(
'Value must be greater than or equal to %d', 'Value must be greater than or equal to %d',
'woocommerce' 'woocommerce'
), ),
min min
); ),
context: clientId,
};
} }
if ( if (
typeof max === 'number' && typeof max === 'number' &&
value && value &&
parseFloat( value ) > max parseFloat( value ) > max
) { ) {
return sprintf( return {
// translators: %d is the maximum value of the number input. message: sprintf(
// translators: %d is the minimum value of the number input.
__( __(
'Value must be less than or equal to %d', 'Value must be less than or equal to %d',
'woocommerce' 'woocommerce'
), ),
max min
); ),
context: clientId,
};
} }
if ( required && ! value ) { if ( required && ! value ) {
return __( 'This field is required.', 'woocommerce' ); return {
message: __( 'This field is required.', 'woocommerce' ),
context: clientId,
};
} }
}, },
[ value ] [ value ]

View File

@ -20,6 +20,7 @@ import { TextBlockAttributes } from './types';
export function Edit( { export function Edit( {
attributes, attributes,
clientId,
context: { postType }, context: { postType },
}: ProductEditorBlockEditProps< TextBlockAttributes > ) { }: ProductEditorBlockEditProps< TextBlockAttributes > ) {
const blockProps = useWooBlockProps( attributes ); const blockProps = useWooBlockProps( attributes );
@ -127,7 +128,10 @@ export function Edit( {
input.setCustomValidity( customErrorMessage ); input.setCustomValidity( customErrorMessage );
if ( ! input.validity.valid ) { if ( ! input.validity.valid ) {
return input.validationMessage; return {
message: customErrorMessage,
context: clientId,
};
} }
}, },
[ type, required, pattern, minLength, maxLength, min, max ] [ type, required, pattern, minLength, maxLength, min, max ]

View File

@ -52,10 +52,13 @@ export function Edit( {
`low_stock_amount-${ clientId }`, `low_stock_amount-${ clientId }`,
async function stockQuantityValidator() { async function stockQuantityValidator() {
if ( lowStockAmount && lowStockAmount < 0 ) { if ( lowStockAmount && lowStockAmount < 0 ) {
return __( return {
message: __(
'This field must be a positive number.', 'This field must be a positive number.',
'woocommerce' 'woocommerce'
); ),
context: clientId,
};
} }
}, },
[ lowStockAmount ] [ lowStockAmount ]

View File

@ -52,10 +52,13 @@ export function Edit( {
`stock_quantity-${ clientId }`, `stock_quantity-${ clientId }`,
async function stockQuantityValidator() { async function stockQuantityValidator() {
if ( manageStock && stockQuantity && stockQuantity < 0 ) { if ( manageStock && stockQuantity && stockQuantity < 0 ) {
return __( return {
message: __(
'Stock quantity must be a positive number.', 'Stock quantity must be a positive number.',
'woocommerce' 'woocommerce'
); ),
context: clientId,
};
} }
}, },
[ manageStock, stockQuantity ] [ manageStock, stockQuantity ]

View File

@ -83,14 +83,20 @@ export function NameBlockEdit( {
'name', 'name',
async function nameValidator() { async function nameValidator() {
if ( ! name || name === AUTO_DRAFT_NAME ) { if ( ! name || name === AUTO_DRAFT_NAME ) {
return __( 'Product name is required.', 'woocommerce' ); return {
message: __( 'Product name is required.', 'woocommerce' ),
context: clientId,
};
} }
if ( name.length > 120 ) { if ( name.length > 120 ) {
return __( return {
message: __(
'Please enter a product name shorter than 120 characters.', 'Please enter a product name shorter than 120 characters.',
'woocommerce' 'woocommerce'
); ),
context: clientId,
};
} }
}, },
[ name ] [ name ]

View File

@ -67,23 +67,35 @@ export function Edit( {
const listPrice = Number.parseFloat( regularPrice ); const listPrice = Number.parseFloat( regularPrice );
if ( listPrice ) { if ( listPrice ) {
if ( listPrice < 0 ) { if ( listPrice < 0 ) {
return __( return {
message: __(
'Regular price must be greater than or equals to zero.', 'Regular price must be greater than or equals to zero.',
'woocommerce' 'woocommerce'
); ),
context: clientId,
};
} }
if ( if (
salePrice && salePrice &&
listPrice <= Number.parseFloat( salePrice ) listPrice <= Number.parseFloat( salePrice )
) { ) {
return __( return {
message: __(
'Regular price must be greater than the sale price.', 'Regular price must be greater than the sale price.',
'woocommerce' 'woocommerce'
); ),
context: clientId,
};
} }
} else if ( isRequired ) { } else if ( isRequired ) {
return {
message: sprintf(
/* translators: label of required field. */ /* translators: label of required field. */
return sprintf( __( '%s is required.', 'woocommerce' ), label ); __( '%s is required.', 'woocommerce' ),
label
),
context: clientId,
};
} }
}, },
[ regularPrice, salePrice ] [ regularPrice, salePrice ]

View File

@ -59,20 +59,26 @@ export function Edit( {
async function salePriceValidator() { async function salePriceValidator() {
if ( salePrice ) { if ( salePrice ) {
if ( Number.parseFloat( salePrice ) < 0 ) { if ( Number.parseFloat( salePrice ) < 0 ) {
return __( return {
message: __(
'Sale price must be greater than or equals to zero.', 'Sale price must be greater than or equals to zero.',
'woocommerce' 'woocommerce'
); ),
context: clientId,
};
} }
const listPrice = Number.parseFloat( regularPrice ); const listPrice = Number.parseFloat( regularPrice );
if ( if (
! listPrice || ! listPrice ||
listPrice <= Number.parseFloat( salePrice ) listPrice <= Number.parseFloat( salePrice )
) { ) {
return __( return {
message: __(
'Sale price must be lower than the regular price.', 'Sale price must be lower than the regular price.',
'woocommerce' 'woocommerce'
); ),
context: clientId,
};
} }
} }
}, },

View File

@ -100,14 +100,23 @@ export function Edit( {
async function dateOnSaleFromValidator() { async function dateOnSaleFromValidator() {
if ( showScheduleSale && dateOnSaleFromGmt ) { if ( showScheduleSale && dateOnSaleFromGmt ) {
if ( ! _dateOnSaleFrom.isValid() ) { if ( ! _dateOnSaleFrom.isValid() ) {
return __( 'Please enter a valid date.', 'woocommerce' ); return {
message: __(
'Please enter a valid date.',
'woocommerce'
),
context: clientId,
};
} }
if ( _dateOnSaleFrom.isAfter( _dateOnSaleTo ) ) { if ( _dateOnSaleFrom.isAfter( _dateOnSaleTo ) ) {
return __( return {
message: __(
'The start date of the sale must be before the end date.', 'The start date of the sale must be before the end date.',
'woocommerce' 'woocommerce'
); ),
context: clientId,
};
} }
} }
}, },
@ -123,14 +132,23 @@ export function Edit( {
async function dateOnSaleToValidator() { async function dateOnSaleToValidator() {
if ( showScheduleSale && dateOnSaleToGmt ) { if ( showScheduleSale && dateOnSaleToGmt ) {
if ( ! _dateOnSaleTo.isValid() ) { if ( ! _dateOnSaleTo.isValid() ) {
return __( 'Please enter a valid date.', 'woocommerce' ); return {
message: __(
'Please enter a valid date.',
'woocommerce'
),
context: clientId,
};
} }
if ( _dateOnSaleTo.isBefore( _dateOnSaleFrom ) ) { if ( _dateOnSaleTo.isBefore( _dateOnSaleFrom ) ) {
return __( return {
message: __(
'The end date of the sale must be after the start date.', 'The end date of the sale must be after the start date.',
'woocommerce' 'woocommerce'
); ),
context: clientId,
};
} }
} }
}, },

View File

@ -96,7 +96,13 @@ export function Edit( {
`dimensions_width-${ clientId }`, `dimensions_width-${ clientId }`,
async function dimensionsWidthValidator() { async function dimensionsWidthValidator() {
if ( dimensions?.width && +dimensions.width <= 0 ) { if ( dimensions?.width && +dimensions.width <= 0 ) {
return __( 'Width must be greater than zero.', 'woocommerce' ); return {
message: __(
'Width must be greater than zero.',
'woocommerce'
),
context: clientId,
};
} }
}, },
[ dimensions?.width ] [ dimensions?.width ]
@ -110,7 +116,13 @@ export function Edit( {
`dimensions_length-${ clientId }`, `dimensions_length-${ clientId }`,
async function dimensionsLengthValidator() { async function dimensionsLengthValidator() {
if ( dimensions?.length && +dimensions.length <= 0 ) { if ( dimensions?.length && +dimensions.length <= 0 ) {
return __( 'Length must be greater than zero.', 'woocommerce' ); return {
message: __(
'Length must be greater than zero.',
'woocommerce'
),
context: clientId,
};
} }
}, },
[ dimensions?.length ] [ dimensions?.length ]
@ -124,7 +136,13 @@ export function Edit( {
`dimensions_height-${ clientId }`, `dimensions_height-${ clientId }`,
async function dimensionsHeightValidator() { async function dimensionsHeightValidator() {
if ( dimensions?.height && +dimensions.height <= 0 ) { if ( dimensions?.height && +dimensions.height <= 0 ) {
return __( 'Height must be greater than zero.', 'woocommerce' ); return {
message: __(
'Height must be greater than zero.',
'woocommerce'
),
context: clientId,
};
} }
}, },
[ dimensions?.height ] [ dimensions?.height ]
@ -138,7 +156,13 @@ export function Edit( {
`weight-${ clientId }`, `weight-${ clientId }`,
async function weightValidator() { async function weightValidator() {
if ( weight && +weight <= 0 ) { if ( weight && +weight <= 0 ) {
return __( 'Weight must be greater than zero.', 'woocommerce' ); return {
message: __(
'Weight must be greater than zero.',
'woocommerce'
),
context: clientId,
};
} }
}, },
[ weight ] [ weight ]

View File

@ -32,6 +32,7 @@ import { EmptyState } from '../../../components/empty-state';
export function Edit( { export function Edit( {
attributes, attributes,
clientId,
context: { isInSelectedTab }, context: { isInSelectedTab },
}: ProductEditorBlockEditProps< VariationOptionsBlockAttributes > ) { }: ProductEditorBlockEditProps< VariationOptionsBlockAttributes > ) {
const noticeDimissed = useRef( false ); const noticeDimissed = useRef( false );
@ -125,10 +126,13 @@ export function Edit( {
}, },
} ); } );
} }
return __( return {
message: __(
'Set variation prices before adding this product.', 'Set variation prices before adding this product.',
'woocommerce' 'woocommerce'
); ),
context: clientId,
};
} }
}, },
[ totalCountWithoutPrice ] [ totalCountWithoutPrice ]

View File

@ -24,7 +24,9 @@ export type ValidationProviderProps = {
productId: number; productId: number;
}; };
export type ValidationError = string | undefined; export type ValidationError =
| { message?: string; context?: string; validatorId?: string }
| undefined;
export type ValidationErrors = Record< string, ValidationError >; export type ValidationErrors = Record< string, ValidationError >;
export type ValidatorRegistration< T > = { export type ValidatorRegistration< T > = {

View File

@ -31,7 +31,7 @@ export function useValidation< T >(
return { return {
ref, ref,
error: context.errors[ validatorId ], error: context.errors[ validatorId ]?.message,
isValidating, isValidating,
async validate( newData?: Record< string, unknown > ) { async validate( newData?: Record< string, unknown > ) {
setIsValidating( true ); setIsValidating( true );

View File

@ -9,6 +9,7 @@ import { createElement, useRef, useState } from '@wordpress/element';
* Internal dependencies * Internal dependencies
*/ */
import { import {
ValidationError,
ValidationErrors, ValidationErrors,
ValidationProviderProps, ValidationProviderProps,
Validator, Validator,
@ -64,11 +65,13 @@ export function ValidationProvider< T >( {
const result = validator( initialValue, newData ); const result = validator( initialValue, newData );
return result.then( ( error ) => { return result.then( ( error ) => {
const errorWithValidatorId: ValidationError =
error !== undefined ? { validatorId, ...error } : undefined;
setErrors( ( currentErrors ) => ( { setErrors( ( currentErrors ) => ( {
...currentErrors, ...currentErrors,
[ validatorId ]: error, [ validatorId ]: errorWithValidatorId,
} ) ); } ) );
return error; return errorWithValidatorId;
} ); } );
} }

View File

@ -18,21 +18,21 @@ export function errorHandler( error: WPError, productStatus: ProductStatus ) {
return error; return error;
} }
const errorObj = Object.values( error ).find(
( value ) => value !== undefined
) as WPError | undefined;
if ( 'variations' in error && error.variations ) { if ( 'variations' in error && error.variations ) {
return { return {
...errorObj,
code: 'variable_product_no_variation_prices', code: 'variable_product_no_variation_prices',
message: error.variations,
}; };
} }
const errorMessage = Object.values( error ).find( if ( errorObj !== undefined ) {
( value ) => value !== undefined
) as string | undefined;
if ( errorMessage !== undefined ) {
return { return {
...errorObj,
code: 'product_form_field_error', code: 'product_form_field_error',
message: errorMessage,
}; };
} }