From 44cf396be6f327ff3821f7afb3867413bc42e24d Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Thu, 15 Dec 2022 15:20:21 -0300 Subject: [PATCH 01/70] Product variation quantity status indicator (#35982) * Add variation status indicator * Add changelog * Add tests * Fix style * Rename enum * Fix lint Co-authored-by: Fernando Marichal --- .../fields/variations/variations.scss | 13 ++ .../products/fields/variations/variations.tsx | 14 +- .../utils/get-product-stock-status.ts | 33 +++++ .../test/get-product-stock-status.test.ts | 124 ++++++++++++++++++ ...-35791_variation_quantity_status_indicator | 4 + 5 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 plugins/woocommerce-admin/client/products/utils/test/get-product-stock-status.test.ts create mode 100644 plugins/woocommerce/changelog/add-35791_variation_quantity_status_indicator diff --git a/plugins/woocommerce-admin/client/products/fields/variations/variations.scss b/plugins/woocommerce-admin/client/products/fields/variations/variations.scss index 7a4b3f5fc62..fe9ceff53b6 100644 --- a/plugins/woocommerce-admin/client/products/fields/variations/variations.scss +++ b/plugins/woocommerce-admin/client/products/fields/variations/variations.scss @@ -22,6 +22,19 @@ } } + &__status-dot { + margin-right: $gap-smaller; + &.green { + color: $alert-green; + } + &.yellow { + color: $alert-yellow; + } + &.red { + color: $alert-red; + } + } + .woocommerce-list-item { display: grid; grid-template-columns: 38px 25% 25% 25%; diff --git a/plugins/woocommerce-admin/client/products/fields/variations/variations.tsx b/plugins/woocommerce-admin/client/products/fields/variations/variations.tsx index ec894b6c944..04c08748e29 100644 --- a/plugins/woocommerce-admin/client/products/fields/variations/variations.tsx +++ b/plugins/woocommerce-admin/client/products/fields/variations/variations.tsx @@ -11,12 +11,16 @@ import { ListItem, Pagination, Sortable, Tag } from '@woocommerce/components'; import { useContext, useState } from '@wordpress/element'; import { useParams } from 'react-router-dom'; import { useSelect } from '@wordpress/data'; +import classnames from 'classnames'; /** * Internal dependencies */ import { CurrencyContext } from '../../../lib/currency-context'; -import { getProductStockStatus } from '../../utils/get-product-stock-status'; +import { + getProductStockStatus, + getProductStockStatusClass, +} from '../../utils/get-product-stock-status'; import './variations.scss'; /** @@ -102,6 +106,14 @@ export const Variations: React.FC = () => { { formatAmount( variation.price ) }
+ + ● + { getProductStockStatus( variation ) }
diff --git a/plugins/woocommerce-admin/client/products/utils/get-product-stock-status.ts b/plugins/woocommerce-admin/client/products/utils/get-product-stock-status.ts index 39a29cd4b50..88ba69a46eb 100644 --- a/plugins/woocommerce-admin/client/products/utils/get-product-stock-status.ts +++ b/plugins/woocommerce-admin/client/products/utils/get-product-stock-status.ts @@ -13,6 +13,15 @@ export enum PRODUCT_STOCK_STATUS_KEYS { outofstock = 'outofstock', } +/** + * Product stock status colors. + */ +export enum PRODUCT_STOCK_STATUS_CLASSES { + instock = 'green', + onbackorder = 'yellow', + outofstock = 'red', +} + /** * Labels for product stock statuses. */ @@ -47,3 +56,27 @@ export const getProductStockStatus = ( return PRODUCT_STOCK_STATUS_LABELS.instock; }; + +/** + * Get the product stock status class. + * + * @param product Product instance. + * @return {PRODUCT_STOCK_STATUS_CLASSES} Product stock status class. + */ +export const getProductStockStatusClass = ( + product: PartialProduct | Partial< ProductVariation > +): string => { + if ( product.manage_stock ) { + const stockQuantity: number = product.stock_quantity || 0; + if ( stockQuantity >= 10 ) { + return PRODUCT_STOCK_STATUS_CLASSES.instock; + } + if ( stockQuantity < 10 && stockQuantity > 2 ) { + return PRODUCT_STOCK_STATUS_CLASSES.onbackorder; + } + return PRODUCT_STOCK_STATUS_CLASSES.outofstock; + } + return product.stock_status + ? PRODUCT_STOCK_STATUS_CLASSES[ product.stock_status ] + : ''; +}; diff --git a/plugins/woocommerce-admin/client/products/utils/test/get-product-stock-status.test.ts b/plugins/woocommerce-admin/client/products/utils/test/get-product-stock-status.test.ts new file mode 100644 index 00000000000..78d581504a0 --- /dev/null +++ b/plugins/woocommerce-admin/client/products/utils/test/get-product-stock-status.test.ts @@ -0,0 +1,124 @@ +/** + * External dependencies + */ +import { PartialProduct } from '@woocommerce/data'; + +/** + * Internal dependencies + */ +import { + getProductStockStatus, + getProductStockStatusClass, +} from '../get-product-stock-status'; + +const products = [ + { + status: 'publish', + } as PartialProduct, + { + status: 'publish', + stock_status: 'outofstock', + } as PartialProduct, + { + manage_stock: true, + stock_quantity: 15, + status: 'publish', + stock_status: 'instock', + } as PartialProduct, + { + manage_stock: true, + status: 'publish', + stock_status: 'instock', + } as PartialProduct, + { + manage_stock: true, + stock_quantity: 5, + status: 'publish', + stock_status: 'instock', + } as PartialProduct, + { + manage_stock: true, + stock_quantity: 1, + status: 'publish', + stock_status: 'instock', + } as PartialProduct, + { + manage_stock: false, + status: 'publish', + stock_status: 'instock', + } as PartialProduct, + { + manage_stock: false, + status: 'publish', + stock_status: 'onbackorder', + } as PartialProduct, + { + manage_stock: false, + status: 'publish', + stock_status: 'outofstock', + } as PartialProduct, +]; + +describe( 'getProductStockStatus', () => { + it( 'should return `In stock` status when the stock is not being managed and there is no stock status', () => { + const status = getProductStockStatus( products[ 0 ] ); + expect( status ).toBe( 'In stock' ); + } ); + + it( 'should return the stock status when there is a stock status and the stock is not being managed', () => { + const status = getProductStockStatus( products[ 1 ] ); + expect( status ).toBe( 'Out of stock' ); + } ); + + it( 'should return the stock quantity when the stock is being managed', () => { + const status = getProductStockStatus( products[ 2 ] ); + expect( status ).toBe( 15 ); + } ); + + it( 'should return stock quantity = 0 when the stock is being managed but there is no a stock quantity', () => { + const status = getProductStockStatus( products[ 3 ] ); + expect( status ).toBe( 0 ); + } ); +} ); + +describe( 'getProductStockStatusClass', () => { + it( 'should return an emtpy string when the stock is not being managed and there is no stock status', () => { + const status = getProductStockStatusClass( products[ 0 ] ); + expect( status ).toBe( '' ); + } ); + + it( 'should return `green` when the stock is being managed and the stock quantity is higher or equal than 10', () => { + const status = getProductStockStatusClass( products[ 2 ] ); + expect( status ).toBe( 'green' ); + } ); + + it( 'should return `yellow` when the stock is being managed and the stock quantity is lower than 10 but higher than 2', () => { + const status = getProductStockStatusClass( products[ 4 ] ); + expect( status ).toBe( 'yellow' ); + } ); + + it( 'should return `red` when the stock is being managed and the stock quantity is lower or equal than 2', () => { + const status = getProductStockStatusClass( products[ 5 ] ); + expect( status ).toBe( 'red' ); + } ); + + it( 'should return `red` when the stock is being managed but there is no a stock quantity', () => { + const status = getProductStockStatusClass( products[ 3 ] ); + expect( status ).toBe( 'red' ); + } ); + + it( 'should return `green` when the stock is not being managed and the stock status is `instock`', () => { + const status = getProductStockStatusClass( products[ 6 ] ); + expect( status ).toBe( 'green' ); + } ); + + it( 'should return `yellow` when the stock is not being managed and the stock status is `onbackorder`', () => { + const status = getProductStockStatusClass( products[ 7 ] ); + expect( status ).toBe( 'yellow' ); + } ); + + it( 'should return `red` when the stock is not being managed and the stock status is `outofstock`', () => { + const status = getProductStockStatusClass( products[ 8 ] ); + expect( status ).toBe( 'red' ); + } ); +} ); diff --git a/plugins/woocommerce/changelog/add-35791_variation_quantity_status_indicator b/plugins/woocommerce/changelog/add-35791_variation_quantity_status_indicator new file mode 100644 index 00000000000..d9bd567e84a --- /dev/null +++ b/plugins/woocommerce/changelog/add-35791_variation_quantity_status_indicator @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Product variation quantity status indicator From 266b61cd4cb4dc8fee5a1a7d6f7e2e1a41b1cde7 Mon Sep 17 00:00:00 2001 From: Joshua T Flowers Date: Thu, 15 Dec 2022 11:20:10 -0800 Subject: [PATCH 02/70] Disable irrelevant product tabs when variations exist (#35939) * Add disabled prop to product form tabs * Add tooltips to disabled tabs * Add styling for tooltips when disabled * Add changelog entry * Update disabled styles for experimental focus buttons --- .../products/layout/product-form-layout.scss | 16 ++++++++++++ .../products/layout/product-form-layout.tsx | 26 +++++++++++++++++-- .../client/products/product-form-tab.tsx | 1 + .../client/products/product-form.tsx | 18 ++++++++++--- plugins/woocommerce/changelog/update-35779 | 4 +++ 5 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 plugins/woocommerce/changelog/update-35779 diff --git a/plugins/woocommerce-admin/client/products/layout/product-form-layout.scss b/plugins/woocommerce-admin/client/products/layout/product-form-layout.scss index 73255c8fc66..59c240aa9d7 100644 --- a/plugins/woocommerce-admin/client/products/layout/product-form-layout.scss +++ b/plugins/woocommerce-admin/client/products/layout/product-form-layout.scss @@ -62,6 +62,22 @@ $product-form-tabs-height: 56px; font-weight: 600; box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) transparent, inset 0 -3px 0 0 var(--wp-admin-theme-color); } + + &:disabled, + &[aria-disabled='true'] { + // We need tooltips at full opacity so only child elements have reduced opacity. + opacity: 1; + + .woocommerce-product-form-tab__item-inner-text { + opacity: 0.3; + } + } + + .woocommerce-product-form-tab__item-inner { + min-height: $product-form-tabs-height; + display: flex; + align-items: center; + } } } diff --git a/plugins/woocommerce-admin/client/products/layout/product-form-layout.tsx b/plugins/woocommerce-admin/client/products/layout/product-form-layout.tsx index 713c33e6bd0..f801958d191 100644 --- a/plugins/woocommerce-admin/client/products/layout/product-form-layout.tsx +++ b/plugins/woocommerce-admin/client/products/layout/product-form-layout.tsx @@ -3,7 +3,7 @@ */ import { __ } from '@wordpress/i18n'; import { Children, useEffect } from '@wordpress/element'; -import { TabPanel } from '@wordpress/components'; +import { TabPanel, Tooltip } from '@wordpress/components'; /** * Internal dependencies @@ -32,7 +32,27 @@ export const ProductFormLayout: React.FC< { } return { name: child.props.name, - title: child.props.title, + title: child.props.disabled ? ( + + + + { child.props.title } + + + + ) : ( + + + { child.props.title } + + + ), + disabled: child.props.disabled, }; } ); @@ -40,6 +60,8 @@ export const ProductFormLayout: React.FC< { ( window.document.documentElement.scrollTop = 0 ) } > diff --git a/plugins/woocommerce-admin/client/products/product-form-tab.tsx b/plugins/woocommerce-admin/client/products/product-form-tab.tsx index 9795f664a11..2c2f0b9a814 100644 --- a/plugins/woocommerce-admin/client/products/product-form-tab.tsx +++ b/plugins/woocommerce-admin/client/products/product-form-tab.tsx @@ -4,6 +4,7 @@ import classnames from 'classnames'; export const ProductFormTab: React.FC< { + disabled?: boolean; name: string; title: string; children: JSX.Element | JSX.Element[] | string; diff --git a/plugins/woocommerce-admin/client/products/product-form.tsx b/plugins/woocommerce-admin/client/products/product-form.tsx index d2a98a57ca5..a7ef34a3941 100644 --- a/plugins/woocommerce-admin/client/products/product-form.tsx +++ b/plugins/woocommerce-admin/client/products/product-form.tsx @@ -48,13 +48,25 @@ export const ProductForm: React.FC< { - + - + - + diff --git a/plugins/woocommerce/changelog/update-35779 b/plugins/woocommerce/changelog/update-35779 new file mode 100644 index 00000000000..98fa093a500 --- /dev/null +++ b/plugins/woocommerce/changelog/update-35779 @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Disable irrelevant product tabs when variations exist From 186dc427b08bc89620dffcd67673a31a8f3db695 Mon Sep 17 00:00:00 2001 From: Barry Hughes <3594411+barryhughes@users.noreply.github.com> Date: Thu, 15 Dec 2022 11:26:32 -0800 Subject: [PATCH 03/70] Change the default currency for Croatia to Euros (#35963) * Change the default currency for Croatia to Euros, beginning 2023-01-01. We do not completely remove the Kuna (HKR) even after the grace period ends (2022-01-15), since an automatic switch to Euros in the storefront, without any sort of conversion, could be challenging for merchants. * Simplify: we don't need the date-conditional because we are shipping in 7.3 (post-transition). * Whitespace. * Update continents test to account for Croatia currency change. * Further change to API test for continents/currencies. --- plugins/woocommerce/changelog/fix-35872-hrk-eur | 4 ++++ plugins/woocommerce/i18n/locale-info.php | 12 ++++++------ .../api-core-tests/tests/data/data-crud.test.js | 8 ++++---- 3 files changed, 14 insertions(+), 10 deletions(-) create mode 100644 plugins/woocommerce/changelog/fix-35872-hrk-eur diff --git a/plugins/woocommerce/changelog/fix-35872-hrk-eur b/plugins/woocommerce/changelog/fix-35872-hrk-eur new file mode 100644 index 00000000000..58565ce6627 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-35872-hrk-eur @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +As of 2023-01-01, Croatia will use the Euro by default. diff --git a/plugins/woocommerce/i18n/locale-info.php b/plugins/woocommerce/i18n/locale-info.php index 49ff4a3e3f4..919cd66999f 100644 --- a/plugins/woocommerce/i18n/locale-info.php +++ b/plugins/woocommerce/i18n/locale-info.php @@ -1532,7 +1532,7 @@ return array( 'locales' => $locales['HNL'], ), 'HR' => array( - 'currency_code' => 'HRK', + 'currency_code' => 'EUR', 'currency_pos' => 'right_space', 'thousand_sep' => '.', 'decimal_sep' => ',', @@ -1541,11 +1541,11 @@ return array( 'dimension_unit' => 'cm', 'direction' => 'ltr', 'default_locale' => 'hr_HR', - 'name' => 'Croatian kuna', - 'singular' => 'Croatian kuna', - 'plural' => 'Croatian kunas', - 'short_symbol' => 'kn', - 'locales' => $locales['HRK'], + 'name' => 'Euro', + 'singular' => 'euro', + 'plural' => 'euros', + 'short_symbol' => '€', + 'locales' => $locales['EUR'], ), 'HT' => array( 'currency_code' => 'USD', diff --git a/plugins/woocommerce/tests/api-core-tests/tests/data/data-crud.test.js b/plugins/woocommerce/tests/api-core-tests/tests/data/data-crud.test.js index 2c31224e053..b977c21fb13 100644 --- a/plugins/woocommerce/tests/api-core-tests/tests/data/data-crud.test.js +++ b/plugins/woocommerce/tests/api-core-tests/tests/data/data-crud.test.js @@ -1694,8 +1694,8 @@ test.describe('Data API tests', () => { }, { "code": "HR", - "name": "Croatian kuna", - "currency_code": "HRK", + "name": "Euro", + "currency_code": "EUR", "currency_pos": "right_space", "decimal_sep": ",", "dimension_unit": "cm", @@ -3333,8 +3333,8 @@ test.describe('Data API tests', () => { }, { "code": "HR", - "name": "Croatian kuna", - "currency_code": "HRK", + "name": "Euro", + "currency_code": "EUR", "currency_pos": "right_space", "decimal_sep": ",", "dimension_unit": "cm", From 895cb1561c749b776040fb1948d6f9f682b87aa9 Mon Sep 17 00:00:00 2001 From: louwie17 Date: Thu, 15 Dec 2022 15:50:34 -0400 Subject: [PATCH 04/70] Add/35126 ces exit prompt orders (#35762) * Add exit tracking for orders * Update exit page CES action * Fix order hook name * Add changelog * Address PR feedback --- .../customer-effort-score-exit-page.ts | 25 ++++++++++ .../client/utils/static-form-helper.ts | 1 + .../order-tracking/exit-order-page.ts | 49 +++++++++++++++++++ .../wp-admin-scripts/order-tracking/index.ts | 1 + plugins/woocommerce-admin/webpack.config.js | 1 + .../add-35126_ces_exit_prompt_orders | 4 ++ .../events/class-wc-orders-tracking.php | 18 +++++++ 7 files changed, 99 insertions(+) create mode 100644 plugins/woocommerce-admin/client/wp-admin-scripts/order-tracking/exit-order-page.ts create mode 100644 plugins/woocommerce-admin/client/wp-admin-scripts/order-tracking/index.ts create mode 100644 plugins/woocommerce/changelog/add-35126_ces_exit_prompt_orders diff --git a/plugins/woocommerce-admin/client/customer-effort-score-tracks/customer-effort-score-exit-page.ts b/plugins/woocommerce-admin/client/customer-effort-score-tracks/customer-effort-score-exit-page.ts index 9b261d3de62..2aab5c6f6fe 100644 --- a/plugins/woocommerce-admin/client/customer-effort-score-tracks/customer-effort-score-exit-page.ts +++ b/plugins/woocommerce-admin/client/customer-effort-score-tracks/customer-effort-score-exit-page.ts @@ -205,6 +205,31 @@ function getExitPageCESCopy( pageId: string ): { 'woocommerce' ), }; + case 'shop_order_update': + return { + action: pageId, + icon: '📦', + noticeLabel: __( + 'How easy or difficult was it to update this order?', + 'woocommerce' + ), + title: __( + "How's your experience with orders?", + 'woocommerce' + ), + description: __( + 'We noticed you started editing an order, then left. How was it? Your feedback will help create a better experience for thousands of merchants like you.', + 'woocommerce' + ), + firstQuestion: __( + 'The order editing screen is easy to use', + 'woocommerce' + ), + secondQuestion: __( + "The order details screen's functionality meets my needs", + 'woocommerce' + ), + }; default: return null; } diff --git a/plugins/woocommerce-admin/client/utils/static-form-helper.ts b/plugins/woocommerce-admin/client/utils/static-form-helper.ts index 7d88d2fde85..a30189ecbbc 100644 --- a/plugins/woocommerce-admin/client/utils/static-form-helper.ts +++ b/plugins/woocommerce-admin/client/utils/static-form-helper.ts @@ -12,6 +12,7 @@ export function staticFormDataToObject( elForm: HTMLFormElement ) { field.type === 'button' || field.type === 'image' || field.type === 'submit' || + field.type === 'hidden' || ! sKey ) continue; diff --git a/plugins/woocommerce-admin/client/wp-admin-scripts/order-tracking/exit-order-page.ts b/plugins/woocommerce-admin/client/wp-admin-scripts/order-tracking/exit-order-page.ts new file mode 100644 index 00000000000..5a4768de0e2 --- /dev/null +++ b/plugins/woocommerce-admin/client/wp-admin-scripts/order-tracking/exit-order-page.ts @@ -0,0 +1,49 @@ +/** + * Internal dependencies + */ +import { addCustomerEffortScoreExitPageListener } from '~/customer-effort-score-tracks/customer-effort-score-exit-page'; +import { staticFormDataToObject } from '~/utils/static-form-helper'; + +type FormElements = { + post?: HTMLFormElement; +} & HTMLCollectionOf< HTMLFormElement >; +const forms: FormElements = document.forms; +if ( forms?.post ) { + let triggeredSaveOrDeleteButton = false; + const saveButton = document.querySelector( '.save_order' ); + const deleteButton = document.querySelector( '.submitdelete' ); + + if ( saveButton ) { + saveButton.addEventListener( 'click', () => { + triggeredSaveOrDeleteButton = true; + } ); + } + if ( deleteButton ) { + deleteButton.addEventListener( 'click', () => { + triggeredSaveOrDeleteButton = true; + } ); + } + const formData = staticFormDataToObject( forms.post ); + addCustomerEffortScoreExitPageListener( 'shop_order_update', () => { + if ( triggeredSaveOrDeleteButton ) { + return false; + } + const newFormData = forms.post + ? staticFormDataToObject( forms.post ) + : {}; + for ( const key of Object.keys( formData ) ) { + const value = + typeof formData[ key ] === 'object' + ? JSON.stringify( formData[ key ] ) + : formData[ key ]; + const newValue = + typeof newFormData[ key ] === 'object' + ? JSON.stringify( newFormData[ key ] ) + : newFormData[ key ]; + if ( value !== newValue ) { + return true; + } + } + return false; + } ); +} diff --git a/plugins/woocommerce-admin/client/wp-admin-scripts/order-tracking/index.ts b/plugins/woocommerce-admin/client/wp-admin-scripts/order-tracking/index.ts new file mode 100644 index 00000000000..ebe12a7927a --- /dev/null +++ b/plugins/woocommerce-admin/client/wp-admin-scripts/order-tracking/index.ts @@ -0,0 +1 @@ +export * from './exit-order-page'; diff --git a/plugins/woocommerce-admin/webpack.config.js b/plugins/woocommerce-admin/webpack.config.js index 5d63446b17c..e6c25a7bb61 100644 --- a/plugins/woocommerce-admin/webpack.config.js +++ b/plugins/woocommerce-admin/webpack.config.js @@ -58,6 +58,7 @@ const wpAdminScripts = [ 'product-tour', 'wc-addons-tour', 'settings-tracking', + 'order-tracking', ]; const getEntryPoints = () => { const entryPoints = { diff --git a/plugins/woocommerce/changelog/add-35126_ces_exit_prompt_orders b/plugins/woocommerce/changelog/add-35126_ces_exit_prompt_orders new file mode 100644 index 00000000000..4e00dd6a4ef --- /dev/null +++ b/plugins/woocommerce/changelog/add-35126_ces_exit_prompt_orders @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add exit prompt CES for users editing orders when tracking is enabled. diff --git a/plugins/woocommerce/includes/tracks/events/class-wc-orders-tracking.php b/plugins/woocommerce/includes/tracks/events/class-wc-orders-tracking.php index e823ee59b74..fb3c5cc9109 100644 --- a/plugins/woocommerce/includes/tracks/events/class-wc-orders-tracking.php +++ b/plugins/woocommerce/includes/tracks/events/class-wc-orders-tracking.php @@ -6,6 +6,7 @@ */ use Automattic\WooCommerce\Utilities\OrderUtil; +use Automattic\WooCommerce\Internal\Admin\WCAdminAssets; defined( 'ABSPATH' ) || exit; @@ -24,6 +25,7 @@ class WC_Orders_Tracking { add_action( 'woocommerce_process_shop_order_meta', array( $this, 'track_order_action' ), 51 ); add_action( 'load-post-new.php', array( $this, 'track_add_order_from_edit' ), 10 ); add_filter( 'woocommerce_shop_order_search_results', array( $this, 'track_order_search' ), 10, 3 ); + add_action( 'admin_enqueue_scripts', array( $this, 'possibly_add_order_tracking_scripts' ) ); } /** @@ -175,4 +177,20 @@ class WC_Orders_Tracking { } } } + + /** + * Adds the tracking scripts for product setting pages. + * + * @param string $hook Page hook. + */ + public function possibly_add_order_tracking_scripts( $hook ) { + // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification + if ( + ( isset( $_GET['post_type'] ) && 'shop_order' === wp_unslash( $_GET['post_type'] ) ) || + ( 'post.php' === $hook && isset( $_GET['post'] ) && 'shop_order' === get_post_type( intval( $_GET['post'] ) ) ) + ) { + WCAdminAssets::register_script( 'wp-admin-scripts', 'order-tracking', false ); + } + // phpcs:enable + } } From 84059746741b4ba830d89dab0cdce4dbcdced9b7 Mon Sep 17 00:00:00 2001 From: Joel Thiessen <444632+joelclimbsthings@users.noreply.github.com> Date: Thu, 15 Dec 2022 13:18:35 -0800 Subject: [PATCH 05/70] Adding ces exit prompt when product importer abandoned (#35996) --- .../customer-effort-score-exit-page.ts | 55 +++++++++++++++++-- .../product-import-tracking/index.ts | 1 + .../product-import-tracking.ts | 29 ++++++++++ plugins/woocommerce-admin/webpack.config.js | 1 + .../add-35126_ces_exit_prompt_import | 4 ++ .../events/class-wc-products-tracking.php | 22 ++++++++ 6 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 plugins/woocommerce-admin/client/wp-admin-scripts/product-import-tracking/index.ts create mode 100644 plugins/woocommerce-admin/client/wp-admin-scripts/product-import-tracking/product-import-tracking.ts create mode 100644 plugins/woocommerce/changelog/add-35126_ces_exit_prompt_import diff --git a/plugins/woocommerce-admin/client/customer-effort-score-tracks/customer-effort-score-exit-page.ts b/plugins/woocommerce-admin/client/customer-effort-score-tracks/customer-effort-score-exit-page.ts index 2aab5c6f6fe..dda0ebb885b 100644 --- a/plugins/woocommerce-admin/client/customer-effort-score-tracks/customer-effort-score-exit-page.ts +++ b/plugins/woocommerce-admin/client/customer-effort-score-tracks/customer-effort-score-exit-page.ts @@ -4,6 +4,7 @@ import { __ } from '@wordpress/i18n'; import { OPTIONS_STORE_NAME } from '@woocommerce/data'; import { dispatch, resolveSelect } from '@wordpress/data'; +import { getQuery } from '@woocommerce/navigation'; /** * Internal dependencies @@ -42,7 +43,7 @@ export const getExitPageData = () => { * @param {string} pageId of page exited early. */ export const addExitPage = ( pageId: string ) => { - if ( ! window.localStorage ) { + if ( ! ( window.localStorage && allowTracking ) ) { return; } @@ -93,8 +94,8 @@ export const addCustomerEffortScoreExitPageListener = ( pageId: string, hasUnsavedChanges: () => boolean ) => { - eventListeners[ pageId ] = ( event ) => { - if ( hasUnsavedChanges() && allowTracking ) { + eventListeners[ pageId ] = () => { + if ( hasUnsavedChanges() ) { addExitPage( pageId ); } }; @@ -230,19 +231,63 @@ function getExitPageCESCopy( pageId: string ): { 'woocommerce' ), }; + case 'import_products': + return { + action: pageId, + icon: '🔄', + noticeLabel: __( + 'How is your experience with importing products?', + 'woocommerce' + ), + title: __( + `How's your experience with importing products?`, + 'woocommerce' + ), + description: __( + 'We noticed you started importing products, then left. How was it? Your feedback will help create a better experience for thousands of merchants like you.', + 'woocommerce' + ), + firstQuestion: __( + 'The product import screen is easy to use', + 'woocommerce' + ), + secondQuestion: __( + "The product import screen's functionality meets my needs", + 'woocommerce' + ), + }; default: return null; } } +/** + * Stores trigger conditions for exit page actions. + * + * @param {string} pageId page id. + */ +function getShouldExitPageFire( pageId: string ) { + const conditionPageMap: Record< string, () => boolean > = { + import_products: () => + ( getQuery() as { page: string } ).page !== 'product_importer', + }; + + return conditionPageMap[ pageId ] ? conditionPageMap[ pageId ]() : true; +} + /** * Checks the exit page list and triggers a CES survey for the first item in the list. */ export function triggerExitPageCesSurvey() { const exitPageItems: string[] = getExitPageData(); - if ( exitPageItems && exitPageItems.length > 0 ) { + if ( exitPageItems?.length ) { + if ( ! getShouldExitPageFire( exitPageItems[ 0 ] ) ) { + return; + } + const copy = getExitPageCESCopy( exitPageItems[ 0 ] ); - if ( copy && copy.title.length > 0 ) { + + if ( copy?.title?.length ) { dispatch( 'wc/customer-effort-score' ).addCesSurvey( { ...copy, pageNow: window.pagenow, diff --git a/plugins/woocommerce-admin/client/wp-admin-scripts/product-import-tracking/index.ts b/plugins/woocommerce-admin/client/wp-admin-scripts/product-import-tracking/index.ts new file mode 100644 index 00000000000..947f762a879 --- /dev/null +++ b/plugins/woocommerce-admin/client/wp-admin-scripts/product-import-tracking/index.ts @@ -0,0 +1 @@ +export * from './product-import-tracking'; diff --git a/plugins/woocommerce-admin/client/wp-admin-scripts/product-import-tracking/product-import-tracking.ts b/plugins/woocommerce-admin/client/wp-admin-scripts/product-import-tracking/product-import-tracking.ts new file mode 100644 index 00000000000..2ad37d9a56e --- /dev/null +++ b/plugins/woocommerce-admin/client/wp-admin-scripts/product-import-tracking/product-import-tracking.ts @@ -0,0 +1,29 @@ +/** + * External dependencies + */ +import { getQuery } from '@woocommerce/navigation'; + +/** + * Internal dependencies + */ +import { + addExitPage, + removeExitPage, +} from '~/customer-effort-score-tracks/customer-effort-score-exit-page'; + +const ACTION_NAME = 'import_products'; + +( () => { + const query: { step?: string; page?: string } = getQuery(); + + if ( query.page !== 'product_importer' ) { + return; + } + + if ( query.step === 'done' ) { + removeExitPage( ACTION_NAME ); + return; + } + + addExitPage( ACTION_NAME ); +} )(); diff --git a/plugins/woocommerce-admin/webpack.config.js b/plugins/woocommerce-admin/webpack.config.js index e6c25a7bb61..00b70e43f47 100644 --- a/plugins/woocommerce-admin/webpack.config.js +++ b/plugins/woocommerce-admin/webpack.config.js @@ -59,6 +59,7 @@ const wpAdminScripts = [ 'wc-addons-tour', 'settings-tracking', 'order-tracking', + 'product-import-tracking', ]; const getEntryPoints = () => { const entryPoints = { diff --git a/plugins/woocommerce/changelog/add-35126_ces_exit_prompt_import b/plugins/woocommerce/changelog/add-35126_ces_exit_prompt_import new file mode 100644 index 00000000000..4f5ad8f016f --- /dev/null +++ b/plugins/woocommerce/changelog/add-35126_ces_exit_prompt_import @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Run ces exit prompt when product import abandoned. diff --git a/plugins/woocommerce/includes/tracks/events/class-wc-products-tracking.php b/plugins/woocommerce/includes/tracks/events/class-wc-products-tracking.php index 129f5072266..920f2ae1e8b 100644 --- a/plugins/woocommerce/includes/tracks/events/class-wc-products-tracking.php +++ b/plugins/woocommerce/includes/tracks/events/class-wc-products-tracking.php @@ -26,6 +26,7 @@ class WC_Products_Tracking { add_action( 'edited_product_cat', array( $this, 'track_product_category_updated' ) ); add_action( 'add_meta_boxes_product', array( $this, 'track_product_updated_client_side' ), 10 ); add_action( 'admin_enqueue_scripts', array( $this, 'possibly_add_product_tracking_scripts' ) ); + add_action( 'admin_enqueue_scripts', array( $this, 'possibly_add_product_import_scripts' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'possibly_add_attribute_tracking_scripts' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'possibly_add_tag_tracking_scripts' ) ); } @@ -330,6 +331,11 @@ class WC_Products_Tracking { ) { return 'edit'; } + + if ( 'product_page_product_importer' === $hook ) { + return 'import'; + } + // phpcs:enable return false; @@ -356,6 +362,22 @@ class WC_Products_Tracking { ); } + /** + * Adds the tracking scripts for product setting pages. + * + * @param string $hook Page hook. + */ + public function possibly_add_product_import_scripts( $hook ) { + $product_screen = $this->get_product_screen( $hook ); + + if ( 'import' !== $product_screen ) { + return; + } + + WCAdminAssets::register_script( 'wp-admin-scripts', 'product-import-tracking', false ); + + } + /** * Adds the tracking scripts for product attributes filtering actions. * From 6e20f6696629d6d470c7896c31c2e68443677779 Mon Sep 17 00:00:00 2001 From: Joel Thiessen <444632+joelclimbsthings@users.noreply.github.com> Date: Thu, 15 Dec 2022 14:31:47 -0800 Subject: [PATCH 06/70] Adding the feedback button on activity panel for the classic product page (#35810) --- .../client/activity-panel/activity-panel.js | 62 +++++++++++++++++-- .../customer-effort-score-modal-container.tsx | 9 ++- .../data/reducer.js | 2 +- .../product-mvp-ces-footer.tsx | 2 +- .../client/products/images/feedback-icon.tsx | 3 +- .../client/products/product-more-menu.tsx | 3 +- .../add-35806-classic-product-feedback | 4 ++ 7 files changed, 72 insertions(+), 13 deletions(-) create mode 100644 plugins/woocommerce/changelog/add-35806-classic-product-feedback diff --git a/plugins/woocommerce-admin/client/activity-panel/activity-panel.js b/plugins/woocommerce-admin/client/activity-panel/activity-panel.js index d94961584fd..84be0d405a9 100644 --- a/plugins/woocommerce-admin/client/activity-panel/activity-panel.js +++ b/plugins/woocommerce-admin/client/activity-panel/activity-panel.js @@ -9,7 +9,7 @@ import { useMemo, useEffect, } from '@wordpress/element'; -import { useSelect } from '@wordpress/data'; +import { useSelect, useDispatch } from '@wordpress/data'; import { uniqueId, find } from 'lodash'; import { Icon, help as helpIcon, external } from '@wordpress/icons'; import { H, Section } from '@woocommerce/components'; @@ -43,9 +43,12 @@ import { import { getUnapprovedReviews } from '../homescreen/activity-panel/reviews/utils'; import { ABBREVIATED_NOTIFICATION_SLOT_NAME } from './panels/inbox/abbreviated-notifications-panel'; import { getAdminSetting } from '~/utils/admin-settings'; +import { getUrlParams } from '~/utils'; import { useActiveSetupTasklist } from '~/tasks'; import { LayoutContext } from '~/layout'; import { getSegmentsFromPath } from '~/utils/url-helpers'; +import { FeedbackIcon } from '~/products/images/feedback-icon'; +import { STORE_KEY as CES_STORE_KEY } from '~/customer-effort-score-tracks/data/constants'; const HelpPanel = lazy( () => import( /* webpackChunkName: "activity-panels-help" */ './panels/help' ) @@ -202,6 +205,9 @@ export const ActivityPanel = ( { isEmbedded, query } ) => { ), }; } ); + + const { showCesModal } = useDispatch( CES_STORE_KEY ); + const { currentUserCan } = useUser(); const togglePanel = ( { name: tabName }, isTabOpen ) => { @@ -237,7 +243,7 @@ export const ActivityPanel = ( { isEmbedded, query } ) => { return query.page === 'wc-admin' && ! query.path; }; - const isProductPage = () => { + const isProductScreen = () => { const [ firstPathSegment ] = getSegmentsFromPath( query.path ); return ( firstPathSegment === 'add-product' || firstPathSegment === 'product' @@ -256,6 +262,8 @@ export const ActivityPanel = ( { isEmbedded, query } ) => { // @todo Pull in dynamic unread status/count const getTabs = () => { + const urlParams = getUrlParams( window.location.search ); + const activity = { name: 'activity', title: __( 'Activity', 'woocommerce' ), @@ -264,7 +272,52 @@ export const ActivityPanel = ( { isEmbedded, query } ) => { visible: ( isEmbedded || ! isHomescreen() ) && ! isPerformingSetupTask() && - ! isProductPage(), + ! isProductScreen(), + }; + + const feedback = { + name: 'feedback', + title: __( 'Feedback', 'woocommerce' ), + icon: , + onClick: () => { + setCurrentTab( 'feedback' ); + setIsPanelOpen( true ); + showCesModal( + { + action: 'product_feedback', + title: __( + "How's your experience with the product editor?", + 'woocommerce' + ), + firstQuestion: __( + 'The product editing screen is easy to use', + 'woocommerce' + ), + secondQuestion: __( + "The product editing screen's functionality meets my needs", + 'woocommerce' + ), + }, + { + onRecordScore: () => { + setCurrentTab( '' ); + setIsPanelOpen( false ); + }, + onCloseModal: () => { + setCurrentTab( '' ); + setIsPanelOpen( false ); + }, + }, + { + type: 'snackbar', + icon: 🌟, + } + ); + }, + visible: + isEmbedded && + urlParams?.post_type === 'product' && + ! urlParams?.taxonomy, }; const setup = { @@ -284,7 +337,7 @@ export const ActivityPanel = ( { isEmbedded, query } ) => { ! setupTaskListComplete && ! setupTaskListHidden && ! isHomescreen() && - ! isProductPage(), + ! isProductScreen(), }; const help = { @@ -337,6 +390,7 @@ export const ActivityPanel = ( { isEmbedded, query } ) => { return [ activity, + feedback, setup, previewSite, previewStore, diff --git a/plugins/woocommerce-admin/client/customer-effort-score-tracks/customer-effort-score-modal-container.tsx b/plugins/woocommerce-admin/client/customer-effort-score-tracks/customer-effort-score-modal-container.tsx index 038f166ac07..01216f84dc1 100644 --- a/plugins/woocommerce-admin/client/customer-effort-score-tracks/customer-effort-score-modal-container.tsx +++ b/plugins/woocommerce-admin/client/customer-effort-score-tracks/customer-effort-score-modal-container.tsx @@ -74,15 +74,18 @@ export const CustomerEffortScoreModalContainer: React.FC = () => { return ( { recordScore( ...args ); hideCesModal(); + visibleCESModalData.props?.onRecordScore?.(); + } } + onCloseModal={ () => { + visibleCESModalData.props?.onCloseModal?.(); + hideCesModal(); } } - onCloseModal={ () => hideCesModal() } /> ); }; diff --git a/plugins/woocommerce-admin/client/customer-effort-score-tracks/data/reducer.js b/plugins/woocommerce-admin/client/customer-effort-score-tracks/data/reducer.js index 9cf2d75e330..63d9c3d1d24 100644 --- a/plugins/woocommerce-admin/client/customer-effort-score-tracks/data/reducer.js +++ b/plugins/woocommerce-admin/client/customer-effort-score-tracks/data/reducer.js @@ -25,7 +25,7 @@ const reducer = ( state = DEFAULT_STATE, action ) => { case TYPES.SHOW_CES_MODAL: const cesModalData = { action: action.surveyProps.action, - label: action.surveyProps.label, + title: action.surveyProps.title, onSubmitLabel: action.onSubmitLabel, firstQuestion: action.surveyProps.firstQuestion, secondQuestion: action.surveyProps.secondQuestion, diff --git a/plugins/woocommerce-admin/client/customer-effort-score-tracks/product-mvp-ces-footer.tsx b/plugins/woocommerce-admin/client/customer-effort-score-tracks/product-mvp-ces-footer.tsx index 3c1ea3809e3..d4cb4c80e05 100644 --- a/plugins/woocommerce-admin/client/customer-effort-score-tracks/product-mvp-ces-footer.tsx +++ b/plugins/woocommerce-admin/client/customer-effort-score-tracks/product-mvp-ces-footer.tsx @@ -67,7 +67,7 @@ export const ProductMVPCESFooter: React.FC = () => { showCesModal( { action: cesAction, - label: __( + title: __( "How's your experience with the product editor?", 'woocommerce' ), diff --git a/plugins/woocommerce-admin/client/products/images/feedback-icon.tsx b/plugins/woocommerce-admin/client/products/images/feedback-icon.tsx index d444327c551..40ad443c5ae 100644 --- a/plugins/woocommerce-admin/client/products/images/feedback-icon.tsx +++ b/plugins/woocommerce-admin/client/products/images/feedback-icon.tsx @@ -4,14 +4,13 @@ export const FeedbackIcon = () => { width="16" height="17" viewBox="0 0 16 17" - fill="none" xmlns="http://www.w3.org/2000/svg" > ); diff --git a/plugins/woocommerce-admin/client/products/product-more-menu.tsx b/plugins/woocommerce-admin/client/products/product-more-menu.tsx index a2b2c11fa9a..69ea7a6a8ab 100644 --- a/plugins/woocommerce-admin/client/products/product-more-menu.tsx +++ b/plugins/woocommerce-admin/client/products/product-more-menu.tsx @@ -38,11 +38,10 @@ export const ProductMoreMenu = () => { <> { - // @todo This should open the CES modal. showCesModal( { action: 'new_product', - label: __( + title: __( "How's your experience with the product editor?", 'woocommerce' ), diff --git a/plugins/woocommerce/changelog/add-35806-classic-product-feedback b/plugins/woocommerce/changelog/add-35806-classic-product-feedback new file mode 100644 index 00000000000..f50465433ee --- /dev/null +++ b/plugins/woocommerce/changelog/add-35806-classic-product-feedback @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Adding feedback button to activity bar on classic product page. From 03d52ff13b231dd73484fa35e968f7936657b661 Mon Sep 17 00:00:00 2001 From: Sam Seay Date: Fri, 16 Dec 2022 11:41:03 +1300 Subject: [PATCH 07/70] Another set of migrations from set-output to GITHUB_OUTPUT (#35843) Co-authored-by: Roy Ho --- .github/workflows/cherry-pick.yml | 617 +++++++++--------- .github/workflows/community-label.yml | 60 +- .github/workflows/post-release.yml | 192 +++--- .../scripts/is-community-contributor.js | 68 +- 4 files changed, 476 insertions(+), 461 deletions(-) diff --git a/.github/workflows/cherry-pick.yml b/.github/workflows/cherry-pick.yml index d9a22a4fb85..3bd236534f0 100644 --- a/.github/workflows/cherry-pick.yml +++ b/.github/workflows/cherry-pick.yml @@ -1,28 +1,28 @@ name: Cherry Pick Tool on: - issues: - types: [milestoned, labeled] - pull_request: - types: [closed] - workflow_dispatch: - inputs: - release_branch: - description: Provide the release branch you want to cherry pick into. Example release/6.9 - default: '' - required: true - pull_requests: - description: The pull request number. - default: '' - required: true - skipSlackPing: - description: "Skip Slack Ping: If true, the Slack ping will be skipped (useful for testing)" - type: boolean - required: false - default: false - slackChannelOverride: - description: "Slack Channel Override: The channel ID to send the Slack ping about the code freeze violation" - required: false - default: '' + issues: + types: [milestoned, labeled] + pull_request: + types: [closed] + workflow_dispatch: + inputs: + release_branch: + description: Provide the release branch you want to cherry pick into. Example release/6.9 + default: '' + required: true + pull_requests: + description: The pull request number. + default: '' + required: true + skipSlackPing: + description: 'Skip Slack Ping: If true, the Slack ping will be skipped (useful for testing)' + type: boolean + required: false + default: false + slackChannelOverride: + description: 'Slack Channel Override: The channel ID to send the Slack ping about the code freeze violation' + required: false + default: '' env: GIT_COMMITTER_NAME: 'WooCommerce Bot' @@ -31,320 +31,321 @@ env: GIT_AUTHOR_EMAIL: 'no-reply@woocommerce.com' jobs: - verify: - name: Verify - runs-on: ubuntu-20.04 - outputs: - run: ${{ steps.check.outputs.run }} - steps: - - name: check - id: check - uses: actions/github-script@v6 - with: - script: | - let run = false; + verify: + name: Verify + runs-on: ubuntu-20.04 + outputs: + run: ${{ steps.check.outputs.run }} + steps: + - name: check + id: check + uses: actions/github-script@v6 + with: + script: | + let run = false; - const isManualTrigger = context.payload.inputs && context.payload.inputs.release_branch && context.payload.inputs.release_branch != null; - - const isMergedMilestonedIssue = context.payload.issue && context.payload.issue.pull_request != null && context.payload.issue.pull_request.merged_at != null && context.payload.issue.milestone != null; - - const isMergedMilestonedPR = context.payload.pull_request && context.payload.pull_request != null && context.payload.pull_request.merged == true && context.payload.pull_request.milestone != null; + const isManualTrigger = context.payload.inputs && context.payload.inputs.release_branch && context.payload.inputs.release_branch != null; - const isBot = context.payload.pull_request && ( context.payload.pull_request.user.login == 'github-actions[bot]' || context.payload.pull_request.user.type == 'Bot' ); + const isMergedMilestonedIssue = context.payload.issue && context.payload.issue.pull_request != null && context.payload.issue.pull_request.merged_at != null && context.payload.issue.milestone != null; - if ( !isBot && ( isManualTrigger || isMergedMilestonedIssue || isMergedMilestonedPR ) ) { - console.log( "::set-output name=run::true" ); - } else { - console.log( "::set-output name=run::false" ); - } - prep: - name: Prep inputs - runs-on: ubuntu-20.04 - needs: verify - if: needs.verify.outputs.run == 'true' - outputs: - release: ${{ steps.prep-inputs.outputs.release }} - pr: ${{ steps.prep-inputs.outputs.pr }} - version: ${{ steps.prep-inputs.outputs.version }} - steps: - - name: Prep inputs - id: prep-inputs - uses: actions/github-script@v6 - with: - script: | - const event = ${{ toJSON( github.event ) }} + const isMergedMilestonedPR = context.payload.pull_request && context.payload.pull_request != null && context.payload.pull_request.merged == true && context.payload.pull_request.milestone != null; - // Means this workflow was triggered manually. - if ( event.inputs && event.inputs.release_branch ) { - const releaseBranch = '${{ inputs.release_branch }}' - const version = releaseBranch.replace( 'release/', '' ); + const isBot = context.payload.pull_request && ( context.payload.pull_request.user.login == 'github-actions[bot]' || context.payload.pull_request.user.type == 'Bot' ); - console.log( "::set-output name=version::" + version ) - console.log( "::set-output name=release::${{ inputs.release_branch }}" ) - } else if ( event.action === 'milestoned' ) { - const version = '${{ github.event.issue.milestone.title }}' - const release = version.substring( 0, 3 ) + if ( !isBot && ( isManualTrigger || isMergedMilestonedIssue || isMergedMilestonedPR ) ) { + core.setOutput( 'run', 'true' ); + } else { + core.setOutput( 'run', 'false' ); + } + prep: + name: Prep inputs + runs-on: ubuntu-20.04 + needs: verify + if: needs.verify.outputs.run == 'true' + outputs: + release: ${{ steps.prep-inputs.outputs.release }} + pr: ${{ steps.prep-inputs.outputs.pr }} + version: ${{ steps.prep-inputs.outputs.version }} + steps: + - name: Prep inputs + id: prep-inputs + uses: actions/github-script@v6 + with: + script: | + const event = ${{ toJSON( github.event ) }} - console.log( "::set-output name=version::" + version ) - console.log( "::set-output name=release::release/" + release ) - } else { - const version = '${{ github.event.pull_request.milestone.title }}' - const release = version.substring( 0, 3 ) + // Means this workflow was triggered manually. + if ( event.inputs && event.inputs.release_branch ) { + const releaseBranch = '${{ inputs.release_branch }}' + const version = releaseBranch.replace( 'release/', '' ) - console.log( "::set-output name=version::" + version ) - console.log( "::set-output name=release::release/" + release ) - } + core.setOutput( 'version', version ) + core.setOutput( 'release', releaseBranch ) + } else if ( event.action === 'milestoned' ) { + const version = '${{ github.event.issue.milestone.title }}' + const release = version.substring( 0, 3 ) - // Means this workflow was triggered manually. - if ( event.inputs && event.inputs.pull_requests ) { - console.log( "::set-output name=pr::${{ inputs.pull_requests }}" ) - } else if ( event.action === 'milestoned' ) { - console.log( "::set-output name=pr::${{ github.event.issue.number }}" ) - } else { - console.log( "::set-output name=pr::${{ github.event.pull_request.number }}" ) - } - check-release-branch-exists: - name: Check for existence of release branch - runs-on: ubuntu-20.04 - needs: prep - steps: - - name: Check for release branch - id: release-breanch-check - uses: actions/github-script@v6 - with: - script: | - // This will throw an error for non-200 responses, which prevents subsequent jobs from completing, as desired. - await github.request( 'GET /repos/{owner}/{repo}/branches/{branch}', { - owner: context.repo.owner, - repo: context.repo.repo, - branch: '${{ needs.prep.outputs.release }}', - } ); - cherry-pick-run: - name: Run cherry pick tool - runs-on: ubuntu-20.04 - needs: [ prep, check-release-branch-exists ] - if: success() - steps: - - name: Checkout release branch - uses: actions/checkout@v3 - with: - fetch-depth: 0 + core.setOutput( 'version', version ) + core.setOutput( 'release', `release/${release}` ) + } else { + const version = '${{ github.event.pull_request.milestone.title }}' + const release = version.substring( 0, 3 ) - - name: Git fetch the release branch - run: git fetch origin ${{ needs.prep.outputs.release }} - - - name: Checkout release branch - run: git checkout ${{ needs.prep.outputs.release }} - - - name: Create a cherry pick branch based on release branch - run: git checkout -b cherry-pick-${{ needs.prep.outputs.version }}/${{ needs.prep.outputs.pr }} - - - name: Get commit sha from PR - id: commit-sha - uses: actions/github-script@v6 - with: - script: | - const pr = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: '${{ needs.prep.outputs.pr }}' - }) - - console.log( `::set-output name=sha::${ pr.data.merge_commit_sha }` ) - - - name: Cherry pick - run: | - git cherry-pick ${{ steps.commit-sha.outputs.sha }} - - - name: Generate changelog - id: changelog - uses: actions/github-script@v6 - with: - script: | - const fs = require( 'node:fs' ); - - const changelogsToBeDeleted = [] - let changelogTxt = ''; - - const commit = await github.rest.repos.getCommit({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: '${{ steps.commit-sha.outputs.sha }}' - }) - - for ( const file of commit.data.files ) { - if ( file.filename.match( 'plugins/woocommerce/changelog/' ) ) { - if ( changelogsToBeDeleted.indexOf( file.filename ) === -1 ) { - changelogsToBeDeleted.push( file.filename ); - } - - let changelogEntry = ''; - let changelogEntryType = ''; - - fs.readFile( './' + file.filename, 'utf-8', function( err, data ) { - if ( err ) { - console.error( err ); - } - - const changelogEntryArr = data.split( "\n" ); - changelogEntryType = data.match( /Type: (.+)/i ); - changelogEntryType = changelogEntryType[ 1 ].charAt( 0 ).toUpperCase() + changelogEntryType[ 1 ].slice( 1 ); - - changelogEntry = changelogEntryArr.filter( el => { - return el !== null && typeof el !== 'undefined' && el !== ''; - } ); - changelogEntry = changelogEntry[ changelogEntry.length - 1 ]; - - // Check if changelogEntry is what we want. - if ( changelogEntry.length < 1 ) { - changelogEntry = false; - } - - if ( changelogEntry.match( /significance:/i ) ) { - changelogEntry = false; - } - - if ( changelogEntry.match( /type:/i ) ) { - changelogEntry = false; - } - - if ( changelogEntry.match( /comment:/i ) ) { - changelogEntry = false; - } - - if ( ! changelogEntry ) { - return; - } - - fs.readFile( './plugins/woocommerce/readme.txt', 'utf-8', function( err, data ) { - if ( err ) { - console.error( err ); - } - - changelogTxt = data.split( "\n" ); - let isInRange = false; - let newChangelogTxt = []; - - for ( const line of changelogTxt ) { - if ( isInRange === false && line === '== Changelog ==' ) { - isInRange = true; + core.setOutput( 'version', version ) + core.setOutput( 'release', `release/${release}` ) } - if ( isInRange === true && line.match( /\*\*WooCommerce Blocks/ ) ) { - isInRange = false; + // Means this workflow was triggered manually. + if ( event.inputs && event.inputs.pull_requests ) { + core.setOutput( 'pr', '${{ inputs.pull_requests }}' ) + } else if ( event.action === 'milestoned' ) { + core.setOutput( 'pr', '${{ github.event.issue.number }}' ) + } else { + core.setOutput( 'pr', '${{ github.event.pull_request.number }}' ) + } + check-release-branch-exists: + name: Check for existence of release branch + runs-on: ubuntu-20.04 + needs: prep + steps: + - name: Check for release branch + id: release-breanch-check + uses: actions/github-script@v6 + with: + script: | + // This will throw an error for non-200 responses, which prevents subsequent jobs from completing, as desired. + await github.request( 'GET /repos/{owner}/{repo}/branches/{branch}', { + owner: context.repo.owner, + repo: context.repo.repo, + branch: '${{ needs.prep.outputs.release }}', + } ); + cherry-pick-run: + name: Run cherry pick tool + runs-on: ubuntu-20.04 + needs: [prep, check-release-branch-exists] + if: success() + steps: + - name: Checkout release branch + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Git fetch the release branch + run: git fetch origin ${{ needs.prep.outputs.release }} + + - name: Checkout release branch + run: git checkout ${{ needs.prep.outputs.release }} + + - name: Create a cherry pick branch based on release branch + run: git checkout -b cherry-pick-${{ needs.prep.outputs.version }}/${{ needs.prep.outputs.pr }} + + - name: Get commit sha from PR + id: commit-sha + uses: actions/github-script@v6 + with: + script: | + const pr = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: '${{ needs.prep.outputs.pr }}' + }) + + core.setOutput( 'sha', pr.data.merge_commit_sha ) + + - name: Cherry pick + run: | + git cherry-pick ${{ steps.commit-sha.outputs.sha }} + + - name: Generate changelog + id: changelog + uses: actions/github-script@v6 + with: + script: | + const fs = require( 'node:fs' ); + + const changelogsToBeDeleted = [] + let changelogTxt = ''; + + const commit = await github.rest.repos.getCommit({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: '${{ steps.commit-sha.outputs.sha }}' + }) + + for ( const file of commit.data.files ) { + if ( file.filename.match( 'plugins/woocommerce/changelog/' ) ) { + if ( changelogsToBeDeleted.indexOf( file.filename ) === -1 ) { + changelogsToBeDeleted.push( file.filename ); + } + + let changelogEntry = ''; + let changelogEntryType = ''; + + fs.readFile( './' + file.filename, 'utf-8', function( err, data ) { + if ( err ) { + console.error( err ); + } + + const changelogEntryArr = data.split( "\n" ); + changelogEntryType = data.match( /Type: (.+)/i ); + changelogEntryType = changelogEntryType[ 1 ].charAt( 0 ).toUpperCase() + changelogEntryType[ 1 ].slice( 1 ); + + changelogEntry = changelogEntryArr.filter( el => { + return el !== null && typeof el !== 'undefined' && el !== ''; + } ); + changelogEntry = changelogEntry[ changelogEntry.length - 1 ]; + + // Check if changelogEntry is what we want. + if ( changelogEntry.length < 1 ) { + changelogEntry = false; + } + + if ( changelogEntry.match( /significance:/i ) ) { + changelogEntry = false; + } + + if ( changelogEntry.match( /type:/i ) ) { + changelogEntry = false; + } + + if ( changelogEntry.match( /comment:/i ) ) { + changelogEntry = false; + } + + if ( ! changelogEntry ) { + return; + } + + fs.readFile( './plugins/woocommerce/readme.txt', 'utf-8', function( err, data ) { + if ( err ) { + console.error( err ); + } + + changelogTxt = data.split( "\n" ); + let isInRange = false; + let newChangelogTxt = []; + + for ( const line of changelogTxt ) { + if ( isInRange === false && line === '== Changelog ==' ) { + isInRange = true; + } + + if ( isInRange === true && line.match( /\*\*WooCommerce Blocks/ ) ) { + isInRange = false; + } + + // Find the first match of the entry "Type". + if ( isInRange && line.match( `\\* ${changelogEntryType} -` ) ) { + newChangelogTxt.push( '* ' + changelogEntryType + ' - ' + changelogEntry + ` [#${{ needs.prep.outputs.pr }}](https://github.com/woocommerce/woocommerce/pull/${{ needs.prep.outputs.pr }})` ); + newChangelogTxt.push( line ); + isInRange = false; + continue; + } + + newChangelogTxt.push( line ); + } + + fs.writeFile( './plugins/woocommerce/readme.txt', newChangelogTxt.join( "\n" ), err => { + if ( err ) { + console.error( `Unable to generate the changelog entry for PR ${{ needs.prep.outputs.pr }}` ); + } + } ); + } ); + } ); + } } - // Find the first match of the entry "Type". - if ( isInRange && line.match( `\\* ${changelogEntryType} -` ) ) { - newChangelogTxt.push( '* ' + changelogEntryType + ' - ' + changelogEntry + ` [#${{ needs.prep.outputs.pr }}](https://github.com/woocommerce/woocommerce/pull/${{ needs.prep.outputs.pr }})` ); - newChangelogTxt.push( line ); - isInRange = false; - continue; - } + core.setOutput( 'changelogsToBeDeleted', changelogsToBeDeleted.join( ' ' ) ) - newChangelogTxt.push( line ); - } + - name: Delete changelog files from cherry pick branch + if: steps.changelog.outputs.changelogsToBeDeleted != '' && steps.changelog.outputs.changelogsToBeDeleted != null + run: git rm ${{ steps.changelog.outputs.changelogsToBeDeleted }} - fs.writeFile( './plugins/woocommerce/readme.txt', newChangelogTxt.join( "\n" ), err => { - if ( err ) { - console.error( `Unable to generate the changelog entry for PR ${{ needs.prep.outputs.pr }}` ); - } - } ); - } ); - } ); - } - } + - name: Commit changes for cherry pick + if: steps.changelog.outputs.changelogsToBeDeleted != '' && steps.changelog.outputs.changelogsToBeDeleted != null + run: git commit --no-verify -am "Prep for cherry pick ${{ needs.prep.outputs.pr }}" - console.log( `::set-output name=changelogsToBeDeleted::${ changelogsToBeDeleted }` ) + - name: Push cherry pick branch up + run: git push origin cherry-pick-${{ needs.prep.outputs.version }}/${{ needs.prep.outputs.pr }} - - name: Delete changelog files from cherry pick branch - if: steps.changelog.outputs.changelogsToBeDeleted != '' && steps.changelog.outputs.changelogsToBeDeleted != null - run: git rm ${{ steps.changelog.outputs.changelogsToBeDeleted }} + - name: Create the PR for cherry pick branch + id: cherry-pick-pr + uses: actions/github-script@v6 + with: + script: | + let cherryPickPRBody = "This PR cherry-picks the following PRs into the release branch:\n"; - - name: Commit changes for cherry pick - if: steps.changelog.outputs.changelogsToBeDeleted != '' && steps.changelog.outputs.changelogsToBeDeleted != null - run: git commit --no-verify -am "Prep for cherry pick ${{ needs.prep.outputs.pr }}" + cherryPickPRBody = `${cherryPickPRBody}` + `* #${{ needs.prep.outputs.pr }}` + "\n"; - - name: Push cherry pick branch up - run: git push origin cherry-pick-${{ needs.prep.outputs.version }}/${{ needs.prep.outputs.pr }} + const pr = await github.rest.pulls.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: "Cherry pick ${{ needs.prep.outputs.pr }} into ${{ needs.prep.outputs.release }}", + head: "cherry-pick-${{ needs.prep.outputs.version }}/${{ needs.prep.outputs.pr }}", + base: "${{ needs.prep.outputs.release }}", + body: cherryPickPRBody + }) - - name: Create the PR for cherry pick branch - id: cherry-pick-pr - uses: actions/github-script@v6 - with: - script: | - let cherryPickPRBody = "This PR cherry-picks the following PRs into the release branch:\n"; + core.setOutput( 'cherry-pick-pr', pr.data.html_url ) - cherryPickPRBody = `${cherryPickPRBody}` + `* #${{ needs.prep.outputs.pr }}` + "\n"; + - name: Checkout trunk branch + if: steps.changelog.outputs.changelogsToBeDeleted != '' && steps.changelog.outputs.changelogsToBeDeleted != null + run: git checkout trunk - const pr = await github.rest.pulls.create({ - owner: context.repo.owner, - repo: context.repo.repo, - title: "Cherry pick ${{ needs.prep.outputs.pr }} into ${{ needs.prep.outputs.release }}", - head: "cherry-pick-${{ needs.prep.outputs.version }}/${{ needs.prep.outputs.pr }}", - base: "${{ needs.prep.outputs.release }}", - body: cherryPickPRBody - }) + - name: Create a branch based on trunk branch + if: steps.changelog.outputs.changelogsToBeDeleted != '' && steps.changelog.outputs.changelogsToBeDeleted != null + run: git checkout -b delete-changelogs/${{ needs.prep.outputs.pr }} - console.log( `::set-output name=cherry-pick-pr::${ pr.data.html_url }` ) - - name: Checkout trunk branch - if: steps.changelog.outputs.changelogsToBeDeleted != '' && steps.changelog.outputs.changelogsToBeDeleted != null - run: git checkout trunk + - name: Delete changelogs from trunk + if: steps.changelog.outputs.changelogsToBeDeleted != '' && steps.changelog.outputs.changelogsToBeDeleted != null + run: git rm ${{ steps.changelog.outputs.changelogsToBeDeleted }} - - name: Create a branch based on trunk branch - if: steps.changelog.outputs.changelogsToBeDeleted != '' && steps.changelog.outputs.changelogsToBeDeleted != null - run: git checkout -b delete-changelogs/${{ needs.prep.outputs.pr }} + - name: Commit changes for deletion + if: steps.changelog.outputs.changelogsToBeDeleted != '' && steps.changelog.outputs.changelogsToBeDeleted != null + run: git commit --no-verify -am "Delete changelog files for ${{ needs.prep.outputs.pr }}" - - name: Delete changelogs from trunk - if: steps.changelog.outputs.changelogsToBeDeleted != '' && steps.changelog.outputs.changelogsToBeDeleted != null - run: git rm ${{ steps.changelog.outputs.changelogsToBeDeleted }} + - name: Push deletion branch up + if: steps.changelog.outputs.changelogsToBeDeleted != '' && steps.changelog.outputs.changelogsToBeDeleted != null + run: git push origin delete-changelogs/${{ needs.prep.outputs.pr }} - - name: Commit changes for deletion - if: steps.changelog.outputs.changelogsToBeDeleted != '' && steps.changelog.outputs.changelogsToBeDeleted != null - run: git commit --no-verify -am "Delete changelog files for ${{ needs.prep.outputs.pr }}" + - name: Create the PR for deletion branch + id: deletion-pr + if: steps.changelog.outputs.changelogsToBeDeleted != '' && steps.changelog.outputs.changelogsToBeDeleted != null + uses: actions/github-script@v6 + with: + script: | + const pr = await github.rest.pulls.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: "Delete changelog files based on PR ${{ needs.prep.outputs.pr }}", + head: "delete-changelogs/${{ needs.prep.outputs.pr }}", + base: "trunk", + body: "Delete changelog files based on PR #${{ needs.prep.outputs.pr }}" + }) - - name: Push deletion branch up - if: steps.changelog.outputs.changelogsToBeDeleted != '' && steps.changelog.outputs.changelogsToBeDeleted != null - run: git push origin delete-changelogs/${{ needs.prep.outputs.pr }} + core.setOutput( 'deletion-pr', pr.data.html_url ) - - name: Create the PR for deletion branch - id: deletion-pr - if: steps.changelog.outputs.changelogsToBeDeleted != '' && steps.changelog.outputs.changelogsToBeDeleted != null - uses: actions/github-script@v6 - with: - script: | - const pr = await github.rest.pulls.create({ - owner: context.repo.owner, - repo: context.repo.repo, - title: "Delete changelog files based on PR ${{ needs.prep.outputs.pr }}", - head: "delete-changelogs/${{ needs.prep.outputs.pr }}", - base: "trunk", - body: "Delete changelog files based on PR #${{ needs.prep.outputs.pr }}" - }) + - name: Notify Slack on failure + if: ${{ failure() && inputs.skipSlackPing != true }} + uses: archive/github-actions-slack@v2.0.0 + with: + slack-bot-user-oauth-access-token: ${{ secrets.CODE_FREEZE_BOT_TOKEN }} + slack-channel: ${{ inputs.slackChannelOverride || secrets.WOO_RELEASE_SLACK_CHANNEL }} + slack-text: | + :warning-8c: Code freeze violation. PR(s) created that breaks the Code Freeze for '${{ needs.prep.outputs.release }}' :ice_cube: - console.log( `::set-output name=deletion-pr::${ pr.data.html_url }` ) + An attempt to cherry pick PR(s) into outgoing release '${{ needs.prep.outputs.release }}' has failed. This could be due to a merge conflict or something else that requires manual attention. Please check: https://github.com/woocommerce/woocommerce/pull/${{ needs.prep.outputs.pr }} - - name: Notify Slack on failure - if: ${{ failure() && inputs.skipSlackPing != true }} - uses: archive/github-actions-slack@v2.0.0 - with: - slack-bot-user-oauth-access-token: ${{ secrets.CODE_FREEZE_BOT_TOKEN }} - slack-channel: ${{ inputs.slackChannelOverride || secrets.WOO_RELEASE_SLACK_CHANNEL }} - slack-text: | - :warning-8c: Code freeze violation. PR(s) created that breaks the Code Freeze for '${{ needs.prep.outputs.release }}' :ice_cube: + - name: Notify Slack on success + if: ${{ success() && inputs.skipSlackPing != true }} + uses: archive/github-actions-slack@v2.0.0 + with: + slack-bot-user-oauth-access-token: ${{ secrets.CODE_FREEZE_BOT_TOKEN }} + slack-channel: ${{ inputs.slackChannelOverride || secrets.WOO_RELEASE_SLACK_CHANNEL }} + slack-text: | + :warning-8c: Code freeze violation. PR(s) created that breaks the Code Freeze for '${{ needs.prep.outputs.release }}' :ice_cube: - An attempt to cherry pick PR(s) into outgoing release '${{ needs.prep.outputs.release }}' has failed. This could be due to a merge conflict or something else that requires manual attention. Please check: https://github.com/woocommerce/woocommerce/pull/${{ needs.prep.outputs.pr }} + Release lead please review: - - name: Notify Slack on success - if: ${{ success() && inputs.skipSlackPing != true }} - uses: archive/github-actions-slack@v2.0.0 - with: - slack-bot-user-oauth-access-token: ${{ secrets.CODE_FREEZE_BOT_TOKEN }} - slack-channel: ${{ inputs.slackChannelOverride || secrets.WOO_RELEASE_SLACK_CHANNEL }} - slack-text: | - :warning-8c: Code freeze violation. PR(s) created that breaks the Code Freeze for '${{ needs.prep.outputs.release }}' :ice_cube: - - Release lead please review: - - ${{ steps.cherry-pick-pr.outputs.cherry-pick-pr }} - ${{ steps.deletion-pr.outputs.deletion-pr }} + ${{ steps.cherry-pick-pr.outputs.cherry-pick-pr }} + ${{ steps.deletion-pr.outputs.deletion-pr }} diff --git a/.github/workflows/community-label.yml b/.github/workflows/community-label.yml index 221753b62e3..6856aec116f 100644 --- a/.github/workflows/community-label.yml +++ b/.github/workflows/community-label.yml @@ -1,38 +1,40 @@ name: Add Community Label on: - pull_request_target: - types: [opened] - issues: - types: [opened] - + pull_request_target: + types: [opened] + issues: + types: [opened] -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: - verify: - name: Verify - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v3 + verify: + name: Verify + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 - - name: Setup Node.js - uses: actions/setup-node@2fddd8803e2f5c9604345a0b591c3020ee971a93 + - name: Setup Node.js + uses: actions/setup-node@2fddd8803e2f5c9604345a0b591c3020ee971a93 - - name: Install Octokit - run: npm --prefix .github/workflows/scripts install @octokit/action + - name: Install Octokit + run: npm --prefix .github/workflows/scripts install @octokit/action - - name: Check if user is a community contributor - id: check - run: node .github/workflows/scripts/is-community-contributor.js - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: "If community PR, assign a reviewer" - if: github.event.pull_request && steps.check.outputs.is-community == 'yes' - uses: shufo/auto-assign-reviewer-by-files@f5f3db9ef06bd72ab6978996988c6462cbdaabf6 - with: - config: ".github/project-community-pr-assigner.yml" - token: ${{ secrets.PR_ASSIGN_TOKEN }} + - name: Install Actions Core + run: npm --prefix .github/workflows/scripts install @actions/core + + - name: Check if user is a community contributor + id: check + run: node .github/workflows/scripts/is-community-contributor.js + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: 'If community PR, assign a reviewer' + if: github.event.pull_request && steps.check.outputs.is-community == 'yes' + uses: shufo/auto-assign-reviewer-by-files@f5f3db9ef06bd72ab6978996988c6462cbdaabf6 + with: + config: '.github/project-community-pr-assigner.yml' + token: ${{ secrets.PR_ASSIGN_TOKEN }} diff --git a/.github/workflows/post-release.yml b/.github/workflows/post-release.yml index c9454db35ef..7ed2c418986 100644 --- a/.github/workflows/post-release.yml +++ b/.github/workflows/post-release.yml @@ -1,7 +1,7 @@ name: Run post release processes -on: - release: - types: [released] +on: + release: + types: [released] env: GIT_COMMITTER_NAME: 'WooCommerce Bot' @@ -10,119 +10,119 @@ env: GIT_AUTHOR_EMAIL: 'no-reply@woocommerce.com' jobs: - changelog-version-update: - name: Update changelog and version - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v3 + changelog-version-update: + name: Update changelog and version + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 - - name: Git fetch trunk branch - run: git fetch origin trunk + - name: Git fetch trunk branch + run: git fetch origin trunk - - name: Copy readme.txt to vm root - run: cp ./plugins/woocommerce/readme.txt ../../readme.txt + - name: Copy readme.txt to vm root + run: cp ./plugins/woocommerce/readme.txt ../../readme.txt - - name: Switch to trunk branch - run: git checkout trunk + - name: Switch to trunk branch + run: git checkout trunk - - name: Create a new branch based on trunk - run: git checkout -b prep/post-release-tasks-${{ github.event.release.tag_name }} + - name: Create a new branch based on trunk + run: git checkout -b prep/post-release-tasks-${{ github.event.release.tag_name }} - - name: Check if we need to continue processing - uses: actions/github-script@v6 - id: check - with: - script: | - const fs = require( 'node:fs' ); - const version = ${{ toJSON( github.event.release.tag_name ) }} + - name: Check if we need to continue processing + uses: actions/github-script@v6 + id: check + with: + script: | + const fs = require( 'node:fs' ); + const version = ${{ toJSON( github.event.release.tag_name ) }} - fs.readFile( './plugins/woocommerce/readme.txt', 'utf-8', function( err, data ) { - if ( err ) { - console.error( err ); - } + fs.readFile( './plugins/woocommerce/readme.txt', 'utf-8', function( err, data ) { + if ( err ) { + console.error( err ); + } - const regex = /Stable\stag:\s(\d+\.\d+\.\d+)/; + const regex = /Stable\stag:\s(\d+\.\d+\.\d+)/; - const stableVersion = data.match( regex )[1]; + const stableVersion = data.match( regex )[1]; - // If the release version is less than stable version we can bail. - if ( version.localeCompare( stableVersion, undefined, { numeric: true, sensitivity: 'base' } ) == -1 ) { - console.log( 'Release version is less than stable version. No automated action taken. A manual process is required.' ); - console.log( `::set-output name=continue::false` ) - return; - } else { - console.log( `::set-output name=continue::true` ) - } - } ) + // If the release version is less than stable version we can bail. + if ( version.localeCompare( stableVersion, undefined, { numeric: true, sensitivity: 'base' } ) == -1 ) { + console.log( 'Release version is less than stable version. No automated action taken. A manual process is required.' ); + core.setOutput( 'continue', 'false' ) + return; + } else { + core.setOutput( 'continue', 'true' ) + } + } ) - - name: Update changelog.txt entries - uses: actions/github-script@v6 - id: update-entries - if: steps.check.outputs.continue == 'true' - with: - script: | - const fs = require( 'node:fs' ); - const version = ${{ toJSON( github.event.release.tag_name ) }} + - name: Update changelog.txt entries + uses: actions/github-script@v6 + id: update-entries + if: steps.check.outputs.continue == 'true' + with: + script: | + const fs = require( 'node:fs' ); + const version = ${{ toJSON( github.event.release.tag_name ) }} - // Read the saved readme.txt file from earlier. - fs.readFile( '../../readme.txt', 'utf-8', function( err, readme ) { - if ( err ) { - console.log( `::set-output name=continue::false` ) - console.error( err ); - } + // Read the saved readme.txt file from earlier. + fs.readFile( '../../readme.txt', 'utf-8', function( err, readme ) { + if ( err ) { + core.setOutput( 'continue', 'false' ); + console.error( err ); + } - const regex = /(== Changelog ==[\s\S]+)\s{2}\[See changelog for all versions\]\(https:\/\/raw\.githubusercontent\.com\/woocommerce\/woocommerce\/trunk\/changelog\.txt\)\./; + const regex = /(== Changelog ==[\s\S]+)\s{2}\[See changelog for all versions\]\(https:\/\/raw\.githubusercontent\.com\/woocommerce\/woocommerce\/trunk\/changelog\.txt\)\./; - const entries = readme.match( regex )[1]; + const entries = readme.match( regex )[1]; - fs.readFile( './changelog.txt', 'utf-8', function( err, changelog ) { - if ( err ) { - console.log( `::set-output name=continue::false` ) - console.error( err ); - } + fs.readFile( './changelog.txt', 'utf-8', function( err, changelog ) { + if ( err ) { + core.setOutput( 'continue', 'false' ); + console.error( err ); + } - const regex = /== Changelog ==/; + const regex = /== Changelog ==/; - const updatedChangelog = changelog.replace( regex, entries ); + const updatedChangelog = changelog.replace( regex, entries ); - fs.writeFile( './changelog.txt', updatedChangelog, err => { - if ( err ) { - console.log( `::set-output name=continue::false` ) - console.error( 'Unable to update changelog entries in changelog.txt' ); - } + fs.writeFile( './changelog.txt', updatedChangelog, err => { + if ( err ) { + core.setOutput( 'continue', 'false' ); + console.error( 'Unable to update changelog entries in changelog.txt' ); + } - console.log( `::set-output name=continue::true` ) - } ) - } ) - } ) + core.setOutput( 'continue', 'true' ); + } ) + } ) + } ) - - name: Commit changes - if: steps.update-entries.outputs.continue == 'true' - run: git commit -am "Prep trunk post release ${{ github.event.release.tag_name }}" + - name: Commit changes + if: steps.update-entries.outputs.continue == 'true' + run: git commit -am "Prep trunk post release ${{ github.event.release.tag_name }}" - - name: Push branch up - if: steps.update-entries.outputs.continue == 'true' - run: git push origin prep/post-release-tasks-${{ github.event.release.tag_name }} + - name: Push branch up + if: steps.update-entries.outputs.continue == 'true' + run: git push origin prep/post-release-tasks-${{ github.event.release.tag_name }} - - name: Create the PR - if: steps.update-entries.outputs.continue == 'true' - uses: actions/github-script@v6 - with: - script: | - const body = "This PR updates the changelog.txt entries based on the latest release: ${{ github.event.release.tag_name }}" + - name: Create the PR + if: steps.update-entries.outputs.continue == 'true' + uses: actions/github-script@v6 + with: + script: | + const body = "This PR updates the changelog.txt entries based on the latest release: ${{ github.event.release.tag_name }}" - const pr = await github.rest.pulls.create({ - owner: context.repo.owner, - repo: context.repo.repo, - title: "Update changelog.txt from release ${{ github.event.release.tag_name }}", - head: "prep/post-release-tasks-${{ github.event.release.tag_name }}", - base: "trunk", - body: body - }) + const pr = await github.rest.pulls.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: "Update changelog.txt from release ${{ github.event.release.tag_name }}", + head: "prep/post-release-tasks-${{ github.event.release.tag_name }}", + base: "trunk", + body: body + }) - const prCreated = await github.rest.pulls.requestReviewers({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: pr.data.number, - reviewers: ["${{ github.event.release.author.login }}"] - }) + const prCreated = await github.rest.pulls.requestReviewers({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pr.data.number, + reviewers: ["${{ github.event.release.author.login }}"] + }) diff --git a/.github/workflows/scripts/is-community-contributor.js b/.github/workflows/scripts/is-community-contributor.js index 7708995fc06..9a4311d7878 100644 --- a/.github/workflows/scripts/is-community-contributor.js +++ b/.github/workflows/scripts/is-community-contributor.js @@ -1,50 +1,62 @@ -// Note you'll need to install this dependency as part of your workflow. -const { Octokit } = require('@octokit/action'); +// Note you'll need to install these dependencies as part of your workflow. +const { Octokit } = require( '@octokit/action' ); +const core = require( '@actions/core' ); // Note that this script assumes you set GITHUB_TOKEN in env, if you don't // this won't work. const octokit = new Octokit(); -const getIssueAuthor = (payload) => { - return payload?.issue?.user?.login || payload?.pull_request?.user?.login || null; -} +const getIssueAuthor = ( payload ) => { + return ( + payload?.issue?.user?.login || + payload?.pull_request?.user?.login || + null + ); +}; -const isCommunityContributor = async (owner, repo, username) => { - if (username) { - const {data: {permission}} = await octokit.rest.repos.getCollaboratorPermissionLevel({ +const isCommunityContributor = async ( owner, repo, username ) => { + if ( username ) { + const { + data: { permission }, + } = await octokit.rest.repos.getCollaboratorPermissionLevel( { owner, repo, username, - }); - + } ); + return permission === 'read' || permission === 'none'; } - return false; -} + return false; +}; -const addLabel = async(label, owner, repo, issueNumber) => { - await octokit.rest.issues.addLabels({ +const addLabel = async ( label, owner, repo, issueNumber ) => { + await octokit.rest.issues.addLabels( { owner, repo, issue_number: issueNumber, - labels: [label], - }); -} + labels: [ label ], + } ); +}; const applyLabelToCommunityContributor = async () => { - const eventPayload = require(process.env.GITHUB_EVENT_PATH); - const username = getIssueAuthor(eventPayload); - const [owner, repo] = process.env.GITHUB_REPOSITORY.split("/"); + const eventPayload = require( process.env.GITHUB_EVENT_PATH ); + const username = getIssueAuthor( eventPayload ); + const [ owner, repo ] = process.env.GITHUB_REPOSITORY.split( '/' ); const { number } = eventPayload?.issue || eventPayload?.pull_request; - - const isCommunityUser = await isCommunityContributor(owner, repo, username); - console.log( '::set-output name=is-community::%s', isCommunityUser ? 'yes' : 'no' ); - - if (isCommunityUser) { - console.log('Adding community contributor label'); - await addLabel('type: community contribution', owner, repo, number); + + const isCommunityUser = await isCommunityContributor( + owner, + repo, + username + ); + + core.setOutput( 'is-community', isCommunityUser ? 'yes' : 'no' ); + + if ( isCommunityUser ) { + console.log( 'Adding community contributor label' ); + await addLabel( 'type: community contribution', owner, repo, number ); } -} +}; applyLabelToCommunityContributor(); From 54f22aa437ccd8ffb72d758514084753ce2fb503 Mon Sep 17 00:00:00 2001 From: Joel Thiessen <444632+joelclimbsthings@users.noreply.github.com> Date: Thu, 15 Dec 2022 16:09:21 -0800 Subject: [PATCH 08/70] Adding delayed spotlight to feedback button on current product page (#35865) --- .../changelog/add-35301-delayed-ces-prompt | 4 + .../tour-kit/components/step-navigation.tsx | 6 +- packages/js/components/src/tour-kit/types.ts | 1 + .../client/activity-panel/activity-panel.js | 21 +++- .../add-product-feedback-tour.tsx | 114 ++++++++++++++++++ .../changelog/add-35301-delayed-ces-prompt | 4 + 6 files changed, 143 insertions(+), 7 deletions(-) create mode 100644 packages/js/components/changelog/add-35301-delayed-ces-prompt create mode 100644 plugins/woocommerce-admin/client/guided-tours/add-product-feedback-tour.tsx create mode 100644 plugins/woocommerce/changelog/add-35301-delayed-ces-prompt diff --git a/packages/js/components/changelog/add-35301-delayed-ces-prompt b/packages/js/components/changelog/add-35301-delayed-ces-prompt new file mode 100644 index 00000000000..a0589d9d4ab --- /dev/null +++ b/packages/js/components/changelog/add-35301-delayed-ces-prompt @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Adding isHidden option for primary button in TourKit component. diff --git a/packages/js/components/src/tour-kit/components/step-navigation.tsx b/packages/js/components/src/tour-kit/components/step-navigation.tsx index a4e78bb7fc2..6c5b2b6ba50 100644 --- a/packages/js/components/src/tour-kit/components/step-navigation.tsx +++ b/packages/js/components/src/tour-kit/components/step-navigation.tsx @@ -25,7 +25,7 @@ const StepNavigation: React.FunctionComponent< Props > = ( { const isFirstStep = currentStepIndex === 0; const isLastStep = currentStepIndex === steps.length - 1; - const { primaryButton = { text: '', isDisabled: false } } = + const { primaryButton = { text: '', isDisabled: false, isHidden: false } } = steps[ currentStepIndex ].meta; const NextButton = ( @@ -80,6 +80,10 @@ const StepNavigation: React.FunctionComponent< Props > = ( { ); }; + if ( primaryButton.isHidden ) { + return null; + } + return (
diff --git a/packages/js/components/src/tour-kit/types.ts b/packages/js/components/src/tour-kit/types.ts index 7fac3ba9f57..543ce6f7d3e 100644 --- a/packages/js/components/src/tour-kit/types.ts +++ b/packages/js/components/src/tour-kit/types.ts @@ -22,6 +22,7 @@ export interface WooStep extends Step { text?: string; /** Disable the button or not. Default to False */ isDisabled?: boolean; + isHidden?: boolean; }; }; /** Auto apply the focus state for the element. Default to null */ diff --git a/plugins/woocommerce-admin/client/activity-panel/activity-panel.js b/plugins/woocommerce-admin/client/activity-panel/activity-panel.js index 84be0d405a9..308cf600904 100644 --- a/plugins/woocommerce-admin/client/activity-panel/activity-panel.js +++ b/plugins/woocommerce-admin/client/activity-panel/activity-panel.js @@ -49,6 +49,7 @@ import { LayoutContext } from '~/layout'; import { getSegmentsFromPath } from '~/utils/url-helpers'; import { FeedbackIcon } from '~/products/images/feedback-icon'; import { STORE_KEY as CES_STORE_KEY } from '~/customer-effort-score-tracks/data/constants'; +import { ProductFeedbackTour } from '~/guided-tours/add-product-feedback-tour'; const HelpPanel = lazy( () => import( /* webpackChunkName: "activity-panels-help" */ './panels/help' ) @@ -250,6 +251,16 @@ export const ActivityPanel = ( { isEmbedded, query } ) => { ); }; + const isAddProductPage = () => { + const urlParams = getUrlParams( window.location.search ); + + return ( + isEmbedded && + /post-new\.php$/.test( window.location.pathname ) && + urlParams?.post_type === 'product' + ); + }; + const isPerformingSetupTask = () => { return ( query.task && @@ -262,8 +273,6 @@ export const ActivityPanel = ( { isEmbedded, query } ) => { // @todo Pull in dynamic unread status/count const getTabs = () => { - const urlParams = getUrlParams( window.location.search ); - const activity = { name: 'activity', title: __( 'Activity', 'woocommerce' ), @@ -314,10 +323,7 @@ export const ActivityPanel = ( { isEmbedded, query } ) => { } ); }, - visible: - isEmbedded && - urlParams?.post_type === 'product' && - ! urlParams?.taxonomy, + visible: isAddProductPage(), }; const setup = { @@ -485,6 +491,9 @@ export const ActivityPanel = ( { isEmbedded, query } ) => { clearPanel={ () => clearPanel() } /> + { isAddProductPage() && ( + + ) } { showHelpHighlightTooltip ? ( { + const { hasShownTour } = useSelect( ( select ) => { + const { getOption } = select( OPTIONS_STORE_NAME ); + + return { + hasShownTour: getOption( FEEDBACK_TOUR_OPTION ) as + | boolean + | undefined, + }; + } ); + + return hasShownTour; +}; + +type ProductFeedbackTourProps = { + currentTab: string; +}; + +export const ProductFeedbackTour: React.FC< ProductFeedbackTourProps > = ( { + currentTab, +} ) => { + const hasShownTour = useShowProductFeedbackTour(); + const [ isTourVisible, setIsTourVisible ] = useState( false ); + const tourTimeout = useRef< ReturnType< typeof setTimeout > | null >( + null + ); + const { updateOptions } = useDispatch( OPTIONS_STORE_NAME ); + + const clearTourTimeout = () => { + clearTimeout( tourTimeout.current as ReturnType< typeof setTimeout > ); + tourTimeout.current = null; + }; + + useEffect( () => { + if ( hasShownTour !== false ) { + return; + } + + tourTimeout.current = setTimeout( () => { + setIsTourVisible( true ); + }, FEEDBACK_TIMEOUT_MS ); + + return () => clearTourTimeout(); + }, [ hasShownTour ] ); + + useEffect( () => { + if ( ! isTourVisible ) { + return; + } + updateOptions( { + [ FEEDBACK_TOUR_OPTION ]: true, + } ); + }, [ isTourVisible ] ); + + if ( + currentTab === 'feedback' && + ( isTourVisible || tourTimeout.current ) + ) { + setIsTourVisible( false ); + clearTourTimeout(); + } + + if ( ! isTourVisible ) { + return null; + } + + return ( + { + setIsTourVisible( false ); + }, + } } + /> + ); +}; diff --git a/plugins/woocommerce/changelog/add-35301-delayed-ces-prompt b/plugins/woocommerce/changelog/add-35301-delayed-ces-prompt new file mode 100644 index 00000000000..1d4e88eca9a --- /dev/null +++ b/plugins/woocommerce/changelog/add-35301-delayed-ces-prompt @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Adding delayed spotlight to feedback button on current product page. From c94a5c6f4b85c88461887ced6c3b965759d0a72b Mon Sep 17 00:00:00 2001 From: Matt Sherman Date: Thu, 15 Dec 2022 21:49:02 -0500 Subject: [PATCH 09/70] Refactor AttributeField into sub-components (#35997) * Add className support to ListItem * Refactor to extract AttributeListItem component * Use AttributeListItem component in AttributeField * Extract AttributeEmptyState from AttributeField * Use AttributeEmptyState in AttributeField * Add default value for label prop on AddAttributeListItem * Add props for labels in AddAttributeModal * Add props for labels in EditAttributeModal --- .../components/changelog/list-item-classname | 4 + .../js/components/src/list-item/list-item.tsx | 4 +- .../attribute-empty-state-logo.svg | 0 .../attribute-empty-state.scss | 14 ++ .../attribute-empty-state.tsx | 58 +++++++ .../fields/attribute-empty-state/index.ts | 2 + .../attribute-field/add-attribute-modal.tsx | 104 ++++++------ .../attribute-field/attribute-field.scss | 51 +----- .../attribute-field/attribute-field.tsx | 151 +++++------------- .../attribute-field/edit-attribute-modal.tsx | 124 ++++++++------ .../add-attribute-list-item.tsx | 29 ++++ .../attribute-list-item.scss | 40 +++++ .../attribute-list-item.tsx | 79 +++++++++ .../fields/attribute-list-item/index.ts | 2 + .../changelog/update-refactor-attribute-field | 4 + 15 files changed, 410 insertions(+), 256 deletions(-) create mode 100644 packages/js/components/changelog/list-item-classname rename plugins/woocommerce-admin/client/products/fields/{attribute-field => attribute-empty-state}/attribute-empty-state-logo.svg (100%) create mode 100644 plugins/woocommerce-admin/client/products/fields/attribute-empty-state/attribute-empty-state.scss create mode 100644 plugins/woocommerce-admin/client/products/fields/attribute-empty-state/attribute-empty-state.tsx create mode 100644 plugins/woocommerce-admin/client/products/fields/attribute-empty-state/index.ts create mode 100644 plugins/woocommerce-admin/client/products/fields/attribute-list-item/add-attribute-list-item.tsx create mode 100644 plugins/woocommerce-admin/client/products/fields/attribute-list-item/attribute-list-item.scss create mode 100644 plugins/woocommerce-admin/client/products/fields/attribute-list-item/attribute-list-item.tsx create mode 100644 plugins/woocommerce-admin/client/products/fields/attribute-list-item/index.ts create mode 100644 plugins/woocommerce/changelog/update-refactor-attribute-field diff --git a/packages/js/components/changelog/list-item-classname b/packages/js/components/changelog/list-item-classname new file mode 100644 index 00000000000..9f5a5ea0d55 --- /dev/null +++ b/packages/js/components/changelog/list-item-classname @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add className prop to ListItem. diff --git a/packages/js/components/src/list-item/list-item.tsx b/packages/js/components/src/list-item/list-item.tsx index d6b77112e6e..f36c7815304 100644 --- a/packages/js/components/src/list-item/list-item.tsx +++ b/packages/js/components/src/list-item/list-item.tsx @@ -12,19 +12,21 @@ import { SortableHandle } from '../sortable'; export type ListItemProps = { children: JSX.Element | JSX.Element[] | string; + className?: string; onDragStart?: DragEventHandler< HTMLDivElement >; onDragEnd?: DragEventHandler< HTMLDivElement >; }; export const ListItem = ( { children, + className, onDragStart, onDragEnd, }: ListItemProps ) => { const isDraggable = onDragEnd && onDragStart; return ( -
+
{ isDraggable && } { children }
diff --git a/plugins/woocommerce-admin/client/products/fields/attribute-field/attribute-empty-state-logo.svg b/plugins/woocommerce-admin/client/products/fields/attribute-empty-state/attribute-empty-state-logo.svg similarity index 100% rename from plugins/woocommerce-admin/client/products/fields/attribute-field/attribute-empty-state-logo.svg rename to plugins/woocommerce-admin/client/products/fields/attribute-empty-state/attribute-empty-state-logo.svg diff --git a/plugins/woocommerce-admin/client/products/fields/attribute-empty-state/attribute-empty-state.scss b/plugins/woocommerce-admin/client/products/fields/attribute-empty-state/attribute-empty-state.scss new file mode 100644 index 00000000000..c340fab81a8 --- /dev/null +++ b/plugins/woocommerce-admin/client/products/fields/attribute-empty-state/attribute-empty-state.scss @@ -0,0 +1,14 @@ +.woocommerce-attribute-empty-state { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + + &__image { + max-width: 150px; + margin: $gap-larger 0 $gap-large; + } + &__add-new { + margin: $gap-large 0 $gap-larger; + } +} diff --git a/plugins/woocommerce-admin/client/products/fields/attribute-empty-state/attribute-empty-state.tsx b/plugins/woocommerce-admin/client/products/fields/attribute-empty-state/attribute-empty-state.tsx new file mode 100644 index 00000000000..9285e395f86 --- /dev/null +++ b/plugins/woocommerce-admin/client/products/fields/attribute-empty-state/attribute-empty-state.tsx @@ -0,0 +1,58 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Button, Card, CardBody } from '@wordpress/components'; +import { Text } from '@woocommerce/experimental'; + +/** + * Internal dependencies + */ +import './attribute-empty-state.scss'; +import AttributeEmptyStateLogo from './attribute-empty-state-logo.svg'; + +type AttributeEmptyStateProps = { + image?: string; + subtitle?: string; + addNewLabel?: string; + onNewClick?: () => void; +}; + +export const AttributeEmptyState: React.FC< AttributeEmptyStateProps > = ( { + image = AttributeEmptyStateLogo, + subtitle = __( 'No attributes yet', 'woocommerce' ), + addNewLabel = __( 'Add first attribute', 'woocommerce' ), + onNewClick, +} ) => { + return ( + + +
+ Completed + + { subtitle } + + { typeof onNewClick === 'function' && ( + + ) } +
+
+
+ ); +}; diff --git a/plugins/woocommerce-admin/client/products/fields/attribute-empty-state/index.ts b/plugins/woocommerce-admin/client/products/fields/attribute-empty-state/index.ts new file mode 100644 index 00000000000..c7f15a07625 --- /dev/null +++ b/plugins/woocommerce-admin/client/products/fields/attribute-empty-state/index.ts @@ -0,0 +1,2 @@ +export * from './attribute-empty-state'; +export { default as AttributeEmptyStateLogo } from './attribute-empty-state-logo.svg'; diff --git a/plugins/woocommerce-admin/client/products/fields/attribute-field/add-attribute-modal.tsx b/plugins/woocommerce-admin/client/products/fields/attribute-field/add-attribute-modal.tsx index fb2bd1f0554..cf5c61288c8 100644 --- a/plugins/woocommerce-admin/client/products/fields/attribute-field/add-attribute-modal.tsx +++ b/plugins/woocommerce-admin/client/products/fields/attribute-field/add-attribute-modal.tsx @@ -30,6 +30,21 @@ import { HydratedAttributeType } from '../attribute-field'; import { getProductAttributeObject } from './utils'; type AddAttributeModalProps = { + title?: string; + notice?: string; + attributeLabel?: string; + valueLabel?: string; + attributePlaceholder?: string; + termPlaceholder?: string; + removeLabel?: string; + addAnotherAccessibleLabel?: string; + addAnotherLabel?: string; + cancelLabel?: string; + addAccessibleLabel?: string; + addLabel?: string; + confirmMessage?: string; + confirmCancelLabel?: string; + confirmConfirmLabel?: string; onCancel: () => void; onAdd: ( newCategories: HydratedAttributeType[] ) => void; selectedAttributeIds?: number[]; @@ -40,6 +55,27 @@ type AttributeForm = { }; export const AddAttributeModal: React.FC< AddAttributeModalProps > = ( { + title = __( 'Add attributes', 'woocommerce' ), + notice = __( + 'By default, attributes are filterable and visible on the product page. You can change these settings for each attribute separately later.', + 'woocommerce' + ), + attributeLabel = __( 'Attribute', 'woocommerce' ), + valueLabel = __( 'Values', 'woocommerce' ), + attributePlaceholder = __( 'Search or create attribute', 'woocommerce' ), + termPlaceholder = __( 'Search or create value', 'woocommerce' ), + removeLabel = __( 'Remove attribute', 'woocommerce' ), + addAnotherAccessibleLabel = __( 'Add another attribute', 'woocommerce' ), + addAnotherLabel = __( '+ Add another', 'woocommerce' ), + cancelLabel = __( 'Cancel', 'woocommerce' ), + addAccessibleLabel = __( 'Add attributes', 'woocommerce' ), + addLabel = __( 'Add', 'woocommerce' ), + confirmMessage = __( + 'You have some attributes added to the list, are you sure you want to cancel?', + 'woocommerce' + ), + confirmCancelLabel = __( 'No thanks', 'woocommerce' ), + confirmConfirmLabel = __( 'Yes please!', 'woocommerce' ), onCancel, onAdd, selectedAttributeIds = [], @@ -124,9 +160,6 @@ export const AddAttributeModal: React.FC< AddAttributeModalProps > = ( { } }; - const attributeLabel = __( 'Attribute', 'woocommerce' ); - const valueLabel = __( 'Values', 'woocommerce' ); - return ( <> @@ -144,7 +177,7 @@ export const AddAttributeModal: React.FC< AddAttributeModalProps > = ( { } ) => { return ( @@ -158,12 +191,7 @@ export const AddAttributeModal: React.FC< AddAttributeModalProps > = ( { className="woocommerce-add-attribute-modal" > -

- { __( - 'By default, attributes are filterable and visible on the product page. You can change these settings for each attribute separately later.', - 'woocommerce' - ) } -

+

{ notice }

@@ -183,10 +211,9 @@ export const AddAttributeModal: React.FC< AddAttributeModalProps > = ( { > = ( { { attribute === null || attribute.id !== 0 ? ( = ( { /> ) : ( = ( { .attributes[ 0 ] === null } - label={ __( - 'Remove attribute', - 'woocommerce' - ) } + label={ + removeLabel + } onClick={ () => onRemove( index, @@ -329,10 +353,7 @@ export const AddAttributeModal: React.FC< AddAttributeModalProps > = ( {
@@ -377,15 +394,12 @@ export const AddAttributeModal: React.FC< AddAttributeModalProps > = ( { { showConfirmClose && ( setShowConfirmClose( false ) } onConfirm={ onCancel } > - { __( - 'You have some attributes added to the list, are you sure you want to cancel?', - 'woocommerce' - ) } + { confirmMessage } ) } diff --git a/plugins/woocommerce-admin/client/products/fields/attribute-field/attribute-field.scss b/plugins/woocommerce-admin/client/products/fields/attribute-field/attribute-field.scss index b056534865b..3a1b21d41f0 100644 --- a/plugins/woocommerce-admin/client/products/fields/attribute-field/attribute-field.scss +++ b/plugins/woocommerce-admin/client/products/fields/attribute-field/attribute-field.scss @@ -1,63 +1,14 @@ .woocommerce-attribute-field { width: 100%; - &__empty-container { - width: 100%; - display: flex; - flex-direction: column; - align-items: center; - } - &__empty-logo { - max-width: 150px; - margin: $gap-larger 0 $gap-large; - } - &__add-new { - margin: $gap-large 0 $gap-larger; - } - &__attribute-option-chip { - padding: $gap-smallest $gap-smaller; - gap: 2px; - - background: $gray-100; - border-radius: 2px; - } - - &__attribute-options { - display: flex; - flex-direction: row; - gap: $gap-smallest; - } - - &__attribute-actions { - display: flex; - flex-direction: row; - align-items: center; - justify-content: end; - gap: $gap-smaller; - } - - .woocommerce-list-item { - min-height: 82px; - padding: 0 $gap-large; - - &:last-child { - margin-bottom: -1px; - } - } - .woocommerce-sortable { margin: 0; - - .woocommerce-list-item { - display: grid; - grid-template-columns: 24px 26% auto 90px; - } } .woocommerce-sortable__item:not(:first-child) { margin-top: -1px; } - .woocommerce-sortable__item:focus-visible:not(:active) + .woocommerce-sortable__item .woocommerce-list-item { + .woocommerce-sortable__item:focus-visible:not(:active) + .woocommerce-sortable__item .woocommerce-attribute-list-item { background: none; border-top: 0; } diff --git a/plugins/woocommerce-admin/client/products/fields/attribute-field/attribute-field.tsx b/plugins/woocommerce-admin/client/products/fields/attribute-field/attribute-field.tsx index 2b70153c1bb..a624eceb89e 100644 --- a/plugins/woocommerce-admin/client/products/fields/attribute-field/attribute-field.tsx +++ b/plugins/woocommerce-admin/client/products/fields/attribute-field/attribute-field.tsx @@ -1,8 +1,7 @@ /** * External dependencies */ -import { sprintf, __ } from '@wordpress/i18n'; -import { Button, Card, CardBody } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; import { useState, useCallback, useEffect } from '@wordpress/element'; import { ProductAttribute, @@ -10,24 +9,25 @@ import { ProductAttributeTerm, } from '@woocommerce/data'; import { resolveSelect } from '@wordpress/data'; -import { Text } from '@woocommerce/experimental'; import { Sortable, - ListItem, __experimentalSelectControlMenuSlot as SelectControlMenuSlot, } from '@woocommerce/components'; -import { closeSmall } from '@wordpress/icons'; import { recordEvent } from '@woocommerce/tracks'; /** * Internal dependencies */ import './attribute-field.scss'; -import AttributeEmptyStateLogo from './attribute-empty-state-logo.svg'; import { AddAttributeModal } from './add-attribute-modal'; import { EditAttributeModal } from './edit-attribute-modal'; import { reorderSortableProductAttributePositions } from './utils'; import { sift } from '../../../utils'; +import { AttributeEmptyState } from '../attribute-empty-state'; +import { + AddAttributeListItem, + AttributeListItem, +} from '../attribute-list-item'; type AttributeFieldProps = { value: ProductAttribute[]; @@ -167,53 +167,29 @@ export const AttributeField: React.FC< AttributeFieldProps > = ( { if ( ! value || value.length === 0 || hydratedAttributes.length === 0 ) { return ( - - -
-
- Completed - - { __( 'No attributes yet', 'woocommerce' ) } - - -
- { showAddAttributeModal && ( - { - recordEvent( CANCEL_BUTTON_EVENT_NAME ); - setShowAddAttributeModal( false ); - } } - onAdd={ onAddNewAttributes } - selectedAttributeIds={ ( value || [] ).map( - ( attr ) => attr.id - ) } - /> + <> + { + recordEvent( + 'product_add_first_attribute_button_click' + ); + setShowAddAttributeModal( true ); + } } + /> + { showAddAttributeModal && ( + { + recordEvent( CANCEL_BUTTON_EVENT_NAME ); + setShowAddAttributeModal( false ); + } } + onAdd={ onAddNewAttributes } + selectedAttributeIds={ ( value || [] ).map( + ( attr ) => attr.id ) } - -
-
-
+ /> + ) } + + ); } @@ -242,63 +218,24 @@ export const AttributeField: React.FC< AttributeFieldProps > = ( { } } > { sortedAttributes.map( ( attribute ) => ( - -
{ attribute.name }
-
- { attribute.options - .slice( 0, 2 ) - .map( ( option, index ) => ( -
- { option } -
- ) ) } - { attribute.options.length > 2 && ( -
- { sprintf( - __( '+ %i more', 'woocommerce' ), - attribute.options.length - 2 - ) } -
- ) } -
-
- - -
-
+ + setEditingAttributeId( + fetchAttributeId( attribute ) + ) + } + onRemoveClick={ () => onRemove( attribute ) } + /> ) ) } - - - + { + recordEvent( 'product_add_attribute_button' ); + setShowAddAttributeModal( true ); + } } + /> { showAddAttributeModal && ( { diff --git a/plugins/woocommerce-admin/client/products/fields/attribute-field/edit-attribute-modal.tsx b/plugins/woocommerce-admin/client/products/fields/attribute-field/edit-attribute-modal.tsx index 19e82720c5e..63e3cd01830 100644 --- a/plugins/woocommerce-admin/client/products/fields/attribute-field/edit-attribute-modal.tsx +++ b/plugins/woocommerce-admin/client/products/fields/attribute-field/edit-attribute-modal.tsx @@ -28,12 +28,67 @@ import { HydratedAttributeType } from './attribute-field'; import './edit-attribute-modal.scss'; type EditAttributeModalProps = { + title?: string; + nameLabel?: string; + globalAttributeHelperMessage?: string; + customAttributeHelperMessage?: string; + termsLabel?: string; + termsPlaceholder?: string; + visibleLabel?: string; + visibleTooltip?: string; + filtersLabel?: string; + filtersTooltip?: string; + cancelAccessibleLabel?: string; + cancelLabel?: string; + updateAccessibleLabel?: string; + updateLabel?: string; onCancel: () => void; onEdit: ( alteredAttribute: HydratedAttributeType ) => void; attribute: HydratedAttributeType; }; export const EditAttributeModal: React.FC< EditAttributeModalProps > = ( { + title = __( 'Edit attribute', 'woocommerce' ), + nameLabel = __( 'Name', 'woocommerce' ), + globalAttributeHelperMessage = interpolateComponents( { + mixedString: __( + `You can change the attribute's name in {{link}}Attributes{{/link}}.`, + 'woocommerce' + ), + components: { + link: ( + + <> + + ), + }, + } ), + customAttributeHelperMessage = __( + 'Your customers will see this on the product page', + 'woocommerce' + ), + termsLabel = __( 'Values', 'woocommerce' ), + termsPlaceholder = __( 'Search or create value', 'woocommerce' ), + visibleLabel = __( 'Visible to customers', 'woocommerce' ), + visibleTooltip = __( + 'Show or hide this attribute on the product page', + 'woocommerce' + ), + filtersLabel = __( 'Used for filters', 'woocommerce' ), + filtersTooltip = __( + `Show or hide this attribute in the filters section on your store's category and shop pages`, + 'woocommerce' + ), + cancelAccessibleLabel = __( 'Cancel', 'woocommerce' ), + cancelLabel = __( 'Cancel', 'woocommerce' ), + updateAccessibleLabel = __( 'Edit attribute', 'woocommerce' ), + updateLabel = __( 'Update', 'woocommerce' ), onCancel, onEdit, attribute, @@ -46,13 +101,13 @@ export const EditAttributeModal: React.FC< EditAttributeModalProps > = ( { return ( onCancel() } className="woocommerce-edit-attribute-modal" >
= ( { />

{ ! isCustomAttribute - ? interpolateComponents( { - mixedString: __( - `You can change the attribute's name in {{link}}Attributes{{/link}}.`, - 'woocommerce' - ), - components: { - link: ( - - <> - - ), - }, - } ) - : __( - 'Your customers will see this on the product page', - 'woocommerce' - ) } + ? globalAttributeHelperMessage + : customAttributeHelperMessage }

{ attribute.terms ? ( { @@ -108,11 +139,8 @@ export const EditAttributeModal: React.FC< EditAttributeModalProps > = ( { /> ) : ( { @@ -133,14 +161,9 @@ export const EditAttributeModal: React.FC< EditAttributeModalProps > = ( { } ) } checked={ editableAttribute?.visible } - label={ __( 'Visible to customers', 'woocommerce' ) } - /> - +
= ( { } ) } checked={ editableAttribute?.variation } - label={ __( 'Used for filters', 'woocommerce' ) } - /> - +
diff --git a/plugins/woocommerce-admin/client/products/fields/attribute-list-item/add-attribute-list-item.tsx b/plugins/woocommerce-admin/client/products/fields/attribute-list-item/add-attribute-list-item.tsx new file mode 100644 index 00000000000..1992bda1889 --- /dev/null +++ b/plugins/woocommerce-admin/client/products/fields/attribute-list-item/add-attribute-list-item.tsx @@ -0,0 +1,29 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { DragEventHandler } from 'react'; +import { Button } from '@wordpress/components'; +import { ListItem } from '@woocommerce/components'; + +type AddAttributeListItemProps = { + label?: string; + onAddClick?: () => void; +}; + +export const AddAttributeListItem: React.FC< AddAttributeListItemProps > = ( { + label = __( 'Add attribute', 'woocommerce' ), + onAddClick, +} ) => { + return ( + + + + ); +}; diff --git a/plugins/woocommerce-admin/client/products/fields/attribute-list-item/attribute-list-item.scss b/plugins/woocommerce-admin/client/products/fields/attribute-list-item/attribute-list-item.scss new file mode 100644 index 00000000000..ada8ad5b84a --- /dev/null +++ b/plugins/woocommerce-admin/client/products/fields/attribute-list-item/attribute-list-item.scss @@ -0,0 +1,40 @@ +.woocommerce-add-attribute-list-item, +.woocommerce-attribute-list-item { + min-height: 82px; + padding: 0 $gap-large; + + &:last-child { + margin-bottom: -1px; + } +} + +.woocommerce-attribute-list-item { + display: grid; + grid-template-columns: 24px 26% auto 90px; + + &:last-child { + margin-bottom: -1px; + } + + &__options { + display: flex; + flex-direction: row; + gap: $gap-smallest; + } + + &__option-chip { + padding: $gap-smallest $gap-smaller; + gap: 2px; + + background: $gray-100; + border-radius: 2px; + } + + &__actions { + display: flex; + flex-direction: row; + align-items: center; + justify-content: end; + gap: $gap-smaller; + } +} diff --git a/plugins/woocommerce-admin/client/products/fields/attribute-list-item/attribute-list-item.tsx b/plugins/woocommerce-admin/client/products/fields/attribute-list-item/attribute-list-item.tsx new file mode 100644 index 00000000000..f2ee31f6707 --- /dev/null +++ b/plugins/woocommerce-admin/client/products/fields/attribute-list-item/attribute-list-item.tsx @@ -0,0 +1,79 @@ +/** + * External dependencies + */ +import { DragEventHandler } from 'react'; +import { ListItem } from '@woocommerce/components'; +import { ProductAttribute } from '@woocommerce/data'; +import { sprintf, __ } from '@wordpress/i18n'; +import { Button } from '@wordpress/components'; +import { closeSmall } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import './attribute-list-item.scss'; + +type AttributeListItemProps = { + attribute: ProductAttribute; + editLabel?: string; + removeLabel?: string; + onDragStart?: DragEventHandler< HTMLDivElement >; + onDragEnd?: DragEventHandler< HTMLDivElement >; + onEditClick?: ( attribute: ProductAttribute ) => void; + onRemoveClick?: ( attribute: ProductAttribute ) => void; +}; + +export const AttributeListItem: React.FC< AttributeListItemProps > = ( { + attribute, + editLabel = __( 'edit', 'woocommerce' ), + removeLabel = __( 'Remove attribute', 'woocommerce' ), + onDragStart, + onDragEnd, + onEditClick, + onRemoveClick, +} ) => { + return ( + +
{ attribute.name }
+
+ { attribute.options.slice( 0, 2 ).map( ( option, index ) => ( +
+ { option } +
+ ) ) } + { attribute.options.length > 2 && ( +
+ { sprintf( + __( '+ %i more', 'woocommerce' ), + attribute.options.length - 2 + ) } +
+ ) } +
+
+ { typeof onEditClick === 'function' && ( + + ) } + { typeof onRemoveClick === 'function' && ( + + ) } +
+
+ ); +}; diff --git a/plugins/woocommerce-admin/client/products/fields/attribute-list-item/index.ts b/plugins/woocommerce-admin/client/products/fields/attribute-list-item/index.ts new file mode 100644 index 00000000000..018701d9d73 --- /dev/null +++ b/plugins/woocommerce-admin/client/products/fields/attribute-list-item/index.ts @@ -0,0 +1,2 @@ +export * from './add-attribute-list-item'; +export * from './attribute-list-item'; diff --git a/plugins/woocommerce/changelog/update-refactor-attribute-field b/plugins/woocommerce/changelog/update-refactor-attribute-field new file mode 100644 index 00000000000..4c85475d6d4 --- /dev/null +++ b/plugins/woocommerce/changelog/update-refactor-attribute-field @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak + +Refactor AttributeField into sub-components. From 915256b709b9989c293bcc0d62644f60297d71e6 Mon Sep 17 00:00:00 2001 From: Adrian Duffell <9312929+adrianduffell@users.noreply.github.com> Date: Fri, 16 Dec 2022 16:57:09 +0800 Subject: [PATCH 10/70] Add Missing Tracks Events to Tax Settings Conflict Warning (#36042) * Import Tracks package * Add tracks event tax_settings_conflict_recommended_settings_clicked * Add tracks event tax_settings_conflict * Add tracks event tax_settings_conflict_dismissed * Add changelog --- .../settings/conflict-error-slotfill.js | 19 ++++++++++++++++--- .../add-tax-settings-conflict-tracks | 4 ++++ 2 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 plugins/woocommerce/changelog/add-tax-settings-conflict-tracks diff --git a/plugins/woocommerce-admin/client/settings/conflict-error-slotfill.js b/plugins/woocommerce-admin/client/settings/conflict-error-slotfill.js index f6992f76c92..18ceb1deb4d 100644 --- a/plugins/woocommerce-admin/client/settings/conflict-error-slotfill.js +++ b/plugins/woocommerce-admin/client/settings/conflict-error-slotfill.js @@ -15,6 +15,7 @@ import { Icon, closeSmall } from '@wordpress/icons'; import { useEffect, useState } from '@wordpress/element'; import { useDispatch } from '@wordpress/data'; import { store as noticesStore } from '@wordpress/notices'; +import { recordEvent } from '@woocommerce/tracks'; /** * Internal dependencies @@ -70,6 +71,8 @@ const SettingsErrorFill = () => { 'success', __( 'Recommended settings applied.', 'woocommerce' ) ); + + recordEvent( 'tax_settings_conflict_recommended_settings_clicked' ); }; const ApplyRecommendedSettingsButton = () => ( @@ -125,6 +128,12 @@ const SettingsErrorFill = () => { setIsConflict( false ); } else { setIsConflict( true ); + + recordEvent( 'tax_settings_conflict', { + main: pricesEnteredWithTaxSetting, + shop: displayPricesInShopWithTaxSetting, + cart: displayPricesInCartWithTaxSetting, + } ); } }, [ displayPricesInCartWithTaxSetting, @@ -169,9 +178,13 @@ const SettingsErrorFill = () => {
diff --git a/plugins/woocommerce/changelog/add-tax-settings-conflict-tracks b/plugins/woocommerce/changelog/add-tax-settings-conflict-tracks new file mode 100644 index 00000000000..276ac2ba1f5 --- /dev/null +++ b/plugins/woocommerce/changelog/add-tax-settings-conflict-tracks @@ -0,0 +1,4 @@ +Significance: patch +Type: add + +Add missing Tracks events to tax settings conflict banner. From d46a1045e843f5f6f9f55bac3b1df47738def674 Mon Sep 17 00:00:00 2001 From: Joshua T Flowers Date: Fri, 16 Dec 2022 06:20:10 -0800 Subject: [PATCH 11/70] Add product variation form and routes (#36033) * Add route for editing product variation * Fix up ID selector for getItem in CRUD data store * Add product variation form * Add changelog entries --- packages/js/data/changelog/add-35786 | 4 ++ packages/js/data/src/crud/types.ts | 2 +- .../client/layout/controller.js | 17 +++++-- .../client/products/add-product-page.tsx | 1 + .../client/products/edit-product-page.tsx | 50 +++++++++++++++---- .../client/products/product-form.tsx | 1 - .../products/product-variation-form.tsx | 42 ++++++++++++++++ plugins/woocommerce/changelog/add-35786 | 4 ++ 8 files changed, 105 insertions(+), 16 deletions(-) create mode 100644 packages/js/data/changelog/add-35786 create mode 100644 plugins/woocommerce-admin/client/products/product-variation-form.tsx create mode 100644 plugins/woocommerce/changelog/add-35786 diff --git a/packages/js/data/changelog/add-35786 b/packages/js/data/changelog/add-35786 new file mode 100644 index 00000000000..c872094db78 --- /dev/null +++ b/packages/js/data/changelog/add-35786 @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Fix IdQuery selector for getItem selector in CRUD data stores diff --git a/packages/js/data/src/crud/types.ts b/packages/js/data/src/crud/types.ts index d0a0c02eb8a..5b5aa46a114 100644 --- a/packages/js/data/src/crud/types.ts +++ b/packages/js/data/src/crud/types.ts @@ -76,7 +76,7 @@ export type CrudSelectors< '': WPDataSelector< typeof getItem >; }, ResourceName, - IdType, + IdQuery, ItemType > & MapSelectors< diff --git a/plugins/woocommerce-admin/client/layout/controller.js b/plugins/woocommerce-admin/client/layout/controller.js index 0ef2068d80f..4612662e02d 100644 --- a/plugins/woocommerce-admin/client/layout/controller.js +++ b/plugins/woocommerce-admin/client/layout/controller.js @@ -29,7 +29,6 @@ const EditProductPage = lazy( () => /* webpackChunkName: "edit-product-page" */ '../products/edit-product-page' ) ); - const AddProductPage = lazy( () => import( /* webpackChunkName: "add-product-page" */ '../products/add-product-page' @@ -46,7 +45,6 @@ const AnalyticsSettings = lazy( () => const Dashboard = lazy( () => import( /* webpackChunkName: "dashboard" */ '../dashboard' ) ); - const Homescreen = lazy( () => import( /* webpackChunkName: "homescreen" */ '../homescreen' ) ); @@ -66,7 +64,6 @@ const ProfileWizard = lazy( () => const SettingsGroup = lazy( () => import( /* webpackChunkName: "profile-wizard" */ '../settings' ) ); - const WCPaymentsWelcomePage = lazy( () => import( /* webpackChunkName: "wcpay-payment-welcome-page" */ '../payments-welcome' @@ -202,6 +199,20 @@ export const getPages = () => { wpOpenMenu: 'menu-posts-product', capability: 'manage_woocommerce', } ); + + pages.push( { + container: EditProductPage, + path: '/product/:productId/variation/:variationId', + breadcrumbs: [ + [ '/edit-product', __( 'Product', 'woocommerce' ) ], + __( 'Edit Product Variation', 'woocommerce' ), + ], + navArgs: { + id: 'woocommerce-edit-product', + }, + wpOpenMenu: 'menu-posts-product', + capability: 'edit_products', + } ); } if ( window.wcAdminFeatures.onboarding ) { diff --git a/plugins/woocommerce-admin/client/products/add-product-page.tsx b/plugins/woocommerce-admin/client/products/add-product-page.tsx index ac84914a862..6b0ab353f11 100644 --- a/plugins/woocommerce-admin/client/products/add-product-page.tsx +++ b/plugins/woocommerce-admin/client/products/add-product-page.tsx @@ -8,6 +8,7 @@ import { useEffect } from '@wordpress/element'; * Internal dependencies */ import { ProductForm } from './product-form'; +import './product-page.scss'; const AddProductPage: React.FC = () => { useEffect( () => { diff --git a/plugins/woocommerce-admin/client/products/edit-product-page.tsx b/plugins/woocommerce-admin/client/products/edit-product-page.tsx index 9544f353adf..b04f633fb75 100644 --- a/plugins/woocommerce-admin/client/products/edit-product-page.tsx +++ b/plugins/woocommerce-admin/client/products/edit-product-page.tsx @@ -2,35 +2,43 @@ * External dependencies */ import { __ } from '@wordpress/i18n'; -import { recordEvent } from '@woocommerce/tracks'; -import { useEffect, useRef } from '@wordpress/element'; -import { useSelect } from '@wordpress/data'; -import { Spinner, FormRef } from '@woocommerce/components'; import { + EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME, PartialProduct, Product, PRODUCTS_STORE_NAME, WCDataSelector, } from '@woocommerce/data'; +import { recordEvent } from '@woocommerce/tracks'; +import { useEffect, useRef } from '@wordpress/element'; +import { useSelect } from '@wordpress/data'; +import { Spinner, FormRef } from '@woocommerce/components'; import { useParams } from 'react-router-dom'; /** * Internal dependencies */ import { ProductForm } from './product-form'; import { ProductFormLayout } from './layout/product-form-layout'; +import { ProductVariationForm } from './product-variation-form'; +import './product-page.scss'; const EditProductPage: React.FC = () => { - const { productId } = useParams(); + const { productId, variationId } = useParams(); + const isProductVariation = !! variationId; const previousProductRef = useRef< PartialProduct >(); const formRef = useRef< FormRef< Partial< Product > > >( null ); - const { product, isLoading, isPendingAction } = useSelect( + const { product, isLoading, isPendingAction, productVariation } = useSelect( ( select: WCDataSelector ) => { const { getProduct, - hasFinishedResolution, + hasFinishedResolution: hasProductFinishedResolution, isPending, getPermalinkParts, } = select( PRODUCTS_STORE_NAME ); + const { + getProductVariation, + hasFinishedResolution: hasProductVariationFinishedResolution, + } = select( EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME ); if ( productId ) { const retrievedProduct = getProduct( parseInt( productId, 10 ), @@ -44,13 +52,26 @@ const EditProductPage: React.FC = () => { permalinkParts && retrievedProduct ? retrievedProduct : undefined, + productVariation: + isProductVariation && + getProductVariation( { + id: parseInt( variationId, 10 ), + product_id: productId, + } ), isLoading: - ! hasFinishedResolution( 'getProduct', [ + ! hasProductFinishedResolution( 'getProduct', [ parseInt( productId, 10 ), ] ) || - ! hasFinishedResolution( 'getPermalinkParts', [ + ! hasProductFinishedResolution( 'getPermalinkParts', [ parseInt( productId, 10 ), - ] ), + ] ) || + ! ( + isProductVariation && + hasProductVariationFinishedResolution( + 'getProductVariation', + [ parseInt( variationId, 10 ) ] + ) + ), isPendingAction: isPending( 'createProduct' ) || isPending( @@ -109,7 +130,14 @@ const EditProductPage: React.FC = () => {
) } - { product && + { productVariation && product && ( + + ) } + { ! isProductVariation && + product && ( product.status !== 'trash' || wasDeletedUsingAction ) && ( ) } diff --git a/plugins/woocommerce-admin/client/products/product-form.tsx b/plugins/woocommerce-admin/client/products/product-form.tsx index a7ef34a3941..4d6ce6696dd 100644 --- a/plugins/woocommerce-admin/client/products/product-form.tsx +++ b/plugins/woocommerce-admin/client/products/product-form.tsx @@ -16,7 +16,6 @@ import { PricingSection } from './sections/pricing-section'; import { ProductShippingSection } from './sections/product-shipping-section'; import { ProductVariationsSection } from './sections/product-variations-section'; import { ImagesSection } from './sections/images-section'; -import './product-page.scss'; import { validate } from './product-validation'; import { AttributesSection } from './sections/attributes-section'; import { ProductFormFooter } from './layout/product-form-footer'; diff --git a/plugins/woocommerce-admin/client/products/product-variation-form.tsx b/plugins/woocommerce-admin/client/products/product-variation-form.tsx new file mode 100644 index 00000000000..277141c3598 --- /dev/null +++ b/plugins/woocommerce-admin/client/products/product-variation-form.tsx @@ -0,0 +1,42 @@ +/** + * External dependencies + */ +import { Form } from '@woocommerce/components'; +import { PartialProduct, ProductVariation } from '@woocommerce/data'; + +/** + * Internal dependencies + */ +import { ProductFormHeader } from './layout/product-form-header'; +import { ProductFormLayout } from './layout/product-form-layout'; +import { ProductFormFooter } from './layout/product-form-footer'; +import { ProductFormTab } from './product-form-tab'; + +export const ProductVariationForm: React.FC< { + product: PartialProduct; + productVariation: Partial< ProductVariation >; +} > = ( { productVariation } ) => { + return ( + > + initialValues={ productVariation } + errors={ {} } + > + + + + <>General + + + <>Pricing + + + <>Inventory + + + <>Shipping + + + + + ); +}; diff --git a/plugins/woocommerce/changelog/add-35786 b/plugins/woocommerce/changelog/add-35786 new file mode 100644 index 00000000000..f0b0758eab7 --- /dev/null +++ b/plugins/woocommerce/changelog/add-35786 @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add product variation form From 469ca96830301f3ed8606215b74b1f335cbdaed0 Mon Sep 17 00:00:00 2001 From: Joshua T Flowers Date: Fri, 16 Dec 2022 09:25:03 -0800 Subject: [PATCH 12/70] Add endpoint to create all product variations (#35980) * Add endpoint to create all product variations * Add changelog entry * Change endpoint name to generate * Fix up phpcs errors --- .../woocommerce/changelog/add-35778-endpoint | 4 ++ ...-wc-rest-product-variations-controller.php | 55 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 plugins/woocommerce/changelog/add-35778-endpoint diff --git a/plugins/woocommerce/changelog/add-35778-endpoint b/plugins/woocommerce/changelog/add-35778-endpoint new file mode 100644 index 00000000000..6be2bd41d92 --- /dev/null +++ b/plugins/woocommerce/changelog/add-35778-endpoint @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add endpoint to create all product variations diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php index 9d2000b23eb..8c025b89a53 100644 --- a/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php +++ b/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php @@ -10,6 +10,8 @@ defined( 'ABSPATH' ) || exit; +use Automattic\Jetpack\Constants; + /** * REST API variations controller class. * @@ -25,6 +27,33 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V */ protected $namespace = 'wc/v3'; + /** + * Register the routes for products. + */ + public function register_routes() { + parent::register_routes(); + + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/generate', + array( + 'args' => array( + 'product_id' => array( + 'description' => __( 'Unique identifier for the variable product.', 'woocommerce' ), + 'type' => 'integer', + ), + ), + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => array( $this, 'generate' ), + 'permission_callback' => array( $this, 'create_item_permissions_check' ), + 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + } + /** * Prepare a single variation output for response. * @@ -877,4 +906,30 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V return $params; } + + /** + * Generate all variations for a given product. + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_Error|WP_REST_Response + */ + public function generate( $request ) { + $product_id = (int) $request['product_id']; + + if ( 'product' !== get_post_type( $product_id ) ) { + return new WP_Error( 'woocommerce_rest_product_invalid_id', __( 'Invalid product ID.', 'woocommerce' ), array( 'status' => 404 ) ); + } + + wc_maybe_define_constant( 'WC_MAX_LINKED_VARIATIONS', 50 ); + wc_set_time_limit( 0 ); + + $response = array(); + $product = wc_get_product( $product_id ); + $data_store = $product->get_data_store(); + $response['count'] = $data_store->create_all_product_variations( $product, Constants::get_constant( 'WC_MAX_LINKED_VARIATIONS' ) ); + + $data_store->sort_all_product_variations( $product->get_id() ); + + return rest_ensure_response( $response ); + } } From a6fa0e71bffb14c886e74cd40ffdd84870fec48a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maikel=20David=20P=C3=A9rez=20G=C3=B3mez?= Date: Fri, 16 Dec 2022 14:44:06 -0300 Subject: [PATCH 13/70] Add product variation visibility toggle (#36020) * Add svg icons and show them in variations list * The visibility icon should reflect the current visibility status * Clicking the visibility icon should toggle on or off the variation visibility and persist these settings on refresh * Price and quantity should be fade when the variant is not visible * Add changelog * Hovering the visibility icon should display a tooltip indicating what clicking this button will do * Fix linter error * Fix spinner opacity if button is disabled --- .../fields/variations/variations.scss | 42 +++++- .../products/fields/variations/variations.tsx | 120 +++++++++++++++++- .../client/products/images/hidden-icon.tsx | 32 +++++ .../client/products/images/visible-icon.tsx | 31 +++++ plugins/woocommerce/changelog/add-35790 | 4 + 5 files changed, 222 insertions(+), 7 deletions(-) create mode 100644 plugins/woocommerce-admin/client/products/images/hidden-icon.tsx create mode 100644 plugins/woocommerce-admin/client/products/images/visible-icon.tsx create mode 100644 plugins/woocommerce/changelog/add-35790 diff --git a/plugins/woocommerce-admin/client/products/fields/variations/variations.scss b/plugins/woocommerce-admin/client/products/fields/variations/variations.scss index fe9ceff53b6..49f9d4827e8 100644 --- a/plugins/woocommerce-admin/client/products/fields/variations/variations.scss +++ b/plugins/woocommerce-admin/client/products/fields/variations/variations.scss @@ -10,7 +10,7 @@ &__header { display: grid; - grid-template-columns: calc(38px + 25%) 25% 25%; + grid-template-columns: auto 25% 25% 88px; padding: $gap-small $gap; h4 { @@ -35,9 +35,45 @@ } } + &__price--fade, + &__quantity--fade { + opacity: 0.5; + } + + &__actions { + display: flex; + align-items: center; + justify-content: end; + + .components-button { + position: relative; + + &:disabled, + &[aria-disabled='true'] { + opacity: 1; + } + + .components-spinner { + margin: 4px; + } + } + + .components-button svg { + fill: none; + } + + .components-button--visible { + color: $gray-700; + } + + .components-button--hidden { + color: $alert-red; + } + } + .woocommerce-list-item { display: grid; - grid-template-columns: 38px 25% 25% 25%; + grid-template-columns: 38px auto 25% 25% 88px; margin-left: -1px; margin-right: -1px; margin-bottom: -1px; @@ -48,7 +84,7 @@ flex: 1 0 auto; } - .components-spinner { + &.is-loading .components-spinner { width: 34px; height: 34px; left: 50%; diff --git a/plugins/woocommerce-admin/client/products/fields/variations/variations.tsx b/plugins/woocommerce-admin/client/products/fields/variations/variations.tsx index 04c08748e29..dccaaee14fc 100644 --- a/plugins/woocommerce-admin/client/products/fields/variations/variations.tsx +++ b/plugins/woocommerce-admin/client/products/fields/variations/variations.tsx @@ -2,7 +2,7 @@ * External dependencies */ import { __, sprintf } from '@wordpress/i18n'; -import { Card, Spinner } from '@wordpress/components'; +import { Button, Card, Spinner, Tooltip } from '@wordpress/components'; import { EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME, ProductVariation, @@ -10,12 +10,14 @@ import { import { ListItem, Pagination, Sortable, Tag } from '@woocommerce/components'; import { useContext, useState } from '@wordpress/element'; import { useParams } from 'react-router-dom'; -import { useSelect } from '@wordpress/data'; +import { useSelect, useDispatch } from '@wordpress/data'; import classnames from 'classnames'; /** * Internal dependencies */ +import HiddenIcon from '~/products/images/hidden-icon'; +import VisibleIcon from '~/products/images/visible-icon'; import { CurrencyContext } from '../../../lib/currency-context'; import { getProductStockStatus, @@ -33,9 +35,16 @@ import './variations.scss'; */ const DEFAULT_PER_PAGE_OPTION = 25; +const NOT_VISIBLE_TEXT = __( 'Not visible to customers', 'woocommerce' ); +const VISIBLE_TEXT = __( 'Visible to customers', 'woocommerce' ); +const UPDATING_TEXT = __( 'Updating product variation', 'woocommerce' ); + export const Variations: React.FC = () => { const [ currentPage, setCurrentPage ] = useState( 1 ); const [ perPage, setPerPage ] = useState( DEFAULT_PER_PAGE_OPTION ); + const [ isUpdating, setIsUpdating ] = useState< Record< string, boolean > >( + {} + ); const { productId } = useParams(); const context = useContext( CurrencyContext ); const { formatAmount, getCurrencyConfig } = context; @@ -64,6 +73,10 @@ export const Variations: React.FC = () => { [ currentPage, perPage ] ); + const { updateProductVariation } = useDispatch( + EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME + ); + if ( ! variations || isLoading ) { return ( @@ -74,6 +87,26 @@ export const Variations: React.FC = () => { const currencyConfig = getCurrencyConfig(); + function handleCustomerVisibilityClick( + variationId: number, + status: 'private' | 'publish' + ) { + if ( isUpdating[ variationId ] ) return; + setIsUpdating( ( prevState ) => ( { + ...prevState, + [ variationId ]: true, + } ) ); + updateProductVariation< Promise< ProductVariation > >( + { product_id: productId, id: variationId }, + { status } + ).finally( () => + setIsUpdating( ( prevState ) => ( { + ...prevState, + [ variationId ]: false, + } ) ) + ); + } + return (
@@ -102,10 +135,26 @@ export const Variations: React.FC = () => { /> ) ) }
-
+
{ formatAmount( variation.price ) }
-
+
{ { getProductStockStatus( variation ) }
+
+ { variation.status === 'private' && ( + + + + ) } + + { variation.status === 'publish' && ( + + + + ) } +
) ) } diff --git a/plugins/woocommerce-admin/client/products/images/hidden-icon.tsx b/plugins/woocommerce-admin/client/products/images/hidden-icon.tsx new file mode 100644 index 00000000000..95e0e985935 --- /dev/null +++ b/plugins/woocommerce-admin/client/products/images/hidden-icon.tsx @@ -0,0 +1,32 @@ +export default function HiddenIcon( { + width = 24, + height = 24, + ...props +}: React.SVGProps< SVGSVGElement > ) { + return ( + + ); +} diff --git a/plugins/woocommerce-admin/client/products/images/visible-icon.tsx b/plugins/woocommerce-admin/client/products/images/visible-icon.tsx new file mode 100644 index 00000000000..7848cef02bc --- /dev/null +++ b/plugins/woocommerce-admin/client/products/images/visible-icon.tsx @@ -0,0 +1,31 @@ +export default function VisibleIcon( { + width = 24, + height = 24, + ...props +}: React.SVGProps< SVGSVGElement > ) { + return ( + + ); +} diff --git a/plugins/woocommerce/changelog/add-35790 b/plugins/woocommerce/changelog/add-35790 new file mode 100644 index 00000000000..cd1f146ab0e --- /dev/null +++ b/plugins/woocommerce/changelog/add-35790 @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add Product variation visibility toggle From d768307e69e20edce68353e0efcade05e8f9db9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maikel=20David=20P=C3=A9rez=20G=C3=B3mez?= Date: Fri, 16 Dec 2022 14:44:54 -0300 Subject: [PATCH 14/70] Add single product variation sections (#36051) --- .../client/products/product-variation-form.tsx | 11 ++++++++--- plugins/woocommerce/changelog/add-35988 | 4 ++++ 2 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 plugins/woocommerce/changelog/add-35988 diff --git a/plugins/woocommerce-admin/client/products/product-variation-form.tsx b/plugins/woocommerce-admin/client/products/product-variation-form.tsx index 277141c3598..dabeefd0d16 100644 --- a/plugins/woocommerce-admin/client/products/product-variation-form.tsx +++ b/plugins/woocommerce-admin/client/products/product-variation-form.tsx @@ -11,6 +11,9 @@ import { ProductFormHeader } from './layout/product-form-header'; import { ProductFormLayout } from './layout/product-form-layout'; import { ProductFormFooter } from './layout/product-form-footer'; import { ProductFormTab } from './product-form-tab'; +import { PricingSection } from './sections/pricing-section'; +import { ProductInventorySection } from './sections/product-inventory-section'; +import { ProductShippingSection } from './sections/product-shipping-section'; export const ProductVariationForm: React.FC< { product: PartialProduct; @@ -27,13 +30,15 @@ export const ProductVariationForm: React.FC< { <>General - <>Pricing + - <>Inventory + - <>Shipping + diff --git a/plugins/woocommerce/changelog/add-35988 b/plugins/woocommerce/changelog/add-35988 new file mode 100644 index 00000000000..d1ed3537c78 --- /dev/null +++ b/plugins/woocommerce/changelog/add-35988 @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add single product variation sections From 103fec668bc3621ab83a376136049e06feb86566 Mon Sep 17 00:00:00 2001 From: Niklas <91073306+nkls-so@users.noreply.github.com> Date: Fri, 16 Dec 2022 18:48:13 +0100 Subject: [PATCH 15/70] Use Imagick instead of putenv() to only use 1 thread and avoid memory issues with OpenMP (#35339) * Use Imagick to set threads instead of putenv() (#31942) * Adapt code to the coding styles * Changelog and PHPCS fix. Co-authored-by: Vedanshu Jain --- plugins/woocommerce/changelog/pr-35339 | 4 ++++ .../includes/class-wc-regenerate-images-request.php | 10 ++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 plugins/woocommerce/changelog/pr-35339 diff --git a/plugins/woocommerce/changelog/pr-35339 b/plugins/woocommerce/changelog/pr-35339 new file mode 100644 index 00000000000..d35dcd7f381 --- /dev/null +++ b/plugins/woocommerce/changelog/pr-35339 @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Use Imagick functions to set parallel thread count instead of direct putenv call as suggested in https://core.trac.wordpress.org/ticket/36534#comment:129. diff --git a/plugins/woocommerce/includes/class-wc-regenerate-images-request.php b/plugins/woocommerce/includes/class-wc-regenerate-images-request.php index 047a9e2f2a4..340c743dae8 100644 --- a/plugins/woocommerce/includes/class-wc-regenerate-images-request.php +++ b/plugins/woocommerce/includes/class-wc-regenerate-images-request.php @@ -33,8 +33,14 @@ class WC_Regenerate_Images_Request extends WC_Background_Process { $this->prefix = 'wp_' . get_current_blog_id(); $this->action = 'wc_regenerate_images'; - // This is needed to prevent timeouts due to threading. See https://core.trac.wordpress.org/ticket/36534. - @putenv( 'MAGICK_THREAD_LIMIT=1' ); // @codingStandardsIgnoreLine. + // Limit Imagick to only use 1 thread to avoid memory issues with OpenMP. + if ( extension_loaded( 'imagick' ) && method_exists( Imagick::class, 'setResourceLimit' ) ) { + if ( defined( 'Imagick::RESOURCETYPE_THREAD' ) ) { + Imagick::setResourceLimit( Imagick::RESOURCETYPE_THREAD, 1 ); + } else { + Imagick::setResourceLimit( 6, 1 ); + } + } parent::__construct(); } From 62a957c75dccb8e7dabf4b39ff8c1f68132c70e4 Mon Sep 17 00:00:00 2001 From: Moon Date: Fri, 16 Dec 2022 09:55:55 -0800 Subject: [PATCH 16/70] Add aria-label for simple select dropdown in select-control component (#35808) * Add aria-label for simple select dropdown * Add changelog * Use placeholder value * Allow aria label prop and use label as a fallback * Update test snapshot * Add changelog --- .../update-add-aria-label-for-simple-select-dropdown | 4 ++++ packages/js/components/src/select-control/control.js | 1 + .../steps/store-details/test/__snapshots__/index.js.snap | 2 ++ .../woocommerce/changelog/update-store-details-test-snapshot | 4 ++++ 4 files changed, 11 insertions(+) create mode 100644 packages/js/components/changelog/update-add-aria-label-for-simple-select-dropdown create mode 100644 plugins/woocommerce/changelog/update-store-details-test-snapshot diff --git a/packages/js/components/changelog/update-add-aria-label-for-simple-select-dropdown b/packages/js/components/changelog/update-add-aria-label-for-simple-select-dropdown new file mode 100644 index 00000000000..ff75398a39b --- /dev/null +++ b/packages/js/components/changelog/update-add-aria-label-for-simple-select-dropdown @@ -0,0 +1,4 @@ +Significance: patch +Type: add + +Add aria-label for simple select dropdown diff --git a/packages/js/components/src/select-control/control.js b/packages/js/components/src/select-control/control.js index c1baa3bf07d..931a4e425a2 100644 --- a/packages/js/components/src/select-control/control.js +++ b/packages/js/components/src/select-control/control.js @@ -154,6 +154,7 @@ class Control extends Component { : null } disabled={ disabled } + aria-label={ this.props.ariaLabel ?? this.props.label } /> ); } diff --git a/plugins/woocommerce-admin/client/profile-wizard/steps/store-details/test/__snapshots__/index.js.snap b/plugins/woocommerce-admin/client/profile-wizard/steps/store-details/test/__snapshots__/index.js.snap index 93d77575b83..1e7a53cbc51 100644 --- a/plugins/woocommerce-admin/client/profile-wizard/steps/store-details/test/__snapshots__/index.js.snap +++ b/plugins/woocommerce-admin/client/profile-wizard/steps/store-details/test/__snapshots__/index.js.snap @@ -115,6 +115,7 @@ Object { aria-autocomplete="list" aria-expanded="false" aria-haspopup="true" + aria-label="Country / Region *" autocomplete="new-password" class="woocommerce-select-control__control-input" id="woocommerce-select-control-0__control-input" @@ -429,6 +430,7 @@ Object { aria-autocomplete="list" aria-expanded="false" aria-haspopup="true" + aria-label="Country / Region *" autocomplete="new-password" class="woocommerce-select-control__control-input" id="woocommerce-select-control-0__control-input" diff --git a/plugins/woocommerce/changelog/update-store-details-test-snapshot b/plugins/woocommerce/changelog/update-store-details-test-snapshot new file mode 100644 index 00000000000..68caa4b6109 --- /dev/null +++ b/plugins/woocommerce/changelog/update-store-details-test-snapshot @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Update store-details test snapshot to reflect updated select-control \ No newline at end of file From 9c4cb19a2d0cc68d51a6a4fc2f200ec5d8b76318 Mon Sep 17 00:00:00 2001 From: "Jorge A. Torres" Date: Fri, 16 Dec 2022 17:11:21 -0300 Subject: [PATCH 17/70] Drop use of WP 5.9 function in `quantity-input.php` (#36054) --- plugins/woocommerce/changelog/fix-36039 | 4 ++++ plugins/woocommerce/templates/global/quantity-input.php | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 plugins/woocommerce/changelog/fix-36039 diff --git a/plugins/woocommerce/changelog/fix-36039 b/plugins/woocommerce/changelog/fix-36039 new file mode 100644 index 00000000000..dde70b7a981 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-36039 @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Drop usage of WP 5.9 function in the product quantity selector template. diff --git a/plugins/woocommerce/templates/global/quantity-input.php b/plugins/woocommerce/templates/global/quantity-input.php index 370576e0b6c..5c346ecd307 100644 --- a/plugins/woocommerce/templates/global/quantity-input.php +++ b/plugins/woocommerce/templates/global/quantity-input.php @@ -12,7 +12,7 @@ * * @see https://docs.woocommerce.com/document/template-structure/ * @package WooCommerce\Templates - * @version 7.2.0 + * @version 7.2.1 */ defined( 'ABSPATH' ) || exit; @@ -40,7 +40,7 @@ if ( $max_value && $min_value === $max_value ) { + id="" class="" name="" From 024e2382cb8bf1a581945bcb8d7a40be5a4944fd Mon Sep 17 00:00:00 2001 From: Kamil <33346738+leskam@users.noreply.github.com> Date: Fri, 16 Dec 2022 21:58:45 +0100 Subject: [PATCH 18/70] Add an `required` argument to function `wc_dropdown_variation_attribute_options` (#34579) * Add an `required` argument to function `wc_dropdown_variation_attribute_options` * Whitespace, changelog. Co-authored-by: barryhughes <3594411+barryhughes@users.noreply.github.com> --- .../woocommerce/changelog/attribute-dropdown-required-attr | 4 ++++ plugins/woocommerce/includes/wc-template-functions.php | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 plugins/woocommerce/changelog/attribute-dropdown-required-attr diff --git a/plugins/woocommerce/changelog/attribute-dropdown-required-attr b/plugins/woocommerce/changelog/attribute-dropdown-required-attr new file mode 100644 index 00000000000..e5e3a747bf0 --- /dev/null +++ b/plugins/woocommerce/changelog/attribute-dropdown-required-attr @@ -0,0 +1,4 @@ +Significance: patch +Type: add + +Adds support for a 'required' argument when invoking `wc_dropdown_variation_attribute_options()`. diff --git a/plugins/woocommerce/includes/wc-template-functions.php b/plugins/woocommerce/includes/wc-template-functions.php index 0935b8c85e9..4b3ef3ec827 100644 --- a/plugins/woocommerce/includes/wc-template-functions.php +++ b/plugins/woocommerce/includes/wc-template-functions.php @@ -3078,6 +3078,7 @@ if ( ! function_exists( 'wc_dropdown_variation_attribute_options' ) ) { 'attribute' => false, 'product' => false, 'selected' => false, + 'required' => false, 'name' => '', 'id' => '', 'class' => '', @@ -3099,6 +3100,7 @@ if ( ! function_exists( 'wc_dropdown_variation_attribute_options' ) ) { $name = $args['name'] ? $args['name'] : 'attribute_' . sanitize_title( $attribute ); $id = $args['id'] ? $args['id'] : sanitize_title( $attribute ); $class = $args['class']; + $required = (bool) $args['required']; $show_option_none = (bool) $args['show_option_none']; $show_option_none_text = $args['show_option_none'] ? $args['show_option_none'] : __( 'Choose an option', 'woocommerce' ); // We'll do our best to hide the placeholder, but we'll need to show something when resetting options. @@ -3107,7 +3109,7 @@ if ( ! function_exists( 'wc_dropdown_variation_attribute_options' ) ) { $options = $attributes[ $attribute ]; } - $html = ''; $html .= ''; if ( ! empty( $options ) ) { From 23299dbafd53b0e0cff545fe1d4d94e7052b6299 Mon Sep 17 00:00:00 2001 From: Paul Sealock Date: Mon, 19 Dec 2022 07:31:26 +1300 Subject: [PATCH 19/70] Create Extension: Prep for initial release (#36002) * Add initial changelog * run changelog release prep tool * revert package.json version bump --- packages/js/create-woo-extension/.gitignore | 3 +++ packages/js/create-woo-extension/CHANGELOG.md | 9 +++++++++ packages/js/create-woo-extension/changelog/add-dev-env | 5 ----- .../js/create-woo-extension/changelog/add-wc-validation | 4 ---- 4 files changed, 12 insertions(+), 9 deletions(-) create mode 100644 packages/js/create-woo-extension/.gitignore create mode 100644 packages/js/create-woo-extension/CHANGELOG.md delete mode 100644 packages/js/create-woo-extension/changelog/add-dev-env delete mode 100644 packages/js/create-woo-extension/changelog/add-wc-validation diff --git a/packages/js/create-woo-extension/.gitignore b/packages/js/create-woo-extension/.gitignore new file mode 100644 index 00000000000..4b894a3e094 --- /dev/null +++ b/packages/js/create-woo-extension/.gitignore @@ -0,0 +1,3 @@ +vendor +node_modules +.turbo diff --git a/packages/js/create-woo-extension/CHANGELOG.md b/packages/js/create-woo-extension/CHANGELOG.md new file mode 100644 index 00000000000..73d08b13bdb --- /dev/null +++ b/packages/js/create-woo-extension/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.0.0](https://www.npmjs.com/package/@woocommerce/create-woo-extension/v/1.0.0) - 2022-12-15 + +- Patch - Add WC validation [#35947] + +[See legacy changelogs for previous versions](https://github.com/woocommerce/woocommerce/blob/68581955106947918d2b17607a01bdfdf22288a9/packages/js/create-woo-extension/CHANGELOG.md). diff --git a/packages/js/create-woo-extension/changelog/add-dev-env b/packages/js/create-woo-extension/changelog/add-dev-env deleted file mode 100644 index a2f0ae8ef75..00000000000 --- a/packages/js/create-woo-extension/changelog/add-dev-env +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: dev -Comment: Initial PR - - diff --git a/packages/js/create-woo-extension/changelog/add-wc-validation b/packages/js/create-woo-extension/changelog/add-wc-validation deleted file mode 100644 index 4e8303e4307..00000000000 --- a/packages/js/create-woo-extension/changelog/add-wc-validation +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -Add WC validation From 8a2f3859af8dd984108cd09bf09c69b8135e401e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 19 Dec 2022 08:33:59 +1300 Subject: [PATCH 20/70] Delete changelog files based on PR 35985 (#35994) Delete changelog files for 35985 Co-authored-by: WooCommerce Bot --- plugins/woocommerce/changelog/spotfix-tested-up-to-61 | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 plugins/woocommerce/changelog/spotfix-tested-up-to-61 diff --git a/plugins/woocommerce/changelog/spotfix-tested-up-to-61 b/plugins/woocommerce/changelog/spotfix-tested-up-to-61 deleted file mode 100644 index 3f82deabb1e..00000000000 --- a/plugins/woocommerce/changelog/spotfix-tested-up-to-61 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: tweak - -WooCommerce has now been tested up to WordPress 6.1.x. From 852b347a83607439cbcbbefec0493ae44fceac85 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 19 Dec 2022 08:34:25 +1300 Subject: [PATCH 21/70] Delete changelog files based on PR 35967 (#36038) Delete changelog files for 35967 Co-authored-by: WooCommerce Bot --- .../changelog/fix-include-taxes-in-states-migrator | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 plugins/woocommerce/changelog/fix-include-taxes-in-states-migrator diff --git a/plugins/woocommerce/changelog/fix-include-taxes-in-states-migrator b/plugins/woocommerce/changelog/fix-include-taxes-in-states-migrator deleted file mode 100644 index 56f896ec324..00000000000 --- a/plugins/woocommerce/changelog/fix-include-taxes-in-states-migrator +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -Include taxes migration in MigrationHelper::migrate_country_states From 54fe53341ef7cb70f8795165915b5c56106f8634 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 19 Dec 2022 08:34:47 +1300 Subject: [PATCH 22/70] Delete changelog files based on PR 36042 (#36045) Delete changelog files for 36042 Co-authored-by: WooCommerce Bot --- .../woocommerce/changelog/add-tax-settings-conflict-tracks | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 plugins/woocommerce/changelog/add-tax-settings-conflict-tracks diff --git a/plugins/woocommerce/changelog/add-tax-settings-conflict-tracks b/plugins/woocommerce/changelog/add-tax-settings-conflict-tracks deleted file mode 100644 index 276ac2ba1f5..00000000000 --- a/plugins/woocommerce/changelog/add-tax-settings-conflict-tracks +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: add - -Add missing Tracks events to tax settings conflict banner. From 6e2f6e9315daf9461d6de404456f546de375092f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 19 Dec 2022 08:47:35 +1300 Subject: [PATCH 23/70] Delete changelog files based on PR 35963 (#36064) Delete changelog files for 35963 Co-authored-by: WooCommerce Bot --- plugins/woocommerce/changelog/fix-35872-hrk-eur | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 plugins/woocommerce/changelog/fix-35872-hrk-eur diff --git a/plugins/woocommerce/changelog/fix-35872-hrk-eur b/plugins/woocommerce/changelog/fix-35872-hrk-eur deleted file mode 100644 index 58565ce6627..00000000000 --- a/plugins/woocommerce/changelog/fix-35872-hrk-eur +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -As of 2023-01-01, Croatia will use the Euro by default. From 1e12453cca257c956cdf5a914ed91d281e0453a1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 19 Dec 2022 09:55:11 +1300 Subject: [PATCH 24/70] Prep trunk for 7.4 cycle (#36022) * Prep trunk for 7.4 cycle * changelog Co-authored-by: WooCommerce Bot Co-authored-by: Paul Sealock --- .../woocommerce/changelog/prep-trunk-for-next-dev-cycle-7.4 | 5 +++++ plugins/woocommerce/composer.json | 2 +- plugins/woocommerce/includes/class-woocommerce.php | 2 +- plugins/woocommerce/package.json | 2 +- plugins/woocommerce/readme.txt | 2 +- plugins/woocommerce/woocommerce.php | 2 +- 6 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 plugins/woocommerce/changelog/prep-trunk-for-next-dev-cycle-7.4 diff --git a/plugins/woocommerce/changelog/prep-trunk-for-next-dev-cycle-7.4 b/plugins/woocommerce/changelog/prep-trunk-for-next-dev-cycle-7.4 new file mode 100644 index 00000000000..2ae78c77c14 --- /dev/null +++ b/plugins/woocommerce/changelog/prep-trunk-for-next-dev-cycle-7.4 @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: 7.4 release prep + + diff --git a/plugins/woocommerce/composer.json b/plugins/woocommerce/composer.json index 2d6bab1ef66..398e184a04e 100644 --- a/plugins/woocommerce/composer.json +++ b/plugins/woocommerce/composer.json @@ -2,7 +2,7 @@ "name": "woocommerce/woocommerce", "description": "An eCommerce toolkit that helps you sell anything. Beautifully.", "homepage": "https://woocommerce.com/", - "version": "7.3.0", + "version": "7.4.0", "type": "wordpress-plugin", "license": "GPL-3.0-or-later", "prefer-stable": true, diff --git a/plugins/woocommerce/includes/class-woocommerce.php b/plugins/woocommerce/includes/class-woocommerce.php index 3940d55040a..de40de7fc70 100644 --- a/plugins/woocommerce/includes/class-woocommerce.php +++ b/plugins/woocommerce/includes/class-woocommerce.php @@ -32,7 +32,7 @@ final class WooCommerce { * * @var string */ - public $version = '7.3.0'; + public $version = '7.4.0'; /** * WooCommerce Schema version. diff --git a/plugins/woocommerce/package.json b/plugins/woocommerce/package.json index a042808f6fc..6a43466b9b7 100644 --- a/plugins/woocommerce/package.json +++ b/plugins/woocommerce/package.json @@ -1,7 +1,7 @@ { "name": "woocommerce", "title": "WooCommerce", - "version": "7.3.0", + "version": "7.4.0", "homepage": "https://woocommerce.com/", "repository": { "type": "git", diff --git a/plugins/woocommerce/readme.txt b/plugins/woocommerce/readme.txt index f1376465c24..3fdd1f65265 100644 --- a/plugins/woocommerce/readme.txt +++ b/plugins/woocommerce/readme.txt @@ -163,6 +163,6 @@ WooCommerce comes with some sample data you can use to see how products look; im == Changelog == -= 7.3.0 2022-XX-XX = += 7.4.0 2022-XX-XX = [See changelog for all versions](https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/changelog.txt). diff --git a/plugins/woocommerce/woocommerce.php b/plugins/woocommerce/woocommerce.php index c4ea4601c34..e17f4e15495 100644 --- a/plugins/woocommerce/woocommerce.php +++ b/plugins/woocommerce/woocommerce.php @@ -3,7 +3,7 @@ * Plugin Name: WooCommerce * Plugin URI: https://woocommerce.com/ * Description: An eCommerce toolkit that helps you sell anything. Beautifully. - * Version: 7.3.0-dev + * Version: 7.4.0-dev * Author: Automattic * Author URI: https://woocommerce.com * Text Domain: woocommerce From 7a22d9a7ff807d45c85d1506c37393b1c9b3fa10 Mon Sep 17 00:00:00 2001 From: Paul Sealock Date: Mon, 19 Dec 2022 11:25:06 +1300 Subject: [PATCH 25/70] Fix/requires at least 5.9 (#36065) * bump requires at least to 5.9 * changelog --- plugins/woocommerce/changelog/fix-requires-at-least-5.9 | 5 +++++ plugins/woocommerce/woocommerce.php | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 plugins/woocommerce/changelog/fix-requires-at-least-5.9 diff --git a/plugins/woocommerce/changelog/fix-requires-at-least-5.9 b/plugins/woocommerce/changelog/fix-requires-at-least-5.9 new file mode 100644 index 00000000000..7dec5628d13 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-requires-at-least-5.9 @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: Bump requires at least to 5.9 + + diff --git a/plugins/woocommerce/woocommerce.php b/plugins/woocommerce/woocommerce.php index e17f4e15495..ea6a562f1c9 100644 --- a/plugins/woocommerce/woocommerce.php +++ b/plugins/woocommerce/woocommerce.php @@ -8,7 +8,7 @@ * Author URI: https://woocommerce.com * Text Domain: woocommerce * Domain Path: /i18n/languages/ - * Requires at least: 5.8 + * Requires at least: 5.9 * Requires PHP: 7.2 * * @package WooCommerce From 76810a4445e9575728bc82d17199f60d85c3eb21 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 19 Dec 2022 13:05:46 +1300 Subject: [PATCH 26/70] Delete changelog files based on PR 36065 (#36069) Delete changelog files for 36065 Co-authored-by: WooCommerce Bot --- plugins/woocommerce/changelog/fix-requires-at-least-5.9 | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 plugins/woocommerce/changelog/fix-requires-at-least-5.9 diff --git a/plugins/woocommerce/changelog/fix-requires-at-least-5.9 b/plugins/woocommerce/changelog/fix-requires-at-least-5.9 deleted file mode 100644 index 7dec5628d13..00000000000 --- a/plugins/woocommerce/changelog/fix-requires-at-least-5.9 +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: dev -Comment: Bump requires at least to 5.9 - - From c4260c935fbc21213589441840765b83de2d110e Mon Sep 17 00:00:00 2001 From: rodelgc Date: Mon, 19 Dec 2022 14:33:22 +0800 Subject: [PATCH 27/70] Consistent folder structure for E2E and API test results (#35907) Consistent folder structure for E2E and REST API test results --- .github/workflows/pr-build-and-e2e-tests.yml | 8 +-- .github/workflows/smoke-test-daily.yml | 4 +- .gitignore | 11 +--- .../changelog/e2e-output-directories | 4 ++ .../tests/api-core-tests/README.md | 61 +++++++++++++++++++ .../tests/api-core-tests/playwright.config.js | 10 +-- plugins/woocommerce/tests/e2e-pw/README.md | 61 +++++++++++++++++++ .../tests/e2e-pw/playwright.config.js | 14 +++-- 8 files changed, 148 insertions(+), 25 deletions(-) create mode 100644 plugins/woocommerce/changelog/e2e-output-directories diff --git a/.github/workflows/pr-build-and-e2e-tests.yml b/.github/workflows/pr-build-and-e2e-tests.yml index b4dbc95df24..d66270ba70d 100644 --- a/.github/workflows/pr-build-and-e2e-tests.yml +++ b/.github/workflows/pr-build-and-e2e-tests.yml @@ -12,8 +12,8 @@ jobs: name: Runs E2E tests. runs-on: ubuntu-20.04 env: - ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/allure-results - ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/allure-report + ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-results + ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-report outputs: E2E_GRAND_TOTAL: ${{ steps.count_e2e_total.outputs.E2E_GRAND_TOTAL }} steps: @@ -80,8 +80,8 @@ jobs: name: Runs API tests. runs-on: ubuntu-20.04 env: - ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/api-test-report/allure-results - ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/api-test-report/allure-report + ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/test-results/allure-results + ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/test-results/allure-report steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/smoke-test-daily.yml b/.github/workflows/smoke-test-daily.yml index 7c985e30813..73223f64242 100644 --- a/.github/workflows/smoke-test-daily.yml +++ b/.github/workflows/smoke-test-daily.yml @@ -79,8 +79,8 @@ jobs: needs: [e2e-tests] if: success() || failure() env: - ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/api-test-report/allure-results - ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/api-test-report/allure-report + ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/test-results/allure-results + ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/test-results/allure-report steps: - uses: actions/checkout@v3 with: diff --git a/.gitignore b/.gitignore index 78d84df67a0..dfa505b1edd 100644 --- a/.gitignore +++ b/.gitignore @@ -72,8 +72,8 @@ yarn.lock # Editors nbproject/private/ -# Test Results -test-results.json +# E2E and API Test Results +test-results # Admin Feature config plugins/woocommerce/includes/react-admin/feature-config.php @@ -89,13 +89,6 @@ allure-results changes.json .env -# Playwright output & working files -/plugins/woocommerce/tests/e2e-pw/output -/plugins/woocommerce/tests/e2e-pw/report -/plugins/woocommerce/tests/e2e-pw/storage -/plugins/woocommerce/tests/e2e-pw/test-results.json -/plugins/woocommerce/tests/api-core-tests/output - # Turborepo .turbo diff --git a/plugins/woocommerce/changelog/e2e-output-directories b/plugins/woocommerce/changelog/e2e-output-directories new file mode 100644 index 00000000000..dadee58a182 --- /dev/null +++ b/plugins/woocommerce/changelog/e2e-output-directories @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Consistent folder structure for E2E and API test results diff --git a/plugins/woocommerce/tests/api-core-tests/README.md b/plugins/woocommerce/tests/api-core-tests/README.md index 4f255159582..32d1b3ca590 100644 --- a/plugins/woocommerce/tests/api-core-tests/README.md +++ b/plugins/woocommerce/tests/api-core-tests/README.md @@ -12,6 +12,9 @@ This package contains automated API tests for WooCommerce, based on Playwright a - [Tools for writing tests](#tools-for-writing-tests) - [Creating test structure](#creating-test-structure) - [Writing the test](#writing-the-test) +- [Guide for using test reports](#guide-for-using-test-reports) + - [Viewing the Playwright HTML report](#viewing-the-playwright-html-report) + - [Viewing the Allure report](#viewing-the-allure-report) - [Debugging tests](#debugging-tests) ## Pre-requisites @@ -146,6 +149,64 @@ test.describe( 'Merchant can create virtual product', () => { } ); ``` +## Guide for using test reports + +The tests would generate three kinds of reports after the run: +1. A Playwright HTML report. +1. A Playwright JSON report. +1. Allure results. + +By default, they are saved inside the `test-results` folder. If you want to save them in a custom location, just assign the absolute path to the environment variables mentioned in the [Playwright](https://playwright.dev/docs/test-reporters) and [Allure-Playwright](https://www.npmjs.com/package/allure-playwright) documentation. + +| Report | Default location | Environment variable for custom location | +| ----------- | ---------------- | ---------------------------------------- | +| Playwright HTML report | `test-results/playwright-report` | `PLAYWRIGHT_HTML_REPORT` | +| Playwright JSON report | `test-results/test-results.json` | `PLAYWRIGHT_JSON_OUTPUT_NAME` | +| Allure results | `test-results/allure-results` | `ALLURE_RESULTS_DIR` | + +### Viewing the Playwright HTML report + +Use the `playwright show-report $PATH_TO_PLAYWRIGHT_HTML_REPORT` command to open the report. For example, assuming that you're at the root of the WooCommerce monorepo, and that you did not specify a custom location for the report, you would use the following commands: + +```bash +cd plugins/woocommerce +pnpm exec playwright show-report tests/api-core-tests/test-results/playwright-report +``` + +For more details about the Playwright HTML report, see their [HTML Reporter](https://playwright.dev/docs/test-reporters#html-reporter) documentation. + +### Viewing the Allure report + +This assumes that you're already familiar with reports generated by the [Allure Framework](https://github.com/allure-framework), particularly: +- What the `allure-results` and `allure-report` folders are, and how they're different from each other. +- Allure commands like `allure generate` and `allure open`. + +Use the `allure generate` command to generate an HTML report from the `allure-results` directory created at the end of the test run. Then, use the `allure open` command to open it on your browser. For example, assuming that: +- You're at the root of the WooCommerce monorepo +- You did not specify a custom location for `allure-results` (you did not assign a value to `ALLURE_RESULTS_DIR`) +- You want to generate the `allure-report` folder in `plugins/woocommerce/tests/api-core-tests/test-results` + +Then you would need to use the following commands: + +```bash +cd plugins/woocommerce +pnpm exec allure generate --clean tests/api-core-tests/test-results/allure-results --output tests/api-core-tests/test-results/allure-report +pnpm exec allure open tests/api-core-tests/test-results/allure-report +``` + +A browser window should open the Allure report. + +If you're using [WSL](https://learn.microsoft.com/en-us/windows/wsl/about) however, you might get this message right after running the `allure open` command: +``` +Starting web server... +2022-12-09 18:52:01.323:INFO::main: Logging initialized @286ms to org.eclipse.jetty.util.log.StdErrLog +Can not open browser because this capability is not supported on your platform. You can use the link below to open the report manually. +Server started at . Press to exit +``` +In this case, take note of the port number (38917 in the example above) and then use it to navigate to `http://localhost`. Taking the example above, you should be able to view the Allure report on http://localhost:38917. + +To know more about the allure-playwright integration, see their [GitHub documentation](https://github.com/allure-framework/allure-js/tree/master/packages/allure-playwright). + ## Debugging tests For Playwright debugging, follow [Playwright's documentation](https://playwright.dev/docs/debug). diff --git a/plugins/woocommerce/tests/api-core-tests/playwright.config.js b/plugins/woocommerce/tests/api-core-tests/playwright.config.js index c1a73685ebc..c66339409e2 100644 --- a/plugins/woocommerce/tests/api-core-tests/playwright.config.js +++ b/plugins/woocommerce/tests/api-core-tests/playwright.config.js @@ -19,7 +19,7 @@ const config = { ? Number( DEFAULT_TIMEOUT_OVERRIDE ) : 90 * 1000, expect: { timeout: 20 * 1000 }, - outputDir: './report', + outputDir: './test-results/report', testDir: 'tests', retries: CI ? 4 : 2, workers: 4, @@ -28,7 +28,7 @@ const config = { [ 'html', { - outputFolder: 'output', + outputFolder: process.env.PLAYWRIGHT_HTML_REPORT ?? './test-results/playwright-report', open: CI ? 'never' : 'always', }, ], @@ -37,14 +37,14 @@ const config = { { outputFolder: process.env.ALLURE_RESULTS_DIR ?? - 'tests/api-core-tests/api-test-report/allure-results', + './tests/api-core-tests/test-results/allure-results', }, ], [ 'json', { - outputFile: - 'tests/api-core-tests/api-test-report/test-results.json', + outputFile: process.env.PLAYWRIGHT_JSON_OUTPUT_NAME ?? + './test-results/test-results.json', }, ], ], diff --git a/plugins/woocommerce/tests/e2e-pw/README.md b/plugins/woocommerce/tests/e2e-pw/README.md index 5016e479715..a6813073019 100644 --- a/plugins/woocommerce/tests/e2e-pw/README.md +++ b/plugins/woocommerce/tests/e2e-pw/README.md @@ -12,6 +12,9 @@ This is the documentation for the new e2e testing setup based on Playwright and - [Tools for writing tests](#tools-for-writing-tests) - [Creating test structure](#creating-test-structure) - [Writing the test](#writing-the-test) +- [Guide for using test reports](#guide-for-using-test-reports) + - [Viewing the Playwright HTML report](#viewing-the-playwright-html-report) + - [Viewing the Allure report](#viewing-the-allure-report) - [Debugging tests](#debugging-tests) ## Pre-requisites @@ -140,6 +143,64 @@ test.describe( 'Merchant can create virtual product', () => { } ); ``` +## Guide for using test reports + +The tests would generate three kinds of reports after the run: +1. A Playwright HTML report. +1. A Playwright JSON report. +1. Allure results. + +By default, they are saved inside the `test-results` folder. If you want to save them in a custom location, just assign the absolute path to the environment variables mentioned in the [Playwright](https://playwright.dev/docs/test-reporters) and [Allure-Playwright](https://www.npmjs.com/package/allure-playwright) documentation. + +| Report | Default location | Environment variable for custom location | +| ----------- | ---------------- | ---------------------------------------- | +| Playwright HTML report | `test-results/playwright-report` | `PLAYWRIGHT_HTML_REPORT` | +| Playwright JSON report | `test-results/test-results.json` | `PLAYWRIGHT_JSON_OUTPUT_NAME` | +| Allure results | `test-results/allure-results` | `ALLURE_RESULTS_DIR` | + +### Viewing the Playwright HTML report + +Use the `playwright show-report $PATH_TO_PLAYWRIGHT_HTML_REPORT` command to open the report. For example, assuming that you're at the root of the WooCommerce monorepo, and that you did not specify a custom location for the report, you would use the following commands: + +```bash +cd plugins/woocommerce +pnpm exec playwright show-report tests/e2e-pw/test-results/playwright-report +``` + +For more details about the Playwright HTML report, see their [HTML Reporter](https://playwright.dev/docs/test-reporters#html-reporter) documentation. + +### Viewing the Allure report + +This assumes that you're already familiar with reports generated by the [Allure Framework](https://github.com/allure-framework), particularly: +- What the `allure-results` and `allure-report` folders are, and how they're different from each other. +- Allure commands like `allure generate` and `allure open`. + +Use the `allure generate` command to generate an HTML report from the `allure-results` directory created at the end of the test run. Then, use the `allure open` command to open it on your browser. For example, assuming that: +- You're at the root of the WooCommerce monorepo +- You did not specify a custom location for `allure-results` (you did not assign a value to `ALLURE_RESULTS_DIR`) +- You want to generate the `allure-report` folder in `plugins/woocommerce/tests/e2e-pw/test-results` + +Then you would need to use the following commands: + +```bash +cd plugins/woocommerce +pnpm exec allure generate --clean tests/e2e-pw/test-results/allure-results --output tests/e2e-pw/test-results/allure-report +pnpm exec allure open tests/e2e-pw/test-results/allure-report +``` + +A browser window should open the Allure report. + +If you're on [WSL](https://learn.microsoft.com/en-us/windows/wsl/about) however, you might get this message right after running the `allure open` command: +``` +Starting web server... +2022-12-09 18:52:01.323:INFO::main: Logging initialized @286ms to org.eclipse.jetty.util.log.StdErrLog +Can not open browser because this capability is not supported on your platform. You can use the link below to open the report manually. +Server started at . Press to exit +``` +In this case, take note of the port number (38917 in the example above) and then use it to navigate to `http://localhost`. Taking the example above, you should be able to view the Allure report on http://localhost:38917. + +To know more about the allure-playwright integration, see their [GitHub documentation](https://github.com/allure-framework/allure-js/tree/master/packages/allure-playwright). + ## Debugging tests For Playwright debugging, follow [Playwright's documentation](https://playwright.dev/docs/debug). diff --git a/plugins/woocommerce/tests/e2e-pw/playwright.config.js b/plugins/woocommerce/tests/e2e-pw/playwright.config.js index 6c67fa25c75..49f76e255bd 100644 --- a/plugins/woocommerce/tests/e2e-pw/playwright.config.js +++ b/plugins/woocommerce/tests/e2e-pw/playwright.config.js @@ -5,6 +5,7 @@ const { CI, DEFAULT_TIMEOUT_OVERRIDE, E2E_MAX_FAILURES, + PLAYWRIGHT_HTML_REPORT, } = process.env; const config = { @@ -12,7 +13,7 @@ const config = { ? Number( DEFAULT_TIMEOUT_OVERRIDE ) : 90 * 1000, expect: { timeout: 20 * 1000 }, - outputDir: './report', + outputDir: './test-results/report', globalSetup: require.resolve( './global-setup' ), globalTeardown: require.resolve( './global-teardown' ), testDir: 'tests', @@ -23,7 +24,9 @@ const config = { [ 'html', { - outputFolder: 'output', + outputFolder: + PLAYWRIGHT_HTML_REPORT ?? + './test-results/playwright-report', open: CI ? 'never' : 'always', }, ], @@ -31,18 +34,19 @@ const config = { 'allure-playwright', { outputFolder: - ALLURE_RESULTS_DIR ?? 'tests/e2e-pw/allure-results', + ALLURE_RESULTS_DIR ?? + './tests/e2e-pw/test-results/allure-results', detail: true, suiteTitle: true, }, ], - [ 'json', { outputFile: 'tests/e2e-pw/test-results.json' } ], + [ 'json', { outputFile: './test-results/test-results.json' } ], ], maxFailures: E2E_MAX_FAILURES ? Number( E2E_MAX_FAILURES ) : 0, use: { baseURL: BASE_URL ?? 'http://localhost:8086', screenshot: 'only-on-failure', - stateDir: 'tests/e2e-pw/storage/', + stateDir: 'tests/e2e-pw/test-results/storage/', trace: 'retain-on-failure', video: 'on-first-retry', viewport: { width: 1280, height: 720 }, From 1d251f217cab2a9f5300d6a1b72b0f4fcf8dcb35 Mon Sep 17 00:00:00 2001 From: Joshua T Flowers Date: Mon, 19 Dec 2022 12:45:43 -0800 Subject: [PATCH 28/70] Extend product variations data store with generate variations actions (#36058) * Allow extending of CRUD data stores * Add generate action to product variations data store * Add controls to storeConfig * Update readme with additional store config args * Move key to catch * Add changelog entry --- .../js/data/changelog/add-35778-data-store | 4 ++ packages/js/data/src/crud/README.md | 8 +++- packages/js/data/src/crud/index.ts | 38 ++++++++++----- .../src/product-variations/action-types.ts | 5 ++ .../js/data/src/product-variations/actions.ts | 46 +++++++++++++++++++ .../js/data/src/product-variations/index.ts | 4 ++ 6 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 packages/js/data/changelog/add-35778-data-store create mode 100644 packages/js/data/src/product-variations/action-types.ts create mode 100644 packages/js/data/src/product-variations/actions.ts diff --git a/packages/js/data/changelog/add-35778-data-store b/packages/js/data/changelog/add-35778-data-store new file mode 100644 index 00000000000..e44e540625d --- /dev/null +++ b/packages/js/data/changelog/add-35778-data-store @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Extend product variations data store with generate variations actions diff --git a/packages/js/data/src/crud/README.md b/packages/js/data/src/crud/README.md index 48e190951cb..a2b29dfb2a1 100644 --- a/packages/js/data/src/crud/README.md +++ b/packages/js/data/src/crud/README.md @@ -18,6 +18,12 @@ createCrudDataStore( { resourceName: 'MyThing', pluralResourceName: 'MyThings', namespace: '/my/rest/namespace', + storeConfig: { + actions: additionalActions, + selectors: additionalSelectors, + resolvers: additionalResolvers, + controls: additionalControls, + } } ); ``` @@ -55,7 +61,7 @@ If the default settings are not adequate for your needs, you can always create y ```js import { createSelectors } from '../crud/selectors'; -import { createResolvers } from '../crud/selectors'; +import { createResolvers } from '../crud/resolvers'; import { createActions } from '../crud/actions'; import { registerStore, combineReducers } from '@wordpress/data'; diff --git a/packages/js/data/src/crud/index.ts b/packages/js/data/src/crud/index.ts index 6e44a1b50b7..6274f94fcad 100644 --- a/packages/js/data/src/crud/index.ts +++ b/packages/js/data/src/crud/index.ts @@ -1,7 +1,7 @@ /** * External dependencies */ -import { registerStore } from '@wordpress/data'; +import { combineReducers, registerStore, StoreConfig } from '@wordpress/data'; import { Reducer } from 'redux'; /** @@ -9,7 +9,7 @@ import { Reducer } from 'redux'; */ import { createSelectors } from './selectors'; import { createDispatchActions } from './actions'; -import controls from '../controls'; +import defaultControls from '../controls'; import { createResolvers } from './resolvers'; import { createReducer, ResourceState } from './reducer'; @@ -18,6 +18,7 @@ type CrudDataStore = { resourceName: string; pluralResourceName: string; namespace: string; + storeConfig?: Partial< StoreConfig< ResourceState > >; }; export const createCrudDataStore = ( { @@ -25,29 +26,44 @@ export const createCrudDataStore = ( { resourceName, namespace, pluralResourceName, + storeConfig = {}, }: CrudDataStore ) => { - const reducer = createReducer(); - const actions = createDispatchActions( { + const crudReducer = createReducer(); + + const crudActions = createDispatchActions( { resourceName, namespace, } ); - const resolvers = createResolvers( { + const crudResolvers = createResolvers( { storeName, resourceName, pluralResourceName, namespace, } ); - const selectors = createSelectors( { + const crudSelectors = createSelectors( { resourceName, pluralResourceName, namespace, } ); + const { + reducer, + actions = {}, + selectors = {}, + resolvers = {}, + controls = {}, + } = storeConfig; + registerStore( storeName, { - reducer: reducer as Reducer< ResourceState >, - actions, - selectors, - resolvers, - controls, + reducer: reducer + ? ( combineReducers( { + crudReducer, + reducer, + } ) as Reducer ) + : ( crudReducer as Reducer< ResourceState > ), + actions: { ...crudActions, ...actions }, + selectors: { ...crudSelectors, ...selectors }, + resolvers: { ...crudResolvers, ...resolvers }, + controls: { ...defaultControls, ...controls }, } ); }; diff --git a/packages/js/data/src/product-variations/action-types.ts b/packages/js/data/src/product-variations/action-types.ts new file mode 100644 index 00000000000..4b6beef6b23 --- /dev/null +++ b/packages/js/data/src/product-variations/action-types.ts @@ -0,0 +1,5 @@ +export enum TYPES { + GENERATE_VARIATIONS_ERROR = 'GENERATE_VARIATIONS_ERROR', +} + +export default TYPES; diff --git a/packages/js/data/src/product-variations/actions.ts b/packages/js/data/src/product-variations/actions.ts new file mode 100644 index 00000000000..28b6fc1b4df --- /dev/null +++ b/packages/js/data/src/product-variations/actions.ts @@ -0,0 +1,46 @@ +/** + * External dependencies + */ +import { apiFetch } from '@wordpress/data-controls'; + +/** + * Internal dependencies + */ +import { getUrlParameters, getRestPath, parseId } from '../crud/utils'; +import TYPES from './action-types'; +import { IdQuery, IdType, Item } from '../crud/types'; +import { WC_PRODUCT_VARIATIONS_NAMESPACE } from './constants'; + +export function generateProductVariationsError( key: IdType, error: unknown ) { + return { + type: TYPES.GENERATE_VARIATIONS_ERROR as const, + key, + error, + errorType: 'GENERATE_VARIATIONS', + }; +} + +export const generateProductVariations = function* ( idQuery: IdQuery ) { + const urlParameters = getUrlParameters( + WC_PRODUCT_VARIATIONS_NAMESPACE, + idQuery + ); + + try { + const result: Item = yield apiFetch( { + path: getRestPath( + `${ WC_PRODUCT_VARIATIONS_NAMESPACE }/generate`, + {}, + urlParameters + ), + method: 'POST', + } ); + + return result; + } catch ( error ) { + const { key } = parseId( idQuery, urlParameters ); + + yield generateProductVariationsError( key, error ); + throw error; + } +}; diff --git a/packages/js/data/src/product-variations/index.ts b/packages/js/data/src/product-variations/index.ts index 4a6a444c202..c9b06818f33 100644 --- a/packages/js/data/src/product-variations/index.ts +++ b/packages/js/data/src/product-variations/index.ts @@ -3,12 +3,16 @@ */ import { STORE_NAME, WC_PRODUCT_VARIATIONS_NAMESPACE } from './constants'; import { createCrudDataStore } from '../crud'; +import * as actions from './actions'; createCrudDataStore( { storeName: STORE_NAME, resourceName: 'ProductVariation', pluralResourceName: 'ProductVariations', namespace: WC_PRODUCT_VARIATIONS_NAMESPACE, + storeConfig: { + actions, + }, } ); export const EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME = STORE_NAME; From 378d5f525e105abf5e9876ae8aa8afd5d08b2951 Mon Sep 17 00:00:00 2001 From: Paul Sealock Date: Tue, 20 Dec 2022 13:53:59 +1300 Subject: [PATCH 29/70] Create Extension: Fix install scripts (#36067) * Update install scripts * changelog --- packages/js/create-woo-extension/README.md.mustache | 2 +- .../changelog/fix-create-woo-ext-install-scripts | 4 ++++ packages/js/create-woo-extension/index.js | 6 +++--- 3 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 packages/js/create-woo-extension/changelog/fix-create-woo-ext-install-scripts diff --git a/packages/js/create-woo-extension/README.md.mustache b/packages/js/create-woo-extension/README.md.mustache index ff8f47f94dd..247a1adb2c7 100644 --- a/packages/js/create-woo-extension/README.md.mustache +++ b/packages/js/create-woo-extension/README.md.mustache @@ -13,7 +13,7 @@ A boilerplate for modern WooCommerce development. This project adds a React page ``` npm install -npm build +npm run build wp-env start ``` diff --git a/packages/js/create-woo-extension/changelog/fix-create-woo-ext-install-scripts b/packages/js/create-woo-extension/changelog/fix-create-woo-ext-install-scripts new file mode 100644 index 00000000000..2615a0dd0a2 --- /dev/null +++ b/packages/js/create-woo-extension/changelog/fix-create-woo-ext-install-scripts @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix install scripts diff --git a/packages/js/create-woo-extension/index.js b/packages/js/create-woo-extension/index.js index b4af0d6a82e..8f8f0c0a5d0 100644 --- a/packages/js/create-woo-extension/index.js +++ b/packages/js/create-woo-extension/index.js @@ -10,8 +10,8 @@ module.exports = { ], namespace: 'extension', license: 'GPL-3.0+', - }, - customScripts: { - postinstall: 'composer install', + customScripts: { + postinstall: 'composer install', + }, }, }; From 35f99dba7d9f60d74f914e5d73844b899cbffc84 Mon Sep 17 00:00:00 2001 From: Dekadinious <37292177+Dekadinious@users.noreply.github.com> Date: Tue, 20 Dec 2022 02:18:57 +0100 Subject: [PATCH 30/70] Update documentation for two methods in abstract-wc-order.php (#34385) * Update abstract-wc-order.php My attempt to make the documentation clearer for these two methods. It's almost impossible to remember all nuances here, so I constantly find myself testing this. Better to have it in the documentation. * Changelog. Co-authored-by: barryhughes <3594411+barryhughes@users.noreply.github.com> --- plugins/woocommerce/changelog/update-order-method-docblocks | 4 ++++ .../woocommerce/includes/abstracts/abstract-wc-order.php | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 plugins/woocommerce/changelog/update-order-method-docblocks diff --git a/plugins/woocommerce/changelog/update-order-method-docblocks b/plugins/woocommerce/changelog/update-order-method-docblocks new file mode 100644 index 00000000000..4cb165f9f7e --- /dev/null +++ b/plugins/woocommerce/changelog/update-order-method-docblocks @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak + +Updates and improves the docblocks for methods WC_Order::get_total() and WC_Order::get_subtotal(). diff --git a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php index 196e4f5a592..78932ad68bc 100644 --- a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php +++ b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php @@ -417,7 +417,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order { } /** - * Gets order grand total. incl. taxes. Used in gateways. + * Gets order grand total including taxes, shipping cost, fees, and coupon discounts. Used in gateways. * * @param string $context View or edit context. * @return float @@ -458,7 +458,9 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order { } /** - * Gets order subtotal. + * Gets order subtotal. Order subtotal is the price of all items excluding taxes, fees, shipping cost, and coupon discounts. + * If sale price is set on an item, the subtotal will include this sale discount. E.g. a product with a regular + * price of $100 bought at a 50% discount will represent $50 of the subtotal for the order. * * @return float */ From 4823a284dcf08bc55028ddded86ebc7f132c877a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 20 Dec 2022 21:33:51 +1300 Subject: [PATCH 31/70] Prepare Packages for Release (#36084) Automated change: Prep @woocommerce/create-woo-extension for release. Co-authored-by: psealock --- packages/js/create-woo-extension/CHANGELOG.md | 8 +++++-- .../fix-create-woo-ext-install-scripts | 4 ---- packages/js/create-woo-extension/package.json | 2 +- pnpm-lock.yaml | 22 +++++++++++++++++-- 4 files changed, 27 insertions(+), 9 deletions(-) delete mode 100644 packages/js/create-woo-extension/changelog/fix-create-woo-ext-install-scripts diff --git a/packages/js/create-woo-extension/CHANGELOG.md b/packages/js/create-woo-extension/CHANGELOG.md index 73d08b13bdb..6fefe003856 100644 --- a/packages/js/create-woo-extension/CHANGELOG.md +++ b/packages/js/create-woo-extension/CHANGELOG.md @@ -1,8 +1,12 @@ -# Changelog +# Changelog This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.0.0](https://www.npmjs.com/package/@woocommerce/create-woo-extension/v/1.0.0) - 2022-12-15 +## [1.0.1](https://www.npmjs.com/package/@woocommerce/create-woo-extension/v/1.0.1) - 2022-12-20 + +- Patch - Fix install scripts [#34385] + +## [1.0.0](https://www.npmjs.com/package/@woocommerce/create-woo-extension/v/1.0.0) - 2022-12-15 - Patch - Add WC validation [#35947] diff --git a/packages/js/create-woo-extension/changelog/fix-create-woo-ext-install-scripts b/packages/js/create-woo-extension/changelog/fix-create-woo-ext-install-scripts deleted file mode 100644 index 2615a0dd0a2..00000000000 --- a/packages/js/create-woo-extension/changelog/fix-create-woo-ext-install-scripts +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -Fix install scripts diff --git a/packages/js/create-woo-extension/package.json b/packages/js/create-woo-extension/package.json index c6d9efe834c..efb49a732d8 100644 --- a/packages/js/create-woo-extension/package.json +++ b/packages/js/create-woo-extension/package.json @@ -1,6 +1,6 @@ { "name": "@woocommerce/create-woo-extension", - "version": "1.0.0", + "version": "1.0.1", "description": "A template to be used with `@wordpress/create-block` to create a WooCommerce extension.", "main": "index.js", "engines": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d4267fefb45..299870a211f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2966,6 +2966,24 @@ packages: - supports-color dev: true + /@babel/helper-define-polyfill-provider/0.3.1_@babel+core@7.16.12: + resolution: {integrity: sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==} + peerDependencies: + '@babel/core': ^7.4.0-0 + dependencies: + '@babel/core': 7.16.12 + '@babel/helper-compilation-targets': 7.19.3_@babel+core@7.16.12 + '@babel/helper-module-imports': 7.18.6 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/traverse': 7.19.3 + debug: 4.3.4 + lodash.debounce: 4.0.8 + resolve: 1.22.1 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + dev: false + /@babel/helper-define-polyfill-provider/0.3.3_@babel+core@7.12.9: resolution: {integrity: sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==} peerDependencies: @@ -19064,7 +19082,7 @@ packages: dependencies: '@babel/compat-data': 7.19.3 '@babel/core': 7.16.12 - '@babel/helper-define-polyfill-provider': 0.3.3_@babel+core@7.16.12 + '@babel/helper-define-polyfill-provider': 0.3.1_@babel+core@7.16.12 semver: 6.3.0 transitivePeerDependencies: - supports-color @@ -39019,7 +39037,7 @@ packages: serialize-javascript: 6.0.0 source-map: 0.6.1 terser: 5.10.0_acorn@8.8.0 - webpack: 5.70.0_webpack-cli@3.3.12 + webpack: 5.70.0 transitivePeerDependencies: - acorn From 91281446e85220a28cb858771a4114bb27232884 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 20 Dec 2022 23:21:53 +1300 Subject: [PATCH 32/70] Update changelog.txt from release 7.2.1 (#36090) Prep trunk post release 7.2.1 Co-authored-by: WooCommerce Bot --- changelog.txt | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/changelog.txt b/changelog.txt index f65998cfc1a..9bc7e601ef4 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,126 @@ == Changelog == += 7.2.1 2022-12-16 = + +**WooCommerce** + +* Update - Include taxes migration in MigrationHelper::migrate_country_states [#35967](https://github.com/woocommerce/woocommerce/pull/35967) + += 7.2.0 2022-12-14 = + +**WooCommerce** + +* Fix - Drop usage of WP 5.9 function in the product quantity selector template. [#36054](https://github.com/woocommerce/woocommerce/pull/36054) +* Fix - Add a data migration for changed New Zealand and Ukraine state codes [#35669](https://github.com/woocommerce/woocommerce/pull/35669) +* Fix - Fix error in onboarding wizard when plugin is activated but includes unexpected output. [#35866](https://github.com/woocommerce/woocommerce/pull/35866) +* Fix - Increased margin so that overflow modal content doesn't clip header [#35780](https://github.com/woocommerce/woocommerce/pull/35780) +* Fix - Added default additional content to emails via filter woocommerce_email_additional_content_. [#35195](https://github.com/woocommerce/woocommerce/pull/35195) +* Fix - Corrects the currency symbol for Libyan Dinar (LYD). [#35395](https://github.com/woocommerce/woocommerce/pull/35395) +* Fix - Fix 'Invalid payment method' error upon double click on Delete button of Payment methods table [#30884](https://github.com/woocommerce/woocommerce/pull/30884) +* Fix - Fix bg color that was not covering the full page [#35476](https://github.com/woocommerce/woocommerce/pull/35476) +* Fix - Fix class name for class FirstDownlaodableProduct [#35383](https://github.com/woocommerce/woocommerce/pull/35383) +* Fix - Fixed "Unsupported operand types" error. [#34327](https://github.com/woocommerce/woocommerce/pull/34327) +* Fix - Fix inconsistent return type of class WC_Shipping_Rate->get_shipping_tax() [#35453](https://github.com/woocommerce/woocommerce/pull/35453) +* Fix - Fix invalid wcadmin_install_plugin_error event props [#35411](https://github.com/woocommerce/woocommerce/pull/35411) +* Fix - Fix JS error when the business step is accessed directly via URL without completing the previous steps [#35045](https://github.com/woocommerce/woocommerce/pull/35045) +* Fix - fix popper position for in-app marketplace tour [#35278](https://github.com/woocommerce/woocommerce/pull/35278) +* Fix - Fix WooCommerce icons not loading in the site editor. [#35532](https://github.com/woocommerce/woocommerce/pull/35532) +* Fix - FQCN for WP_Error in PHPDoc. [#35305](https://github.com/woocommerce/woocommerce/pull/35305) +* Fix - Make the user search metabox for orders show the same information for the loaded user and for search results [#35244](https://github.com/woocommerce/woocommerce/pull/35244) +* Fix - Override filter_meta_data method, since it should be a no-op anyway. [#35192](https://github.com/woocommerce/woocommerce/pull/35192) +* Fix - Remove the direct dependency on `$_POST` when validating checkout data. [#35329](https://github.com/woocommerce/woocommerce/pull/35329) +* Fix - Revert change that auto collapses the product short description field. [#35213](https://github.com/woocommerce/woocommerce/pull/35213) +* Fix - Skip flaky settings API test [#35338](https://github.com/woocommerce/woocommerce/pull/35338) +* Fix - Update Playwright from 1.26.1 to 1.27.1 [#35106](https://github.com/woocommerce/woocommerce/pull/35106) +* Fix - When the minimum and maximum quantity are identical, render the quantity input and set it to disabled. [#34282](https://github.com/woocommerce/woocommerce/pull/34282) +* Add - Add "Empty Trash" functionality to HPOS list table. [#35489](https://github.com/woocommerce/woocommerce/pull/35489) +* Add - Add add attribute modal to the attribute field in the new product management MVP [#34999](https://github.com/woocommerce/woocommerce/pull/34999) +* Add - Add add new option for the category dropdown within the product MVP [#35132](https://github.com/woocommerce/woocommerce/pull/35132) +* Add - Add contextual product more menu [#35447](https://github.com/woocommerce/woocommerce/pull/35447) +* Add - Added a guided tour for WooCommerce Extensions page [#35278](https://github.com/woocommerce/woocommerce/pull/35278) +* Add - Added npm script for Playwright API Core Tests [#35283](https://github.com/woocommerce/woocommerce/pull/35283) +* Add - Added states for Senegal. [#35199](https://github.com/woocommerce/woocommerce/pull/35199) +* Add - Added the "Tour the WooCommerce Marketplace" task to onboarding tasks list [#35278](https://github.com/woocommerce/woocommerce/pull/35278) +* Add - Added Ukrainian subdivisions. [#35493](https://github.com/woocommerce/woocommerce/pull/35493) +* Add - Adding attribute edit modal for new product screen. [#35269](https://github.com/woocommerce/woocommerce/pull/35269) +* Add - Add manual stock management section to product management experience [#35047](https://github.com/woocommerce/woocommerce/pull/35047) +* Add - Add new Category dropdown field to the new Product Management screen. [#34400](https://github.com/woocommerce/woocommerce/pull/34400) +* Add - add new track events for in-app marketplace tour [#35278](https://github.com/woocommerce/woocommerce/pull/35278) +* Add - Add option and modal to create new attribute terms within MVP attribute modal. [#35131](https://github.com/woocommerce/woocommerce/pull/35131) +* Add - Add placeholder to description field [#35286](https://github.com/woocommerce/woocommerce/pull/35286) +* Add - Add playwright api-core-tests for data crud operations [#35347](https://github.com/woocommerce/woocommerce/pull/35347) +* Add - Add playwright api-core-tests for payment gateways crud operations [#35279](https://github.com/woocommerce/woocommerce/pull/35279) +* Add - Add playwright api-core-tests for product reviews crud operations [#35163](https://github.com/woocommerce/woocommerce/pull/35163) +* Add - Add playwright api-core-tests for product variations crud operations [#35355](https://github.com/woocommerce/woocommerce/pull/35355) +* Add - Add playwright api-core-tests for reports crud operations [#35388](https://github.com/woocommerce/woocommerce/pull/35388) +* Add - Add playwright api-core-tests for settingss crud operations [#35253](https://github.com/woocommerce/woocommerce/pull/35253) +* Add - Add playwright api-core-tests for system status crud operations [#35254](https://github.com/woocommerce/woocommerce/pull/35254) +* Add - Add playwright api-core-tests for webhooks crud operations [#35292](https://github.com/woocommerce/woocommerce/pull/35292) +* Add - Add Product description title in old editor for clarification. [#35154](https://github.com/woocommerce/woocommerce/pull/35154) +* Add - Add product inventory advanced section [#35164](https://github.com/woocommerce/woocommerce/pull/35164) +* Add - Add product management description to new product management experience [#34961](https://github.com/woocommerce/woocommerce/pull/34961) +* Add - Add product state badge to product form header [#35460](https://github.com/woocommerce/woocommerce/pull/35460) +* Add - Add product title to header when available [#35431](https://github.com/woocommerce/woocommerce/pull/35431) +* Add - Add scheduled sale support to new product edit page. [#34538](https://github.com/woocommerce/woocommerce/pull/34538) +* Add - Adds new Inbox Note to provide more information about WooCommerce Payments to users who dismiss the WCPay promo but say that they want more information in the exit survey. [#35581](https://github.com/woocommerce/woocommerce/pull/35581) +* Add - Add summary to new product page experience [#35201](https://github.com/woocommerce/woocommerce/pull/35201) +* Add - Include order datastore information in status report. [#35487](https://github.com/woocommerce/woocommerce/pull/35487) +* Add - Make it possible to add custom bulk action handling to the admin order list screen (when HPOS is enabled). [#35442](https://github.com/woocommerce/woocommerce/pull/35442) +* Add - Set In-App Marketplace Tour as completed on tour close [#35278](https://github.com/woocommerce/woocommerce/pull/35278) +* Add - When custom order tables are not authoritative, admin UI requests will be redirected to the matching legacy order screen as appropriate. [#35463](https://github.com/woocommerce/woocommerce/pull/35463) +* Update - Woo Blocks 8.9.2 [#35805](https://github.com/woocommerce/woocommerce/pull/35805) +* Update - Comment: Update WooCommerce Blocks to 8.7.2 [#35101](https://github.com/woocommerce/woocommerce/pull/35101) +* Update - Comment: Update WooCommerce Blocks to 8.7.3 [#35219](https://github.com/woocommerce/woocommerce/pull/35219) +* Update - Comment: Update WooCommerce Blocks to 8.9.1 [#35564](https://github.com/woocommerce/woocommerce/pull/35564) +* Update - CustomOrdersTableController::custom_orders_table_usage_is_enabled returns now false if the HPOS feature is disabled [#35597](https://github.com/woocommerce/woocommerce/pull/35597) +* Update - Disable inventory stock toggle when product stock management is disabled [#35059](https://github.com/woocommerce/woocommerce/pull/35059) +* Update - Improve the loading time of WooCommerce setup widget for large databases [#35334](https://github.com/woocommerce/woocommerce/pull/35334) +* Update - Permit showing a guided tour for WooCommerce Extensions page on desktops only [#35278](https://github.com/woocommerce/woocommerce/pull/35278) +* Update - Remove adding and managing products note [#35319](https://github.com/woocommerce/woocommerce/pull/35319) +* Update - Remove first downloadable product note [#35318](https://github.com/woocommerce/woocommerce/pull/35318) +* Update - Remove InsightFirstProductAndPayment note [#35309](https://github.com/woocommerce/woocommerce/pull/35309) +* Update - Remove insight on first sale note [#35341](https://github.com/woocommerce/woocommerce/pull/35341) +* Update - Remove manage store activity note [#35320](https://github.com/woocommerce/woocommerce/pull/35320) +* Update - Remove Popover.Slot usage and make use of exported SelectControlMenuSlot. [#35353](https://github.com/woocommerce/woocommerce/pull/35353) +* Update - Remove update store details note [#35322](https://github.com/woocommerce/woocommerce/pull/35322) +* Update - Update Array checks in playwright api-core-tests as some of the existing tests would produce false positives [#35462](https://github.com/woocommerce/woocommerce/pull/35462) +* Update - Update playwright api-core-tests for shipping crud operations [#35332](https://github.com/woocommerce/woocommerce/pull/35332) +* Update - Update playwright api-core-tests to execute for both base test environment and base JN environment with WooCommerce [#35522](https://github.com/woocommerce/woocommerce/pull/35522) +* Update - Update products task list UI [#35611](https://github.com/woocommerce/woocommerce/pull/35611) +* Update - Update ShippingLabelBanner add_meta_box action to only trigger on shop_order pages and remove deprecated function call. [#35212](https://github.com/woocommerce/woocommerce/pull/35212) +* Update - Update WooCommerce Blocks to 8.9.0 [#35521](https://github.com/woocommerce/woocommerce/pull/35521) +* Dev - Add variation price shortcut [#34948](https://github.com/woocommerce/woocommerce/pull/34948) +* Dev - Cleanup and deprecate unused Task properties and methods [#35450](https://github.com/woocommerce/woocommerce/pull/35450) +* Dev - Enable Playwright tests on Daily Smoke Test workflow and upload its Allure reports to S3 bucket. [#35114](https://github.com/woocommerce/woocommerce/pull/35114) +* Dev - Move product action buttons to header menu [#35214](https://github.com/woocommerce/woocommerce/pull/35214) +* Dev - Revert the changes introduced in PR #35282 [#35337](https://github.com/woocommerce/woocommerce/pull/35337) +* Dev - Show a dismissible snackbar if the server responds with an error [#35160](https://github.com/woocommerce/woocommerce/pull/35160) +* Dev - Update api-core-tests readme for consistency with new command and updates to other commands too. [#35303](https://github.com/woocommerce/woocommerce/pull/35303) +* Dev - Updated the COT plugin URL now that this feature can be enabled in a different way. [#34990](https://github.com/woocommerce/woocommerce/pull/34990) +* Dev - Update the list of tags for WC plugin on .org [#35573](https://github.com/woocommerce/woocommerce/pull/35573) +* Dev - Update unit test install script for db sockets. [#35152](https://github.com/woocommerce/woocommerce/pull/35152) +* Dev - Use plugins/woocommerce/tests/e2e-pw folder for saving test outputs [#35206](https://github.com/woocommerce/woocommerce/pull/35206) +* Dev - Uses the globa-setup.js to setup permalinks structure [#35282](https://github.com/woocommerce/woocommerce/pull/35282) +* Tweak - Move HPOS hook woocommerce_before_delete_order before deleting order. [#35517](https://github.com/woocommerce/woocommerce/pull/35517) +* Tweak - Adds new filter `woocommerce_get_customer_payment_tokens_limit` to set limit on number of payment methods fetched within the My Account page. [#29850](https://github.com/woocommerce/woocommerce/pull/29850) +* Tweak - Add source parameter for calls to the subscriptions endpoint on WooCommerce.com [#35051](https://github.com/woocommerce/woocommerce/pull/35051) +* Tweak - Fix @version header in form-login.php [#35479](https://github.com/woocommerce/woocommerce/pull/35479) +* Tweak - Move HPOS hook woocommerce_before_delete_order before deleting order. [#35517](https://github.com/woocommerce/woocommerce/pull/35517) +* Tweak - typo fix [#35111](https://github.com/woocommerce/woocommerce/pull/35111) +* Tweak - Unwrap product page input props and pass via getInputProps [#35034](https://github.com/woocommerce/woocommerce/pull/35034) +* Tweak - Updates the currency symbol used for the Azerbaijani manat. [#30605](https://github.com/woocommerce/woocommerce/pull/30605) +* Tweak - Use new Tooltip component instead of EnrichedLabel [#35024](https://github.com/woocommerce/woocommerce/pull/35024) +* Enhancement - Change the product info section title to Product Details [#35255](https://github.com/woocommerce/woocommerce/pull/35255) +* Enhancement - Fix the display of letter descenders in the shipping class dropdown menu [#35258](https://github.com/woocommerce/woocommerce/pull/35258) +* Enhancement - Improve the communication around required and optional [#35266](https://github.com/woocommerce/woocommerce/pull/35266) +* Enhancement - Increase the spacing between the shipping box illustration and the dimensions fields [#35259](https://github.com/woocommerce/woocommerce/pull/35259) +* Enhancement - Optimize query usage in the Onboarding tasks [#35065](https://github.com/woocommerce/woocommerce/pull/35065) +* Enhancement - Remove some placeholder values [#35267](https://github.com/woocommerce/woocommerce/pull/35267) +* Enhancement - Replace the trash can icon in the attribute list [#35133](https://github.com/woocommerce/woocommerce/pull/35133) +* Enhancement - Select the current new added shipping class [#35123](https://github.com/woocommerce/woocommerce/pull/35123) +* Enhancement - Tweaks the PR template for GitHub pull requests [#34597](https://github.com/woocommerce/woocommerce/pull/34597) + + = 7.1.1 2022-12-07 = **WooCommerce** From 843337d248175ddf262684a8638215b240c6c877 Mon Sep 17 00:00:00 2001 From: Sebastian Volland Date: Tue, 20 Dec 2022 12:02:17 +0100 Subject: [PATCH 33/70] Fix docblock type annotations for `$meta_value` parameter. (#33853) Fix docblock type annotations for `meta_value` parameter. --- plugins/woocommerce/changelog/fix-docblock-type-annotation | 4 ++++ plugins/woocommerce/includes/class-wc-post-data.php | 2 +- .../includes/data-stores/class-wc-order-item-data-store.php | 2 +- .../interfaces/class-wc-order-item-data-store-interface.php | 2 +- plugins/woocommerce/includes/wc-deprecated-functions.php | 2 +- plugins/woocommerce/includes/wc-order-item-functions.php | 6 +++--- plugins/woocommerce/includes/wc-user-functions.php | 2 +- 7 files changed, 12 insertions(+), 8 deletions(-) create mode 100644 plugins/woocommerce/changelog/fix-docblock-type-annotation diff --git a/plugins/woocommerce/changelog/fix-docblock-type-annotation b/plugins/woocommerce/changelog/fix-docblock-type-annotation new file mode 100644 index 00000000000..d696a34b2cd --- /dev/null +++ b/plugins/woocommerce/changelog/fix-docblock-type-annotation @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix docblock type annotations for $meta_value. diff --git a/plugins/woocommerce/includes/class-wc-post-data.php b/plugins/woocommerce/includes/class-wc-post-data.php index 084045722a6..b27cfe47db8 100644 --- a/plugins/woocommerce/includes/class-wc-post-data.php +++ b/plugins/woocommerce/includes/class-wc-post-data.php @@ -502,7 +502,7 @@ class WC_Post_Data { * @param int $meta_id Meta ID. * @param int $object_id Object ID. * @param string $meta_key Meta key. - * @param string $meta_value Meta value. + * @param mixed $meta_value Meta value. */ public static function flush_object_meta_cache( $meta_id, $object_id, $meta_key, $meta_value ) { WC_Cache_Helper::invalidate_cache_group( 'object_' . $object_id ); diff --git a/plugins/woocommerce/includes/data-stores/class-wc-order-item-data-store.php b/plugins/woocommerce/includes/data-stores/class-wc-order-item-data-store.php index 46600ac21c0..0f41e9ad347 100644 --- a/plugins/woocommerce/includes/data-stores/class-wc-order-item-data-store.php +++ b/plugins/woocommerce/includes/data-stores/class-wc-order-item-data-store.php @@ -113,7 +113,7 @@ class WC_Order_Item_Data_Store implements WC_Order_Item_Data_Store_Interface { * @since 3.0.0 * @param int $item_id Item ID. * @param string $meta_key Meta key. - * @param string $meta_value (default: ''). + * @param mixed $meta_value (default: ''). * @param bool $delete_all (default: false). * @return bool */ diff --git a/plugins/woocommerce/includes/interfaces/class-wc-order-item-data-store-interface.php b/plugins/woocommerce/includes/interfaces/class-wc-order-item-data-store-interface.php index b23f42727bd..80493094a1c 100644 --- a/plugins/woocommerce/includes/interfaces/class-wc-order-item-data-store-interface.php +++ b/plugins/woocommerce/includes/interfaces/class-wc-order-item-data-store-interface.php @@ -68,7 +68,7 @@ interface WC_Order_Item_Data_Store_Interface { * * @param int $item_id Item ID. * @param string $meta_key Meta key. - * @param string $meta_value Meta value (default: ''). + * @param mixed $meta_value Meta value (default: ''). * @param bool $delete_all Delete all matching entries? (default: false). * @return bool */ diff --git a/plugins/woocommerce/includes/wc-deprecated-functions.php b/plugins/woocommerce/includes/wc-deprecated-functions.php index 71c00b38513..18c83d878da 100644 --- a/plugins/woocommerce/includes/wc-deprecated-functions.php +++ b/plugins/woocommerce/includes/wc-deprecated-functions.php @@ -1098,7 +1098,7 @@ function add_woocommerce_term_meta( $term_id, $meta_key, $meta_value, $unique = * @deprecated 3.6.0 * @param int $term_id Term ID. * @param string $meta_key Meta key. - * @param string $meta_value Meta value (default: ''). + * @param mixed $meta_value Meta value (default: ''). * @param bool $deprecated Deprecated param (default: false). * @return bool */ diff --git a/plugins/woocommerce/includes/wc-order-item-functions.php b/plugins/woocommerce/includes/wc-order-item-functions.php index 5f7c6a1f942..7c6fcee4a7e 100644 --- a/plugins/woocommerce/includes/wc-order-item-functions.php +++ b/plugins/woocommerce/includes/wc-order-item-functions.php @@ -95,7 +95,7 @@ function wc_delete_order_item( $item_id ) { * * @param int $item_id Item ID. * @param string $meta_key Meta key. - * @param string $meta_value Meta value. + * @param mixed $meta_value Meta value. * @param string $prev_value Previous value (default: ''). * * @throws Exception When `WC_Data_Store::load` validation fails. @@ -115,7 +115,7 @@ function wc_update_order_item_meta( $item_id, $meta_key, $meta_value, $prev_valu * * @param int $item_id Item ID. * @param string $meta_key Meta key. - * @param string $meta_value Meta value. + * @param mixed $meta_value Meta value. * @param bool $unique If meta data should be unique (default: false). * * @throws Exception When `WC_Data_Store::load` validation fails. @@ -137,7 +137,7 @@ function wc_add_order_item_meta( $item_id, $meta_key, $meta_value, $unique = fal * * @param int $item_id Item ID. * @param string $meta_key Meta key. - * @param string $meta_value Meta value (default: ''). + * @param mixed $meta_value Meta value (default: ''). * @param bool $delete_all Delete all meta data, defaults to `false`. * * @throws Exception When `WC_Data_Store::load` validation fails. diff --git a/plugins/woocommerce/includes/wc-user-functions.php b/plugins/woocommerce/includes/wc-user-functions.php index 8204fa2394c..6b98a8a8df0 100644 --- a/plugins/woocommerce/includes/wc-user-functions.php +++ b/plugins/woocommerce/includes/wc-user-functions.php @@ -779,7 +779,7 @@ add_action( 'profile_update', 'wc_update_profile_last_update_time', 10, 2 ); * @param int $meta_id ID of the meta object that was changed. * @param int $user_id The user that was updated. * @param string $meta_key Name of the meta key that was changed. - * @param string $_meta_value Value of the meta that was changed. + * @param mixed $_meta_value Value of the meta that was changed. */ function wc_meta_update_last_update_time( $meta_id, $user_id, $meta_key, $_meta_value ) { $keys_to_track = apply_filters( 'woocommerce_user_last_update_fields', array( 'first_name', 'last_name' ) ); From 6a1a7d7e15f488064f872020d42b7a58a2980c38 Mon Sep 17 00:00:00 2001 From: Barry Hughes <3594411+barryhughes@users.noreply.github.com> Date: Tue, 20 Dec 2022 06:08:34 -0800 Subject: [PATCH 34/70] Replace hardcoded table prefix. (#36100) --- plugins/woocommerce/changelog/fix-36099-migration-query | 4 ++++ .../woocommerce/src/Database/Migrations/MigrationHelper.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 plugins/woocommerce/changelog/fix-36099-migration-query diff --git a/plugins/woocommerce/changelog/fix-36099-migration-query b/plugins/woocommerce/changelog/fix-36099-migration-query new file mode 100644 index 00000000000..2177849d334 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-36099-migration-query @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Corrects a hard-coded reference to the WP post meta table within the HPOS Migration Helper, that would fail on some sites. diff --git a/plugins/woocommerce/src/Database/Migrations/MigrationHelper.php b/plugins/woocommerce/src/Database/Migrations/MigrationHelper.php index 5395afa8fa5..45080d9fb36 100644 --- a/plugins/woocommerce/src/Database/Migrations/MigrationHelper.php +++ b/plugins/woocommerce/src/Database/Migrations/MigrationHelper.php @@ -205,7 +205,7 @@ class MigrationHelper { AS states_in_country WHERE (meta_key='_billing_state' OR meta_key='_shipping_state') AND meta_value=%s - AND wp_postmeta.post_id = states_in_country.post_id + AND {$wpdb->postmeta}.post_id = states_in_country.post_id LIMIT %d", $country_code, $old_state, From bea954c2e592e0f38acede3f015fb556b098be60 Mon Sep 17 00:00:00 2001 From: jonathansadowski Date: Tue, 20 Dec 2022 10:51:41 -0600 Subject: [PATCH 35/70] Update code-freeze actions to Monday 22 days before release (#36023) * Update code-freeze actions to Monday 22 days before release * Fix incorrect params in workflow dispatch --- .github/workflows/release-code-freeze.yml | 12 ++++++------ .github/workflows/scripts/release-code-freeze.php | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release-code-freeze.yml b/.github/workflows/release-code-freeze.yml index 2ad6197d74f..17aa826f569 100644 --- a/.github/workflows/release-code-freeze.yml +++ b/.github/workflows/release-code-freeze.yml @@ -1,7 +1,7 @@ name: 'Release: Code freeze' on: schedule: - - cron: '0 16 * * 4' # Run at 1600 UTC on Thursdays. + - cron: '0 23 * * 1' # Run at 2300 UTC on Mondays. workflow_dispatch: inputs: timeOverride: @@ -42,12 +42,12 @@ jobs: $now = strtotime( getenv( 'TIME_OVERRIDE' ) ); } - // Code freeze comes 26 days prior to release day. - $release_time = strtotime( '+26 days', $now ); + // Code freeze comes 22 days prior to release day. + $release_time = strtotime( '+22 days', $now ); $release_day_of_week = date( 'l', $release_time ); $release_day_of_month = (int) date( 'j', $release_time ); - // If 26 days from now isn't the second Tuesday, then it's not code freeze day. + // If 22 days from now isn't the second Tuesday, then it's not code freeze day. if ( 'Tuesday' !== $release_day_of_week || $release_day_of_month < 8 || $release_day_of_month > 14 ) { file_put_contents( getenv( 'GITHUB_OUTPUT' ), "freeze=1\n", FILE_APPEND ); } else { @@ -163,7 +163,7 @@ jobs: workflow_id: 'release-changelog.yml', ref: 'trunk', inputs: { - releaseVersion: "release/${{ needs.maybe-create-next-milestone-and-release-branch.outputs.next_version }}", - releaseBranch: "${{ needs.maybe-create-next-milestone-and-release-branch.outputs.next_version }}" + releaseVersion: "${{ needs.maybe-create-next-milestone-and-release-branch.outputs.release_version }}", + releaseBranch: "${{ needs.maybe-create-next-milestone-and-release-branch.outputs.branch }}" } }) diff --git a/.github/workflows/scripts/release-code-freeze.php b/.github/workflows/scripts/release-code-freeze.php index 210e4f5767b..5990e89c36d 100644 --- a/.github/workflows/scripts/release-code-freeze.php +++ b/.github/workflows/scripts/release-code-freeze.php @@ -23,14 +23,14 @@ function set_output( $name, $value ) { file_put_contents( getenv( 'GITHUB_OUTPUT' ), "{$name}={$value}" . PHP_EOL, FILE_APPEND ); } -// Code freeze comes 26 days prior to release day. -$release_time = strtotime( '+26 days', $now ); +// Code freeze comes 22 days prior to release day. +$release_time = strtotime( '+22 days', $now ); $release_day_of_week = date( 'l', $release_time ); $release_day_of_month = (int) date( 'j', $release_time ); -// If 26 days from now isn't the second Tuesday, then it's not code freeze day. +// If 22 days from now isn't the second Tuesday, then it's not code freeze day. if ( 'Tuesday' !== $release_day_of_week || $release_day_of_month < 8 || $release_day_of_month > 14 ) { - echo 'Info: Today is not the Thursday of the code freeze.' . PHP_EOL; + echo 'Info: Today is not the Monday of the code freeze.' . PHP_EOL; exit( 1 ); } From 92496e357825ac0a0a6ca5c5d5b4f380a507620c Mon Sep 17 00:00:00 2001 From: Joshua T Flowers Date: Tue, 20 Dec 2022 09:03:51 -0800 Subject: [PATCH 36/70] Add product variation General section (#36081) * Add product variation general section * Add changelog entry --- .../products/product-variation-form.tsx | 3 +- .../product-variation-details-section.tsx | 84 +++++++++++++++++++ plugins/woocommerce/changelog/add-35987 | 4 + 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 plugins/woocommerce-admin/client/products/sections/product-variation-details-section.tsx create mode 100644 plugins/woocommerce/changelog/add-35987 diff --git a/plugins/woocommerce-admin/client/products/product-variation-form.tsx b/plugins/woocommerce-admin/client/products/product-variation-form.tsx index dabeefd0d16..25bd6d1619c 100644 --- a/plugins/woocommerce-admin/client/products/product-variation-form.tsx +++ b/plugins/woocommerce-admin/client/products/product-variation-form.tsx @@ -14,6 +14,7 @@ import { ProductFormTab } from './product-form-tab'; import { PricingSection } from './sections/pricing-section'; import { ProductInventorySection } from './sections/product-inventory-section'; import { ProductShippingSection } from './sections/product-shipping-section'; +import { ProductVariationDetailsSection } from './sections/product-variation-details-section'; export const ProductVariationForm: React.FC< { product: PartialProduct; @@ -27,7 +28,7 @@ export const ProductVariationForm: React.FC< { - <>General + diff --git a/plugins/woocommerce-admin/client/products/sections/product-variation-details-section.tsx b/plugins/woocommerce-admin/client/products/sections/product-variation-details-section.tsx new file mode 100644 index 00000000000..d67ede8e144 --- /dev/null +++ b/plugins/woocommerce-admin/client/products/sections/product-variation-details-section.tsx @@ -0,0 +1,84 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { BlockInstance, serialize, parse } from '@wordpress/blocks'; +import { CheckboxControl, Card, CardBody } from '@wordpress/components'; +import { + useFormContext, + __experimentalRichTextEditor as RichTextEditor, + __experimentalTooltip as Tooltip, +} from '@woocommerce/components'; +import { ProductVariation } from '@woocommerce/data'; +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { getCheckboxTracks } from './utils'; +import { ProductSectionLayout } from '../layout/product-section-layout'; + +export const ProductVariationDetailsSection: React.FC = () => { + const { getCheckboxControlProps, values, setValue } = + useFormContext< ProductVariation >(); + + const [ descriptionBlocks, setDescriptionBlocks ] = useState< + BlockInstance[] + >( parse( values.description || '' ) ); + + return ( + + + + + { __( 'Visible to customers', 'woocommerce' ) } + + + } + { ...getCheckboxControlProps( + 'status', + getCheckboxTracks( 'status' ) + ) } + checked={ values.status === 'publish' } + onChange={ () => + setValue( + 'status', + values.status !== 'publish' + ? 'publish' + : 'private' + ) + } + /> + { + setDescriptionBlocks( blocks ); + if ( ! descriptionBlocks.length ) { + return; + } + setValue( 'description', serialize( blocks ) ); + } } + placeholder={ __( + 'Describe this product. What makes it unique? What are its most important features?', + 'woocommerce' + ) } + /> + + + + ); +}; diff --git a/plugins/woocommerce/changelog/add-35987 b/plugins/woocommerce/changelog/add-35987 new file mode 100644 index 00000000000..920ef526d99 --- /dev/null +++ b/plugins/woocommerce/changelog/add-35987 @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add product variation General section From f2a4eed807abe28c3fa5f20c3e906872638fc517 Mon Sep 17 00:00:00 2001 From: Kathy <507025+helgatheviking@users.noreply.github.com> Date: Tue, 20 Dec 2022 11:48:19 -0700 Subject: [PATCH 37/70] Make quantity selector more specific Only select the primary quantity (#36087) * Add changelog * Use the input name when selecting the quantity field. * Changelog tweak. Co-authored-by: Barry Hughes <3594411+barryhughes@users.noreply.github.com> --- .../issues-36086-more-specific-variation-qty-selector | 4 ++++ .../client/legacy/js/frontend/add-to-cart-variation.js | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 plugins/woocommerce/changelog/issues-36086-more-specific-variation-qty-selector diff --git a/plugins/woocommerce/changelog/issues-36086-more-specific-variation-qty-selector b/plugins/woocommerce/changelog/issues-36086-more-specific-variation-qty-selector new file mode 100644 index 00000000000..7a67eec9fa3 --- /dev/null +++ b/plugins/woocommerce/changelog/issues-36086-more-specific-variation-qty-selector @@ -0,0 +1,4 @@ +Significance: patch +Type: enhancement + +Improves handling of the single product page quantity selector, in relation to variable products. diff --git a/plugins/woocommerce/client/legacy/js/frontend/add-to-cart-variation.js b/plugins/woocommerce/client/legacy/js/frontend/add-to-cart-variation.js index eb28659f96e..24e32dbe3a3 100644 --- a/plugins/woocommerce/client/legacy/js/frontend/add-to-cart-variation.js +++ b/plugins/woocommerce/client/legacy/js/frontend/add-to-cart-variation.js @@ -245,7 +245,7 @@ $dimensions = form.$product.find( '.product_dimensions, .woocommerce-product-attributes-item--dimensions .woocommerce-product-attributes-item__value' ), - $qty = form.$singleVariationWrap.find( '.quantity' ), + $qty = form.$singleVariationWrap.find( '.quantity input[name="quantity"]' ), purchasable = true, variation_id = '', template = false, From 60e3adabd9b8a418fdfe31d988a4236c2eaaf57d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maikel=20David=20P=C3=A9rez=20G=C3=B3mez?= Date: Tue, 20 Dec 2022 18:02:25 -0300 Subject: [PATCH 38/70] Add product variation navigation component (#36076) * Add simple navigation component * Add useProductVariationNavigation hook to manage navigation logic * Integrate navigation component and hook in product variation form * Add changelog file * Add comment feedbacks --- .../hooks/use-product-variation-navigation.ts | 56 +++++++++++ .../products/product-variation-form.scss | 25 +++++ .../products/product-variation-form.tsx | 26 +++++- .../products/shared/posts-navigation/index.ts | 2 + .../posts-navigation/posts-navigation.scss | 40 ++++++++ .../posts-navigation/posts-navigation.tsx | 92 +++++++++++++++++++ plugins/woocommerce/changelog/add-35991 | 4 + 7 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 plugins/woocommerce-admin/client/products/hooks/use-product-variation-navigation.ts create mode 100644 plugins/woocommerce-admin/client/products/product-variation-form.scss create mode 100644 plugins/woocommerce-admin/client/products/shared/posts-navigation/index.ts create mode 100644 plugins/woocommerce-admin/client/products/shared/posts-navigation/posts-navigation.scss create mode 100644 plugins/woocommerce-admin/client/products/shared/posts-navigation/posts-navigation.tsx create mode 100644 plugins/woocommerce/changelog/add-35991 diff --git a/plugins/woocommerce-admin/client/products/hooks/use-product-variation-navigation.ts b/plugins/woocommerce-admin/client/products/hooks/use-product-variation-navigation.ts new file mode 100644 index 00000000000..fc2b97e6442 --- /dev/null +++ b/plugins/woocommerce-admin/client/products/hooks/use-product-variation-navigation.ts @@ -0,0 +1,56 @@ +/** + * External dependencies + */ +import { PartialProduct, ProductVariation } from '@woocommerce/data'; +import { getNewPath, getPersistedQuery } from '@woocommerce/navigation'; + +/** + * Internal dependencies + */ +import { PostsNavigationProps } from '../shared/posts-navigation'; + +export default function useProductVariationNavigation( { + product, + productVariation, +}: UseProductVariationNavigationInput ): UseProductVariationNavigationOutput { + const { variations } = product; + const variationIds = variations ?? []; + + const currentIndex = variationIds.indexOf( productVariation.id ?? -1 ); + const canNavigatePrev = currentIndex > 0; + const canNavigateNext = currentIndex < variationIds.length - 1; + const prevVariationId = canNavigatePrev + ? variationIds[ currentIndex - 1 ] + : undefined; + const nextVariationId = canNavigateNext + ? variationIds[ currentIndex + 1 ] + : undefined; + + const persistedQuery = getPersistedQuery(); + + return { + actionHref: getNewPath( persistedQuery, `/product/${ product.id }` ), + prevHref: prevVariationId + ? getNewPath( + persistedQuery, + `/product/${ product.id }/variation/${ prevVariationId }` + ) + : undefined, + nextHref: nextVariationId + ? getNewPath( + persistedQuery, + `/product/${ product.id }/variation/${ nextVariationId }` + ) + : undefined, + }; +} + +export type UseProductVariationNavigationInput = { + product: PartialProduct; + productVariation: Partial< ProductVariation >; +}; + +export type UseProductVariationNavigationOutput = Pick< + PostsNavigationProps, + 'actionHref' | 'prevHref' | 'nextHref' +>; diff --git a/plugins/woocommerce-admin/client/products/product-variation-form.scss b/plugins/woocommerce-admin/client/products/product-variation-form.scss new file mode 100644 index 00000000000..88aceb40971 --- /dev/null +++ b/plugins/woocommerce-admin/client/products/product-variation-form.scss @@ -0,0 +1,25 @@ +.product-variation-form__navigation { + display: flex; + align-items: end; + justify-content: center; + width: 100%; + position: fixed; + bottom: 16px; + left: 0; + right: 0; + padding-left: 160px; + + @include breakpoint('782px-960px') { + padding-left: 18px; + } + + @include breakpoint('<782px') { + padding-left: 0; + bottom: calc(69px + 16px); + } + + .simple-navigation { + position: absolute; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); + } +} diff --git a/plugins/woocommerce-admin/client/products/product-variation-form.tsx b/plugins/woocommerce-admin/client/products/product-variation-form.tsx index 25bd6d1619c..b3bba8eafc8 100644 --- a/plugins/woocommerce-admin/client/products/product-variation-form.tsx +++ b/plugins/woocommerce-admin/client/products/product-variation-form.tsx @@ -1,12 +1,14 @@ /** * External dependencies */ +import { __ } from '@wordpress/i18n'; import { Form } from '@woocommerce/components'; import { PartialProduct, ProductVariation } from '@woocommerce/data'; /** * Internal dependencies */ +import PostsNavigation from './shared/posts-navigation'; import { ProductFormHeader } from './layout/product-form-header'; import { ProductFormLayout } from './layout/product-form-layout'; import { ProductFormFooter } from './layout/product-form-footer'; @@ -15,11 +17,18 @@ import { PricingSection } from './sections/pricing-section'; import { ProductInventorySection } from './sections/product-inventory-section'; import { ProductShippingSection } from './sections/product-shipping-section'; import { ProductVariationDetailsSection } from './sections/product-variation-details-section'; +import useProductVariationNavigation from './hooks/use-product-variation-navigation'; +import './product-variation-form.scss'; export const ProductVariationForm: React.FC< { product: PartialProduct; productVariation: Partial< ProductVariation >; -} > = ( { productVariation } ) => { +} > = ( { product, productVariation } ) => { + const navigationProps = useProductVariationNavigation( { + product, + productVariation, + } ); + return ( > initialValues={ productVariation } @@ -43,6 +52,21 @@ export const ProductVariationForm: React.FC< { + +
+ +
); }; diff --git a/plugins/woocommerce-admin/client/products/shared/posts-navigation/index.ts b/plugins/woocommerce-admin/client/products/shared/posts-navigation/index.ts new file mode 100644 index 00000000000..9c29a211d11 --- /dev/null +++ b/plugins/woocommerce-admin/client/products/shared/posts-navigation/index.ts @@ -0,0 +1,2 @@ +export { default } from './posts-navigation'; +export type { PostsNavigationProps } from './posts-navigation'; diff --git a/plugins/woocommerce-admin/client/products/shared/posts-navigation/posts-navigation.scss b/plugins/woocommerce-admin/client/products/shared/posts-navigation/posts-navigation.scss new file mode 100644 index 00000000000..74e362b0874 --- /dev/null +++ b/plugins/woocommerce-admin/client/products/shared/posts-navigation/posts-navigation.scss @@ -0,0 +1,40 @@ +.posts-navigation { + display: flex; + align-items: center; + background: $white; + border: 1px solid $gray-400; + border-radius: 2px; + width: fit-content; + height: 48px; + + &__action, + &__prev, + &__next { + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + padding: 12px; + text-decoration: none; + height: 100%; + color: $gray-900; + + &:focus { + z-index: 1; + } + + &:disabled, + &[aria-disabled='true'] { + color: $gray-400; + } + } + + &__action { + border-right: 1px solid $gray-400; + border-left: 1px solid $gray-400; + } + + .screen-reader-only { + @include screen-reader-only(); + } +} diff --git a/plugins/woocommerce-admin/client/products/shared/posts-navigation/posts-navigation.tsx b/plugins/woocommerce-admin/client/products/shared/posts-navigation/posts-navigation.tsx new file mode 100644 index 00000000000..23eb54417cd --- /dev/null +++ b/plugins/woocommerce-admin/client/products/shared/posts-navigation/posts-navigation.tsx @@ -0,0 +1,92 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Icon, arrowLeft, arrowRight } from '@wordpress/icons'; +import { Link } from '@woocommerce/components'; +import classNames from 'classnames'; + +/** + * Internal dependencies + */ +import './posts-navigation.scss'; + +export default function PostsNavigation( { + actionLabel, + actionHref, + prevHref, + prevLabel, + nextHref, + nextLabel, + className, + ...props +}: PostsNavigationProps ) { + const prevNavigationProps = { + className: 'posts-navigation__prev', + 'aria-label': prevLabel ?? __( 'Previous post', 'woocommerce' ), + children: , + }; + + const nextNavigationProps = { + className: 'posts-navigation__next', + 'aria-label': nextLabel ?? __( 'Next post', 'woocommerce' ), + children: , + }; + + return ( + + ); +} + +export type PostsNavigationProps = React.DetailedHTMLProps< + React.HTMLAttributes< HTMLElement >, + HTMLElement +> & { + actionLabel: string; + actionHref: string; + prevHref?: string; + prevLabel?: string; + nextHref?: string; + nextLabel?: string; +}; diff --git a/plugins/woocommerce/changelog/add-35991 b/plugins/woocommerce/changelog/add-35991 new file mode 100644 index 00000000000..37c0b771fe4 --- /dev/null +++ b/plugins/woocommerce/changelog/add-35991 @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add product variation navigation component From 400ace67a3599eef7bf149c8005c19fd81727f81 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 21 Dec 2022 11:33:02 +1300 Subject: [PATCH 39/70] Delete changelog files based on PR 36100 (#36106) Delete changelog files for 36100 Co-authored-by: WooCommerce Bot --- plugins/woocommerce/changelog/fix-36099-migration-query | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 plugins/woocommerce/changelog/fix-36099-migration-query diff --git a/plugins/woocommerce/changelog/fix-36099-migration-query b/plugins/woocommerce/changelog/fix-36099-migration-query deleted file mode 100644 index 2177849d334..00000000000 --- a/plugins/woocommerce/changelog/fix-36099-migration-query +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Corrects a hard-coded reference to the WP post meta table within the HPOS Migration Helper, that would fail on some sites. From 4877e4b36e029b8084a6ee467b3d848b4bd29f40 Mon Sep 17 00:00:00 2001 From: Sam Seay Date: Wed, 21 Dec 2022 13:58:10 +1300 Subject: [PATCH 40/70] Support installing live branches from the manifest (#36072) --- plugins/woocommerce-beta-tester/api/api.php | 5 +- .../api/live-branches/install.php | 95 +++++++++++ .../api/live-branches/manifest.php | 17 +- .../changelog/dev-wc-beta-install-plugin | 4 + ...wc-beta-tester-live-branches-installer.php | 159 ++++++++++++++++++ .../live-branches/components/BranchList.tsx | 52 +++++- .../src/live-branches/hooks/live-branches.tsx | 101 +++++++++++ 7 files changed, 418 insertions(+), 15 deletions(-) create mode 100644 plugins/woocommerce-beta-tester/api/live-branches/install.php create mode 100644 plugins/woocommerce-beta-tester/changelog/dev-wc-beta-install-plugin create mode 100644 plugins/woocommerce-beta-tester/includes/class-wc-beta-tester-live-branches-installer.php diff --git a/plugins/woocommerce-beta-tester/api/api.php b/plugins/woocommerce-beta-tester/api/api.php index 54775b66741..aa5438c8f73 100644 --- a/plugins/woocommerce-beta-tester/api/api.php +++ b/plugins/woocommerce-beta-tester/api/api.php @@ -17,8 +17,8 @@ function register_woocommerce_admin_test_helper_rest_route( $route, $callback, $ 'rest_api_init', function() use ( $route, $callback, $additional_options ) { $default_options = array( - 'methods' => 'POST', - 'callback' => $callback, + 'methods' => 'POST', + 'callback' => $callback, 'permission_callback' => function( $request ) { if ( ! wc_rest_check_manager_permissions( 'settings', 'edit' ) ) { return new \WP_Error( @@ -55,3 +55,4 @@ require 'features/features.php'; require 'rest-api-filters/rest-api-filters.php'; require 'rest-api-filters/hook.php'; require 'live-branches/manifest.php'; +require 'live-branches/install.php'; diff --git a/plugins/woocommerce-beta-tester/api/live-branches/install.php b/plugins/woocommerce-beta-tester/api/live-branches/install.php new file mode 100644 index 00000000000..11143154634 --- /dev/null +++ b/plugins/woocommerce-beta-tester/api/live-branches/install.php @@ -0,0 +1,95 @@ + 'POST', + ) +); + +register_woocommerce_admin_test_helper_rest_route( + '/live-branches/deactivate/v1', + 'deactivate_woocommerce', + array( + 'methods' => 'GET', + ) +); + +register_woocommerce_admin_test_helper_rest_route( + '/live-branches/activate/v1', + 'activate_version', + array( + 'methods' => 'POST', + 'permission_callback' => function( $request ) { + // Avoid using WC functions as core will be deactivated during this request. + $user = wp_get_current_user(); + $allowed_roles = array( 'administrator' ); + if ( array_intersect( $allowed_roles, $user->roles ) ) { + return true; + } else { + return new \WP_Error( + 'woocommerce_rest_cannot_edit', + __( 'Sorry, you cannot perform this action', 'woocommerce' ) + ); + } + }, + ) +); + +/** + * Respond to POST request to install a plugin by download url. + * + * @param Object $request - The request parameter. + */ +function install_version( $request ) { + $params = json_decode( $request->get_body() ); + $download_url = $params->download_url; + $pr_name = $params->pr_name; + $version = $params->version; + + $installer = new WC_Beta_Tester_Live_Branches_Installer(); + $result = $installer->install( $download_url, $pr_name, $version ); + + if ( is_wp_error( $result ) ) { + return new WP_Error( 400, "Could not install $pr_name with error {$result->get_error_message()}", '' ); + } else { + return new WP_REST_Response( wp_json_encode( array( 'ok' => true ) ), 200 ); + } +} + +/** + * Respond to POST request to activate a plugin by version. + * + * @param Object $request - The request parameter. + */ +function activate_version( $request ) { + $params = json_decode( $request->get_body() ); + $version = $params->version; + + $installer = new WC_Beta_Tester_Live_Branches_Installer(); + $result = $installer->activate( $version ); + + if ( is_wp_error( $result ) ) { + return new WP_Error( 400, "Could not activate version: $version with error {$result->get_error_message()}", '' ); + } else { + return new WP_REST_Response( wp_json_encode( array( 'ok' => true ) ), 200 ); + } +} + +/** + * Respond to GET request to deactivate WooCommerce. + */ +function deactivate_woocommerce() { + $installer = new WC_Beta_Tester_Live_Branches_Installer(); + $installer->deactivate_woocommerce(); + + return new WP_REST_Response( wp_json_encode( array( 'ok' => true ) ), 200 ); +} diff --git a/plugins/woocommerce-beta-tester/api/live-branches/manifest.php b/plugins/woocommerce-beta-tester/api/live-branches/manifest.php index a49e944c17a..56101d6a85c 100644 --- a/plugins/woocommerce-beta-tester/api/live-branches/manifest.php +++ b/plugins/woocommerce-beta-tester/api/live-branches/manifest.php @@ -1,10 +1,12 @@ -pr as $key => $value ) { + $value->install_status = $installer->check_install_status( $value->version ); + } + + return new WP_REST_Response( $obj, 200 ); } diff --git a/plugins/woocommerce-beta-tester/changelog/dev-wc-beta-install-plugin b/plugins/woocommerce-beta-tester/changelog/dev-wc-beta-install-plugin new file mode 100644 index 00000000000..767e8ba828f --- /dev/null +++ b/plugins/woocommerce-beta-tester/changelog/dev-wc-beta-install-plugin @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add feature to install and activate live branches. diff --git a/plugins/woocommerce-beta-tester/includes/class-wc-beta-tester-live-branches-installer.php b/plugins/woocommerce-beta-tester/includes/class-wc-beta-tester-live-branches-installer.php new file mode 100644 index 00000000000..8d4d6aa6af8 --- /dev/null +++ b/plugins/woocommerce-beta-tester/includes/class-wc-beta-tester-live-branches-installer.php @@ -0,0 +1,159 @@ +file_system = $this->init_filesystem(); + } + + /** + * Initialize the WP_Filesystem API + */ + private function init_filesystem() { + require_once ABSPATH . 'wp-admin/includes/file.php'; + $creds = request_filesystem_credentials( site_url() . '/wp-admin/', '', false, false, array() ); + + if ( ! WP_Filesystem( $creds ) ) { + return new WP_Error( 'fs_api_error', __( 'WooCommerce Beta Tester: No File System access', 'woocommerce-beta-tester' ) ); // @codingStandardsIgnoreLine. + } + + global $wp_filesystem; + + return $wp_filesystem; + } + + /** + * Install a WooCommerce plugin version by download url. + * + * @param string $download_url The download url of the plugin version. + * @param string $pr_name The name of the associated PR. + * @param string $version The version of the plugin. + */ + public function install( $download_url, $pr_name, $version ) { + // Download the plugin. + $tmp_dir = download_url( $download_url ); + + if ( is_wp_error( $tmp_dir ) ) { + return new WP_Error( + 'download_error', + sprintf( __( 'Error Downloading: %1$s - Error: %2$s', 'woocommerce-beta-tester' ), $download_url, $tmp_dir->get_error_message() ) // @codingStandardsIgnoreLine. + ); + } + + // Unzip the plugin. + $plugin_dir = str_replace( ABSPATH, $this->file_system->abspath(), WP_PLUGIN_DIR ); + $plugin_path = $plugin_dir . '/' . LIVE_BRANCH_PLUGIN_PREFIX . "_$version"; + $unzip_path = $plugin_dir . "/woocommerce-$version"; + + $unzip = unzip_file( $tmp_dir, $unzip_path ); + + // The plugin is nested under woocommerce-dev, so we need to move it up one level. + $this->file_system->mkdir( $plugin_path ); + $this->move( $unzip_path . '/woocommerce-dev', $plugin_path ); + + if ( is_wp_error( $unzip ) ) { + return new WP_Error( 'unzip_error', sprintf( __( 'Error Unzipping file: Error: %1$s', 'woocommerce-beta-tester' ), $result->get_error_message() ) ); // @codingStandardsIgnoreLine. + } + + // Delete the downloaded zip file. + unlink( $tmp_dir ); + + return true; + } + + /** + * Move all files from one folder to another. + * + * @param string $from The folder to move files from. + * @param string $to The folder to move files to. + */ + private function move( $from, $to ) { + $files = scandir( $from ); + $oldfolder = "$from/"; + $newfolder = "$to/"; + + foreach ( $files as $fname ) { + if ( '.' !== $fname && '..' !== $fname ) { + $this->file_system->move( $oldfolder . $fname, $newfolder . $fname ); + } + } + } + + /** + * Deactivate all currently active WooCommerce plugins. + */ + public function deactivate_woocommerce() { + // First check is the regular woo plugin active. + if ( is_plugin_active( 'woocommerce/woocommerce.php' ) ) { + deactivate_plugins( 'woocommerce/woocommerce.php' ); + } + + // Check if any beta tester installed plugins are active. + $active_plugins = get_option( 'active_plugins' ); + + $active_woo_plugins = array_filter( + $active_plugins, + function( $plugin ) { + return str_contains( $plugin, LIVE_BRANCH_PLUGIN_PREFIX ); + } + ); + + if ( ! empty( $active_woo_plugins ) ) { + deactivate_plugins( $active_woo_plugins ); + } + } + + /** + * Activate a beta tester installed WooCommerce plugin + * + * @param string $version The version of the plugin to activate. + */ + public function activate( $version ) { + if ( ! is_plugin_active( LIVE_BRANCH_PLUGIN_PREFIX . "_$version/woocommerce.php" ) ) { + activate_plugin( LIVE_BRANCH_PLUGIN_PREFIX . "_$version/woocommerce.php" ); + } + } + + /** + * Check the install status of a plugin version. + * + * @param string $version The version of the plugin to check. + */ + public function check_install_status( $version ) { + $plugin_path = WP_PLUGIN_DIR . '/' . LIVE_BRANCH_PLUGIN_PREFIX . "_$version/woocommerce.php"; + + if ( ! file_exists( $plugin_path ) ) { + return 'not-installed'; + } + + if ( is_plugin_active( LIVE_BRANCH_PLUGIN_PREFIX . "_$version/woocommerce.php" ) ) { + return 'active'; + } + + return 'installed'; + } +} diff --git a/plugins/woocommerce-beta-tester/src/live-branches/components/BranchList.tsx b/plugins/woocommerce-beta-tester/src/live-branches/components/BranchList.tsx index df57fccbac5..be5f242cebb 100644 --- a/plugins/woocommerce-beta-tester/src/live-branches/components/BranchList.tsx +++ b/plugins/woocommerce-beta-tester/src/live-branches/components/BranchList.tsx @@ -7,14 +7,42 @@ import { // @ts-ignore __experimentalItem as Item, Button, + Spinner, } from '@wordpress/components'; +import { useState } from 'react'; /** * Internal dependencies */ -import { Branch } from '../hooks/live-branches'; +import { Branch, useLiveBranchInstall } from '../hooks/live-branches'; const BranchListItem = ( { branch }: { branch: Branch } ) => { + const { isError, isInProgress, installAndActivate, activate, status } = + useLiveBranchInstall( + branch.download_url, + `https://github.com/woocommerce/woocommerce/pull/${ branch.pr }`, + branch.version, + branch.install_status + ); + + const ActionButton = { + 'not-installed': () => ( + + ), + installed: () => ( + + ), + active: () => ( + + ), + }[ status ]; + return (

@@ -29,21 +57,27 @@ const BranchListItem = ( { branch }: { branch: Branch } ) => { { branch.branch }

- + { isError &&

Something Went Wrong!

} + { isInProgress && } + { ! isError && ! isInProgress && }
); }; export const BranchList = ( { branches }: { branches: Branch[] } ) => { + const activeBranch = branches.find( + ( branch ) => branch.install_status === 'active' + ); + + const nonActiveBranches = branches.filter( + ( branch ) => branch.install_status !== 'active' + ); + return ( - { /* @ts-ignore */ } - { branches.map( ( branch ) => ( + { /* Sort the active branch if it exists to the top of the list */ } + { activeBranch && } + { nonActiveBranches.map( ( branch ) => ( ) ) } diff --git a/plugins/woocommerce-beta-tester/src/live-branches/hooks/live-branches.tsx b/plugins/woocommerce-beta-tester/src/live-branches/hooks/live-branches.tsx index 340d1dcdb17..7ea26886c22 100644 --- a/plugins/woocommerce-beta-tester/src/live-branches/hooks/live-branches.tsx +++ b/plugins/woocommerce-beta-tester/src/live-branches/hooks/live-branches.tsx @@ -4,6 +4,8 @@ import { useEffect, useState } from 'react'; // @ts-ignore import { API_NAMESPACE } from '../../features/data/constants'; +type PluginStatus = 'not-installed' | 'installed' | 'active'; + export type Branch = { branch: string; commit: string; @@ -11,6 +13,7 @@ export type Branch = { update_date: string; version: string; pr: number; + install_status: PluginStatus; }; export const useLiveBranchesData = () => { @@ -39,3 +42,101 @@ export const useLiveBranchesData = () => { return { branches, isLoading: loading }; }; + +export const useLiveBranchInstall = ( + downloadUrl: string, + prName: string, + version: string, + status: PluginStatus +) => { + const [ isInProgress, setIsInProgress ] = useState( false ); + const [ isError, setIsError ] = useState( false ); + const [ pluginStatus, setPluginStatus ] = useState( status ); + + const activate = async () => { + setIsInProgress( true ); + + try { + const deactivateResult = await apiFetch< Response >( { + path: `${ API_NAMESPACE }/live-branches/deactivate/v1`, + } ); + + if ( deactivateResult.status >= 400 ) { + throw new Error( 'Could not deactivate' ); + } + + const activateResult = await apiFetch< Response >( { + path: `${ API_NAMESPACE }/live-branches/activate/v1`, + method: 'POST', + body: JSON.stringify( { + version, + } ), + } ); + + if ( activateResult.status >= 400 ) { + throw new Error( 'Could not activate' ); + } + } catch ( e ) { + setIsError( true ); + } + + setPluginStatus( 'active' ); + setIsInProgress( false ); + }; + + const installAndActivate = async () => { + setIsInProgress( true ); + + try { + const installResult = await apiFetch< Response >( { + path: `${ API_NAMESPACE }/live-branches/install/v1`, + method: 'POST', + body: JSON.stringify( { + pr_name: prName, + download_url: downloadUrl, + version, + } ), + } ); + + if ( installResult.status >= 400 ) { + throw new Error( 'Could not install' ); + } + + setPluginStatus( 'installed' ); + + const deactivateResult = await apiFetch< Response >( { + path: `${ API_NAMESPACE }/live-branches/deactivate/v1`, + } ); + + if ( deactivateResult.status >= 400 ) { + throw new Error( 'Could not deactivate' ); + } + + const activateResult = await apiFetch< Response >( { + path: `${ API_NAMESPACE }/live-branches/activate/v1`, + method: 'POST', + body: JSON.stringify( { + version, + } ), + } ); + + if ( activateResult.status >= 400 ) { + throw new Error( 'Could not activate' ); + } + + setPluginStatus( 'active' ); + } catch ( e ) { + setIsError( true ); + } + + setIsInProgress( false ); + }; + + return { + installAndActivate, + activate, + isError, + isInProgress, + status: pluginStatus, + }; +}; From 679fac1bfa3586170ae5ed7274c9570b6fab9b5d Mon Sep 17 00:00:00 2001 From: Barry Hughes <3594411+barryhughes@users.noreply.github.com> Date: Tue, 20 Dec 2022 23:30:46 -0800 Subject: [PATCH 41/70] Re-run the 7.2.1 state migrations when merchants update to 7.2.2. (#36119) The wc_update_721_* functions were not guaranteed to succeed, because of a hardcoded prefix. We also could not depend on the async migration tasks persisting through to the 7.2.2 update. --- .../fix-36096-nz-ua-states-migration | 5 +++++ .../woocommerce/includes/class-wc-install.php | 4 ++++ .../includes/wc-update-functions.php | 21 +++++++++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 plugins/woocommerce/changelog/fix-36096-nz-ua-states-migration diff --git a/plugins/woocommerce/changelog/fix-36096-nz-ua-states-migration b/plugins/woocommerce/changelog/fix-36096-nz-ua-states-migration new file mode 100644 index 00000000000..55ab3f040a9 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-36096-nz-ua-states-migration @@ -0,0 +1,5 @@ +Significance: patch +Type: fix +Comment: A changelog entry was added via https://github.com/woocommerce/woocommerce/pull/36100. This is an amendment to that change, and an additional changelog entry is therefore not required. + + diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index 1f368fd6a08..0460da9bbb9 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -221,6 +221,10 @@ class WC_Install { 'wc_update_721_adjust_new_zealand_states', 'wc_update_721_adjust_ukraine_states', ), + '7.2.2' => array( + 'wc_update_722_adjust_new_zealand_states', + 'wc_update_722_adjust_ukraine_states', + ), ); /** diff --git a/plugins/woocommerce/includes/wc-update-functions.php b/plugins/woocommerce/includes/wc-update-functions.php index 4e2580edc61..7ff5ef67aef 100644 --- a/plugins/woocommerce/includes/wc-update-functions.php +++ b/plugins/woocommerce/includes/wc-update-functions.php @@ -2537,3 +2537,24 @@ function wc_update_721_adjust_ukraine_states() { ); } +/** + * Update the New Zealand state codes in the database after they were updated in code to the CLDR standard. + * + * This is a simple wrapper for the corresponding 7.2.1 update function. The reason we do this (instead of + * reusing the original function directly) is for better traceability in the Action Scheduler log, in case + * of problems. + */ +function wc_update_722_adjust_new_zealand_states() { + return wc_update_721_adjust_new_zealand_states(); +} + +/** + * Update the Ukraine state codes in the database after they were updated in code to the CLDR standard. + * + * This is a simple wrapper for the corresponding 7.2.1 update function. The reason we do this (instead of + * reusing the original function directly) is for better traceability in the Action Scheduler log, in case + * of problems. + */ +function wc_update_722_adjust_ukraine_states() { + return wc_update_721_adjust_ukraine_states(); +} From 309ed6303f5a4d377b0a2cd8e47c8757b261b82e Mon Sep 17 00:00:00 2001 From: louwie17 Date: Wed, 21 Dec 2022 04:11:12 -0400 Subject: [PATCH 42/70] Make sure attribute term dropdown adheres to sort order setting (#36047) * Make sure attribute term dropdown adheres to sort order setting of attribute * Fix spacing * Fix lint errors --- .../changelog/fix-35639_attribute_term_sort_order | 4 ++++ .../client/legacy/js/admin/wc-enhanced-select.js | 1 + .../meta-boxes/views/html-product-attribute.php | 14 +++++++++----- plugins/woocommerce/includes/class-wc-ajax.php | 3 ++- 4 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 plugins/woocommerce/changelog/fix-35639_attribute_term_sort_order diff --git a/plugins/woocommerce/changelog/fix-35639_attribute_term_sort_order b/plugins/woocommerce/changelog/fix-35639_attribute_term_sort_order new file mode 100644 index 00000000000..1ba8b7e0ff0 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-35639_attribute_term_sort_order @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix issue where attribute term dropdown was not adhering to sort order setting. diff --git a/plugins/woocommerce/client/legacy/js/admin/wc-enhanced-select.js b/plugins/woocommerce/client/legacy/js/admin/wc-enhanced-select.js index b77da8e08b1..4d35445642e 100644 --- a/plugins/woocommerce/client/legacy/js/admin/wc-enhanced-select.js +++ b/plugins/woocommerce/client/legacy/js/admin/wc-enhanced-select.js @@ -324,6 +324,7 @@ jQuery( function( $ ) { return { taxonomy: $( this ).data( 'taxonomy' ), limit: $( this ).data( 'limit' ), + orderby: $( this ).data( 'orderby'), term: params.term, action: 'woocommerce_json_search_taxonomy_terms', security: wc_enhanced_select_params.search_taxonomy_terms_nonce diff --git a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-attribute.php b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-attribute.php index bd836803ae4..f63e6444159 100644 --- a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-attribute.php +++ b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-attribute.php @@ -37,13 +37,17 @@ if ( ! defined( 'ABSPATH' ) ) { } if ( 'select' === $attribute_taxonomy->attribute_type ) { + $attribute_orderby = ! empty( $attribute_taxonomy->attribute_orderby ) ? $attribute_taxonomy->attribute_orderby : 'name'; ?> - ! empty( $attribute_taxonomy->attribute_orderby ) ? $attribute_taxonomy->attribute_orderby : 'name', - 'hide_empty' => 0, - ); $selected_terms = $attribute->get_terms(); if ( $selected_terms ) { foreach ( $selected_terms as $selected_term ) { diff --git a/plugins/woocommerce/includes/class-wc-ajax.php b/plugins/woocommerce/includes/class-wc-ajax.php index 9e7b0b5d1d8..375b1d5a674 100644 --- a/plugins/woocommerce/includes/class-wc-ajax.php +++ b/plugins/woocommerce/includes/class-wc-ajax.php @@ -1745,10 +1745,11 @@ class WC_AJAX { $search_text = isset( $_GET['term'] ) ? wc_clean( wp_unslash( $_GET['term'] ) ) : ''; $limit = isset( $_GET['limit'] ) ? absint( wp_unslash( $_GET['limit'] ) ) : null; $taxonomy = isset( $_GET['taxonomy'] ) ? wc_clean( wp_unslash( $_GET['taxonomy'] ) ) : ''; + $orderby = isset( $_GET['orderby'] ) ? wc_clean( wp_unslash( $_GET['orderby'] ) ) : 'name'; $args = array( 'taxonomy' => $taxonomy, - 'orderby' => 'id', + 'orderby' => $orderby, 'order' => 'ASC', 'hide_empty' => false, 'fields' => 'all', From 1e815be51625855c2f9dcefafb9f0f503a560a4c Mon Sep 17 00:00:00 2001 From: louwie17 Date: Wed, 21 Dec 2022 04:11:40 -0400 Subject: [PATCH 43/70] Fix product table dropdown on mobile (#36046) * Only add top when product row is expanded. * Add changelog --- .../changelog/fix-34534_product_dropdown_on_mobile | 4 ++++ plugins/woocommerce/client/legacy/css/admin.scss | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 plugins/woocommerce/changelog/fix-34534_product_dropdown_on_mobile diff --git a/plugins/woocommerce/changelog/fix-34534_product_dropdown_on_mobile b/plugins/woocommerce/changelog/fix-34534_product_dropdown_on_mobile new file mode 100644 index 00000000000..8f61dfe6309 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-34534_product_dropdown_on_mobile @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix product table dropdown issue on mobile. diff --git a/plugins/woocommerce/client/legacy/css/admin.scss b/plugins/woocommerce/client/legacy/css/admin.scss index ef4043efed3..1b20c18b198 100644 --- a/plugins/woocommerce/client/legacy/css/admin.scss +++ b/plugins/woocommerce/client/legacy/css/admin.scss @@ -6741,7 +6741,7 @@ table.bar_chart { overflow: visible; } - .toggle-row { + .is-expanded .toggle-row { top: -28px; } } From c7c032216377d7bb346579b18e735a3a3b79dda6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maikel=20David=20P=C3=A9rez=20G=C3=B3mez?= Date: Wed, 21 Dec 2022 14:47:13 -0300 Subject: [PATCH 44/70] Persist active tab on refresh (#36112) --- .../client/products/layout/product-form-layout.tsx | 11 ++++++++++- plugins/woocommerce/changelog/add-36061 | 4 ++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 plugins/woocommerce/changelog/add-36061 diff --git a/plugins/woocommerce-admin/client/products/layout/product-form-layout.tsx b/plugins/woocommerce-admin/client/products/layout/product-form-layout.tsx index f801958d191..38b4d237cc4 100644 --- a/plugins/woocommerce-admin/client/products/layout/product-form-layout.tsx +++ b/plugins/woocommerce-admin/client/products/layout/product-form-layout.tsx @@ -4,6 +4,7 @@ import { __ } from '@wordpress/i18n'; import { Children, useEffect } from '@wordpress/element'; import { TabPanel, Tooltip } from '@wordpress/components'; +import { navigateTo, getNewPath, getQuery } from '@woocommerce/navigation'; /** * Internal dependencies @@ -14,6 +15,8 @@ import { ProductFormTab } from '../product-form-tab'; export const ProductFormLayout: React.FC< { children: JSX.Element | JSX.Element[]; } > = ( { children } ) => { + const query = getQuery() as Record< string, string >; + useEffect( () => { window.document.body.classList.add( 'woocommerce-admin-product-layout' @@ -63,7 +66,13 @@ export const ProductFormLayout: React.FC< { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore Disabled properties will be included in newer versions of Gutenberg. tabs={ tabs } - onSelect={ () => ( window.document.documentElement.scrollTop = 0 ) } + initialTabName={ query.tab ?? tabs[ 0 ].name } + onSelect={ ( tabName: string ) => { + window.document.documentElement.scrollTop = 0; + navigateTo( { + url: getNewPath( { tab: tabName } ), + } ); + } } > { ( tab ) => ( <> diff --git a/plugins/woocommerce/changelog/add-36061 b/plugins/woocommerce/changelog/add-36061 new file mode 100644 index 00000000000..d8e750c274d --- /dev/null +++ b/plugins/woocommerce/changelog/add-36061 @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Persist active tab on refresh From 1ab7a851cb436b7abf3aa5d5f23ee4a98b30822f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maikel=20David=20P=C3=A9rez=20G=C3=B3mez?= Date: Wed, 21 Dec 2022 15:00:35 -0300 Subject: [PATCH 45/70] Reset variation form if a new variation is given (#36078) --- .../client/products/product-variation-form.tsx | 17 ++++++++++++++++- plugins/woocommerce/changelog/fix-36077 | 4 ++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 plugins/woocommerce/changelog/fix-36077 diff --git a/plugins/woocommerce-admin/client/products/product-variation-form.tsx b/plugins/woocommerce-admin/client/products/product-variation-form.tsx index b3bba8eafc8..c86f1b00c30 100644 --- a/plugins/woocommerce-admin/client/products/product-variation-form.tsx +++ b/plugins/woocommerce-admin/client/products/product-variation-form.tsx @@ -2,7 +2,8 @@ * External dependencies */ import { __ } from '@wordpress/i18n'; -import { Form } from '@woocommerce/components'; +import { useEffect, useRef } from '@wordpress/element'; +import { Form, FormRef } from '@woocommerce/components'; import { PartialProduct, ProductVariation } from '@woocommerce/data'; /** @@ -24,15 +25,29 @@ export const ProductVariationForm: React.FC< { product: PartialProduct; productVariation: Partial< ProductVariation >; } > = ( { product, productVariation } ) => { + const previousVariationIdRef = useRef< number >(); + const formRef = useRef< FormRef< Partial< ProductVariation > > >( null ); + const navigationProps = useProductVariationNavigation( { product, productVariation, } ); + useEffect( () => { + if ( + productVariation && + previousVariationIdRef.current !== productVariation.id + ) { + formRef.current?.resetForm( productVariation ); + previousVariationIdRef.current = productVariation.id; + } + }, [ productVariation ] ); + return ( > initialValues={ productVariation } errors={ {} } + ref={ formRef } > diff --git a/plugins/woocommerce/changelog/fix-36077 b/plugins/woocommerce/changelog/fix-36077 new file mode 100644 index 00000000000..4d38db78f21 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-36077 @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Reset variation form if a new variation is given From 9ee2948954d9025a577f0d402ac4ccc938cb7f31 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 21 Dec 2022 12:41:22 -0600 Subject: [PATCH 46/70] Delete changelog files based on PR 36119 (#36127) Delete changelog files for 36119 Co-authored-by: WooCommerce Bot --- .../woocommerce/changelog/fix-36096-nz-ua-states-migration | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 plugins/woocommerce/changelog/fix-36096-nz-ua-states-migration diff --git a/plugins/woocommerce/changelog/fix-36096-nz-ua-states-migration b/plugins/woocommerce/changelog/fix-36096-nz-ua-states-migration deleted file mode 100644 index 55ab3f040a9..00000000000 --- a/plugins/woocommerce/changelog/fix-36096-nz-ua-states-migration +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: fix -Comment: A changelog entry was added via https://github.com/woocommerce/woocommerce/pull/36100. This is an amendment to that change, and an additional changelog entry is therefore not required. - - From 34ac47969c9dff3d27ff2e85c10c9c9b8d0c671b Mon Sep 17 00:00:00 2001 From: Vedanshu Jain Date: Thu, 22 Dec 2022 09:26:14 +0530 Subject: [PATCH 47/70] Add cherry-pick support for git merge strategy. (#35927) * Add changelog. * Add merge strategy support to cherry-picker tool. --- .github/workflows/cherry-pick.yml | 2 +- plugins/woocommerce/changelog/git-add-cherry-pick-merge | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 plugins/woocommerce/changelog/git-add-cherry-pick-merge diff --git a/.github/workflows/cherry-pick.yml b/.github/workflows/cherry-pick.yml index 3bd236534f0..312ff9d6dcb 100644 --- a/.github/workflows/cherry-pick.yml +++ b/.github/workflows/cherry-pick.yml @@ -154,7 +154,7 @@ jobs: - name: Cherry pick run: | - git cherry-pick ${{ steps.commit-sha.outputs.sha }} + git cherry-pick ${{ steps.commit-sha.outputs.sha }} -m1 - name: Generate changelog id: changelog diff --git a/plugins/woocommerce/changelog/git-add-cherry-pick-merge b/plugins/woocommerce/changelog/git-add-cherry-pick-merge new file mode 100644 index 00000000000..d25742be0f7 --- /dev/null +++ b/plugins/woocommerce/changelog/git-add-cherry-pick-merge @@ -0,0 +1,5 @@ +Significance: patch +Type: fix +Comment: Update the build tool, no code will be shipped to production. + + From a9986fe75eaffc56c54058ee3da6ae415643d573 Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Thu, 22 Dec 2022 09:03:07 -0300 Subject: [PATCH 48/70] Product variations - Set fixed height for card (#36053) * Add className prop to Sortable * Add styles * Add `rows` class * Add changelogs * Remove `row-#` class * Fix min-height * Set task list item min height Co-authored-by: Fernando Marichal Co-authored-by: Joshua T Flowers --- .../changelog/add-35789_set_variations_list_fixed_height | 4 ++++ .../client/products/fields/variations/variations.scss | 7 ++++++- .../changelog/add-35789_set_variations_list_fixed_height | 4 ++++ 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 packages/js/components/changelog/add-35789_set_variations_list_fixed_height create mode 100644 plugins/woocommerce/changelog/add-35789_set_variations_list_fixed_height diff --git a/packages/js/components/changelog/add-35789_set_variations_list_fixed_height b/packages/js/components/changelog/add-35789_set_variations_list_fixed_height new file mode 100644 index 00000000000..6d397019d36 --- /dev/null +++ b/packages/js/components/changelog/add-35789_set_variations_list_fixed_height @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add className prop to Sortable diff --git a/plugins/woocommerce-admin/client/products/fields/variations/variations.scss b/plugins/woocommerce-admin/client/products/fields/variations/variations.scss index 49f9d4827e8..fdc56d59f1c 100644 --- a/plugins/woocommerce-admin/client/products/fields/variations/variations.scss +++ b/plugins/woocommerce-admin/client/products/fields/variations/variations.scss @@ -1,5 +1,9 @@ .woocommerce-product-variations { - min-height: 300px; + ol { + @media ( min-width: #{ ($break-medium) } ) { + min-height: 420px; + } + } display: flex; flex-direction: column; > div { @@ -77,6 +81,7 @@ margin-left: -1px; margin-right: -1px; margin-bottom: -1px; + min-height: 84px; } .woocommerce-sortable { diff --git a/plugins/woocommerce/changelog/add-35789_set_variations_list_fixed_height b/plugins/woocommerce/changelog/add-35789_set_variations_list_fixed_height new file mode 100644 index 00000000000..d2d0a5c3978 --- /dev/null +++ b/plugins/woocommerce/changelog/add-35789_set_variations_list_fixed_height @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Product variations card should have a fixed height. From 60f2ced82ba798e74402b7baabe9ca3bcd60a2d5 Mon Sep 17 00:00:00 2001 From: louwie17 Date: Thu, 22 Dec 2022 10:13:44 -0400 Subject: [PATCH 49/70] Fix no email report on product filter (#35971) * Make sure order total count uses distinct for order ids * Add unit test for order count bug * Add changelog * Fix lint errors * Fix lint errors --- ...ix-32220_no_email_report_on_product_filter | 4 + .../Admin/API/Reports/Orders/DataStore.php | 2 +- .../reports/class-wc-tests-reports-orders.php | 103 ++++++++++++++++++ 3 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 plugins/woocommerce/changelog/fix-32220_no_email_report_on_product_filter diff --git a/plugins/woocommerce/changelog/fix-32220_no_email_report_on_product_filter b/plugins/woocommerce/changelog/fix-32220_no_email_report_on_product_filter new file mode 100644 index 00000000000..85e6ede9681 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-32220_no_email_report_on_product_filter @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Fix total count query of orders within Analytics reports data store. diff --git a/plugins/woocommerce/src/Admin/API/Reports/Orders/DataStore.php b/plugins/woocommerce/src/Admin/API/Reports/Orders/DataStore.php index 6673af7a446..b3bc39a3048 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Orders/DataStore.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Orders/DataStore.php @@ -256,7 +256,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface { $this->add_sql_query_params( $query_args ); /* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */ $db_records_count = (int) $wpdb->get_var( - "SELECT COUNT(*) FROM ( + "SELECT COUNT( DISTINCT tt.order_id ) FROM ( {$this->subquery->get_query_statement()} ) AS tt" ); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-orders.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-orders.php index 91510c13a4e..a20df290975 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-orders.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-orders.php @@ -175,6 +175,109 @@ class WC_Admin_Tests_Reports_Orders extends WC_Unit_Test_Case { $this->assertEquals( $expected, $data_2->data[0]['extended_info']['products'] ); } + /** + * Test that product includes count returns correctly when multiple variations of the same product is added. + */ + public function test_product_and_variation_includes_count() { + global $wpdb; + WC_Helper_Reports::reset_stats_dbs(); + + // Populate all of the data. + $parent_product = new WC_Product_Variable(); + $parent_product->set_name( 'Variable Product' ); + $parent_product->set_regular_price( 25 ); + + $attribute = new WC_Product_Attribute(); + $attribute->set_id( 0 ); + $attribute->set_name( 'pa_color' ); + $attribute->set_options( explode( WC_DELIMITER, 'green | red' ) ); + $attribute->set_visible( false ); + $attribute->set_variation( true ); + $parent_product->set_attributes( array( $attribute ) ); + $parent_product->save(); + + $variation = new WC_Product_Variation(); + $variation->set_name( 'Test Variation - Green' ); + $variation->set_parent_id( $parent_product->get_id() ); + $variation->set_regular_price( 10 ); + $variation->set_attributes( array( 'pa_color' => 'green' ) ); + $variation->set_manage_stock( true ); + $variation->set_stock_quantity( 25 ); + $variation->save(); + + $variation2 = new WC_Product_Variation(); + $variation2->set_name( 'Test Variation - Red' ); + $variation2->set_parent_id( $parent_product->get_id() ); + $variation2->set_regular_price( 10 ); + $variation2->set_attributes( array( 'pa_color' => 'red' ) ); + $variation2->set_manage_stock( true ); + $variation2->set_stock_quantity( 25 ); + $variation2->save(); + + $simple_product = new WC_Product_Simple(); + $simple_product->set_name( 'Simple Product' ); + $simple_product->set_regular_price( 25 ); + $simple_product->save(); + + $order = WC_Helper_Order::create_order( 1, $variation ); + $order2 = WC_Helper_Order::create_order( 1, $simple_product ); + // Add simple product. + $item = new WC_Order_Item_Product(); + $item->set_props( + array( + 'product' => $simple_product, + 'quantity' => 1, + 'subtotal' => wc_get_price_excluding_tax( $simple_product, array( 'qty' => 1 ) ), + 'total' => wc_get_price_excluding_tax( $simple_product, array( 'qty' => 1 ) ), + ) + ); + $item->save(); + $order->add_item( $item ); + $item2 = new WC_Order_Item_Product(); + $item2->set_props( + array( + 'product' => $variation2, + 'quantity' => 2, + 'subtotal' => wc_get_price_excluding_tax( $variation2, array( 'qty' => 1 ) ), + 'total' => wc_get_price_excluding_tax( $variation2, array( 'qty' => 1 ) ), + ) + ); + $item2->save(); + $order->add_item( $item2 ); + // Fix totals. + $order->set_total( 95 ); // ( 6 * 10 ) + 25 + 10 shipping (in helper). + $order->set_status( 'completed' ); + $order->save(); + + $order2->set_total( 45 ); // ( 1 * 10 ) + 25 + 10 shipping (in helper). + $order2->set_status( 'completed' ); + $order2->save(); + + WC_Helper_Queue::run_all_pending(); + + $data_store = new OrdersDataStore(); + $start_time = gmdate( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() ); + $end_time = gmdate( 'Y-m-d H:59:59', $order2->get_date_created()->getOffsetTimestamp() ); + $args = array( + 'after' => $start_time, + 'before' => $end_time, + 'extended_info' => 1, + 'product_includes' => array( $parent_product->get_id() ), + ); + // Test retrieving the stats through the data store. + $data = $data_store->get_data( $args ); + $this->assertEquals( 1, $data->total ); + + $args_variation = array( + 'after' => $start_time, + 'before' => $end_time, + 'variation_includes' => array( $variation->get_id() ), + ); + // Test retrieving the stats through the data store. + $data_variation = $data_store->get_data( $args_variation ); + $this->assertEquals( 1, $data_variation->total ); + } + /** * Test that excluding specific coupons doesn't exclude orders without coupons. * See: https://github.com/woocommerce/woocommerce-admin/issues/6824. From 80ee44c45dec24a8c9cabe0b372401ddc271aa83 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 22 Dec 2022 10:43:58 -0600 Subject: [PATCH 50/70] Update changelog.txt from release 7.2.2 (#36137) * Prep trunk post release 7.2.2 * Update changelog.txt Co-authored-by: WooCommerce Bot Co-authored-by: jonathansadowski --- changelog.txt | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/changelog.txt b/changelog.txt index 9bc7e601ef4..2b78a742a29 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,133 @@ == Changelog == += 7.2.2 2022-12-21 = + +** WooCommerce** + +* Fix - Corrects a hard-coded reference to the WP post meta table within the HPOS Migration Helper, that would fail on some sites. [#36100](https://github.com/woocommerce/woocommerce/pull/36100) + += 7.2.1 2022-12-16 = + +**WooCommerce** + +* Update - Include taxes migration in MigrationHelper::migrate_country_states [#35967](https://github.com/woocommerce/woocommerce/pull/35967) + += 7.2.0 2022-12-14 = + +**WooCommerce** + +* Fix - Corrects a hard-coded reference to the WP post meta table within the HPOS Migration Helper, that would fail on some sites. [#36100](https://github.com/woocommerce/woocommerce/pull/36100) +* Fix - Drop usage of WP 5.9 function in the product quantity selector template. [#36054](https://github.com/woocommerce/woocommerce/pull/36054) +* Fix - Add a data migration for changed New Zealand and Ukraine state codes [#35669](https://github.com/woocommerce/woocommerce/pull/35669) +* Fix - Fix error in onboarding wizard when plugin is activated but includes unexpected output. [#35866](https://github.com/woocommerce/woocommerce/pull/35866) +* Fix - Increased margin so that overflow modal content doesn't clip header [#35780](https://github.com/woocommerce/woocommerce/pull/35780) +* Fix - Added default additional content to emails via filter woocommerce_email_additional_content_. [#35195](https://github.com/woocommerce/woocommerce/pull/35195) +* Fix - Corrects the currency symbol for Libyan Dinar (LYD). [#35395](https://github.com/woocommerce/woocommerce/pull/35395) +* Fix - Fix 'Invalid payment method' error upon double click on Delete button of Payment methods table [#30884](https://github.com/woocommerce/woocommerce/pull/30884) +* Fix - Fix bg color that was not covering the full page [#35476](https://github.com/woocommerce/woocommerce/pull/35476) +* Fix - Fix class name for class FirstDownlaodableProduct [#35383](https://github.com/woocommerce/woocommerce/pull/35383) +* Fix - Fixed "Unsupported operand types" error. [#34327](https://github.com/woocommerce/woocommerce/pull/34327) +* Fix - Fix inconsistent return type of class WC_Shipping_Rate->get_shipping_tax() [#35453](https://github.com/woocommerce/woocommerce/pull/35453) +* Fix - Fix invalid wcadmin_install_plugin_error event props [#35411](https://github.com/woocommerce/woocommerce/pull/35411) +* Fix - Fix JS error when the business step is accessed directly via URL without completing the previous steps [#35045](https://github.com/woocommerce/woocommerce/pull/35045) +* Fix - fix popper position for in-app marketplace tour [#35278](https://github.com/woocommerce/woocommerce/pull/35278) +* Fix - Fix WooCommerce icons not loading in the site editor. [#35532](https://github.com/woocommerce/woocommerce/pull/35532) +* Fix - FQCN for WP_Error in PHPDoc. [#35305](https://github.com/woocommerce/woocommerce/pull/35305) +* Fix - Make the user search metabox for orders show the same information for the loaded user and for search results [#35244](https://github.com/woocommerce/woocommerce/pull/35244) +* Fix - Override filter_meta_data method, since it should be a no-op anyway. [#35192](https://github.com/woocommerce/woocommerce/pull/35192) +* Fix - Remove the direct dependency on `$_POST` when validating checkout data. [#35329](https://github.com/woocommerce/woocommerce/pull/35329) +* Fix - Revert change that auto collapses the product short description field. [#35213](https://github.com/woocommerce/woocommerce/pull/35213) +* Fix - Skip flaky settings API test [#35338](https://github.com/woocommerce/woocommerce/pull/35338) +* Fix - Update Playwright from 1.26.1 to 1.27.1 [#35106](https://github.com/woocommerce/woocommerce/pull/35106) +* Fix - When the minimum and maximum quantity are identical, render the quantity input and set it to disabled. [#34282](https://github.com/woocommerce/woocommerce/pull/34282) +* Add - Add "Empty Trash" functionality to HPOS list table. [#35489](https://github.com/woocommerce/woocommerce/pull/35489) +* Add - Add add attribute modal to the attribute field in the new product management MVP [#34999](https://github.com/woocommerce/woocommerce/pull/34999) +* Add - Add add new option for the category dropdown within the product MVP [#35132](https://github.com/woocommerce/woocommerce/pull/35132) +* Add - Add contextual product more menu [#35447](https://github.com/woocommerce/woocommerce/pull/35447) +* Add - Added a guided tour for WooCommerce Extensions page [#35278](https://github.com/woocommerce/woocommerce/pull/35278) +* Add - Added npm script for Playwright API Core Tests [#35283](https://github.com/woocommerce/woocommerce/pull/35283) +* Add - Added states for Senegal. [#35199](https://github.com/woocommerce/woocommerce/pull/35199) +* Add - Added the "Tour the WooCommerce Marketplace" task to onboarding tasks list [#35278](https://github.com/woocommerce/woocommerce/pull/35278) +* Add - Added Ukrainian subdivisions. [#35493](https://github.com/woocommerce/woocommerce/pull/35493) +* Add - Adding attribute edit modal for new product screen. [#35269](https://github.com/woocommerce/woocommerce/pull/35269) +* Add - Add manual stock management section to product management experience [#35047](https://github.com/woocommerce/woocommerce/pull/35047) +* Add - Add new Category dropdown field to the new Product Management screen. [#34400](https://github.com/woocommerce/woocommerce/pull/34400) +* Add - add new track events for in-app marketplace tour [#35278](https://github.com/woocommerce/woocommerce/pull/35278) +* Add - Add option and modal to create new attribute terms within MVP attribute modal. [#35131](https://github.com/woocommerce/woocommerce/pull/35131) +* Add - Add placeholder to description field [#35286](https://github.com/woocommerce/woocommerce/pull/35286) +* Add - Add playwright api-core-tests for data crud operations [#35347](https://github.com/woocommerce/woocommerce/pull/35347) +* Add - Add playwright api-core-tests for payment gateways crud operations [#35279](https://github.com/woocommerce/woocommerce/pull/35279) +* Add - Add playwright api-core-tests for product reviews crud operations [#35163](https://github.com/woocommerce/woocommerce/pull/35163) +* Add - Add playwright api-core-tests for product variations crud operations [#35355](https://github.com/woocommerce/woocommerce/pull/35355) +* Add - Add playwright api-core-tests for reports crud operations [#35388](https://github.com/woocommerce/woocommerce/pull/35388) +* Add - Add playwright api-core-tests for settingss crud operations [#35253](https://github.com/woocommerce/woocommerce/pull/35253) +* Add - Add playwright api-core-tests for system status crud operations [#35254](https://github.com/woocommerce/woocommerce/pull/35254) +* Add - Add playwright api-core-tests for webhooks crud operations [#35292](https://github.com/woocommerce/woocommerce/pull/35292) +* Add - Add Product description title in old editor for clarification. [#35154](https://github.com/woocommerce/woocommerce/pull/35154) +* Add - Add product inventory advanced section [#35164](https://github.com/woocommerce/woocommerce/pull/35164) +* Add - Add product management description to new product management experience [#34961](https://github.com/woocommerce/woocommerce/pull/34961) +* Add - Add product state badge to product form header [#35460](https://github.com/woocommerce/woocommerce/pull/35460) +* Add - Add product title to header when available [#35431](https://github.com/woocommerce/woocommerce/pull/35431) +* Add - Add scheduled sale support to new product edit page. [#34538](https://github.com/woocommerce/woocommerce/pull/34538) +* Add - Adds new Inbox Note to provide more information about WooCommerce Payments to users who dismiss the WCPay promo but say that they want more information in the exit survey. [#35581](https://github.com/woocommerce/woocommerce/pull/35581) +* Add - Add summary to new product page experience [#35201](https://github.com/woocommerce/woocommerce/pull/35201) +* Add - Include order datastore information in status report. [#35487](https://github.com/woocommerce/woocommerce/pull/35487) +* Add - Make it possible to add custom bulk action handling to the admin order list screen (when HPOS is enabled). [#35442](https://github.com/woocommerce/woocommerce/pull/35442) +* Add - Set In-App Marketplace Tour as completed on tour close [#35278](https://github.com/woocommerce/woocommerce/pull/35278) +* Add - When custom order tables are not authoritative, admin UI requests will be redirected to the matching legacy order screen as appropriate. [#35463](https://github.com/woocommerce/woocommerce/pull/35463) +* Update - Woo Blocks 8.9.2 [#35805](https://github.com/woocommerce/woocommerce/pull/35805) +* Update - Comment: Update WooCommerce Blocks to 8.7.2 [#35101](https://github.com/woocommerce/woocommerce/pull/35101) +* Update - Comment: Update WooCommerce Blocks to 8.7.3 [#35219](https://github.com/woocommerce/woocommerce/pull/35219) +* Update - Comment: Update WooCommerce Blocks to 8.9.1 [#35564](https://github.com/woocommerce/woocommerce/pull/35564) +* Update - CustomOrdersTableController::custom_orders_table_usage_is_enabled returns now false if the HPOS feature is disabled [#35597](https://github.com/woocommerce/woocommerce/pull/35597) +* Update - Disable inventory stock toggle when product stock management is disabled [#35059](https://github.com/woocommerce/woocommerce/pull/35059) +* Update - Improve the loading time of WooCommerce setup widget for large databases [#35334](https://github.com/woocommerce/woocommerce/pull/35334) +* Update - Permit showing a guided tour for WooCommerce Extensions page on desktops only [#35278](https://github.com/woocommerce/woocommerce/pull/35278) +* Update - Remove adding and managing products note [#35319](https://github.com/woocommerce/woocommerce/pull/35319) +* Update - Remove first downloadable product note [#35318](https://github.com/woocommerce/woocommerce/pull/35318) +* Update - Remove InsightFirstProductAndPayment note [#35309](https://github.com/woocommerce/woocommerce/pull/35309) +* Update - Remove insight on first sale note [#35341](https://github.com/woocommerce/woocommerce/pull/35341) +* Update - Remove manage store activity note [#35320](https://github.com/woocommerce/woocommerce/pull/35320) +* Update - Remove Popover.Slot usage and make use of exported SelectControlMenuSlot. [#35353](https://github.com/woocommerce/woocommerce/pull/35353) +* Update - Remove update store details note [#35322](https://github.com/woocommerce/woocommerce/pull/35322) +* Update - Update Array checks in playwright api-core-tests as some of the existing tests would produce false positives [#35462](https://github.com/woocommerce/woocommerce/pull/35462) +* Update - Update playwright api-core-tests for shipping crud operations [#35332](https://github.com/woocommerce/woocommerce/pull/35332) +* Update - Update playwright api-core-tests to execute for both base test environment and base JN environment with WooCommerce [#35522](https://github.com/woocommerce/woocommerce/pull/35522) +* Update - Update products task list UI [#35611](https://github.com/woocommerce/woocommerce/pull/35611) +* Update - Update ShippingLabelBanner add_meta_box action to only trigger on shop_order pages and remove deprecated function call. [#35212](https://github.com/woocommerce/woocommerce/pull/35212) +* Update - Update WooCommerce Blocks to 8.9.0 [#35521](https://github.com/woocommerce/woocommerce/pull/35521) +* Dev - Add variation price shortcut [#34948](https://github.com/woocommerce/woocommerce/pull/34948) +* Dev - Cleanup and deprecate unused Task properties and methods [#35450](https://github.com/woocommerce/woocommerce/pull/35450) +* Dev - Enable Playwright tests on Daily Smoke Test workflow and upload its Allure reports to S3 bucket. [#35114](https://github.com/woocommerce/woocommerce/pull/35114) +* Dev - Move product action buttons to header menu [#35214](https://github.com/woocommerce/woocommerce/pull/35214) +* Dev - Revert the changes introduced in PR #35282 [#35337](https://github.com/woocommerce/woocommerce/pull/35337) +* Dev - Show a dismissible snackbar if the server responds with an error [#35160](https://github.com/woocommerce/woocommerce/pull/35160) +* Dev - Update api-core-tests readme for consistency with new command and updates to other commands too. [#35303](https://github.com/woocommerce/woocommerce/pull/35303) +* Dev - Updated the COT plugin URL now that this feature can be enabled in a different way. [#34990](https://github.com/woocommerce/woocommerce/pull/34990) +* Dev - Update the list of tags for WC plugin on .org [#35573](https://github.com/woocommerce/woocommerce/pull/35573) +* Dev - Update unit test install script for db sockets. [#35152](https://github.com/woocommerce/woocommerce/pull/35152) +* Dev - Use plugins/woocommerce/tests/e2e-pw folder for saving test outputs [#35206](https://github.com/woocommerce/woocommerce/pull/35206) +* Dev - Uses the globa-setup.js to setup permalinks structure [#35282](https://github.com/woocommerce/woocommerce/pull/35282) +* Tweak - Move HPOS hook woocommerce_before_delete_order before deleting order. [#35517](https://github.com/woocommerce/woocommerce/pull/35517) +* Tweak - Adds new filter `woocommerce_get_customer_payment_tokens_limit` to set limit on number of payment methods fetched within the My Account page. [#29850](https://github.com/woocommerce/woocommerce/pull/29850) +* Tweak - Add source parameter for calls to the subscriptions endpoint on WooCommerce.com [#35051](https://github.com/woocommerce/woocommerce/pull/35051) +* Tweak - Fix @version header in form-login.php [#35479](https://github.com/woocommerce/woocommerce/pull/35479) +* Tweak - Move HPOS hook woocommerce_before_delete_order before deleting order. [#35517](https://github.com/woocommerce/woocommerce/pull/35517) +* Tweak - typo fix [#35111](https://github.com/woocommerce/woocommerce/pull/35111) +* Tweak - Unwrap product page input props and pass via getInputProps [#35034](https://github.com/woocommerce/woocommerce/pull/35034) +* Tweak - Updates the currency symbol used for the Azerbaijani manat. [#30605](https://github.com/woocommerce/woocommerce/pull/30605) +* Tweak - Use new Tooltip component instead of EnrichedLabel [#35024](https://github.com/woocommerce/woocommerce/pull/35024) +* Enhancement - Change the product info section title to Product Details [#35255](https://github.com/woocommerce/woocommerce/pull/35255) +* Enhancement - Fix the display of letter descenders in the shipping class dropdown menu [#35258](https://github.com/woocommerce/woocommerce/pull/35258) +* Enhancement - Improve the communication around required and optional [#35266](https://github.com/woocommerce/woocommerce/pull/35266) +* Enhancement - Increase the spacing between the shipping box illustration and the dimensions fields [#35259](https://github.com/woocommerce/woocommerce/pull/35259) +* Enhancement - Optimize query usage in the Onboarding tasks [#35065](https://github.com/woocommerce/woocommerce/pull/35065) +* Enhancement - Remove some placeholder values [#35267](https://github.com/woocommerce/woocommerce/pull/35267) +* Enhancement - Replace the trash can icon in the attribute list [#35133](https://github.com/woocommerce/woocommerce/pull/35133) +* Enhancement - Select the current new added shipping class [#35123](https://github.com/woocommerce/woocommerce/pull/35123) +* Enhancement - Tweaks the PR template for GitHub pull requests [#34597](https://github.com/woocommerce/woocommerce/pull/34597) + + = 7.2.1 2022-12-16 = **WooCommerce** From 4b4fe7c2277be52c87c8a55cf52fe89d8d77d23e Mon Sep 17 00:00:00 2001 From: Joshua T Flowers Date: Thu, 22 Dec 2022 09:23:44 -0800 Subject: [PATCH 51/70] Add product variation title to page header (#36085) * Add method to get product variation title from data * Conditionally add variation to page header * Fix CRUD data store id query on selector * Make getProductVariation calls and data types consistent to prevent multiple calls * Add changelog entries * Update product link type to avoid page refresh * Expose function to truncate title to 32 character limit --- packages/js/data/changelog/add-35990 | 4 + packages/js/data/src/crud/types.ts | 2 +- .../client/products/edit-product-page.tsx | 2 +- .../client/products/product-title.tsx | 109 +++++++++++++++--- .../utils/get-product-variation-title.ts | 44 +++++++ .../utils/test/get-product-variation-title.ts | 90 +++++++++++++++ plugins/woocommerce/changelog/add-35990 | 4 + 7 files changed, 235 insertions(+), 20 deletions(-) create mode 100644 packages/js/data/changelog/add-35990 create mode 100644 plugins/woocommerce-admin/client/products/utils/get-product-variation-title.ts create mode 100644 plugins/woocommerce-admin/client/products/utils/test/get-product-variation-title.ts create mode 100644 plugins/woocommerce/changelog/add-35990 diff --git a/packages/js/data/changelog/add-35990 b/packages/js/data/changelog/add-35990 new file mode 100644 index 00000000000..46e9fc22c40 --- /dev/null +++ b/packages/js/data/changelog/add-35990 @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Update IdQuery type on get item selectors diff --git a/packages/js/data/src/crud/types.ts b/packages/js/data/src/crud/types.ts index 5b5aa46a114..d38ed4296b4 100644 --- a/packages/js/data/src/crud/types.ts +++ b/packages/js/data/src/crud/types.ts @@ -86,7 +86,7 @@ export type CrudSelectors< UpdateError: WPDataSelector< typeof getItemUpdateError >; }, ResourceName, - IdType, + IdQuery, unknown > & MapSelectors< diff --git a/plugins/woocommerce-admin/client/products/edit-product-page.tsx b/plugins/woocommerce-admin/client/products/edit-product-page.tsx index b04f633fb75..b1104679c46 100644 --- a/plugins/woocommerce-admin/client/products/edit-product-page.tsx +++ b/plugins/woocommerce-admin/client/products/edit-product-page.tsx @@ -56,7 +56,7 @@ const EditProductPage: React.FC = () => { isProductVariation && getProductVariation( { id: parseInt( variationId, 10 ), - product_id: productId, + product_id: parseInt( productId, 10 ), } ), isLoading: ! hasProductFinishedResolution( 'getProduct', [ diff --git a/plugins/woocommerce-admin/client/products/product-title.tsx b/plugins/woocommerce-admin/client/products/product-title.tsx index 6ffba5169d4..7a377c657c3 100644 --- a/plugins/woocommerce-admin/client/products/product-title.tsx +++ b/plugins/woocommerce-admin/client/products/product-title.tsx @@ -2,12 +2,14 @@ * External dependencies */ import { __ } from '@wordpress/i18n'; -import { getAdminLink } from '@woocommerce/settings'; import { + EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME, Product, PRODUCTS_STORE_NAME, WCDataSelector, } from '@woocommerce/data'; +import { getAdminLink } from '@woocommerce/settings'; +import { getNewPath } from '@woocommerce/navigation'; import { useFormContext } from '@woocommerce/components'; import { useParams } from 'react-router-dom'; import { useSelect } from '@wordpress/data'; @@ -16,6 +18,10 @@ import { useSelect } from '@wordpress/data'; * Internal dependencies */ import { getProductTitle } from './utils/get-product-title'; +import { + getProductVariationTitle, + getTruncatedProductVariationTitle, +} from './utils/get-product-variation-title'; import { ProductBreadcrumbs } from './product-breadcrumbs'; import { ProductStatusBadge } from './product-status-badge'; import { WooHeaderPageTitle } from '~/header/utils'; @@ -23,35 +29,102 @@ import './product-title.scss'; export const ProductTitle: React.FC = () => { const { values } = useFormContext< Product >(); - const { productId } = useParams(); - const { persistedName } = useSelect( ( select: WCDataSelector ) => { - const product = productId - ? select( PRODUCTS_STORE_NAME ).getProduct( + const { productId, variationId } = useParams(); + const { isLoading, persistedName, productVariation } = useSelect( + ( select: WCDataSelector ) => { + const { + getProduct, + hasFinishedResolution: hasProductFinishedResolution, + } = select( PRODUCTS_STORE_NAME ); + const { + getProductVariation, + hasFinishedResolution: hasProductVariationFinishedResolution, + } = select( EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME ); + const product = productId + ? getProduct( parseInt( productId, 10 ) ) + : null; + const variation = + variationId && productId + ? getProductVariation( { + id: parseInt( variationId, 10 ), + product_id: parseInt( productId, 10 ), + } ) + : null; + + const isProductLoading = + productId && + ! hasProductFinishedResolution( 'getProduct', [ parseInt( productId, 10 ), - undefined - ) - : null; + ] ); - return { - persistedName: product?.name, - }; - } ); + const isVariationLoading = + variationId && + productId && + ! hasProductVariationFinishedResolution( + 'getProductVariation', + [ + { + id: parseInt( variationId, 10 ), + product_id: parseInt( productId, 10 ), + }, + ] + ); - const breadcrumbs = [ + return { + persistedName: product?.name, + productVariation: variation, + isLoading: isProductLoading || isVariationLoading, + }; + } + ); + + const productTitle = getProductTitle( + values.name, + values.type, + persistedName + ); + const productVariationTitle = + productVariation && getProductVariationTitle( productVariation ); + + const pageHierarchy = [ { href: getAdminLink( 'edit.php?post_type=product' ), title: __( 'Products', 'woocommerce' ), }, - ]; - const title = getProductTitle( values.name, values.type, persistedName ); + { + href: getNewPath( {}, '/product/' + productId ), + type: 'wc-admin', + title: ( + <> + { productTitle } + + + ), + }, + productVariationTitle && { + title: ( + + { getTruncatedProductVariationTitle( productVariation ) } + + ), + }, + ].filter( ( page ) => !! page ) as { + href: string; + title: string | JSX.Element; + }[]; + + const current = pageHierarchy.pop(); + + if ( isLoading ) { + return null; + } return ( - + - { title } - + { current?.title } diff --git a/plugins/woocommerce-admin/client/products/utils/get-product-variation-title.ts b/plugins/woocommerce-admin/client/products/utils/get-product-variation-title.ts new file mode 100644 index 00000000000..32fffcec771 --- /dev/null +++ b/plugins/woocommerce-admin/client/products/utils/get-product-variation-title.ts @@ -0,0 +1,44 @@ +/** + * External dependencies + */ +import { ProductVariation } from '@woocommerce/data'; + +export const PRODUCT_VARIATION_TITLE_LIMIT = 32; + +/** + * Get the product variation title for use in the header. + * + * @param productVariation The product variation. + * @return string + */ +export const getProductVariationTitle = ( + productVariation: Partial< ProductVariation > +) => { + if ( ! productVariation?.attributes?.length ) { + return '#' + productVariation.id; + } + + return productVariation.attributes + .map( ( attribute ) => { + return attribute.option; + } ) + .join( ', ' ); +}; + +/** + * Get the truncated product variation title. + * + * @param productVariation The product variation. + * @return string + */ +export const getTruncatedProductVariationTitle = ( + productVariation: Partial< ProductVariation > +) => { + const title = getProductVariationTitle( productVariation ); + + if ( title.length > PRODUCT_VARIATION_TITLE_LIMIT ) { + return title.substring( 0, PRODUCT_VARIATION_TITLE_LIMIT ) + '…'; + } + + return title; +}; diff --git a/plugins/woocommerce-admin/client/products/utils/test/get-product-variation-title.ts b/plugins/woocommerce-admin/client/products/utils/test/get-product-variation-title.ts new file mode 100644 index 00000000000..ebc0f50a158 --- /dev/null +++ b/plugins/woocommerce-admin/client/products/utils/test/get-product-variation-title.ts @@ -0,0 +1,90 @@ +/** + * Internal dependencies + */ +import { + getProductVariationTitle, + getTruncatedProductVariationTitle, +} from '../get-product-variation-title'; + +describe( 'getProductVariationTitle', () => { + it( 'should return the product variation options in a comma separated list', () => { + const title = getProductVariationTitle( { + id: 123, + attributes: [ + { + id: 0, + name: 'Color', + option: 'Red', + }, + { + id: 0, + name: 'Size', + option: 'Medium', + }, + ], + } ); + expect( title ).toBe( 'Red, Medium' ); + } ); + + it( 'should return the only product variation attribute option name', () => { + const title = getProductVariationTitle( { + id: 123, + attributes: [ + { + id: 0, + name: 'Color', + option: 'Blue', + }, + ], + } ); + expect( title ).toBe( 'Blue' ); + } ); + + it( 'should return the product variation id when no attributes exist', () => { + const title = getProductVariationTitle( { + id: 123, + attributes: [], + } ); + expect( title ).toBe( '#123' ); + } ); +} ); + +describe( 'getTruncatedProductVariationTitle', () => { + it( 'should return the default product variation title when the limit is not met', () => { + const truncatedTitle = getTruncatedProductVariationTitle( { + id: 123, + attributes: [ + { + id: 0, + name: 'Color', + option: 'Red', + }, + { + id: 0, + name: 'Size', + option: 'Medium', + }, + ], + } ); + expect( truncatedTitle ).toBe( 'Red, Medium' ); + } ); + + it( 'should return the truncated product title when the limit is reached', () => { + const truncatedTitle = getTruncatedProductVariationTitle( { + id: 123, + attributes: [ + { + id: 0, + name: 'Color', + option: 'Reddish', + }, + { + id: 0, + name: 'Size', + option: 'MediumLargeSmallishTypeOfSize', + }, + ], + } ); + expect( truncatedTitle ).toBe( 'Reddish, MediumLargeSmallishType…' ); + } ); +} ); diff --git a/plugins/woocommerce/changelog/add-35990 b/plugins/woocommerce/changelog/add-35990 new file mode 100644 index 00000000000..04d4847a350 --- /dev/null +++ b/plugins/woocommerce/changelog/add-35990 @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add product variation title to page header From bfa2d4f3a790686c18b99c4cf0f6f6b9e15d6a28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maikel=20David=20P=C3=A9rez=20G=C3=B3mez?= Date: Thu, 22 Dec 2022 16:01:51 -0300 Subject: [PATCH 52/70] Product variation order should be persisted on save (#36109) * Persist product variation order on product save * Add batchUpdate to product variations datastore * Add useVariationOrders hook to manage the ordering logic * Add local ordering logic to variations field * Persist variation orders on product save * Add changelog file * Add comments suggestions * Add more comment seggestions --- packages/js/data/changelog/add-36060 | 4 + .../src/product-variations/action-types.ts | 1 + .../js/data/src/product-variations/actions.ts | 42 +++++++ .../js/data/src/product-variations/types.ts | 13 ++ packages/js/data/src/products/types.ts | 1 + .../products/fields/variations/variations.tsx | 12 +- .../products/hooks/use-variations-order.ts | 97 +++++++++++++++ .../client/products/use-product-helper.ts | 116 ++++++++++++------ plugins/woocommerce/changelog/add-36060 | 4 + 9 files changed, 251 insertions(+), 39 deletions(-) create mode 100644 packages/js/data/changelog/add-36060 create mode 100644 plugins/woocommerce-admin/client/products/hooks/use-variations-order.ts create mode 100644 plugins/woocommerce/changelog/add-36060 diff --git a/packages/js/data/changelog/add-36060 b/packages/js/data/changelog/add-36060 new file mode 100644 index 00000000000..885c83a6fab --- /dev/null +++ b/packages/js/data/changelog/add-36060 @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add batchUpdate to product variations datastore diff --git a/packages/js/data/src/product-variations/action-types.ts b/packages/js/data/src/product-variations/action-types.ts index 4b6beef6b23..89489d516ab 100644 --- a/packages/js/data/src/product-variations/action-types.ts +++ b/packages/js/data/src/product-variations/action-types.ts @@ -1,5 +1,6 @@ export enum TYPES { GENERATE_VARIATIONS_ERROR = 'GENERATE_VARIATIONS_ERROR', + BATCH_UPDATE_VARIATIONS_ERROR = 'BATCH_UPDATE_VARIATIONS_ERROR', } export default TYPES; diff --git a/packages/js/data/src/product-variations/actions.ts b/packages/js/data/src/product-variations/actions.ts index 28b6fc1b4df..613b3864ce9 100644 --- a/packages/js/data/src/product-variations/actions.ts +++ b/packages/js/data/src/product-variations/actions.ts @@ -10,6 +10,7 @@ import { getUrlParameters, getRestPath, parseId } from '../crud/utils'; import TYPES from './action-types'; import { IdQuery, IdType, Item } from '../crud/types'; import { WC_PRODUCT_VARIATIONS_NAMESPACE } from './constants'; +import type { BatchUpdateRequest, BatchUpdateResponse } from './types'; export function generateProductVariationsError( key: IdType, error: unknown ) { return { @@ -44,3 +45,44 @@ export const generateProductVariations = function* ( idQuery: IdQuery ) { throw error; } }; + +export function batchUpdateProductVariationsError( + key: IdType, + error: unknown +) { + return { + type: TYPES.BATCH_UPDATE_VARIATIONS_ERROR as const, + key, + error, + errorType: 'BATCH_UPDATE_VARIATIONS', + }; +} + +export function* batchUpdateProductVariations( + idQuery: IdQuery, + data: BatchUpdateRequest +) { + const urlParameters = getUrlParameters( + WC_PRODUCT_VARIATIONS_NAMESPACE, + idQuery + ); + + try { + const result: BatchUpdateResponse = yield apiFetch( { + path: getRestPath( + `${ WC_PRODUCT_VARIATIONS_NAMESPACE }/batch`, + {}, + urlParameters + ), + method: 'POST', + data, + } ); + + return result; + } catch ( error ) { + const { key } = parseId( idQuery, urlParameters ); + + yield batchUpdateProductVariationsError( key, error ); + throw error; + } +} diff --git a/packages/js/data/src/product-variations/types.ts b/packages/js/data/src/product-variations/types.ts index 2369bce1d8c..efd9df6b37c 100644 --- a/packages/js/data/src/product-variations/types.ts +++ b/packages/js/data/src/product-variations/types.ts @@ -43,3 +43,16 @@ export type ProductVariationSelectors = CrudSelectors< >; export type ActionDispatchers = DispatchFromMap< ProductVariationActions >; + +export type BatchUpdateRequest = { + create?: Partial< Omit< ProductVariation, 'id' > >[]; + update?: ( Pick< ProductVariation, 'id' > & + Partial< Omit< ProductVariation, 'id' > > )[]; + delete?: ProductVariation[ 'id' ][]; +}; + +export type BatchUpdateResponse = { + create?: ProductVariation[]; + update?: ProductVariation[]; + delete?: ProductVariation[]; +}; diff --git a/packages/js/data/src/products/types.ts b/packages/js/data/src/products/types.ts index 7b3f6154634..4050775f183 100644 --- a/packages/js/data/src/products/types.ts +++ b/packages/js/data/src/products/types.ts @@ -69,6 +69,7 @@ export type Product< Status = ProductStatus, Type = ProductType > = Omit< id: number; low_stock_amount: number; manage_stock: boolean; + menu_order: number; name: string; on_sale: boolean; permalink: string; diff --git a/plugins/woocommerce-admin/client/products/fields/variations/variations.tsx b/plugins/woocommerce-admin/client/products/fields/variations/variations.tsx index dccaaee14fc..63b5ba8b5cc 100644 --- a/plugins/woocommerce-admin/client/products/fields/variations/variations.tsx +++ b/plugins/woocommerce-admin/client/products/fields/variations/variations.tsx @@ -16,6 +16,7 @@ import classnames from 'classnames'; /** * Internal dependencies */ +import useVariationsOrder from '~/products/hooks/use-variations-order'; import HiddenIcon from '~/products/images/hidden-icon'; import VisibleIcon from '~/products/images/visible-icon'; import { CurrencyContext } from '../../../lib/currency-context'; @@ -59,6 +60,8 @@ export const Variations: React.FC = () => { product_id: productId, page: currentPage, per_page: perPage, + order: 'asc', + orderby: 'menu_order', }; return { isLoading: ! hasFinishedResolution( 'getProductVariations', [ @@ -77,6 +80,9 @@ export const Variations: React.FC = () => { EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME ); + const { sortedVariations, getVariationKey, onOrderChange } = + useVariationsOrder( { variations, currentPage } ); + if ( ! variations || isLoading ) { return ( @@ -120,9 +126,9 @@ export const Variations: React.FC = () => {

{ __( 'Quantity', 'woocommerce' ) }

- - { variations.map( ( variation ) => ( - + + { sortedVariations.map( ( variation ) => ( +
{ variation.attributes.map( ( attribute ) => ( /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ diff --git a/plugins/woocommerce-admin/client/products/hooks/use-variations-order.ts b/plugins/woocommerce-admin/client/products/hooks/use-variations-order.ts new file mode 100644 index 00000000000..99b2ffc4e37 --- /dev/null +++ b/plugins/woocommerce-admin/client/products/hooks/use-variations-order.ts @@ -0,0 +1,97 @@ +/** + * External dependencies + */ +import { useFormContext } from '@woocommerce/components'; +import type { ProductVariation } from '@woocommerce/data'; + +/** + * Internal dependencies + */ + +const KEY_SEPARATOR = ':'; + +function getVariationKey( variation: ProductVariation ) { + return `${ variation.id }${ KEY_SEPARATOR }${ variation.menu_order }`; +} + +function getVariationId( { key }: JSX.Element ) { + return typeof key === 'string' + ? Number.parseInt( key.split( KEY_SEPARATOR )[ 0 ], 10 ) + : 0; +} + +function getVariationOrder( { key }: JSX.Element ) { + return typeof key === 'string' + ? Number.parseInt( key.split( KEY_SEPARATOR )[ 1 ], 10 ) + : Number.MAX_SAFE_INTEGER; +} + +function sort( + variations: ProductVariation[], + currentPage: number, + { variationsOrder }: ProductVariationsOrder +) { + if ( ! variationsOrder || ! variationsOrder[ currentPage ] ) + return variations; + + const currentPageVariationsOrder = variationsOrder[ currentPage ]; + + return [ ...variations ].sort( ( a, b ) => { + if ( + ! currentPageVariationsOrder[ a.id ] || + ! currentPageVariationsOrder[ b.id ] + ) + return 0; + return ( + currentPageVariationsOrder[ a.id ] - + currentPageVariationsOrder[ b.id ] + ); + } ); +} + +export default function useVariationsOrder( { + variations, + currentPage, +}: UseVariationsOrderInput ): UseVariationsOrderOutput { + const { setValue, values } = useFormContext< ProductVariationsOrder >(); + + function onOrderChange( items: JSX.Element[] ) { + const minOrder = Math.min( ...items.map( getVariationOrder ) ); + + setValue( 'variationsOrder', { + ...values.variationsOrder, + [ currentPage ]: items.reduce( ( prev, item, index ) => { + const id = getVariationId( item ); + return { + ...prev, + [ id ]: minOrder + index, + }; + }, {} ), + } ); + } + + return { + sortedVariations: sort( variations, currentPage, values ), + getVariationKey, + onOrderChange, + }; +} + +export type UseVariationsOrderInput = { + variations: ProductVariation[]; + currentPage: number; +}; + +export type UseVariationsOrderOutput = { + sortedVariations: ProductVariation[]; + getVariationKey( variation: ProductVariation ): string; + onOrderChange( items: JSX.Element[] ): void; +}; + +export type ProductVariationsOrder = { + variationsOrder?: { + [ page: number ]: { + [ variationId: number ]: number; + }; + }; +}; diff --git a/plugins/woocommerce-admin/client/products/use-product-helper.ts b/plugins/woocommerce-admin/client/products/use-product-helper.ts index 89b52441514..c53ef2c7f4e 100644 --- a/plugins/woocommerce-admin/client/products/use-product-helper.ts +++ b/plugins/woocommerce-admin/client/products/use-product-helper.ts @@ -12,6 +12,8 @@ import { PRODUCTS_STORE_NAME, ReadOnlyProperties, productReadOnlyProperties, + EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME, + ProductVariation, } from '@woocommerce/data'; import { recordEvent } from '@woocommerce/tracks'; @@ -23,6 +25,7 @@ import { NUMBERS_AND_DECIMAL_SEPARATOR, ONLY_ONE_DECIMAL_SEPARATOR, } from './constants'; +import { ProductVariationsOrder } from './hooks/use-variations-order'; function removeReadonlyProperties( product: Product @@ -51,6 +54,11 @@ export function useProductHelper() { const { createProduct, updateProduct, deleteProduct } = useDispatch( PRODUCTS_STORE_NAME ) as ProductsStoreActions; + const { + batchUpdateProductVariations, + invalidateResolutionForStoreSelector, + } = useDispatch( EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME ); + const { createNotice } = useDispatch( 'core/notices' ); const [ isDeleting, setIsDeleting ] = useState( false ); const [ updating, setUpdating ] = useState( { @@ -129,6 +137,29 @@ export function useProductHelper() { [ updating ] ); + async function updateVariationsOrder( + productId: number, + variationsOrder?: { [ page: number ]: { [ id: number ]: number } } + ) { + if ( ! variationsOrder ) return undefined; + + return batchUpdateProductVariations< + Promise< { update: ProductVariation[] } > + >( + { + product_id: productId, + }, + { + update: Object.values( variationsOrder ) + .flatMap( Object.entries ) + .map( ( [ id, menu_order ] ) => ( { + id, + menu_order, + } ) ), + } + ); + } + /** * Update product with status. * @@ -152,44 +183,57 @@ export function useProductHelper() { return updateProduct( productId, { ...product, status, - } ).then( - ( updatedProduct ) => { - if ( ! skipNotice ) { - const noticeContent = - product.status === 'draft' && - updatedProduct.status === 'publish' - ? __( 'Product published.', 'woocommerce' ) - : __( - 'Product successfully updated.', - 'woocommerce' - ); - createNotice( 'success', `🎉‎ ${ noticeContent }`, { - actions: getNoticePreviewActions( - updatedProduct.status, - updatedProduct.permalink - ), + } ) + .then( async ( updatedProduct ) => + updateVariationsOrder( + updatedProduct.id, + ( product as ProductVariationsOrder ).variationsOrder + ) + .then( () => + invalidateResolutionForStoreSelector( + 'getProductVariations' + ) + ) + .then( () => updatedProduct ) + ) + .then( + ( updatedProduct ) => { + if ( ! skipNotice ) { + const noticeContent = + product.status === 'draft' && + updatedProduct.status === 'publish' + ? __( 'Product published.', 'woocommerce' ) + : __( + 'Product successfully updated.', + 'woocommerce' + ); + createNotice( 'success', `🎉‎ ${ noticeContent }`, { + actions: getNoticePreviewActions( + updatedProduct.status, + updatedProduct.permalink + ), + } ); + } + setUpdating( { + ...updating, + [ status ]: false, } ); + return updatedProduct; + }, + ( error ) => { + if ( ! skipNotice ) { + createNotice( + 'error', + __( 'Failed to update product.', 'woocommerce' ) + ); + } + setUpdating( { + ...updating, + [ status ]: false, + } ); + return error; } - setUpdating( { - ...updating, - [ status ]: false, - } ); - return updatedProduct; - }, - ( error ) => { - if ( ! skipNotice ) { - createNotice( - 'error', - __( 'Failed to update product.', 'woocommerce' ) - ); - } - setUpdating( { - ...updating, - [ status ]: false, - } ); - return error; - } - ); + ); }, [ updating ] ); diff --git a/plugins/woocommerce/changelog/add-36060 b/plugins/woocommerce/changelog/add-36060 new file mode 100644 index 00000000000..a8cd77e4eb6 --- /dev/null +++ b/plugins/woocommerce/changelog/add-36060 @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Persist variations order on product save From 52640de58ae0b0be5fe0dfe5f9cffee94d33821b Mon Sep 17 00:00:00 2001 From: Moon Date: Thu, 22 Dec 2022 21:30:50 -0800 Subject: [PATCH 53/70] Update country select control regex (#36159) * Update regex to match country name or " - region " * Add changelog * Fix style --- .../dashboard/components/settings/general/store-address.tsx | 5 ++++- .../changelog/update-fix-country-select-control-match | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 plugins/woocommerce/changelog/update-fix-country-select-control-match diff --git a/plugins/woocommerce-admin/client/dashboard/components/settings/general/store-address.tsx b/plugins/woocommerce-admin/client/dashboard/components/settings/general/store-address.tsx index de8c9c40ddd..3d173b9e091 100644 --- a/plugins/woocommerce-admin/client/dashboard/components/settings/general/store-address.tsx +++ b/plugins/woocommerce-admin/client/dashboard/components/settings/general/store-address.tsx @@ -358,7 +358,10 @@ export function StoreAddress( { required autoComplete="new-password" // disable autocomplete and autofill getSearchExpression={ ( query: string ) => { - return new RegExp( '^' + query, 'i' ); + return new RegExp( + '(^' + query + '| — (' + query + '))', + 'i' + ); } } options={ countryStateOptions } excludeSelectedOptions={ false } diff --git a/plugins/woocommerce/changelog/update-fix-country-select-control-match b/plugins/woocommerce/changelog/update-fix-country-select-control-match new file mode 100644 index 00000000000..65c9ab1c64e --- /dev/null +++ b/plugins/woocommerce/changelog/update-fix-country-select-control-match @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Match country name or ' - region' when filtering country select control #36120 \ No newline at end of file From ed6b0c841be114ec1e99740f25ade09e52356d54 Mon Sep 17 00:00:00 2001 From: Luigi Teschio Date: Fri, 23 Dec 2022 19:22:29 +0100 Subject: [PATCH 54/70] Update WooCommerce Blocks to 9.1.3 (#36125) * Update WooCommerce Blocks to 9.1.2 * Update WooCommerce Blocks to 9.1.3 * add results of composer update * use assertNotSame * Pin sebastian/comparator to 3.0.3 * Revert "add results of composer update" This reverts commit 1be3a0fcec80753ee6828c010e6710543f570ec5. Co-authored-by: Paul Sealock --- .../changelog/update-woocommerce-blocks-9.1.2 | 4 + plugins/woocommerce/composer.json | 5 +- plugins/woocommerce/composer.lock | 104 +++++++++--------- .../tests/legacy/unit-tests/crud/data.php | 2 +- 4 files changed, 60 insertions(+), 55 deletions(-) create mode 100644 plugins/woocommerce/changelog/update-woocommerce-blocks-9.1.2 diff --git a/plugins/woocommerce/changelog/update-woocommerce-blocks-9.1.2 b/plugins/woocommerce/changelog/update-woocommerce-blocks-9.1.2 new file mode 100644 index 00000000000..b34dcab7320 --- /dev/null +++ b/plugins/woocommerce/changelog/update-woocommerce-blocks-9.1.2 @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Update WooCommerce Blocks to 9.1.3 diff --git a/plugins/woocommerce/composer.json b/plugins/woocommerce/composer.json index 398e184a04e..d5daec6c988 100644 --- a/plugins/woocommerce/composer.json +++ b/plugins/woocommerce/composer.json @@ -21,13 +21,14 @@ "maxmind-db/reader": "^1.11", "pelago/emogrifier": "^6.0", "woocommerce/action-scheduler": "3.4.2", - "woocommerce/woocommerce-blocks": "9.1.1" + "woocommerce/woocommerce-blocks": "9.1.3" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.4", "yoast/phpunit-polyfills": "^1.0", "phpunit/phpunit": "7.5.20", - "automattic/jetpack-changelogger": "3.1.3" + "automattic/jetpack-changelogger": "3.1.3", + "sebastian/comparator": "3.0.3" }, "config": { "optimize-autoloader": true, diff --git a/plugins/woocommerce/composer.lock b/plugins/woocommerce/composer.lock index dfe797ba686..635c758280d 100644 --- a/plugins/woocommerce/composer.lock +++ b/plugins/woocommerce/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8e9c4d1c5d49505131692dfcbc8f267c", + "content-hash": "7b14c7f0f38737384718c9a013402d48", "packages": [ { "name": "automattic/jetpack-autoloader", @@ -439,16 +439,16 @@ }, { "name": "symfony/css-selector", - "version": "v4.4.37", + "version": "v4.4.44", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "0628e6c6d7c92f1a7bae543959bdc17347be2436" + "reference": "bd0a6737e48de45b4b0b7b6fc98c78404ddceaed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/0628e6c6d7c92f1a7bae543959bdc17347be2436", - "reference": "0628e6c6d7c92f1a7bae543959bdc17347be2436", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/bd0a6737e48de45b4b0b7b6fc98c78404ddceaed", + "reference": "bd0a6737e48de45b4b0b7b6fc98c78404ddceaed", "shasum": "" }, "require": { @@ -485,7 +485,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v4.4.37" + "source": "https://github.com/symfony/css-selector/tree/v4.4.44" }, "funding": [ { @@ -501,20 +501,20 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:41:36+00:00" + "time": "2022-06-27T13:16:42+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", - "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", "shasum": "" }, "require": { @@ -523,7 +523,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -568,7 +568,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" }, "funding": [ { @@ -584,7 +584,7 @@ "type": "tidelift" } ], - "time": "2022-05-10T07:21:04+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "woocommerce/action-scheduler", @@ -628,16 +628,16 @@ }, { "name": "woocommerce/woocommerce-blocks", - "version": "v9.1.1", + "version": "v9.1.3", "source": { "type": "git", "url": "https://github.com/woocommerce/woocommerce-blocks.git", - "reference": "ee40ff5e5e0997d6cc82c6cfeba6ade808fadc96" + "reference": "6e49666a8270f395d15c6b58cd0c5ce111df5ecf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/woocommerce/woocommerce-blocks/zipball/ee40ff5e5e0997d6cc82c6cfeba6ade808fadc96", - "reference": "ee40ff5e5e0997d6cc82c6cfeba6ade808fadc96", + "url": "https://api.github.com/repos/woocommerce/woocommerce-blocks/zipball/6e49666a8270f395d15c6b58cd0c5ce111df5ecf", + "reference": "6e49666a8270f395d15c6b58cd0c5ce111df5ecf", "shasum": "" }, "require": { @@ -683,9 +683,9 @@ ], "support": { "issues": "https://github.com/woocommerce/woocommerce-blocks/issues", - "source": "https://github.com/woocommerce/woocommerce-blocks/tree/v9.1.1" + "source": "https://github.com/woocommerce/woocommerce-blocks/tree/v9.1.3" }, - "time": "2022-12-15T08:39:05+00:00" + "time": "2022-12-22T09:15:52+00:00" } ], "packages-dev": [ @@ -1197,21 +1197,21 @@ }, { "name": "phpspec/prophecy", - "version": "v1.15.0", + "version": "v1.16.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13" + "reference": "be8cac52a0827776ff9ccda8c381ac5b71aeb359" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13", - "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be8cac52a0827776ff9ccda8c381ac5b71aeb359", + "reference": "be8cac52a0827776ff9ccda8c381ac5b71aeb359", "shasum": "" }, "require": { "doctrine/instantiator": "^1.2", - "php": "^7.2 || ~8.0, <8.2", + "php": "^7.2 || 8.0.* || 8.1.* || 8.2.*", "phpdocumentor/reflection-docblock": "^5.2", "sebastian/comparator": "^3.0 || ^4.0", "sebastian/recursion-context": "^3.0 || ^4.0" @@ -1258,9 +1258,9 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/v1.15.0" + "source": "https://github.com/phpspec/prophecy/tree/v1.16.0" }, - "time": "2021-12-08T12:19:24+00:00" + "time": "2022-11-29T15:06:56+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1951,16 +1951,16 @@ }, { "name": "sebastian/exporter", - "version": "3.1.4", + "version": "3.1.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "0c32ea2e40dbf59de29f3b49bf375176ce7dd8db" + "reference": "73a9676f2833b9a7c36968f9d882589cd75511e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0c32ea2e40dbf59de29f3b49bf375176ce7dd8db", - "reference": "0c32ea2e40dbf59de29f3b49bf375176ce7dd8db", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/73a9676f2833b9a7c36968f9d882589cd75511e6", + "reference": "73a9676f2833b9a7c36968f9d882589cd75511e6", "shasum": "" }, "require": { @@ -2016,7 +2016,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.4" + "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.5" }, "funding": [ { @@ -2024,7 +2024,7 @@ "type": "github" } ], - "time": "2021-11-11T13:51:24+00:00" + "time": "2022-09-14T06:00:17+00:00" }, { "name": "sebastian/global-state", @@ -2441,16 +2441,16 @@ }, { "name": "symfony/debug", - "version": "v4.4.41", + "version": "v4.4.44", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "6637e62480b60817b9a6984154a533e8e64c6bd5" + "reference": "1a692492190773c5310bc7877cb590c04c2f05be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/6637e62480b60817b9a6984154a533e8e64c6bd5", - "reference": "6637e62480b60817b9a6984154a533e8e64c6bd5", + "url": "https://api.github.com/repos/symfony/debug/zipball/1a692492190773c5310bc7877cb590c04c2f05be", + "reference": "1a692492190773c5310bc7877cb590c04c2f05be", "shasum": "" }, "require": { @@ -2489,7 +2489,7 @@ "description": "Provides tools to ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/debug/tree/v4.4.41" + "source": "https://github.com/symfony/debug/tree/v4.4.44" }, "funding": [ { @@ -2506,20 +2506,20 @@ } ], "abandoned": "symfony/error-handler", - "time": "2022-04-12T15:19:55+00:00" + "time": "2022-07-28T16:29:46+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", - "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", "shasum": "" }, "require": { @@ -2534,7 +2534,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -2573,7 +2573,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" }, "funding": [ { @@ -2589,7 +2589,7 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/process", @@ -2817,16 +2817,16 @@ }, { "name": "yoast/phpunit-polyfills", - "version": "1.0.3", + "version": "1.0.4", "source": { "type": "git", "url": "https://github.com/Yoast/PHPUnit-Polyfills.git", - "reference": "5ea3536428944955f969bc764bbe09738e151ada" + "reference": "3c621ff5429d2b1ff96dc5808ad6cde99d31ea4c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/5ea3536428944955f969bc764bbe09738e151ada", - "reference": "5ea3536428944955f969bc764bbe09738e151ada", + "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/3c621ff5429d2b1ff96dc5808ad6cde99d31ea4c", + "reference": "3c621ff5429d2b1ff96dc5808ad6cde99d31ea4c", "shasum": "" }, "require": { @@ -2834,7 +2834,7 @@ "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0" }, "require-dev": { - "yoast/yoastcs": "^2.2.0" + "yoast/yoastcs": "^2.2.1" }, "type": "library", "extra": { @@ -2874,7 +2874,7 @@ "issues": "https://github.com/Yoast/PHPUnit-Polyfills/issues", "source": "https://github.com/Yoast/PHPUnit-Polyfills" }, - "time": "2021-11-23T01:37:03+00:00" + "time": "2022-11-16T09:07:52+00:00" } ], "aliases": [], diff --git a/plugins/woocommerce/tests/legacy/unit-tests/crud/data.php b/plugins/woocommerce/tests/legacy/unit-tests/crud/data.php index a9d56645f95..e4a4c4ce33b 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/crud/data.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/crud/data.php @@ -86,7 +86,7 @@ class WC_Tests_CRUD_Data extends WC_Unit_Test_Case { $result = $object->set_props( $data_to_set ); $this->assertTrue( is_wp_error( $result ) ); $this->assertEquals( 'I am also a fish', $object->get_content() ); - $this->assertNotEquals( 'thisisinvalid', $object->get_bool_value() ); + $this->assertNotSame( 'thisisinvalid', $object->get_bool_value() ); } /** From dd94bb78eed4abc73e1c96c917a7e5843eb7e28a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maikel=20David=20P=C3=A9rez=20G=C3=B3mez?= Date: Fri, 23 Dec 2022 15:28:44 -0300 Subject: [PATCH 55/70] Add product variation image (#36133) * Convert getCheckboxTracks into generic function because of a type mismatch * Add image to product variation and export types * Add single image field * Integrate SingleImageField in variation details section * Add changelog file * Add comment suggestions * Fix set image onFileUploadChange --- packages/js/data/changelog/add-36115 | 4 + packages/js/data/src/index.ts | 6 +- .../js/data/src/product-variations/types.ts | 44 ++++++- .../fields/single-image-field/index.ts | 1 + .../single-image-field.scss | 28 +++++ .../single-image-field/single-image-field.tsx | 107 ++++++++++++++++++ .../product-variation-details-section.tsx | 56 ++++++++- .../client/products/sections/utils.ts | 12 +- plugins/woocommerce/changelog/add-36115 | 4 + 9 files changed, 249 insertions(+), 13 deletions(-) create mode 100644 packages/js/data/changelog/add-36115 create mode 100644 plugins/woocommerce-admin/client/products/fields/single-image-field/index.ts create mode 100644 plugins/woocommerce-admin/client/products/fields/single-image-field/single-image-field.scss create mode 100644 plugins/woocommerce-admin/client/products/fields/single-image-field/single-image-field.tsx create mode 100644 plugins/woocommerce/changelog/add-36115 diff --git a/packages/js/data/changelog/add-36115 b/packages/js/data/changelog/add-36115 new file mode 100644 index 00000000000..08103beb8f0 --- /dev/null +++ b/packages/js/data/changelog/add-36115 @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add image to product variation and export types diff --git a/packages/js/data/src/index.ts b/packages/js/data/src/index.ts index f386b083731..8805b6dc794 100644 --- a/packages/js/data/src/index.ts +++ b/packages/js/data/src/index.ts @@ -77,7 +77,11 @@ export * from './countries/types'; export * from './onboarding/types'; export * from './plugins/types'; export * from './products/types'; -export { ProductVariation } from './product-variations/types'; +export type { + ProductVariation, + ProductVariationAttribute, + ProductVariationImage, +} from './product-variations/types'; export { QueryProductAttribute, ProductAttributeSelectors, diff --git a/packages/js/data/src/product-variations/types.ts b/packages/js/data/src/product-variations/types.ts index efd9df6b37c..5275ec24961 100644 --- a/packages/js/data/src/product-variations/types.ts +++ b/packages/js/data/src/product-variations/types.ts @@ -15,11 +15,53 @@ export type ProductVariationAttribute = { option: string; }; +/** + * Product variation - Image properties + */ +export interface ProductVariationImage { + /** + * Image ID. + */ + id: number; + /** + * The date the image was created, in the site's timezone. + */ + readonly date_created: string; + /** + * The date the image was created, as GMT. + */ + readonly date_created_gmt: string; + /** + * The date the image was last modified, in the site's timezone. + */ + readonly date_modified: string; + /** + * The date the image was last modified, as GMT. + */ + readonly date_modified_gmt: string; + /** + * Image URL. + */ + src: string; + /** + * Image name. + */ + name: string; + /** + * Image alternative text. + */ + alt: string; +} + export type ProductVariation = Omit< Product, - 'name' | 'slug' | 'attributes' + 'name' | 'slug' | 'attributes' | 'images' > & { attributes: ProductVariationAttribute[]; + /** + * Variation image data. + */ + image?: ProductVariationImage; }; type Query = Omit< ProductQuery, 'name' >; diff --git a/plugins/woocommerce-admin/client/products/fields/single-image-field/index.ts b/plugins/woocommerce-admin/client/products/fields/single-image-field/index.ts new file mode 100644 index 00000000000..b3430e285c3 --- /dev/null +++ b/plugins/woocommerce-admin/client/products/fields/single-image-field/index.ts @@ -0,0 +1 @@ +export * from './single-image-field'; diff --git a/plugins/woocommerce-admin/client/products/fields/single-image-field/single-image-field.scss b/plugins/woocommerce-admin/client/products/fields/single-image-field/single-image-field.scss new file mode 100644 index 00000000000..75eecca08fa --- /dev/null +++ b/plugins/woocommerce-admin/client/products/fields/single-image-field/single-image-field.scss @@ -0,0 +1,28 @@ +.woocommerce-single-image-field { + &__gallery { + margin-top: $gap-smaller; + + .woocommerce-image-gallery .woocommerce-sortable { + margin: 0; + } + } + + &__drop-zone { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: calc($gap * 2) 0; + margin-top: $gap-smaller; + gap: calc($gap * 2); + isolation: isolate; + + min-height: 144px; + + background: $white; + border: 1px dashed $gray-700; + border-radius: 2px; + + position: relative; + } +} diff --git a/plugins/woocommerce-admin/client/products/fields/single-image-field/single-image-field.tsx b/plugins/woocommerce-admin/client/products/fields/single-image-field/single-image-field.tsx new file mode 100644 index 00000000000..77f31422bcc --- /dev/null +++ b/plugins/woocommerce-admin/client/products/fields/single-image-field/single-image-field.tsx @@ -0,0 +1,107 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + MediaUploader, + ImageGallery, + ImageGalleryItem, +} from '@woocommerce/components'; +import { MediaItem } from '@wordpress/media-utils'; +import uniqueId from 'lodash/uniqueId'; +import classNames from 'classnames'; + +/** + * Internal dependencies + */ +import './single-image-field.scss'; + +export function SingleImageField( { + id, + label, + value, + className, + onChange, + ...props +}: SingleImageFieldProps ) { + const fieldId = id ?? uniqueId(); + + function handleChange( image?: MediaItem ) { + if ( typeof onChange === 'function' ) { + onChange( image ); + } + } + + return ( +
+ + + { value ? ( +
+ handleChange( media ) } + onRemove={ () => handleChange( undefined ) } + > + + +
+ ) : ( +
+ null } + onSelect={ ( image ) => + handleChange( image as MediaItem ) + } + onUpload={ ( [ image ] ) => handleChange( image ) } + onFileUploadChange={ ( [ image ] ) => + handleChange( image ) + } + label={ __( + 'Drag image here or click to upload', + 'woocommerce' + ) } + buttonText={ __( 'Choose image', 'woocommerce' ) } + /> +
+ ) } +
+ ); +} + +export type SingleImageFieldProps = Omit< + React.DetailedHTMLProps< + React.HTMLAttributes< HTMLDivElement >, + HTMLDivElement + >, + 'onChange' +> & { + label: string; + value?: MediaItem; + onChange?( value?: MediaItem ): void; +}; diff --git a/plugins/woocommerce-admin/client/products/sections/product-variation-details-section.tsx b/plugins/woocommerce-admin/client/products/sections/product-variation-details-section.tsx index d67ede8e144..c92c265c1f7 100644 --- a/plugins/woocommerce-admin/client/products/sections/product-variation-details-section.tsx +++ b/plugins/woocommerce-admin/client/products/sections/product-variation-details-section.tsx @@ -3,13 +3,19 @@ */ import { __ } from '@wordpress/i18n'; import { BlockInstance, serialize, parse } from '@wordpress/blocks'; -import { CheckboxControl, Card, CardBody } from '@wordpress/components'; +import { + CheckboxControl, + Card, + CardBody, + BaseControl, +} from '@wordpress/components'; +import { MediaItem } from '@wordpress/media-utils'; import { useFormContext, __experimentalRichTextEditor as RichTextEditor, __experimentalTooltip as Tooltip, } from '@woocommerce/components'; -import { ProductVariation } from '@woocommerce/data'; +import { ProductVariation, ProductVariationImage } from '@woocommerce/data'; import { useState } from '@wordpress/element'; /** @@ -17,15 +23,42 @@ import { useState } from '@wordpress/element'; */ import { getCheckboxTracks } from './utils'; import { ProductSectionLayout } from '../layout/product-section-layout'; +import { SingleImageField } from '../fields/single-image-field'; + +function parseVariationImage( + media?: MediaItem +): ProductVariationImage | undefined { + if ( ! media ) return undefined; + return { + id: media.id, + src: media.url, + alt: media.alt, + name: media.title, + } as ProductVariationImage; +} + +function formatVariationImage( + image?: ProductVariationImage +): MediaItem | undefined { + if ( ! image ) return undefined; + return { + id: image.id, + url: image.src, + alt: image.alt, + title: image.name, + } as MediaItem; +} export const ProductVariationDetailsSection: React.FC = () => { - const { getCheckboxControlProps, values, setValue } = + const { getCheckboxControlProps, getInputProps, values, setValue } = useFormContext< ProductVariation >(); const [ descriptionBlocks, setDescriptionBlocks ] = useState< BlockInstance[] >( parse( values.description || '' ) ); + const imageFieldProps = getInputProps( 'image' ); + return ( { } { ...getCheckboxControlProps( 'status', - getCheckboxTracks( 'status' ) + getCheckboxTracks< ProductVariation >( 'status' ) ) } checked={ values.status === 'publish' } onChange={ () => @@ -77,6 +110,21 @@ export const ProductVariationDetailsSection: React.FC = () => { 'woocommerce' ) } /> + + + + setValue( + 'image', + parseVariationImage( media ) + ) + } + /> + diff --git a/plugins/woocommerce-admin/client/products/sections/utils.ts b/plugins/woocommerce-admin/client/products/sections/utils.ts index d2e6e418355..ab56552bcf6 100644 --- a/plugins/woocommerce-admin/client/products/sections/utils.ts +++ b/plugins/woocommerce-admin/client/products/sections/utils.ts @@ -23,22 +23,20 @@ type CurrencyConfig = { /** * Get additional props to be passed to all checkbox inputs. * - * @param {string} name Name of the checkbox - * @return {Object} Props. + * @param name Name of the checkbox. + * @return Props. */ -export const getCheckboxTracks = ( name: string ) => { +export function getCheckboxTracks< T = Product >( name: string ) { return { onChange: ( - isChecked: - | ChangeEvent< HTMLInputElement > - | Product[ keyof Product ] + isChecked: ChangeEvent< HTMLInputElement > | T[ keyof T ] ) => { recordEvent( `product_checkbox_${ name }`, { checked: isChecked, } ); }, }; -}; +} /** * Get input props for currency related values and symbol positions. diff --git a/plugins/woocommerce/changelog/add-36115 b/plugins/woocommerce/changelog/add-36115 new file mode 100644 index 00000000000..094918475cd --- /dev/null +++ b/plugins/woocommerce/changelog/add-36115 @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add product variation image From 88280f2a5c2b1b7d82435933c6331d591cb45f8c Mon Sep 17 00:00:00 2001 From: Paul Sealock Date: Sat, 24 Dec 2022 07:56:22 +1300 Subject: [PATCH 56/70] Update requires at least 5.9 in readme to same as woocommerce.php (#36170) * requires at least to 5.9 * changelog --- plugins/woocommerce/changelog/update-requires-at-least-5.9 | 5 +++++ plugins/woocommerce/readme.txt | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 plugins/woocommerce/changelog/update-requires-at-least-5.9 diff --git a/plugins/woocommerce/changelog/update-requires-at-least-5.9 b/plugins/woocommerce/changelog/update-requires-at-least-5.9 new file mode 100644 index 00000000000..5807dcd8cf6 --- /dev/null +++ b/plugins/woocommerce/changelog/update-requires-at-least-5.9 @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: update requires at least to same 5.9 + + diff --git a/plugins/woocommerce/readme.txt b/plugins/woocommerce/readme.txt index 3fdd1f65265..3e6e005e4a7 100644 --- a/plugins/woocommerce/readme.txt +++ b/plugins/woocommerce/readme.txt @@ -1,7 +1,7 @@ === WooCommerce === Contributors: automattic, mikejolley, jameskoster, claudiosanches, rodrigosprimo, peterfabian1000, vedjain, jamosova, obliviousharmony, konamiman, sadowski, wpmuguru, royho, barryhughes-1 Tags: online store, ecommerce, shop, shopping cart, sell online, storefront, checkout, payments, woo, woo commerce, e-commerce, store -Requires at least: 5.8 +Requires at least: 5.9 Tested up to: 6.1 Requires PHP: 7.2 Stable tag: 7.1.1 From 8c2180e144578faa15833ac196d9d6a6e36f4006 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 24 Dec 2022 07:56:48 +1300 Subject: [PATCH 57/70] Delete changelog files based on PR 36125 (#36169) Delete changelog files for 36125 Co-authored-by: WooCommerce Bot --- plugins/woocommerce/changelog/update-woocommerce-blocks-9.1.2 | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 plugins/woocommerce/changelog/update-woocommerce-blocks-9.1.2 diff --git a/plugins/woocommerce/changelog/update-woocommerce-blocks-9.1.2 b/plugins/woocommerce/changelog/update-woocommerce-blocks-9.1.2 deleted file mode 100644 index b34dcab7320..00000000000 --- a/plugins/woocommerce/changelog/update-woocommerce-blocks-9.1.2 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -Update WooCommerce Blocks to 9.1.3 From 5b3b5dab59e80075512cfa28f08416af9f5ec7cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maikel=20David=20P=C3=A9rez=20G=C3=B3mez?= Date: Fri, 23 Dec 2022 16:29:44 -0300 Subject: [PATCH 58/70] Truncate attribute option name to a max of 32 chars in variations list (#36134) * Truncate attribute option name to a max of 32 chars in variations list * Use PRODUCT_VARIATION_TITLE_LIMIT to truncate attribute option names * Fix up lint error Co-authored-by: Joshua Flowers --- .../client/products/constants.ts | 1 + .../products/fields/variations/variations.tsx | 40 ++++++++++++++----- .../utils/get-product-variation-title.ts | 5 ++- plugins/woocommerce/changelog/add-36116 | 4 ++ 4 files changed, 39 insertions(+), 11 deletions(-) create mode 100644 plugins/woocommerce/changelog/add-36116 diff --git a/plugins/woocommerce-admin/client/products/constants.ts b/plugins/woocommerce-admin/client/products/constants.ts index 5a340309aea..090e98116df 100644 --- a/plugins/woocommerce-admin/client/products/constants.ts +++ b/plugins/woocommerce-admin/client/products/constants.ts @@ -5,3 +5,4 @@ export const ONLY_ONE_DECIMAL_SEPARATOR = '[%s](?=%s*[%s])'; export const ADD_NEW_SHIPPING_CLASS_OPTION_VALUE = '__ADD_NEW_SHIPPING_CLASS_OPTION__'; export const UNCATEGORIZED_CATEGORY_SLUG = 'uncategorized'; +export const PRODUCT_VARIATION_TITLE_LIMIT = 32; diff --git a/plugins/woocommerce-admin/client/products/fields/variations/variations.tsx b/plugins/woocommerce-admin/client/products/fields/variations/variations.tsx index 63b5ba8b5cc..968303638be 100644 --- a/plugins/woocommerce-admin/client/products/fields/variations/variations.tsx +++ b/plugins/woocommerce-admin/client/products/fields/variations/variations.tsx @@ -12,10 +12,12 @@ import { useContext, useState } from '@wordpress/element'; import { useParams } from 'react-router-dom'; import { useSelect, useDispatch } from '@wordpress/data'; import classnames from 'classnames'; +import truncate from 'lodash/truncate'; /** * Internal dependencies */ +import { PRODUCT_VARIATION_TITLE_LIMIT } from '~/products/constants'; import useVariationsOrder from '~/products/hooks/use-variations-order'; import HiddenIcon from '~/products/images/hidden-icon'; import VisibleIcon from '~/products/images/visible-icon'; @@ -130,16 +132,34 @@ export const Variations: React.FC = () => { { sortedVariations.map( ( variation ) => (
- { variation.attributes.map( ( attribute ) => ( - /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ - /* @ts-ignore Additional props are not required. */ - - ) ) } + { variation.attributes.map( ( attribute ) => { + const tag = ( + /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ + /* @ts-ignore Additional props are not required. */ + + ); + + return attribute.option.length <= + PRODUCT_VARIATION_TITLE_LIMIT ? ( + tag + ) : ( + + { tag } + + ); + } ) }
Date: Fri, 23 Dec 2022 14:57:28 -0500 Subject: [PATCH 59/70] Add Options section to new product experience (#35910) * Support passing in filter and new attribute properties to AttributeField * Changelog * Pass addButtonLabel as prop * Add OptionsSection to options tab * Refactor more to create Attributes and Options fields * Refactor a couple of things * Refactor globalAttributeHelperMessage * Remove `Used for filters` checkbox * Remove `hydrationComplete` * Add subtitle to empty state component * Fix 'Add option' button * Fix tests Co-authored-by: Fernando Marichal --- .../attribute-field/attribute-field.tsx | 161 ++++++++++++++---- .../attribute-field/edit-attribute-modal.tsx | 49 +----- .../test/attribute-field.spec.tsx | 13 +- .../products/fields/attributes/attributes.tsx | 30 ++++ .../products/fields/attributes/index.ts | 1 + .../client/products/fields/options/index.ts | 1 + .../products/fields/options/options.tsx | 31 ++++ .../client/products/product-form.tsx | 2 + .../products/sections/attributes-section.tsx | 8 +- .../products/sections/options-section.scss | 6 + .../products/sections/options-section.tsx | 55 ++++++ .../add-new-product-experience-options | 4 + 12 files changed, 275 insertions(+), 86 deletions(-) create mode 100644 plugins/woocommerce-admin/client/products/fields/attributes/attributes.tsx create mode 100644 plugins/woocommerce-admin/client/products/fields/attributes/index.ts create mode 100644 plugins/woocommerce-admin/client/products/fields/options/index.ts create mode 100644 plugins/woocommerce-admin/client/products/fields/options/options.tsx create mode 100644 plugins/woocommerce-admin/client/products/sections/options-section.scss create mode 100644 plugins/woocommerce-admin/client/products/sections/options-section.tsx create mode 100644 plugins/woocommerce/changelog/add-new-product-experience-options diff --git a/plugins/woocommerce-admin/client/products/fields/attribute-field/attribute-field.tsx b/plugins/woocommerce-admin/client/products/fields/attribute-field/attribute-field.tsx index a624eceb89e..fbfe04f1681 100644 --- a/plugins/woocommerce-admin/client/products/fields/attribute-field/attribute-field.tsx +++ b/plugins/woocommerce-admin/client/products/fields/attribute-field/attribute-field.tsx @@ -1,7 +1,7 @@ /** * External dependencies */ -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { useState, useCallback, useEffect } from '@wordpress/element'; import { ProductAttribute, @@ -12,8 +12,11 @@ import { resolveSelect } from '@wordpress/data'; import { Sortable, __experimentalSelectControlMenuSlot as SelectControlMenuSlot, + Link, } from '@woocommerce/components'; import { recordEvent } from '@woocommerce/tracks'; +import interpolateComponents from '@automattic/interpolate-components'; +import { getAdminLink } from '@woocommerce/settings'; /** * Internal dependencies @@ -33,23 +36,24 @@ type AttributeFieldProps = { value: ProductAttribute[]; onChange: ( value: ProductAttribute[] ) => void; productId?: number; + // TODO: should we support an 'any' option to show all attributes? + attributeType?: 'regular' | 'for-variations'; }; export type HydratedAttributeType = Omit< ProductAttribute, 'options' > & { options?: string[]; terms?: ProductAttributeTerm[]; + visible?: boolean; }; export const AttributeField: React.FC< AttributeFieldProps > = ( { value, onChange, productId, + attributeType = 'regular', } ) => { const [ showAddAttributeModal, setShowAddAttributeModal ] = useState( false ); - const [ hydrationComplete, setHydrationComplete ] = useState< boolean >( - value ? false : true - ); const [ hydratedAttributes, setHydratedAttributes ] = useState< HydratedAttributeType[] >( [] ); @@ -57,8 +61,13 @@ export const AttributeField: React.FC< AttributeFieldProps > = ( { null | string >( null ); - const CANCEL_BUTTON_EVENT_NAME = - 'product_add_attributes_modal_cancel_button_click'; + const isOnlyForVariations = attributeType === 'for-variations'; + + const newAttributeProps = { variation: isOnlyForVariations }; + + const CANCEL_BUTTON_EVENT_NAME = isOnlyForVariations + ? 'product_add_options_modal_cancel_button_click' + : 'product_add_attributes_modal_cancel_button_click'; const fetchTerms = useCallback( ( attributeId: number ) => { @@ -82,7 +91,19 @@ export const AttributeField: React.FC< AttributeFieldProps > = ( { ); useEffect( () => { - if ( ! value || hydrationComplete ) { + // Temporarily always doing hydration, since otherwise new attributes + // get removed from Options and Attributes when the other list is then + // modified + // + // This is because the hydration is out of date -- the logic currently + // assumes modifications are only made from within the component + // + // I think we'll need to move the hydration out of the individual component + // instance. To where, I do not yet know... maybe in the form context + // somewhere so that a single hydration source can be shared between multiple + // instances? Something like a simple key-value store in the form context + // would be handy. + if ( ! value ) { return; } @@ -94,19 +115,26 @@ export const AttributeField: React.FC< AttributeFieldProps > = ( { ).then( ( allResults ) => { setHydratedAttributes( [ ...globalAttributes.map( ( attr, index ) => { + const fetchedTerms = allResults[ index ]; + const newAttr = { ...attr, - terms: allResults[ index ], - options: undefined, + // I'm not sure this is quite right for handling unpersisted terms, + // but this gets things kinda working for now + terms: + fetchedTerms.length > 0 ? fetchedTerms : undefined, + options: + fetchedTerms.length === 0 + ? attr.options + : undefined, }; return newAttr; } ), ...customAttributes, ] ); - setHydrationComplete( true ); } ); - }, [ productId, value, hydrationComplete ] ); + }, [ fetchTerms, productId, value ] ); const fetchAttributeId = ( attribute: { id: number; name: string } ) => `${ attribute.id }-${ attribute.name }`; @@ -121,6 +149,7 @@ export const AttributeField: React.FC< AttributeFieldProps > = ( { ? attr.terms.map( ( term ) => term.name ) : ( attr.options as string[] ), terms: undefined, + visible: attr.visible || false, }; } ) ); @@ -157,24 +186,48 @@ export const AttributeField: React.FC< AttributeFieldProps > = ( { ) ) .map( ( newAttr, index ) => { - newAttr.position = ( value || [] ).length + index; - return newAttr; + return { + ...newAttributeProps, + ...newAttr, + position: ( value || [] ).length + index, + }; } ), ] ); recordEvent( 'product_add_attributes_modal_add_button_click' ); setShowAddAttributeModal( false ); }; - if ( ! value || value.length === 0 || hydratedAttributes.length === 0 ) { + const filteredAttributes = value + ? value.filter( + ( attribute: ProductAttribute ) => + attribute.variation === isOnlyForVariations + ) + : false; + + if ( + ! filteredAttributes || + filteredAttributes.length === 0 || + hydratedAttributes.length === 0 + ) { return ( <> { recordEvent( 'product_add_first_attribute_button_click' ); setShowAddAttributeModal( true ); } } + subtitle={ + isOnlyForVariations + ? __( 'No options yet', 'woocommerce' ) + : undefined + } /> { showAddAttributeModal && ( = ( { setShowAddAttributeModal( false ); } } onAdd={ onAddNewAttributes } - selectedAttributeIds={ ( value || [] ).map( + selectedAttributeIds={ ( filteredAttributes || [] ).map( ( attr ) => attr.id ) } /> @@ -193,8 +246,10 @@ export const AttributeField: React.FC< AttributeFieldProps > = ( { ); } - const sortedAttributes = value.sort( ( a, b ) => a.position - b.position ); - const attributeKeyValues = value.reduce( + const sortedAttributes = filteredAttributes.sort( + ( a, b ) => a.position - b.position + ); + const attributeKeyValues = filteredAttributes.reduce( ( keyValue: Record< number, ProductAttribute >, attribute: ProductAttribute @@ -205,6 +260,20 @@ export const AttributeField: React.FC< AttributeFieldProps > = ( { {} as Record< number, ProductAttribute > ); + const attribute = hydratedAttributes.find( + ( attr ) => fetchAttributeId( attr ) === editingAttributeId + ) as HydratedAttributeType; + + const editAttributeCopy = isOnlyForVariations + ? __( + `You can change the option's name in {{link}}Attributes{{/link}}.`, + 'woocommerce' + ) + : __( + `You can change the attribute's name in {{link}}Attributes{{/link}}.`, + 'woocommerce' + ); + return (
= ( { ); } } > - { sortedAttributes.map( ( attribute ) => ( + { sortedAttributes.map( ( attr ) => ( - setEditingAttributeId( - fetchAttributeId( attribute ) - ) + setEditingAttributeId( fetchAttributeId( attr ) ) } - onRemoveClick={ () => onRemove( attribute ) } + onRemoveClick={ () => onRemove( attr ) } /> ) ) } { - recordEvent( 'product_add_attribute_button' ); + recordEvent( + isOnlyForVariations + ? 'product_add_option_button' + : 'product_add_attribute_button' + ); setShowAddAttributeModal( true ); } } /> { showAddAttributeModal && ( { recordEvent( CANCEL_BUTTON_EVENT_NAME ); setShowAddAttributeModal( false ); @@ -249,6 +330,29 @@ export const AttributeField: React.FC< AttributeFieldProps > = ( { { editingAttributeId && ( + <> + + ), + }, + } ) } onCancel={ () => setEditingAttributeId( null ) } onEdit={ ( changedAttribute ) => { const newAttributesSet = [ ...hydratedAttributes ]; @@ -266,12 +370,7 @@ export const AttributeField: React.FC< AttributeFieldProps > = ( { updateAttributes( newAttributesSet ); setEditingAttributeId( null ); } } - attribute={ - hydratedAttributes.find( - ( attr ) => - fetchAttributeId( attr ) === editingAttributeId - ) as HydratedAttributeType - } + attribute={ attribute } /> ) }
diff --git a/plugins/woocommerce-admin/client/products/fields/attribute-field/edit-attribute-modal.tsx b/plugins/woocommerce-admin/client/products/fields/attribute-field/edit-attribute-modal.tsx index 63e3cd01830..deaf03ee225 100644 --- a/plugins/woocommerce-admin/client/products/fields/attribute-field/edit-attribute-modal.tsx +++ b/plugins/woocommerce-admin/client/products/fields/attribute-field/edit-attribute-modal.tsx @@ -9,12 +9,7 @@ import { TextControl, } from '@wordpress/components'; import { useState } from '@wordpress/element'; -import { - __experimentalTooltip as Tooltip, - Link, -} from '@woocommerce/components'; -import interpolateComponents from '@automattic/interpolate-components'; -import { getAdminLink } from '@woocommerce/settings'; +import { __experimentalTooltip as Tooltip } from '@woocommerce/components'; /** * Internal dependencies @@ -30,14 +25,12 @@ import './edit-attribute-modal.scss'; type EditAttributeModalProps = { title?: string; nameLabel?: string; - globalAttributeHelperMessage?: string; + globalAttributeHelperMessage?: JSX.Element; customAttributeHelperMessage?: string; termsLabel?: string; termsPlaceholder?: string; visibleLabel?: string; visibleTooltip?: string; - filtersLabel?: string; - filtersTooltip?: string; cancelAccessibleLabel?: string; cancelLabel?: string; updateAccessibleLabel?: string; @@ -50,25 +43,7 @@ type EditAttributeModalProps = { export const EditAttributeModal: React.FC< EditAttributeModalProps > = ( { title = __( 'Edit attribute', 'woocommerce' ), nameLabel = __( 'Name', 'woocommerce' ), - globalAttributeHelperMessage = interpolateComponents( { - mixedString: __( - `You can change the attribute's name in {{link}}Attributes{{/link}}.`, - 'woocommerce' - ), - components: { - link: ( - - <> - - ), - }, - } ), + globalAttributeHelperMessage, customAttributeHelperMessage = __( 'Your customers will see this on the product page', 'woocommerce' @@ -80,11 +55,6 @@ export const EditAttributeModal: React.FC< EditAttributeModalProps > = ( { 'Show or hide this attribute on the product page', 'woocommerce' ), - filtersLabel = __( 'Used for filters', 'woocommerce' ), - filtersTooltip = __( - `Show or hide this attribute in the filters section on your store's category and shop pages`, - 'woocommerce' - ), cancelAccessibleLabel = __( 'Cancel', 'woocommerce' ), cancelLabel = __( 'Cancel', 'woocommerce' ), updateAccessibleLabel = __( 'Edit attribute', 'woocommerce' ), @@ -165,19 +135,6 @@ export const EditAttributeModal: React.FC< EditAttributeModalProps > = ( { />
-
- - setEditableAttribute( { - ...( editableAttribute as HydratedAttributeType ), - variation: val, - } ) - } - checked={ editableAttribute?.variation } - label={ filtersLabel } - /> - -