diff --git a/.env_sample b/.env_sample index a59de0cf7..628d06270 100644 --- a/.env_sample +++ b/.env_sample @@ -115,11 +115,16 @@ FASTLY_SERVICE_ID="" # (https://www.honeycomb.io/) HONEYCOMB_API_KEY="" -# Google analytics +# Google analytics 3/Universal Analytics # (https://developers.google.com/analytics/devguides/reporting/core/v4) # When blank don't render Google Analytics. GA_TRACKING_ID="" +# Google analytics 4/Tag Manager +# (https://developers.google.com/analytics/devguides/reporting/data/v1) +# When blank don't render Google Analytics 4. +GA_ANALYTICS_4_ID="" + # Mailchimp for mails # (https://mailchimp.com/developer/) MAILCHIMP_API_KEY="Optional-valid" diff --git a/app/assets/javascripts/.eslintrc.js b/app/assets/javascripts/.eslintrc.js index d3f1296e5..0ea391c19 100644 --- a/app/assets/javascripts/.eslintrc.js +++ b/app/assets/javascripts/.eslintrc.js @@ -19,6 +19,7 @@ module.exports = { preventDefaultAction: false, userData: false, ga: false, // Google Analytics + gtag: false, // Google Analytics 4 handleOptimisticButtRender: false, handleFollowButtPress: false, browserStoreCache: false, diff --git a/app/assets/javascripts/initializers/initializeBaseTracking.js.erb b/app/assets/javascripts/initializers/initializeBaseTracking.js.erb index f27c15747..113f36acc 100644 --- a/app/assets/javascripts/initializers/initializeBaseTracking.js.erb +++ b/app/assets/javascripts/initializers/initializeBaseTracking.js.erb @@ -1,4 +1,10 @@ function initializeBaseTracking() { + trackGoogleAnalytics3(); + trackGoogleAnalytics4(); + trackCustomImpressions(); +} + +function trackGoogleAnalytics3() { var wait = 0; var addedGA = false; var gaTrackingCode = document.body.dataset.gaTracking; @@ -25,9 +31,48 @@ function initializeBaseTracking() { }, 25); eventListening(); } - trackCustomImpressions(); } +function trackGoogleAnalytics4() { + var wait = 0; + var addedGA4 = false; + var ga4MeasurementCode = document.body.dataset.ga4TrackingId; + if (ga4MeasurementCode) { + var waitingOnGA4 = setInterval(function() { + if (!addedGA4) { + <%# Dynamically add the Google Analytics 4 script tag %> + var script = document.createElement('script'); + script.src = "http://www.googletagmanager.com/gtag/js?id=" + ga4MeasurementCode; + script.async = true; + document.head.appendChild(script); + } + addedGA4 = true; + wait++; + if (window.google_tag_manager) { + <%# Define the gtag function and call it. Adapted from https://stackoverflow.com/questions/22716542/google-analytics-code-explanation %> + window.dataLayer = window.dataLayer || []; + function gtag(){dataLayer.push(arguments);} + + window['gtag'] = window['gtag'] || function () { + dataLayer.push(arguments) + } + + gtag('js', new Date()); + gtag('config', ga4MeasurementCode, { 'anonymize_ip': true }); + clearInterval(waitingOnGA4); + } + if (wait > 85) { + clearInterval(waitingOnGA4); + <%# The gem we're using server-side (Staccato) is not yet compatible with the Google Analytics 4 tracking code. + More details: https://github.com/tpitale/staccato/issues/97 %> + <%# fallbackActivityRecording(); %> + } + }, 25); + eventListening(); + } +} + + function fallbackActivityRecording() { var tokenMeta = document.querySelector("meta[name='csrf-token']") if (!tokenMeta) { @@ -69,6 +114,15 @@ function eventListening(){ } } +function ga4eventListening(){ + var registerNowButt = document.getElementById("cta-comment-register-now-link"); + if (registerNowButt) { + registerNowButt.onclick = function(){ + gtag('event', 'register-now-click' ); + } + } +} + function trackCustomImpressions() { setTimeout(function(){ var ArticleElement = document.getElementById('article-body') || document.getElementById('comment-article-indicator'); diff --git a/app/assets/javascripts/initializers/initializeBodyData.js b/app/assets/javascripts/initializers/initializeBodyData.js index c68cc2e27..945c7f28e 100644 --- a/app/assets/javascripts/initializers/initializeBodyData.js +++ b/app/assets/javascripts/initializers/initializeBodyData.js @@ -41,6 +41,9 @@ function fetchBaseData() { if (typeof ga === 'function') { ga('set', 'userId', JSON.parse(user).id); } + if (typeof gtag === 'function') { + gtag('set', 'user_Id', JSON.parse(user).id); + } }, 400); } else { // Ensure user data is not exposed if no one is logged in diff --git a/app/assets/javascripts/initializers/initializeSponsorshipVisibility.js b/app/assets/javascripts/initializers/initializeSponsorshipVisibility.js index e55aaa545..b3c799d8b 100644 --- a/app/assets/javascripts/initializers/initializeSponsorshipVisibility.js +++ b/app/assets/javascripts/initializers/initializeSponsorshipVisibility.js @@ -13,13 +13,25 @@ function sponsorClickHandler(event) { ); } +function sponsorClickHandlerGA4(event) { + gtag('event', 'click sponsor link', { + event_category: 'click', + event_label: event.target.dataset.details, + }); +} + function listenForSponsorClick() { setTimeout(() => { - if (window.ga) { + if (window.ga || window.gtag) { var links = document.getElementsByClassName('partner-link'); // eslint-disable-next-line no-plusplus for (var i = 0; i < links.length; i++) { - links[i].onclick = sponsorClickHandler; + if (window.ga) { + links[i].onclick = sponsorClickHandler; + } + if (window.gtag) { + links[i].onclick = sponsorClickHandlerGA4; + } } } }, 400); @@ -32,8 +44,8 @@ function initializeSponsorshipVisibility() { var user = userData(); if (el) { setTimeout(() => { - if (window.ga) { - if (document.querySelectorAll('[data-partner-seen]').length === 0) { + if (document.querySelectorAll('[data-partner-seen]').length === 0) { + if (window.ga) { ga( 'send', 'event', @@ -42,8 +54,14 @@ function initializeSponsorshipVisibility() { el.dataset.details, null, ); - el.dataset.partnerSeen = 'true'; } + if (window.gtag) { + gtag('event', 'sponsor displayed on page', { + event_category: 'view', + event_label: el.dataset.details, + }); + } + el.dataset.partnerSeen = 'true'; } }, 400); } diff --git a/app/javascript/.eslintrc.js b/app/javascript/.eslintrc.js index de1780faa..39b7a3236 100644 --- a/app/javascript/.eslintrc.js +++ b/app/javascript/.eslintrc.js @@ -79,6 +79,7 @@ module.exports = { InstantClick: false, filterXSS: false, ga: false, + gtag: false, Honeybadger: false, AndroidBridge: false, }, diff --git a/app/javascript/packs/homePageFeed.jsx b/app/javascript/packs/homePageFeed.jsx index 86e64826c..30326ecc2 100644 --- a/app/javascript/packs/homePageFeed.jsx +++ b/app/javascript/packs/homePageFeed.jsx @@ -11,10 +11,10 @@ import { getUserDataAndCsrfToken } from '@utilities/getUserDataAndCsrfToken'; * * @param {number} articleId */ -function sendFeaturedArticleAnalytics(articleId) { - (function logFeaturedArticleImpression() { +function sendFeaturedArticleGoogleAnalytics(articleId) { + (function logFeaturedArticleImpressionGA() { if (!window.ga || !ga.create) { - setTimeout(logFeaturedArticleImpression, 20); + setTimeout(logFeaturedArticleImpressionGA, 20); return; } @@ -29,6 +29,20 @@ function sendFeaturedArticleAnalytics(articleId) { })(); } +function sendFeaturedArticleAnalyticsGA4(articleId) { + (function logFeaturedArticleImpressionGA4() { + if (!window.gtag) { + setTimeout(logFeaturedArticleImpressionGA4, 20); + return; + } + + gtag('event', 'featured-feed-impression', { + event_category: 'view', + event_label: `articles-${articleId}`, + }); + })(); +} + const FeedLoading = () => (