Add tracks to new product and edit product screens (#33120)

* Move product list tracks to seperate file

* Change product list tracks to TS

* Check init of tracks on client-side

* Add new product tracks

* Add product screen tracks

* Add product edit tracks

* Localize product screen variable

* Add changelog entry

* Handle PR feedback

* Track date changes on edit
This commit is contained in:
Joshua T Flowers 2022-05-26 16:56:22 -04:00 committed by GitHub
parent 2294082869
commit e4824b9660
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 440 additions and 134 deletions

View File

@ -0,0 +1 @@
declare const productScreen: string;

View File

@ -1,127 +0,0 @@
/**
* External dependencies
*/
import { recordEvent } from '@woocommerce/tracks';
const actionButtons = document.querySelectorAll( '.row-actions span' );
const bulkActions = document.querySelector( '#bulk-action-selector-top' );
const bulkActionsButton = document.querySelector( '#doaction' );
const bulkActionsCancelButton = document.querySelector( '#bulk-edit .cancel' );
const bulkActionsUpdateButton = document.querySelector( '#bulk_edit' );
const featuredButtons = document.querySelectorAll( '#the-list .featured a' );
const filterButton = document.querySelector( '#post-query-submit' );
const productCategory = document.querySelector( '#product_cat' );
const productType = document.querySelector( '#dropdown_product_type' );
const searchButton = document.querySelector( '#search-submit' );
const searchInput = document.querySelector( '#post-search-input' );
const sortableColumnHeaders = document.querySelectorAll(
'.wp-list-table.posts thead .sortable a, .wp-list-table.posts thead .sorted a'
);
const stockStatus = document.querySelector( '[name="stock_status"]' );
const hasValue = ( selector ) => {
const element = document.querySelector( selector );
return !! element && element.value !== '' && element.value !== '-1';
};
filterButton?.addEventListener( 'click', function () {
recordEvent( 'products_list_filter_click', {
search_string_length: searchInput?.value.length,
filter_category: productCategory.value !== '',
filter_product_type: productType.value,
filter_stock_status: stockStatus.value,
} );
} );
bulkActionsButton?.addEventListener( 'click', function () {
const productNumber = document.querySelectorAll( '[name="post[]"]:checked' )
.length;
recordEvent( 'products_list_bulk_actions_click', {
selected_action: bulkActions.value,
product_number: productNumber,
} );
} );
bulkActionsUpdateButton?.addEventListener( 'click', function () {
recordEvent( 'products_list_bulk_edit_update', {
product_number: document.querySelector( '#bulk-titles' )?.children
.length,
product_categories:
document.querySelectorAll(
'[name="tax_input[product_cat][]"]:checked'
)?.length > 0,
comments: hasValue( '[name="comment_status"]' ),
status: hasValue( '[name="_status"]' ),
product_tags: hasValue( '[name="tax_input[product_tag]"]' ),
price: hasValue( '[name="change_regular_price"]' ),
sale: hasValue( '[name="change_sale_price"]' ),
tax_status: hasValue( '[name="_tax_status"]' ),
tax_class: hasValue( '[name="_tax_class"]' ),
weight: hasValue( '[name="change_weight"]' ),
dimensions: hasValue( '[name="change_dimensions"]' ),
shipping_class: hasValue( '[name="_shipping_class"]' ),
visibility: hasValue( '[name="_visibility"]' ),
featured: hasValue( '[name="_featured"]' ),
stock_status: hasValue( '[name="_stock_status"]' ),
manage_stock: hasValue( '[name="_manage_stock"]' ),
stock_quantity: hasValue( '[name="change_stock"]' ),
backorders: hasValue( '[name="_backorders"]' ),
sold_individually: hasValue( '[name="_sold_individually"]' ),
} );
} );
bulkActionsCancelButton?.addEventListener( 'click', function () {
recordEvent( 'products_list_bulk_edit_cancel' );
} );
actionButtons.forEach( ( button ) => {
button.addEventListener( 'click', function ( event ) {
const actionClass = event.target.parentElement.classList[ 0 ];
const actions = {
edit: 'edit',
inline: 'quick_edit',
trash: 'trash',
view: 'preview',
duplicate: 'duplicate',
};
if ( ! actions[ actionClass ] ) {
return;
}
recordEvent( 'products_list_product_action_click', {
selected_action: actions[ actionClass ],
} );
} );
} );
featuredButtons.forEach( ( button ) => {
button.addEventListener( 'click', function ( event ) {
const willFeature = event.target.classList.contains( 'not-featured' );
recordEvent( 'products_list_featured_click', {
featured: willFeature ? 'yes' : 'no',
} );
} );
} );
searchButton?.addEventListener( 'click', function () {
recordEvent( 'products_search', {
search_string_length: searchInput.value.length,
filter_category: productCategory.value !== '',
filter_product_type: productType.value,
filter_stock_status: stockStatus.value,
} );
} );
sortableColumnHeaders.forEach( ( header ) => {
header.addEventListener( 'click', function ( event ) {
const tableHeader = event.target.closest( 'th' );
const willBeDescending = tableHeader.classList.contains( 'asc' );
recordEvent( 'products_list_column_header_click', {
field_slug: tableHeader.id,
order: willBeDescending ? 'desc' : 'asc',
} );
} );
} );

View File

@ -0,0 +1,6 @@
/**
* Internal dependencies
*/
import './products-list';
import './product-edit';
import './product-new';

View File

@ -0,0 +1,18 @@
/**
* External dependencies
*/
import { recordEvent } from '@woocommerce/tracks';
/**
* Internal dependencies
*/
import { initProductScreenTracks } from './shared';
const initTracks = () => {
recordEvent( 'product_edit_view' );
initProductScreenTracks();
};
if ( productScreen === 'edit' ) {
initTracks();
}

View File

@ -0,0 +1,18 @@
/**
* External dependencies
*/
import { recordEvent } from '@woocommerce/tracks';
/**
* Internal dependencies
*/
import { initProductScreenTracks } from './shared';
const initTracks = () => {
recordEvent( 'product_add_view' );
};
if ( productScreen === 'new' ) {
initTracks();
initProductScreenTracks();
}

View File

@ -0,0 +1,158 @@
/**
* External dependencies
*/
import { recordEvent } from '@woocommerce/tracks';
const initTracks = () => {
const actionButtons = document.querySelectorAll( '.row-actions span' );
const bulkActions = document.querySelector(
'#bulk-action-selector-top'
) as HTMLInputElement;
const bulkActionsButton = document.querySelector( '#doaction' );
const bulkActionsCancelButton = document.querySelector(
'#bulk-edit .cancel'
);
const bulkActionsUpdateButton = document.querySelector( '#bulk_edit' );
const featuredButtons = document.querySelectorAll(
'#the-list .featured a'
);
const filterButton = document.querySelector( '#post-query-submit' );
const productCategory = document.querySelector(
'#product_cat'
) as HTMLInputElement;
const productType = document.querySelector(
'#dropdown_product_type'
) as HTMLInputElement;
const searchButton = document.querySelector( '#search-submit' );
const searchInput = document.querySelector(
'#post-search-input'
) as HTMLInputElement;
const sortableColumnHeaders = document.querySelectorAll(
'.wp-list-table.posts thead .sortable a, .wp-list-table.posts thead .sorted a'
);
const stockStatus = document.querySelector(
'[name="stock_status"]'
) as HTMLInputElement;
const hasValue = ( selector: string ) => {
const element = <HTMLInputElement>document.querySelector( selector );
return !! element && element.value !== '' && element.value !== '-1';
};
filterButton?.addEventListener( 'click', function () {
recordEvent( 'products_list_filter_click', {
search_string_length: searchInput?.value.length,
filter_category: productCategory.value !== '',
filter_product_type: productType.value,
filter_stock_status: stockStatus.value,
} );
} );
bulkActionsButton?.addEventListener( 'click', function () {
const productNumber = document.querySelectorAll(
'[name="post[]"]:checked'
).length;
recordEvent( 'products_list_bulk_actions_click', {
selected_action: bulkActions.value,
product_number: productNumber,
} );
} );
bulkActionsUpdateButton?.addEventListener( 'click', function () {
recordEvent( 'products_list_bulk_edit_update', {
product_number: document.querySelector( '#bulk-titles' )?.children
.length,
product_categories:
document.querySelectorAll(
'[name="tax_input[product_cat][]"]:checked'
)?.length > 0,
comments: hasValue( '[name="comment_status"]' ),
status: hasValue( '[name="_status"]' ),
product_tags: hasValue( '[name="tax_input[product_tag]"]' ),
price: hasValue( '[name="change_regular_price"]' ),
sale: hasValue( '[name="change_sale_price"]' ),
tax_status: hasValue( '[name="_tax_status"]' ),
tax_class: hasValue( '[name="_tax_class"]' ),
weight: hasValue( '[name="change_weight"]' ),
dimensions: hasValue( '[name="change_dimensions"]' ),
shipping_class: hasValue( '[name="_shipping_class"]' ),
visibility: hasValue( '[name="_visibility"]' ),
featured: hasValue( '[name="_featured"]' ),
stock_status: hasValue( '[name="_stock_status"]' ),
manage_stock: hasValue( '[name="_manage_stock"]' ),
stock_quantity: hasValue( '[name="change_stock"]' ),
backorders: hasValue( '[name="_backorders"]' ),
sold_individually: hasValue( '[name="_sold_individually"]' ),
} );
} );
bulkActionsCancelButton?.addEventListener( 'click', function () {
recordEvent( 'products_list_bulk_edit_cancel' );
} );
actionButtons.forEach( ( button ) => {
button.addEventListener( 'click', function ( event ) {
const actionClass = ( event.target as HTMLElement )?.parentElement
?.classList[ 0 ];
interface actionsInterface {
[ key: string ]: string;
}
const actions: actionsInterface = {
edit: 'edit',
inline: 'quick_edit',
trash: 'trash',
view: 'preview',
duplicate: 'duplicate',
};
if ( ! actionClass || ! actions[ actionClass ] ) {
return;
}
recordEvent( 'products_list_product_action_click', {
selected_action: actions[ actionClass ],
} );
} );
} );
featuredButtons.forEach( ( button ) => {
button.addEventListener( 'click', function ( event ) {
const willFeature = ( event.target as HTMLElement ).classList.contains(
'not-featured'
);
recordEvent( 'products_list_featured_click', {
featured: willFeature ? 'yes' : 'no',
} );
} );
} );
searchButton?.addEventListener( 'click', function () {
recordEvent( 'products_search', {
search_string_length: searchInput.value.length,
filter_category: productCategory.value !== '',
filter_product_type: productType.value,
filter_stock_status: stockStatus.value,
} );
} );
sortableColumnHeaders.forEach( ( header ) => {
header.addEventListener( 'click', function ( event ) {
const tableHeader = ( event.target as HTMLElement ).closest( 'th' );
if ( ! tableHeader ) {
return;
}
const willBeDescending = tableHeader.classList.contains( 'asc' );
recordEvent( 'products_list_column_header_click', {
field_slug: tableHeader.id,
order: willBeDescending ? 'desc' : 'asc',
} );
} );
} );
};
if ( productScreen === 'list' ) {
initTracks();
}

View File

@ -0,0 +1,196 @@
/**
* External dependencies
*/
import { recordEvent } from '@woocommerce/tracks';
/**
* Get the product data.
*
* @return object
*/
const getProductData = () => {
return {
product_id: ( document.querySelector( '#post_ID' ) as HTMLInputElement )
?.value,
product_type: ( document.querySelector(
'#product-type'
) as HTMLInputElement )?.value,
is_downloadable: ( document.querySelector(
'#_downloadable'
) as HTMLInputElement )?.value,
is_virtual: ( document.querySelector(
'#_virtual'
) as HTMLInputElement )?.value,
manage_stock: ( document.querySelector(
'#_manage_stock'
) as HTMLInputElement )?.value,
};
};
/**
* Get the publish date as a string.
*
* @param prefix Prefix for date element selectors.
* @return string
*/
const getPublishDate = ( prefix = '' ) => {
const month = ( document.querySelector(
`#${ prefix }mm`
) as HTMLInputElement )?.value;
const day = ( document.querySelector(
`#${ prefix }jj`
) as HTMLInputElement )?.value;
const year = ( document.querySelector(
`#${ prefix }aa`
) as HTMLInputElement )?.value;
const hours = ( document.querySelector(
`#${ prefix }hh`
) as HTMLInputElement )?.value;
const seconds = ( document.querySelector(
`#${ prefix }mn`
) as HTMLInputElement )?.value;
return `${ month }-${ day }-${ year } ${ hours }:${ seconds }`;
};
/**
* Get the data from the publishing widget.
*
* @return object
*/
const getPublishingWidgetData = () => {
return {
status: ( document.querySelector( '#post_status' ) as HTMLInputElement )
?.value,
visibility: ( document.querySelector(
'input[name="visibility"]:checked'
) as HTMLInputElement )?.value,
date: getPublishDate() !== getPublishDate( 'hidden_' ) ? 'yes' : 'no',
catalog_visibility: ( document.querySelector(
'input[name="_visibility"]:checked'
) as HTMLInputElement )?.value,
featured: ( document.querySelector( '#_featured' ) as HTMLInputElement )
?.checked,
};
};
/**
* Prefix all object keys with a string.
*
* @param obj Object to create keys from.
* @param prefix Prefix used before all keys.
* @return object
*/
const prefixObjectKeys = (
obj: { [ key: string ]: unknown },
prefix: string
) => {
return Object.fromEntries(
Object.entries( obj ).map( ( [ k, v ] ) => [ `${ prefix }${ k }`, v ] )
);
};
/**
* Initialize all product screen tracks.
*/
export const initProductScreenTracks = () => {
const initialPublishingData = getPublishingWidgetData();
document
.querySelector( '#post-preview' )
?.addEventListener( 'click', () => {
recordEvent( 'product_preview_changes' );
} );
document
.querySelector( '.submitduplicate' )
?.addEventListener( 'click', () => {
recordEvent( 'product_copy', getProductData() );
} );
document
.querySelector( '.submitdelete' )
?.addEventListener( 'click', () => {
recordEvent( 'product_delete', getProductData() );
} );
document
.querySelectorAll(
'.edit-post-status, .edit-visibility, .edit-timestamp, .edit-catalog-visibility'
)
.forEach( ( button ) => {
button.addEventListener( 'click', () => {
recordEvent( 'product_publish_widget_edit', {
...getPublishingWidgetData(),
...getProductData(),
} );
} );
} );
document
.querySelectorAll(
'.save-post-status, .save-post-visibility, .save-timestamp, .save-post-visibility'
)
.forEach( ( button ) => {
button.addEventListener( 'click', () => {
recordEvent( 'product_publish_widget_save', {
...prefixObjectKeys( getPublishingWidgetData(), 'new_' ),
...prefixObjectKeys( initialPublishingData, 'current_' ),
...getProductData(),
} );
} );
} );
document
.querySelectorAll( '.handle-order-lower, .handle-order-higher' )
.forEach( ( button ) => {
button.addEventListener( 'click', ( event ) => {
const postBox = ( event.target as HTMLElement ).closest(
'.postbox'
);
if ( ! postBox ) {
return;
}
recordEvent( 'product_widget_order_change', {
widget: postBox.id,
} );
} );
} );
document
.querySelector( '#show-settings-link' )
?.addEventListener( 'click', () => {
recordEvent( 'product_screen_options_open' );
} );
document
.querySelectorAll( '#adv-settings .metabox-prefs input[type=checkbox]' )
.forEach( ( input ) => {
input.addEventListener( 'change', () => {
recordEvent( 'product_screen_elements', {
selected_element: ( input as HTMLInputElement ).value,
checkbox: ( input as HTMLInputElement ).checked,
} );
} );
} );
document
.querySelectorAll( 'input[name="screen_columns"]' )
.forEach( ( input ) => {
input.addEventListener( 'change', () => {
recordEvent( 'product_layout', {
selected_layout: ( input as HTMLInputElement ).value,
} );
} );
} );
document
.querySelector( '#editor-expand-toggle' )
?.addEventListener( 'change', ( event ) => {
recordEvent( 'product_additional_settings', {
checkbox: ( event.target as HTMLInputElement ).checked,
} );
} );
};

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add tracks to new product and edit product screens #33120

View File

@ -219,21 +219,51 @@ class WC_Products_Tracking {
WC_Tracks::record_event( 'product_category_add', $properties );
}
/**
* Get the product screen name if current hook and page is a products type page.
*
* @param string $hook Hook of the current page.
* @return string|boolean
*/
protected function get_product_screen( $hook ) {
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification
if (
'edit.php' === $hook &&
isset( $_GET['post_type'] ) &&
'product' === wp_unslash( $_GET['post_type'] )
) {
return 'list';
}
if (
'post-new.php' === $hook &&
'product' === wp_unslash( $_GET['post_type'] )
) {
return 'new';
}
if (
'post.php' === $hook &&
isset( $_GET['post'] ) &&
'product' === get_post_type( intval( $_GET['post'] ) )
) {
return 'edit';
}
// phpcs:enable
return false;
}
/**
* Adds the tracking scripts for product filtering actions.
*
* @param string $hook Page hook.
*/
public function possibly_add_tracking_scripts( $hook ) {
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification
if (
'edit.php' !== $hook ||
! isset( $_GET['post_type'] ) ||
'product' !== wp_unslash( $_GET['post_type'] )
) {
$product_screen = $this->get_product_screen( $hook );
if ( ! $product_screen ) {
return;
}
// phpcs:enable
$script_assets_filename = WCAdminAssets::get_script_asset_filename( 'wp-admin-scripts', 'product-tracking' );
$script_assets = require WC_ADMIN_ABSPATH . WC_ADMIN_DIST_JS_FOLDER . 'wp-admin-scripts/' . $script_assets_filename;
@ -245,5 +275,7 @@ class WC_Products_Tracking {
WCAdminAssets::get_file_version( 'js' ),
true
);
wp_localize_script( 'wc-admin-product-tracking', 'productScreen', $product_screen );
}
}