From fa3612917b8f792441b254b6bf01992afb25ef43 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Tue, 30 May 2023 10:02:04 -0400 Subject: [PATCH 1/6] Add react-ga4 lib --- package.json | 1 + yarn.lock | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/package.json b/package.json index 1f4c37dad7..6c9f838995 100644 --- a/package.json +++ b/package.json @@ -130,6 +130,7 @@ "react": "16.12.0", "react-dates": "21.8.0", "react-dom": "16.12.0", + "react-ga4": "2.1.0", "react-hooks-testing-library": "0.6.0", "react-hot-loader": "4.12.19", "react-i18next": "7.13.0", diff --git a/yarn.lock b/yarn.lock index fdd0f02e9f..16bd90badc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15587,6 +15587,11 @@ react-focus-lock@^2.1.0: use-callback-ref "^1.2.1" use-sidecar "^1.0.1" +react-ga4@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/react-ga4/-/react-ga4-2.1.0.tgz#56601f59d95c08466ebd6edfbf8dede55c4678f9" + integrity sha512-ZKS7PGNFqqMd3PJ6+C2Jtz/o1iU9ggiy8Y8nUeksgVuvNISbmrQtJiZNvC/TjDsqD0QlU5Wkgs7i+w9+OjHhhQ== + react-helmet-async@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/react-helmet-async/-/react-helmet-async-1.0.4.tgz#079ef10b7fefcaee6240fefd150711e62463cc97" From 385ebedb494e20beae783bf3053f5ddcb1830048 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Wed, 31 May 2023 11:04:14 -0400 Subject: [PATCH 2/6] Set up react-ga4 page_view tracking --- Dockerfile | 2 ++ app/bootstrap.js | 4 +++- app/routes.js | 11 +++++++++++ config.webpack.js | 1 + config/local.example.js | 2 ++ webpack.config.js | 2 ++ 6 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index cab60159de..3fc4e7e959 100644 --- a/Dockerfile +++ b/Dockerfile @@ -60,6 +60,7 @@ ARG REALM_HOST ARG PORT=3000 ARG SERVICE_NAME=blip ARG ROLLBAR_POST_SERVER_TOKEN +ARG REACT_APP_GAID ARG I18N_ENABLED=false ARG RX_ENABLED=false ARG PENDO_ENABLED=true @@ -71,6 +72,7 @@ ENV \ PORT=$PORT \ SERVICE_NAME=$SERVICE_NAME \ ROLLBAR_POST_SERVER_TOKEN=$ROLLBAR_POST_SERVER_TOKEN \ + REACT_APP_GAID=$REACT_APP_GAID \ I18N_ENABLED=$I18N_ENABLED \ RX_ENABLED=$RX_ENABLED \ PENDO_ENABLED=$PENDO_ENABLED \ diff --git a/app/bootstrap.js b/app/bootstrap.js index 65c25e67b0..61339fde6f 100644 --- a/app/bootstrap.js +++ b/app/bootstrap.js @@ -14,6 +14,7 @@ */ import React from 'react'; +import ReactGA from 'react-ga4'; import { render } from 'react-dom'; import bows from 'bows'; import _ from 'lodash'; @@ -28,7 +29,7 @@ import api from './core/api'; import personUtils from './core/personutils'; import detectTouchScreen from './core/notouch'; -/* global __DEV_TOOLS__ */ +/* global __DEV_TOOLS__, __REACT_APP_GAID__ */ // For React developer tools window.React = React; @@ -59,6 +60,7 @@ appContext.props = { }; appContext.init = callback => { + __REACT_APP_GAID__ && ReactGA.initialize(__REACT_APP_GAID__, { gtagOptions: { 'send_page_view': false } }); function beginInit() { initNoTouch(); diff --git a/app/routes.js b/app/routes.js index 6327251cfc..3a29c346cf 100644 --- a/app/routes.js +++ b/app/routes.js @@ -1,5 +1,6 @@ import _ from 'lodash'; import React from 'react'; +import ReactGA from 'react-ga4'; import async from 'async'; import { Route, Switch, Redirect } from 'react-router-dom'; import { push } from 'connected-react-router'; @@ -326,6 +327,16 @@ export const getRoutes = (appContext) => { return ( ( + { + if (ReactGA.isInitialized) { + ReactGA.send({ hitType: 'pageview', page: location?.pathname + location?.search + location?.hash }); + } + return null; + }} + /> + ()} /> ()} /> diff --git a/config.webpack.js b/config.webpack.js index a4d6749b19..504b6bd2e0 100644 --- a/config.webpack.js +++ b/config.webpack.js @@ -27,6 +27,7 @@ const defineEnvPlugin = new webpack.DefinePlugin({ __PENDO_ENABLED__: JSON.stringify(process.env.PENDO_ENABLED || true), __VERSION__: JSON.stringify(VERSION), __ROLLBAR_POST_CLIENT_TOKEN__: JSON.stringify(ROLLBAR_POST_CLIENT_TOKEN), + __REACT_APP_GAID__: JSON.stringify(process.env.REACT_APP_GAID || null), __VERSION_SHA__: JSON.stringify(VERSION_SHA), __DEV__: isDev, __TEST__: false, diff --git a/config/local.example.js b/config/local.example.js index cf88dbe323..57c83c3143 100644 --- a/config/local.example.js +++ b/config/local.example.js @@ -35,6 +35,7 @@ const environments = { }; const apiHost = environments.dev; +const reactAppGAID = ' '; const uploadApi = apiHost; module.exports = { @@ -42,5 +43,6 @@ module.exports = { linkedPackages, featureFlags, apiHost, + reactAppGAID, uploadApi, }; diff --git a/webpack.config.js b/webpack.config.js index 1a00db3443..82897c6657 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -21,6 +21,7 @@ const isProd = (process.env.NODE_ENV === 'production'); const linkedPackages = (isDev || isTest) ? _.get(optional('./config/local'), 'linkedPackages', {}) : {}; const apiHost = _.get(optional('./config/local'), 'apiHost', process.env.API_HOST || null); const uploadApi = _.get(optional('./config/local'), 'uploadApi', process.env.UPLOAD_API || null); +const reactAppGAID = _.get(optional('./config/local'), 'reactAppGAID', process.env.REACT_APP_GAID || null); const featureFlags = _.get(optional('./config/local'), 'featureFlags', { i18nEnabled: process.env.I18N_ENABLED || false, rxEnabled: process.env.RX_ENABLED || false, @@ -158,6 +159,7 @@ const plugins = [ __PENDO_ENABLED__: JSON.stringify(featureFlags.pendoEnabled), __VERSION__: JSON.stringify(VERSION), __ROLLBAR_POST_CLIENT_TOKEN__: JSON.stringify(ROLLBAR_POST_CLIENT_TOKEN), + __REACT_APP_GAID__: JSON.stringify(reactAppGAID), __VERSION_SHA__: JSON.stringify(VERSION_SHA), __DEV__: isDev, __TEST__: isTest, From d5464f9670f628ac40f25f785be6dff626ec6c97 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Wed, 31 May 2023 11:16:33 -0400 Subject: [PATCH 3/6] Track page_search and page_hash in addition to page_location --- app/routes.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/routes.js b/app/routes.js index 3a29c346cf..d217ceb202 100644 --- a/app/routes.js +++ b/app/routes.js @@ -331,7 +331,12 @@ export const getRoutes = (appContext) => { path="/" render={({ location }) => { if (ReactGA.isInitialized) { - ReactGA.send({ hitType: 'pageview', page: location?.pathname + location?.search + location?.hash }); + ReactGA.send({ + hitType: 'pageview', + page: location?.pathname + location?.search + location?.hash, + 'page_search': location.search, + 'page_hash': location.hash, + }); } return null; }} From 20cf73e1d2bc9a98758f8add7e2b93a094ea2d7d Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Wed, 31 May 2023 12:02:05 -0400 Subject: [PATCH 4/6] Track keycloak signup redirects in GA --- app/pages/signup/signup.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/pages/signup/signup.js b/app/pages/signup/signup.js index 7a5eb3cd70..32e7d31ea8 100644 --- a/app/pages/signup/signup.js +++ b/app/pages/signup/signup.js @@ -1,5 +1,6 @@ import PropTypes from 'prop-types'; import React from 'react'; +import ReactGA from 'react-ga4'; import { connect } from 'react-redux'; import { translate, Trans} from 'react-i18next'; import { bindActionCreators } from 'redux'; @@ -168,6 +169,14 @@ export let Signup = translate()(class extends React.Component { options.loginHint = inviteEmail; } keycloak.register(options); + + if (ReactGA.isInitialized) { + ReactGA.event({ + category: 'redirect', + action: 'Keycloak Sign Up', + label: inviteEmail ? 'Has inviteEmail' : 'No inviteEmail', + }); + } } let content = isLoading || keycloakConfig.initialized ? null : ( From e7b5c7b924628d767a5ef45859e2e119666440db Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Thu, 1 Jun 2023 09:44:19 -0400 Subject: [PATCH 5/6] Move signup redirect ga event above keycloak register --- app/pages/signup/signup.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/pages/signup/signup.js b/app/pages/signup/signup.js index 32e7d31ea8..5e920ff6ed 100644 --- a/app/pages/signup/signup.js +++ b/app/pages/signup/signup.js @@ -168,7 +168,6 @@ export let Signup = translate()(class extends React.Component { if(inviteEmail){ options.loginHint = inviteEmail; } - keycloak.register(options); if (ReactGA.isInitialized) { ReactGA.event({ @@ -177,6 +176,8 @@ export let Signup = translate()(class extends React.Component { label: inviteEmail ? 'Has inviteEmail' : 'No inviteEmail', }); } + + keycloak.register(options); } let content = isLoading || keycloakConfig.initialized ? null : ( From 67f1986eee5365ef16022de9f1df5a2ddabd89bf Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Thu, 1 Jun 2023 13:18:40 -0400 Subject: [PATCH 6/6] Move google analytics init and pageview tracking to dedicated wrapper component --- app/bootstrap.js | 5 +---- app/googleanalytics.js | 31 +++++++++++++++++++++++++++++++ app/redux/containers/Root.dev.js | 5 ++++- app/redux/containers/Root.prod.js | 5 ++++- app/routes.js | 16 ---------------- 5 files changed, 40 insertions(+), 22 deletions(-) create mode 100644 app/googleanalytics.js diff --git a/app/bootstrap.js b/app/bootstrap.js index 61339fde6f..cd7f22facb 100644 --- a/app/bootstrap.js +++ b/app/bootstrap.js @@ -14,7 +14,6 @@ */ import React from 'react'; -import ReactGA from 'react-ga4'; import { render } from 'react-dom'; import bows from 'bows'; import _ from 'lodash'; @@ -29,7 +28,7 @@ import api from './core/api'; import personUtils from './core/personutils'; import detectTouchScreen from './core/notouch'; -/* global __DEV_TOOLS__, __REACT_APP_GAID__ */ +/* global __DEV_TOOLS__ */ // For React developer tools window.React = React; @@ -60,8 +59,6 @@ appContext.props = { }; appContext.init = callback => { - __REACT_APP_GAID__ && ReactGA.initialize(__REACT_APP_GAID__, { gtagOptions: { 'send_page_view': false } }); - function beginInit() { initNoTouch(); } diff --git a/app/googleanalytics.js b/app/googleanalytics.js new file mode 100644 index 0000000000..c1b34cafeb --- /dev/null +++ b/app/googleanalytics.js @@ -0,0 +1,31 @@ +import React from 'react'; +import ReactGA from 'react-ga4'; +import { useLocation } from 'react-router-dom'; + +/* global __REACT_APP_GAID__ */ + +const GoogleAnalyticsWrapper = ({ children }) => { + const location = useLocation(); + const isInitialized = ReactGA.isInitialized; + + React.useEffect(() => { + if (__REACT_APP_GAID__ && isInitialized === false) { + ReactGA.initialize(__REACT_APP_GAID__, { gtagOptions: { 'send_page_view': false } }); + } + }, [isInitialized]); + + React.useEffect(() => { + if (ReactGA.isInitialized) { + ReactGA.send({ + hitType: 'pageview', + page: location?.pathname + location?.search + location?.hash, + 'page_search': location?.search, + 'page_hash': location?.hash, + }); + } + }, [location]); + + return children; +}; + +export default GoogleAnalyticsWrapper diff --git a/app/redux/containers/Root.dev.js b/app/redux/containers/Root.dev.js index f3e12bf529..fff375cf53 100644 --- a/app/redux/containers/Root.dev.js +++ b/app/redux/containers/Root.dev.js @@ -8,6 +8,7 @@ import { KeycloakWrapper } from '../../keycloak'; import baseTheme from '../../themes/baseTheme'; import { history } from '../store/configureStore.dev'; import { ToastProvider } from '../../providers/ToastProvider'; +import GoogleAnalyticsWrapper from '../../googleanalytics'; setConfig({ logLevel: 'warning' }) @@ -21,7 +22,9 @@ class Root extends Component {
- {routing} + + {routing} +
diff --git a/app/redux/containers/Root.prod.js b/app/redux/containers/Root.prod.js index 7d2b3ebe60..2a83577d49 100644 --- a/app/redux/containers/Root.prod.js +++ b/app/redux/containers/Root.prod.js @@ -6,6 +6,7 @@ import { ThemeProvider } from 'styled-components'; import baseTheme from '../../themes/baseTheme'; import { history } from '../store/configureStore.prod'; import { ToastProvider } from '../../providers/ToastProvider'; +import GoogleAnalyticsWrapper from '../../googleanalytics'; import { KeycloakWrapper } from '../../keycloak'; export default class Root extends Component { @@ -18,7 +19,9 @@ export default class Root extends Component {
- {routing} + + {routing} +
diff --git a/app/routes.js b/app/routes.js index d217ceb202..6327251cfc 100644 --- a/app/routes.js +++ b/app/routes.js @@ -1,6 +1,5 @@ import _ from 'lodash'; import React from 'react'; -import ReactGA from 'react-ga4'; import async from 'async'; import { Route, Switch, Redirect } from 'react-router-dom'; import { push } from 'connected-react-router'; @@ -327,21 +326,6 @@ export const getRoutes = (appContext) => { return ( ( - { - if (ReactGA.isInitialized) { - ReactGA.send({ - hitType: 'pageview', - page: location?.pathname + location?.search + location?.hash, - 'page_search': location.search, - 'page_hash': location.hash, - }); - } - return null; - }} - /> - ()} /> ()} />