Product Collection: Make attributes available in rest_product_query hook (#44150)
* Make attributes available in rest_product_query hook
This commit introduces the 'includeInQueryContext' attribute to the 'woocommerce/product-collection' block and updates the 'woocommerce/product-template' block to consume this new attribute.
Key Changes:
1. `woocommerce/product-collection` Block:
- A new attribute 'includeInQueryContext' is added in `block.json`. This attribute is designed to hold a list of attribute names relevant for the query context.
- The 'includeInQueryContext' attribute is included in the `providesContext` field to ensure its availability to child blocks.
- In `constants.ts`, default values for 'includeInQueryContext' are defined, specifying 'collection' and 'id' as initial attributes.
- The `types.ts` file is updated with a comment explaining the purpose of 'includeInQueryContext'.
2. `woocommerce/product-template` Block:
- Modified `block.json` to utilize the 'includeInQueryContext' context provided by the parent `woocommerce/product-collection` block.
- The `edit.tsx` file is updated to handle the new context. It uses a newly added utility function `useProductCollectionBlockAttributes` from `utils.tsx` to access parent block attributes.
- The `utils.tsx` file is introduced, containing the `useProductCollectionBlockAttributes` hook. This hook is responsible for finding the parent 'woocommerce/product-collection' block and returning its attributes.
- Within `edit.tsx`, logic is added to create a query context object based on the attributes specified in 'includeInQueryContext', enhancing the block's ability to dynamically adapt to changes.
* Remove commented code
* Rename query context attribute and optimize parent block detection
This commit introduces two significant changes aimed at improving code readability and efficiency.
1. **Renaming of Query Context Attribute:**
- The attribute `includeInQueryContext` has been renamed to `queryContextIncludes` across various files, including block JSON configurations and TypeScript definitions. This change makes the attribute's purpose more intuitive, indicating it specifies which attributes to include in the query context.
2. **Optimized Parent Block Detection:**
- Replaced the use of `getBlockParents` with `getBlockParentsByBlockName` in utility functions to find the closest Product Collection block. This optimization allows for a more direct and efficient way to identify the relevant parent block by specifying the block name, reducing unnecessary iterations and improving code performance.
* Streamline query context construction in product template
Key Changes:
- **Introduction of `useProductCollectionQueryContext` Hook:** This new hook takes the `clientId` and `queryContextIncludes` as inputs and returns a query context object. It encapsulates the logic for fetching parent product collection block attributes and constructing the query context accordingly. This abstraction simplifies the edit component's logic, focusing on the essentials and improving code readability.
- **Optimization of Parent Block Detection:** The hook uses `getBlockParentsByBlockName` to accurately and efficiently find the closest parent `Product Collection` block, minimizing the overhead previously associated with traversing the block hierarchy.
* Always include `collection` and `id` in query context
* Minor refactor
* Enhance query context handling for more maintainable code
- Introduced `DEFAULT_QUERY_CONTEXT_ATTRIBUTES` in `edit.tsx` to maintain a clear list of default query context attributes.
- Modified `ProductTemplateEdit` to automatically include these default attributes in `queryContextIncludes`, ensuring they are always part of the query context without manual initialization.
- Simplified `useProductCollectionQueryContext` in `utils.tsx` by removing static initialization of 'collection' and 'id', relying instead on the dynamic addition of necessary attributes from `queryContextIncludes`.
This refactor enhances the maintainability and clarity of the code, ensuring a solid foundation for future enhancements and features.
* Add E2E tests for Product Collection query context
- Added a new test suite 'Query Context in Editor' to validate the correctness of query context parameters when the Product Collection block is used. This suite ensures that:
- For the 'Product Catalog', only the ID is sent in the query context, confirming that collection-specific parameters are excluded when not relevant.
- For collections such as 'On Sale', the collection name is correctly passed in the query context, validating that the block dynamically adjusts query parameters based on its settings.
- Introduced a new utility method `setupAndFetchQueryContextURL` in `product-collection.page.ts`. This method automates the setup of a post with the Product Collection block and fetches the URL with query context parameters, facilitating the validation of query context handling.
* Add changefile(s) from automation for the following project(s): woocommerce-blocks
* Fix edge case when `queryContextIncludes` is not defined
- Initializing `queryContextIncludes` with a default empty array directly in the destructuring assignment of the component's props. This approach ensures that `queryContextIncludes` is always an array, simplifying downstream logic.
- Creating a new constant `queryContextIncludesWithDefaults` to hold the combination of `queryContextIncludes` and `DEFAULT_QUERY_CONTEXT_ATTRIBUTES`. This step avoids directly mutating the `queryContextIncludes` prop, aligning with best practices for functional purity and making the code easier to understand and debug.
- Updating the `useProductCollectionQueryContext` hook call to use `queryContextIncludesWithDefaults`. This ensures that the default query context attributes are consistently included without altering the original prop.
These adjustments not only enhance the code's maintainability but also ensure more predictable behavior by avoiding side effects related to parameter mutation.
---------
Co-authored-by: github-actions <github-actions@github.com>
2024-02-12 08:45:24 +00:00
|
|
|
/**
|
|
|
|
* External dependencies
|
|
|
|
*/
|
2024-02-12 19:59:40 +00:00
|
|
|
import { resolveSelect, useSelect } from '@wordpress/data';
|
|
|
|
import { useState, useEffect, useMemo } from '@wordpress/element';
|
|
|
|
import { store as coreStore } from '@wordpress/core-data';
|
|
|
|
import { store as blockEditorStore } from '@wordpress/block-editor';
|
|
|
|
|
2024-04-05 13:54:56 +00:00
|
|
|
export const enum LocationType {
|
|
|
|
Product = 'product',
|
|
|
|
Archive = 'archive',
|
|
|
|
Cart = 'cart',
|
|
|
|
Order = 'order',
|
|
|
|
Site = 'site',
|
|
|
|
}
|
2024-02-12 19:59:40 +00:00
|
|
|
type Context< T > = T & {
|
|
|
|
templateSlug?: string;
|
|
|
|
postId?: number;
|
|
|
|
};
|
|
|
|
type SetEntityId = (
|
|
|
|
kind: 'postType' | 'taxonomy',
|
|
|
|
name: 'product' | 'product_cat' | 'product_tag',
|
|
|
|
slug: string,
|
|
|
|
stateSetter: ( entityId: number | null ) => void
|
|
|
|
) => void;
|
|
|
|
|
|
|
|
const templateSlugs = {
|
|
|
|
singleProduct: 'single-product',
|
|
|
|
productCategory: 'taxonomy-product_cat',
|
|
|
|
productTag: 'taxonomy-product_tag',
|
|
|
|
productAttribute: 'taxonomy-product_attribute',
|
|
|
|
orderConfirmation: 'order-confirmation',
|
|
|
|
cart: 'page-cart',
|
|
|
|
checkout: 'page-checkout',
|
|
|
|
};
|
|
|
|
|
|
|
|
const getIdFromResponse = ( resp?: Record< 'id', number >[] ): number | null =>
|
|
|
|
resp && resp.length && resp[ 0 ]?.id ? resp[ 0 ].id : null;
|
|
|
|
|
|
|
|
const setEntityId: SetEntityId = async ( kind, name, slug, stateSetter ) => {
|
|
|
|
const response = ( await resolveSelect( coreStore ).getEntityRecords(
|
|
|
|
kind,
|
|
|
|
name,
|
|
|
|
{
|
|
|
|
_fields: [ 'id' ],
|
|
|
|
slug,
|
|
|
|
}
|
|
|
|
) ) as Record< 'id', number >[];
|
|
|
|
const entityId = getIdFromResponse( response );
|
|
|
|
stateSetter( entityId );
|
|
|
|
};
|
|
|
|
|
|
|
|
const prepareGetEntitySlug =
|
|
|
|
( templateSlug: string ) =>
|
|
|
|
( entitySlug: string ): string =>
|
|
|
|
templateSlug.replace( `${ entitySlug }-`, '' );
|
|
|
|
const prepareIsInSpecificTemplate =
|
|
|
|
( templateSlug: string ) =>
|
|
|
|
( entitySlug: string ): boolean =>
|
|
|
|
templateSlug.includes( entitySlug ) && templateSlug !== entitySlug;
|
|
|
|
const prepareIsInGenericTemplate =
|
|
|
|
( templateSlug: string ) =>
|
|
|
|
( entitySlug: string ): boolean =>
|
|
|
|
templateSlug === entitySlug;
|
|
|
|
|
Product Collection - Show product picker in Editor when collection requires a product but it doesn't exist (#50164)
* Show product picker control in the editor when a product context is required but not provided
Enhanced the Product Collection block by introducing the `selectedReference` attribute and implementing a product picker control. This control appears in the editor when a product context is required but not provided in the current template/page/post.
1. **block.json**: Added `selectedReference` attribute of type `object`.
2. **constants.ts**: Included `selectedReference` in the `queryContextIncludes` array.
3. **EditorProductPicker.tsx**: Created a new component for selecting products within the editor.
4. **editor.scss**: Added styles for the new Editor Product Picker component.
5. **index.tsx**: Updated logic to determine the component to render, incorporating the new Editor Product Picker.
6. **types.ts**: Defined types for `selectedReference` and updated `ProductCollectionAttributes` interface.
This enhancement allows merchants to manually select a product for collections that require a product context, ensuring the block displays correctly even when the product context is not available in the current template/page/post.
- **Product Picker Control**: Utilizes the existing `ProductControl` component for selecting products. This component is displayed in the editor when a collection requires a product context but it doesn't exist in the current template/page/post.
* Update label on ProductControl component
* Implement dynamic UI state management for product collection block
- Introduced `ProductCollectionUIStatesInEditor` enum to define various UI states for the product collection block.
- Added `getProductCollectionUIStateInEditor` utility function to determine the appropriate UI state based on context.
- Updated `Edit` component to use `getProductCollectionUIStateInEditor` for dynamic state management.
- Refactored `ProductCollectionContent` to utilize the new Editor UI state management.
* Fix: Product picker isn't showing
* Fix: Preview label state isn't showing
* Add changefile(s) from automation for the following project(s): woocommerce-blocks
* Refactor WooCommerceBlockLocation type
- Introduced specific interfaces for WooCommerceBlockLocation, including ProductLocation, ArchiveLocation, CartLocation, OrderLocation, and SiteLocation, to improve type safety and code clarity.
- Updated createLocationObject function to return a BaseLocation type.
- Refactored useSetPreviewState hook in product-collection utils:
- Extracted termId from location.sourceData for cleaner and more readable code.
- Replaced direct access of location.sourceData?.termId with termId variable.
* Remove fallback to 0 in case there may be a product with id 0
* Use optional chaining to avoid undefined errors
* Rename to
* Change order of arguments in function
* Pass boolean prop instead of making further recognition of the UI state in ProductCollectionContent
* Destructure props in component
* Rename to
* Update names in enum
* Rename to and change the structure to single number.
* Rename location to
* Add a method to choose a product in the product picker in Editor
* Add E2E tests
* Fix failing e2e tests by changing location to productCollectionLocation
* Add changefile(s) from automation for the following project(s): woocommerce-blocks
* Don't allow selecting product variations
* Minor code refactoring
* Fix: Product control isn't showing products
**Before**
```tsx
const getRenderItemFunc = () => {
if ( renderItem ) {
return renderItem;
} else if ( showVariations ) {
return renderItemWithVariations;
}
return () => null;
};
```
As you can see above, `return () => null;` is returning a function which is causing the issue. This will render nothing in the list. I changed this to `return undefined;`. This way, we will use default render item function.
* Translate text in ProductPicker component
* Improve E2E test
* Use createInterpolateElement to safely render strong HTML tag
* Fix E2E tests
* Fix E2E tests
* Product Collection: Inspector control to change selected product (#50590)
* Add Linked Product Control to Product Collection Block Inspector Controls
- Introduced a new `LinkedProductControl` component in the Product Collection block's Inspector Controls.
- This control allows users to link a specific product to the product collection via a dropdown with a search capability.
- Added corresponding styles to `editor.scss`.
- Integrated a `useGetProduct` hook in the `utils.tsx` to fetch and manage the state of the linked product data, including handling loading states and errors.
- Updated the Inspector Controls to include the new Linked Product Control component, enhancing the block's customization options for users.
* Add E2E tests
* Hide product picker when product context is available
* Improve logic to hide Linked Product Control
* Add changefile(s) from automation for the following project(s): woocommerce-blocks
* Remove hasError state from useGetProduct hook
* Rename isShowLinkedProductControl to showLinkedProductControl
* Convert jsxProductButton to ProductButton component
* Refactor jsxPopoverContent to LinkedProductPopoverContent component
* Improve UI of Linked Product Control
* Address PR feedback
* Fix E2E tests
---------
Co-authored-by: github-actions <github-actions@github.com>
* Rename isUsesReferencePreviewMode to isUsingReferencePreviewMode
* Change order of conditions in getProductCollectionUIStateInEditor
---------
Co-authored-by: github-actions <github-actions@github.com>
2024-09-02 07:09:33 +00:00
|
|
|
interface WooCommerceBaseLocation {
|
|
|
|
type: LocationType;
|
|
|
|
sourceData?: object | undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface ProductLocation extends WooCommerceBaseLocation {
|
|
|
|
type: LocationType.Product;
|
|
|
|
sourceData?:
|
|
|
|
| {
|
|
|
|
productId: number;
|
|
|
|
}
|
|
|
|
| undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface ArchiveLocation extends WooCommerceBaseLocation {
|
|
|
|
type: LocationType.Archive;
|
|
|
|
sourceData?:
|
|
|
|
| {
|
|
|
|
taxonomy: string;
|
|
|
|
termId: number;
|
|
|
|
}
|
|
|
|
| undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface CartLocation extends WooCommerceBaseLocation {
|
|
|
|
type: LocationType.Cart;
|
|
|
|
sourceData?:
|
|
|
|
| {
|
|
|
|
productIds: number[];
|
|
|
|
}
|
|
|
|
| undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface OrderLocation extends WooCommerceBaseLocation {
|
|
|
|
type: LocationType.Order;
|
|
|
|
sourceData?:
|
|
|
|
| {
|
|
|
|
orderId: number;
|
|
|
|
}
|
|
|
|
| undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface SiteLocation extends WooCommerceBaseLocation {
|
|
|
|
type: LocationType.Site;
|
|
|
|
sourceData?: object | undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
export type WooCommerceBlockLocation =
|
|
|
|
| ProductLocation
|
|
|
|
| ArchiveLocation
|
|
|
|
| CartLocation
|
|
|
|
| OrderLocation
|
|
|
|
| SiteLocation;
|
|
|
|
|
|
|
|
const createLocationObject = ( type: LocationType, sourceData: object = {} ) =>
|
|
|
|
( {
|
|
|
|
type,
|
|
|
|
sourceData,
|
|
|
|
} as WooCommerceBlockLocation );
|
2024-02-12 19:59:40 +00:00
|
|
|
|
|
|
|
type ContextProperties = {
|
|
|
|
templateSlug: string;
|
|
|
|
postId?: string;
|
|
|
|
};
|
|
|
|
|
|
|
|
export const useGetLocation = < T, >(
|
|
|
|
context: Context< T & ContextProperties >,
|
|
|
|
clientId: string
|
Product Collection - Show product picker in Editor when collection requires a product but it doesn't exist (#50164)
* Show product picker control in the editor when a product context is required but not provided
Enhanced the Product Collection block by introducing the `selectedReference` attribute and implementing a product picker control. This control appears in the editor when a product context is required but not provided in the current template/page/post.
1. **block.json**: Added `selectedReference` attribute of type `object`.
2. **constants.ts**: Included `selectedReference` in the `queryContextIncludes` array.
3. **EditorProductPicker.tsx**: Created a new component for selecting products within the editor.
4. **editor.scss**: Added styles for the new Editor Product Picker component.
5. **index.tsx**: Updated logic to determine the component to render, incorporating the new Editor Product Picker.
6. **types.ts**: Defined types for `selectedReference` and updated `ProductCollectionAttributes` interface.
This enhancement allows merchants to manually select a product for collections that require a product context, ensuring the block displays correctly even when the product context is not available in the current template/page/post.
- **Product Picker Control**: Utilizes the existing `ProductControl` component for selecting products. This component is displayed in the editor when a collection requires a product context but it doesn't exist in the current template/page/post.
* Update label on ProductControl component
* Implement dynamic UI state management for product collection block
- Introduced `ProductCollectionUIStatesInEditor` enum to define various UI states for the product collection block.
- Added `getProductCollectionUIStateInEditor` utility function to determine the appropriate UI state based on context.
- Updated `Edit` component to use `getProductCollectionUIStateInEditor` for dynamic state management.
- Refactored `ProductCollectionContent` to utilize the new Editor UI state management.
* Fix: Product picker isn't showing
* Fix: Preview label state isn't showing
* Add changefile(s) from automation for the following project(s): woocommerce-blocks
* Refactor WooCommerceBlockLocation type
- Introduced specific interfaces for WooCommerceBlockLocation, including ProductLocation, ArchiveLocation, CartLocation, OrderLocation, and SiteLocation, to improve type safety and code clarity.
- Updated createLocationObject function to return a BaseLocation type.
- Refactored useSetPreviewState hook in product-collection utils:
- Extracted termId from location.sourceData for cleaner and more readable code.
- Replaced direct access of location.sourceData?.termId with termId variable.
* Remove fallback to 0 in case there may be a product with id 0
* Use optional chaining to avoid undefined errors
* Rename to
* Change order of arguments in function
* Pass boolean prop instead of making further recognition of the UI state in ProductCollectionContent
* Destructure props in component
* Rename to
* Update names in enum
* Rename to and change the structure to single number.
* Rename location to
* Add a method to choose a product in the product picker in Editor
* Add E2E tests
* Fix failing e2e tests by changing location to productCollectionLocation
* Add changefile(s) from automation for the following project(s): woocommerce-blocks
* Don't allow selecting product variations
* Minor code refactoring
* Fix: Product control isn't showing products
**Before**
```tsx
const getRenderItemFunc = () => {
if ( renderItem ) {
return renderItem;
} else if ( showVariations ) {
return renderItemWithVariations;
}
return () => null;
};
```
As you can see above, `return () => null;` is returning a function which is causing the issue. This will render nothing in the list. I changed this to `return undefined;`. This way, we will use default render item function.
* Translate text in ProductPicker component
* Improve E2E test
* Use createInterpolateElement to safely render strong HTML tag
* Fix E2E tests
* Fix E2E tests
* Product Collection: Inspector control to change selected product (#50590)
* Add Linked Product Control to Product Collection Block Inspector Controls
- Introduced a new `LinkedProductControl` component in the Product Collection block's Inspector Controls.
- This control allows users to link a specific product to the product collection via a dropdown with a search capability.
- Added corresponding styles to `editor.scss`.
- Integrated a `useGetProduct` hook in the `utils.tsx` to fetch and manage the state of the linked product data, including handling loading states and errors.
- Updated the Inspector Controls to include the new Linked Product Control component, enhancing the block's customization options for users.
* Add E2E tests
* Hide product picker when product context is available
* Improve logic to hide Linked Product Control
* Add changefile(s) from automation for the following project(s): woocommerce-blocks
* Remove hasError state from useGetProduct hook
* Rename isShowLinkedProductControl to showLinkedProductControl
* Convert jsxProductButton to ProductButton component
* Refactor jsxPopoverContent to LinkedProductPopoverContent component
* Improve UI of Linked Product Control
* Address PR feedback
* Fix E2E tests
---------
Co-authored-by: github-actions <github-actions@github.com>
* Rename isUsesReferencePreviewMode to isUsingReferencePreviewMode
* Change order of conditions in getProductCollectionUIStateInEditor
---------
Co-authored-by: github-actions <github-actions@github.com>
2024-09-02 07:09:33 +00:00
|
|
|
): WooCommerceBlockLocation => {
|
2024-02-12 19:59:40 +00:00
|
|
|
const templateSlug = context.templateSlug || '';
|
|
|
|
const postId = context.postId || null;
|
|
|
|
|
|
|
|
const getEntitySlug = prepareGetEntitySlug( templateSlug );
|
|
|
|
const isInSpecificTemplate = prepareIsInSpecificTemplate( templateSlug );
|
|
|
|
|
|
|
|
// Detect Specific Templates
|
|
|
|
const isInSpecificProductTemplate = isInSpecificTemplate(
|
|
|
|
templateSlugs.singleProduct
|
|
|
|
);
|
|
|
|
const isInSpecificCategoryTemplate = isInSpecificTemplate(
|
|
|
|
templateSlugs.productCategory
|
|
|
|
);
|
|
|
|
const isInSpecificTagTemplate = isInSpecificTemplate(
|
|
|
|
templateSlugs.productTag
|
|
|
|
);
|
|
|
|
|
|
|
|
const [ productId, setProductId ] = useState< number | null >( null );
|
|
|
|
const [ categoryId, setCategoryId ] = useState< number | null >( null );
|
|
|
|
const [ tagId, setTagId ] = useState< number | null >( null );
|
|
|
|
|
|
|
|
useEffect( () => {
|
|
|
|
if ( isInSpecificProductTemplate ) {
|
|
|
|
const slug = getEntitySlug( templateSlugs.singleProduct );
|
|
|
|
setEntityId( 'postType', 'product', slug, setProductId );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( isInSpecificCategoryTemplate ) {
|
|
|
|
const slug = getEntitySlug( templateSlugs.productCategory );
|
|
|
|
setEntityId( 'taxonomy', 'product_cat', slug, setCategoryId );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( isInSpecificTagTemplate ) {
|
|
|
|
const slug = getEntitySlug( templateSlugs.productTag );
|
|
|
|
setEntityId( 'taxonomy', 'product_tag', slug, setTagId );
|
|
|
|
}
|
|
|
|
}, [
|
|
|
|
isInSpecificProductTemplate,
|
|
|
|
isInSpecificCategoryTemplate,
|
|
|
|
isInSpecificTagTemplate,
|
|
|
|
getEntitySlug,
|
|
|
|
] );
|
|
|
|
|
2024-04-02 07:05:17 +00:00
|
|
|
const { isInSingleProductBlock, isInSomeCartCheckoutBlock } = useSelect(
|
|
|
|
( select ) => {
|
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
|
|
// @ts-ignore No types for this selector exist yet
|
|
|
|
const { getBlockParentsByBlockName } = select( blockEditorStore );
|
|
|
|
const isInBlocks = ( parentBlockNames: string[] ) =>
|
|
|
|
getBlockParentsByBlockName( clientId, parentBlockNames )
|
|
|
|
.length > 0;
|
|
|
|
|
|
|
|
return {
|
|
|
|
isInSingleProductBlock: isInBlocks( [
|
|
|
|
'woocommerce/single-product',
|
|
|
|
] ),
|
|
|
|
isInSomeCartCheckoutBlock: isInBlocks( [
|
|
|
|
'woocommerce/cart',
|
|
|
|
'woocommerce/checkout',
|
|
|
|
'woocommerce/mini-cart-contents',
|
|
|
|
] ),
|
|
|
|
};
|
|
|
|
},
|
2024-02-12 19:59:40 +00:00
|
|
|
[ clientId ]
|
|
|
|
);
|
|
|
|
|
|
|
|
/**
|
2024-04-02 07:05:17 +00:00
|
|
|
* Case 1.1: BLOCK LEVEL: SPECIFIC PRODUCT
|
2024-02-12 19:59:40 +00:00
|
|
|
* Single Product block - take product ID from context
|
|
|
|
*/
|
|
|
|
|
|
|
|
if ( isInSingleProductBlock ) {
|
2024-04-05 13:54:56 +00:00
|
|
|
return createLocationObject( LocationType.Product, {
|
|
|
|
productId: postId,
|
|
|
|
} );
|
2024-02-12 19:59:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2024-04-02 07:05:17 +00:00
|
|
|
* Case 1.2: BLOCK LEVEL: GENERIC CART
|
|
|
|
* Cart, Checkout or Mini Cart blocks - block scope is more important than template
|
|
|
|
*/
|
|
|
|
|
|
|
|
if ( isInSomeCartCheckoutBlock ) {
|
2024-04-05 13:54:56 +00:00
|
|
|
return createLocationObject( LocationType.Cart );
|
2024-04-02 07:05:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Case 2.1: TEMPLATES: SPECIFIC PRODUCT
|
2024-02-12 19:59:40 +00:00
|
|
|
* Specific Single Product template - take product ID from taxononmy
|
|
|
|
*/
|
|
|
|
|
|
|
|
if ( isInSpecificProductTemplate ) {
|
2024-04-05 13:54:56 +00:00
|
|
|
return createLocationObject( LocationType.Product, { productId } );
|
2024-02-12 19:59:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const isInGenericTemplate = prepareIsInGenericTemplate( templateSlug );
|
|
|
|
|
|
|
|
/**
|
2024-04-02 07:05:17 +00:00
|
|
|
* Case 2.2: TEMPLATES: GENERIC PRODUCT
|
2024-02-12 19:59:40 +00:00
|
|
|
* Generic Single Product template
|
|
|
|
*/
|
|
|
|
|
|
|
|
const isInSingleProductTemplate = isInGenericTemplate(
|
|
|
|
templateSlugs.singleProduct
|
|
|
|
);
|
|
|
|
|
|
|
|
if ( isInSingleProductTemplate ) {
|
2024-04-05 13:54:56 +00:00
|
|
|
return createLocationObject( LocationType.Product, {
|
|
|
|
productId: null,
|
|
|
|
} );
|
2024-02-12 19:59:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2024-04-02 07:05:17 +00:00
|
|
|
* Case 2.3: TEMPLATES: SPECIFIC TAXONOMY
|
2024-02-12 19:59:40 +00:00
|
|
|
* Specific Category template - take category ID from
|
|
|
|
*/
|
|
|
|
|
|
|
|
if ( isInSpecificCategoryTemplate ) {
|
2024-04-05 13:54:56 +00:00
|
|
|
return createLocationObject( LocationType.Archive, {
|
2024-02-12 19:59:40 +00:00
|
|
|
taxonomy: 'product_cat',
|
|
|
|
termId: categoryId,
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2024-04-02 07:05:17 +00:00
|
|
|
* Case 2.4: TEMPLATES: SPECIFIC TAXONOMY
|
2024-02-12 19:59:40 +00:00
|
|
|
* Specific Tag template
|
|
|
|
*/
|
|
|
|
|
|
|
|
if ( isInSpecificTagTemplate ) {
|
2024-04-05 13:54:56 +00:00
|
|
|
return createLocationObject( LocationType.Archive, {
|
2024-02-12 19:59:40 +00:00
|
|
|
taxonomy: 'product_tag',
|
|
|
|
termId: tagId,
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2024-04-02 07:05:17 +00:00
|
|
|
* Case 2.5: TEMPLATES: GENERIC TAXONOMY
|
2024-02-12 19:59:40 +00:00
|
|
|
* Generic Taxonomy template
|
|
|
|
*/
|
|
|
|
|
|
|
|
const isInProductsByCategoryTemplate = isInGenericTemplate(
|
|
|
|
templateSlugs.productCategory
|
|
|
|
);
|
|
|
|
|
|
|
|
if ( isInProductsByCategoryTemplate ) {
|
2024-04-05 13:54:56 +00:00
|
|
|
return createLocationObject( LocationType.Archive, {
|
2024-02-12 19:59:40 +00:00
|
|
|
taxonomy: 'product_cat',
|
|
|
|
termId: null,
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
|
|
|
const isInProductsByTagTemplate = isInGenericTemplate(
|
|
|
|
templateSlugs.productTag
|
|
|
|
);
|
|
|
|
|
|
|
|
if ( isInProductsByTagTemplate ) {
|
2024-04-05 13:54:56 +00:00
|
|
|
return createLocationObject( LocationType.Archive, {
|
2024-02-12 19:59:40 +00:00
|
|
|
taxonomy: 'product_tag',
|
|
|
|
termId: null,
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
|
|
|
const isInProductsByAttributeTemplate = isInGenericTemplate(
|
|
|
|
templateSlugs.productAttribute
|
|
|
|
);
|
|
|
|
|
|
|
|
if ( isInProductsByAttributeTemplate ) {
|
2024-04-05 13:54:56 +00:00
|
|
|
return createLocationObject( LocationType.Archive, {
|
2024-02-12 19:59:40 +00:00
|
|
|
taxonomy: null,
|
|
|
|
termId: null,
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2024-04-02 07:05:17 +00:00
|
|
|
* Case 2.6: TEMPLATES: GENERIC CART
|
|
|
|
* Cart/Checkout templates
|
2024-02-12 19:59:40 +00:00
|
|
|
*/
|
|
|
|
|
2024-04-02 07:05:17 +00:00
|
|
|
const isInCartCheckoutTemplate =
|
2024-02-12 19:59:40 +00:00
|
|
|
templateSlug === templateSlugs.cart ||
|
2024-04-02 07:05:17 +00:00
|
|
|
templateSlug === templateSlugs.checkout;
|
2024-02-12 19:59:40 +00:00
|
|
|
|
2024-04-02 07:05:17 +00:00
|
|
|
if ( isInCartCheckoutTemplate ) {
|
2024-04-05 13:54:56 +00:00
|
|
|
return createLocationObject( LocationType.Cart );
|
2024-02-12 19:59:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2024-04-02 07:05:17 +00:00
|
|
|
* Case 2.7: TEMPLATES: GENERIC ORDER
|
2024-02-12 19:59:40 +00:00
|
|
|
* Order Confirmation template
|
|
|
|
*/
|
|
|
|
|
|
|
|
const isInOrderTemplate = isInGenericTemplate(
|
|
|
|
templateSlugs.orderConfirmation
|
|
|
|
);
|
|
|
|
|
|
|
|
if ( isInOrderTemplate ) {
|
2024-04-05 13:54:56 +00:00
|
|
|
return createLocationObject( LocationType.Order );
|
2024-02-12 19:59:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2024-04-02 07:05:17 +00:00
|
|
|
* Case 3: GENERIC
|
2024-02-12 19:59:40 +00:00
|
|
|
* All other cases
|
|
|
|
*/
|
|
|
|
|
2024-04-05 13:54:56 +00:00
|
|
|
return createLocationObject( LocationType.Site );
|
2024-02-12 19:59:40 +00:00
|
|
|
};
|
Product Collection: Make attributes available in rest_product_query hook (#44150)
* Make attributes available in rest_product_query hook
This commit introduces the 'includeInQueryContext' attribute to the 'woocommerce/product-collection' block and updates the 'woocommerce/product-template' block to consume this new attribute.
Key Changes:
1. `woocommerce/product-collection` Block:
- A new attribute 'includeInQueryContext' is added in `block.json`. This attribute is designed to hold a list of attribute names relevant for the query context.
- The 'includeInQueryContext' attribute is included in the `providesContext` field to ensure its availability to child blocks.
- In `constants.ts`, default values for 'includeInQueryContext' are defined, specifying 'collection' and 'id' as initial attributes.
- The `types.ts` file is updated with a comment explaining the purpose of 'includeInQueryContext'.
2. `woocommerce/product-template` Block:
- Modified `block.json` to utilize the 'includeInQueryContext' context provided by the parent `woocommerce/product-collection` block.
- The `edit.tsx` file is updated to handle the new context. It uses a newly added utility function `useProductCollectionBlockAttributes` from `utils.tsx` to access parent block attributes.
- The `utils.tsx` file is introduced, containing the `useProductCollectionBlockAttributes` hook. This hook is responsible for finding the parent 'woocommerce/product-collection' block and returning its attributes.
- Within `edit.tsx`, logic is added to create a query context object based on the attributes specified in 'includeInQueryContext', enhancing the block's ability to dynamically adapt to changes.
* Remove commented code
* Rename query context attribute and optimize parent block detection
This commit introduces two significant changes aimed at improving code readability and efficiency.
1. **Renaming of Query Context Attribute:**
- The attribute `includeInQueryContext` has been renamed to `queryContextIncludes` across various files, including block JSON configurations and TypeScript definitions. This change makes the attribute's purpose more intuitive, indicating it specifies which attributes to include in the query context.
2. **Optimized Parent Block Detection:**
- Replaced the use of `getBlockParents` with `getBlockParentsByBlockName` in utility functions to find the closest Product Collection block. This optimization allows for a more direct and efficient way to identify the relevant parent block by specifying the block name, reducing unnecessary iterations and improving code performance.
* Streamline query context construction in product template
Key Changes:
- **Introduction of `useProductCollectionQueryContext` Hook:** This new hook takes the `clientId` and `queryContextIncludes` as inputs and returns a query context object. It encapsulates the logic for fetching parent product collection block attributes and constructing the query context accordingly. This abstraction simplifies the edit component's logic, focusing on the essentials and improving code readability.
- **Optimization of Parent Block Detection:** The hook uses `getBlockParentsByBlockName` to accurately and efficiently find the closest parent `Product Collection` block, minimizing the overhead previously associated with traversing the block hierarchy.
* Always include `collection` and `id` in query context
* Minor refactor
* Enhance query context handling for more maintainable code
- Introduced `DEFAULT_QUERY_CONTEXT_ATTRIBUTES` in `edit.tsx` to maintain a clear list of default query context attributes.
- Modified `ProductTemplateEdit` to automatically include these default attributes in `queryContextIncludes`, ensuring they are always part of the query context without manual initialization.
- Simplified `useProductCollectionQueryContext` in `utils.tsx` by removing static initialization of 'collection' and 'id', relying instead on the dynamic addition of necessary attributes from `queryContextIncludes`.
This refactor enhances the maintainability and clarity of the code, ensuring a solid foundation for future enhancements and features.
* Add E2E tests for Product Collection query context
- Added a new test suite 'Query Context in Editor' to validate the correctness of query context parameters when the Product Collection block is used. This suite ensures that:
- For the 'Product Catalog', only the ID is sent in the query context, confirming that collection-specific parameters are excluded when not relevant.
- For collections such as 'On Sale', the collection name is correctly passed in the query context, validating that the block dynamically adjusts query parameters based on its settings.
- Introduced a new utility method `setupAndFetchQueryContextURL` in `product-collection.page.ts`. This method automates the setup of a post with the Product Collection block and fetches the URL with query context parameters, facilitating the validation of query context handling.
* Add changefile(s) from automation for the following project(s): woocommerce-blocks
* Fix edge case when `queryContextIncludes` is not defined
- Initializing `queryContextIncludes` with a default empty array directly in the destructuring assignment of the component's props. This approach ensures that `queryContextIncludes` is always an array, simplifying downstream logic.
- Creating a new constant `queryContextIncludesWithDefaults` to hold the combination of `queryContextIncludes` and `DEFAULT_QUERY_CONTEXT_ATTRIBUTES`. This step avoids directly mutating the `queryContextIncludes` prop, aligning with best practices for functional purity and making the code easier to understand and debug.
- Updating the `useProductCollectionQueryContext` hook call to use `queryContextIncludesWithDefaults`. This ensures that the default query context attributes are consistently included without altering the original prop.
These adjustments not only enhance the code's maintainability but also ensure more predictable behavior by avoiding side effects related to parameter mutation.
---------
Co-authored-by: github-actions <github-actions@github.com>
2024-02-12 08:45:24 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* In Product Collection block, queryContextIncludes attribute contains
|
|
|
|
* list of attribute names that should be included in the query context.
|
|
|
|
*
|
|
|
|
* This hook returns the query context object based on the attribute names
|
|
|
|
* provided in the queryContextIncludes array.
|
|
|
|
*
|
|
|
|
* Example:
|
|
|
|
* {
|
|
|
|
* clientID = 'd2c7e34f-70d6-417c-b582-f554a3a575f3',
|
|
|
|
* queryContextIncludes = [ 'collection' ]
|
|
|
|
* }
|
|
|
|
*
|
|
|
|
* The hook will return the following query context object:
|
|
|
|
* {
|
|
|
|
* collection: 'woocommerce/product-collection/featured'
|
|
|
|
* }
|
|
|
|
*
|
|
|
|
* @param args Arguments for the hook.
|
|
|
|
* @param args.clientId Client ID of the inner block.
|
|
|
|
* @param args.queryContextIncludes Array of attribute names to be included in the query context.
|
|
|
|
*
|
|
|
|
* @return Query context object.
|
|
|
|
*/
|
|
|
|
export const useProductCollectionQueryContext = ( {
|
|
|
|
clientId,
|
|
|
|
queryContextIncludes,
|
|
|
|
}: {
|
|
|
|
clientId: string;
|
|
|
|
queryContextIncludes: string[];
|
|
|
|
} ) => {
|
|
|
|
const productCollectionBlockAttributes = useSelect(
|
|
|
|
( select ) => {
|
|
|
|
const { getBlockParentsByBlockName, getBlockAttributes } =
|
|
|
|
select( 'core/block-editor' );
|
|
|
|
|
|
|
|
const parentBlocksClientIds = getBlockParentsByBlockName(
|
|
|
|
clientId,
|
|
|
|
'woocommerce/product-collection',
|
|
|
|
true
|
|
|
|
);
|
|
|
|
|
|
|
|
if ( parentBlocksClientIds?.length ) {
|
|
|
|
const closestParentClientId = parentBlocksClientIds[ 0 ];
|
|
|
|
return getBlockAttributes( closestParentClientId );
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
[ clientId ]
|
|
|
|
);
|
|
|
|
|
|
|
|
return useMemo( () => {
|
|
|
|
// If the product collection block is not found, return null.
|
|
|
|
if ( ! productCollectionBlockAttributes ) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
const queryContext: {
|
|
|
|
[ key: string ]: unknown;
|
|
|
|
} = {};
|
|
|
|
|
|
|
|
if ( queryContextIncludes?.length ) {
|
|
|
|
queryContextIncludes.forEach( ( attribute: string ) => {
|
|
|
|
if ( productCollectionBlockAttributes?.[ attribute ] ) {
|
|
|
|
queryContext[ attribute ] =
|
|
|
|
productCollectionBlockAttributes[ attribute ];
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
|
|
|
return queryContext;
|
|
|
|
}, [ queryContextIncludes, productCollectionBlockAttributes ] );
|
|
|
|
};
|
2024-07-29 12:21:44 +00:00
|
|
|
|
|
|
|
export const parseTemplateSlug = ( rawTemplateSlug = '' ) => {
|
|
|
|
const categoryPrefix = 'category-';
|
|
|
|
const productCategoryPrefix = 'taxonomy-product_cat-';
|
|
|
|
const productTagPrefix = 'taxonomy-product_tag-';
|
|
|
|
|
|
|
|
if ( rawTemplateSlug.startsWith( categoryPrefix ) ) {
|
|
|
|
return {
|
|
|
|
taxonomy: 'category',
|
|
|
|
slug: rawTemplateSlug.replace( categoryPrefix, '' ),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( rawTemplateSlug.startsWith( productCategoryPrefix ) ) {
|
|
|
|
return {
|
|
|
|
taxonomy: 'product_cat',
|
|
|
|
slug: rawTemplateSlug.replace( productCategoryPrefix, '' ),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( rawTemplateSlug.startsWith( productTagPrefix ) ) {
|
|
|
|
return {
|
|
|
|
taxonomy: 'product_tag',
|
|
|
|
slug: rawTemplateSlug.replace( productTagPrefix, '' ),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return { taxonomy: '', slug: '' };
|
|
|
|
};
|