Skip to content

Commit

Permalink
Merge pull request #2158 from graphcommerce-org/feature/GCOM-1312
Browse files Browse the repository at this point in the history
Refactor GA4 / GTM tracking preferences (GCOM-1312)
  • Loading branch information
paales authored Mar 27, 2024
2 parents 488ccc2 + 429616c commit 4194be6
Show file tree
Hide file tree
Showing 73 changed files with 1,502 additions and 842 deletions.
7 changes: 7 additions & 0 deletions .changeset/flat-wombats-lie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@graphcommerce/google-datalayer": patch
"@graphcommerce/googletagmanager": patch
"@graphcommerce/googleanalytics": patch
---

Extracted the datalayer from the googleanalytics package and moved to google-datalayer package. Make sure Google Analytics and Google Tagmanager both can send events individually. Be able to configure the datalayer will send as GA4 or legacy GA3 events.
10 changes: 10 additions & 0 deletions docs/framework/config.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
<!-- Automatically generated from Config.graphqls -->
### AnalyticsConfig

AnalyticsConfig will contain all configuration values for the analytics in GraphCommerce.

#### eventFormat: GA3 | GA4[]

eventFormat contains the list of fired and formatted events

# GraphCommerce configuration system

Global GraphCommerce configuration can be configured in your `graphcommerce.config.js` file
Expand Down Expand Up @@ -105,6 +113,8 @@ Examples:

All storefront configuration for the project

#### analytics: [AnalyticsConfig](#AnalyticsConfig)

#### cartDisplayPricesInclTax: boolean

Due to a limitation of the GraphQL API it is not possible to determine if a cart should be displayed including or excluding tax.
Expand Down
21 changes: 21 additions & 0 deletions packages/google-datalayer/Config.graphqls
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
extend input GraphCommerceConfig {
analytics: AnalyticsConfig
}

"""
EventFormat is an enumatation of different event formats. This decides what the format of the event data will be.
"""
enum EventFormat {
GA3
GA4
}

"""
AnalyticsConfig will contain all configuration values for the analytics in GraphCommerce.
"""
input AnalyticsConfig {
"""
eventFormat contains the list of fired and formatted events
"""
eventFormat: [EventFormat!]
}
6 changes: 6 additions & 0 deletions packages/google-datalayer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# @graphcommerce/google-datalayer

This package contains utilities that can be used in different app analytics
trackers.

add_payment_info add_shipping_info
63 changes: 63 additions & 0 deletions packages/google-datalayer/components/AnalyticsItemList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { nonNullable, useMemoObject } from '@graphcommerce/next-ui'
import { useEventCallback } from '@mui/material'
import React, { useContext, useEffect, useMemo } from 'react'
import { Item, ProductToItemFragment, productToItem } from '../lib'
import { event } from '../lib/event'

export type UseViewItemListProps<P extends ProductToItemFragment = ProductToItemFragment> = {
title: string
items?: (P | null | undefined)[] | null
listId?: string
}

export type ViewItemList = {
item_list_id: string
item_list_name: string
items: Item[]
}

const GoogleTagManagerItemListContext = React.createContext<{
item_list_id: string
item_list_name: string
}>({ item_list_id: '', item_list_name: '' })

export function useListItemHandler(item: ProductToItemFragment) {
const { item_list_id, item_list_name } = useContext(GoogleTagManagerItemListContext)
return useEventCallback(() =>
event('select_item', {
item_list_id,
item_list_name,
items: productToItem(item),
}),
)
}

export function ItemList<P extends ProductToItemFragment = ProductToItemFragment>(
props: UseViewItemListProps<P> & { children: React.ReactNode },
) {
const { title, items, listId, children } = props

const eventData: ViewItemList = useMemoObject({
item_list_id: listId ?? title?.toLowerCase().replace(/\s/g, '_'),
item_list_name: title,
items: items?.map((item) => (item ? productToItem(item) : null)).filter(nonNullable) ?? [],
})

useEffect(() => {
event('view_item_list', eventData)
}, [eventData])

const value = useMemo(
() => ({
item_list_id: listId ?? title?.toLowerCase().replace(/\s/g, '_'),
item_list_name: title ?? listId,
}),
[listId, title],
)

return (
<GoogleTagManagerItemListContext.Provider value={value}>
{children}
</GoogleTagManagerItemListContext.Provider>
)
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
fragment GtagAddPaymentInfo on Cart
fragment AddPaymentInfoFragment on Cart
@inject(into: ["PaymentMethodContext", "PaymentMethodUpdated"]) {
items {
uid
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { GtagAddPaymentInfoFragment } from './GtagAddPaymentInfo.gql'
import { event } from '../../lib/event'
import { AddPaymentInfoFragment } from './AddPaymentInfoFragment.gql'

export function gtagAddPaymentInfo<C extends GtagAddPaymentInfoFragment>(cart?: C | null) {
globalThis.gtag?.('event', 'add_payment_info', {
export const addPaymentInfo = <C extends AddPaymentInfoFragment>(cart?: C | null) =>
event('add_payment_info', {
...cart,
currency: cart?.prices?.grand_total?.currency,
value: cart?.prices?.grand_total?.value,
coupon: cart?.applied_coupons?.map((coupon) => coupon?.code),
Expand All @@ -19,4 +21,3 @@ export function gtagAddPaymentInfo<C extends GtagAddPaymentInfoFragment>(cart?:
quantity: item?.quantity,
})),
})
}
2 changes: 2 additions & 0 deletions packages/google-datalayer/events/add_payment_info/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './addPaymentInfo'
export * from './AddPaymentInfoFragment.gql'
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
fragment GtagAddShippingInfo on Cart @inject(into: ["ShippingMethodSelected"]) {
fragment AddShippingInfoFragment on Cart @inject(into: ["ShippingMethodSelected"]) {
prices {
grand_total {
currency
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { GtagAddShippingInfoFragment } from './GtagAddShippingInfo.gql'
import { event } from '../../lib/event'
import { AddShippingInfoFragment } from './AddSchippingInfoFragment.gql'

export function gtagAddShippingInfo<C extends GtagAddShippingInfoFragment>(cart?: C | null) {
if (!cart) return

globalThis.gtag?.('event', 'add_shipping_info', {
export const addShippingInfo = <C extends AddShippingInfoFragment>(cart?: C) =>
event('add_shipping_info', {
...cart,
currency: cart?.prices?.grand_total?.currency,
value: cart?.prices?.grand_total?.value,
coupon: cart?.applied_coupons?.map((coupon) => coupon?.code),
Expand All @@ -15,9 +15,8 @@ export function gtagAddShippingInfo<C extends GtagAddShippingInfoFragment>(cart?
(sum, discount) => sum + (discount?.amount?.value ?? 0),
0,
),
item_variant: item?.__typename === 'ConfigurableCartItem' ? item.configured_variant.sku : '',
item_variant: item && 'configured_variant' in item ? item.configured_variant.sku : '',
price: item?.prices?.price.value,
quantity: item?.quantity,
})),
})
}
2 changes: 2 additions & 0 deletions packages/google-datalayer/events/add_shipping_info/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './addShippingInfo'
export * from './AddSchippingInfoFragment.gql'
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
fragment GtagAddToCart on Cart @inject(into: ["CartItemCountChanged"]) {
fragment AddToCartFragment on Cart @inject(into: ["CartItemCountChanged"]) {
items {
quantity
product {
sku
name
}
prices {
discounts {
amount {
Expand All @@ -17,4 +22,9 @@ fragment GtagAddToCart on Cart @inject(into: ["CartItemCountChanged"]) {
}
}
}
prices {
grand_total {
currency
}
}
}
58 changes: 58 additions & 0 deletions packages/google-datalayer/events/add_to_cart/addToCart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { FetchResult } from '@graphcommerce/graphql'
import {
AddProductsToCartFields,
AddProductsToCartMutation,
findAddedItems,
toUserErrors,
} from '@graphcommerce/magento-product'
import { nonNullable } from '@graphcommerce/next-ui'
import { event } from '../../lib/event'

export const addToCart = (
result: FetchResult<AddProductsToCartMutation>,
variables: AddProductsToCartFields,
) => {
const { data, errors } = result
const cart = data?.addProductsToCart?.cart

const addedItems = findAddedItems(data, variables)

const items = addedItems
.map(({ itemVariable, itemInCart }) => {
if (!itemInCart) return null
const { product, prices } = itemInCart
return {
item_id: product.sku,
item_name: product.name,
currency: prices?.price.currency,
price: (prices?.row_total_including_tax.value ?? 1) / itemInCart.quantity,
quantity: itemVariable.quantity,
discount: prices?.discounts?.reduce(
(sum, discount) => sum + (discount?.amount?.value ?? 0) / itemVariable.quantity,
0,
),
}
})
.filter(nonNullable)

const userErrors = toUserErrors(result.data)
if ((errors && errors.length > 0) || userErrors.length > 0) {
event('add_to_cart_error', {
userErrors: userErrors?.map((e) => e.message),
errors: errors?.map((e) => e.message),
variables,
})
}

if (!items.length) return

event('add_to_cart', {
currency: cart?.prices?.grand_total?.currency,
value: addedItems.reduce(
(sum, { itemVariable, itemInCart }) =>
sum + (itemInCart?.prices?.row_total_including_tax.value ?? 1) / itemVariable.quantity,
0,
),
items,
})
}
2 changes: 2 additions & 0 deletions packages/google-datalayer/events/add_to_cart/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './addToCart'
export * from './AddToCartFragment.gql'
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
fragment GtagBeginCheckout on Cart @inject(into: ["CartStartCheckout"]) {
fragment BeginCheckoutFragment on Cart @inject(into: ["CartStartCheckout"]) {
prices {
grand_total {
currency
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { GtagBeginCheckoutFragment } from './GtagBeginCheckout.gql'
import { event } from '../../lib/event'
import { BeginCheckoutFragment } from './BeginCheckoutFragment.gql'

export function gtagBeginCheckout<C extends GtagBeginCheckoutFragment>(cart?: C | null) {
globalThis.gtag?.('event', 'begin_checkout', {
export const beginCheckout = <C extends BeginCheckoutFragment>(cart?: C | null) =>
event('begin_checkout', {
...cart,
currency: cart?.prices?.grand_total?.currency,
value: cart?.prices?.grand_total?.value,
coupon: cart?.applied_coupons?.map((coupon) => coupon?.code),
Expand All @@ -18,4 +20,3 @@ export function gtagBeginCheckout<C extends GtagBeginCheckoutFragment>(cart?: C
quantity: item?.quantity,
})),
})
}
2 changes: 2 additions & 0 deletions packages/google-datalayer/events/begin_checkout/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './beginCheckout'
export * from './BeginCheckoutFragment.gql'
1 change: 1 addition & 0 deletions packages/google-datalayer/events/purchase/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './purchase'
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import { PaymentMethodContextFragment } from '@graphcommerce/magento-cart-payment-method/Api/PaymentMethodContext.gql'
import { event } from '../../lib/event'

export function gtagAddPurchaseInfo(
export const purchase = <C extends PaymentMethodContextFragment>(
orderNumber: string,
cart: PaymentMethodContextFragment | null | undefined,
) {
globalThis.gtag?.('event', 'purchase', {
cart: C | null | undefined,
) =>
event('purchase', {
...cart,
transaction_id: orderNumber,
currency: cart?.prices?.grand_total?.currency,
value: cart?.prices?.grand_total?.value,
coupon: cart?.applied_coupons?.map((coupon) => coupon?.code),
coupon: cart?.applied_coupons?.map((coupon) => coupon?.code).join(' '),
payment_type: cart?.selected_payment_method?.code,
tax: cart?.prices?.applied_taxes?.reduce((sum, tax) => sum + (tax?.amount?.value ?? 0), 0),
items: cart?.items?.map((item) => ({
item_id: item?.product.sku,
item_name: item?.product.name,
currency: item?.prices?.price.currency,
discount: item?.prices?.discounts?.reduce(
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
(sum, discount) => sum + (discount?.amount?.value ?? 0),
0,
),
price: item?.prices?.price.value,
quantity: item?.quantity,
})),
})
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
fragment GtagRemoveFromCart on Cart @inject(into: ["CartItemCountChanged"]) {
fragment RemoveFromCartFragment on Cart @inject(into: ["CartItemCountChanged"]) {
__typename
prices {
grand_total {
Expand Down
2 changes: 2 additions & 0 deletions packages/google-datalayer/events/remove_from_cart/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './removeFromCart'
export * from './RemoveFromCartFragment.gql'
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { GtagRemoveFromCartFragment } from './GtagRemoveFromCart.gql'
import { event } from '../../lib/event'
import { RemoveFromCartFragment } from './RemoveFromCartFragment.gql'

export function gtagRemoveFromCart<C extends GtagRemoveFromCartFragment>(cart?: C | null) {
if (!cart) return

globalThis.gtag?.('event', 'remove_from_cart', {
export const removeFromCart = <C extends RemoveFromCartFragment>(cart: C | null | undefined) =>
event('remove_from_cart', {
...cart,
currency: cart?.prices?.grand_total?.currency,
value: cart?.prices?.grand_total?.value,
items: cart?.items?.map((item) => ({
Expand All @@ -19,4 +19,3 @@ export function gtagRemoveFromCart<C extends GtagRemoveFromCartFragment>(cart?:
quantity: item?.quantity,
})),
})
}
1 change: 1 addition & 0 deletions packages/google-datalayer/events/select_item/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './select_item'
8 changes: 8 additions & 0 deletions packages/google-datalayer/events/select_item/select_item.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { event } from '../../lib/event'

export const selectItem = (itemListId: string, itemListName: string, items: unknown[]) =>
event('select_item', {
item_list_id: itemListId,
item_list_name: itemListName,
items,
})
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
fragment GtagViewCart on Cart @inject(into: ["CartItemCountChanged"]) {
fragment ViewCartFragment on Cart @inject(into: ["CartItemCountChanged"]) {
prices {
grand_total {
currency
Expand Down
2 changes: 2 additions & 0 deletions packages/google-datalayer/events/view_cart/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './viewCart'
export * from './ViewCartFragment.gql'
Loading

0 comments on commit 4194be6

Please sign in to comment.