-
Notifications
You must be signed in to change notification settings - Fork 85
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
Ga4/prod #173
Ga4/prod #173
Changes from 4 commits
58837d4
923a803
222b325
a821868
2bb0b3b
a036105
720b4ac
366d838
5ee6d8c
8848083
313dd45
a6cc5bc
5804064
217e8c9
2d2d9c5
0953dad
4c1b1f0
d912d0b
8f7eab6
7996e57
15b7f7f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
const eventName = [ | ||
// Browsing Section | ||
{ | ||
src: ["products searched", "product searched"], | ||
dest: "search", | ||
}, | ||
{ src: ["product list viewed"], dest: "view_item_list" }, | ||
// { src: ["product list filtered"], dest: "" }, | ||
|
||
// Promotion Section | ||
{ src: ["promotion viewed"], dest: "view_promotion" }, | ||
{ src: ["promotion clicked"], dest: "select_promotion" }, | ||
|
||
// Ordering Section | ||
{ | ||
src: ["product clicked", "products clicked"], | ||
dest: "select_item", | ||
hasItem: true, | ||
}, | ||
{ src: ["product viewed"], dest: "view_item", hasItem: true }, | ||
{ src: ["product added"], dest: "add_to_cart", hasItem: true }, | ||
{ src: ["product removed"], dest: "remove_from_cart", hasItem: true }, | ||
{ src: ["cart viewed"], dest: "view_cart", hasItem: true }, | ||
{ src: ["checkout started"], dest: "begin_checkout" }, | ||
{ src: ["checkout step viewed"], dest: "" }, | ||
{ src: ["checkout step completed"], dest: "" }, | ||
{ src: ["payment info entered"], dest: "add_payment_info" }, | ||
{ src: ["order updated"], dest: "" }, | ||
{ src: ["order completed"], dest: "purchase" }, | ||
// { src: ["order refunded"], dest: "refund" }, GA4 refund is different it supports two refund, partial and full refund | ||
{ src: ["order cancelled"], dest: "" }, | ||
|
||
// Coupon Section | ||
|
||
//---------- | ||
// do I need the two below events | ||
// Wishlist Section | ||
// { src: ["product added to wishlist"], dest: "add_to_wishlist" }, | ||
//------- | ||
|
||
// Sharing Section | ||
// { src: ["product shared", "cart shared"], dest: "share" }, | ||
//--------- | ||
|
||
// Reviewing Section | ||
|
||
// -------- | ||
]; | ||
|
||
const eventParameter = [ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. similar as above There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
{ src: "query", dest: ["search_term"] }, | ||
{ src: "list_id", dest: ["item_list_id", "items.item_list_id"] }, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. instead of array, use a flag like storeAlsoInItems: true/false There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. okkk looking into it |
||
{ src: "category", dest: ["item_list_name", "items.item_list_name"] }, | ||
{ src: "promotion_id", dest: ["items.promotion_id"] }, | ||
{ src: "creative", dest: ["items.creative_slot"] }, | ||
{ src: "name", dest: ["items.promotion_name"] }, | ||
{ src: "position", dest: ["location_id", "items.location_id"] }, | ||
{ src: "price", dest: ["value"] }, | ||
{ src: "currency", dest: ["currency"] }, | ||
{ src: "coupon", dest: ["coupon"] }, | ||
{ src: "payment_method", dest: ["payment_type"] }, | ||
{ src: "affiliation", dest: ["affiliation"] }, | ||
{ src: "shipping", dest: ["shipping"] }, | ||
{ src: "tax", dest: ["tax"] }, | ||
{ src: "affiliation", dest: ["affiliation"] }, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove duplicate There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
{ src: "total", dest: ["value"] }, | ||
{ src: "checkout_id", dest: ["transaction_id"] }, | ||
]; | ||
|
||
const itemParameter = [ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. similar as above There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
{ src: "product_id", dest: "item_id" }, | ||
{ src: "order_id", dest: "item_id" }, | ||
{ src: "name", dest: "item_name" }, | ||
{ src: "coupon", dest: "coupon" }, | ||
{ src: "category", dest: "item_category" }, | ||
{ src: "brand", dest: "item_brand" }, | ||
{ src: "variant", dest: "item_variant" }, | ||
{ src: "price", dest: "price" }, | ||
{ src: "quantity", dest: "quantity" }, | ||
{ src: "position", dest: "index" }, | ||
]; | ||
|
||
export { eventName, eventParameter, itemParameter }; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
/* eslint-disable class-methods-use-this */ | ||
import logger from "../../utils/logUtil"; | ||
import ScriptLoader from "../ScriptLoader"; | ||
import { | ||
eventName, | ||
eventParameter, | ||
itemParameter, | ||
} from "./ECommerceEventConfig"; | ||
|
||
export default class GA4 { | ||
constructor(config, analytics) { | ||
this.measurementId = config.measurementId; | ||
this.analytics = analytics; | ||
this.sendUserId = config.sendUserId || false; | ||
this.blockPageView = config.blockPageViewEvent || false; | ||
this.name = "GA4"; | ||
} | ||
|
||
loadScript(measurementId, userId) { | ||
window.dataLayer = window.dataLayer || []; | ||
window.gtag = | ||
window.gtag || | ||
function gt() { | ||
// eslint-disable-next-line prefer-rest-params | ||
window.dataLayer.push(arguments); | ||
}; | ||
window.gtag("js", new Date()); | ||
|
||
// This condition is not working, even after disabling page view | ||
// page_view is even getting called on page load | ||
if (this.blockPageView) { | ||
window.gtag("config", measurementId, { | ||
user_id: userId, | ||
send_page_view: false, | ||
}); | ||
} else { | ||
window.gtag("config", measurementId); | ||
} | ||
|
||
ScriptLoader( | ||
"google-analytics 4", | ||
`https://www.googletagmanager.com/gtag/js?id=${measurementId}` | ||
); | ||
} | ||
|
||
init() { | ||
// To do :: check how custom dimension and metrics is used | ||
const userId = this.analytics.userId || this.analytics.anonymousId; | ||
this.loadScript(this.measurementId, userId); | ||
} | ||
|
||
// When adding events do everything ion lowercase. | ||
// use underscores instead of spaces | ||
// Register your parameters to show them up in UI even user_id | ||
|
||
/* utility functions ---Start here --- */ | ||
isLoaded() { | ||
return !!(window.gtag && window.gtag.push !== Array.prototype.push); | ||
} | ||
|
||
isReady() { | ||
return !!(window.gtag && window.gtag.push !== Array.prototype.push); | ||
} | ||
/* utility functions --- Ends here --- */ | ||
|
||
isReservedName(eventName) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add the helper methods to GA4/utils ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
const reservedName = [ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. reservedEventNames There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
"ad_activeview", | ||
"ad_click", | ||
"ad_exposure", | ||
"ad_impression", | ||
"ad_query", | ||
"adunit_exposure", | ||
"app_clear_data", | ||
"app_install", | ||
"app_update", | ||
"app_remove", | ||
"error", | ||
"first_open", | ||
"first_visit", | ||
"in_app_purchase", | ||
"notification_dismiss", | ||
"notification_foreground", | ||
"notification_open", | ||
"notification_receive", | ||
"os_update", | ||
"screen_view", | ||
"session_start", | ||
"user_engagement", | ||
]; | ||
|
||
return reservedName.includes(eventName); | ||
} | ||
|
||
sendGAEvent(event, props) { | ||
window.gtag("event", event, props); | ||
} | ||
|
||
getDestinationEvent(event) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this too to utils There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
return eventName.find((p) => p.src.includes(event.toLowerCase())); | ||
} | ||
|
||
getDestinationEventProperties(props, destParameter) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as above There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As method doc like what is the input and output format with an example There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. better to use a library that does this? can you check once like get-value we use for transformer? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There is a library : " loadash " using that we can do, but loading some external library means loading extra js, isn't if we use our own implementation where we can modify code as required. which means we can move above function to global and make more generic. One of the way I can think is going through loadash code and provide same implementation in our utils. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we won't be loading a js but yes our total JS SDK size will increase. We can leave it |
||
const destinationProperties = {}; | ||
const item = {}; | ||
Object.keys(props).forEach((key) => { | ||
destParameter.forEach((param) => { | ||
if (key === param.src) { | ||
if (Array.isArray(param.dest)) { | ||
param.dest.forEach((d) => { | ||
const result = d.split("."); | ||
// Here we only support mapping single level object mapping. | ||
// To Do Future Scope :: implement using recursion to handle multi level prop mapping | ||
if (result.length > 1) { | ||
const levelOne = result[0]; | ||
const levelTwo = result[1]; | ||
item[levelTwo] = props[key]; | ||
if (!destinationProperties[levelOne]) { | ||
destinationProperties[levelOne] = []; | ||
destinationProperties[levelOne].push(item); | ||
} | ||
} else { | ||
destinationProperties[result] = props[key]; | ||
} | ||
}); | ||
} else { | ||
destinationProperties[param.dest] = props[key]; | ||
} | ||
} | ||
}); | ||
}); | ||
return destinationProperties; | ||
} | ||
|
||
getDestinationItemProperties(products, item) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as above There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. forgot to remove from here ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. now removed |
||
const items = []; | ||
let obj = {}; | ||
products.forEach((p) => { | ||
obj = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. better name than obj There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. obj -> eventMappingObj |
||
...this.getDestinationEventProperties(p, itemParameter), | ||
...(item && item[0]), | ||
}; | ||
items.push(obj); | ||
}); | ||
return items; | ||
} | ||
|
||
track(rudderElement) { | ||
let { event } = rudderElement.message; | ||
const { properties } = rudderElement.message; | ||
const { products } = properties; | ||
let destinationProperties = {}; | ||
if (!event || this.isReservedName(event)) { | ||
throw Error("Cannot call un-named/reserved named track event"); | ||
} | ||
|
||
const obj = this.getDestinationEvent(event); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. better name like destEventName There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
if (obj) { | ||
if (products && Array.isArray(products)) { | ||
event = obj.dest; | ||
// eslint-disable-next-line no-const-assign | ||
destinationProperties = this.getDestinationEventProperties( | ||
properties, | ||
eventParameter | ||
); | ||
destinationProperties.items = this.getDestinationItemProperties( | ||
products, | ||
destinationProperties.items | ||
); | ||
} else { | ||
event = obj.dest; | ||
if (!obj.hasItem) { | ||
// eslint-disable-next-line no-const-assign | ||
destinationProperties = this.getDestinationEventProperties( | ||
properties, | ||
eventParameter | ||
); | ||
} else { | ||
// create items | ||
destinationProperties.items = this.getDestinationItemProperties([ | ||
properties, | ||
]); | ||
} | ||
} | ||
} else { | ||
destinationProperties = properties; | ||
} | ||
|
||
this.sendGAEvent(event, destinationProperties); | ||
} | ||
|
||
identify(rudderElement) { | ||
if (this.sendUserId && rudderElement.message.userId) { | ||
const userId = this.analytics.userId || this.analytics.anonymousId; | ||
window.gtag("config", this.measurementId, { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this works even if identify is not the first call? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, user is identified even if no identify call is made. As soon as ga scripts load he gets identified in google analytics. |
||
user_id: userId, | ||
}); | ||
} | ||
window.gtag("set", "user_properties", this.analytics.userTraits); | ||
logger.debug("in GoogleAnalyticsManager identify"); | ||
} | ||
|
||
page(rudderElement) { | ||
window.gtag( | ||
"event", | ||
"page_view", | ||
(rudderElement.message.context && rudderElement.message.context.page) || | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://support.google.com/analytics/answer/9234069
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also check if GA4 allows sending campaign info present under rudderElement.message.context.campaign There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added function to support GA4 params and added config to decide if need to send extra params |
||
{} | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import GA4 from "./browser.js"; | ||
|
||
export default GA4; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,8 +20,12 @@ const clientToServerNames = { | |
OPTIMIZELY: "Optimizely", | ||
FULLSTORY: "Fullstory", | ||
TVSQUUARED: "TVSquared", | ||
<<<<<<< HEAD | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. resolve conflict There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
MOENGAGE: "MoEngage", | ||
AM: "Amplitude" | ||
======= | ||
GA4: "Google Analytics 4", | ||
>>>>>>> 23bdbcd... GA4 sdk implementation | ||
}; | ||
|
||
export { clientToServerNames }; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,8 +17,12 @@ import * as Optimizely from "./Optimizely"; | |
import * as Bugsnag from "./Bugsnag"; | ||
import * as Fullstory from "./Fullstory"; | ||
import * as TVSquared from "./TVSquared"; | ||
<<<<<<< HEAD | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. missed this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I fixed it |
||
import * as MoEngage from "./MoEngage"; | ||
import * as Amplitude from "./Amplitude"; | ||
======= | ||
import * as GA4 from "./GA4"; | ||
>>>>>>> 23bdbcd... GA4 sdk implementation | ||
|
||
// the key names should match the destination.name value to keep partity everywhere | ||
// (config-plan name, native destination.name , exported integration name(this one below)) | ||
|
@@ -43,8 +47,12 @@ const integrations = { | |
BUGSNAG: Bugsnag.default, | ||
FULLSTORY: Fullstory.default, | ||
TVSQUARED: TVSquared.default, | ||
<<<<<<< HEAD | ||
MOENGAGE: MoEngage.default, | ||
AM: Amplitude.default, | ||
======= | ||
GA4: GA4.default, | ||
>>>>>>> 23bdbcd... GA4 sdk implementation | ||
}; | ||
|
||
export { integrations }; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,10 +43,16 @@ const commonNames = { | |
Fullstory: "FULLSTORY", | ||
BUGSNAG: "BUGSNAG", | ||
TVSQUARED: "TVSQUARED", | ||
<<<<<<< HEAD | ||
MOENGAGE: "MoEngage", | ||
AM: "AM", | ||
AMPLITUDE: "AM", | ||
Amplitude: "AM" | ||
======= | ||
"Google Analytics 4": "GA4", | ||
GoogleAnalytics4: "GA4", | ||
GA4: "GA4", | ||
>>>>>>> 23bdbcd... GA4 sdk implementation | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. resolve conflict There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
}; | ||
|
||
export { commonNames }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
eventNamesConfigArray
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done