diff --git a/app/assets/javascripts/discourse/app/initializers/page-tracking.js b/app/assets/javascripts/discourse/app/initializers/page-tracking.js index 589f4b66cf..80a462d5e8 100644 --- a/app/assets/javascripts/discourse/app/initializers/page-tracking.js +++ b/app/assets/javascripts/discourse/app/initializers/page-tracking.js @@ -36,8 +36,11 @@ export default { return; } - // Also use Universal Analytics if it is present - if (typeof window.ga !== "undefined") { + // Use Universal Analytics v3 if it is present + if ( + typeof window.ga !== "undefined" && + typeof window.gtag === "undefined" + ) { appEvents.on("page:changed", (data) => { if (!data.replacedOnlyQueryParams) { window.ga("send", "pageview", { page: data.url, title: data.title }); @@ -45,7 +48,19 @@ export default { }); } - // And Google Tag Manager too + // And Universal Analytics v4 if we're upgraded + if (typeof window.gtag !== "undefined") { + appEvents.on("page:changed", (data) => { + if (!data.replacedOnlyQueryParams) { + window.gtag("event", "page_view", { + page_location: data.url, + page_title: data.title, + }); + } + }); + } + + // Google Tag Manager too if (typeof window.dataLayer !== "undefined") { appEvents.on("page:changed", (data) => { if (!data.replacedOnlyQueryParams) { diff --git a/app/assets/javascripts/google-universal-analytics.js b/app/assets/javascripts/google-universal-analytics-v3.js similarity index 100% rename from app/assets/javascripts/google-universal-analytics.js rename to app/assets/javascripts/google-universal-analytics-v3.js diff --git a/app/assets/javascripts/google-universal-analytics-v4.js b/app/assets/javascripts/google-universal-analytics-v4.js new file mode 100644 index 0000000000..d16f3de9eb --- /dev/null +++ b/app/assets/javascripts/google-universal-analytics-v4.js @@ -0,0 +1,26 @@ +// discourse-skip-module +(function () { + const gaDataElement = document.getElementById("data-ga-universal-analytics"); + window.dataLayer = window.dataLayer || []; + + window.gtag = function () { + window.dataLayer.push(arguments); + }; + window.gtag("js", new Date()); + + let autoLinkConfig = {}; + + if (gaDataElement.dataset.autoLinkDomains.length) { + const autoLinkDomains = gaDataElement.dataset.autoLinkDomains.split("|"); + autoLinkConfig = { + linker: { + accept_incoming: true, + domains: autoLinkDomains, + }, + }; + } + window.gtag("config", gaDataElement.dataset.trackingCode, { + send_page_view: false, + autoLinkConfig, + }); +})(); diff --git a/app/views/common/_google_universal_analytics.html.erb b/app/views/common/_google_universal_analytics.html.erb index 81d7a96db9..572c281768 100644 --- a/app/views/common/_google_universal_analytics.html.erb +++ b/app/views/common/_google_universal_analytics.html.erb @@ -4,4 +4,9 @@ auto_link_domains: SiteSetting.ga_universal_auto_link_domains } %> -<%= preload_script "google-universal-analytics" %> +<% if SiteSetting.ga_version == "v3_analytics" %> + <%= preload_script "google-universal-analytics-v3" %> +<% elsif SiteSetting.ga_version == "v4_gtag" %> + + <%= preload_script "google-universal-analytics-v4" %> +<% end %> diff --git a/config/application.rb b/config/application.rb index a55a24e27a..b4b9efeb8e 100644 --- a/config/application.rb +++ b/config/application.rb @@ -159,7 +159,8 @@ module Discourse markdown-it-bundle.js service-worker.js google-tag-manager.js - google-universal-analytics.js + google-universal-analytics-v3.js + google-universal-analytics-v4.js start-discourse.js print-page.js omniauth-complete.js diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index f6ff02dba9..f5c3dbf817 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1555,9 +1555,10 @@ 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 https://google.com/analytics" - ga_universal_domain_name: "Google Universal Analytics (analytics.js) domain name, eg: mysite.com; see https://google.com/analytics" - 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 Google's Cross-Domain Tracking guide." + ga_version: "Version of Google Universal Analytics to use: v3 (analytics.js), v4 (gtag)" + ga_universal_tracking_code: "Google Universal Analytics tracking code ID, eg: UA-12345678-9; see https://google.com/analytics" + ga_universal_domain_name: "Google Universal Analytics domain name, eg: mysite.com; see https://google.com/analytics" + ga_universal_auto_link_domains: "Enable Google Universal Analytics cross-domain tracking. Outgoing links to these domains will have the client id added to them. See Google's Cross-Domain Tracking guide." gtm_container_id: "Google Tag Manager container id. eg: GTM-ABCDEF.
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 https://developers.google.com/webmasters/ajax-crawling/docs/learn-more" moderators_manage_categories_and_groups: "Allow moderators to manage categories and groups" diff --git a/config/site_settings.yml b/config/site_settings.yml index 0934a42be6..8f502a803a 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -139,6 +139,12 @@ basic: default: 365 min: 7 max: 36500 + ga_version: + type: enum + default: v3_analytics + choices: + - v3_analytics + - v4_gtag ga_universal_tracking_code: client: true default: "" diff --git a/lib/content_security_policy/default.rb b/lib/content_security_policy/default.rb index 29c59c44d9..1d5315709e 100644 --- a/lib/content_security_policy/default.rb +++ b/lib/content_security_policy/default.rb @@ -56,7 +56,9 @@ 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 + # we need analytics.js still as gtag/js is a script wrapper for it 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? && SiteSetting.ga_version == "v4_gtag" sources << 'https://www.googletagmanager.com/gtm.js' if SiteSetting.gtm_container_id.present? end end diff --git a/lib/discourse_js_processor.rb b/lib/discourse_js_processor.rb index e5893ce460..f2f601a5e9 100644 --- a/lib/discourse_js_processor.rb +++ b/lib/discourse_js_processor.rb @@ -52,7 +52,8 @@ class DiscourseJsProcessor wizard-start onpopstate-handler google-tag-manager - google-universal-analytics + google-universal-analytics-v3 + google-universal-analytics-v4 activate-account auto-redirect embed-application diff --git a/spec/lib/content_security_policy_spec.rb b/spec/lib/content_security_policy_spec.rb index 04557e3900..14b5c8f658 100644 --- a/spec/lib/content_security_policy_spec.rb +++ b/spec/lib/content_security_policy_spec.rb @@ -69,12 +69,30 @@ describe ContentSecurityPolicy do expect(script_srcs).to include("'report-sample'") end - it 'allowlists Google Analytics and Tag Manager when integrated' do - SiteSetting.ga_universal_tracking_code = 'UA-12345678-9' + context 'for Google Analytics' do + before do + SiteSetting.ga_universal_tracking_code = 'UA-12345678-9' + end + + it 'allowlists Google Analytics v3 when integrated' do + script_srcs = parse(policy)['script-src'] + expect(script_srcs).to include('https://www.google-analytics.com/analytics.js') + expect(script_srcs).not_to include('https://www.googletagmanager.com/gtag/js') + end + + it 'allowlists Google Analytics v4 when integrated' do + SiteSetting.ga_version = 'v4_gtag' + + 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') + end + end + + it 'allowlists Google Tag Manager when integrated' 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/gtm.js') end