FEATURE: Upgrade analytics.js to gtag.js (#10893)

FEATURE: Upgrade analytics.js to gtag.js (#10893)

Per Google, sites are encouraged to upgrade from analytics.js to gtag.js for Google Analytics tracking. This commit updates core Discourse to use the new gtag.js API Google is asking sites to use. This API has feature parity with analytics.js but does not use trackers.

diff --git a/app/assets/javascripts/google-universal-analytics.js b/app/assets/javascripts/google-universal-analytics.js
index b0e06a5..5a70da0 100644
--- a/app/assets/javascripts/google-universal-analytics.js
+++ b/app/assets/javascripts/google-universal-analytics.js
@@ -1,21 +1,23 @@
 // discourse-skip-module
-/* eslint-disable */
-// prettier-ignore
-(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
-  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
-  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
-  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
-/* eslint-enable */
-
 (function () {
   const gaDataElement = document.getElementById("data-ga-universal-analytics");
-  const gaJson = JSON.parse(gaDataElement.dataset.json);
 
-  window.ga("create", gaDataElement.dataset.trackingCode, gaJson);
+  window.dataLayer = window.dataLayer || [];
+  function gtag() {
+    window.dataLayer.push(arguments);
+  }
+  gtag("js", new Date());
+
+  let autoLinkConfig = {};
+
   if (gaDataElement.dataset.autoLinkDomains.length) {
     const autoLinkDomains = gaDataElement.dataset.autoLinkDomains.split("|");
-
-    window.ga("require", "linker");
-    window.ga("linker:autoLink", autoLinkDomains);
+    autoLinkConfig = {
+      linker: {
+        accept_incoming: true,
+        domains: autoLinkDomains,
+      },
+    };
   }
+  gtag("config", gaDataElement.dataset.trackingCode, autoLinkConfig);
 })();
diff --git a/app/views/common/_google_universal_analytics.html.erb b/app/views/common/_google_universal_analytics.html.erb
index 81d7a96..8a855ae 100644
--- a/app/views/common/_google_universal_analytics.html.erb
+++ b/app/views/common/_google_universal_analytics.html.erb
@@ -4,4 +4,6 @@
   auto_link_domains: SiteSetting.ga_universal_auto_link_domains
 } %>
 
+<script async src="https://www.googletagmanager.com/gtag/js?id=<%= SiteSetting.ga_universal_tracking_code %>"></script>
+
 <%= preload_script "google-universal-analytics" %>
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index d885ac3..870d217 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -1551,9 +1551,9 @@ en:
     pending_users_reminder_delay: "Notify moderators if new users have been waiting for approval for longer than this many hours. Set to -1 to disable notifications."
     persistent_sessions: "Users will remain logged in when the web browser is closed"
     maximum_session_age: "User will remain logged in for n hours since last visit"
-    ga_universal_tracking_code: "Google Universal Analytics (analytics.js) tracking code ID, eg: UA-12345678-9; see <a href='https://google.com/analytics' target='_blank'>https://google.com/analytics</a>"
-    ga_universal_domain_name: "Google Universal Analytics (analytics.js) domain name, eg: mysite.com; see <a href='https://google.com/analytics' target='_blank'>https://google.com/analytics</a>"
-    ga_universal_auto_link_domains: "Enable Google Universal Analytics (analytics.js) cross-domain tracking. Outgoing links to these domains will have the client id added to them. See <a href='https://support.google.com/analytics/answer/1034342?hl=en' target='_blank'>Google's Cross-Domain Tracking guide.</a>"
+    ga_universal_tracking_code: "Google Universal Analytics (gtm.js) tracking code ID, eg: UA-12345678-9; see <a href='https://google.com/analytics' target='_blank'>https://google.com/analytics</a>"
+    ga_universal_domain_name: "Google Universal Analytics (gtm.js) domain name, eg: mysite.com; see <a href='https://google.com/analytics' target='_blank'>https://google.com/analytics</a>"
+    ga_universal_auto_link_domains: "Enable Google Universal Analytics (gtm.js) cross-domain tracking. Outgoing links to these domains will have the client id added to them. See <a href='https://support.google.com/analytics/answer/1034342?hl=en' target='_blank'>Google's Cross-Domain Tracking guide.</a>"
     gtm_container_id: "Google Tag Manager container id. eg: GTM-ABCDEF. <br/>Note: Third-party scripts loaded by GTM may need to be allowlisted in 'content security policy script src'."
     enable_escaped_fragments: "Fall back to Google's Ajax-Crawling API if no webcrawler is detected. See <a href='https://developers.google.com/webmasters/ajax-crawling/docs/learn-more' target='_blank'>https://developers.google.com/webmasters/ajax-crawling/docs/learn-more</a>"
     moderators_manage_categories_and_groups: "Allow moderators to manage categories and groups"
diff --git a/lib/content_security_policy/default.rb b/lib/content_security_policy/default.rb
index 29c59c4..e8ff3bf 100644
--- a/lib/content_security_policy/default.rb
+++ b/lib/content_security_policy/default.rb
@@ -56,7 +56,7 @@ class ContentSecurityPolicy
       ].tap do |sources|
         sources << :report_sample if SiteSetting.content_security_policy_collect_reports
         sources << :unsafe_eval if Rails.env.development? # TODO remove this once we have proper source maps in dev
-        sources << 'https://www.google-analytics.com/analytics.js' if SiteSetting.ga_universal_tracking_code.present?
+        sources << 'https://www.googletagmanager.com/gtag/js' if SiteSetting.ga_universal_tracking_code.present?
         sources << 'https://www.googletagmanager.com/gtm.js' if SiteSetting.gtm_container_id.present?
       end
     end
diff --git a/spec/lib/content_security_policy_spec.rb b/spec/lib/content_security_policy_spec.rb
index 04557e3..be2d462 100644
--- a/spec/lib/content_security_policy_spec.rb
+++ b/spec/lib/content_security_policy_spec.rb
@@ -74,7 +74,7 @@ describe ContentSecurityPolicy do
       SiteSetting.gtm_container_id = 'GTM-ABCDEF'
 
       script_srcs = parse(policy)['script-src']
-      expect(script_srcs).to include('https://www.google-analytics.com/analytics.js')
+      expect(script_srcs).to include('https://www.googletagmanager.com/gtag/js')
       expect(script_srcs).to include('https://www.googletagmanager.com/gtm.js')
     end
 

GitHub sha: f4034226

This commit appears in #10893 which was approved by pmusaraj and pmusaraj. It was merged by justindirose.

Should this be .js rather than /js?

We have custom google analytics logic for page navigation - I think that might need to be updated as well:

https://github.com/discourse/discourse/blob/46d1c91e1a0fd40110619b85bd2ed1623c70c6d0/app/assets/javascripts/discourse/app/initializers/page-tracking.js#L27-L55

Looks like ga(...) is replaced with gtag(...), with some argument differences as well: https://developers.google.com/analytics/devguides/collection/upgrade/analyticsjs#measure_events

No the script is actually gtag/js

My bad, it should indeed be /js. Weird URL though!

Good catch @davidtaylorhq. I was looking at that but didn’t catch the differences.