diff --git a/assets/src/components/GoogleAnalyticsTracking.js b/assets/src/components/GoogleAnalyticsTracking.js deleted file mode 100644 index f00fd35a..00000000 --- a/assets/src/components/GoogleAnalyticsTracking.js +++ /dev/null @@ -1,30 +0,0 @@ -import { useState, useEffect } from 'react' -import GoogleAnalytics from 'react-ga4' - -function GoogleAnalyticsTracking (props) { - const { gaId, cspNonce } = props - - const [initialized, setInitialized] = useState(false) - const [previousPage, setPreviousPage] = useState(null) - - if (gaId && !initialized) { - setInitialized(true) - GoogleAnalytics.initialize([{ - trackingId: gaId, - gaOptions: { nonce: cspNonce, cookieFlags: 'SameSite=None; Secure' } - }]) - } - - useEffect(() => { - const page = window.location.pathname + window.location.search + window.location.hash - if (gaId && page !== previousPage) { - setPreviousPage(page) - GoogleAnalytics.send({ hitType: 'pageview', page }) - } - }) - - return null -} - -export default GoogleAnalyticsTracking - \ No newline at end of file diff --git a/assets/src/containers/App.js b/assets/src/containers/App.js index 2a131928..b9226fb3 100755 --- a/assets/src/containers/App.js +++ b/assets/src/containers/App.js @@ -1,14 +1,15 @@ import React from 'react' import { Route, Routes, useMatch } from 'react-router-dom' -import GoogleAnalyticsTracking from '../components/GoogleAnalyticsTracking' import CourseList from './CourseList' import Course from './Course' import WarningBanner from '../components/WarningBanner' import AlertBanner from '../components/AlertBanner' import { Helmet } from 'react-helmet' +import useGoogleAnalytics from 'react-ga-onetrust-consent' function App (props) { - const { user, gaId, cspNonce } = props + const { user, gaId, cspNonce, oneTrustScriptDomain } = props + useGoogleAnalytics({ googleAnalyticsId: gaId, nonce: cspNonce, oneTrustScriptDomain}) if (!user.isLoggedIn) { if (user.loginURL === '') { @@ -21,7 +22,6 @@ function App (props) { return ( <> - } /> } /> diff --git a/assets/src/globals.js b/assets/src/globals.js index f6a56252..27f9361c 100644 --- a/assets/src/globals.js +++ b/assets/src/globals.js @@ -55,5 +55,6 @@ const siteTheme = createTheme({ props: componentSettings }) const gaId = mylaGlobals.google_analytics_id +const oneTrustScriptDomain = mylaGlobals.one_trust_script_domain -export { user, siteTheme, gaId, cspNonce, viewHelpURLs, surveyLink } +export { user, siteTheme, gaId, cspNonce, viewHelpURLs, surveyLink, oneTrustScriptDomain } diff --git a/assets/src/index.js b/assets/src/index.js index a3fb8d67..503de2d6 100755 --- a/assets/src/index.js +++ b/assets/src/index.js @@ -6,7 +6,7 @@ import './index.css' import App from './containers/App' import client from './service/client' import { ApolloProvider } from '@apollo/client' -import { user, siteTheme, gaId, cspNonce } from './globals' +import { user, siteTheme, gaId, cspNonce, oneTrustScriptDomain } from './globals' // import * as serviceWorker from './serviceWorker' const container = document.getElementById('root') @@ -15,7 +15,7 @@ root.render( - + diff --git a/config/env_sample.hjson b/config/env_sample.hjson index f367b8db..2ac31cf5 100644 --- a/config/env_sample.hjson +++ b/config/env_sample.hjson @@ -11,12 +11,15 @@ ], # The URL of a generic help or documentation site, to be used in the front end's drop-down menu "HELP_URL": "https://its.umich.edu/academics-research/teaching-learning/my-learning-analytics", + "PRIVACY_REDIRECT_URL": "https://umich.edu/about/privacy/" # Help for views. Specify URLs to overide defaults # URL_VIEW_RESOURCES_ACCESSED # URL_VIEW_ASSIGNMENT_PLANNING # URL_VIEW_GRADE_DISTRIBUTION # ID for your Google Analytics, defaults to nothing/disabled "GA_ID": "", + # ID of OneTrust script domain, used for google analytics integration + "OT_SCRIPT_DOMAIN": "" # The hex value to be used in the front end for the primary color of the palette and theme "PRIMARY_UI_COLOR": "#00274C", # Configuration of CSP see https://django-csp.readthedocs.io/en/latest/configuration.html diff --git a/dashboard/common/utils.py b/dashboard/common/utils.py index cb27c1b9..9a32edb7 100644 --- a/dashboard/common/utils.py +++ b/dashboard/common/utils.py @@ -103,6 +103,8 @@ def get_myla_globals(request): if settings.GA_ID: google_analytics_id = settings.GA_ID + if settings.OT_SCRIPT_DOMAIN: + one_trust_script_domain = settings.OT_SCRIPT_DOMAIN primary_ui_color = settings.PRIMARY_UI_COLOR myla_globals = { @@ -115,6 +117,7 @@ def get_myla_globals(request): "logout": logout_url, "primary_ui_color": primary_ui_color, "google_analytics_id": google_analytics_id, + "one_trust_script_domain": one_trust_script_domain, "view_help_urls": { 'ra': settings.URL_VIEW_RESOURCES_ACCESSED, 'ap': settings.URL_VIEW_ASSIGNMENT_PLANNING, diff --git a/dashboard/settings.py b/dashboard/settings.py index 99acf547..56aa013a 100644 --- a/dashboard/settings.py +++ b/dashboard/settings.py @@ -70,6 +70,7 @@ def apply_env_overrides(env: Dict[str, Any], environ: os._Environ) -> Dict[str, LOGIN_URL = '/accounts/login/' LOGOUT_URL = '/accounts/logout/' HELP_URL = ENV.get("HELP_URL", "https://its.umich.edu/academics-research/teaching-learning/my-learning-analytics") +PRIVACY_REDIRECT_URL = ENV.get("PRIVACY_REDIRECT_URL","https://umich.edu/about/privacy/") URL_VIEW_RESOURCES_ACCESSED = ENV.get("URL_VIEW_RESOURCES_ACCESSED", "https://its.umich.edu/academics-research/teaching-learning/my-learning-analytics/support/resources-accessed") URL_VIEW_ASSIGNMENT_PLANNING = ENV.get("URL_VIEW_ASSIGNMENT_PLANNING", "https://its.umich.edu/academics-research/teaching-learning/my-learning-analytics/support/assignment-planning-goals") @@ -77,6 +78,7 @@ def apply_env_overrides(env: Dict[str, Any], environ: os._Environ) -> Dict[str, # Google Analytics ID GA_ID = ENV.get('GA_ID', '') +OT_SCRIPT_DOMAIN = ENV.get('OT_SCRIPT_DOMAIN', '') # Resource values from env RESOURCE_VALUES = ENV.get("RESOURCE_VALUES", {"files": {"types": ["canvas"], "icon": "fas fa-file fa-lg"}}) diff --git a/dashboard/urls.py b/dashboard/urls.py index 542a2adf..de058777 100644 --- a/dashboard/urls.py +++ b/dashboard/urls.py @@ -42,6 +42,8 @@ path('status/bare_status/', watchman.views.bare_status), path('admin/', admin.site.urls), + # Use PRIVACY_REDIRECT_URL for the privacy policy + path('privacy/', views.privacy_policy_redirect, name='privacy_policy'), # Note the absence of a trailing slash; adding one breaks the GraphQL implementation. path('graphql', DashboardGraphQLView.as_view( middleware=[] if settings.DEBUG else [DisableIntrospectionMiddleware],graphiql=settings.DEBUG)), diff --git a/dashboard/views.py b/dashboard/views.py index be0a0b68..f0c2eeb5 100644 --- a/dashboard/views.py +++ b/dashboard/views.py @@ -678,6 +678,8 @@ def logout(request): auth.logout(request) return redirect(settings.LOGOUT_REDIRECT_URL) +def privacy_policy_redirect(request): + return redirect(settings.PRIVACY_REDIRECT_URL) def courses_enabled(request): """ Returns json for all courses we currently support and are enabled """ diff --git a/package-lock.json b/package-lock.json index 4ae2c4df..be1cc72b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,11 +20,11 @@ "lodash.debounce": "4.0.8", "lodash.isequal": "4.5.0", "rc-slider": "10.5.0", - "react": "18.2.0", - "react-dom": "18.2.0", - "react-ga4": "2.1.0", + "react": "18.3.1", + "react-dom": "18.3.1", + "react-ga-onetrust-consent": "github:tl-its-umich-edu/react-ga-onetrust-consent", "react-helmet": "6.1.0", - "react-router-dom": "6.21.1" + "react-router-dom": "6.25.1" }, "devDependencies": { "@babel/cli": "7.23.4", @@ -3368,9 +3368,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.1.tgz", - "integrity": "sha512-Qg4DMQsfPNAs88rb2xkdk03N3bjK4jgX5fR24eHCTR9q6PrhZQZ4UJBPzCHJkIpTRN1UKxx2DzjZmnC+7Lj0Ow==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.18.0.tgz", + "integrity": "sha512-L3jkqmqoSVBVKHfpGZmLrex0lxR5SucGA0sUfFzGctehw+S/ggL9L/0NnC5mw6P8HUWpFZ3nQw3cRApjjWx9Sw==", "engines": { "node": ">=14.0.0" } @@ -11474,9 +11474,9 @@ } }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dependencies": { "loose-envify": "^1.1.0" }, @@ -11508,15 +11508,15 @@ "dev": true }, "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "dependencies": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^18.2.0" + "react": "^18.3.1" } }, "node_modules/react-fast-compare": { @@ -11524,6 +11524,17 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" }, + "node_modules/react-ga-onetrust-consent": { + "version": "1.0.0", + "resolved": "git+ssh://git@github.com/tl-its-umich-edu/react-ga-onetrust-consent.git#9f34eefa515d1c3d33ee071c6a935f23c85a4de4", + "license": "Apache-2.0", + "dependencies": { + "js-cookie": "^3.0.5", + "react": "^18.3.1", + "react-ga4": "^2.1.0", + "react-router-dom": "^6.25.1" + } + }, "node_modules/react-ga4": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/react-ga4/-/react-ga4-2.1.0.tgz", @@ -11549,11 +11560,11 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "node_modules/react-router": { - "version": "6.21.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.1.tgz", - "integrity": "sha512-W0l13YlMTm1YrpVIOpjCADJqEUpz1vm+CMo47RuFX4Ftegwm6KOYsL5G3eiE52jnJpKvzm6uB/vTKTPKM8dmkA==", + "version": "6.25.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.25.1.tgz", + "integrity": "sha512-u8ELFr5Z6g02nUtpPAggP73Jigj1mRePSwhS/2nkTrlPU5yEkH1vYzWNyvSnSzeeE2DNqWdH+P8OhIh9wuXhTw==", "dependencies": { - "@remix-run/router": "1.14.1" + "@remix-run/router": "1.18.0" }, "engines": { "node": ">=14.0.0" @@ -11563,12 +11574,12 @@ } }, "node_modules/react-router-dom": { - "version": "6.21.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.21.1.tgz", - "integrity": "sha512-QCNrtjtDPwHDO+AO21MJd7yIcr41UetYt5jzaB9Y1UYaPTCnVuJq6S748g1dE11OQlCFIQg+RtAA1SEZIyiBeA==", + "version": "6.25.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.25.1.tgz", + "integrity": "sha512-0tUDpbFvk35iv+N89dWNrJp+afLgd+y4VtorJZuOCXK0kkCWjEvb3vTJM++SYvMEpbVwXKf3FjeVveVEb6JpDQ==", "dependencies": { - "@remix-run/router": "1.14.1", - "react-router": "6.21.1" + "@remix-run/router": "1.18.0", + "react-router": "6.25.1" }, "engines": { "node": ">=14.0.0" diff --git a/package.json b/package.json index d7c95b38..f192feb8 100644 --- a/package.json +++ b/package.json @@ -18,11 +18,11 @@ "lodash.debounce": "4.0.8", "lodash.isequal": "4.5.0", "rc-slider": "10.5.0", - "react": "18.2.0", - "react-dom": "18.2.0", - "react-ga4": "2.1.0", + "react": "18.3.1", + "react-dom": "18.3.1", + "react-ga-onetrust-consent": "github:tl-its-umich-edu/react-ga-onetrust-consent", "react-helmet": "6.1.0", - "react-router-dom": "6.21.1" + "react-router-dom": "6.25.1" }, "babel": { "presets": [