From deaca578cc858cd5e68a079c06c39b1bb77eadfa Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Tue, 20 Aug 2024 13:06:57 +0800 Subject: [PATCH] Migrate LYS user meta (#50664) * Migrate lys meta * Update lys meta logic * Update user preferences types and logic * Add changelog * Revert changes * Fix types * Fix logic * Fix lint --- .../js/data/changelog/update-lys-tour-meta | 4 + packages/js/data/src/user/types.ts | 9 +- .../js/data/src/user/use-user-preferences.ts | 31 ++--- .../tour/use-site-visibility-tour.tsx | 65 +++++---- .../changelog/update-lys-tour-meta | 4 + .../client/legacy/js/frontend/woocommerce.js | 129 +++++++++++------- .../woocommerce/includes/class-wc-install.php | 1 + .../includes/wc-update-functions.php | 28 ++++ .../src/Admin/Features/LaunchYourStore.php | 33 ++++- 9 files changed, 195 insertions(+), 109 deletions(-) create mode 100644 packages/js/data/changelog/update-lys-tour-meta create mode 100644 plugins/woocommerce/changelog/update-lys-tour-meta diff --git a/packages/js/data/changelog/update-lys-tour-meta b/packages/js/data/changelog/update-lys-tour-meta new file mode 100644 index 00000000000..eec09d7e111 --- /dev/null +++ b/packages/js/data/changelog/update-lys-tour-meta @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Update user preferences types and logic diff --git a/packages/js/data/src/user/types.ts b/packages/js/data/src/user/types.ts index 91b057b7eff..34328a41f18 100644 --- a/packages/js/data/src/user/types.ts +++ b/packages/js/data/src/user/types.ts @@ -33,13 +33,12 @@ export type UserPreferences = { product_advice_card_dismissed?: { [ key: string ]: 'yes' | 'no'; }; + launch_your_store_tour_hidden?: 'yes' | 'no' | ''; + coming_soon_banner_dismissed?: 'yes' | 'no' | ''; }; -export type WoocommerceMeta = UserPreferences & { - task_list_tracked_started_tasks?: string; - variable_items_without_price_notice_dismissed?: string; - local_attributes_notice_dismissed_ids?: string; - product_advice_card_dismissed?: string; +export type WoocommerceMeta = { + [ key in keyof UserPreferences ]: string; }; export type WCUser< diff --git a/packages/js/data/src/user/use-user-preferences.ts b/packages/js/data/src/user/use-user-preferences.ts index e1367d482f0..779cb8adf03 100644 --- a/packages/js/data/src/user/use-user-preferences.ts +++ b/packages/js/data/src/user/use-user-preferences.ts @@ -19,28 +19,15 @@ import { WCUser, UserPreferences } from './types'; const getWooCommerceMeta = ( user: WCUser ) => { const wooMeta = user.woocommerce_meta || {}; - const userData = mapValues( wooMeta, ( data, key ) => { + const userData = mapValues( wooMeta, ( data ) => { if ( ! data || data.length === 0 ) { return ''; } try { return JSON.parse( data ); } catch ( e ) { - if ( e instanceof Error ) { - /* eslint-disable no-console */ - console.error( - `Error parsing value '${ data }' for ${ key }`, - e.message - ); - /* eslint-enable no-console */ - } else { - /* eslint-disable no-console */ - console.error( - `Unexpected Error parsing value '${ data }' for ${ key } ${ e }` - ); - /* eslint-enable no-console */ - } - return ''; + // If we can't parse the value, return the raw data. The meta value could be a string like 'yes' or 'no'. + return data; } } ); @@ -53,7 +40,7 @@ async function updateUserPrefs( user: WCUser, saveUser: ( userToSave: { id: number; - woocommerce_meta: { [ key: string ]: boolean }; + woocommerce_meta: WCUser[ 'woocommerce_meta' ]; } ) => WCUser, getLastEntitySaveError: ( kind: string, @@ -64,7 +51,14 @@ async function updateUserPrefs( ) { // @todo Handle unresolved getCurrentUser() here. // Prep fields for update. - const metaData = mapValues( userPrefs, JSON.stringify ); + const metaData = mapValues( userPrefs, ( value ) => { + if ( typeof value === 'string' ) { + // If the value is a string, we don't need to serialize it. + return value; + } + + return JSON.stringify( value ); + } ); if ( Object.keys( metaData ).length === 0 ) { return { @@ -81,7 +75,6 @@ async function updateUserPrefs( ...metaData, }, } ); - // Use saveUser() to update WooCommerce meta values. const updatedUser = await saveUser( { id: user.id, diff --git a/plugins/woocommerce-admin/client/launch-your-store/tour/use-site-visibility-tour.tsx b/plugins/woocommerce-admin/client/launch-your-store/tour/use-site-visibility-tour.tsx index 3b6562d254f..4dc98b1ee09 100644 --- a/plugins/woocommerce-admin/client/launch-your-store/tour/use-site-visibility-tour.tsx +++ b/plugins/woocommerce-admin/client/launch-your-store/tour/use-site-visibility-tour.tsx @@ -1,50 +1,57 @@ /** * External dependencies */ -import { OPTIONS_STORE_NAME } from '@woocommerce/data'; -import { useSelect, dispatch } from '@wordpress/data'; +import { OPTIONS_STORE_NAME, useUserPreferences } from '@woocommerce/data'; +import { useSelect } from '@wordpress/data'; import { useState } from 'react'; -const LYS_TOUR_HIDDEN = 'woocommerce_launch_your_store_tour_hidden'; - export const useSiteVisibilityTour = () => { const [ showTour, setShowTour ] = useState( true ); - const { shouldTourBeShown } = useSelect( ( select ) => { - // Tour should only be shown if the user has not seen it before and the `woocommerce_show_lys_tour` option is "yes" (for sites upgrading from a previous WooCommerce version) - const { getCurrentUser } = select( 'core' ); - const wasTourShown = + // Tour should only be shown if the user has not seen it before and the `woocommerce_show_lys_tour` option is "yes" (for sites upgrading from a previous WooCommerce version) + const shouldStoreShowLYSTour = useSelect( + ( select ) => + select( OPTIONS_STORE_NAME ).getOption( + 'woocommerce_show_lys_tour' + ) === 'yes' + ); + + /** + * This is temporary to support sites upgrading from a previous version of WooCommerce. + * We used user meta to store the tour dismissal state but now we use WooCommerce meta instead. + * It will be removed in WC 9.4. + */ + const hasUserDismissedTourMeta = useSelect( ( select ) => { + const currentUser = select( 'core' ).getCurrentUser(); + if ( ! currentUser ) { + // If the user is not logged in, we don't want to show the tour. + return true; + } + + return ( // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - ( getCurrentUser() as { meta?: { [ key: string ]: string } } ) - ?.meta?.[ LYS_TOUR_HIDDEN ] === 'yes'; - - const { getOption } = select( OPTIONS_STORE_NAME ); - - const showLYSTourOption = getOption( 'woocommerce_show_lys_tour' ); - - const _shouldTourBeShown = - showLYSTourOption === 'yes' && ! wasTourShown; - - return { - shouldTourBeShown: _shouldTourBeShown, - }; + ( currentUser as { meta: { [ key: string ]: string } } ).meta + .woocommerce_launch_your_store_tour_hidden === 'yes' + ); } ); + const { + launch_your_store_tour_hidden: lysTourHidden, + updateUserPreferences, + } = useUserPreferences(); + const onClose = () => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - dispatch( 'core' ).saveUser( { - id: window?.wcSettings?.currentUserId, - meta: { - woocommerce_launch_your_store_tour_hidden: 'yes', - }, + updateUserPreferences( { + launch_your_store_tour_hidden: 'yes', } ); }; return { onClose, - shouldTourBeShown, + shouldTourBeShown: + shouldStoreShowLYSTour && + ! ( hasUserDismissedTourMeta || lysTourHidden ), showTour, setShowTour, }; diff --git a/plugins/woocommerce/changelog/update-lys-tour-meta b/plugins/woocommerce/changelog/update-lys-tour-meta new file mode 100644 index 00000000000..662915b86e2 --- /dev/null +++ b/plugins/woocommerce/changelog/update-lys-tour-meta @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Migrate LYS user meta diff --git a/plugins/woocommerce/client/legacy/js/frontend/woocommerce.js b/plugins/woocommerce/client/legacy/js/frontend/woocommerce.js index c3190ac687b..10946dc0407 100644 --- a/plugins/woocommerce/client/legacy/js/frontend/woocommerce.js +++ b/plugins/woocommerce/client/legacy/js/frontend/woocommerce.js @@ -1,20 +1,20 @@ /* global Cookies */ -jQuery( function( $ ) { +jQuery( function ( $ ) { // Orderby - $( '.woocommerce-ordering' ).on( 'change', 'select.orderby', function() { + $( '.woocommerce-ordering' ).on( 'change', 'select.orderby', function () { $( this ).closest( 'form' ).trigger( 'submit' ); - }); + } ); // Target quantity inputs on product pages - $( 'input.qty:not(.product-quantity input.qty)' ).each( function() { + $( 'input.qty:not(.product-quantity input.qty)' ).each( function () { var min = parseFloat( $( this ).attr( 'min' ) ); if ( min >= 0 && parseFloat( $( this ).val() ) < min ) { $( this ).val( min ); } - }); + } ); - var noticeID = $( '.woocommerce-store-notice' ).data( 'noticeId' ) || '', + var noticeID = $( '.woocommerce-store-notice' ).data( 'noticeId' ) || '', cookieName = 'store_notice' + noticeID; // Check the value of that cookie and show/hide the notice accordingly @@ -25,43 +25,56 @@ jQuery( function( $ ) { } // Set a cookie and hide the store notice when the dismiss button is clicked - $( '.woocommerce-store-notice__dismiss-link' ).on( 'click', function( event ) { - Cookies.set( cookieName, 'hidden', { path: '/' } ); - $( '.woocommerce-store-notice' ).hide(); - event.preventDefault(); - }); + $( '.woocommerce-store-notice__dismiss-link' ).on( + 'click', + function ( event ) { + Cookies.set( cookieName, 'hidden', { path: '/' } ); + $( '.woocommerce-store-notice' ).hide(); + event.preventDefault(); + } + ); // Make form field descriptions toggle on focus. if ( $( '.woocommerce-input-wrapper span.description' ).length ) { - $( document.body ).on( 'click', function() { - $( '.woocommerce-input-wrapper span.description:visible' ).prop( 'aria-hidden', true ).slideUp( 250 ); + $( document.body ).on( 'click', function () { + $( '.woocommerce-input-wrapper span.description:visible' ) + .prop( 'aria-hidden', true ) + .slideUp( 250 ); } ); } - $( '.woocommerce-input-wrapper' ).on( 'click', function( event ) { + $( '.woocommerce-input-wrapper' ).on( 'click', function ( event ) { event.stopPropagation(); } ); $( '.woocommerce-input-wrapper :input' ) - .on( 'keydown', function( event ) { - var input = $( this ), - parent = input.parent(), + .on( 'keydown', function ( event ) { + var input = $( this ), + parent = input.parent(), description = parent.find( 'span.description' ); - if ( 27 === event.which && description.length && description.is( ':visible' ) ) { + if ( + 27 === event.which && + description.length && + description.is( ':visible' ) + ) { description.prop( 'aria-hidden', true ).slideUp( 250 ); event.preventDefault(); return false; } } ) - .on( 'click focus', function() { - var input = $( this ), - parent = input.parent(), + .on( 'click focus', function () { + var input = $( this ), + parent = input.parent(), description = parent.find( 'span.description' ); parent.addClass( 'currentTarget' ); - $( '.woocommerce-input-wrapper:not(.currentTarget) span.description:visible' ).prop( 'aria-hidden', true ).slideUp( 250 ); + $( + '.woocommerce-input-wrapper:not(.currentTarget) span.description:visible' + ) + .prop( 'aria-hidden', true ) + .slideUp( 250 ); if ( description.length && description.is( ':hidden' ) ) { description.prop( 'aria-hidden', false ).slideDown( 250 ); @@ -71,52 +84,66 @@ jQuery( function( $ ) { } ); // Common scroll to element code. - $.scroll_to_notices = function( scrollElement ) { + $.scroll_to_notices = function ( scrollElement ) { if ( scrollElement.length ) { - $( 'html, body' ).animate( { - scrollTop: ( scrollElement.offset().top - 100 ) - }, 1000 ); + $( 'html, body' ).animate( + { + scrollTop: scrollElement.offset().top - 100, + }, + 1000 + ); } }; // Show password visibility hover icon on woocommerce forms - $( '.woocommerce form .woocommerce-Input[type="password"]' ).wrap( '' ); + $( '.woocommerce form .woocommerce-Input[type="password"]' ).wrap( + '' + ); // Add 'password-input' class to the password wrapper in checkout page. - $( '.woocommerce form input' ).filter(':password').parent('span').addClass('password-input'); - $( '.password-input' ).append( '' ); - - $( '.show-password-input' ).on( 'click', - function() { - if ( $( this ).hasClass( 'display-password' ) ) { - $( this ).removeClass( 'display-password' ); - } else { - $( this ).addClass( 'display-password' ); - } - if ( $( this ).hasClass( 'display-password' ) ) { - $( this ).siblings( ['input[type="password"]'] ).prop( 'type', 'text' ); - } else { - $( this ).siblings( 'input[type="text"]' ).prop( 'type', 'password' ); - } - } + $( '.woocommerce form input' ) + .filter( ':password' ) + .parent( 'span' ) + .addClass( 'password-input' ); + $( '.password-input' ).append( + '' ); + $( '.show-password-input' ).on( 'click', function () { + if ( $( this ).hasClass( 'display-password' ) ) { + $( this ).removeClass( 'display-password' ); + } else { + $( this ).addClass( 'display-password' ); + } + if ( $( this ).hasClass( 'display-password' ) ) { + $( this ) + .siblings( [ 'input[type="password"]' ] ) + .prop( 'type', 'text' ); + } else { + $( this ) + .siblings( 'input[type="text"]' ) + .prop( 'type', 'password' ); + } + } ); - $( 'a.coming-soon-footer-banner-dismiss' ).on( 'click', function( e ) { + $( 'a.coming-soon-footer-banner-dismiss' ).on( 'click', function ( e ) { var target = $( e.target ); $.ajax( { type: 'post', url: target.data( 'rest-url' ), data: { - meta: { - 'woocommerce_coming_soon_banner_dismissed': 'yes' - } + woocommerce_meta: { + coming_soon_banner_dismissed: 'yes', + }, }, beforeSend: function ( xhr ) { - xhr.setRequestHeader( 'X-WP-Nonce', target.data( 'rest-nonce' ) ); + xhr.setRequestHeader( + 'X-WP-Nonce', + target.data( 'rest-nonce' ) + ); }, complete: function () { - $('#coming-soon-footer-banner').hide(); - } + $( '#coming-soon-footer-banner' ).hide(); + }, } ); } ); -}); +} ); diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index d5184396673..e6d591d8d11 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -264,6 +264,7 @@ class WC_Install { ), '9.3.0' => array( 'wc_update_930_add_woocommerce_coming_soon_option', + 'wc_update_930_migrate_user_meta_for_launch_your_store_tour', ), ); diff --git a/plugins/woocommerce/includes/wc-update-functions.php b/plugins/woocommerce/includes/wc-update-functions.php index 9879e6e5538..6076aa58646 100644 --- a/plugins/woocommerce/includes/wc-update-functions.php +++ b/plugins/woocommerce/includes/wc-update-functions.php @@ -2824,3 +2824,31 @@ function wc_update_910_remove_obsolete_user_meta() { function wc_update_930_add_woocommerce_coming_soon_option() { add_option( 'woocommerce_coming_soon', 'no' ); } + +/** + * Migrate Launch Your Store tour meta keys to the woocommerce_meta user data fields. + */ +function wc_update_930_migrate_user_meta_for_launch_your_store_tour() { + // Rename `woocommerce_launch_your_store_tour_hidden` meta key to `woocommerce_admin_launch_your_store_tour_hidden`. + global $wpdb; + $wpdb->query( + $wpdb->prepare( + "UPDATE {$wpdb->usermeta} + SET meta_key = %s + WHERE meta_key = %s", + 'woocommerce_admin_launch_your_store_tour_hidden', + 'woocommerce_launch_your_store_tour_hidden' + ) + ); + + // Rename `woocommerce_coming_soon_banner_dismissed` meta key to `woocommerce_admin_coming_soon_banner_dismissed`. + $wpdb->query( + $wpdb->prepare( + "UPDATE {$wpdb->usermeta} + SET meta_key = %s + WHERE meta_key = %s", + 'woocommerce_admin_coming_soon_banner_dismissed', + 'woocommerce_coming_soon_banner_dismissed' + ) + ); +} diff --git a/plugins/woocommerce/src/Admin/Features/LaunchYourStore.php b/plugins/woocommerce/src/Admin/Features/LaunchYourStore.php index cc83a73dd15..3bfff4518fe 100644 --- a/plugins/woocommerce/src/Admin/Features/LaunchYourStore.php +++ b/plugins/woocommerce/src/Admin/Features/LaunchYourStore.php @@ -5,12 +5,13 @@ namespace Automattic\WooCommerce\Admin\Features; use Automattic\WooCommerce\Admin\PageController; use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils; use Automattic\WooCommerce\Admin\WCAdminHelper; +use Automattic\WooCommerce\Internal\Admin\WCAdminUser; /** * Takes care of Launch Your Store related actions. */ class LaunchYourStore { - const BANNER_DISMISS_USER_META_KEY = 'woocommerce_coming_soon_banner_dismissed'; + const BANNER_DISMISS_USER_META_KEY = 'coming_soon_banner_dismissed'; /** * Constructor. */ @@ -21,6 +22,7 @@ class LaunchYourStore { add_action( 'init', array( $this, 'register_launch_your_store_user_meta_fields' ) ); add_filter( 'woocommerce_tracks_event_properties', array( $this, 'append_coming_soon_global_tracks' ), 10, 2 ); add_action( 'wp_login', array( $this, 'reset_woocommerce_coming_soon_banner_dismissed' ), 10, 2 ); + add_filter( 'woocommerce_admin_get_user_data_fields', array( $this, 'add_user_data_fields' ) ); } /** @@ -160,7 +162,10 @@ class LaunchYourStore { return false; } - if ( get_user_meta( $current_user_id, self::BANNER_DISMISS_USER_META_KEY, true ) === 'yes' ) { + $has_dismissed_banner = WCAdminUser::get_user_data_field( $current_user_id, self::BANNER_DISMISS_USER_META_KEY ) + // Remove this check in WC 9.4. + || get_user_meta( $current_user_id, 'woocommerce_' . self::BANNER_DISMISS_USER_META_KEY, true ) === 'yes'; + if ( $has_dismissed_banner ) { return false; } @@ -198,6 +203,8 @@ class LaunchYourStore { /** * Register user meta fields for Launch Your Store. + * + * This should be removed in WC 9.4. */ public function register_launch_your_store_user_meta_fields() { if ( ! $this->is_manager_or_admin() ) { @@ -217,7 +224,7 @@ class LaunchYourStore { register_meta( 'user', - self::BANNER_DISMISS_USER_META_KEY, + 'woocommerce_coming_soon_banner_dismissed', array( 'type' => 'string', 'description' => 'Indicate whether the user has dismissed the coming soon notice or not.', @@ -227,6 +234,22 @@ class LaunchYourStore { ); } + /** + * Register user meta fields for Launch Your Store. + * + * @param array $user_data_fields user data fields. + * @return array + */ + public function add_user_data_fields( $user_data_fields ) { + return array_merge( + $user_data_fields, + array( + 'launch_your_store_tour_hidden', + self::BANNER_DISMISS_USER_META_KEY, + ) + ); + } + /** * Reset 'woocommerce_coming_soon_banner_dismissed' user meta to 'no'. * @@ -236,9 +259,9 @@ class LaunchYourStore { * @param object $user user object. */ public function reset_woocommerce_coming_soon_banner_dismissed( $user_login, $user ) { - $existing_meta = get_user_meta( $user->ID, self::BANNER_DISMISS_USER_META_KEY, true ); + $existing_meta = WCAdminUser::get_user_data_field( $user->ID, self::BANNER_DISMISS_USER_META_KEY ); if ( 'yes' === $existing_meta ) { - update_user_meta( $user->ID, self::BANNER_DISMISS_USER_META_KEY, 'no' ); + WCAdminUser::update_user_data_field( $user->ID, self::BANNER_DISMISS_USER_META_KEY, 'no' ); } } }