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,
"__experimentalToolbar": false
},
"usesContext": [ "isInSelectedTab" ],
"editorStyle": "file:./editor.css"
}

View File

@ -2,9 +2,11 @@
* External dependencies
*/
import { BlockAttributes } from '@wordpress/blocks';
import { createElement } from '@wordpress/element';
import { createElement, useEffect } from '@wordpress/element';
import { useWooBlockProps } from '@woocommerce/block-templates';
import { ProductProductAttribute } from '@woocommerce/data';
import { __ } from '@wordpress/i18n';
import { recordEvent } from '@woocommerce/tracks';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore No types for this exist yet.
// eslint-disable-next-line @woocommerce/dependency-group
@ -13,11 +15,13 @@ import { useEntityProp, useEntityId } from '@wordpress/core-data';
/**
* Internal dependencies
*/
import { Attributes as AttributesContainer } from '../../../components/attributes/attributes';
import { AttributeControl } from '../../../components/attribute-control';
import { ProductEditorBlockEditProps } from '../../../types';
import { useProductAttributes } from '../../../hooks/use-product-attributes';
export function AttributesBlockEdit( {
attributes,
context: { isInSelectedTab },
}: ProductEditorBlockEditProps< BlockAttributes > ) {
const [ entityAttributes, setEntityAttributes ] = useEntityProp<
ProductProductAttribute[]
@ -27,12 +31,77 @@ export function AttributesBlockEdit( {
const blockProps = useWooBlockProps( attributes );
const {
attributes: attributeList,
fetchAttributes,
handleChange,
} = useProductAttributes( {
allAttributes: entityAttributes,
onChange: setEntityAttributes,
productId,
} );
useEffect( () => {
if ( isInSelectedTab ) {
fetchAttributes();
}
}, [ entityAttributes, isInSelectedTab ] );
return (
<div { ...blockProps }>
<AttributesContainer
productId={ productId }
value={ entityAttributes }
onChange={ setEntityAttributes }
<AttributeControl
value={ attributeList }
disabledAttributeIds={ entityAttributes
.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>
);

View File

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

View File

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

View File

@ -10,28 +10,26 @@ import { recordEvent } from '@woocommerce/tracks';
* Internal dependencies
*/
import { AttributeControl } from '../attribute-control';
import { useProductAttributes } from '../../hooks/use-product-attributes';
type AttributesProps = {
attributeList?: ProductProductAttribute[];
value: ProductProductAttribute[];
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 > = ( {
value,
onChange,
productId,
attributeList = [],
} ) => {
const { attributes, handleChange } = useProductAttributes( {
allAttributes: value,
onChange,
productId,
} );
return (
<AttributeControl
value={ attributes }
value={ attributeList }
disabledAttributeIds={ value
.filter( ( attr ) => !! attr.variation )
.map( ( attr ) => attr.id ) }
@ -44,14 +42,14 @@ export const Attributes: React.FC< AttributesProps > = ( {
onAdd={ () => {
recordEvent( 'product_add_attributes_modal_add_button_click' );
} }
onChange={ handleChange }
onChange={ onChange }
onNewModalCancel={ () => {
recordEvent(
'product_add_attributes_modal_cancel_button_click'
);
} }
onNewModalOpen={ () => {
if ( ! attributes.length ) {
if ( ! attributeList.length ) {
recordEvent( 'product_add_first_attribute_button_click' );
return;
}

View File

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

View File

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