Add Google Analytics 4 Support (#18124)
This commit is contained in:
parent
656d698da1
commit
fc1031f52a
16 changed files with 145 additions and 19 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ module.exports = {
|
|||
InstantClick: false,
|
||||
filterXSS: false,
|
||||
ga: false,
|
||||
gtag: false,
|
||||
Honeybadger: false,
|
||||
AndroidBridge: false,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue