Google Tag Manager Integration
#1. Background
#2. Time Estimates
- Set up in Platform: n/a hours
- Integration: 1.5 hours
- Styling: n/a
#3. Functional Overview
Components:
- [components/Cards/Product] https://github.com/findify/findify-js/blob/develop/packages/react-components/src/components/Cards/Product/index.tsx
- [components/search/LazyResults] https://github.com/findify/findify-js/blob/develop/packages/react-components/src/components/search/LazyResults/index.tsx
#4. Integration Steps
1. Product Click Events.
In order to send Product Click events to Google Analytics, you need to override the default Findify onClick function in 'components/Cards/Product/index.tsx' and create a 'sendFindifyClickEvent' function:
/**
* @module components/Cards/Product
*/
import cx from 'classnames';
import trackProductPosition from 'helpers/trackProductPosition';
const sendFindifyClickEvent = (list, item, config, index) => new Promise(resolve =>
window.dataLayer.push({
eventCallback: resolve,
event: "gaEvent",
eventCategory: "Ecommerce",
eventAction: "Product Click",
ecommerce: {
click: {
actionField: { list },
products: [{
base_product: item.get('item_group_id'),
variant: item.get('selected_variant_id'),
name: item.get('title'), // Name or ID is required.
id: item.get('id'),
price: item.getIn(['price', 0]),
brand: item.get('brand'),
category: item.getIn(['category', 0, 'category1']),
list: list,
position: index + 1
}]
}
}
})
);
export default ({
index,
item,
theme = styles,
className,
config,
highlighted,
isAutocomplete,
isSearch,
isCollection,
isRecommendation
}: IProductCardProps) => {
const container = trackProductPosition(item);
const [variant, setVariant] = useVariants(item);
let list = '';
switch(true){
case isAutocomplete:
list = 'Autocomplete Results'
break;
case isCollection:
list = 'Collection Results'
break;
case isRecommendation:
list = 'Recommendation'
break;
default: list = 'Search Results';
}
const itemOnclick = (e) => {
const openInNewWindow = e && (e.ctrlKey || e.metaKey);
const productUrl = item.get('product_url');
item.analytics.sendEvent(
'click-item',
{ rid: item.meta.get('rid'), item_id: item.get('id'), variant_item_id: item.get('selected_variant_id')},
!openInNewWindow
);
if (isSearch && !openInNewWindow && typeof window !== 'undefined') {
document.location.hash = item.get('id');
}
sendFindifyClickEvent(list, item, config, index);
}
return (
<a
ref={container}
data-element="card"
className={cx(
theme.root,
theme[config.get('template')],
highlighted && theme.highlighted,
isAutocomplete && theme.autocomplete,
className
)}
href={url}
onClick={itemOnclick}
>
// default code
</a>
);
};
2. Product Impressions.
If you need to send Product Impressions to StaticResults (or LazyResults), Recommendations and Autocomplete, this is what the build looks like:
/**
* @module components/search/LazyResults
*/
import {useEffect} from 'react';
import { useConfig } from '@findify/react-connect';
import styles from 'components/search/LazyResults/styles.css';
import useLazy from 'helpers/useLazy';
// default code
const pushDataLayers = (data, config) => window.dataLayer && data && window.dataLayer.push({
event: "data.pageView",
ecommerce: {
currencyCode: config.getIn(['currency', 'code']),
impressions: data.toJS()
}
});
const pushImpressions = (item, index, listType) => ({
base_product: item.get('item_group_id'),
variant: item.get('selected_variant_id'),
name: item.get('title'), // Name or ID is required.
id: item.get('id'),
price: item.getIn(['price', 0]),
brand: item.get('brand'),
category: item.getIn(['category', 0, 'category1']),
list: listType,
position: index + 1
});
const sendGAEvents = (items, config, meta, isCollection) => {
const offset = meta.get('offset');
const limit = meta.get('limit');
const listType = isCollection ? 'Collection Results' : 'Search Results';
const itemsCutLimit = items.slice(offset, offset + limit);
const itemsImpressions = itemsCutLimit.map((item, index) => pushImpressions(item, offset + index, listType));
if(itemsImpressions.size > 0){
pushDataLayers(itemsImpressions, config, isCollection);
}
}
const checkIfTheSameProducts = (currItems) => {
const lastDataLayer = dataLayer.filter(i => i.event === 'data.pageView').pop();
const check = lastDataLayer && lastDataLayer.ecommerce.impressions.length === currItems.size && lastDataLayer.ecommerce.impressions.filter(i => currItems.find(currItem => currItem.get('title') === i.name));
return check && check.length === currItems.size;
}
const LazyResults = ({ theme = styles, card = ProductCard, itemConfig, isMobile, isCollection, meta }: ILazyResultsProps) => {
const {
// default code
items
} = useLazy();
const { config } = useConfig<Immutable.SearchConfig>();
// default code
useEffect(
() => {
if(!checkIfTheSameProducts(items)){
sendGAEvents(items, config, meta, isCollection);
}
}, [items]
);
return (
// default code
);
};
export default LazyResults;
/**
* @module components/Dropdown
*/
import {useEffect} from 'react';
import { useItems, useQuery, useConfig } from '@findify/react-connect';
import { Immutable } from '@findify/store-configuration';
import styles from 'layouts/Recommendation/Slider/styles.css';
const pushDataLayers = (data, config) => window.dataLayer && data && window.dataLayer.push({
event: "data.pageView",
ecommerce: {
currencyCode: config.getIn(['currency', 'code']),
impressions: data.toJS()
}
});
const pushImpressions = (item, index, listType) => ({
base_product: item.get('item_group_id'),
variant: item.get('selected_variant_id'),
name: item.get('title'), // Name or ID is required.
id: item.get('id'),
price: item.getIn(['price', 0]),
brand: item.get('brand'),
category: item.getIn(['category', 0, 'category1']),
list: listType,
position: index + 1
});
const sendGAEvents = (items, config, meta) => {
const offset = meta.get('offset');
const limit = meta.get('limit');
const listType = 'Recommendation Results';
const itemsCutLimit = items.slice(offset, offset + limit);
const itemsImpressions = itemsCutLimit.map((item, index) => pushImpressions(item, offset + index, listType));
if(itemsImpressions.size > 0){
pushDataLayers(itemsImpressions, config);
}
}
const checkIfTheSameProducts = (currItems) => {
const lastDataLayer = dataLayer.filter(i => i.event === 'data.pageView').pop();
const check = lastDataLayer && lastDataLayer.ecommerce.impressions.length === currItems.size && lastDataLayer.ecommerce.impressions.filter(i => currItems.find(currItem => currItem.get('title') === i.name));
return check && check.length === currItems.size;
}
export default ({ theme = styles }) => {
const { items, config } = useItems<Immutable.RecommendationConfig>();
const { meta } = useQuery();
const { config: baseConfig } = useConfig<Immutable.AutocompleteConfig>();
useEffect(
() => {
if(!checkIfTheSameProducts(items)){
sendGAEvents(items, baseConfig, meta);
}
},[]
)
if (!items?.size) return null;
return (
<>
// default code
</>
);
};
/**
* @module components/autocomplete/Products
*/
import { useCallback, useEffect } from 'react';
import styles from 'components/autocomplete/Products/styles.css';
import { useItems, useQuery } from '@findify/react-connect';
import { Immutable } from '@findify/store-configuration';
// default code
const pushDataLayers = (data, config) => window.dataLayer && data && window.dataLayer.push({
event: "data.pageView",
ecommerce: {
currencyCode: config.getIn(['currency', 'code']),
impressions: data.toJS()
}
});
const pushImpressions = (item, index, listType) => ({
base_product: item.get('item_group_id'),
variant: item.get('selected_variant_id'),
name: item.get('title'), // Name or ID is required.
id: item.get('id'),
price: item.getIn(['price', 0]),
brand: item.get('brand'),
category: item.getIn(['category', 0, 'category1']),
list: listType,
position: index + 1
});
const sendGAEvents = (items, config) => {
const listType = 'Autocomplete Results';
const itemsImpressions = items.map((item, index) => pushImpressions(item, index, listType));
if(itemsImpressions.size > 0){
pushDataLayers(itemsImpressions, config);
}
}
const checkIfTheSameProducts = (currItems) => {
const lastDataLayer = dataLayer.filter(i => i.event === 'data.pageView').pop();
const check = lastDataLayer && lastDataLayer.ecommerce.impressions.length === currItems.size && lastDataLayer.ecommerce.impressions.filter(i => currItems.find(currItem => currItem.get('title') === i.name));
return check && check.length === currItems.size;
}
export default ({
theme = styles,
config,
isTrendingSearches,
highlightedItem,
}) => {
const {
items,
config: baseConfig,
} = useItems<Immutable.AutocompleteConfig>();
const { query } = useQuery();
// default code
useEffect(
() => {
if(!checkIfTheSameProducts(items)){
sendGAEvents(items, config);
}
}, []
)
return (
// default code
);
};
#5. MJS Version
This module has been optimized for MJS version 7.1.27
Updated about 1 year ago