From b624339d6fee1617768afe06198b83603630ee55 Mon Sep 17 00:00:00 2001 From: Jaydon Krooss Date: Tue, 25 Jun 2024 11:41:57 -0400 Subject: [PATCH 01/15] fixes #493 add oneTrust consent banner, update google Analytics settings --- src/assets/src/hooks/useGoogleAnalytics.ts | 50 ++++++++++++++++++++++ src/assets/src/hooks/useOneTrust.ts | 50 ++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 src/assets/src/hooks/useOneTrust.ts diff --git a/src/assets/src/hooks/useGoogleAnalytics.ts b/src/assets/src/hooks/useGoogleAnalytics.ts index 88eb6d42..6e19a299 100644 --- a/src/assets/src/hooks/useGoogleAnalytics.ts +++ b/src/assets/src/hooks/useGoogleAnalytics.ts @@ -1,17 +1,34 @@ import { useState, useEffect } from 'react'; import GoogleAnalytics from 'react-ga4'; import { useLocation } from 'react-router-dom'; +import { useOneTrust } from './useOneTrust'; + export const useGoogleAnalytics = (googleAnalyticsId?: string, debug?: boolean) => { let location = useLocation(); + const [initialized, setInitialized] = useState(false); const [previousPage, setPreviousPage] = useState(null as string | null); if (googleAnalyticsId && !initialized) { + GoogleAnalytics.gtag("consent", "default", { + ad_storage: "denied", + analytics_storage: "denied", + functionality_storage: "denied", + personalization_storage: "denied", + ad_user_data: "denied", + ad_personalization: "denied", + wait_for_update: 500 + }); setInitialized(true); GoogleAnalytics.initialize(googleAnalyticsId, { testMode: debug }); } + const [loaded, oneTrustActiveGroups, setOptanonWrapper] = useOneTrust(); + if (loaded) { + console.log("loaded oneTrustActiveGroups", oneTrustActiveGroups); + } + useEffect(() => { const page = location.pathname + location.search + location.hash; if (googleAnalyticsId && page !== previousPage) { @@ -19,4 +36,37 @@ export const useGoogleAnalytics = (googleAnalyticsId?: string, debug?: boolean) GoogleAnalytics.send({ hitType: "pageview", page }); } }, [location]); + + useEffect(() => { + const updateGtagConsent = () => { + console.log("effect oneTrustActiveGroups", oneTrustActiveGroups); + + if (oneTrustActiveGroups.includes("C0002")) { + GoogleAnalytics.gtag("consent", "update", { analytics_storage: "granted" }); + } + if (oneTrustActiveGroups.includes("C0003")) { + GoogleAnalytics.gtag("consent", "update", { functional_storage: "granted" }); + } + if (oneTrustActiveGroups.includes("C0004")) { + GoogleAnalytics.gtag("consent", "update", { + ad_storage: "granted", + ad_user_data: "granted", + ad_personalization: "granted", + personalization_storage: "granted" + }); + } else { + // Remove Google Analytics cookies if tracking is declined + document.cookie.split(';').forEach(cookie => { + const [name] = cookie.split('='); + if (name.trim().match(/^_ga(_.+)?$/)) { + document.cookie = `${name}=;path=/;domain=.${window.location.host.replace(/^(.*\.)?(.+\..+)$/, '$2')};expires=Thu, 01 Jan 1970 00:00:01 GMT`; + } + }); + } + // window.dataLayer.push({ event: 'um_consent_updated' }); + GoogleAnalytics.event({ action: 'um_consent_updated', category: 'consent' }); + }; + + setOptanonWrapper(updateGtagConsent) + }, [oneTrustActiveGroups]); } diff --git a/src/assets/src/hooks/useOneTrust.ts b/src/assets/src/hooks/useOneTrust.ts new file mode 100644 index 00000000..2b8e0dd8 --- /dev/null +++ b/src/assets/src/hooks/useOneTrust.ts @@ -0,0 +1,50 @@ +import { useState, useEffect } from 'react'; + +declare global { + interface Window { + OnetrustActiveGroups?: string; + OptanonWrapper?: () => void; + } + } + +export const useOneTrust = (): [boolean, string, (callback: () => void) => void] => { + const src = 'https://cdn.cookielaw.org/consent/03e0096b-3569-4b70-8a31-918e55aa20da/otSDKStub.js' + const dataDomainScript ='03e0096b-3569-4b70-8a31-918e55aa20da' + const [oneTrustActiveGroups, setOneTrustActiveGroups] = useState(""); + const [loaded, setLoaded] = useState(false) + + useEffect(() => { + const script = document.createElement('script'); + script.src = src; + script.type = 'text/javascript'; + if (dataDomainScript) script.dataset.domainScript = dataDomainScript; + + script.onload = () => { + setLoaded(true); + } + script.onerror = () => { + setLoaded(false); + console.error(`Failed to load script: ${src}`); + } + + document.head.appendChild(script); + + return () => { + if (document.head.contains(script)) { + document.head.removeChild(script); + } + }; + }, []); + + useEffect(() => { + if (window.OnetrustActiveGroups) { + setOneTrustActiveGroups(window.OnetrustActiveGroups); + } + }, [window.OnetrustActiveGroups]); + + const setOptanonWrapper = (callback: () => void) => { + window.OptanonWrapper = callback; + } + + return [ loaded, oneTrustActiveGroups, setOptanonWrapper ]; +}; From eacb9d93b0c7162b2b1382583c218989d18642a7 Mon Sep 17 00:00:00 2001 From: Jaydon Krooss Date: Tue, 25 Jun 2024 14:43:22 -0400 Subject: [PATCH 02/15] move log statement --- src/assets/src/hooks/useGoogleAnalytics.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/src/hooks/useGoogleAnalytics.ts b/src/assets/src/hooks/useGoogleAnalytics.ts index 6e19a299..afdecf35 100644 --- a/src/assets/src/hooks/useGoogleAnalytics.ts +++ b/src/assets/src/hooks/useGoogleAnalytics.ts @@ -26,7 +26,7 @@ export const useGoogleAnalytics = (googleAnalyticsId?: string, debug?: boolean) const [loaded, oneTrustActiveGroups, setOptanonWrapper] = useOneTrust(); if (loaded) { - console.log("loaded oneTrustActiveGroups", oneTrustActiveGroups); + console.log("loaded oneTrustActiveGroups" + oneTrustActiveGroups); } useEffect(() => { @@ -39,7 +39,6 @@ export const useGoogleAnalytics = (googleAnalyticsId?: string, debug?: boolean) useEffect(() => { const updateGtagConsent = () => { - console.log("effect oneTrustActiveGroups", oneTrustActiveGroups); if (oneTrustActiveGroups.includes("C0002")) { GoogleAnalytics.gtag("consent", "update", { analytics_storage: "granted" }); @@ -65,6 +64,7 @@ export const useGoogleAnalytics = (googleAnalyticsId?: string, debug?: boolean) } // window.dataLayer.push({ event: 'um_consent_updated' }); GoogleAnalytics.event({ action: 'um_consent_updated', category: 'consent' }); + console.log("effect oneTrustActiveGroups "+ oneTrustActiveGroups); }; setOptanonWrapper(updateGtagConsent) From 38698babaf0ce5b134381af8d6ae07cc0909748c Mon Sep 17 00:00:00 2001 From: Jaydon Krooss Date: Wed, 26 Jun 2024 16:49:25 -0400 Subject: [PATCH 03/15] log everything --- src/assets/src/containers/app.tsx | 1 + src/assets/src/hooks/useGoogleAnalytics.ts | 10 +++++++--- src/assets/src/hooks/useOneTrust.ts | 3 +++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/assets/src/containers/app.tsx b/src/assets/src/containers/app.tsx index 82014155..05dbfc48 100644 --- a/src/assets/src/containers/app.tsx +++ b/src/assets/src/containers/app.tsx @@ -19,6 +19,7 @@ interface AppProps { export function App(props: AppProps) { useGoogleAnalytics(props.globals.ga_tracking_id, props.globals.debug); + console.log("APP Rendered") const commonProps: PageProps = { user: props.globals.user, diff --git a/src/assets/src/hooks/useGoogleAnalytics.ts b/src/assets/src/hooks/useGoogleAnalytics.ts index afdecf35..430d0e69 100644 --- a/src/assets/src/hooks/useGoogleAnalytics.ts +++ b/src/assets/src/hooks/useGoogleAnalytics.ts @@ -9,8 +9,9 @@ export const useGoogleAnalytics = (googleAnalyticsId?: string, debug?: boolean) const [initialized, setInitialized] = useState(false); const [previousPage, setPreviousPage] = useState(null as string | null); - + console.log("useGA rendered, initialized " + initialized ? "true" : "false") if (googleAnalyticsId && !initialized) { + console.log("useGA initializing"); GoogleAnalytics.gtag("consent", "default", { ad_storage: "denied", analytics_storage: "denied", @@ -26,10 +27,12 @@ export const useGoogleAnalytics = (googleAnalyticsId?: string, debug?: boolean) const [loaded, oneTrustActiveGroups, setOptanonWrapper] = useOneTrust(); if (loaded) { - console.log("loaded oneTrustActiveGroups" + oneTrustActiveGroups); + console.log("useGA loaded " + oneTrustActiveGroups); } + console.log("useGA oneTrustActiveGroups " + oneTrustActiveGroups); useEffect(() => { + console.log("useGA Effect location" + location.pathname + location.search + location.hash + " previousPage " + previousPage) const page = location.pathname + location.search + location.hash; if (googleAnalyticsId && page !== previousPage) { setPreviousPage(page); @@ -38,7 +41,9 @@ export const useGoogleAnalytics = (googleAnalyticsId?: string, debug?: boolean) }, [location]); useEffect(() => { + console.log("useGA Effect oneTrustActiveGroups" + oneTrustActiveGroups) const updateGtagConsent = () => { + console.log("updateGtagConsent RUN oneTrustActiveGroups " + oneTrustActiveGroups) if (oneTrustActiveGroups.includes("C0002")) { GoogleAnalytics.gtag("consent", "update", { analytics_storage: "granted" }); @@ -64,7 +69,6 @@ export const useGoogleAnalytics = (googleAnalyticsId?: string, debug?: boolean) } // window.dataLayer.push({ event: 'um_consent_updated' }); GoogleAnalytics.event({ action: 'um_consent_updated', category: 'consent' }); - console.log("effect oneTrustActiveGroups "+ oneTrustActiveGroups); }; setOptanonWrapper(updateGtagConsent) diff --git a/src/assets/src/hooks/useOneTrust.ts b/src/assets/src/hooks/useOneTrust.ts index 2b8e0dd8..ae99ac70 100644 --- a/src/assets/src/hooks/useOneTrust.ts +++ b/src/assets/src/hooks/useOneTrust.ts @@ -12,8 +12,10 @@ export const useOneTrust = (): [boolean, string, (callback: () => void) => void] const dataDomainScript ='03e0096b-3569-4b70-8a31-918e55aa20da' const [oneTrustActiveGroups, setOneTrustActiveGroups] = useState(""); const [loaded, setLoaded] = useState(false) + console.log("useOT rendered, loaded " + loaded ? "true" : "false" + " oneTrustActiveGroups " + oneTrustActiveGroups + " window.OnetrustActiveGroups " + window.OnetrustActiveGroups) useEffect(() => { + console.log("useOT Effect Initializing") const script = document.createElement('script'); script.src = src; script.type = 'text/javascript'; @@ -37,6 +39,7 @@ export const useOneTrust = (): [boolean, string, (callback: () => void) => void] }, []); useEffect(() => { + console.log("useOT Effect window.oneTrustActiveGroups " + window.OnetrustActiveGroups) if (window.OnetrustActiveGroups) { setOneTrustActiveGroups(window.OnetrustActiveGroups); } From 795096c63e9ad79065fa257e3091723fecadedb1 Mon Sep 17 00:00:00 2001 From: Jaydon Krooss Date: Wed, 26 Jun 2024 17:06:51 -0400 Subject: [PATCH 04/15] fix logging --- .env.sample | 6 ++++++ docker-compose.yml | 4 ++++ src/assets/src/hooks/useGoogleAnalytics.ts | 2 +- src/assets/src/hooks/useOneTrust.ts | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 .env.sample diff --git a/.env.sample b/.env.sample new file mode 100644 index 00000000..36a34a16 --- /dev/null +++ b/.env.sample @@ -0,0 +1,6 @@ +# Remote Office Hours Configuration + +# Twilio Credentials +TWILIO_ACCOUNT_SID= +TWILIO_AUTH_TOKEN= +TWILIO_MESSAGING_SERVICE_SID= \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index b8fdb83e..18272b73 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,6 +4,8 @@ version: '3.4' services: web: + env_file: + - .env build: context: src environment: @@ -29,6 +31,8 @@ services: - POSTGRES_PASSWORD=admin_pw volumes: - officehours-postgres-data:/var/lib/postgresql/data + ports: + - "15432:5432" redis: image: redis:7 diff --git a/src/assets/src/hooks/useGoogleAnalytics.ts b/src/assets/src/hooks/useGoogleAnalytics.ts index 430d0e69..44651ad4 100644 --- a/src/assets/src/hooks/useGoogleAnalytics.ts +++ b/src/assets/src/hooks/useGoogleAnalytics.ts @@ -9,7 +9,7 @@ export const useGoogleAnalytics = (googleAnalyticsId?: string, debug?: boolean) const [initialized, setInitialized] = useState(false); const [previousPage, setPreviousPage] = useState(null as string | null); - console.log("useGA rendered, initialized " + initialized ? "true" : "false") + console.log("useGA rendered, initialized " + (initialized ? "true" : "false")) if (googleAnalyticsId && !initialized) { console.log("useGA initializing"); GoogleAnalytics.gtag("consent", "default", { diff --git a/src/assets/src/hooks/useOneTrust.ts b/src/assets/src/hooks/useOneTrust.ts index ae99ac70..a6847536 100644 --- a/src/assets/src/hooks/useOneTrust.ts +++ b/src/assets/src/hooks/useOneTrust.ts @@ -12,7 +12,7 @@ export const useOneTrust = (): [boolean, string, (callback: () => void) => void] const dataDomainScript ='03e0096b-3569-4b70-8a31-918e55aa20da' const [oneTrustActiveGroups, setOneTrustActiveGroups] = useState(""); const [loaded, setLoaded] = useState(false) - console.log("useOT rendered, loaded " + loaded ? "true" : "false" + " oneTrustActiveGroups " + oneTrustActiveGroups + " window.OnetrustActiveGroups " + window.OnetrustActiveGroups) + console.log("useOT rendered, loaded " + (loaded ? "true" : "false") + " oneTrustActiveGroups " + oneTrustActiveGroups + " window.OnetrustActiveGroups " + window.OnetrustActiveGroups) useEffect(() => { console.log("useOT Effect Initializing") From 4390f986eab26c7d64a2affae09f5030bda2cdb7 Mon Sep 17 00:00:00 2001 From: Jaydon Krooss Date: Wed, 26 Jun 2024 17:09:08 -0400 Subject: [PATCH 05/15] Revert "fix logging" This reverts commit 795096c63e9ad79065fa257e3091723fecadedb1. --- .env.sample | 6 ------ docker-compose.yml | 4 ---- src/assets/src/hooks/useGoogleAnalytics.ts | 2 +- src/assets/src/hooks/useOneTrust.ts | 2 +- 4 files changed, 2 insertions(+), 12 deletions(-) delete mode 100644 .env.sample diff --git a/.env.sample b/.env.sample deleted file mode 100644 index 36a34a16..00000000 --- a/.env.sample +++ /dev/null @@ -1,6 +0,0 @@ -# Remote Office Hours Configuration - -# Twilio Credentials -TWILIO_ACCOUNT_SID= -TWILIO_AUTH_TOKEN= -TWILIO_MESSAGING_SERVICE_SID= \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 18272b73..b8fdb83e 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,8 +4,6 @@ version: '3.4' services: web: - env_file: - - .env build: context: src environment: @@ -31,8 +29,6 @@ services: - POSTGRES_PASSWORD=admin_pw volumes: - officehours-postgres-data:/var/lib/postgresql/data - ports: - - "15432:5432" redis: image: redis:7 diff --git a/src/assets/src/hooks/useGoogleAnalytics.ts b/src/assets/src/hooks/useGoogleAnalytics.ts index 44651ad4..430d0e69 100644 --- a/src/assets/src/hooks/useGoogleAnalytics.ts +++ b/src/assets/src/hooks/useGoogleAnalytics.ts @@ -9,7 +9,7 @@ export const useGoogleAnalytics = (googleAnalyticsId?: string, debug?: boolean) const [initialized, setInitialized] = useState(false); const [previousPage, setPreviousPage] = useState(null as string | null); - console.log("useGA rendered, initialized " + (initialized ? "true" : "false")) + console.log("useGA rendered, initialized " + initialized ? "true" : "false") if (googleAnalyticsId && !initialized) { console.log("useGA initializing"); GoogleAnalytics.gtag("consent", "default", { diff --git a/src/assets/src/hooks/useOneTrust.ts b/src/assets/src/hooks/useOneTrust.ts index a6847536..ae99ac70 100644 --- a/src/assets/src/hooks/useOneTrust.ts +++ b/src/assets/src/hooks/useOneTrust.ts @@ -12,7 +12,7 @@ export const useOneTrust = (): [boolean, string, (callback: () => void) => void] const dataDomainScript ='03e0096b-3569-4b70-8a31-918e55aa20da' const [oneTrustActiveGroups, setOneTrustActiveGroups] = useState(""); const [loaded, setLoaded] = useState(false) - console.log("useOT rendered, loaded " + (loaded ? "true" : "false") + " oneTrustActiveGroups " + oneTrustActiveGroups + " window.OnetrustActiveGroups " + window.OnetrustActiveGroups) + console.log("useOT rendered, loaded " + loaded ? "true" : "false" + " oneTrustActiveGroups " + oneTrustActiveGroups + " window.OnetrustActiveGroups " + window.OnetrustActiveGroups) useEffect(() => { console.log("useOT Effect Initializing") From f3a037a119534585730d6a6350abf13a8d638449 Mon Sep 17 00:00:00 2001 From: Jaydon Krooss Date: Wed, 26 Jun 2024 17:09:52 -0400 Subject: [PATCH 06/15] fix logging (again) --- src/assets/src/hooks/useGoogleAnalytics.ts | 2 +- src/assets/src/hooks/useOneTrust.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/src/hooks/useGoogleAnalytics.ts b/src/assets/src/hooks/useGoogleAnalytics.ts index 430d0e69..44651ad4 100644 --- a/src/assets/src/hooks/useGoogleAnalytics.ts +++ b/src/assets/src/hooks/useGoogleAnalytics.ts @@ -9,7 +9,7 @@ export const useGoogleAnalytics = (googleAnalyticsId?: string, debug?: boolean) const [initialized, setInitialized] = useState(false); const [previousPage, setPreviousPage] = useState(null as string | null); - console.log("useGA rendered, initialized " + initialized ? "true" : "false") + console.log("useGA rendered, initialized " + (initialized ? "true" : "false")) if (googleAnalyticsId && !initialized) { console.log("useGA initializing"); GoogleAnalytics.gtag("consent", "default", { diff --git a/src/assets/src/hooks/useOneTrust.ts b/src/assets/src/hooks/useOneTrust.ts index ae99ac70..a6847536 100644 --- a/src/assets/src/hooks/useOneTrust.ts +++ b/src/assets/src/hooks/useOneTrust.ts @@ -12,7 +12,7 @@ export const useOneTrust = (): [boolean, string, (callback: () => void) => void] const dataDomainScript ='03e0096b-3569-4b70-8a31-918e55aa20da' const [oneTrustActiveGroups, setOneTrustActiveGroups] = useState(""); const [loaded, setLoaded] = useState(false) - console.log("useOT rendered, loaded " + loaded ? "true" : "false" + " oneTrustActiveGroups " + oneTrustActiveGroups + " window.OnetrustActiveGroups " + window.OnetrustActiveGroups) + console.log("useOT rendered, loaded " + (loaded ? "true" : "false") + " oneTrustActiveGroups " + oneTrustActiveGroups + " window.OnetrustActiveGroups " + window.OnetrustActiveGroups) useEffect(() => { console.log("useOT Effect Initializing") From 328774bcddac64de1a10c77c4988f768315c3a41 Mon Sep 17 00:00:00 2001 From: Jaydon Krooss Date: Thu, 27 Jun 2024 11:31:34 -0400 Subject: [PATCH 07/15] useOneTrust rewrite --- src/assets/src/hooks/useGoogleAnalytics.ts | 44 +------------ src/assets/src/hooks/useOneTrust.ts | 74 ++++++++++++---------- 2 files changed, 44 insertions(+), 74 deletions(-) diff --git a/src/assets/src/hooks/useGoogleAnalytics.ts b/src/assets/src/hooks/useGoogleAnalytics.ts index 44651ad4..5828ebaa 100644 --- a/src/assets/src/hooks/useGoogleAnalytics.ts +++ b/src/assets/src/hooks/useGoogleAnalytics.ts @@ -6,6 +6,7 @@ import { useOneTrust } from './useOneTrust'; 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); @@ -21,16 +22,11 @@ export const useGoogleAnalytics = (googleAnalyticsId?: string, debug?: boolean) ad_personalization: "denied", wait_for_update: 500 }); - setInitialized(true); GoogleAnalytics.initialize(googleAnalyticsId, { testMode: debug }); + initializeOneTrust(GoogleAnalytics); + setInitialized(true); } - const [loaded, oneTrustActiveGroups, setOptanonWrapper] = useOneTrust(); - if (loaded) { - console.log("useGA loaded " + oneTrustActiveGroups); - } - console.log("useGA oneTrustActiveGroups " + oneTrustActiveGroups); - useEffect(() => { console.log("useGA Effect location" + location.pathname + location.search + location.hash + " previousPage " + previousPage) const page = location.pathname + location.search + location.hash; @@ -39,38 +35,4 @@ export const useGoogleAnalytics = (googleAnalyticsId?: string, debug?: boolean) GoogleAnalytics.send({ hitType: "pageview", page }); } }, [location]); - - useEffect(() => { - console.log("useGA Effect oneTrustActiveGroups" + oneTrustActiveGroups) - const updateGtagConsent = () => { - console.log("updateGtagConsent RUN oneTrustActiveGroups " + oneTrustActiveGroups) - - if (oneTrustActiveGroups.includes("C0002")) { - GoogleAnalytics.gtag("consent", "update", { analytics_storage: "granted" }); - } - if (oneTrustActiveGroups.includes("C0003")) { - GoogleAnalytics.gtag("consent", "update", { functional_storage: "granted" }); - } - if (oneTrustActiveGroups.includes("C0004")) { - GoogleAnalytics.gtag("consent", "update", { - ad_storage: "granted", - ad_user_data: "granted", - ad_personalization: "granted", - personalization_storage: "granted" - }); - } else { - // Remove Google Analytics cookies if tracking is declined - document.cookie.split(';').forEach(cookie => { - const [name] = cookie.split('='); - if (name.trim().match(/^_ga(_.+)?$/)) { - document.cookie = `${name}=;path=/;domain=.${window.location.host.replace(/^(.*\.)?(.+\..+)$/, '$2')};expires=Thu, 01 Jan 1970 00:00:01 GMT`; - } - }); - } - // window.dataLayer.push({ event: 'um_consent_updated' }); - GoogleAnalytics.event({ action: 'um_consent_updated', category: 'consent' }); - }; - - setOptanonWrapper(updateGtagConsent) - }, [oneTrustActiveGroups]); } diff --git a/src/assets/src/hooks/useOneTrust.ts b/src/assets/src/hooks/useOneTrust.ts index a6847536..cb17e8ea 100644 --- a/src/assets/src/hooks/useOneTrust.ts +++ b/src/assets/src/hooks/useOneTrust.ts @@ -1,4 +1,5 @@ import { useState, useEffect } from 'react'; +import { GA4 } from 'react-ga4/types/ga4'; declare global { interface Window { @@ -7,47 +8,54 @@ declare global { } } -export const useOneTrust = (): [boolean, string, (callback: () => void) => void] => { - const src = 'https://cdn.cookielaw.org/consent/03e0096b-3569-4b70-8a31-918e55aa20da/otSDKStub.js' - const dataDomainScript ='03e0096b-3569-4b70-8a31-918e55aa20da' +export const useOneTrust = (): [(googleAnalytics:GA4) => void] => { const [oneTrustActiveGroups, setOneTrustActiveGroups] = useState(""); - const [loaded, setLoaded] = useState(false) - console.log("useOT rendered, loaded " + (loaded ? "true" : "false") + " oneTrustActiveGroups " + oneTrustActiveGroups + " window.OnetrustActiveGroups " + window.OnetrustActiveGroups) + console.log("useOT rendering, " + " oneTrustActiveGroups " + oneTrustActiveGroups + " window.OnetrustActiveGroups " + window.OnetrustActiveGroups) useEffect(() => { - console.log("useOT Effect Initializing") - const script = document.createElement('script'); - script.src = src; - script.type = 'text/javascript'; - if (dataDomainScript) script.dataset.domainScript = dataDomainScript; - - script.onload = () => { - setLoaded(true); - } - script.onerror = () => { - setLoaded(false); - console.error(`Failed to load script: ${src}`); - } - - document.head.appendChild(script); - - return () => { - if (document.head.contains(script)) { - document.head.removeChild(script); - } - }; - }, []); - - useEffect(() => { - console.log("useOT Effect window.oneTrustActiveGroups " + window.OnetrustActiveGroups) + console.log("useOT Effect window.onerustActiveGroups " + window.OnetrustActiveGroups) if (window.OnetrustActiveGroups) { setOneTrustActiveGroups(window.OnetrustActiveGroups); } }, [window.OnetrustActiveGroups]); - const setOptanonWrapper = (callback: () => void) => { - window.OptanonWrapper = callback; + const initializeOneTrust = (googleAnalytics: GA4) => { + console.log("OT initializing ") + const updateGtagCallback = () => { + if (oneTrustActiveGroups.includes("C0002")) { + googleAnalytics.gtag("consent", "update", { analytics_storage: "granted" }); + } + if (oneTrustActiveGroups.includes("C0003")) { + googleAnalytics.gtag("consent", "update", { functional_storage: "granted" }); + } + if (oneTrustActiveGroups.includes("C0004")) { + googleAnalytics.gtag("consent", "update", { + ad_storage: "granted", + ad_user_data: "granted", + ad_personalization: "granted", + personalization_storage: "granted" + }); + } else { + // Remove Google Analytics cookies if tracking is declined + document.cookie.split(';').forEach(cookie => { + const [name] = cookie.split('='); + if (name.trim().match(/^_ga(_.+)?$/)) { + document.cookie = `${name}=;path=/;domain=.${window.location.host.replace(/^(.*\.)?(.+\..+)$/, '$2')};expires=Thu, 01 Jan 1970 00:00:01 GMT`; + } + }); + } + // window.dataLayer.push({ event: 'um_consent_updated' }); + googleAnalytics.event({ action: 'um_consent_updated', category: 'consent' }); + } + window.OptanonWrapper = updateGtagCallback; + + const src = 'https://cdn.cookielaw.org/consent/03e0096b-3569-4b70-8a31-918e55aa20da/otSDKStub.js' + const dataDomainScript ='03e0096b-3569-4b70-8a31-918e55aa20da' + const script = document.createElement('script'); + script.src = src; + script.type = 'text/javascript'; + script.dataset.domainScript = dataDomainScript; } - return [ loaded, oneTrustActiveGroups, setOptanonWrapper ]; + return [ initializeOneTrust ]; }; From c683d8ed6240ec259a0f682d907ad851ed415627 Mon Sep 17 00:00:00 2001 From: Jaydon Krooss Date: Thu, 27 Jun 2024 12:11:17 -0400 Subject: [PATCH 08/15] append & one more log --- src/assets/src/hooks/useOneTrust.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/assets/src/hooks/useOneTrust.ts b/src/assets/src/hooks/useOneTrust.ts index cb17e8ea..875b86bc 100644 --- a/src/assets/src/hooks/useOneTrust.ts +++ b/src/assets/src/hooks/useOneTrust.ts @@ -22,6 +22,7 @@ export const useOneTrust = (): [(googleAnalytics:GA4) => void] => { const initializeOneTrust = (googleAnalytics: GA4) => { console.log("OT initializing ") const updateGtagCallback = () => { + console.log("OT CALLBACK RUNNING") if (oneTrustActiveGroups.includes("C0002")) { googleAnalytics.gtag("consent", "update", { analytics_storage: "granted" }); } @@ -52,9 +53,11 @@ export const useOneTrust = (): [(googleAnalytics:GA4) => void] => { const src = 'https://cdn.cookielaw.org/consent/03e0096b-3569-4b70-8a31-918e55aa20da/otSDKStub.js' const dataDomainScript ='03e0096b-3569-4b70-8a31-918e55aa20da' const script = document.createElement('script'); + console.log("OT SCRIPT is added") script.src = src; script.type = 'text/javascript'; script.dataset.domainScript = dataDomainScript; + document.head.appendChild(script); } return [ initializeOneTrust ]; From e299bf9fc31b0433aaff9d88924d08627c27854d Mon Sep 17 00:00:00 2001 From: Jaydon Krooss Date: Thu, 27 Jun 2024 15:11:16 -0400 Subject: [PATCH 09/15] don't use state for activegroups --- src/assets/src/hooks/useOneTrust.ts | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/assets/src/hooks/useOneTrust.ts b/src/assets/src/hooks/useOneTrust.ts index 875b86bc..14cc9fb0 100644 --- a/src/assets/src/hooks/useOneTrust.ts +++ b/src/assets/src/hooks/useOneTrust.ts @@ -1,4 +1,3 @@ -import { useState, useEffect } from 'react'; import { GA4 } from 'react-ga4/types/ga4'; declare global { @@ -9,27 +8,21 @@ declare global { } export const useOneTrust = (): [(googleAnalytics:GA4) => void] => { - const [oneTrustActiveGroups, setOneTrustActiveGroups] = useState(""); - console.log("useOT rendering, " + " oneTrustActiveGroups " + oneTrustActiveGroups + " window.OnetrustActiveGroups " + window.OnetrustActiveGroups) - - useEffect(() => { - console.log("useOT Effect window.onerustActiveGroups " + window.OnetrustActiveGroups) - if (window.OnetrustActiveGroups) { - setOneTrustActiveGroups(window.OnetrustActiveGroups); - } - }, [window.OnetrustActiveGroups]); const initializeOneTrust = (googleAnalytics: GA4) => { console.log("OT initializing ") const updateGtagCallback = () => { - console.log("OT CALLBACK RUNNING") - if (oneTrustActiveGroups.includes("C0002")) { + if (!window.OnetrustActiveGroups) { + console.log("OT CALLBACK, no active groups") + return; + } + if (window.OnetrustActiveGroups.includes("C0002")) { googleAnalytics.gtag("consent", "update", { analytics_storage: "granted" }); } - if (oneTrustActiveGroups.includes("C0003")) { + if (window.OnetrustActiveGroups.includes("C0003")) { googleAnalytics.gtag("consent", "update", { functional_storage: "granted" }); } - if (oneTrustActiveGroups.includes("C0004")) { + if (window.OnetrustActiveGroups.includes("C0004")) { googleAnalytics.gtag("consent", "update", { ad_storage: "granted", ad_user_data: "granted", @@ -37,6 +30,7 @@ export const useOneTrust = (): [(googleAnalytics:GA4) => void] => { personalization_storage: "granted" }); } else { + console.log("OT CALLBACK remove cookies! ") // Remove Google Analytics cookies if tracking is declined document.cookie.split(';').forEach(cookie => { const [name] = cookie.split('='); @@ -53,7 +47,6 @@ export const useOneTrust = (): [(googleAnalytics:GA4) => void] => { const src = 'https://cdn.cookielaw.org/consent/03e0096b-3569-4b70-8a31-918e55aa20da/otSDKStub.js' const dataDomainScript ='03e0096b-3569-4b70-8a31-918e55aa20da' const script = document.createElement('script'); - console.log("OT SCRIPT is added") script.src = src; script.type = 'text/javascript'; script.dataset.domainScript = dataDomainScript; From 233aab750f54092733a4f59ccfe42ac38199a74e Mon Sep 17 00:00:00 2001 From: Jaydon Krooss Date: Thu, 27 Jun 2024 15:58:56 -0400 Subject: [PATCH 10/15] use cookie library for removing ga4 cookie --- src/assets/src/hooks/useOneTrust.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/assets/src/hooks/useOneTrust.ts b/src/assets/src/hooks/useOneTrust.ts index 14cc9fb0..835a02c0 100644 --- a/src/assets/src/hooks/useOneTrust.ts +++ b/src/assets/src/hooks/useOneTrust.ts @@ -1,4 +1,5 @@ import { GA4 } from 'react-ga4/types/ga4'; +import Cookies from "js-cookie"; declare global { interface Window { @@ -32,12 +33,10 @@ export const useOneTrust = (): [(googleAnalytics:GA4) => void] => { } else { console.log("OT CALLBACK remove cookies! ") // Remove Google Analytics cookies if tracking is declined - document.cookie.split(';').forEach(cookie => { - const [name] = cookie.split('='); - if (name.trim().match(/^_ga(_.+)?$/)) { - document.cookie = `${name}=;path=/;domain=.${window.location.host.replace(/^(.*\.)?(.+\..+)$/, '$2')};expires=Thu, 01 Jan 1970 00:00:01 GMT`; - } - }); + // 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"); } // window.dataLayer.push({ event: 'um_consent_updated' }); googleAnalytics.event({ action: 'um_consent_updated', category: 'consent' }); From 8d11459680ce853c6a7ca6b1e4877abba6ec4d9b Mon Sep 17 00:00:00 2001 From: Jaydon Krooss Date: Thu, 27 Jun 2024 16:30:37 -0400 Subject: [PATCH 11/15] package updates --- src/package-lock.json | 16 ++++++++++++++++ src/package.json | 2 ++ 2 files changed, 18 insertions(+) diff --git a/src/package-lock.json b/src/package-lock.json index f350ee50..ff32f231 100644 --- a/src/package-lock.json +++ b/src/package-lock.json @@ -11,6 +11,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", @@ -21,6 +22,7 @@ "yup": "^1.2.0" }, "devDependencies": { + "@types/js-cookie": "^3.0.6", "@types/react": "^18.2.7", "@types/react-dom": "^18.2.4", "css-loader": "~6.8.1", @@ -331,6 +333,12 @@ "@types/node": "*" } }, + "node_modules/@types/js-cookie": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", + "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -1933,6 +1941,14 @@ "node": ">=0.10.0" } }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "engines": { + "node": ">=14" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/src/package.json b/src/package.json index ff4159ad..231bedc4 100644 --- a/src/package.json +++ b/src/package.json @@ -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", @@ -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", From 77a69832f4e55d8d8b84ad4b90c562fde29fca40 Mon Sep 17 00:00:00 2001 From: Jaydon Krooss Date: Fri, 28 Jun 2024 09:08:15 -0400 Subject: [PATCH 12/15] implement feedback - clearer comments, use variables --- docker-compose.yml | 1 + src/assets/src/containers/app.tsx | 3 +- src/assets/src/hooks/useGoogleAnalytics.ts | 27 +++---- src/assets/src/hooks/useOneTrust.ts | 82 +++++++++++++--------- src/assets/src/index.tsx | 1 + src/officehours/settings.py | 1 + src/officehours_ui/context_processors.py | 1 + 7 files changed, 69 insertions(+), 47 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index b8fdb83e..887c65c8 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,6 +11,7 @@ services: - DEBUG=True - DATABASE_URL=postgresql://admin:admin_pw@database/admin - FEEDBACK_EMAIL=office-hours-devs@umich.edu + - ONE_TRUST_SCRIPT_DOMAIN=03e0096b-3569-4b70-8a31-918e55aa20da ports: - 8003:8001 volumes: diff --git a/src/assets/src/containers/app.tsx b/src/assets/src/containers/app.tsx index 05dbfc48..1ce59052 100644 --- a/src/assets/src/containers/app.tsx +++ b/src/assets/src/containers/app.tsx @@ -18,8 +18,7 @@ interface AppProps { } export function App(props: AppProps) { - useGoogleAnalytics(props.globals.ga_tracking_id, props.globals.debug); - console.log("APP Rendered") + useGoogleAnalytics(props.globals.ga_tracking_id, props.globals.debug, props.globals.one_trust_script_domain); const commonProps: PageProps = { user: props.globals.user, diff --git a/src/assets/src/hooks/useGoogleAnalytics.ts b/src/assets/src/hooks/useGoogleAnalytics.ts index 5828ebaa..fe5cd0d9 100644 --- a/src/assets/src/hooks/useGoogleAnalytics.ts +++ b/src/assets/src/hooks/useGoogleAnalytics.ts @@ -3,32 +3,35 @@ 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) => { +export const useGoogleAnalytics = (googleAnalyticsId?: string, debug?: boolean, oneTrustScriptDomain?: string) => { let location = useLocation(); - const [initializeOneTrust] = useOneTrust(); + const [initializeOneTrust] = useOneTrust(oneTrustScriptDomain); const [initialized, setInitialized] = useState(false); const [previousPage, setPreviousPage] = useState(null as string | null); - console.log("useGA rendered, initialized " + (initialized ? "true" : "false")) if (googleAnalyticsId && !initialized) { - console.log("useGA initializing"); GoogleAnalytics.gtag("consent", "default", { - ad_storage: "denied", - analytics_storage: "denied", - functionality_storage: "denied", - personalization_storage: "denied", - ad_user_data: "denied", - ad_personalization: "denied", + 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 }); - initializeOneTrust(GoogleAnalytics); + if (initializeOneTrust) { + initializeOneTrust(GoogleAnalytics); + } setInitialized(true); } useEffect(() => { - console.log("useGA Effect location" + location.pathname + location.search + location.hash + " previousPage " + previousPage) const page = location.pathname + location.search + location.hash; if (googleAnalyticsId && page !== previousPage) { setPreviousPage(page); diff --git a/src/assets/src/hooks/useOneTrust.ts b/src/assets/src/hooks/useOneTrust.ts index 835a02c0..e7f55d2e 100644 --- a/src/assets/src/hooks/useOneTrust.ts +++ b/src/assets/src/hooks/useOneTrust.ts @@ -1,5 +1,6 @@ import { GA4 } from 'react-ga4/types/ga4'; import Cookies from "js-cookie"; +import { GoogleAnalyticsConsentValue } from './useGoogleAnalytics'; declare global { interface Window { @@ -8,47 +9,62 @@ declare global { } } -export const useOneTrust = (): [(googleAnalytics:GA4) => 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 = (oneTrustScriptDomain?: string): [(googleAnalytics:GA4) => void] | [] => + { + if (!oneTrustScriptDomain) { + return []; + } + // 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) => { - console.log("OT initializing ") + // Callback is used by OneTrust to update Google Analytics consent tags and remove cookies const updateGtagCallback = () => { - if (!window.OnetrustActiveGroups) { - console.log("OT CALLBACK, no active groups") - return; - } - if (window.OnetrustActiveGroups.includes("C0002")) { - googleAnalytics.gtag("consent", "update", { analytics_storage: "granted" }); - } - if (window.OnetrustActiveGroups.includes("C0003")) { - googleAnalytics.gtag("consent", "update", { functional_storage: "granted" }); - } - if (window.OnetrustActiveGroups.includes("C0004")) { - googleAnalytics.gtag("consent", "update", { - ad_storage: "granted", - ad_user_data: "granted", - ad_personalization: "granted", - personalization_storage: "granted" - }); - } else { - console.log("OT CALLBACK remove cookies! ") - // Remove Google Analytics cookies if tracking is declined - // 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"); - } - // window.dataLayer.push({ event: 'um_consent_updated' }); - googleAnalytics.event({ action: 'um_consent_updated', category: 'consent' }); - } + 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 src = 'https://cdn.cookielaw.org/consent/03e0096b-3569-4b70-8a31-918e55aa20da/otSDKStub.js' - const dataDomainScript ='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 = dataDomainScript; + script.dataset.domainScript = oneTrustScriptDomain; document.head.appendChild(script); } diff --git a/src/assets/src/index.tsx b/src/assets/src/index.tsx index 7a091ae0..54162410 100755 --- a/src/assets/src/index.tsx +++ b/src/assets/src/index.tsx @@ -15,6 +15,7 @@ export interface Globals { backends: MeetingBackend[]; default_backend: string; otp_request_buffer: number; + one_trust_script_domain: string; } const globalsId = 'spa_globals'; diff --git a/src/officehours/settings.py b/src/officehours/settings.py index d31565f1..546597ea 100644 --- a/src/officehours/settings.py +++ b/src/officehours/settings.py @@ -302,6 +302,7 @@ def skip_auth_callback_requests(record): # Google Analytics GA_TRACKING_ID = os.getenv('GA_TRACKING_ID') +ONE_TRUST_SCRIPT_DOMAIN = os.getenv('ONE_TRUST_SCRIPT_DOMAIN') # Django Flatpages SITE_ID = 1 diff --git a/src/officehours_ui/context_processors.py b/src/officehours_ui/context_processors.py index 6d3e8394..da60e8ce 100644 --- a/src/officehours_ui/context_processors.py +++ b/src/officehours_ui/context_processors.py @@ -42,5 +42,6 @@ def spa_globals(request): 'backends': backend_dicts, 'default_backend': settings.DEFAULT_BACKEND, 'otp_request_buffer': settings.OTP_REQUEST_BUFFER, + 'one_trust_script_domain': settings.ONE_TRUST_SCRIPT_DOMAIN } } From a78f282d56c8d468bbf2692f4f583f5032e5c9cd Mon Sep 17 00:00:00 2001 From: Jaydon Krooss Date: Fri, 28 Jun 2024 11:28:34 -0400 Subject: [PATCH 13/15] leave oneTrust script domain hardcoded for now --- docker-compose.yml | 1 - src/assets/src/containers/app.tsx | 2 +- src/assets/src/hooks/useGoogleAnalytics.ts | 4 ++-- src/assets/src/hooks/useOneTrust.ts | 6 ++---- src/officehours/settings.py | 1 - src/officehours_ui/context_processors.py | 1 - 6 files changed, 5 insertions(+), 10 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 887c65c8..b8fdb83e 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,6 @@ services: - DEBUG=True - DATABASE_URL=postgresql://admin:admin_pw@database/admin - FEEDBACK_EMAIL=office-hours-devs@umich.edu - - ONE_TRUST_SCRIPT_DOMAIN=03e0096b-3569-4b70-8a31-918e55aa20da ports: - 8003:8001 volumes: diff --git a/src/assets/src/containers/app.tsx b/src/assets/src/containers/app.tsx index 1ce59052..82014155 100644 --- a/src/assets/src/containers/app.tsx +++ b/src/assets/src/containers/app.tsx @@ -18,7 +18,7 @@ interface AppProps { } export function App(props: AppProps) { - useGoogleAnalytics(props.globals.ga_tracking_id, props.globals.debug, props.globals.one_trust_script_domain); + useGoogleAnalytics(props.globals.ga_tracking_id, props.globals.debug); const commonProps: PageProps = { user: props.globals.user, diff --git a/src/assets/src/hooks/useGoogleAnalytics.ts b/src/assets/src/hooks/useGoogleAnalytics.ts index fe5cd0d9..aaa9bb77 100644 --- a/src/assets/src/hooks/useGoogleAnalytics.ts +++ b/src/assets/src/hooks/useGoogleAnalytics.ts @@ -8,9 +8,9 @@ export enum GoogleAnalyticsConsentValue { Granted = "granted" } -export const useGoogleAnalytics = (googleAnalyticsId?: string, debug?: boolean, oneTrustScriptDomain?: string) => { +export const useGoogleAnalytics = (googleAnalyticsId?: string, debug?: boolean) => { let location = useLocation(); - const [initializeOneTrust] = useOneTrust(oneTrustScriptDomain); + const [initializeOneTrust] = useOneTrust(); const [initialized, setInitialized] = useState(false); const [previousPage, setPreviousPage] = useState(null as string | null); diff --git a/src/assets/src/hooks/useOneTrust.ts b/src/assets/src/hooks/useOneTrust.ts index e7f55d2e..4b7d34c5 100644 --- a/src/assets/src/hooks/useOneTrust.ts +++ b/src/assets/src/hooks/useOneTrust.ts @@ -18,11 +18,8 @@ enum OneTrustCookieCategory { SocialMedia = "C0005", } -export const useOneTrust = (oneTrustScriptDomain?: string): [(googleAnalytics:GA4) => void] | [] => +export const useOneTrust = (): [(googleAnalytics:GA4) => void] | [] => { - if (!oneTrustScriptDomain) { - return []; - } // 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) => { @@ -59,6 +56,7 @@ export const useOneTrust = (oneTrustScriptDomain?: string): [(googleAnalytics:GA } 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'); diff --git a/src/officehours/settings.py b/src/officehours/settings.py index 546597ea..d31565f1 100644 --- a/src/officehours/settings.py +++ b/src/officehours/settings.py @@ -302,7 +302,6 @@ def skip_auth_callback_requests(record): # Google Analytics GA_TRACKING_ID = os.getenv('GA_TRACKING_ID') -ONE_TRUST_SCRIPT_DOMAIN = os.getenv('ONE_TRUST_SCRIPT_DOMAIN') # Django Flatpages SITE_ID = 1 diff --git a/src/officehours_ui/context_processors.py b/src/officehours_ui/context_processors.py index da60e8ce..6d3e8394 100644 --- a/src/officehours_ui/context_processors.py +++ b/src/officehours_ui/context_processors.py @@ -42,6 +42,5 @@ def spa_globals(request): 'backends': backend_dicts, 'default_backend': settings.DEFAULT_BACKEND, 'otp_request_buffer': settings.OTP_REQUEST_BUFFER, - 'one_trust_script_domain': settings.ONE_TRUST_SCRIPT_DOMAIN } } From ce0ca6c3a7e1ca687ddc8fa44bc4f176224e6683 Mon Sep 17 00:00:00 2001 From: Jaydon Krooss Date: Fri, 28 Jun 2024 11:34:23 -0400 Subject: [PATCH 14/15] remove from spa_globals --- src/assets/src/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/assets/src/index.tsx b/src/assets/src/index.tsx index 54162410..7a091ae0 100755 --- a/src/assets/src/index.tsx +++ b/src/assets/src/index.tsx @@ -15,7 +15,6 @@ export interface Globals { backends: MeetingBackend[]; default_backend: string; otp_request_buffer: number; - one_trust_script_domain: string; } const globalsId = 'spa_globals'; From 72729b9460b6dc5704654d7d1fd9cc9698142662 Mon Sep 17 00:00:00 2001 From: Jaydon Krooss Date: Fri, 28 Jun 2024 15:08:43 -0400 Subject: [PATCH 15/15] add privacy policy resource --- src/officehours/settings.py | 2 ++ src/officehours_ui/urls.py | 3 ++- src/officehours_ui/views.py | 4 ++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/officehours/settings.py b/src/officehours/settings.py index d31565f1..d9e35691 100644 --- a/src/officehours/settings.py +++ b/src/officehours/settings.py @@ -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') diff --git a/src/officehours_ui/urls.py b/src/officehours_ui/urls.py index fe152ce4..e2a628a3 100644 --- a/src/officehours_ui/urls.py +++ b/src/officehours_ui/urls.py @@ -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 = [ @@ -19,4 +19,5 @@ path('callback//', 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') ] diff --git a/src/officehours_ui/views.py b/src/officehours_ui/views.py index b270ce72..36164df5 100644 --- a/src/officehours_ui/views.py +++ b/src/officehours_ui/views.py @@ -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 @@ -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) \ No newline at end of file