Add Google Analytics 4 Support (#18124)

This commit is contained in:
Josh Puetz 2022-07-21 09:26:13 -05:00 committed by GitHub
parent 656d698da1
commit fc1031f52a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 145 additions and 19 deletions

View file

@ -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"

View file

@ -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,

View file

@ -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');

View file

@ -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

View file

@ -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);
}

View file

@ -79,6 +79,7 @@ module.exports = {
InstantClick: false,
filterXSS: false,
ga: false,
gtag: false,
Honeybadger: false,
AndroidBridge: false,
},

View file

@ -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 = () => (
<div data-testid="feed-loading">
<LoadingArticle version="featured" />
@ -89,7 +103,8 @@ export const renderFeed = async (timeFrame) => {
const [featuredStory, ...subStories] = feedItems;
if (featuredStory) {
sendFeaturedArticleAnalytics(featuredStory.id);
sendFeaturedArticleGoogleAnalytics(featuredStory.id);
sendFeaturedArticleAnalyticsGA4(featuredStory.id);
}
// 1. Show the pinned article first

View file

@ -35,6 +35,10 @@ module Constants
description: I18n.t("lib.constants.settings.general.ga_tracking.description"),
placeholder: ""
},
ga_analytics_4_id: {
description: I18n.t("lib.constants.settings.general.ga_analytics_4.description"),
placeholder: ""
},
health_check_token: {
description: I18n.t("lib.constants.settings.general.health.description"),
placeholder: I18n.t("lib.constants.settings.general.health.placeholder")

View file

@ -23,6 +23,7 @@ module Settings
# Google Analytics Tracking ID, e.g. UA-71991000-1
setting :ga_tracking_id, type: :string, default: ApplicationConfig["GA_TRACKING_ID"]
setting :ga_analytics_4_id, type: :string, default: ApplicationConfig["GA_ANALYTICS_4_ID"]
# Images
setting :main_social_image,

View file

@ -6,12 +6,19 @@
<div class="p-6 pt-0">
<fieldset class="grid gap-4">
<div class="crayons-field">
<%= admin_config_label :ga_tracking_id, "View ID" %>
<%= admin_config_label :ga_tracking_id, "Google Universal Analytics View ID" %>
<%= admin_config_description Constants::Settings::General.details[:ga_tracking_id][:description] %>
<%= f.text_field :ga_tracking_id,
class: "crayons-textfield",
value: Settings::General.ga_tracking_id %>
</div>
<div class="crayons-field">
<%= admin_config_label :ga_analytics_4_id, "Google Analytics 4 Measurement ID" %>
<%= admin_config_description Constants::Settings::General.details[:ga_analytics_4_id][:description] %>
<%= f.text_field :ga_analytics_4_id,
class: "crayons-textfield",
value: Settings::General.ga_analytics_4_id %>
</div>
</fieldset>
<%= render "update_setting_button", f: f %>
</div>

View file

@ -27,6 +27,7 @@
data-deployed-at="<%= j(ForemInstance.deployed_at) %>"
data-latest-commit-id="<%= j(ForemInstance.latest_commit_id) %>"
data-ga-tracking="<%= j(Settings::General.ga_tracking_id) %>"
data-ga4-tracking-id="<%= j(Settings::General.ga_analytics_4_id) %>"
data-controller="snackbar"
data-action="snackbar:add@document->snackbar#addItem">
<div id="body-styles">

View file

@ -54,7 +54,9 @@
data-honeybadger-key="<%= j(ApplicationConfig["HONEYBADGER_JS_API_KEY"]) %>"
data-deployed-at="<%= j(ForemInstance.deployed_at) %>"
data-latest-commit-id="<%= j(ForemInstance.latest_commit_id) %>"
data-ga-tracking="<%= j(Settings::General.ga_tracking_id) %>">
data-ga-tracking="<%= j(Settings::General.ga_tracking_id) %>"
data-ga4-tracking-id="<%= j(Settings::General.ga_analytics_4_id) %>"
>
<%# Repeat of stylesheets in <head> to fix rendering glitch: https://github.com/forem/forem/issues/12377 %>
<%= render "layouts/styles", qualifier: "secondary" %>
<div id="body-styles">

View file

@ -108,7 +108,9 @@ en:
favicon:
description: Used as the site favicon
ga_tracking:
description: Google Analytics Tracking ID, e.g. UA-71991000-1
description: Google Universal Analytics Tracking ID, e.g. UA-XXXXXXXX-1
ga_analytics_4:
description: Google Analytics 4 Measurement ID, e.g. G-XXXXXXXXX
hashtag:
description: Used as the twitter hashtag of the community
placeholder: '#DEVCommunity'
@ -205,4 +207,4 @@ en:
primary_hex:
description: Determines background/border of buttons etc. Must be dark enough to contrast with white text.
tag_feed:
description: Minimum score needed for a post to show up on default tag page.
description: Minimum score needed for a post to show up on default tag page.

View file

@ -108,7 +108,9 @@ fr:
favicon:
description: Used as the site favicon
ga_tracking:
description: Google Analytics Tracking ID, e.g. UA-71991000-1
description: Google Universal Analytics Tracking ID, e.g. UA-XXXXXXXX-1
ga_analytics_4:
description: Google Analytics 4 Measurement ID, e.g. G-XXXXXXXXX
hashtag:
description: Used as the twitter hashtag of the community
placeholder: '#DEVCommunity'

View file

@ -256,7 +256,7 @@ RSpec.describe "/admin/customization/config", type: :request do
end
end
describe "Google Analytics Reporting API v4" do
describe "Google Universal Analytics Reporting" do
it "updates ga_tracking_id" do
post admin_settings_general_settings_path, params: {
settings_general: { ga_tracking_id: "abc" }
@ -265,6 +265,15 @@ RSpec.describe "/admin/customization/config", type: :request do
end
end
describe "Google Analytics 4 Reporting" do
it "updates ga_analytics_4_id" do
post admin_settings_general_settings_path, params: {
settings_general: { ga_analytics_4_id: "abc" }
}
expect(Settings::General.ga_analytics_4_id).to eq("abc")
end
end
describe "Images" do
it "updates main_social_image" do
expected_default_image_url = URL.local_image("social-media-cover.png")

View file

@ -20,7 +20,7 @@ RSpec.describe "StoriesIndex", type: :request do
get "/"
expect(response.body).to include(CGI.escapeHTML(article.title))
renders_ga_tracking_data
renders_ga_tracking_fields
renders_proper_description
renders_min_read_time
renders_proper_sidebar(navigation_link)
@ -44,8 +44,9 @@ RSpec.describe "StoriesIndex", type: :request do
expect(response.body).to include(CGI.escapeHTML(navigation_link.name))
end
def renders_ga_tracking_data
def renders_ga_tracking_fields
expect(response.body).to include("data-ga-tracking=\"#{Settings::General.ga_tracking_id}\"")
expect(response.body).to include("data-ga4-tracking-id=\"#{Settings::General.ga_analytics_4_id}\"")
end
it "renders registration page if the Forem instance is private" do