Request attributes conditionally (#47361)

* Request terms conditionally

* Add changelog

* Add used context

* Transform fetchAttributes into a hook

* Call fetchAttributes inside Attributes component

* Fix tests

* Remove useless useEntityId and fix dependency

* Refactor attriburtes component

* Add AttributeControl to Attributes component

* Add warning comment to Attributes component
This commit is contained in:
Fernando Marichal 2024-05-13 14:00:31 -03:00 committed by GitHub
parent 8da8a70839
commit b3e76c41b4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 126 additions and 32 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: dev
Request attributes conditionally #47361

View File

@ -22,5 +22,6 @@
"lock": false, "lock": false,
"__experimentalToolbar": false "__experimentalToolbar": false
}, },
"usesContext": [ "isInSelectedTab" ],
"editorStyle": "file:./editor.css" "editorStyle": "file:./editor.css"
} }

View File

@ -2,9 +2,11 @@
* External dependencies * External dependencies
*/ */
import { BlockAttributes } from '@wordpress/blocks'; import { BlockAttributes } from '@wordpress/blocks';
import { createElement } from '@wordpress/element'; import { createElement, useEffect } from '@wordpress/element';
import { useWooBlockProps } from '@woocommerce/block-templates'; import { useWooBlockProps } from '@woocommerce/block-templates';
import { ProductProductAttribute } from '@woocommerce/data'; import { ProductProductAttribute } from '@woocommerce/data';
import { __ } from '@wordpress/i18n';
import { recordEvent } from '@woocommerce/tracks';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore No types for this exist yet. // @ts-ignore No types for this exist yet.
// eslint-disable-next-line @woocommerce/dependency-group // eslint-disable-next-line @woocommerce/dependency-group
@ -13,11 +15,13 @@ import { useEntityProp, useEntityId } from '@wordpress/core-data';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { Attributes as AttributesContainer } from '../../../components/attributes/attributes'; import { AttributeControl } from '../../../components/attribute-control';
import { ProductEditorBlockEditProps } from '../../../types'; import { ProductEditorBlockEditProps } from '../../../types';
import { useProductAttributes } from '../../../hooks/use-product-attributes';
export function AttributesBlockEdit( { export function AttributesBlockEdit( {
attributes, attributes,
context: { isInSelectedTab },
}: ProductEditorBlockEditProps< BlockAttributes > ) { }: ProductEditorBlockEditProps< BlockAttributes > ) {
const [ entityAttributes, setEntityAttributes ] = useEntityProp< const [ entityAttributes, setEntityAttributes ] = useEntityProp<
ProductProductAttribute[] ProductProductAttribute[]
@ -27,12 +31,77 @@ export function AttributesBlockEdit( {
const blockProps = useWooBlockProps( attributes ); const blockProps = useWooBlockProps( attributes );
const {
attributes: attributeList,
fetchAttributes,
handleChange,
} = useProductAttributes( {
allAttributes: entityAttributes,
onChange: setEntityAttributes,
productId,
} );
useEffect( () => {
if ( isInSelectedTab ) {
fetchAttributes();
}
}, [ entityAttributes, isInSelectedTab ] );
return ( return (
<div { ...blockProps }> <div { ...blockProps }>
<AttributesContainer <AttributeControl
productId={ productId } value={ attributeList }
value={ entityAttributes } disabledAttributeIds={ entityAttributes
onChange={ setEntityAttributes } .filter( ( attr ) => !! attr.variation )
.map( ( attr ) => attr.id ) }
uiStrings={ {
disabledAttributeMessage: __(
'Already used in Variations',
'woocommerce'
),
} }
onAdd={ () => {
recordEvent(
'product_add_attributes_modal_add_button_click'
);
} }
onChange={ handleChange }
onNewModalCancel={ () => {
recordEvent(
'product_add_attributes_modal_cancel_button_click'
);
} }
onNewModalOpen={ () => {
if ( ! attributeList.length ) {
recordEvent(
'product_add_first_attribute_button_click'
);
return;
}
recordEvent( 'product_add_attribute_button' );
} }
onAddAnother={ () => {
recordEvent(
'product_add_attributes_modal_add_another_attribute_button_click'
);
} }
onRemoveItem={ () => {
recordEvent(
'product_add_attributes_modal_remove_attribute_button_click'
);
} }
onRemove={ () =>
recordEvent(
'product_remove_attribute_confirmation_confirm_click'
)
}
onRemoveCancel={ () =>
recordEvent(
'product_remove_attribute_confirmation_cancel_click'
)
}
termsAutoSelection="first"
defaultVisibility={ true }
/> />
</div> </div>
); );

View File

@ -22,5 +22,6 @@
"lock": false, "lock": false,
"__experimentalToolbar": false "__experimentalToolbar": false
}, },
"usesContext": [ "postType", "isInSelectedTab" ],
"editorStyle": "file:./editor.css" "editorStyle": "file:./editor.css"
} }

View File

@ -7,6 +7,7 @@ import { Button } from '@wordpress/components';
import { import {
createElement, createElement,
createInterpolateElement, createInterpolateElement,
useEffect,
useMemo, useMemo,
} from '@wordpress/element'; } from '@wordpress/element';
import { useWooBlockProps } from '@woocommerce/block-templates'; import { useWooBlockProps } from '@woocommerce/block-templates';
@ -37,7 +38,7 @@ import { ProductTShirt } from './images';
export function Edit( { export function Edit( {
attributes: blockAttributes, attributes: blockAttributes,
context, context: { postType, isInSelectedTab },
}: ProductEditorBlockEditProps< BlockAttributes > ) { }: ProductEditorBlockEditProps< BlockAttributes > ) {
const blockProps = useWooBlockProps( blockAttributes ); const blockProps = useWooBlockProps( blockAttributes );
const { generateProductVariations } = useProductVariationsHelper(); const { generateProductVariations } = useProductVariationsHelper();
@ -57,19 +58,26 @@ export function Edit( {
'default_attributes' 'default_attributes'
); );
const { postType } = context;
const productId = useEntityId( 'postType', postType ); const productId = useEntityId( 'postType', postType );
const { attributes, handleChange } = useProductAttributes( { const { attributes, fetchAttributes, handleChange } = useProductAttributes(
{
allAttributes: entityAttributes, allAttributes: entityAttributes,
isVariationAttributes: true, isVariationAttributes: true,
productId: useEntityId( 'postType', 'product' ), productId,
onChange( values, defaultAttributes ) { onChange( values, defaultAttributes ) {
setEntityAttributes( values ); setEntityAttributes( values );
setEntityDefaultAttributes( defaultAttributes ); setEntityDefaultAttributes( defaultAttributes );
generateProductVariations( values, defaultAttributes ); generateProductVariations( values, defaultAttributes );
}, },
} ); }
);
useEffect( () => {
if ( isInSelectedTab ) {
fetchAttributes();
}
}, [ isInSelectedTab, entityAttributes ] );
const localAttributeNames = attributes const localAttributeNames = attributes
.filter( ( attr ) => attr.id === 0 ) .filter( ( attr ) => attr.id === 0 )

View File

@ -10,28 +10,26 @@ import { recordEvent } from '@woocommerce/tracks';
* Internal dependencies * Internal dependencies
*/ */
import { AttributeControl } from '../attribute-control'; import { AttributeControl } from '../attribute-control';
import { useProductAttributes } from '../../hooks/use-product-attributes';
type AttributesProps = { type AttributesProps = {
attributeList?: ProductProductAttribute[];
value: ProductProductAttribute[]; value: ProductProductAttribute[];
onChange: ( value: ProductProductAttribute[] ) => void; onChange: ( value: ProductProductAttribute[] ) => void;
productId?: number;
}; };
/**
* This component is no longer in active use.
* It is kept here for backward compatibility because is being used in the `AttributesField` component, under
* `plugins/woocommerce-admin/client/products/fills/attributes-section/attributes-field.tsx`.
*/
export const Attributes: React.FC< AttributesProps > = ( { export const Attributes: React.FC< AttributesProps > = ( {
value, value,
onChange, onChange,
productId, attributeList = [],
} ) => { } ) => {
const { attributes, handleChange } = useProductAttributes( {
allAttributes: value,
onChange,
productId,
} );
return ( return (
<AttributeControl <AttributeControl
value={ attributes } value={ attributeList }
disabledAttributeIds={ value disabledAttributeIds={ value
.filter( ( attr ) => !! attr.variation ) .filter( ( attr ) => !! attr.variation )
.map( ( attr ) => attr.id ) } .map( ( attr ) => attr.id ) }
@ -44,14 +42,14 @@ export const Attributes: React.FC< AttributesProps > = ( {
onAdd={ () => { onAdd={ () => {
recordEvent( 'product_add_attributes_modal_add_button_click' ); recordEvent( 'product_add_attributes_modal_add_button_click' );
} } } }
onChange={ handleChange } onChange={ onChange }
onNewModalCancel={ () => { onNewModalCancel={ () => {
recordEvent( recordEvent(
'product_add_attributes_modal_cancel_button_click' 'product_add_attributes_modal_cancel_button_click'
); );
} } } }
onNewModalOpen={ () => { onNewModalOpen={ () => {
if ( ! attributes.length ) { if ( ! attributeList.length ) {
recordEvent( 'product_add_first_attribute_button_click' ); recordEvent( 'product_add_first_attribute_button_click' );
return; return;
} }

View File

@ -152,6 +152,7 @@ describe( 'useProductAttributes', () => {
}, },
} }
); );
result.current.fetchAttributes();
await waitForNextUpdate(); await waitForNextUpdate();
expect( resolveSelect ).not.toHaveBeenCalled(); expect( resolveSelect ).not.toHaveBeenCalled();
expect( result.current.attributes ).toEqual( [] ); expect( result.current.attributes ).toEqual( [] );
@ -175,6 +176,7 @@ describe( 'useProductAttributes', () => {
}, },
} }
); );
result.current.fetchAttributes();
jest.runOnlyPendingTimers(); jest.runOnlyPendingTimers();
await waitForNextUpdate(); await waitForNextUpdate();
result.current.handleChange( [ result.current.handleChange( [
@ -210,6 +212,7 @@ describe( 'useProductAttributes', () => {
} }
); );
jest.runOnlyPendingTimers(); jest.runOnlyPendingTimers();
result.current.fetchAttributes();
await waitForNextUpdate(); await waitForNextUpdate();
result.current.handleChange( [ result.current.handleChange( [
{ ...testAttributes[ 0 ], isDefault: false }, { ...testAttributes[ 0 ], isDefault: false },
@ -242,6 +245,7 @@ describe( 'useProductAttributes', () => {
} }
); );
jest.runOnlyPendingTimers(); jest.runOnlyPendingTimers();
result.current.fetchAttributes();
await waitForNextUpdate(); await waitForNextUpdate();
result.current.handleChange( [ result.current.handleChange( [
{ ...testAttributes[ 0 ], isDefault: false }, { ...testAttributes[ 0 ], isDefault: false },
@ -274,6 +278,7 @@ describe( 'useProductAttributes', () => {
} }
); );
jest.runOnlyPendingTimers(); jest.runOnlyPendingTimers();
result.current.fetchAttributes();
await waitForNextUpdate(); await waitForNextUpdate();
result.current.handleChange( [ result.current.handleChange( [
{ ...testAttributes[ 1 ], isDefault: false }, { ...testAttributes[ 1 ], isDefault: false },
@ -305,6 +310,7 @@ describe( 'useProductAttributes', () => {
} }
); );
jest.runOnlyPendingTimers(); jest.runOnlyPendingTimers();
result.current.fetchAttributes();
await waitForNextUpdate(); await waitForNextUpdate();
result.current.handleChange( [ result.current.handleChange( [
{ ...testAttributes[ 0 ], isDefault: false }, { ...testAttributes[ 0 ], isDefault: false },
@ -336,6 +342,7 @@ describe( 'useProductAttributes', () => {
} }
); );
jest.runOnlyPendingTimers(); jest.runOnlyPendingTimers();
result.current.fetchAttributes();
await waitForNextUpdate(); await waitForNextUpdate();
result.current.handleChange( [ { ...testAttributes[ 0 ] } ] ); result.current.handleChange( [ { ...testAttributes[ 0 ] } ] );
expect( onChange ).toHaveBeenCalledWith( expect( onChange ).toHaveBeenCalledWith(
@ -371,6 +378,7 @@ describe( 'useProductAttributes', () => {
} }
); );
jest.runOnlyPendingTimers(); jest.runOnlyPendingTimers();
result.current.fetchAttributes();
await waitForNextUpdate(); await waitForNextUpdate();
result.current.handleChange( [ result.current.handleChange( [
{ ...testAttributes[ 0 ], isDefault: true }, { ...testAttributes[ 0 ], isDefault: true },
@ -415,6 +423,7 @@ describe( 'useProductAttributes', () => {
}, },
} }
); );
result.current.fetchAttributes();
jest.runOnlyPendingTimers(); jest.runOnlyPendingTimers();
await waitForNextUpdate(); await waitForNextUpdate();
expect( result.current.attributes.length ).toBe( 2 ); expect( result.current.attributes.length ).toBe( 2 );
@ -445,6 +454,7 @@ describe( 'useProductAttributes', () => {
}, },
} }
); );
result.current.fetchAttributes();
jest.runOnlyPendingTimers(); jest.runOnlyPendingTimers();
await waitForNextUpdate(); await waitForNextUpdate();
expect( result.current.attributes.length ).toBe( 2 ); expect( result.current.attributes.length ).toBe( 2 );
@ -460,6 +470,7 @@ describe( 'useProductAttributes', () => {
isVariationAttributes: false, isVariationAttributes: false,
productId: 123, productId: 123,
} ); } );
result.current.fetchAttributes();
jest.runOnlyPendingTimers(); jest.runOnlyPendingTimers();
await waitForNextUpdate(); await waitForNextUpdate();
expect( result.current.attributes.length ).toBe( 1 ); expect( result.current.attributes.length ).toBe( 1 );
@ -486,6 +497,7 @@ describe( 'useProductAttributes', () => {
}, },
} }
); );
result.current.fetchAttributes();
jest.runOnlyPendingTimers(); jest.runOnlyPendingTimers();
await waitForNextUpdate(); await waitForNextUpdate();
expect( result.current.attributes.length ).toBe( 3 ); expect( result.current.attributes.length ).toBe( 3 );

View File

@ -9,7 +9,7 @@ import {
ProductDefaultAttribute, ProductDefaultAttribute,
} from '@woocommerce/data'; } from '@woocommerce/data';
import { resolveSelect } from '@wordpress/data'; import { resolveSelect } from '@wordpress/data';
import { useCallback, useEffect, useState } from '@wordpress/element'; import { useCallback, useState } from '@wordpress/element';
/** /**
* Internal dependencies * Internal dependencies
@ -167,7 +167,7 @@ export function useProductAttributes( {
} }
}; };
useEffect( () => { const fetchAttributes = useCallback( () => {
const [ const [
localAttributes, localAttributes,
globalAttributes, globalAttributes,
@ -190,6 +190,7 @@ export function useProductAttributes( {
return { return {
attributes, attributes,
fetchAttributes,
handleChange, handleChange,
setAttributes, setAttributes,
}; };