Commit 76ee6398dfd for woocommerce

commit 76ee6398dfd85a33278eb5eaf7e5a5bf4dc3c56d
Author: verofasulo <98944206+verofasulo@users.noreply.github.com>
Date:   Wed May 13 17:45:59 2026 +0200

    Add Tags filter to the experimental products app product list (#64822)

    * Add Tags filter to the experimental products app product list

    Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

    * resolve conflicts

    ---------

    Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
    Co-authored-by: Luigi Teschio <gigitux@gmail.com>

diff --git a/packages/js/experimental-products-app/changelog/add-products-app-tag-filter b/packages/js/experimental-products-app/changelog/add-products-app-tag-filter
new file mode 100644
index 00000000000..41eab996507
--- /dev/null
+++ b/packages/js/experimental-products-app/changelog/add-products-app-tag-filter
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Add a Tags filter to the experimental products app product list.
diff --git a/packages/js/experimental-products-app/src/fields/tags/field.tsx b/packages/js/experimental-products-app/src/fields/tags/field.tsx
index 250f3e5b7b1..ea08fafceb6 100644
--- a/packages/js/experimental-products-app/src/fields/tags/field.tsx
+++ b/packages/js/experimental-products-app/src/fields/tags/field.tsx
@@ -3,6 +3,8 @@
  */
 import { __ } from '@wordpress/i18n';
 import { decodeEntities } from '@wordpress/html-entities';
+import { resolveSelect } from '@wordpress/data';
+import { store as coreStore } from '@wordpress/core-data';

 import type { DataFormControlProps, Field } from '@wordpress/dataviews';

@@ -17,7 +19,9 @@ const fieldDefinition = {
 	type: 'array',
 	label: __( 'Tags', 'woocommerce' ),
 	enableSorting: false,
-	filterBy: false,
+	filterBy: {
+		operators: [ 'isAny', 'isNone' ],
+	},
 } satisfies Partial< Field< ProductEntityRecord > >;

 export const fieldExtensions: Partial< Field< ProductEntityRecord > > = {
@@ -32,6 +36,17 @@ export const fieldExtensions: Partial< Field< ProductEntityRecord > > = {
 			} ) ),
 		};
 	},
+	getElements: async () => {
+		const records = ( await resolveSelect( coreStore ).getEntityRecords(
+			'taxonomy',
+			'product_tag',
+			{ per_page: -1 }
+		) ) as Array< { id: number; name: string } > | null;
+		return ( records ?? [] ).map( ( { id, name } ) => ( {
+			value: id.toString(),
+			label: decodeEntities( name ),
+		} ) );
+	},
 	render: ( { item } ) => {
 		return ( item.tags ?? [] )
 			.map( ( { name } ) => decodeEntities( name ?? '' ) )
diff --git a/packages/js/experimental-products-app/src/product-list/query.test.ts b/packages/js/experimental-products-app/src/product-list/query.test.ts
index a840e641542..90f3675b94c 100644
--- a/packages/js/experimental-products-app/src/product-list/query.test.ts
+++ b/packages/js/experimental-products-app/src/product-list/query.test.ts
@@ -159,6 +159,36 @@ describe( 'buildProductListQuery', () => {
 		expect( query.stock_status ).toBe( 'onbackorder' );
 	} );

+	it( 'maps the tags isAny filter to the tag query param', () => {
+		const query = buildProductListQuery( {
+			...baseView,
+			filters: [
+				{
+					field: 'tags',
+					operator: 'isAny',
+					value: [ '5', 7 ],
+				},
+			],
+		} as View );
+
+		expect( query.tag ).toEqual( '5,7' );
+	} );
+
+	it( 'maps the tags isNone filter to exclude_tag', () => {
+		const query = buildProductListQuery( {
+			...baseView,
+			filters: [
+				{
+					field: 'tags',
+					operator: 'isNone',
+					value: [ '5', 7 ],
+				},
+			],
+		} as View );
+
+		expect( query.exclude_tag ).toEqual( [ 5, 7 ] );
+	} );
+
 	it( 'maps the brands isAny filter to the brand query param', () => {
 		const query = buildProductListQuery( {
 			...baseView,
diff --git a/packages/js/experimental-products-app/src/product-list/query.ts b/packages/js/experimental-products-app/src/product-list/query.ts
index 2447ae5ceed..6fce94a14c8 100644
--- a/packages/js/experimental-products-app/src/product-list/query.ts
+++ b/packages/js/experimental-products-app/src/product-list/query.ts
@@ -16,6 +16,7 @@ export type ProductListQuery = Omit< ProductQuery, 'status' > & {
 	include_types?: ProductType[];
 	exclude_types?: ProductType[];
 	exclude_category?: number[];
+	exclude_tag?: number[];
 	min_stock_quantity?: string;
 	max_stock_quantity?: string;
 	brand?: string;
@@ -112,6 +113,21 @@ function applyCategoryFilter( query: ProductListQuery, filter: Filter ) {
 	query.category = values.join( ',' );
 }

+function applyTagFilter( query: ProductListQuery, filter: Filter ) {
+	const values = getNumericValues( filter.value );
+
+	if ( values.length === 0 ) {
+		return;
+	}
+
+	if ( filter.operator === 'isNone' ) {
+		query.exclude_tag = values;
+		return;
+	}
+
+	query.tag = values.join( ',' );
+}
+
 function applyBrandFilter( query: ProductListQuery, filter: Filter ) {
 	const values = getNumericValues( filter.value );

@@ -184,6 +200,9 @@ export function buildProductListQuery( view: View ): ProductListQuery {
 			case 'categories':
 				applyCategoryFilter( query, filter );
 				break;
+			case 'tags':
+				applyTagFilter( query, filter );
+				break;
 			case 'brands':
 				applyBrandFilter( query, filter );
 				break;