Show spotlight when switching to variable product type (#37413)
* Show variable product tour * Only show tour when product type is changed to variable * Only show tour if it hasn't been shown before * Add variable_product_tour_shown to UserPreferences type * Store whether tour has been shown in user preferences * Record Tracks events * Add docblock for woocommerce_admin_get_user_data_fields filter * Add test for tour
This commit is contained in:
parent
eab7750208
commit
30ea6cfc71
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add variable_product_tour_shown to UserPreferences type.
|
|
@ -25,6 +25,7 @@ export type UserPreferences = {
|
|||
[ key: string ]: number;
|
||||
};
|
||||
taxes_report_columns?: string;
|
||||
variable_product_tour_shown?: string;
|
||||
variations_report_columns?: string;
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useEffect, useState } from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { TourKit, TourKitTypes } from '@woocommerce/components';
|
||||
import { useUserPreferences } from '@woocommerce/data';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
|
||||
function getStepName(
|
||||
steps: TourKitTypes.WooStep[],
|
||||
currentStepIndex: number
|
||||
) {
|
||||
return steps[ currentStepIndex ]?.meta?.name;
|
||||
}
|
||||
|
||||
export const VariableProductTour = () => {
|
||||
const [ isTourOpen, setIsTourOpen ] = useState( false );
|
||||
|
||||
const { updateUserPreferences, variable_product_tour_shown: hasShownTour } =
|
||||
useUserPreferences();
|
||||
|
||||
const config: TourKitTypes.WooConfig = {
|
||||
steps: [
|
||||
{
|
||||
referenceElements: {
|
||||
desktop: '.attribute_tab',
|
||||
},
|
||||
focusElement: {
|
||||
desktop: '.attribute_tab',
|
||||
},
|
||||
meta: {
|
||||
name: 'attributes',
|
||||
heading: __( 'Start by adding attributes', 'woocommerce' ),
|
||||
descriptions: {
|
||||
desktop: __(
|
||||
'Add attributes like size and color for customers to choose from on the product page. We will use them to generate product variations.',
|
||||
'woocommerce'
|
||||
),
|
||||
},
|
||||
primaryButton: {
|
||||
text: __( 'Got it', 'woocommerce' ),
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
options: {
|
||||
// WooTourKit does not handle merging of default options properly,
|
||||
// so we need to duplicate the effects options here.
|
||||
effects: {
|
||||
spotlight: {
|
||||
interactivity: {
|
||||
enabled: true,
|
||||
rootElementSelector: '#wpwrap',
|
||||
},
|
||||
},
|
||||
arrowIndicator: true,
|
||||
liveResize: {
|
||||
mutation: true,
|
||||
resize: true,
|
||||
rootElementSelector: '#wpwrap',
|
||||
},
|
||||
},
|
||||
},
|
||||
closeHandler: ( steps, currentStepIndex ) => {
|
||||
updateUserPreferences( { variable_product_tour_shown: 'yes' } );
|
||||
setIsTourOpen( false );
|
||||
|
||||
if ( currentStepIndex === steps.length - 1 ) {
|
||||
recordEvent( 'variable_product_tour_completed', {
|
||||
step: getStepName(
|
||||
steps as TourKitTypes.WooStep[],
|
||||
currentStepIndex
|
||||
),
|
||||
} );
|
||||
} else {
|
||||
recordEvent( 'variable_product_tour_dismissed', {
|
||||
step: getStepName(
|
||||
steps as TourKitTypes.WooStep[],
|
||||
currentStepIndex
|
||||
),
|
||||
} );
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// show the tour when the product type is changed to variable
|
||||
useEffect( () => {
|
||||
const productTypeSelect = document.querySelector(
|
||||
'#product-type'
|
||||
) as HTMLSelectElement;
|
||||
|
||||
if ( hasShownTour === 'yes' || ! productTypeSelect ) {
|
||||
return;
|
||||
}
|
||||
|
||||
function handleProductTypeChange() {
|
||||
if ( productTypeSelect.value === 'variable' ) {
|
||||
setIsTourOpen( true );
|
||||
recordEvent( 'variable_product_tour_started', {
|
||||
step: getStepName( config.steps, 0 ),
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
productTypeSelect.addEventListener( 'change', handleProductTypeChange );
|
||||
|
||||
return () => {
|
||||
productTypeSelect.removeEventListener(
|
||||
'change',
|
||||
handleProductTypeChange
|
||||
);
|
||||
};
|
||||
} );
|
||||
|
||||
if ( ! isTourOpen ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <TourKit config={ config } />;
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { render } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { VariableProductTour } from '../../guided-tours/variable-product-tour';
|
||||
|
||||
const root = document.createElement( 'div' );
|
||||
root.setAttribute( 'id', 'variable-product-tour-root' );
|
||||
render( <VariableProductTour />, document.body.appendChild( root ) );
|
|
@ -65,6 +65,7 @@ const wpAdminScripts = [
|
|||
'settings-tracking',
|
||||
'order-tracking',
|
||||
'product-import-tracking',
|
||||
'variable-product-tour',
|
||||
];
|
||||
const getEntryPoints = () => {
|
||||
const entryPoints = {
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: enhancement
|
||||
|
||||
Show tour when product type is changed to variable.
|
|
@ -38,6 +38,7 @@ class WC_Admin_Pointers {
|
|||
switch ( $screen->id ) {
|
||||
case 'product':
|
||||
$this->create_product_tutorial();
|
||||
$this->create_variable_product_tutorial();
|
||||
break;
|
||||
case 'woocommerce_page_wc-addons':
|
||||
$this->create_wc_addons_tutorial();
|
||||
|
@ -64,6 +65,17 @@ class WC_Admin_Pointers {
|
|||
WCAdminAssets::register_script( 'wp-admin-scripts', 'product-tour', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Pointers for creating a variable product.
|
||||
*/
|
||||
public function create_variable_product_tutorial() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return;
|
||||
}
|
||||
|
||||
WCAdminAssets::register_script( 'wp-admin-scripts', 'variable-product-tour', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Pointers for accessing In-App Marketplace.
|
||||
*/
|
||||
|
|
|
@ -103,7 +103,13 @@ class WCAdminUser {
|
|||
* @return array Fields to expose over the WP user endpoint.
|
||||
*/
|
||||
public function get_user_data_fields() {
|
||||
return apply_filters( 'woocommerce_admin_get_user_data_fields', array() );
|
||||
/**
|
||||
* Filter user data fields exposed over the WordPress user endpoint.
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @param array $fields Array of fields to expose over the WP user endpoint.
|
||||
*/
|
||||
return apply_filters( 'woocommerce_admin_get_user_data_fields', array( 'variable_product_tour_shown' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -40,6 +40,32 @@ test.describe( 'Add New Variable Product Page', () => {
|
|||
await api.post( 'products/batch', { delete: ids } );
|
||||
} );
|
||||
|
||||
test( 'shows the variable product tour', async ( { page } ) => {
|
||||
await page.goto( 'wp-admin/post-new.php?post_type=product' );
|
||||
await page.selectOption( '#product-type', 'variable', { force: true } );
|
||||
|
||||
// because of the way that the tour is dynamically positioned,
|
||||
// Playwright can't automatically scroll the button into view,
|
||||
// so we will manually scroll the attributes tab into view,
|
||||
// which will cause the tour to be scrolled into view as well
|
||||
await page
|
||||
.locator( '.attribute_tab' )
|
||||
.getByRole( 'link', { name: 'Attributes' } )
|
||||
.scrollIntoViewIfNeeded();
|
||||
|
||||
// dismiss the variable product tour
|
||||
await page
|
||||
.getByRole( 'button', { name: 'Got it' } )
|
||||
.click( { force: true } );
|
||||
|
||||
// wait for the tour's dismissal to be saved
|
||||
await page.waitForResponse(
|
||||
( response ) =>
|
||||
response.url().includes( '/users/' ) &&
|
||||
response.status() === 200
|
||||
);
|
||||
} );
|
||||
|
||||
test( 'can create product, attributes and variations, edit variations and delete variations', async ( {
|
||||
page,
|
||||
} ) => {
|
||||
|
@ -54,10 +80,18 @@ test.describe( 'Add New Variable Product Page', () => {
|
|||
if ( i > 0 ) {
|
||||
await page.click( 'button.add_attribute' );
|
||||
}
|
||||
await page.waitForSelector( `input[name="attribute_names[${ i }]"]` );
|
||||
await page.waitForSelector(
|
||||
`input[name="attribute_names[${ i }]"]`
|
||||
);
|
||||
|
||||
await page.locator( `input[name="attribute_names[${ i }]"]` ).first().type( `attr #${ i + 1 }` );
|
||||
await page.locator( `textarea[name="attribute_values[${ i }]"]` ).first().type( 'val1 | val2' );
|
||||
await page
|
||||
.locator( `input[name="attribute_names[${ i }]"]` )
|
||||
.first()
|
||||
.type( `attr #${ i + 1 }` );
|
||||
await page
|
||||
.locator( `textarea[name="attribute_values[${ i }]"]` )
|
||||
.first()
|
||||
.type( 'val1 | val2' );
|
||||
}
|
||||
await page.click( 'text=Save attributes' );
|
||||
// wait for the attributes to be saved
|
||||
|
@ -167,10 +201,11 @@ test.describe( 'Add New Variable Product Page', () => {
|
|||
} );
|
||||
const variationsCount = await page.$$( '.woocommerce_variation' );
|
||||
await expect( variationsCount ).toHaveLength( 0 );
|
||||
|
||||
} );
|
||||
|
||||
test( 'can manually add a variation, manage stock levels, set variation defaults and remove a variation', async ( { page } ) => {
|
||||
test( 'can manually add a variation, manage stock levels, set variation defaults and remove a variation', async ( {
|
||||
page,
|
||||
} ) => {
|
||||
await page.goto( 'wp-admin/post-new.php?post_type=product' );
|
||||
await page.fill( '#title', manualVariableProduct );
|
||||
await page.selectOption( '#product-type', 'variable', { force: true } );
|
||||
|
@ -180,10 +215,18 @@ test.describe( 'Add New Variable Product Page', () => {
|
|||
if ( i > 0 ) {
|
||||
await page.click( 'button.add_attribute' );
|
||||
}
|
||||
await page.waitForSelector( `input[name="attribute_names[${ i }]"]` );
|
||||
await page.waitForSelector(
|
||||
`input[name="attribute_names[${ i }]"]`
|
||||
);
|
||||
|
||||
await page.locator( `input[name="attribute_names[${ i }]"]` ).first().type( `attr #${ i + 1 }` );
|
||||
await page.locator( `textarea[name="attribute_values[${ i }]"]` ).first().type( 'val1 | val2' );
|
||||
await page
|
||||
.locator( `input[name="attribute_names[${ i }]"]` )
|
||||
.first()
|
||||
.type( `attr #${ i + 1 }` );
|
||||
await page
|
||||
.locator( `textarea[name="attribute_values[${ i }]"]` )
|
||||
.first()
|
||||
.type( 'val1 | val2' );
|
||||
}
|
||||
await page.click( 'text=Save attributes' );
|
||||
// wait for the attributes to be saved
|
||||
|
@ -289,6 +332,8 @@ test.describe( 'Add New Variable Product Page', () => {
|
|||
page.on( 'dialog', ( dialog ) => dialog.accept() );
|
||||
await page.hover( '.woocommerce_variation' );
|
||||
await page.click( '.remove_variation.delete' );
|
||||
await expect( page.locator( '.woocommerce_variation' ) ).toHaveCount( 0 );
|
||||
await expect( page.locator( '.woocommerce_variation' ) ).toHaveCount(
|
||||
0
|
||||
);
|
||||
} );
|
||||
} );
|
||||
|
|
Loading…
Reference in New Issue