Fix: Ensure new filter blocks work with inherited query context (https://github.com/woocommerce/woocommerce-blocks/pull/12022)
* make the collection filters block available globally * make query optional * remove unnecessary editor contextual
This commit is contained in:
parent
945e476648
commit
2bc48e4e4a
|
@ -19,9 +19,6 @@
|
|||
"providesContext": {
|
||||
"collectionData": "collectionData"
|
||||
},
|
||||
"ancestor": [
|
||||
"woocommerce/product-collection"
|
||||
],
|
||||
"attributes": {
|
||||
"collectionData": {
|
||||
"type": "object",
|
||||
|
|
|
@ -2,19 +2,10 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { useEffect } from '@wordpress/element';
|
||||
import { useCollection } from '@woocommerce/base-context/hooks';
|
||||
import { sprintf, __ } from '@wordpress/i18n';
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
import type { AttributeSetting } from '@woocommerce/types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { formatQuery, getQueryParams } from './utils';
|
||||
import type { EditProps } from './type';
|
||||
|
||||
const ATTRIBUTES = getSetting< AttributeSetting[] >( 'attributes', [] );
|
||||
|
||||
const template = [
|
||||
|
@ -63,32 +54,12 @@ if ( firstAttribute ) {
|
|||
);
|
||||
}
|
||||
|
||||
const Edit = ( { clientId, setAttributes, context }: EditProps ) => {
|
||||
const Edit = () => {
|
||||
const blockProps = useBlockProps();
|
||||
const innerBlockProps = useInnerBlocksProps( blockProps, {
|
||||
template,
|
||||
} );
|
||||
|
||||
// Get inner blocks by clientId
|
||||
const currentBlock = useSelect( ( select ) => {
|
||||
return select( 'core/block-editor' ).getBlock( clientId );
|
||||
} );
|
||||
|
||||
const { results } = useCollection( {
|
||||
namespace: '/wc/store/v1',
|
||||
resourceName: 'products/collection-data',
|
||||
query: {
|
||||
...formatQuery( context.query ),
|
||||
...getQueryParams( currentBlock ),
|
||||
},
|
||||
} );
|
||||
|
||||
useEffect( () => {
|
||||
setAttributes( {
|
||||
collectionData: results,
|
||||
} );
|
||||
}, [ results, setAttributes ] );
|
||||
|
||||
return <nav { ...innerBlockProps } />;
|
||||
};
|
||||
|
||||
|
|
|
@ -5,7 +5,10 @@ import { __ } from '@wordpress/i18n';
|
|||
import { useEffect, useState } from '@wordpress/element';
|
||||
import { BlockControls, useBlockProps } from '@wordpress/block-editor';
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
import { useCollection } from '@woocommerce/base-context/hooks';
|
||||
import {
|
||||
useCollection,
|
||||
useCollectionData,
|
||||
} from '@woocommerce/base-context/hooks';
|
||||
import {
|
||||
AttributeSetting,
|
||||
AttributeTerm,
|
||||
|
@ -41,7 +44,6 @@ const Edit = ( props: EditProps ) => {
|
|||
attributes: blockAttributes,
|
||||
setAttributes,
|
||||
debouncedSpeak,
|
||||
context,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
|
@ -70,6 +72,15 @@ const Edit = ( props: EditProps ) => {
|
|||
query: { orderby: 'menu_order' },
|
||||
} );
|
||||
|
||||
const { results: filteredCounts } = useCollectionData( {
|
||||
queryAttribute: {
|
||||
taxonomy: attributeObject?.taxonomy || '',
|
||||
queryType: blockAttributes.queryType,
|
||||
},
|
||||
queryState: {},
|
||||
isEditor: true,
|
||||
} );
|
||||
|
||||
const blockProps = useBlockProps();
|
||||
|
||||
useEffect( () => {
|
||||
|
@ -103,11 +114,9 @@ const Edit = ( props: EditProps ) => {
|
|||
|
||||
useEffect( () => {
|
||||
const termIdHasProducts =
|
||||
objectHasProp( context.collectionData, 'attribute_counts' ) &&
|
||||
isAttributeCounts( context.collectionData.attribute_counts )
|
||||
? context.collectionData.attribute_counts.map(
|
||||
( term ) => term.term
|
||||
)
|
||||
objectHasProp( filteredCounts, 'attribute_counts' ) &&
|
||||
isAttributeCounts( filteredCounts.attribute_counts )
|
||||
? filteredCounts.attribute_counts.map( ( term ) => term.term )
|
||||
: [];
|
||||
|
||||
if ( termIdHasProducts.length === 0 ) return setAttributeOptions( [] );
|
||||
|
@ -117,7 +126,7 @@ const Edit = ( props: EditProps ) => {
|
|||
return termIdHasProducts.includes( term.id );
|
||||
} )
|
||||
);
|
||||
}, [ attributeTerms, context.collectionData ] );
|
||||
}, [ attributeTerms, filteredCounts ] );
|
||||
|
||||
const Toolbar = () => (
|
||||
<BlockControls>
|
||||
|
|
|
@ -15,9 +15,6 @@ export type BlockAttributes = {
|
|||
|
||||
export interface EditProps extends BlockEditProps< BlockAttributes > {
|
||||
debouncedSpeak: ( label: string ) => void;
|
||||
context: {
|
||||
collectionData: unknown[];
|
||||
};
|
||||
}
|
||||
|
||||
type AttributeCount = {
|
||||
|
|
|
@ -15,12 +15,9 @@ import {
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import type { FilterComponentProps } from '../types';
|
||||
import type { EditProps } from '../types';
|
||||
|
||||
export const Inspector = ( {
|
||||
attributes,
|
||||
setAttributes,
|
||||
}: Omit< FilterComponentProps, 'collectionData' > ) => {
|
||||
export const Inspector = ( { attributes, setAttributes }: EditProps ) => {
|
||||
const { showInputFields, inlineInput } = attributes;
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useCollectionData } from '@woocommerce/base-context/hooks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
|
@ -8,10 +13,19 @@ import { getFormattedPrice } from '../utils';
|
|||
* We pass the whole props from Edit component to <PriceSlider/> so we're
|
||||
* reusing the EditProps type here.
|
||||
*/
|
||||
export const PriceSlider = ( { attributes, context }: EditProps ) => {
|
||||
export const PriceSlider = ( { attributes }: EditProps ) => {
|
||||
const { showInputFields } = attributes;
|
||||
|
||||
const { results, isLoading } = useCollectionData( {
|
||||
queryPrices: true,
|
||||
queryState: {},
|
||||
isEditor: true,
|
||||
} );
|
||||
|
||||
if ( isLoading ) return null;
|
||||
|
||||
const { minPrice, maxPrice, formattedMinPrice, formattedMaxPrice } =
|
||||
getFormattedPrice( context.collectionData );
|
||||
getFormattedPrice( results );
|
||||
|
||||
const onChange = () => null;
|
||||
|
||||
|
|
|
@ -8,11 +8,7 @@ export type BlockAttributes = {
|
|||
inlineInput: boolean;
|
||||
};
|
||||
|
||||
export interface EditProps extends BlockEditProps< BlockAttributes > {
|
||||
context: {
|
||||
collectionData: unknown[];
|
||||
};
|
||||
}
|
||||
export type EditProps = BlockEditProps< BlockAttributes >;
|
||||
|
||||
export type PriceFilterState = {
|
||||
minPrice?: number;
|
||||
|
@ -23,7 +19,3 @@ export type PriceFilterState = {
|
|||
formattedMinPrice: string;
|
||||
formattedMaxPrice: string;
|
||||
};
|
||||
|
||||
export type FilterComponentProps = BlockEditProps< BlockAttributes > & {
|
||||
collectionData: Partial< PriceFilterState >;
|
||||
};
|
||||
|
|
|
@ -7,16 +7,12 @@ import { useBlockProps } from '@wordpress/block-editor';
|
|||
import { Disabled } from '@wordpress/components';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Icon, chevronDown } from '@wordpress/icons';
|
||||
import { ProductQueryContext as Context } from '@woocommerce/blocks/product-query/types';
|
||||
import { CheckboxList } from '@woocommerce/blocks-components';
|
||||
import Label from '@woocommerce/base-components/filter-element-label';
|
||||
import FormTokenField from '@woocommerce/base-components/form-token-field';
|
||||
import type { BlockEditProps } from '@wordpress/blocks';
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
import {
|
||||
useCollectionData,
|
||||
useQueryStateByContext,
|
||||
} from '@woocommerce/base-context/hooks';
|
||||
import { useCollectionData } from '@woocommerce/base-context/hooks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -36,7 +32,7 @@ type StockStatusCount = {
|
|||
count: number;
|
||||
};
|
||||
|
||||
const Edit = ( props: BlockEditProps< BlockProps > & { context: Context } ) => {
|
||||
const Edit = ( props: BlockEditProps< BlockProps > ) => {
|
||||
const blockProps = useBlockProps( {
|
||||
className: classnames(
|
||||
'wc-block-stock-filter',
|
||||
|
@ -50,11 +46,9 @@ const Edit = ( props: BlockEditProps< BlockProps > & { context: Context } ) => {
|
|||
{}
|
||||
);
|
||||
|
||||
const [ queryState ] = useQueryStateByContext();
|
||||
|
||||
const { results: filteredCounts } = useCollectionData( {
|
||||
queryStock: true,
|
||||
queryState,
|
||||
queryState: {},
|
||||
isEditor: true,
|
||||
} );
|
||||
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { DEFAULT_QUERY } from '@woocommerce/blocks/product-collection/constants';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { sharedParams, mappedParams, formatQuery } from '../utils';
|
||||
|
||||
describe( 'formatQuery: transform Product Collection Block query to Product Collection Data Store API query', () => {
|
||||
it( 'shared param is carried over', () => {
|
||||
const formattedQuery = formatQuery( DEFAULT_QUERY );
|
||||
sharedParams.forEach( ( key ) => {
|
||||
expect( formattedQuery ).toHaveProperty(
|
||||
key,
|
||||
DEFAULT_QUERY[ key ]
|
||||
);
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'mapped param key is transformed', () => {
|
||||
const formattedQuery = formatQuery( DEFAULT_QUERY );
|
||||
mappedParams.forEach( ( { key, map } ) => {
|
||||
expect( formattedQuery ).toHaveProperty(
|
||||
map,
|
||||
DEFAULT_QUERY[ key ]
|
||||
);
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'taxQuery is transformed', () => {
|
||||
const queryWithTax = Object.assign( {}, DEFAULT_QUERY, {
|
||||
taxQuery: {
|
||||
product_cat: [ 1, 2 ],
|
||||
product_tag: [ 3, 4 ],
|
||||
custom_taxonomy: [ 5, 6 ],
|
||||
},
|
||||
} );
|
||||
const formattedQuery = formatQuery( queryWithTax );
|
||||
expect( formattedQuery ).toHaveProperty( 'cat', [ 1, 2 ] );
|
||||
expect( formattedQuery ).toHaveProperty( 'tag', [ 3, 4 ] );
|
||||
expect( formattedQuery ).toHaveProperty(
|
||||
'_unstable_tax_custom_taxonomy',
|
||||
[ 5, 6 ]
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'attribute query is transformed', () => {
|
||||
const woocommerceAttributes = [
|
||||
{ termId: 11, taxonomy: 'pa_size' },
|
||||
{ termId: 12, taxonomy: 'pa_color' },
|
||||
{ termId: 13, taxonomy: 'pa_custom' },
|
||||
{ termId: 14, taxonomy: 'pa_custom' },
|
||||
];
|
||||
const queryWithAttributes = Object.assign( {}, DEFAULT_QUERY, {
|
||||
woocommerceAttributes,
|
||||
} );
|
||||
const formattedQuery = formatQuery( queryWithAttributes );
|
||||
|
||||
expect( formattedQuery ).toHaveProperty( 'attributes' );
|
||||
|
||||
woocommerceAttributes.forEach( ( { termId, taxonomy } ) => {
|
||||
expect( formattedQuery.attributes ).toEqual(
|
||||
expect.arrayContaining( [
|
||||
{ term_id: termId, attribute: taxonomy },
|
||||
] )
|
||||
);
|
||||
} );
|
||||
} );
|
||||
} );
|
|
@ -1,15 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import type { BlockEditProps } from '@wordpress/blocks';
|
||||
import type { ProductCollectionQuery } from '@woocommerce/blocks/product-collection/types';
|
||||
|
||||
type BlockAttributes = {
|
||||
collectionData: unknown[];
|
||||
};
|
||||
|
||||
export interface EditProps extends BlockEditProps< BlockAttributes > {
|
||||
context: {
|
||||
query: ProductCollectionQuery;
|
||||
};
|
||||
}
|
|
@ -1,116 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { objectHasProp } from '@woocommerce/types';
|
||||
import type { BlockInstance } from '@wordpress/blocks';
|
||||
import type { ProductCollectionQuery } from '@woocommerce/blocks/product-collection/types';
|
||||
|
||||
function mergeAttributeParams(
|
||||
acc: Record< string, unknown >,
|
||||
innerBlock: BlockInstance
|
||||
) {
|
||||
const current =
|
||||
( acc?.calculate_attribute_counts as Array< unknown > ) ?? [];
|
||||
acc.calculate_attribute_counts = [
|
||||
...current,
|
||||
innerBlock.attributes.queryParam.calculate_attribute_counts,
|
||||
];
|
||||
return acc;
|
||||
}
|
||||
|
||||
function getInnerBlocksParams(
|
||||
block: BlockInstance,
|
||||
initial: Record< string, unknown > = {}
|
||||
) {
|
||||
return block.innerBlocks.reduce(
|
||||
( acc, innerBlock ): Record< string, unknown > => {
|
||||
if (
|
||||
objectHasProp(
|
||||
innerBlock.attributes.queryParam,
|
||||
'calculate_attribute_counts'
|
||||
)
|
||||
) {
|
||||
acc = mergeAttributeParams( acc, innerBlock );
|
||||
} else {
|
||||
acc = { ...acc, ...innerBlock.attributes?.queryParam };
|
||||
}
|
||||
|
||||
return getInnerBlocksParams( innerBlock, acc );
|
||||
},
|
||||
initial
|
||||
);
|
||||
}
|
||||
|
||||
export function getQueryParams( block: BlockInstance | null ) {
|
||||
if ( ! block ) return {};
|
||||
|
||||
return getInnerBlocksParams( block );
|
||||
}
|
||||
|
||||
export const sharedParams: Array< keyof ProductCollectionQuery > = [
|
||||
'exclude',
|
||||
'offset',
|
||||
'search',
|
||||
];
|
||||
|
||||
/**
|
||||
* There is an open dicussion around the shape of this object. Check it out on GH.
|
||||
*
|
||||
* @see {@link https://github.com/woocommerce/woocommerce-blocks/pull/11218#discussion_r1365171167 | #11218 review comment}.
|
||||
*/
|
||||
export const mappedParams: {
|
||||
key: keyof ProductCollectionQuery;
|
||||
map: string;
|
||||
}[] = [
|
||||
{ key: 'woocommerceStockStatus', map: 'stock_status' },
|
||||
{ key: 'woocommerceOnSale', map: 'on_sale' },
|
||||
{ key: 'woocommerceHandPickedProducts', map: 'include' },
|
||||
];
|
||||
|
||||
function mapTaxonomy( taxonomy: string ) {
|
||||
const map = {
|
||||
product_tag: 'tag',
|
||||
product_cat: 'cat',
|
||||
};
|
||||
|
||||
return map[ taxonomy as keyof typeof map ] || `_unstable_tax_${ taxonomy }`;
|
||||
}
|
||||
|
||||
function getTaxQueryMap( taxQuery: ProductCollectionQuery[ 'taxQuery' ] ) {
|
||||
return Object.entries( taxQuery ).map( ( [ taxonomy, terms ] ) => ( {
|
||||
[ mapTaxonomy( taxonomy ) ]: terms,
|
||||
} ) );
|
||||
}
|
||||
|
||||
function getAttributeQuery(
|
||||
woocommerceAttributes: ProductCollectionQuery[ 'woocommerceAttributes' ]
|
||||
) {
|
||||
if ( ! woocommerceAttributes ) {
|
||||
return {};
|
||||
}
|
||||
return woocommerceAttributes.map( ( attribute ) => ( {
|
||||
attribute: attribute.taxonomy,
|
||||
term_id: attribute.termId,
|
||||
} ) );
|
||||
}
|
||||
|
||||
export function formatQuery( query: ProductCollectionQuery ) {
|
||||
if ( ! query ) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return Object.assign(
|
||||
{
|
||||
attributes: getAttributeQuery( query.woocommerceAttributes ),
|
||||
catalog_visibility: 'visible',
|
||||
},
|
||||
...sharedParams.map(
|
||||
( key ) => key in query && { [ key ]: query[ key ] }
|
||||
),
|
||||
...mappedParams.map(
|
||||
( param ) =>
|
||||
param.key in query && { [ param.map ]: query[ param.key ] }
|
||||
),
|
||||
...getTaxQueryMap( query.taxQuery )
|
||||
);
|
||||
}
|
|
@ -128,7 +128,7 @@ final class CollectionFilters extends AbstractBlock {
|
|||
$response = Package::container()->get( Hydration::class )->get_rest_api_response_data(
|
||||
add_query_arg(
|
||||
array_merge(
|
||||
$this->get_formatted_products_params( $block->context['query'] ),
|
||||
$this->get_formatted_products_params( $block->context['query'] ?? array() ),
|
||||
$collection_data_params,
|
||||
),
|
||||
'/wc/store/v1/products/collection-data'
|
||||
|
|
Loading…
Reference in New Issue