Commit fc331cd415d for woocommerce
commit fc331cd415dde2afe7494a95279073ef03f644b4
Author: Luigi Teschio <gigitux@gmail.com>
Date: Mon May 11 15:05:03 2026 +0200
Fix bulk quick edit behavior (#64734)
Fix bulk quick edit selection
diff --git a/packages/js/experimental-products-app/changelog/fix-quick-edit-bulk-selection b/packages/js/experimental-products-app/changelog/fix-quick-edit-bulk-selection
new file mode 100644
index 00000000000..27ec239d527
--- /dev/null
+++ b/packages/js/experimental-products-app/changelog/fix-quick-edit-bulk-selection
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix quick edit bulk selection and hide unsupported SKU field in bulk edits.
diff --git a/packages/js/experimental-products-app/src/dataviews-actions/actions.test.tsx b/packages/js/experimental-products-app/src/dataviews-actions/actions.test.tsx
index aa69254fc65..d76183c72f0 100644
--- a/packages/js/experimental-products-app/src/dataviews-actions/actions.test.tsx
+++ b/packages/js/experimental-products-app/src/dataviews-actions/actions.test.tsx
@@ -87,6 +87,11 @@ describe( 'product list actions', () => {
status: 'draft',
name: 'Beanie',
} as ProductEntityRecord;
+ const hoodie = {
+ id: 34,
+ status: 'draft',
+ name: 'Hoodie',
+ } as ProductEntityRecord;
const deleteEntityRecord = jest.fn();
const invalidateResolution = jest.fn();
@@ -136,7 +141,11 @@ describe( 'product list actions', () => {
expect( quickEditProductAction ).toBeDefined();
- getCallbackAction( quickEditProductAction! ).callback( [ product ], {
+ if ( ! quickEditProductAction ) {
+ throw new Error( 'Quick edit action not found.' );
+ }
+
+ getCallbackAction( quickEditProductAction ).callback( [ product ], {
onActionPerformed,
} );
@@ -155,6 +164,31 @@ describe( 'product list actions', () => {
expect( quickEditProductAction?.supportsBulk ).toBe( true );
} );
+ it( 'opens quick edit panel with all selected products when triggered as a bulk action', () => {
+ const { result } = renderHook( () => useProductActions() );
+ const quickEditProductAction = result.current.find(
+ ( action ) => action.id === 'quick-edit-product'
+ );
+
+ expect( quickEditProductAction ).toBeDefined();
+
+ if ( ! quickEditProductAction ) {
+ throw new Error( 'Quick edit action not found.' );
+ }
+
+ getCallbackAction( quickEditProductAction ).callback(
+ [ product, hoodie ],
+ {
+ onActionPerformed,
+ }
+ );
+
+ expect( navigate ).toHaveBeenCalledWith(
+ '/products?activeView=draft&postId=12%2C34&quickEdit=true'
+ );
+ expect( onActionPerformed ).toHaveBeenCalledWith( [ product, hoodie ] );
+ } );
+
it( 'opens product editor when the Edit action is triggered', () => {
const { result } = renderHook( () => useProductActions() );
const editProductAction = result.current.find(
@@ -163,13 +197,17 @@ describe( 'product list actions', () => {
expect( editProductAction ).toBeDefined();
+ if ( ! editProductAction ) {
+ throw new Error( 'Edit action not found.' );
+ }
+
const originalLocation = window.location;
Object.defineProperty( window, 'location', {
writable: true,
value: { href: '' },
} );
- getCallbackAction( editProductAction! ).callback( [ product ], {
+ getCallbackAction( editProductAction ).callback( [ product ], {
onActionPerformed,
} );
diff --git a/packages/js/experimental-products-app/src/dataviews-actions/actions.tsx b/packages/js/experimental-products-app/src/dataviews-actions/actions.tsx
index f8b965e8203..c59975513a6 100644
--- a/packages/js/experimental-products-app/src/dataviews-actions/actions.tsx
+++ b/packages/js/experimental-products-app/src/dataviews-actions/actions.tsx
@@ -31,7 +31,7 @@ type EditActionOptions = {
function getQuickEditPath(
path: string,
query: Record< string, string | undefined >,
- productId: number
+ productIds: number[]
) {
const nextQuery = Object.entries( query ).reduce(
( acc, [ key, value ] ) => {
@@ -46,7 +46,7 @@ function getQuickEditPath(
return getProductListNavigationPath( path, {
...nextQuery,
- postId: String( productId ),
+ postId: productIds.join( ',' ),
quickEdit: 'true',
} );
}
@@ -132,10 +132,10 @@ export const quickEditAction = ( {
return product.status !== 'trash';
},
callback( items, { onActionPerformed } ) {
- const product = items[ 0 ];
+ const productIds = items.map( ( product ) => product.id );
- if ( product ) {
- navigate( getQuickEditPath( path, query, product.id ) );
+ if ( productIds.length > 0 ) {
+ navigate( getQuickEditPath( path, query, productIds ) );
}
if ( onActionPerformed ) {
diff --git a/packages/js/experimental-products-app/src/product-edit/utils.test.ts b/packages/js/experimental-products-app/src/product-edit/utils.test.ts
index 18ca85b30d9..aeeef708fbb 100644
--- a/packages/js/experimental-products-app/src/product-edit/utils.test.ts
+++ b/packages/js/experimental-products-app/src/product-edit/utils.test.ts
@@ -270,6 +270,9 @@ describe( 'product edit utils', () => {
'shipping_class',
'tax_status',
];
+ const bulkUniversalFieldIds = universalFieldIds.filter(
+ ( fieldId ) => fieldId !== 'sku'
+ );
it( 'shows pricing, shipping, and linked product fields for simple physical products', () => {
const fieldIds = getVisibleFieldIds( [
@@ -432,6 +435,7 @@ describe( 'product edit utils', () => {
] );
expectFieldsHidden( fieldIds, priceFieldIds );
+ expectFieldsHidden( fieldIds, [ 'sku' ] );
expect( fieldIds ).toEqual(
expect.arrayContaining( [
'name',
@@ -442,11 +446,33 @@ describe( 'product edit utils', () => {
'featured',
'upsell_ids',
'cross_sell_ids',
- ...universalFieldIds,
+ ...bulkUniversalFieldIds,
] )
);
} );
+ it( 'shows price but not SKU when bulk editing simple products', () => {
+ const fieldIds = getVisibleFieldIds( [
+ buildProduct( {
+ id: 1,
+ type: 'simple',
+ regular_price: '12',
+ price: '12',
+ } ),
+ buildProduct( {
+ id: 2,
+ type: 'simple',
+ regular_price: '15',
+ price: '15',
+ } ),
+ ] );
+
+ expect( fieldIds ).toEqual(
+ expect.arrayContaining( [ 'price', 'regular_price' ] )
+ );
+ expectFieldsHidden( fieldIds, [ 'sku' ] );
+ } );
+
it( 'shows sellable instance fields for variations', () => {
const fieldIds = getVisibleFieldIds( [
buildProduct( {
@@ -496,12 +522,13 @@ describe( 'product edit utils', () => {
expect( fieldIds ).toEqual(
expect.arrayContaining( [
...priceFieldIds,
- ...universalFieldIds,
+ ...bulkUniversalFieldIds,
] )
);
expectFieldsHidden( fieldIds, [
...parentOwnedFieldIds,
'downloadable',
+ 'sku',
] );
} );
@@ -524,12 +551,13 @@ describe( 'product edit utils', () => {
] );
expect( fieldIds ).toEqual(
- expect.arrayContaining( universalFieldIds )
+ expect.arrayContaining( bulkUniversalFieldIds )
);
expectFieldsHidden( fieldIds, [
...parentOwnedFieldIds,
...priceFieldIds,
'downloadable',
+ 'sku',
] );
} );
@@ -560,12 +588,13 @@ describe( 'product edit utils', () => {
] );
expect( fieldIds ).toEqual(
- expect.arrayContaining( universalFieldIds )
+ expect.arrayContaining( bulkUniversalFieldIds )
);
expectFieldsHidden( fieldIds, [
...parentOwnedFieldIds,
...priceFieldIds,
'downloadable',
+ 'sku',
] );
} );
diff --git a/packages/js/experimental-products-app/src/product-edit/utils.ts b/packages/js/experimental-products-app/src/product-edit/utils.ts
index 0a9c0656c27..badf7158313 100644
--- a/packages/js/experimental-products-app/src/product-edit/utils.ts
+++ b/packages/js/experimental-products-app/src/product-edit/utils.ts
@@ -195,6 +195,9 @@ const SELLABLE_PRODUCT_EDIT_FIELD_ID_SET = new Set< ProductEditFieldId >( [
'date_on_sale_to',
] );
+const BULK_UNSUPPORTED_PRODUCT_EDIT_FIELD_ID_SET =
+ new Set< ProductEditFieldId >( [ 'sku' ] );
+
function normalizeValue( value: unknown ) {
if ( value === undefined ) {
return '__undefined__';
@@ -435,12 +438,22 @@ export function getVisibleProductEditFields(
) {
const compatibleFieldIds =
getCommonProductTypeCompatibleFieldIds( products );
+ const isBulkEdit = products.length > 1;
return fields.reduce< ProductField[] >( ( visibleFields, field ) => {
if ( ! compatibleFieldIds.has( field.id ) ) {
return visibleFields;
}
+ if (
+ isBulkEdit &&
+ BULK_UNSUPPORTED_PRODUCT_EDIT_FIELD_ID_SET.has(
+ field.id as ProductEditFieldId
+ )
+ ) {
+ return visibleFields;
+ }
+
if ( ! isFieldVisibleForProductRelationships( field.id, products ) ) {
return visibleFields;
}