Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update tracking for blocks #311

Merged
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 17 additions & 75 deletions assets/js/src/actions.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { __ } from '@wordpress/i18n';

import { removeAction } from '@wordpress/hooks';
import { NAMESPACE, ACTION_PREFIX } from './constants';
import {
trackBeginCheckout,
trackShippingTier,
trackListProducts,
trackAddToCart,
trackChangeCartItemQuantity,
trackRemoveCartItem,
trackCheckoutStep,
trackCheckoutOption,
trackEvent,
trackSelectContent,
trackSearch,
trackViewItem,
Expand All @@ -17,79 +15,36 @@ import {
import { addUniqueAction } from './utils';

/**
* Track customer progress through steps of the checkout. Triggers the event when the step changes:
* 1 - Contact information
* 2 - Shipping address
* 3 - Billing address
* 4 - Shipping options
* 5 - Payment options
* Track begin_checkout
*
* @summary Track checkout progress with begin_checkout and checkout_progress
* @see https://developers.google.com/analytics/devguides/collection/gtagjs/enhanced-ecommerce#1_measure_checkout_steps
* @summary Track the customer has started the checkout process
* @see https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtag#begin_checkout
*/
addUniqueAction(
`${ ACTION_PREFIX }-checkout-render-checkout-form`,
NAMESPACE,
( { ...storeCart } ) => trackCheckoutStep( 0 )( storeCart )
);
addUniqueAction(
`${ ACTION_PREFIX }-checkout-set-email-address`,
NAMESPACE,
( { ...storeCart } ) => trackCheckoutStep( 1 )( storeCart )
);
addUniqueAction(
`${ ACTION_PREFIX }-checkout-set-shipping-address`,
NAMESPACE,
( { ...storeCart } ) => trackCheckoutStep( 2 )( storeCart )
);
addUniqueAction(
`${ ACTION_PREFIX }-checkout-set-billing-address`,
NAMESPACE,
( { ...storeCart } ) => trackCheckoutStep( 3 )( storeCart )
);
addUniqueAction(
`${ ACTION_PREFIX }-checkout-set-phone-number`,
NAMESPACE,
( { step, ...storeCart } ) => {
trackCheckoutStep( step === 'shipping' ? 2 : 3 )( storeCart );
}
trackBeginCheckout
);

/**
* Choose a shipping rate
* Track add_shipping_info
*
* @summary Track the shipping rate being set using set_checkout_option
* @see https://developers.google.com/analytics/devguides/collection/gtagjs/enhanced-ecommerce#2_measure_checkout_options
* @summary Track the selected shipping tier when the checkout form is submitted
* @see https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtag#add_shipping_info
*/
addUniqueAction(
`${ ACTION_PREFIX }-checkout-set-selected-shipping-rate`,
`${ ACTION_PREFIX }-checkout-submit`,
NAMESPACE,
( { shippingRateId } ) => {
trackCheckoutOption( {
step: 4,
option: __( 'Shipping Method', 'woo-gutenberg-products-block' ),
value: shippingRateId,
} )();
}
trackShippingTier
);

/**
* Choose a payment method
*
* @summary Track the payment method being set using set_checkout_option
* @see https://developers.google.com/analytics/devguides/collection/gtagjs/enhanced-ecommerce#2_measure_checkout_options
* The following actions were previously tracked using]checkout_progress
* in UA but there is no comparable event in GA4.
*/
addUniqueAction(
`${ ACTION_PREFIX }-checkout-set-active-payment-method`,
NAMESPACE,
( { paymentMethodSlug } ) => {
trackCheckoutOption( {
step: 5,
option: __( 'Payment Method', 'woo-gutenberg-products-block' ),
value: paymentMethodSlug,
} )();
}
);
removeAction( `${ ACTION_PREFIX }-checkout-set-email-address`, NAMESPACE );
removeAction( `${ ACTION_PREFIX }-checkout-set-phone-number`, NAMESPACE );
removeAction( `${ ACTION_PREFIX }-checkout-set-billing-address`, NAMESPACE );

/**
* Product List View
Expand Down Expand Up @@ -140,19 +95,6 @@ addUniqueAction(
trackRemoveCartItem
);

/**
* Add Payment Information
*
* This event signifies a user has submitted their payment information. Note, this is used to indicate checkout
* submission, not `purchase` which is triggered on the thanks page.
*
* @summary Track the add_payment_info event
* @see https://developers.google.com/gtagjs/reference/ga4-events#add_payment_info
*/
addUniqueAction( `${ ACTION_PREFIX }-checkout-submit`, NAMESPACE, () => {
trackEvent( 'add_payment_info' );
} );

/**
* Product View Link Clicked
*
Expand Down
125 changes: 48 additions & 77 deletions assets/js/src/tracking.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,10 @@ import { __ } from '@wordpress/i18n';
import {
getProductFieldObject,
getProductImpressionObject,
getProductId,
formatPrice,
} from './utils';

/**
* Variable holding the current checkout step. It will be modified by trackCheckoutOption and trackCheckoutStep methods.
*
* @type {number}
*/
let currentStep = -1;

/**
* Tracks view_item_list event
*
Expand All @@ -23,17 +17,19 @@ export const trackListProducts = ( {
products,
listName = __( 'Product List', 'woocommerce-google-analytics-integration' ),
} ) => {
trackEvent( 'view_item_list', {
event_category: 'engagement',
event_label: __(
'Viewing products',
'woocommerce-google-analytics-integration'
),
items: products.map( ( product, index ) => ( {
...getProductImpressionObject( product, listName ),
list_position: index + 1,
} ) ),
} );
if ( products.length > 0 ) {
trackEvent( 'view_item_list', {
item_list_id: 'engagement',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📜 💅
If I understand the docs correctly, this id is to identify the list from which we're reporting.
With blocks, we can have multiple lists on different pages. But currently, we send engagement regardless of which list we track. Maybe we could somehow forward the block instance id, or at least page ID?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be a nice addition! I've opened issue #324 so as to not get off track in this PR.

item_list_name: __(
'Viewing products',
'woocommerce-google-analytics-integration'
),
items: products.map( ( product, index ) => ( {
...getProductImpressionObject( product, listName ),
index: index + 1,
} ) ),
} );
}
};

/**
Expand All @@ -45,11 +41,6 @@ export const trackListProducts = ( {
*/
export const trackAddToCart = ( { product, quantity = 1 } ) => {
trackEvent( 'add_to_cart', {
event_category: 'ecommerce',
event_label: __(
'Add to Cart',
'woocommerce-google-analytics-integration'
),
items: [ getProductFieldObject( product, quantity ) ],
} );
};
Expand All @@ -63,11 +54,6 @@ export const trackAddToCart = ( { product, quantity = 1 } ) => {
*/
export const trackRemoveCartItem = ( { product, quantity = 1 } ) => {
trackEvent( 'remove_from_cart', {
event_category: 'ecommerce',
event_label: __(
'Remove Cart Item',
'woocommerce-google-analytics-integration'
),
items: [ getProductFieldObject( product, quantity ) ],
} );
};
Expand All @@ -91,70 +77,55 @@ export const trackChangeCartItemQuantity = ( { product, quantity = 1 } ) => {
};

/**
* Track a begin_checkout and checkout_progress event
* Notice calling this will set the current checkout step as the step provided in the parameter.
* Track begin_checkout event
*
* @param {number} step The checkout step for to track
* @return {(function( { storeCart: Object } ): void)} A callable receiving the cart to track the checkout event.
* @param {Object} params The function params
* @param {Object} params.storeCart The cart object
*/
export const trackCheckoutStep =
( step ) =>
( { storeCart } ) => {
if ( currentStep === step ) {
return;
}

trackEvent( step === 0 ? 'begin_checkout' : 'checkout_progress', {
items: storeCart.cartItems.map( getProductFieldObject ),
coupon: storeCart.cartCoupons[ 0 ]?.code || '',
currency: storeCart.cartTotals.currency_code,
value: formatPrice(
storeCart.cartTotals.total_price,
storeCart.cartTotals.currency_minor_unit
),
checkout_step: step,
} );

currentStep = step;
};
export const trackBeginCheckout = ( { storeCart } ) => {
trackEvent( 'begin_checkout', {
currency: storeCart.totals.currency_code,
value: formatPrice(
storeCart.totals.total_price,
storeCart.totals.currency_minor_unit
),
Comment on lines +89 to +92
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation states it should be a number, we send a string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch! Updated in 4a06f52

coupon: storeCart.coupons[ 0 ]?.code || '',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


I wonder if the empty "" in case of lack of coupon is the most accurate representation. Shouldn't we just don't send this property in case of lack of coupon? I'm not sure how the value being set (event to empty string) would be interpreted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point. I'm not sure what the behavior would be and from a quick search I couldn't find any thing in the GA documentation about it 🤔 I've updated how it works in 813b7ee so we're not including an empty string.

items: storeCart.items.map( getProductFieldObject ),
} );
};

/**
* Track a set_checkout_option event
* Notice calling this will set the current checkout step as the step provided in the parameter.
*
* @param {Object} params The params from the option.
* @param {number} params.step The step to track
* @param {string} params.option The option to set in checkout
* @param {string} params.value The value for the option
* Track add_shipping_info event
*
* @return {(function() : void)} A callable to track the checkout event.
* @param {Object} params The function params
* @param {Object} params.storeCart The cart object
*/
export const trackCheckoutOption =
( { step, option, value } ) =>
() => {
trackEvent( 'set_checkout_option', {
checkout_step: step,
checkout_option: option,
value,
} );

currentStep = step;
};
export const trackShippingTier = ( { storeCart } ) => {
trackEvent( 'add_shipping_info', {
currency: storeCart.totals.currency_code,
value: formatPrice(
storeCart.totals.total_price,
storeCart.totals.currency_minor_unit
),
coupon: storeCart.coupons[ 0 ]?.code || '',
shipping_tier:
storeCart.shippingRates[ 0 ]?.shipping_rates?.find(
( rate ) => rate.selected
)?.name || '',
items: storeCart.items.map( getProductFieldObject ),
} );
};

/**
* Tracks select_content event.
*
* @param {Object} params The function params
* @param {Object} params.product The product to track
* @param {string} params.listName The name of the list in which the item was presented to the user.
*/
export const trackSelectContent = ( {
product,
listName = __( 'Product List', 'woocommerce-google-analytics-integration' ),
} ) => {
export const trackSelectContent = ( { product } ) => {
trackEvent( 'select_content', {
content_type: 'product',
items: [ getProductImpressionObject( product, listName ) ],
content_id: getProductId( product ),
} );
};

Expand Down
50 changes: 38 additions & 12 deletions assets/js/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import { addAction, removeAction } from '@wordpress/hooks';
*/
export const getProductFieldObject = ( product, quantity ) => {
return {
id: getProductId( product ),
name: product.name,
quantity,
category: getProductCategory( product ),
item_id: getProductId( product ),
item_name: product.name,
quantity: product.quantity ?? quantity,
...getProductCategories( product ),
price: formatPrice(
product.prices.price,
product.prices.currency_minor_unit
Expand All @@ -33,10 +33,10 @@ export const getProductFieldObject = ( product, quantity ) => {
*/
export const getProductImpressionObject = ( product, listName ) => {
return {
id: getProductId( product ),
name: product.name,
list_name: listName,
category: getProductCategory( product ),
item_id: getProductId( product ),
item_name: product.name,
item_list_name: listName,
...getProductCategories( product ),
price: formatPrice(
product.prices.price,
product.prices.currency_minor_unit
Expand Down Expand Up @@ -75,7 +75,7 @@ export const addUniqueAction = ( hookName, namespace, callback ) => {
*
* @return {string} - The product ID
*/
const getProductId = ( product ) => {
export const getProductId = ( product ) => {
return product.sku ? product.sku : '#' + product.id;
};

Expand All @@ -86,8 +86,34 @@ const getProductId = ( product ) => {
*
* @return {string} - The name of the first category of the product or an empty string if the product has no categories.
*/
const getProductCategory = ( product ) => {
const getProductCategories = ( product ) => {
return 'categories' in product && product.categories.length
? product.categories[ 0 ].name
: '';
? getCategoryObject( product.categories )
: {};
};

/**
* Returns an object containing up to 5 categories for the product.
*
* @param {Object} categories - An array of product categories
*
* @return {Object} - An categories object
*/
const getCategoryObject = ( categories ) => {
return Object.fromEntries(
categories.slice( 0, 5 ).map( ( category, index ) => {
return [ formatCategoryKey( index ), category.name ];
} )
);
};

/**
* Returns the correctly formatted key for the category object.
*
* @param {number} index Index of the current category
*
* @return {string} - A formatted key for the category object
*/
const formatCategoryKey = ( index ) => {
return 'item_category' + ( index > 0 ? index + 1 : '' );
};