Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Product Query: pass any product taxonomies existing in the URL parameters. #6152

Merged
merged 11 commits into from
Apr 21, 2022

Conversation

cpapazoglou
Copy link
Contributor

@cpapazoglou cpapazoglou commented Mar 29, 2022

While working on 12894-gh-Automattic/woocommerce.com I have noticed that there isn't a way to filter products based on custom product taxonomies. The code only allows a static list of product_cat and product_tag. This PR aims at using any product taxonomy present in the URL parameters to build the $query_args for the WP_Query.

Related convo: p1648465508181829-slack-C8X6Q7XQU

Automated Tests

  • Changes in this PR are covered by Automated Tests.
    • Unit tests
    • E2E tests

Manual Testing

How to test the changes in this Pull Request:

  • On your local environment register a new taxonomy
$labels = array(
	'name'                       => 'Custom Category',
	'singular_name'              => 'Category',
	'menu_name'                  => 'Categories',
	'all_items'                  => 'All Categories',
	'parent_item'                => 'Parent Category',
	'parent_item_colon'          => 'Parent Category:',
	'new_item_name'              => 'New Category Name',
	'add_new_item'               => 'Add New Category',
	'edit_item'                  => 'Edit Category',
	'update_item'                => 'Update Category',
	'separate_items_with_commas' => 'Separate Category with commas',
	'search_items'               => 'Search Categories',
	'add_or_remove_items'        => 'Add or remove Categories',
	'choose_from_most_used'      => 'Choose from the most used Categories',
);
$taxonomy_args = array(
	'labels'       => $labels,
	'description'  => 'A new custom product taxonomy',
	'hierarchical' => true,
	'public'       => true,
	'show_ui'      => true,
);
register_taxonomy( 'custom_categories', 'product', $taxonomy_args );
  • Edit a product and add to it a new term of the custom_categories taxonomy
  • Go to /wp-json/wc/store/products?_unstable_tax_custom_categories=[that-category-id] and make sure you get only products where this term was added
  • Go to /wp-json/wc/store/products and make sure that all products get returned as expected

User Facing Testing

These are steps for user testing (where "user" is someone interacting with this change that is not editing any code).

  • Same as above, or
  • See steps below.
  1. Add an All Products Block to a page, add the Filter Products by Attribute Block, and Filter by Price Block too.
  2. Go to the page and use the filters, ensure they update the All Products Block correctly.

Changelog

Enhancements

Allow Store API to filter products by custom taxonomies.

Bug Fixes

Fix not_in operator for category, category, and attribute params.

Release Note

Store API
As part of enhancing the Store API, we added a way to filter products by custom defined product taxonomies. The are two new parameters that can be passed to the API and should follow the dynamic patters _unstable_tax_[product-taxonomy] and _unstable_tax_[product-taxonomy]_operator. They work the same way as category and category_operator but in this case you should replace [product-taxonomy] with any custom defined product taxonomy. eg
?_unstable_tax_hot_categories=1,2,3&_unstable_tax_hot_categories_operator=in
will return all products appearing in hot_categories taxonomy for terms 1 or 2 or 3.

@cpapazoglou cpapazoglou self-assigned this Mar 29, 2022
@github-actions
Copy link
Contributor

github-actions bot commented Mar 29, 2022

Size Change: 0 B

Total Size: 868 kB

ℹ️ View Unchanged
Filename Size
build/active-filters-frontend.js 5.92 kB
build/active-filters.js 6.59 kB
build/all-products-frontend.js 18.1 kB
build/all-products.js 33.4 kB
build/all-reviews.js 7.78 kB
build/attribute-filter-frontend.js 17.6 kB
build/attribute-filter.js 13.5 kB
build/blocks-checkout.js 17.4 kB
build/cart-blocks/cart-accepted-payment-methods-frontend.js 1.16 kB
build/cart-blocks/cart-express-payment-frontend.js 5.24 kB
build/cart-blocks/cart-items-frontend.js 299 B
build/cart-blocks/cart-line-items--mini-cart-contents-block/products-table-frontend.js 5.27 kB
build/cart-blocks/cart-line-items-frontend.js 432 B
build/cart-blocks/cart-order-summary-frontend.js 1.11 kB
build/cart-blocks/cart-totals-frontend.js 322 B
build/cart-blocks/empty-cart-frontend.js 345 B
build/cart-blocks/filled-cart-frontend.js 783 B
build/cart-blocks/order-summary-coupon-form-frontend.js 2.81 kB
build/cart-blocks/order-summary-discount-frontend.js 2.31 kB
build/cart-blocks/order-summary-fee-frontend.js 273 B
build/cart-blocks/order-summary-heading-frontend.js 454 B
build/cart-blocks/order-summary-shipping--checkout-blocks/order-summary-shipping-frontend.js 6.34 kB
build/cart-blocks/order-summary-shipping-frontend.js 429 B
build/cart-blocks/order-summary-subtotal-frontend.js 274 B
build/cart-blocks/order-summary-taxes-frontend.js 433 B
build/cart-blocks/proceed-to-checkout-frontend.js 1.15 kB
build/cart-frontend.js 45.5 kB
build/cart.js 44.3 kB
build/checkout-blocks/actions-frontend.js 1.41 kB
build/checkout-blocks/billing-address--checkout-blocks/shipping-address-frontend.js 4.12 kB
build/checkout-blocks/billing-address-frontend.js 892 B
build/checkout-blocks/contact-information-frontend.js 2.83 kB
build/checkout-blocks/express-payment-frontend.js 5.54 kB
build/checkout-blocks/fields-frontend.js 345 B
build/checkout-blocks/order-note-frontend.js 1.07 kB
build/checkout-blocks/order-summary-cart-items-frontend.js 3.67 kB
build/checkout-blocks/order-summary-coupon-form-frontend.js 2.96 kB
build/checkout-blocks/order-summary-discount-frontend.js 2.43 kB
build/checkout-blocks/order-summary-fee-frontend.js 275 B
build/checkout-blocks/order-summary-frontend.js 1.1 kB
build/checkout-blocks/order-summary-shipping-frontend.js 603 B
build/checkout-blocks/order-summary-subtotal-frontend.js 273 B
build/checkout-blocks/order-summary-taxes-frontend.js 432 B
build/checkout-blocks/payment-frontend.js 7.84 kB
build/checkout-blocks/shipping-address-frontend.js 996 B
build/checkout-blocks/shipping-methods-frontend.js 4.72 kB
build/checkout-blocks/terms-frontend.js 1.22 kB
build/checkout-blocks/totals-frontend.js 326 B
build/checkout-frontend.js 47.7 kB
build/checkout.js 45.6 kB
build/featured-category.js 12.1 kB
build/featured-product.js 10.6 kB
build/handpicked-products.js 7.11 kB
build/legacy-template.js 2.19 kB
build/mini-cart-component-frontend.js 16.6 kB
build/mini-cart-contents-block/empty-cart-frontend.js 326 B
build/mini-cart-contents-block/filled-cart-frontend.js 230 B
build/mini-cart-contents-block/footer--mini-cart-contents-block/products-table-frontend.js 4.69 kB
build/mini-cart-contents-block/footer-frontend.js 5.64 kB
build/mini-cart-contents-block/items-frontend.js 225 B
build/mini-cart-contents-block/products-table-frontend.js 288 B
build/mini-cart-contents-block/shopping-button-frontend.js 287 B
build/mini-cart-contents-block/title-frontend.js 366 B
build/mini-cart-contents.js 22.7 kB
build/mini-cart-frontend.js 1.72 kB
build/mini-cart.js 6.1 kB
build/price-filter-frontend.js 12.5 kB
build/price-filter.js 8.49 kB
build/price-format.js 1.19 kB
build/product-add-to-cart--product-button--product-category-list--product-image--product-price--product-r--a0326d00.js 223 B
build/product-add-to-cart--product-button--product-image--product-title.js 2.65 kB
build/product-add-to-cart--product-button.js 565 B
build/product-add-to-cart-frontend.js 6.96 kB
build/product-add-to-cart.js 6.64 kB
build/product-best-sellers.js 7.38 kB
build/product-button--product-category-list--product-image--product-price--product-rating--product-sale-b--e17c7c01.js 500 B
build/product-button-frontend.js 1.84 kB
build/product-button.js 1.08 kB
build/product-categories.js 2.78 kB
build/product-category-list-frontend.js 925 B
build/product-category-list.js 502 B
build/product-category.js 8.49 kB
build/product-image-frontend.js 1.84 kB
build/product-image.js 1.07 kB
build/product-new.js 7.68 kB
build/product-on-sale.js 7.99 kB
build/product-price-frontend.js 1.94 kB
build/product-price.js 1.5 kB
build/product-rating-frontend.js 1.15 kB
build/product-rating.js 730 B
build/product-sale-badge-frontend.js 1.09 kB
build/product-sale-badge.js 680 B
build/product-search.js 2.18 kB
build/product-sku-frontend.js 380 B
build/product-sku.js 382 B
build/product-stock-indicator-frontend.js 1.03 kB
build/product-stock-indicator.js 621 B
build/product-summary-frontend.js 1.33 kB
build/product-summary.js 918 B
build/product-tag-list-frontend.js 919 B
build/product-tag-list.js 496 B
build/product-tag.js 7.81 kB
build/product-title-frontend.js 1.3 kB
build/product-title.js 910 B
build/product-top-rated.js 7.91 kB
build/products-by-attribute.js 8.39 kB
build/reviews-by-category.js 11.2 kB
build/reviews-by-product.js 12.2 kB
build/reviews-frontend.js 7 kB
build/single-product-frontend.js 21.5 kB
build/single-product.js 10 kB
build/stock-filter-frontend.js 6.87 kB
build/stock-filter.js 6.92 kB
build/vendors--cart-blocks/cart-line-items--cart-blocks/cart-order-summary--cart-blocks/order-summary-shi--c02aad66-frontend.js 5.26 kB
build/vendors--cart-blocks/cart-line-items--checkout-blocks/order-summary-cart-items--mini-cart-contents---233ab542-frontend.js 3.14 kB
build/vendors--cart-blocks/order-summary-shipping--checkout-blocks/billing-address--checkout-blocks/order--5b8feb0b-frontend.js 4.75 kB
build/vendors--cart-blocks/order-summary-shipping--checkout-blocks/billing-address--checkout-blocks/order--decc3dc6-frontend.js 20.5 kB
build/vendors--mini-cart-contents-block/footer-frontend.js 6.86 kB
build/vendors--product-add-to-cart-frontend.js 7.53 kB
build/wc-blocks-data.js 9.87 kB
build/wc-blocks-editor-style-rtl.css 4.92 kB
build/wc-blocks-editor-style.css 4.92 kB
build/wc-blocks-google-analytics.js 1.56 kB
build/wc-blocks-middleware.js 930 B
build/wc-blocks-registry.js 2.7 kB
build/wc-blocks-shared-context.js 1.52 kB
build/wc-blocks-shared-hocs.js 1.14 kB
build/wc-blocks-style-rtl.css 22 kB
build/wc-blocks-style.css 22 kB
build/wc-blocks-vendors-style-rtl.css 1.28 kB
build/wc-blocks-vendors-style.css 1.28 kB
build/wc-blocks-vendors.js 71.4 kB
build/wc-blocks.js 2.63 kB
build/wc-payment-method-bacs.js 816 B
build/wc-payment-method-cheque.js 811 B
build/wc-payment-method-cod.js 909 B
build/wc-payment-method-paypal.js 837 B
build/wc-settings.js 2.61 kB

compressed-size-action

@cpapazoglou cpapazoglou marked this pull request as ready for review March 29, 2022 09:21
@cpapazoglou cpapazoglou requested review from a team, tarhi-saad and nielslange and removed request for a team, tarhi-saad and nielslange March 29, 2022 09:21
@nielslange nielslange requested review from a team and dinhtungdu and removed request for a team and dinhtungdu March 31, 2022 11:13
Copy link
Contributor

@Aljullu Aljullu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this @cpapazoglou! I definitely agree that's a good addition. I will leave the final review for @opr, I see you already discussed this implementation with him.

I left a couple of questions regarding the docs part below. I also wonder if we could add some tests (maybe here)?

| `sku` | string | no | Limit result set to products with specific SKU(s). Use commas to separate. |
| `featured` | boolean | no | Limit result set to featured products. |
| `category` | string | no | Limit result set to products assigned a specific category ID. |
| `product-taxonomy` | string | no | Limit result set to products assigned to the term ID of that custom product taxonomy. `product-taxonomy` should be the key of the custom product taxonomy registered. |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpicking, but I feel it's odd to list product-taxonomy between category and category_operator.

Can we move product-taxonomy above category or, even better, after category_operator?

And two more questions:

  • Should we also document product-category_operator?
  • And should we make it more explicit that product-category is a placeholder? Maybe enclosing it in square brackets or something along these lines?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great ideas, thanks @Aljullu

0de1d1d moves the product-taxonomy after category operator, encloses it in square brackets and also documents [product-taxonomy]_operator.

@Aljullu Aljullu requested a review from opr April 1, 2022 08:10
// Map between taxonomy name and arg key.
$taxonomies = [
$default_taxonomies = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't these $default_taxonomies also get returned by get_taxonomies()? If so, is there any need for $default_taxonomies and $taxonomies = array_merge( $default_taxonomies, $all_product_taxonomies );

Copy link
Contributor Author

@cpapazoglou cpapazoglou Apr 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question @tjcafferkey. get_taxonomies( array( 'object_type' => array( 'product' ) ), 'names' ); returns all product taxonomies including product_cat and product_tag. The thing is that the endpoint currently uses category instead of product_cat and tag instead of product_tag for parameters. So I have opted merging with $default_taxonomies for backwards compatibility. Would be preferable updating the docs and all code references (if any)?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thing is that the endpoint currently uses category instead of product_cat and tag instead of product_tag for parameters.

@cpapazoglou it looks like after the merge it uses product_cat and product_tag unless I am wrong?

Screenshot 2022-04-01 at 11 32 28

Copy link
Contributor Author

@cpapazoglou cpapazoglou Apr 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦, sorry for this and good catch. I was merging with wrong order. Fixed here 6885fd2

Works in my local machine 😁
CleanShot 2022-04-01 at 14 10 03@2x

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tjcafferkey is the following preferable?

$all_product_taxonomies = get_taxonomies( array( 'object_type' => array( 'product' ) ), 'names' );
$all_product_taxonomies['product_cat'] = 'category';
$all_product_taxonomies['product_tag'] = 'tag';

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @cpapazoglou this seems to work as expected now. However like @Aljullu mentioned I will leave the final review for @opr

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the following preferable?

I think as it currently is works fine.

@opr opr added status: needs review focus: rest api Work impacting REST api routes. type: task The issue is an internally driven task (e.g. from another A8c team). labels Apr 4, 2022
Copy link
Contributor

@opr opr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey thanks @Aljullu and @tjcafferkey for reviewing this for me while I was away.

@cpapazoglou it looks great, just wondering about the operator, the AND operator doesn't seem to work for me, could you check it for me? (in and not_in seem to be fine)

  • Set two products up assign two custom categories to the first one, to the second one give only one custom category.
  • Go to the following URL and expect to only see the product with two categories.
    /wp-json/wc/store/products?custom_categories=35,36&custom_categories_operator=and

Copy link
Member

@senadir senadir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for working on this Harris! I feel this is a much safer option for now while we look into better options into how we filter things. I'd like to request some changes first if that's possible:

  • This needs a friendlier changelog, possibly: Allow Store API to filter products by custom taxonomies, unless I'm getting this PR wrong.
  • This needs a dev note as well, given it's a change to a developer related page.
  • It doesn't seem like we have any protection against core params, what if someone names a taxonomy featured or sku, would that conflict? can we make sure all of our core args are prioritized above custom taxonomies.

@cpapazoglou
Copy link
Contributor Author

Hey thanks @Aljullu and @tjcafferkey for reviewing this for me while I was away.

@cpapazoglou it looks great, just wondering about the operator, the AND operator doesn't seem to work for me, could you check it for me? (in and not_in seem to be fine)

  • Set two products up assign two custom categories to the first one, to the second one give only one custom category.
  • Go to the following URL and expect to only see the product with two categories.
    /wp-json/wc/store/products?custom_categories=35,36&custom_categories_operator=and

👋 @opr, I agree the behaviour is very weird and decided to check what happens with the product_cat since it seems like a broader issue. It seems that when using multiple ids by default all children are included
https://github.com/WordPress/WordPress/blob/master/wp-includes/class-wp-tax-query.php#L109
this results in more results for in and less results for not_in, and. Especially for and, selecting even one parent category might return 0 results as there are very few chances that a product is assigned to all children terms of that parent 🤦. Anyway, selecting leaf nodes of an hierarchical taxonomy should be enough.

BUT, I notice another bug with custom taxonomies and multiple term ids passed in the arguments. Passing for example 4201,4202 doesn't get transformed to an array of terms and so it doesn't work as expected. For the category param, this happens in the sanitize_callback (not sure why sanitize_callback changes the data structure..)
https://github.com/Automattic/woocommerce.com/blob/33bb013c94ed0838f06d95b94bae41d4ec9bf792/plugins/woo-gutenberg-products-block/src/StoreApi/Routes/V1/Products.php#L273
I will be working to fix that.

👋 @senadir

This needs a friendlier changelog, possibly: Allow Store API to filter products by custom taxonomies, unless I'm getting this PR wrong.

Yep, very good suggestion ✅

This needs a dev note as well, given it's a change to a developer related page.

I am not familiar with dev notes, can you point me to relevant documentation or code?

It doesn't seem like we have any protection against core params, what if someone names a taxonomy featured or sku, would that conflict? can we make sure all of our core args are prioritized above custom taxonomies.

Very good catch! Yes it would conflict. I am thinking of prefixing the query parameter with tax_ or taxonomy_ and the request will look like /wp-json/wc/store/products?tax_custom_categories=[that-category-id] this will make sure the key isn't clashing with any other argument this endpoint currently accepts. What's your take?

@cpapazoglou cpapazoglou requested review from senadir and opr April 6, 2022 15:32
@cpapazoglou
Copy link
Contributor Author

Hi, sorry for the delay!

I tested and it is working with and, but in and not in aren't working - I also left a comment about not in.

It's unclear from your last comment whether you expect in and not in to be working or if you still need to work on it?

I am not familiar with dev notes, can you point me to relevant documentation or code?

It's something we include in the release notes to describe some changes at a higher level. If you could write one for these changes (after they are finalised of course) that would go into the post.

https://developer.woocommerce.com/2022/03/15/woocommerce-blocks-7-2-0-release-notes/

Also you may want to try rebasing on trunk to get the E2E tests passing

Thanks @opr for reviewing / testing this!

in is working as expected for me. Bear in mind, that if you select a parent term it will add the parent term and all children terms in the query.

I hadn't tested not in but after making the changes you suggested here #6152 (comment), not_in seems also working fine for me. This patch should also fix a bug with all other params not_in operator.

Before After
CleanShot 2022-04-20 at 12 26 52@2x CleanShot 2022-04-20 at 12 25 49@2x

@cpapazoglou
Copy link
Contributor Author

Added a release note and updated the changelog.

@senadir
Copy link
Member

senadir commented Apr 20, 2022

I'm not sure if a tax prefix makes sense for now, and it would hard to roll back, but I don't think we can prioritize core params without creating confusion, I'm happy to move this forward with an _unstable_tax prefix instead so that we have room to change this in the future if it doesn't make sense, and also to address any future bugs we might not be seeing right now.

@cpapazoglou
Copy link
Contributor Author

cpapazoglou commented Apr 20, 2022

I'm not sure if a tax prefix makes sense for now, and it would hard to roll back, but I don't think we can prioritize core params without creating confusion, I'm happy to move this forward with an _unstable_tax prefix instead so that we have room to change this in the future if it doesn't make sense, and also to address any future bugs we might not be seeing right now.

Good idea @senadir, I replaced tax_ with _unstable_tax_ in code, changelog, and release note. ba4b819

Here are the query args returned
CleanShot 2022-04-20 at 13 03 40@2x

@cpapazoglou cpapazoglou force-pushed the add/all_product_taxonomies_to_product_query branch from ba4b819 to 96ffaef Compare April 20, 2022 15:14
@cpapazoglou cpapazoglou force-pushed the add/all_product_taxonomies_to_product_query branch from 96ffaef to 8843a78 Compare April 21, 2022 11:01
Copy link
Member

@senadir senadir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for all the effort and back and forth with us @cpapazoglou
Let's merge this!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
focus: rest api Work impacting REST api routes. type: task The issue is an internally driven task (e.g. from another A8c team).
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants