Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 7 additions & 26 deletions src/common/utils.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
/*eslint no-process-env: 0*/
import 'core-js/features/url-search-params';
import React from 'react';
import ReactGA from 'react-ga4';
import moment from 'moment';
import { Tooltip } from '@mui/material';
import {
filter, difference, compact, find, reject, intersectionBy, size, keys, omitBy, isEmpty,
get, includes, map, isArray, values, pick, sortBy, zipObject, orderBy, isObject, merge,
uniqBy, cloneDeep, isEqual, without, capitalize, last, nth, startCase, uniq, flatten, pickBy, upperFirst
uniqBy, cloneDeep, isEqual, without, capitalize, last, nth, uniq, flatten, pickBy, upperFirst
} from 'lodash';
import {
DATE_FORMAT, TIME_FORMAT, DATETIME_FORMAT, OCL_SERVERS_GROUP, OCL_FHIR_SERVERS_GROUP, HAPI_FHIR_SERVERS_GROUP,
OPENMRS_URL, DEFAULT_FHIR_SERVER_FOR_LOCAL_ID, OPERATIONS_PANEL_GROUP, ID_REGEX,
RESERVED_ROUTE_KEYWORDS, OCL_CLIENT_HEADERS
} from './constants';
import APIService from '../services/APIService';
import GAService from '../services/GAService';
import { SERVER_CONFIGS } from './serverConfigs';

export const currentPath = () => window.location.hash.split('?')[0];
Expand Down Expand Up @@ -594,32 +594,9 @@ export const getOpenMRSURL = () => {
return OPENMRS_URL.replace('openmrs.', `openmrs.${env}`);
}

export const recordGAPageView = () => {
/*eslint no-undef: 0*/
ReactGA.initialize(window.GA_ACCOUNT_ID || process.env.GA_ACCOUNT_ID);
// Strip the query string from the hash so auth params (code, state, session_state) on the OIDC callback are not sent to GA
ReactGA.send({ hitType: "pageview", page: window.location.pathname + window.location.hash.split('?')[0] });
}

export const recordGAAction = (category, action, label) => {
/*eslint no-undef: 0*/
if(category && action) {
ReactGA.initialize(window.GA_ACCOUNT_ID || process.env.GA_ACCOUNT_ID);
ReactGA.event({category: category, action: action, label: label || action, transport: "xhr"});
}
}

export const recordGAUpsertEvent = (category, edit, resource) => {
const actionPrefix = edit ? 'update' : 'create'
resource = resource || category.replaceAll(' ', '_').toLowerCase()
let action = `${actionPrefix}_${resource}`
let label = `${startCase(actionPrefix)} ${startCase(resource)}`
recordGAAction(category, action, label)
}

export const setUpRecentHistory = history => {
history.listen(location => {
recordGAPageView()
GAService.recordPageView()
let visits = JSON.parse(get(localStorage, 'visits', '[]'));
let urlParts = compact(location.pathname.split('/'));
let type = '';
Expand Down Expand Up @@ -922,6 +899,8 @@ export const getLoginURL = async returnTo => {
const state = prepareOAuthState()
const nonce = generateSecureRandomString(32)

GAService.clearSignupFlow()

return `${getAPIURL()}/users/login/?client_id=${oidClientID}&state=${state}&nonce=${nonce}&redirect_uri=${redirectURL}&code_challenge=${codeChallenge}&code_challenge_method=S256`
}

Expand All @@ -946,6 +925,8 @@ export const getRegisterURL = async returnTo => {
const state = prepareOAuthState()
const nonce = generateSecureRandomString(32)

GAService.recordSignupStart()

return `${getAPIURL()}/users/signup/?client_id=${oidClientID}&state=${state}&nonce=${nonce}&redirect_uri=${redirectURL}&code_challenge=${codeChallenge}&code_challenge_method=S256`
}

Expand Down
5 changes: 3 additions & 2 deletions src/components/app/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
import React from 'react';
import { Route, Switch, withRouter } from 'react-router-dom';
import {
recordGAPageView, isLoggedIn, getCurrentUser, getLoginURL, isOtherOCLClientURL, isRedirectingToLoginViaReferrer
isLoggedIn, getCurrentUser, getLoginURL, isOtherOCLClientURL, isRedirectingToLoginViaReferrer
} from '../../common/utils';
import GAService from '../../services/GAService';
import { REPO_ROUTE_PATHS } from '../../common/repoRoute';
import Error404 from '../errors/Error404';
import Error403 from '../errors/Error403';
Expand Down Expand Up @@ -141,7 +142,7 @@ const App = props => {
forceLoginUser()
fetchToggles()
addLogoutListenerForAllTabs()
recordGAPageView()
GAService.recordPageView()
setupHotJar()
}, [])

Expand Down
3 changes: 3 additions & 0 deletions src/components/users/OIDLoginCallback.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
refreshCurrentUserCache, consumeStoredPKCECodeVerifier, consumeAndValidateOAuthState
} from '../../common/utils';
import APIService from '../../services/APIService'
import GAService from '../../services/GAService'
import { OperationsContext } from '../app/LayoutContext';

class OIDLoginCallback extends React.Component {
Expand Down Expand Up @@ -39,6 +40,7 @@ class OIDLoginCallback extends React.Component {

APIService.users().appendToUrl('oidc/code-exchange/').post({code: code, redirect_uri: redirectURL, client_id: clientId, code_verifier: codeVerifier}).then(res => {
if(res.data?.access_token) {
GAService.recordSignupComplete()
localStorage.removeItem('server_configs')
localStorage.setItem('token', res.data.access_token)
localStorage.setItem('id_token', res.data.id_token)
Expand All @@ -51,6 +53,7 @@ class OIDLoginCallback extends React.Component {
})
this.cacheUserData()
} else {
GAService.clearSignupFlow()
setAlert({severity: 'error', message: res.data?.error_description || this.props.t('auth.sign_in_error')})
}
})
Expand Down
66 changes: 66 additions & 0 deletions src/services/GAService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*eslint no-process-env: 0*/
import ReactGA from 'react-ga4';
import { startCase } from 'lodash';
import { OCL_CLIENT } from '../common/constants';

const SIGNUP_FLOW_PENDING_KEY = 'signup_flow_pending';

const gaId = () => window.GA_ACCOUNT_ID || process.env.GA_ACCOUNT_ID;

// The marketing site only exists in prod, at these two domains, regardless
// of which env this app itself is running in. map./app. mirror this app's
// own env (e.g. app.v3.qa. -> map.qa., app.v3.qa. -> app.qa.).
const linkedDomains = () => {
const host = window.location.host;

return [
'openconceptlab.org',
'preview.openconceptlab.org',
host.replace('app.v3.', 'map.'),
host.replace('app.v3.', 'app.'),
];
};

const initialize = options => {
/*eslint no-undef: 0*/
ReactGA.initialize(gaId(), options);
};

const GAService = {
recordPageView() {
// eslint-disable-next-line spellcheck/spell-checker
initialize({ gtagOptions: { linker: { domains: linkedDomains() } } });
ReactGA.send({ hitType: 'pageview', page: window.location.pathname + window.location.hash.split('?')[0] });
},

recordUpsertEvent(category, edit, resource) {
const actionPrefix = edit ? 'update' : 'create';
resource = resource || category.replaceAll(' ', '_').toLowerCase();
const action = `${actionPrefix}_${resource}`;
const label = `${startCase(actionPrefix)} ${startCase(resource)}`;
this.recordEvent(action, { event_category: category, event_label: label });
},

recordSignupStart() {
sessionStorage.setItem(SIGNUP_FLOW_PENDING_KEY, '1');
this.recordEvent('signup_start', { event_category: 'auth', event_label: 'signup_start' });
},

clearSignupFlow() {
sessionStorage.removeItem(SIGNUP_FLOW_PENDING_KEY);
},

recordSignupComplete() {
const pending = sessionStorage.getItem(SIGNUP_FLOW_PENDING_KEY) === '1';
this.clearSignupFlow();
if(pending)
this.recordEvent('signup_complete', { event_category: 'auth', event_label: 'signup_complete' });
},

recordEvent(name, params) {
initialize();
ReactGA.event(name, { client: OCL_CLIENT, ...params });
},
};

export default GAService;