From 1ea439fb6197573dd0c49279c4729ba95912d94e Mon Sep 17 00:00:00 2001 From: Matt Sherman Date: Wed, 24 Jan 2024 09:50:00 -0500 Subject: [PATCH] [Product Editor] Variation quick actions succeed when sku is inherited from parent (#44017) --- .../changelog/fix-variation-quick-actions-sku | 4 + packages/js/data/src/index.ts | 1 + .../js/data/src/product-variations/types.ts | 42 ++-- .../changelog/fix-variation-quick-actions-sku | 4 + .../product-fields/variation-items/edit.tsx | 6 +- .../downloads-menu-item.tsx | 45 ++-- .../inventory-menu-item.tsx | 98 +++------ .../pricing-menu-item/pricing-menu-item.tsx | 207 ++++++------------ .../set-list-price-menu-item.tsx | 20 +- .../shipping-menu-item/shipping-menu-item.tsx | 63 ++---- .../toggle-visibility-menu-item.tsx | 22 +- .../src/components/variations-table/types.ts | 8 +- .../update-stock-menu-item.tsx | 21 +- .../use-variations/use-variations.ts | 9 +- .../multiple-update-menu.tsx | 4 + .../single-update-menu.tsx | 7 +- .../test/variation-actions.test.tsx | 121 +++++----- .../variation-quick-update-menu-item.test.tsx | 12 +- .../variation-actions-menus/types.ts | 20 +- .../variation-actions.tsx | 13 +- .../variations-table-row/types.ts | 10 +- .../variations-table-row.tsx | 17 +- .../variations-table/variations-table.tsx | 22 +- 23 files changed, 322 insertions(+), 454 deletions(-) create mode 100644 packages/js/data/changelog/fix-variation-quick-actions-sku create mode 100644 packages/js/product-editor/changelog/fix-variation-quick-actions-sku diff --git a/packages/js/data/changelog/fix-variation-quick-actions-sku b/packages/js/data/changelog/fix-variation-quick-actions-sku new file mode 100644 index 00000000000..34be9289481 --- /dev/null +++ b/packages/js/data/changelog/fix-variation-quick-actions-sku @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add PartialProductVariation type, where id is required diff --git a/packages/js/data/src/index.ts b/packages/js/data/src/index.ts index 9184d621f61..4e181f78ab2 100644 --- a/packages/js/data/src/index.ts +++ b/packages/js/data/src/index.ts @@ -87,6 +87,7 @@ export * from './onboarding/types'; export * from './plugins/types'; export * from './products/types'; export type { + PartialProductVariation, ProductVariation, ProductVariationAttribute, ProductVariationImage, diff --git a/packages/js/data/src/product-variations/types.ts b/packages/js/data/src/product-variations/types.ts index 78abae22ceb..39fa0d47e96 100644 --- a/packages/js/data/src/product-variations/types.ts +++ b/packages/js/data/src/product-variations/types.ts @@ -57,25 +57,29 @@ export interface ProductVariationImage { export type ProductVariation = Omit< Product, 'slug' | 'attributes' | 'images' | 'manage_stock' -> & { - attributes: ProductVariationAttribute[]; - /** - * Variation image data. - */ - image?: ProductVariationImage; - /** - * Stock management at variation level. It can have a - * 'parent' value if the parent product is managing - * the stock at the time the variation was created. - * - * @default false - */ - manage_stock: boolean | 'parent'; - /** - * The product id this variation belongs to - */ - parent_id: number; -}; +> & + Pick< Product, 'id' > & { + attributes: ProductVariationAttribute[]; + /** + * Variation image data. + */ + image?: ProductVariationImage; + /** + * Stock management at variation level. It can have a + * 'parent' value if the parent product is managing + * the stock at the time the variation was created. + * + * @default false + */ + manage_stock: boolean | 'parent'; + /** + * The product id this variation belongs to + */ + parent_id: number; + }; + +export type PartialProductVariation = Partial< ProductVariation > & + Pick< ProductVariation, 'id' >; type Query = Omit< ProductQuery, 'name' >; diff --git a/packages/js/product-editor/changelog/fix-variation-quick-actions-sku b/packages/js/product-editor/changelog/fix-variation-quick-actions-sku new file mode 100644 index 00000000000..e6b3d9a5315 --- /dev/null +++ b/packages/js/product-editor/changelog/fix-variation-quick-actions-sku @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Variation quick actions update properly when variation inherits sku of parent; slot fills always deal with arrays diff --git a/packages/js/product-editor/src/blocks/product-fields/variation-items/edit.tsx b/packages/js/product-editor/src/blocks/product-fields/variation-items/edit.tsx index 60ff79e6de4..66e726b89fa 100644 --- a/packages/js/product-editor/src/blocks/product-fields/variation-items/edit.tsx +++ b/packages/js/product-editor/src/blocks/product-fields/variation-items/edit.tsx @@ -4,8 +4,8 @@ import { sprintf, __ } from '@wordpress/i18n'; import { EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME, + PartialProductVariation, Product, - ProductVariation, useUserPreferences, } from '@woocommerce/data'; import { useWooBlockProps } from '@woocommerce/block-templates'; @@ -115,14 +115,14 @@ export function Edit( { ); function onSetPrices( - handleUpdateAll: ( update: Partial< ProductVariation >[] ) => void + handleUpdateAll: ( update: PartialProductVariation[] ) => void ) { recordEvent( 'product_variations_set_prices_select', { source: TRACKS_SOURCE, } ); const productVariationsListPromise = resolveSelect( EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME - ).getProductVariations< Pick< ProductVariation, 'id' >[] >( { + ).getProductVariations< PartialProductVariation[] >( { product_id: productId, order: 'asc', orderby: 'menu_order', diff --git a/packages/js/product-editor/src/components/variations-table/downloads-menu-item/downloads-menu-item.tsx b/packages/js/product-editor/src/components/variations-table/downloads-menu-item/downloads-menu-item.tsx index 0552990c247..148123efa7a 100644 --- a/packages/js/product-editor/src/components/variations-table/downloads-menu-item/downloads-menu-item.tsx +++ b/packages/js/product-editor/src/components/variations-table/downloads-menu-item/downloads-menu-item.tsx @@ -31,15 +31,11 @@ export function DownloadsMenuItem( { onClose, supportsMultipleSelection = false, }: VariationActionsMenuItemProps ) { - const ids = Array.isArray( selection ) - ? selection.map( ( { id } ) => id ) - : selection.id; + const ids = selection.map( ( { id } ) => id ); - const downloadsIds: number[] = ( - Array.isArray( selection ) - ? selection[ 0 ].downloads - : selection.downloads - ).map( ( { id }: ProductDownload ) => Number.parseInt( id, 10 ) ); + const downloadsIds: number[] = selection[ 0 ].downloads.map( + ( { id }: ProductDownload ) => Number.parseInt( id, 10 ) + ); const [ uploadFilesModalOpen, setUploadFilesModalOpen ] = useState( false ); @@ -55,16 +51,12 @@ export function DownloadsMenuItem( { downloads, }; - if ( Array.isArray( selection ) ) { - onChange( - selection.map( ( { id } ) => ( { - ...partialVariation, - id, - } ) ) - ); - } else { - onChange( partialVariation ); - } + onChange( + selection.map( ( { id } ) => ( { + ...partialVariation, + id, + } ) ) + ); recordEvent( 'product_variations_menu_downloads_update', { source: TRACKS_SOURCE, @@ -98,20 +90,13 @@ export function DownloadsMenuItem( { handlePrompt( { message, onOk( value ) { - if ( Array.isArray( selection ) ) { - onChange( - selection.map( ( { id } ) => ( { - id, - downloadable: true, - [ name ]: value, - } ) ) - ); - } else { - onChange( { + onChange( + selection.map( ( { id } ) => ( { + id, downloadable: true, [ name ]: value, - } ); - } + } ) ) + ); recordEvent( 'product_variations_menu_downloads_update', { source: TRACKS_SOURCE, action: `${ name }_set`, diff --git a/packages/js/product-editor/src/components/variations-table/inventory-menu-item/inventory-menu-item.tsx b/packages/js/product-editor/src/components/variations-table/inventory-menu-item/inventory-menu-item.tsx index a610011c79b..72eba053be5 100644 --- a/packages/js/product-editor/src/components/variations-table/inventory-menu-item/inventory-menu-item.tsx +++ b/packages/js/product-editor/src/components/variations-table/inventory-menu-item/inventory-menu-item.tsx @@ -23,9 +23,7 @@ export function InventoryMenuItem( { onClose, supportsMultipleSelection = false, }: VariationActionsMenuItemProps ) { - const ids = Array.isArray( selection ) - ? selection.map( ( { id } ) => id ) - : selection.id; + const ids = selection.map( ( { id } ) => id ); return ( ( { - id, - manage_stock: ! manage_stock, - } ) - ) - ); - } else { - onChange( { - manage_stock: ! selection.manage_stock, - } ); - } + onChange( + selection.map( + ( { id, manage_stock } ) => ( { + id, + manage_stock: ! manage_stock, + } ) + ) + ); onClose(); } } > @@ -99,22 +91,14 @@ export function InventoryMenuItem( { variation_id: ids, } ); - if ( Array.isArray( selection ) ) { - onChange( - selection.map( ( { id } ) => ( { - id, - stock_status: - PRODUCT_STOCK_STATUS_KEYS.instock, - manage_stock: false, - } ) ) - ); - } else { - onChange( { + onChange( + selection.map( ( { id } ) => ( { + id, stock_status: PRODUCT_STOCK_STATUS_KEYS.instock, manage_stock: false, - } ); - } + } ) ) + ); onClose(); } } > @@ -130,22 +114,14 @@ export function InventoryMenuItem( { variation_id: ids, } ); - if ( Array.isArray( selection ) ) { - onChange( - selection.map( ( { id } ) => ( { - id, - stock_status: - PRODUCT_STOCK_STATUS_KEYS.outofstock, - manage_stock: false, - } ) ) - ); - } else { - onChange( { + onChange( + selection.map( ( { id } ) => ( { + id, stock_status: PRODUCT_STOCK_STATUS_KEYS.outofstock, manage_stock: false, - } ); - } + } ) ) + ); onClose(); } } > @@ -164,22 +140,14 @@ export function InventoryMenuItem( { variation_id: ids, } ); - if ( Array.isArray( selection ) ) { - onChange( - selection.map( ( { id } ) => ( { - id, - stock_status: - PRODUCT_STOCK_STATUS_KEYS.onbackorder, - manage_stock: false, - } ) ) - ); - } else { - onChange( { + onChange( + selection.map( ( { id } ) => ( { + id, stock_status: PRODUCT_STOCK_STATUS_KEYS.onbackorder, manage_stock: false, - } ); - } + } ) ) + ); onClose(); } } > @@ -212,22 +180,14 @@ export function InventoryMenuItem( { if ( Number.isNaN( lowStockAmount ) ) { return null; } - if ( Array.isArray( selection ) ) { - onChange( - selection.map( ( { id } ) => ( { - id, - low_stock_amount: - lowStockAmount, - manage_stock: true, - } ) ) - ); - } else { - onChange( { + onChange( + selection.map( ( { id } ) => ( { + id, low_stock_amount: lowStockAmount, manage_stock: true, - } ); - } + } ) ) + ); }, } ); onClose(); diff --git a/packages/js/product-editor/src/components/variations-table/pricing-menu-item/pricing-menu-item.tsx b/packages/js/product-editor/src/components/variations-table/pricing-menu-item/pricing-menu-item.tsx index a21c4f8029d..84941404897 100644 --- a/packages/js/product-editor/src/components/variations-table/pricing-menu-item/pricing-menu-item.tsx +++ b/packages/js/product-editor/src/components/variations-table/pricing-menu-item/pricing-menu-item.tsx @@ -61,9 +61,7 @@ export function PricingMenuItem( { onClose, supportsMultipleSelection = false, }: VariationActionsMenuItemProps ) { - const ids = Array.isArray( selection ) - ? selection.map( ( { id } ) => id ) - : selection.id; + const ids = selection.map( ( { id } ) => id ); return ( ( { - id, - regular_price: - addFixedOrPercentage( - regular_price, - value - )?.toFixed( 2 ), - } ) - ) - ); - } else { - onChange( { - regular_price: - addFixedOrPercentage( - selection.regular_price, - value - )?.toFixed( 2 ), - } ); - } + onChange( + selection.map( + ( { id, regular_price } ) => ( { + id, + regular_price: + addFixedOrPercentage( + regular_price, + value + )?.toFixed( 2 ), + } ) + ) + ); }, } ); onClose(); @@ -175,33 +160,19 @@ export function PricingMenuItem( { variation_id: ids, } ); - if ( Array.isArray( selection ) ) { - onChange( - selection.map( - ( { - id, - regular_price, - } ) => ( { - id, - regular_price: - addFixedOrPercentage( - regular_price, - value, - -1 - )?.toFixed( 2 ), - } ) - ) - ); - } else { - onChange( { - regular_price: - addFixedOrPercentage( - selection.regular_price, - value, - -1 - )?.toFixed( 2 ), - } ); - } + onChange( + selection.map( + ( { id, regular_price } ) => ( { + id, + regular_price: + addFixedOrPercentage( + regular_price, + value, + -1 + )?.toFixed( 2 ), + } ) + ) + ); }, } ); onClose(); @@ -231,18 +202,12 @@ export function PricingMenuItem( { variation_id: ids, } ); - if ( Array.isArray( selection ) ) { - onChange( - selection.map( ( { id } ) => ( { - id, - sale_price: value, - } ) ) - ); - } else { - onChange( { + onChange( + selection.map( ( { id } ) => ( { + id, sale_price: value, - } ); - } + } ) ) + ); }, } ); onClose(); @@ -274,31 +239,18 @@ export function PricingMenuItem( { variation_id: ids, } ); - if ( Array.isArray( selection ) ) { - onChange( - selection.map( - ( { - id, - sale_price, - } ) => ( { - id, - sale_price: - addFixedOrPercentage( - sale_price, - value - )?.toFixed( 2 ), - } ) - ) - ); - } else { - onChange( { - sale_price: - addFixedOrPercentage( - selection.sale_price, - value - )?.toFixed( 2 ), - } ); - } + onChange( + selection.map( + ( { id, sale_price } ) => ( { + id, + sale_price: + addFixedOrPercentage( + sale_price, + value + )?.toFixed( 2 ), + } ) + ) + ); }, } ); onClose(); @@ -330,33 +282,19 @@ export function PricingMenuItem( { variation_id: ids, } ); - if ( Array.isArray( selection ) ) { - onChange( - selection.map( - ( { - id, - sale_price, - } ) => ( { - id, - sale_price: - addFixedOrPercentage( - sale_price, - value, - -1 - )?.toFixed( 2 ), - } ) - ) - ); - } else { - onChange( { - sale_price: - addFixedOrPercentage( - selection.sale_price, - value, - -1 - )?.toFixed( 2 ), - } ); - } + onChange( + selection.map( + ( { id, sale_price } ) => ( { + id, + sale_price: + addFixedOrPercentage( + sale_price, + value, + -1 + )?.toFixed( 2 ), + } ) + ) + ); }, } ); onClose(); @@ -388,19 +326,12 @@ export function PricingMenuItem( { variation_id: ids, } ); - if ( Array.isArray( selection ) ) { - onChange( - selection.map( ( { id } ) => ( { - id, - date_on_sale_from_gmt: - value, - } ) ) - ); - } else { - onChange( { + onChange( + selection.map( ( { id } ) => ( { + id, date_on_sale_from_gmt: value, - } ); - } + } ) ) + ); }, } ); handlePrompt( { @@ -417,18 +348,12 @@ export function PricingMenuItem( { variation_id: ids, } ); - if ( Array.isArray( selection ) ) { - onChange( - selection.map( ( { id } ) => ( { - id, - date_on_sale_to_gmt: value, - } ) ) - ); - } else { - onChange( { + onChange( + selection.map( ( { id } ) => ( { + id, date_on_sale_to_gmt: value, - } ); - } + } ) ) + ); }, } ); onClose(); diff --git a/packages/js/product-editor/src/components/variations-table/set-list-price-menu-item/set-list-price-menu-item.tsx b/packages/js/product-editor/src/components/variations-table/set-list-price-menu-item/set-list-price-menu-item.tsx index fd07c6a6bc7..7f894653ec5 100644 --- a/packages/js/product-editor/src/components/variations-table/set-list-price-menu-item/set-list-price-menu-item.tsx +++ b/packages/js/product-editor/src/components/variations-table/set-list-price-menu-item/set-list-price-menu-item.tsx @@ -21,9 +21,7 @@ export function SetListPriceMenuItem( { return ( { - const ids = Array.isArray( selection ) - ? selection.map( ( { id } ) => id ) - : selection.id; + const ids = selection.map( ( { id } ) => id ); recordEvent( 'product_variations_menu_pricing_select', { source: TRACKS_SOURCE, @@ -37,18 +35,12 @@ export function SetListPriceMenuItem( { action: 'list_price_set', variation_id: ids, } ); - if ( Array.isArray( selection ) ) { - onChange( - selection.map( ( { id } ) => ( { - id, - regular_price: value, - } ) ) - ); - } else { - onChange( { + onChange( + selection.map( ( { id } ) => ( { + id, regular_price: value, - } ); - } + } ) ) + ); }, } ); onClose(); diff --git a/packages/js/product-editor/src/components/variations-table/shipping-menu-item/shipping-menu-item.tsx b/packages/js/product-editor/src/components/variations-table/shipping-menu-item/shipping-menu-item.tsx index 0b20d0e5145..150d118f76b 100644 --- a/packages/js/product-editor/src/components/variations-table/shipping-menu-item/shipping-menu-item.tsx +++ b/packages/js/product-editor/src/components/variations-table/shipping-menu-item/shipping-menu-item.tsx @@ -22,31 +22,20 @@ export function ShippingMenuItem( { onClose, supportsMultipleSelection = false, }: VariationActionsMenuItemProps ) { - const ids = Array.isArray( selection ) - ? selection.map( ( { id } ) => id ) - : selection.id; + const ids = selection.map( ( { id } ) => id ); function handleDimensionsChange( value: Partial< ProductVariation[ 'dimensions' ] > ) { - if ( Array.isArray( selection ) ) { - onChange( - selection.map( ( { id, dimensions } ) => ( { - id, - dimensions: { - ...dimensions, - ...value, - }, - } ) ) - ); - } else { - onChange( { + onChange( + selection.map( ( { id, dimensions } ) => ( { + id, dimensions: { - ...selection.dimensions, + ...dimensions, ...value, }, - } ); - } + } ) ) + ); } return ( @@ -87,20 +76,14 @@ export function ShippingMenuItem( { variation_id: ids, } ); - if ( Array.isArray( selection ) ) { - onChange( - selection.map( - ( { id, virtual } ) => ( { - id, - virtual: ! virtual, - } ) - ) - ); - } else { - onChange( { - virtual: ! selection.virtual, - } ); - } + onChange( + selection.map( + ( { id, virtual } ) => ( { + id, + virtual: ! virtual, + } ) + ) + ); recordEvent( 'product_variations_menu_shipping_update', { @@ -225,16 +208,12 @@ export function ShippingMenuItem( { variation_id: ids, } ); - if ( Array.isArray( selection ) ) { - onChange( - selection.map( ( { id } ) => ( { - id, - weight: value, - } ) ) - ); - } else { - onChange( { weight: value } ); - } + onChange( + selection.map( ( { id } ) => ( { + id, + weight: value, + } ) ) + ); }, } ); onClose(); diff --git a/packages/js/product-editor/src/components/variations-table/toggle-visibility-menu-item/toggle-visibility-menu-item.tsx b/packages/js/product-editor/src/components/variations-table/toggle-visibility-menu-item/toggle-visibility-menu-item.tsx index b000ac2daeb..1622c08a99c 100644 --- a/packages/js/product-editor/src/components/variations-table/toggle-visibility-menu-item/toggle-visibility-menu-item.tsx +++ b/packages/js/product-editor/src/components/variations-table/toggle-visibility-menu-item/toggle-visibility-menu-item.tsx @@ -22,9 +22,7 @@ export function ToggleVisibilityMenuItem( { } function handleMenuItemClick() { - const ids = Array.isArray( selection ) - ? selection.map( ( { id } ) => id ) - : selection.id; + const ids = selection.map( ( { id } ) => id ); recordEvent( 'product_variations_menu_toggle_visibility_select', { source: TRACKS_SOURCE, @@ -32,18 +30,12 @@ export function ToggleVisibilityMenuItem( { variation_id: ids, } ); - if ( Array.isArray( selection ) ) { - onChange( - selection.map( ( { id, status } ) => ( { - id, - status: toggleStatus( status ), - } ) ) - ); - } else { - onChange( { - status: toggleStatus( selection.status ), - } ); - } + onChange( + selection.map( ( { id, status } ) => ( { + id, + status: toggleStatus( status ), + } ) ) + ); recordEvent( 'product_variations_toggle_visibility_update', { source: TRACKS_SOURCE, diff --git a/packages/js/product-editor/src/components/variations-table/types.ts b/packages/js/product-editor/src/components/variations-table/types.ts index 5b9cd72bd63..daec17262a5 100644 --- a/packages/js/product-editor/src/components/variations-table/types.ts +++ b/packages/js/product-editor/src/components/variations-table/types.ts @@ -1,13 +1,11 @@ /** * External dependencies */ -import { ProductVariation } from '@woocommerce/data'; +import { PartialProductVariation, ProductVariation } from '@woocommerce/data'; export type VariationActionsMenuItemProps = { - selection: ProductVariation | ProductVariation[]; - onChange( - variation: Partial< ProductVariation > | Partial< ProductVariation >[] - ): void; + selection: ProductVariation[]; + onChange( values: PartialProductVariation[] ): void; onClose(): void; supportsMultipleSelection?: boolean; }; diff --git a/packages/js/product-editor/src/components/variations-table/update-stock-menu-item/update-stock-menu-item.tsx b/packages/js/product-editor/src/components/variations-table/update-stock-menu-item/update-stock-menu-item.tsx index b3b3f2cf72e..cc471066d5d 100644 --- a/packages/js/product-editor/src/components/variations-table/update-stock-menu-item/update-stock-menu-item.tsx +++ b/packages/js/product-editor/src/components/variations-table/update-stock-menu-item/update-stock-menu-item.tsx @@ -21,9 +21,7 @@ export function UpdateStockMenuItem( { return ( { - const ids = Array.isArray( selection ) - ? selection.map( ( { id } ) => id ) - : selection.id; + const ids = selection.map( ( { id } ) => id ); recordEvent( 'product_variations_menu_inventory_select', { source: TRACKS_SOURCE, @@ -44,20 +42,13 @@ export function UpdateStockMenuItem( { variation_id: ids, } ); - if ( Array.isArray( selection ) ) { - onChange( - selection.map( ( { id } ) => ( { - id, - stock_quantity: stockQuantity, - manage_stock: true, - } ) ) - ); - } else { - onChange( { + onChange( + selection.map( ( { id } ) => ( { + id, stock_quantity: stockQuantity, manage_stock: true, - } ); - } + } ) ) + ); }, } ); onClose(); diff --git a/packages/js/product-editor/src/components/variations-table/use-variations/use-variations.ts b/packages/js/product-editor/src/components/variations-table/use-variations/use-variations.ts index 9af662f08c2..a3d8ce53532 100644 --- a/packages/js/product-editor/src/components/variations-table/use-variations/use-variations.ts +++ b/packages/js/product-editor/src/components/variations-table/use-variations/use-variations.ts @@ -3,6 +3,7 @@ */ import { EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME, + PartialProductVariation, ProductAttribute, ProductVariation, } from '@woocommerce/data'; @@ -251,7 +252,7 @@ export function useVariations( { productId }: UseVariationsProps ) { async function onUpdate( { id: variationId, ...variation - }: Partial< ProductVariation > ) { + }: PartialProductVariation ) { if ( isUpdating[ variationId ] ) return; const { updateProductVariation } = dispatch( @@ -315,7 +316,7 @@ export function useVariations( { productId }: UseVariationsProps ) { } ); } - async function onBatchUpdate( values: Partial< ProductVariation >[] ) { + async function onBatchUpdate( values: PartialProductVariation[] ) { // @ts-expect-error There are no types for this. const { invalidateResolution: coreInvalidateResolution } = dispatch( 'core' ); @@ -376,7 +377,7 @@ export function useVariations( { productId }: UseVariationsProps ) { return { update: result }; } - async function onBatchDelete( values: Pick< ProductVariation, 'id' >[] ) { + async function onBatchDelete( values: PartialProductVariation[] ) { // @ts-expect-error There are no types for this. const { invalidateResolution: coreInvalidateResolution } = dispatch( 'core' ); @@ -409,7 +410,7 @@ export function useVariations( { productId }: UseVariationsProps ) { } >( { product_id: productId }, { - delete: subset, + delete: subset.map( ( { id } ) => id ), } ); diff --git a/packages/js/product-editor/src/components/variations-table/variation-actions-menus/multiple-update-menu.tsx b/packages/js/product-editor/src/components/variations-table/variation-actions-menus/multiple-update-menu.tsx index b8c46d5ad7f..8c5f6e74fa7 100644 --- a/packages/js/product-editor/src/components/variations-table/variation-actions-menus/multiple-update-menu.tsx +++ b/packages/js/product-editor/src/components/variations-table/variation-actions-menus/multiple-update-menu.tsx @@ -18,6 +18,10 @@ export function MultipleUpdateMenu( { onChange, onDelete, }: VariationActionsMenuProps ) { + if ( ! selection ) { + return null; + } + return ( { let onChangeMock: jest.Mock, onDeleteMock: jest.Mock; @@ -83,7 +83,7 @@ describe( 'SingleUpdateMenu', () => { it( 'should trigger product_variations_menu_view track when dropdown toggled', () => { const { getByRole } = render( @@ -101,7 +101,7 @@ describe( 'SingleUpdateMenu', () => { it( 'should render dropdown with pricing, inventory, and delete options when opened', () => { const { queryByText, getByRole } = render( @@ -115,7 +115,7 @@ describe( 'SingleUpdateMenu', () => { it( 'should call onDelete when Delete menuItem is clicked', async () => { const { getByRole, getByText } = render( @@ -129,7 +129,7 @@ describe( 'SingleUpdateMenu', () => { it( 'should open Inventory sub-menu if Inventory is clicked with click track', async () => { const { queryByText, getByRole, getByText } = render( @@ -140,7 +140,7 @@ describe( 'SingleUpdateMenu', () => { 'product_variations_menu_inventory_click', { source: TRACKS_SOURCE, - variation_id: 10, + variation_id: [ 10 ], } ); expect( queryByText( 'Update stock' ) ).toBeInTheDocument(); @@ -156,7 +156,7 @@ describe( 'SingleUpdateMenu', () => { window.prompt = jest.fn().mockReturnValue( '10' ); const { getByRole, getByText } = render( @@ -170,19 +170,22 @@ describe( 'SingleUpdateMenu', () => { { source: TRACKS_SOURCE, action: 'stock_quantity_set', - variation_id: 10, + variation_id: [ 10 ], } ); - expect( onChangeMock ).toHaveBeenCalledWith( { - stock_quantity: 10, - manage_stock: true, - } ); + expect( onChangeMock ).toHaveBeenCalledWith( [ + { + id: 10, + stock_quantity: 10, + manage_stock: true, + }, + ] ); expect( recordEvent ).toHaveBeenCalledWith( 'product_variations_menu_inventory_update', { source: TRACKS_SOURCE, action: 'stock_quantity_set', - variation_id: 10, + variation_id: [ 10 ], } ); } ); @@ -191,7 +194,7 @@ describe( 'SingleUpdateMenu', () => { window.prompt = jest.fn().mockReturnValue( null ); const { getByRole, getByText } = render( @@ -205,7 +208,7 @@ describe( 'SingleUpdateMenu', () => { { source: TRACKS_SOURCE, action: 'stock_quantity_set', - variation_id: 10, + variation_id: [ 10 ], } ); expect( onChangeMock ).not.toHaveBeenCalledWith( { @@ -217,7 +220,7 @@ describe( 'SingleUpdateMenu', () => { { source: TRACKS_SOURCE, action: 'stock_quantity_set', - variation_id: 10, + variation_id: [ 10 ], } ); } ); @@ -225,7 +228,7 @@ describe( 'SingleUpdateMenu', () => { it( 'should call onChange with toggled manage_stock when toggle "track quantity" is clicked', async () => { const { getByRole, getByText, rerender } = render( @@ -239,16 +242,19 @@ describe( 'SingleUpdateMenu', () => { { source: TRACKS_SOURCE, action: 'manage_stock_toggle', - variation_id: 10, + variation_id: [ 10 ], } ); - expect( onChangeMock ).toHaveBeenCalledWith( { - manage_stock: true, - } ); + expect( onChangeMock ).toHaveBeenCalledWith( [ + { + id: 10, + manage_stock: true, + }, + ] ); onChangeMock.mockClear(); rerender( @@ -256,15 +262,18 @@ describe( 'SingleUpdateMenu', () => { await fireEvent.click( getByRole( 'button', { name: 'Actions' } ) ); await fireEvent.click( getByText( 'Inventory' ) ); await fireEvent.click( getByText( 'Toggle "track quantity"' ) ); - expect( onChangeMock ).toHaveBeenCalledWith( { - manage_stock: false, - } ); + expect( onChangeMock ).toHaveBeenCalledWith( [ + { + id: 10, + manage_stock: false, + }, + ] ); } ); it( 'should call onChange with toggled stock_status when toggle "Set status to In stock" is clicked', async () => { const { getByRole, getByText } = render( @@ -278,19 +287,22 @@ describe( 'SingleUpdateMenu', () => { { source: TRACKS_SOURCE, action: 'set_status_in_stock', - variation_id: 10, + variation_id: [ 10 ], } ); - expect( onChangeMock ).toHaveBeenCalledWith( { - stock_status: PRODUCT_STOCK_STATUS_KEYS.instock, - manage_stock: false, - } ); + expect( onChangeMock ).toHaveBeenCalledWith( [ + { + id: 10, + stock_status: PRODUCT_STOCK_STATUS_KEYS.instock, + manage_stock: false, + }, + ] ); } ); it( 'should call onChange with toggled stock_status when toggle "Set status to Out of stock" is clicked', async () => { const { getByRole, getByText } = render( @@ -304,19 +316,22 @@ describe( 'SingleUpdateMenu', () => { { source: TRACKS_SOURCE, action: 'set_status_out_of_stock', - variation_id: 10, + variation_id: [ 10 ], } ); - expect( onChangeMock ).toHaveBeenCalledWith( { - stock_status: PRODUCT_STOCK_STATUS_KEYS.outofstock, - manage_stock: false, - } ); + expect( onChangeMock ).toHaveBeenCalledWith( [ + { + id: 10, + stock_status: PRODUCT_STOCK_STATUS_KEYS.outofstock, + manage_stock: false, + }, + ] ); } ); it( 'should call onChange with toggled stock_status when toggle "Set status to On back order" is clicked', async () => { const { getByRole, getByText } = render( @@ -330,20 +345,23 @@ describe( 'SingleUpdateMenu', () => { { source: TRACKS_SOURCE, action: 'set_status_on_back_order', - variation_id: 10, + variation_id: [ 10 ], } ); - expect( onChangeMock ).toHaveBeenCalledWith( { - stock_status: PRODUCT_STOCK_STATUS_KEYS.onbackorder, - manage_stock: false, - } ); + expect( onChangeMock ).toHaveBeenCalledWith( [ + { + id: 10, + stock_status: PRODUCT_STOCK_STATUS_KEYS.onbackorder, + manage_stock: false, + }, + ] ); } ); it( 'should call onChange with low_stock_amount when Edit low stock threshold is clicked', async () => { window.prompt = jest.fn().mockReturnValue( '7' ); const { getByRole, getByText } = render( @@ -357,13 +375,16 @@ describe( 'SingleUpdateMenu', () => { { source: TRACKS_SOURCE, action: 'low_stock_amount_set', - variation_id: 10, + variation_id: [ 10 ], } ); - expect( onChangeMock ).toHaveBeenCalledWith( { - low_stock_amount: 7, - manage_stock: true, - } ); + expect( onChangeMock ).toHaveBeenCalledWith( [ + { + id: 10, + low_stock_amount: 7, + manage_stock: true, + }, + ] ); } ); } ); } ); diff --git a/packages/js/product-editor/src/components/variations-table/variation-actions-menus/test/variation-quick-update-menu-item.test.tsx b/packages/js/product-editor/src/components/variations-table/variation-actions-menus/test/variation-quick-update-menu-item.test.tsx index ab6d6405b7c..e023827c011 100644 --- a/packages/js/product-editor/src/components/variations-table/variation-actions-menus/test/variation-quick-update-menu-item.test.tsx +++ b/packages/js/product-editor/src/components/variations-table/variation-actions-menus/test/variation-quick-update-menu-item.test.tsx @@ -26,7 +26,7 @@ const mockVariation = { downloads: [], name: '', parent_id: 1, -} as ProductVariation; +} as unknown as ProductVariation; const anotherMockVariation = { id: 11, @@ -35,7 +35,7 @@ const anotherMockVariation = { downloads: [], name: '', parent_id: 1, -} as ProductVariation; +} as unknown as ProductVariation; describe( 'SingleUpdateMenu', () => { let onClickMock: jest.Mock, @@ -60,7 +60,7 @@ describe( 'SingleUpdateMenu', () => { My top level item @@ -90,7 +90,7 @@ describe( 'SingleUpdateMenu', () => { My secondary item @@ -120,7 +120,7 @@ describe( 'SingleUpdateMenu', () => { My tertiary item @@ -150,7 +150,7 @@ describe( 'SingleUpdateMenu', () => { My shipping item diff --git a/packages/js/product-editor/src/components/variations-table/variation-actions-menus/types.ts b/packages/js/product-editor/src/components/variations-table/variation-actions-menus/types.ts index eb9001e8cbb..003fd161279 100644 --- a/packages/js/product-editor/src/components/variations-table/variation-actions-menus/types.ts +++ b/packages/js/product-editor/src/components/variations-table/variation-actions-menus/types.ts @@ -1,24 +1,20 @@ /** * External dependencies */ -import { ProductVariation } from '@woocommerce/data'; +import { PartialProductVariation, ProductVariation } from '@woocommerce/data'; export type VariationActionsMenuProps = { disabled?: boolean; - selection: ProductVariation | ProductVariation[]; - onChange( variation: Partial< ProductVariation > ): void; - onDelete( - variation: ProductVariation | Partial< ProductVariation >[] - ): void; + selection: ProductVariation[]; + onChange( values: PartialProductVariation[] ): void; + onDelete( values: PartialProductVariation[] ): void; }; export type VariationQuickUpdateSlotProps = { group: string; supportsMultipleSelection: boolean; - selection: ProductVariation | ProductVariation[]; - onChange: ( - variation: Partial< ProductVariation > | Partial< ProductVariation >[] - ) => void; + selection: ProductVariation[]; + onChange: ( values: PartialProductVariation[] ) => void; onClose: () => void; }; @@ -37,8 +33,6 @@ export type MenuItemProps = { | 'onClose' | 'selection' ]: VariationQuickUpdateSlotProps[ K ]; } ) => void; - onChange?: ( - variation: Partial< ProductVariation > | Partial< ProductVariation >[] - ) => void; + onChange?: ( values: PartialProductVariation[] ) => void; onClose?: () => void; }; diff --git a/packages/js/product-editor/src/components/variations-table/variation-actions-menus/variation-actions.tsx b/packages/js/product-editor/src/components/variations-table/variation-actions-menus/variation-actions.tsx index d8864aac56c..a1882f1db77 100644 --- a/packages/js/product-editor/src/components/variations-table/variation-actions-menus/variation-actions.tsx +++ b/packages/js/product-editor/src/components/variations-table/variation-actions-menus/variation-actions.tsx @@ -5,7 +5,6 @@ import { MenuGroup, MenuItem } from '@wordpress/components'; import { createElement, Fragment } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; import { recordEvent } from '@woocommerce/tracks'; -import { ProductVariation } from '@woocommerce/data'; import classNames from 'classnames'; /** @@ -32,6 +31,11 @@ export function VariationActions( { onClose: () => void; supportsMultipleSelection: boolean; } ) { + const singleSelection = + ! supportsMultipleSelection && selection.length === 1 + ? selection[ 0 ] + : null; + return (
@@ -64,14 +68,13 @@ export function VariationActions( { ) : ( { recordEvent( 'product_variations_preview', { source: TRACKS_SOURCE, - variation_id: ( selection as ProductVariation ) - .id, + variation_id: singleSelection?.id, } ); } } > diff --git a/packages/js/product-editor/src/components/variations-table/variations-table-row/types.ts b/packages/js/product-editor/src/components/variations-table/variations-table-row/types.ts index 6420a922752..6b9c4566113 100644 --- a/packages/js/product-editor/src/components/variations-table/variations-table-row/types.ts +++ b/packages/js/product-editor/src/components/variations-table/variations-table-row/types.ts @@ -2,7 +2,11 @@ * External dependencies */ import { MouseEvent } from 'react'; -import { ProductAttribute, ProductVariation } from '@woocommerce/data'; +import { + PartialProductVariation, + ProductAttribute, + ProductVariation, +} from '@woocommerce/data'; export type VariationsTableRowProps = { variation: ProductVariation; @@ -11,8 +15,8 @@ export type VariationsTableRowProps = { isSelected?: boolean; isSelectionDisabled?: boolean; hideActionButtons?: boolean; - onChange( variation: ProductVariation ): void; - onDelete( variation: ProductVariation ): void; + onChange( variation: PartialProductVariation ): void; + onDelete( variation: PartialProductVariation ): void; onEdit( event: MouseEvent< HTMLAnchorElement > ): void; onSelect( value: boolean ): void; }; diff --git a/packages/js/product-editor/src/components/variations-table/variations-table-row/variations-table-row.tsx b/packages/js/product-editor/src/components/variations-table/variations-table-row/variations-table-row.tsx index 4c3918fc9b6..879dab29f84 100644 --- a/packages/js/product-editor/src/components/variations-table/variations-table-row/variations-table-row.tsx +++ b/packages/js/product-editor/src/components/variations-table/variations-table-row/variations-table-row.tsx @@ -4,7 +4,7 @@ import { Tag, __experimentalTooltip as Tooltip } from '@woocommerce/components'; import { CurrencyContext } from '@woocommerce/currency'; -import { ProductVariation } from '@woocommerce/data'; +import { PartialProductVariation, ProductVariation } from '@woocommerce/data'; import { getNewPath } from '@woocommerce/navigation'; import { Button, CheckboxControl, Spinner } from '@wordpress/components'; import { @@ -90,11 +90,12 @@ export function VariationsTableRow( { [ variableAttributes, variation ] ); - function handleChange( value: Partial< ProductVariation > ) { - onChange( { - ...variation, - ...value, - } ); + function handleChange( values: PartialProductVariation[] ) { + onChange( values[ 0 ] ); + } + + function handleDelete( values: PartialProductVariation[] ) { + onDelete( values[ 0 ] ); } return ( @@ -241,9 +242,9 @@ export function VariationsTableRow( { ) } diff --git a/packages/js/product-editor/src/components/variations-table/variations-table.tsx b/packages/js/product-editor/src/components/variations-table/variations-table.tsx index b405f7047fb..d89414ea184 100644 --- a/packages/js/product-editor/src/components/variations-table/variations-table.tsx +++ b/packages/js/product-editor/src/components/variations-table/variations-table.tsx @@ -3,7 +3,11 @@ */ import { __, sprintf } from '@wordpress/i18n'; import { Button, CheckboxControl, Notice } from '@wordpress/components'; -import { Product, ProductVariation } from '@woocommerce/data'; +import { + PartialProductVariation, + Product, + ProductVariation, +} from '@woocommerce/data'; import { recordEvent } from '@woocommerce/tracks'; import { ListItem, Sortable } from '@woocommerce/components'; import { @@ -37,10 +41,8 @@ type VariationsTableProps = { noticeActions?: { label: string; onClick: ( - handleUpdateAll: ( values: Partial< ProductVariation >[] ) => void, - handleDeleteAll: ( - values: Pick< ProductVariation, 'id' >[] - ) => void + handleUpdateAll: ( values: PartialProductVariation[] ) => void, + handleDeleteAll: ( values: PartialProductVariation[] ) => void ) => void; className?: string; variant?: string; @@ -174,7 +176,7 @@ export const VariationsTable = forwardRef< ); } - function handleDeleteVariationClick( variation: ProductVariation ) { + function handleDeleteVariationClick( variation: PartialProductVariation ) { onDelete( variation.id ) .then( ( response ) => { recordEvent( 'product_variations_delete', { @@ -194,7 +196,7 @@ export const VariationsTable = forwardRef< } ); } - function handleVariationChange( variation: Partial< ProductVariation > ) { + function handleVariationChange( variation: PartialProductVariation ) { onUpdate( variation ) .then( ( response ) => { recordEvent( 'product_variations_change', { @@ -214,7 +216,7 @@ export const VariationsTable = forwardRef< } ); } - function handleUpdateAll( values: Partial< ProductVariation >[] ) { + function handleUpdateAll( values: PartialProductVariation[] ) { const now = Date.now(); onBatchUpdate( values ) @@ -235,10 +237,10 @@ export const VariationsTable = forwardRef< } ); } - function handleDeleteAll( values: Partial< ProductVariation >[] ) { + function handleDeleteAll( values: PartialProductVariation[] ) { const now = Date.now(); - onBatchDelete( values.map( ( variation ) => variation.id ) ) + onBatchDelete( values ) .then( ( response: VariationResponseProps ) => { recordEvent( 'product_variations_delete_all', { source: TRACKS_SOURCE,