From f569f333a1b255f1c76047f11cbbcbb48b0c5811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomek=20Wytr=C4=99bowicz?= Date: Mon, 5 Jun 2023 09:55:20 +0200 Subject: [PATCH] Fix Layout Controller forwarding arrays from the URL query string. (#38593) * Revert part of "Remove `qs` dependency from `woocommerce-admin` (#35128)" This reverts Layout- and Filter-related changes from commit 00c151f9aabd8902486c7b1bd95a0b7e8e4f7b82, reversing changes made to eef417fe39d6b8e7340ef127c4e15240a6d1dc20. Removes the fix (keeping the test) from https://github.com/woocommerce/woocommerce/pull/38542 as it's not needed for `qs` Fixes https://github.com/woocommerce/woocommerce/issues/38582. * Simplify the use of query params in `ProductTour` * Add changelog entry --- .../client/analytics/report/revenue/table.js | 3 +- .../embedded-body-layout.tsx | 11 +-- .../guided-tours/add-product-tour/index.tsx | 6 +- .../client/layout/controller.js | 27 +++----- .../woocommerce-admin/client/layout/index.js | 4 +- plugins/woocommerce-admin/package.json | 1 + .../changelog/fix-38582-restore-qs-arrays | 4 ++ pnpm-lock.yaml | 68 +++++++++++++------ 8 files changed, 74 insertions(+), 50 deletions(-) create mode 100644 plugins/woocommerce/changelog/fix-38582-restore-qs-arrays diff --git a/plugins/woocommerce-admin/client/analytics/report/revenue/table.js b/plugins/woocommerce-admin/client/analytics/report/revenue/table.js index 2cb1b84a040..bb490516483 100644 --- a/plugins/woocommerce-admin/client/analytics/report/revenue/table.js +++ b/plugins/woocommerce-admin/client/analytics/report/revenue/table.js @@ -21,6 +21,7 @@ import { defaultTableDateFormat, getCurrentDates, } from '@woocommerce/date'; +import { stringify } from 'qs'; import { CurrencyContext } from '@woocommerce/currency'; /** @@ -309,7 +310,7 @@ const formatProps = memoize( [ isError, isRequesting, - new URLSearchParams( tableQuery ).toString(), + stringify( tableQuery ), get( revenueData, [ 'totalResults' ], 0 ), get( revenueData, [ 'data', 'intervals' ], EMPTY_ARRAY ).length, dateType, diff --git a/plugins/woocommerce-admin/client/embedded-body-layout/embedded-body-layout.tsx b/plugins/woocommerce-admin/client/embedded-body-layout/embedded-body-layout.tsx index 4a69d546df7..23338f69e39 100644 --- a/plugins/woocommerce-admin/client/embedded-body-layout/embedded-body-layout.tsx +++ b/plugins/woocommerce-admin/client/embedded-body-layout/embedded-body-layout.tsx @@ -8,6 +8,7 @@ import { LayoutContextProvider, getLayoutContextValue, } from '@woocommerce/admin-layout'; +import QueryString, { parse } from 'qs'; /** * Internal dependencies @@ -20,8 +21,10 @@ import './style.scss'; type QueryParams = EmbeddedBodyProps; -function isWPPage( params: URLSearchParams ): boolean { - return params.get( 'page' ) !== null; +function isWPPage( + params: QueryParams | QueryString.ParsedQs +): params is QueryParams { + return ( params as QueryParams ).page !== undefined; } const EMBEDDED_BODY_COMPONENT_LIST: React.ElementType[] = [ @@ -41,10 +44,10 @@ export const EmbeddedBodyLayout = () => { triggerExitPageCesSurvey(); }, [] ); - const query = new URLSearchParams( location.search ); + const query = parse( location.search.substring( 1 ) ); let queryParams: QueryParams = { page: '', tab: '' }; if ( isWPPage( query ) ) { - queryParams = Object.fromEntries( query ) as QueryParams; + queryParams = query; } /** * Filter an array of body components for WooCommerce non-react pages. diff --git a/plugins/woocommerce-admin/client/guided-tours/add-product-tour/index.tsx b/plugins/woocommerce-admin/client/guided-tours/add-product-tour/index.tsx index 361b54095a7..18db500f1a1 100644 --- a/plugins/woocommerce-admin/client/guided-tours/add-product-tour/index.tsx +++ b/plugins/woocommerce-admin/client/guided-tours/add-product-tour/index.tsx @@ -293,10 +293,8 @@ export const ProductTour = () => { recordEvent( 'walkthrough_product_enable_button_click' ); } ); - const query = Object.fromEntries( - new URLSearchParams( window.location.search ) - ); - if ( query && query.tutorial === 'true' ) { + const query = new URLSearchParams( window.location.search ); + if ( query.get( 'tutorial' ) === 'true' ) { const intervalId = waitUntilElementTopNotChange( tourConfig.steps[ 0 ].referenceElements?.desktop || '', () => { diff --git a/plugins/woocommerce-admin/client/layout/controller.js b/plugins/woocommerce-admin/client/layout/controller.js index d3b04ebf9d2..411b9da37f7 100644 --- a/plugins/woocommerce-admin/client/layout/controller.js +++ b/plugins/woocommerce-admin/client/layout/controller.js @@ -3,6 +3,7 @@ */ import { Suspense, lazy } from '@wordpress/element'; import { useRef, useEffect } from 'react'; +import { parse, stringify } from 'qs'; import { find, isEqual, last, omit } from 'lodash'; import { applyFilters } from '@wordpress/hooks'; import { __ } from '@wordpress/i18n'; @@ -429,28 +430,18 @@ export const Controller = ( { ...props } ) => { */ export function updateLinkHref( item, nextQuery, excludedScreens ) { if ( isWCAdmin( item.href ) ) { - // If we accept a full HTMLAnchorElement, then we should be able to use `.search`. - // const query = new URLSearchParams( item.search ); - // but to remain backward compatible, we support any object with `href` property. const search = last( item.href.split( '?' ) ); - let query = new URLSearchParams( search ); - const path = query.get( 'path' ) || 'homescreen'; + const query = parse( search ); + const path = query.path || 'homescreen'; const screen = getScreenFromPath( path ); - if ( ! excludedScreens.includes( screen ) ) { - query = new URLSearchParams( { - ...Object.fromEntries( query ), - ...nextQuery, - } ); - } - // Remove undefined keys. - for ( const [ key, value ] of Array.from( query.entries() ) ) { - if ( value === 'undefined' || value === undefined ) { - query.delete( key ); - } - } + const isExcludedScreen = excludedScreens.includes( screen ); - const href = 'admin.php?' + query.toString(); + const href = + 'admin.php?' + + stringify( + Object.assign( query, isExcludedScreen ? {} : nextQuery ) + ); // Replace the href so you can see the url on hover. item.href = href; diff --git a/plugins/woocommerce-admin/client/layout/index.js b/plugins/woocommerce-admin/client/layout/index.js index 13e1f8cc38b..44399423a5b 100644 --- a/plugins/woocommerce-admin/client/layout/index.js +++ b/plugins/woocommerce-admin/client/layout/index.js @@ -188,9 +188,7 @@ function _Layout( { const { breadcrumbs, layout = { header: true, footer: true } } = page; const { header: showHeader = true, footer: showFooter = true } = layout; - const query = Object.fromEntries( - new URLSearchParams( location && location.search ) - ); + const query = getQuery(); return ( =6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.17.8 + '@babel/helper-plugin-utils': 7.21.5 + /@babel/plugin-syntax-jsx@7.18.6(@babel/core@7.21.3): resolution: {integrity: sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==} engines: {node: '>=6.9.0'} @@ -6575,6 +6587,7 @@ packages: dependencies: '@babel/core': 7.17.8 '@babel/helper-plugin-utils': 7.21.5 + dev: true /@babel/plugin-syntax-jsx@7.21.4(@babel/core@7.21.3): resolution: {integrity: sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ==} @@ -8087,7 +8100,7 @@ packages: dependencies: '@babel/core': 7.17.8 '@babel/helper-module-transforms': 7.21.2 - '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-plugin-utils': 7.21.5 '@babel/helper-simple-access': 7.20.2 transitivePeerDependencies: - supports-color @@ -8101,7 +8114,7 @@ packages: dependencies: '@babel/core': 7.21.3 '@babel/helper-module-transforms': 7.21.2 - '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-plugin-utils': 7.21.5 '@babel/helper-simple-access': 7.20.2 transitivePeerDependencies: - supports-color @@ -8823,9 +8836,9 @@ packages: '@babel/core': 7.21.3 '@babel/helper-annotate-as-pure': 7.16.7 '@babel/helper-module-imports': 7.16.7 - '@babel/helper-plugin-utils': 7.21.5 + '@babel/helper-plugin-utils': 7.18.9 '@babel/plugin-syntax-jsx': 7.16.7(@babel/core@7.21.3) - '@babel/types': 7.22.4 + '@babel/types': 7.17.0 dev: true /@babel/plugin-transform-react-jsx@7.19.0(@babel/core@7.17.8): @@ -10333,8 +10346,8 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.21.3 - '@babel/helper-plugin-utils': 7.20.2 - '@babel/helper-validator-option': 7.18.6 + '@babel/helper-plugin-utils': 7.21.5 + '@babel/helper-validator-option': 7.21.0 '@babel/plugin-transform-react-display-name': 7.16.7(@babel/core@7.21.3) '@babel/plugin-transform-react-jsx': 7.19.0(@babel/core@7.21.3) '@babel/plugin-transform-react-jsx-development': 7.16.7(@babel/core@7.21.3) @@ -10635,8 +10648,8 @@ packages: '@babel/core': ^7.0.0 dependencies: '@babel/core': 7.17.8 - '@babel/helper-module-imports': 7.21.4 - '@babel/plugin-syntax-jsx': 7.21.4(@babel/core@7.17.8) + '@babel/helper-module-imports': 7.18.6 + '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.17.8) '@babel/runtime': 7.21.0 '@emotion/hash': 0.9.0 '@emotion/memoize': 0.8.0 @@ -12121,6 +12134,7 @@ packages: dependencies: '@jridgewell/resolve-uri': 3.1.0 '@jridgewell/sourcemap-codec': 1.4.14 + dev: true /@jridgewell/trace-mapping@0.3.17: resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==} @@ -19947,6 +19961,15 @@ packages: prettier: /wp-prettier@2.2.1-beta-1 dev: true + /@wordpress/prettier-config@2.17.0(wp-prettier@2.6.2): + resolution: {integrity: sha512-g4TEQIRDwNW/O8cAf895YV8a3eFfJr7RNLjqu70r8JFH4jmt2clJNu/9nxRburBxJZwqQnykrnDUz0HvXSbcsA==} + engines: {node: '>=14'} + peerDependencies: + prettier: '>=2' + dependencies: + prettier: /wp-prettier@2.6.2 + dev: true + /@wordpress/prettier-config@2.17.0(wp-prettier@2.8.5): resolution: {integrity: sha512-g4TEQIRDwNW/O8cAf895YV8a3eFfJr7RNLjqu70r8JFH4jmt2clJNu/9nxRburBxJZwqQnykrnDUz0HvXSbcsA==} engines: {node: '>=14'} @@ -21421,8 +21444,8 @@ packages: peerDependencies: postcss: ^8.1.0 dependencies: - browserslist: 4.21.4 - caniuse-lite: 1.0.30001418 + browserslist: 4.20.2 + caniuse-lite: 1.0.30001352 fraction.js: 4.2.0 normalize-range: 0.1.2 picocolors: 1.0.0 @@ -22987,7 +23010,6 @@ packages: escalade: 3.1.1 node-releases: 2.0.6 picocolors: 1.0.0 - dev: true /browserslist@4.20.4: resolution: {integrity: sha512-ok1d+1WpnU24XYN7oC3QWgTyMhY/avPJ/r9T00xxvUOIparA/gc+UPUMaod3i+G6s+nI2nUb9xZ5k794uIwShw==} @@ -45124,6 +45146,12 @@ packages: hasBin: true dev: true + /wp-prettier@2.6.2: + resolution: {integrity: sha512-AV33EzqiFJ3fj+mPlKABN59YFPReLkDxQnj067Z3uEOeRQf3g05WprL0RDuqM7UBhSRo9W1rMSC2KvZmjE5UOA==} + engines: {node: '>=10.13.0'} + hasBin: true + dev: true + /wp-prettier@2.8.5: resolution: {integrity: sha512-gkphzYtVksWV6D7/V530bTehKkhrABUru/Gy4reOLOHJoKH4i9lcE1SxqU2VDxC3gCOx/Nk9alZmWk6xL/IBCw==} engines: {node: '>=10.13.0'}