Skip to content

Commit

Permalink
Merge pull request #518 from jaydonkrooss/493-ga4-oneTrust-integration
Browse files Browse the repository at this point in the history
493: update Google Analytics settings, use Umich OneTrust banner
  • Loading branch information
jaydonkrooss committed Jul 1, 2024
2 parents dfbbc16 + 72729b9 commit bc6410c
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 3 deletions.
23 changes: 21 additions & 2 deletions src/assets/src/hooks/useGoogleAnalytics.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,34 @@
import { useState, useEffect } from 'react';
import GoogleAnalytics from 'react-ga4';
import { useLocation } from 'react-router-dom';
import { useOneTrust } from './useOneTrust';

export enum GoogleAnalyticsConsentValue {
Denied = "denied",
Granted = "granted"
}

export const useGoogleAnalytics = (googleAnalyticsId?: string, debug?: boolean) => {
let location = useLocation();
const [initializeOneTrust] = useOneTrust();

const [initialized, setInitialized] = useState(false);
const [previousPage, setPreviousPage] = useState(null as string | null);

if (googleAnalyticsId && !initialized) {
setInitialized(true);
GoogleAnalytics.gtag("consent", "default", {
ad_storage: GoogleAnalyticsConsentValue.Denied,
analytics_storage: GoogleAnalyticsConsentValue.Denied,
functionality_storage: GoogleAnalyticsConsentValue.Denied,
personalization_storage: GoogleAnalyticsConsentValue.Denied,
ad_user_data: GoogleAnalyticsConsentValue.Denied,
ad_personalization: GoogleAnalyticsConsentValue.Denied,
wait_for_update: 500
});
GoogleAnalytics.initialize(googleAnalyticsId, { testMode: debug });
if (initializeOneTrust) {
initializeOneTrust(GoogleAnalytics);
}
setInitialized(true);
}

useEffect(() => {
Expand Down
70 changes: 70 additions & 0 deletions src/assets/src/hooks/useOneTrust.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { GA4 } from 'react-ga4/types/ga4';
import Cookies from "js-cookie";
import { GoogleAnalyticsConsentValue } from './useGoogleAnalytics';

declare global {
interface Window {
OnetrustActiveGroups?: string;
OptanonWrapper?: () => void;
}
}

// UofM OneTrust cookie documentation: https://vpcomm.umich.edu/resources/cookie-disclosure/
enum OneTrustCookieCategory {
StrictlyNecessary = "C0001",
Performance = "C0002",
Functionality = "C0003",
Targeting = "C0004",
SocialMedia = "C0005",
}

export const useOneTrust = (): [(googleAnalytics:GA4) => void] | [] =>
{
// Embeds the script for UofM OneTrust consent banner implementation
// See instructions at https://vpcomm.umich.edu/resources/cookie-disclosure/#3rd-party-google-analytics
const initializeOneTrust = (googleAnalytics: GA4) => {
// Callback is used by OneTrust to update Google Analytics consent tags and remove cookies
const updateGtagCallback = () => {
if (!window.OnetrustActiveGroups) {
return;
}
// Update Google Analytics consent based on OneTrust active groups
// "Strictly Necessary Cookies" are always granted. C0001 (StrictlyNecessary), C0003 (Functionality)
if (window.OnetrustActiveGroups.includes(OneTrustCookieCategory.Performance)) {
googleAnalytics.gtag("consent", "update", { analytics_storage: GoogleAnalyticsConsentValue.Granted });
}
if (window.OnetrustActiveGroups.includes(OneTrustCookieCategory.Functionality)) {
googleAnalytics.gtag("consent", "update", { functional_storage: GoogleAnalyticsConsentValue.Granted });
}

// "Analytics & Advertising Cookies" are optional for EU users. C0002 (Performance)
if (window.OnetrustActiveGroups.includes(OneTrustCookieCategory.Targeting)) {
googleAnalytics.gtag("consent", "update", {
ad_storage: GoogleAnalyticsConsentValue.Granted,
ad_user_data: GoogleAnalyticsConsentValue.Granted,
ad_personalization: GoogleAnalyticsConsentValue.Granted,
personalization_storage: GoogleAnalyticsConsentValue.Granted
});
} else {
// Remove Google Analytics cookies if tracking is declined by EU users
// Uses same library as this GA4 implementation: https://dev.to/ramonak/react-enable-google-analytics-after-a-user-grants-consent-5bg3
Cookies.remove("_ga");
Cookies.remove("_gat");
Cookies.remove("_gid");
}
googleAnalytics.event({ action: 'um_consent_updated', category: 'consent' });
}
window.OptanonWrapper = updateGtagCallback;

const oneTrustScriptDomain = "03e0096b-3569-4b70-8a31-918e55aa20da"
const src =`https://cdn.cookielaw.org/consent/${oneTrustScriptDomain}/otSDKStub.js`

const script = document.createElement('script');
script.src = src;
script.type = 'text/javascript';
script.dataset.domainScript = oneTrustScriptDomain;
document.head.appendChild(script);
}

return [ initializeOneTrust ];
};
2 changes: 2 additions & 0 deletions src/officehours/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ def str_to_bool(val):
LOGIN_REDIRECT_URL = '/'
LOGOUT_REDIRECT_URL = '/'

PRIVACY_REDIRECT_URL = 'https://umich.edu/about/privacy/'

OIDC_RP_CLIENT_ID = os.getenv('OIDC_RP_CLIENT_ID')
OIDC_RP_CLIENT_SECRET = os.getenv('OIDC_RP_CLIENT_SECRET')
OIDC_OP_AUTHORIZATION_ENDPOINT = os.getenv('OIDC_OP_AUTHORIZATION_ENDPOINT')
Expand Down
3 changes: 2 additions & 1 deletion src/officehours_ui/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from django.conf import settings
from django.views.generic.base import RedirectView

from .views import SpaView, AuthPromptView, auth_callback_view
from .views import SpaView, AuthPromptView, auth_callback_view, privacy_policy_redirect_view


urlpatterns = [
Expand All @@ -19,4 +19,5 @@
path('callback/<backend_name>/', auth_callback_view, name='auth_callback'),
path("robots.txt", RedirectView.as_view(url='/static/robots.txt', permanent=True)),
path("favicon.ico", RedirectView.as_view(url='/static/favicon.ico', permanent=True)),
path("privacy/", privacy_policy_redirect_view, name='privacy-policy')
]
4 changes: 4 additions & 0 deletions src/officehours_ui/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from django.shortcuts import redirect
from django.views.generic import TemplateView
from django.conf import settings
from django.http import Http404
Expand Down Expand Up @@ -45,3 +46,6 @@ def auth_callback_view(request, backend_name: IMPLEMENTED_BACKEND_NAME):
except AttributeError:
raise Http404(f"Backend {backend_name} does not use three-legged OAuth2.")
return auth_callback(request)

def privacy_policy_redirect_view(request):
return redirect(settings.PRIVACY_REDIRECT_URL)
16 changes: 16 additions & 0 deletions src/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"@fortawesome/fontawesome-svg-core": "^6.4.0",
"@fortawesome/free-solid-svg-icons": "^6.4.0",
"@fortawesome/react-fontawesome": "^0.2.0",
"js-cookie": "^3.0.5",
"react": "^18.2.0",
"react-bootstrap": "^2.7.4",
"react-dom": "^18.2.0",
Expand All @@ -21,6 +22,7 @@
"check-types": "tsc"
},
"devDependencies": {
"@types/js-cookie": "^3.0.6",
"@types/react": "^18.2.7",
"@types/react-dom": "^18.2.4",
"css-loader": "~6.8.1",
Expand Down

0 comments on commit bc6410c

Please sign in to comment.