Add configuration for admins to determine whether to show cookie banner (#20517)

* Cookie banner admin config (wip)

* GA4 consent mode based on cookie acceptance

* Fix i18n

* Merge upstream

* Adjust heading
This commit is contained in:
Ben Halpern 2024-01-15 13:22:17 -05:00 committed by GitHub
parent 539844165f
commit ae2371d9a4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 95 additions and 13 deletions

View file

@ -1,4 +1,5 @@
/*eslint-disable prefer-rest-params*/
/* global isTouchDevice */
function initializeBaseTracking() {
showCookieConsentBanner();
@ -7,11 +8,12 @@ function initializeBaseTracking() {
trackCustomImpressions();
}
// Google Anlytics 3 is deprecated, and mostly not supported, but some sites may still be using it for now.
function trackGoogleAnalytics3() {
let wait = 0;
let addedGA = false;
const gaTrackingCode = document.body.dataset.gaTracking;
if (gaTrackingCode) {
if (gaTrackingCode && localStorage.getItem('cookie_status') === 'allowed') {
const waitingOnGA = setInterval(() => {
if (!addedGA) {
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
@ -33,6 +35,8 @@ function trackGoogleAnalytics3() {
}
}, 25);
eventListening();
} else if (gaTrackingCode) {
fallbackActivityRecording();
}
}
@ -60,9 +64,13 @@ function trackGoogleAnalytics4() {
window['gtag'] = window['gtag'] || function () {
window.dataLayer.push(arguments)
}
const consent = localStorage.getItem('cookie_status') === 'allowed' ? 'granted' : 'denied';
gtag('js', new Date());
gtag('config', ga4MeasurementCode, { 'anonymize_ip': true });
gtag('consent', 'default', {
'ad_storage': consent,
'analytics_storage': consent
});
clearInterval(waitingOnGA4);
}
if (wait > 85) {
@ -181,7 +189,7 @@ function trackCustomImpressions() {
function showCookieConsentBanner() {
// if current url includes ?cookietest=true
if (window.location.href.includes('cookietest=true')) {
if (shouldShowCookieBanner()) {
// show modal with cookie consent
const cookieDiv = document.getElementById('cookie-consent');
@ -190,7 +198,7 @@ function showCookieConsentBanner() {
<div class="cookie-consent-modal">
<div class="cookie-consent-modal__content">
<p>
<strong>Some content on our site requires cookies for personalization and analytics.</strong>
<strong>Some content on our site requires cookies for personalization.</strong>
</p>
<p>
Read our full <a href="/privacy">privacy policy</a> to learn more.
@ -210,6 +218,12 @@ function showCookieConsentBanner() {
document.getElementById('cookie-accept').onclick = (() => {
localStorage.setItem('cookie_status', 'allowed');
cookieDiv.style.display = 'none';
if (window.gtag) {
gtag('consent', 'update', {
'ad_storage': 'granted',
'analytics_storage': 'granted'
});
}
});
document.getElementById('cookie-dismiss').onclick = (() => {
@ -220,6 +234,38 @@ function showCookieConsentBanner() {
}
}
function shouldShowCookieBanner() {
const { userStatus, cookieBannerUserContext, cookieBannerPlatformContext } = document.body.dataset;
function determineActualPlatformContext() {
if (navigator.userAgent.includes('DEV-Native')) {
return 'mobile_app'
} else if (isTouchDevice()) {
return 'mobile_web'
}
return 'desktop_web'
}
// Determine the actual platform context
const actualPlatformContext = determineActualPlatformContext();
// Check if either user or platform context is set to 'off'
if (cookieBannerUserContext === 'off' || cookieBannerPlatformContext === 'off') {
return false;
}
// Check based on user status
const showForUserContext = (userStatus === 'logged-in' && cookieBannerUserContext === 'all') ||
(userStatus !== 'logged-in' && cookieBannerUserContext !== 'off');
// Check based on platform context
const showForPlatformContext = (cookieBannerPlatformContext === 'all') ||
(cookieBannerPlatformContext === 'all_web' && ['desktop_web', 'mobile_web'].includes(actualPlatformContext)) ||
(cookieBannerPlatformContext === actualPlatformContext);
// Return true if both user context and platform context conditions are met
return showForUserContext && showForPlatformContext;
}
function trackPageView(dataBody, csrfToken) {
window.fetch('/page_views', {
method: 'POST',

View file

@ -45,6 +45,14 @@ module Constants
description: I18n.t("lib.constants.settings.general.ga_analytics_4.description"),
placeholder: ""
},
cookie_banner_user_context: {
description: I18n.t("lib.constants.settings.general.cookie_banner_user_context.description"),
placeholder: "off"
},
coolie_banner_platform_context: {
description: I18n.t("lib.constants.settings.general.coolie_banner_platform_context.description"),
placeholder: "off"
},
health_check_token: {
description: I18n.t("lib.constants.settings.general.health.description"),
placeholder: I18n.t("lib.constants.settings.general.health.placeholder")

View file

@ -1,5 +1,8 @@
module Settings
class General < Base
BANNER_USER_CONFIGS = %w[off logged_out_only all].freeze
BANNER_PLATFORM_CONFIGS = %w[off all all_web desktop_web mobile_web mobile_app].freeze
self.table_name = "site_configs"
SOCIAL_MEDIA_SERVICES = %w[
twitter facebook github instagram twitch mastodon
@ -24,9 +27,15 @@ module Settings
setting :contact_email, type: :string, default: ApplicationConfig["DEFAULT_EMAIL"]
setting :periodic_email_digest, type: :integer, default: 2
# Google Analytics Tracking ID, e.g. UA-71991000-1
# Analytics and tracking
setting :ga_tracking_id, type: :string, default: ApplicationConfig["GA_TRACKING_ID"]
setting :ga_analytics_4_id, type: :string, default: ApplicationConfig["GA_ANALYTICS_4_ID"]
setting :cookie_banner_user_context, type: :string, default: "off", validates: {
inclusion: { in: BANNER_USER_CONFIGS }
}
setting :coolie_banner_platform_context, type: :string, default: "off", validates: {
inclusion: { in: BANNER_PLATFORM_CONFIGS }
}
# Ahoy Tracking
setting :ahoy_tracking, type: :boolean, default: false

View file

@ -2,16 +2,9 @@
url: admin_settings_general_settings_path,
html: { data: { action: "submit->config#updateConfigurationSettings" } }) do |f| %>
<details class="crayons-card">
<summary class="crayons-subtitle-2 p-6">Google Analytics</summary>
<summary class="crayons-subtitle-2 p-6">Google Analytics and Cookies</summary>
<div class="p-6 pt-0">
<fieldset class="grid gap-4">
<div class="crayons-field">
<%= 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] %>
@ -19,6 +12,22 @@
class: "crayons-textfield",
value: Settings::General.ga_analytics_4_id %>
</div>
<div class="crayons-field">
<%= admin_config_label :cookie_banner_user_context, model: Settings::General %>
<%= admin_config_description Constants::Settings::General.details[:cookie_banner_user_context][:description] %>
<%= select_tag "settings_general[cookie_banner_user_context]",
options_for_select(Settings::General::BANNER_USER_CONFIGS, Settings::General.cookie_banner_user_context),
multiple: false,
class: "crayons-select" %>
</div>
<div class="crayons-field">
<%= admin_config_label :coolie_banner_platform_context, model: Settings::General %>
<%= admin_config_description Constants::Settings::General.details[:coolie_banner_platform_context][:description] %>
<%= select_tag "settings_general[coolie_banner_platform_context]",
options_for_select(Settings::General::BANNER_PLATFORM_CONFIGS, Settings::General.coolie_banner_platform_context),
multiple: false,
class: "crayons-select" %>
</div>
</fieldset>
<%= render "update_setting_button", f: f %>
</div>

View file

@ -55,6 +55,8 @@
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-cookie-banner-user-context="<%= j(Settings::General.cookie_banner_user_context) %>"
data-cookie-banner-platform-context="<%= j(Settings::General.coolie_banner_platform_context) %>"
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" %>

View file

@ -118,6 +118,10 @@ en:
description: Google Universal Analytics Tracking ID, e.g. UA-XXXXXXXX-1
ga_analytics_4:
description: Google Analytics 4 Measurement ID, e.g. G-XXXXXXXXX
cookie_banner_user_context:
description: Used to determine whether the cookie banner should be shown based on user context (i.e. logged in or not)
coolie_banner_platform_context:
description: Used to determine whether the cookie banner should be shown based on platform (i.e. web/mobile)
hashtag:
description: Used as the twitter hashtag of the community
placeholder: '#DEVCommunity'

View file

@ -118,6 +118,10 @@ fr:
description: Google Universal Analytics Tracking ID, e.g. UA-XXXXXXXX-1
ga_analytics_4:
description: Google Analytics 4 Measurement ID, e.g. G-XXXXXXXXX
cookie_banner_user_context:
description: Used to determine whether the cookie banner should be shown based on user context (i.e. logged in or not)
coolie_banner_platform_context:
description: Used to determine whether the cookie banner should be shown based on platform (i.e. web/mobile)
hashtag:
description: Used as the twitter hashtag of the community
placeholder: '#DEVCommunity'