-
Notifications
You must be signed in to change notification settings - Fork 139
Google Analytics 4 #570
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
Merged
Google Analytics 4 #570
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
60fe963
Initial GA4 Commit
e105d3f
Add tests, Fix bugs
a95db2b
Update config and set calls
fcd55b8
Remove commented language var
5b7f617
Remove reject from group handler
b957450
Fix comments and add complex setting example
1502749
Update setting keys
0a2b74e
Merge branch 'master' of ssh://github.com/segmentio/analytics.js-inte…
b280ceb
Iterate on feedback
771ff7e
Remove stray comment
b5cdb10
Update version to 0.0.1
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| module.exports = require('../../karma.conf-ci.js'); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| module.exports = require('../../karma.conf'); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,309 @@ | ||
| 'use strict'; | ||
|
|
||
| /** | ||
| * Module dependencies. | ||
| */ | ||
| var integration = require('@segment/analytics.js-integration'); | ||
| var reject = require('reject'); | ||
|
|
||
| /** | ||
| * GA4 | ||
| */ | ||
| var GA4 = (module.exports = integration('Google Analytics 4') | ||
| .global('gtag') | ||
| .global('ga4DataLayer') | ||
| .option('measurementIds', []) | ||
| .option('cookieDomainName', 'auto') | ||
| .option('cookiePrefix', '_ga') | ||
| .option('cookieExpiration', 63072000) | ||
| .option('cookieUpdate', true) | ||
| .option('cookieFlags', '') | ||
| .option('sendAutomaticPageViewEvent', false) | ||
| .option('allowAllAdvertisingFeatures', false) | ||
| .option('allowAdvertisingPersonalization', false) | ||
| .option('disableGoogleAnalytics', false) | ||
| .option('googleReportingIdentity', 'device') | ||
| .option('userProperties', {}) | ||
| /** | ||
| * Custom Events and Parameters setting. This setting is used by the track | ||
| * handler to map Segment events and fields to Google analytics events and parameters. | ||
| * | ||
| * Example: | ||
| * [ | ||
| * { | ||
| * "googleEvent": "new_episode", | ||
| * "parameters": [ | ||
| * { | ||
| * "key": "properties.title", | ||
| * "value": "title" | ||
| * }, | ||
| * { | ||
| * "key": "properties.genre", | ||
| * "value": "genre" | ||
| * } | ||
| * ], | ||
| * "segmentEvent": "Started Episode" | ||
| * } | ||
| * ] | ||
| */ | ||
| .option('customEventsAndParameters', []) | ||
| .tag( | ||
| '<script src="//www.googletagmanager.com/gtag/js?id={{ measurementId }}&l=ga4DataLayer">' | ||
| )); | ||
|
|
||
| /** | ||
| * Initialize. | ||
| * | ||
| * https://developers.google.com/analytics/devguides/collection/ga4 | ||
| * | ||
| * @api public | ||
| */ | ||
| GA4.prototype.initialize = function() { | ||
| window.ga4DataLayer = window.ga4DataLayer || []; | ||
| window.gtag = function() { | ||
| window.ga4DataLayer.push(arguments); | ||
| }; | ||
|
|
||
| /** | ||
| * This line is in all of the gtag examples but is not well documented. Research | ||
| * says that it is is related to deduplication. | ||
| * https://stackoverflow.com/questions/59256532/what-is-the-js-gtags-js-command | ||
| */ | ||
| window.gtag('js', new Date()); | ||
|
|
||
| var opts = this.options; | ||
| var measurementIds = opts.measurementIds; | ||
|
|
||
| /** | ||
| * Avoid loading and configuring gtag.js if any are true: | ||
| * - Disable Google Analytics setting is enabled | ||
| * - No measurement IDs are configured | ||
| */ | ||
| if (!measurementIds.length || opts.disableGoogleAnalytics) { | ||
| return; | ||
| } | ||
|
|
||
| var config = { | ||
| /** | ||
| * Disable Google's Automatic Page View Measurement | ||
| * https://developers.google.com/analytics/devguides/collection/ga4/disable-page-view | ||
| */ | ||
| send_page_view: opts.sendAutomaticPageViewEvent, | ||
|
|
||
| /** | ||
| * Cookie Update | ||
| * https://developers.google.com/analytics/devguides/collection/ga4/cookies-user-id#cookie_update_parameter | ||
| */ | ||
| cookie_update: opts.cookieUpdate, | ||
|
|
||
| /** | ||
| * Cookie Domain Name | ||
| * https://developers.google.com/analytics/devguides/collection/ga4/cookies-user-id#cookie_domain_configuration | ||
| */ | ||
| cookie_domain: opts.cookieDomainName, | ||
|
|
||
| /** | ||
| * Cookie Prefix | ||
| * https://developers.google.com/analytics/devguides/collection/ga4/cookies-user-id#cookie_prefix | ||
| */ | ||
| cookie_prefix: opts.cookiePrefix, | ||
|
|
||
| /** | ||
| * Cookie Expiration | ||
| * https://developers.google.com/analytics/devguides/collection/ga4/cookies-user-id#cookie_expiration | ||
| */ | ||
| cookie_expires: opts.cookieExpiration, | ||
| }; | ||
|
|
||
| var sets = [ | ||
| /** | ||
| * Cookie Flags | ||
| * https://developers.google.com/analytics/devguides/collection/ga4/cookies-user-id#cookie_flags | ||
| */ | ||
| [{ cookie_flags: opts.cookieFlags }], | ||
|
|
||
| /** | ||
| * Disable All Advertising | ||
| * https://developers.google.com/analytics/devguides/collection/ga4/display-features#disable_all_advertising_features | ||
| */ | ||
| ['allow_google_signals', opts.allowAllAdvertisingFeatures], | ||
|
|
||
| /** | ||
| * Disable Advertising Personalization | ||
| * https://developers.google.com/analytics/devguides/collection/ga4/display-features#disable_advertising_personalization | ||
| */ | ||
| ['allow_ad_personalization_signals', opts.allowAdvertisingPersonalization] | ||
| ]; | ||
|
|
||
| // Load gtag.js using the first measurement ID, then configure using the `config` commands built above. | ||
| var self = this; | ||
| this.load({ measurementId: measurementIds[0] }, function() { | ||
| /** | ||
| * Measurement IDs. | ||
| * The same configuration information is shared across all measurement IDs. | ||
| * https://developers.google.com/analytics/devguides/collection/ga4#add_an_additional_google_analytics_property_to_an_existing_tag | ||
| */ | ||
| for (var i = 0; i < measurementIds.length; i++) { | ||
| window.gtag('config', measurementIds[i], config) | ||
|
|
||
| } | ||
|
|
||
| /** | ||
| * Set persistent values shared across all gtag.js usage. | ||
| * https://developers.google.com/gtagjs/reference/api#set | ||
| */ | ||
| for (var i = 0; i < sets.length; i++) { | ||
| window.gtag.apply(null, sets[i]); | ||
| } | ||
|
|
||
| self.ready(); | ||
| }); | ||
| }; | ||
|
|
||
| /** | ||
| * Loaded? | ||
| * | ||
| * @api private | ||
| * @return {boolean} | ||
| */ | ||
| GA4.prototype.loaded = function() { | ||
| return !!( | ||
| window.ga4DataLayer && Array.prototype.push !== window.ga4DataLayer.push | ||
| ); | ||
| }; | ||
|
|
||
| /** | ||
| * Identify. | ||
| * | ||
| * @api public | ||
| * @param {Facade.Identify} event | ||
| */ | ||
| GA4.prototype.identify = function(identify) { | ||
| var opts = this.options; | ||
| var userPropertyMappings = opts.userProperties; | ||
|
|
||
| var userProperties = {}; | ||
|
|
||
| // Map all customer-defined user property mappings. | ||
| for (var eventField in userPropertyMappings) { | ||
| if (!userPropertyMappings.hasOwnProperty(eventField)) { | ||
| continue; | ||
| } | ||
|
|
||
| var userProp = userPropertyMappings[eventField]; | ||
| var value = identify.proxy(eventField); | ||
|
|
||
| userProperties[userProp] = value; | ||
| } | ||
|
|
||
| /** | ||
| * Map the user_id property if the Google Reporting Identity is set one of: | ||
| * - By User ID, Google signals, then device (userIdSignalsAndDevice) | ||
| * - By User ID and Devicea (userIdAndDevice) | ||
| * | ||
| * Google's Reporting Identity: https://support.google.com/analytics/answer/9213390?hl=en | ||
| * | ||
| * Note that the user ID can be appended as part of the user_properties | ||
| * object instead of being configured by an explicit command. | ||
| * https://developers.google.com/analytics/devguides/collection/ga4/cookies-user-id#set_user_id | ||
| */ | ||
| var userId = identify.userId(); | ||
| var validReportingIdentity = opts.googleReportingIdentity === 'userIdSignalsAndDevice' || opts.googleReportingIdentity === 'userIdAndDevice' | ||
| if (userId && validReportingIdentity) { | ||
| userProperties.user_id = userId; | ||
| } | ||
|
|
||
| if (Object.keys(userProperties).length) { | ||
| window.gtag('set', 'user_properties', userProperties); | ||
| } | ||
| }; | ||
|
|
||
| /** | ||
| * Group | ||
| * | ||
| * @api public | ||
| * @param {Facade.Group} group | ||
| */ | ||
| GA4.prototype.group = function(group) { | ||
| window.gtag('event', 'join_group', { | ||
| group_id: group.groupId() | ||
| }); | ||
briemcnally marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }; | ||
|
|
||
| /** | ||
| * Page | ||
| * | ||
| * @api public | ||
| * @param {Facade.Page} page | ||
| */ | ||
| GA4.prototype.page = function(page) { | ||
| // If the Send Google's Automatic Page View Measurement setting is set to true then | ||
| // don't handle page calls to avoid duplicate page_view events. | ||
| if (this.options.sendAutomaticPageViewEvent) { | ||
| return; | ||
| } | ||
|
|
||
| var props = page.properties(); | ||
| var name = page.fullName(); | ||
|
|
||
| var pageLocation = props.url; | ||
| var pageReferrer = page.referrer(); | ||
| var pageTitle = name || props.title; | ||
|
|
||
| window.gtag('event', 'page_view', { | ||
| page_location: pageLocation, | ||
| page_referrer: pageReferrer, | ||
| page_title: pageTitle | ||
| }); | ||
| }; | ||
|
|
||
| /** | ||
| * Track | ||
| * | ||
| * @api public | ||
| * @param {Track} track | ||
| */ | ||
|
|
||
| GA4.prototype.track = function(track) { | ||
|
|
||
| var mappings = this.options.customEventsAndParameters; | ||
|
|
||
| for (var i = 0; i < mappings.length; i++) { | ||
gpsamson marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| var mapping = mappings[i]; | ||
| if (typeof mapping !== 'object') { | ||
| continue; | ||
| } | ||
|
|
||
| var segmentEvent = mapping.segmentEvent; | ||
| var googleEvent = mapping.googleEvent; | ||
|
|
||
| if (!segmentEvent || !googleEvent || segmentEvent !== track.event()) { | ||
| continue; | ||
| } | ||
|
|
||
| var parameterMappings = mapping.parameters || []; | ||
| var parameters = {}; | ||
|
|
||
| if (!(parameterMappings instanceof Array)) { | ||
| continue; | ||
| } | ||
|
|
||
| // Map Segment event fields to Google Event Parameters. | ||
| // Text map settings that are nested in a mixed settings take on a different shape | ||
| // than a top-level text map setting. | ||
| // eg; [{ key: 'properties.genre', value: 'primary_genre }] | ||
| // | ||
| for (var j = 0; j < parameterMappings.length; j++) { | ||
| var map = parameterMappings[j] || {}; | ||
| if (typeof map !== 'object' || !map.key || !map.value) { | ||
| continue; | ||
| } | ||
|
|
||
| var param = map.value; | ||
| var value = track.proxy(map.key); | ||
| parameters[param] = value; | ||
| } | ||
|
|
||
| window.gtag('event', googleEvent, parameters); | ||
| } | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| { | ||
| "name": "@segment/analytics.js-integration-google-analytics-4", | ||
| "version": "0.0.1", | ||
| "description": "", | ||
| "main": "lib/index.js", | ||
| "directories": { | ||
| "lib": "lib", | ||
| "test": "test" | ||
| }, | ||
| "scripts": { | ||
| "test": "karma start" | ||
| }, | ||
| "author": "Segment <friends@segment.com>", | ||
| "license": "SEE LICENSE IN LICENSE", | ||
| "homepage": "https://github.com/segmentio/analytics.js-integrations/blob/master/integrations/google-analytics-4#readme", | ||
| "bugs": { | ||
| "url": "https://github.com/segmentio/analytics.js-integrations/issues" | ||
| }, | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/segmentio/analytics.js-integrations.git" | ||
| }, | ||
| "dependencies": { | ||
| "@ndhoule/defaults": "^2.0.1", | ||
| "@segment/analytics.js-integration": "^3.1.0", | ||
| "component-each": "^0.2.6", | ||
| "extend": "^3.0.2", | ||
| "global-queue": "^1.0.1", | ||
| "is": "^3.1.0", | ||
| "lodash": "^4.17.4", | ||
| "obj-case": "^0.2.0", | ||
| "object-component": "0.0.3", | ||
| "reject": "0.0.1", | ||
| "segmentio-facade": "^3.2.7", | ||
| "use-https": "^0.1.1" | ||
| }, | ||
| "devDependencies": { | ||
| "@segment/analytics.js-core": "^3.8.2", | ||
| "@segment/analytics.js-integration-tester": "^3.1.1", | ||
| "@segment/clear-env": "^2.1.1", | ||
| "browserify": "^16.2.3", | ||
| "eslint": "^5.16.0", | ||
| "karma": "^4.1.0", | ||
| "karma-browserify": "^6.0.0", | ||
| "karma-chrome-launcher": "^2.2.0", | ||
| "karma-mocha": "^1.3.0", | ||
| "karma-mocha-reporter": "^2.2.5", | ||
| "karma-sauce-launcher": "^2.0.2", | ||
| "karma-spec-reporter": "^0.0.32", | ||
| "karma-summary-reporter": "^1.6.0", | ||
| "mocha": "^6.1.4", | ||
| "to-array": "^0.1.4", | ||
| "watchify": "^3.7.0" | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Should this be an array of objects? A little bit confused by the different format of Cookie Flags and the rest of them. Could be missing something
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.
gtag.js has a
setcommand and it's really confusing. Sometimes it accepts a single object argument (L1) and sometimes it accepts multiple arguments (L2):The
setsvar we have is an array of arrays to represent a list of set commands and the arguments to pass.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.
Gotcha makes sense. Thanks for clarifying