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
3 changes: 3 additions & 0 deletions src/common/constants.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import packageJson from '../../package.json';

export const OCL_CLIENT = `oclweb2/${packageJson.version}`;
export const WHITE = '#FFF';
export const BLACK = '#000';
export const BLUE = 'rgb(51, 115, 170)';
Expand Down
35 changes: 9 additions & 26 deletions src/common/utils.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
/*eslint no-process-env: 0*/
import 'core-js/features/url-search-params';
import React from 'react';
import ReactGA from 'react-ga4';
import alertifyjs from 'alertifyjs';
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, isNumber, uniq, flatten, pickBy,
uniqBy, cloneDeep, isEqual, without, capitalize, last, nth, isNumber, uniq, flatten, pickBy,
isString
} from 'lodash';
import {
DATE_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
} 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 @@ -715,32 +715,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 @@ -1010,6 +987,9 @@ export const getLoginURL = async returnTo => {
const codeChallenge = await preparePKCECodeChallenge()
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 @@ -1030,6 +1010,9 @@ export const getRegisterURL = async returnTo => {
const codeChallenge = await preparePKCECodeChallenge()
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`
}
let url = '/#/accounts/signup'
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 @@ -4,9 +4,10 @@ import { Route, Switch, withRouter } from 'react-router-dom';
import { get, isEmpty } from 'lodash';
import {
isFHIRServer, isLoggedIn, setUpRecentHistory, getAppliedServerConfig, getSiteTitle,
isDeprecatedBrowser, recordGAPageView, canViewOperationsPanel, isRedirectingToLoginViaReferrer,
isDeprecatedBrowser, canViewOperationsPanel, isRedirectingToLoginViaReferrer,
isSSOEnabled, getLoginURL, isOtherOCLClientURL
} from '../../common/utils';
import GAService from '../../services/GAService';
import Search from '../search/Search';
import SourceHome from '../sources/SourceHome';
import CollectionHome from '../collections/CollectionHome';
Expand Down Expand Up @@ -132,7 +133,7 @@ const App = props => {
if(!isFHIRServer())
fetchToggles()
addLogoutListenerForAllTabs()
recordGAPageView()
GAService.recordPageView()
setupHotJar()
operationsPanelAccess && document.body.addEventListener("keydown", _listenKey)
return () => operationsPanelAccess && document.body.removeEventListener("keydown", _listenKey)
Expand Down
4 changes: 2 additions & 2 deletions src/components/collections/CollectionForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import alertifyjs from 'alertifyjs';
import { Divider, Button } from '@mui/material';
import { orderBy, map, merge, cloneDeep, get, isEmpty } from 'lodash';
import APIService from '../../services/APIService';
import { recordGAUpsertEvent } from '../../common/utils';
import GAService from '../../services/GAService';
import { COLLECTION_TYPES, WHITE } from '../../common/constants'
import FormHeader from '../common/conceptContainerFormComponents/FormHeader';
import NameAndDescription from '../common/conceptContainerFormComponents/NameAndDescription';
Expand Down Expand Up @@ -43,7 +43,7 @@ class CollectionForm extends React.Component {
form.reportValidity()
const isFormValid = form.checkValidity()
if(isFormValid) {
recordGAUpsertEvent('Collection', edit)
GAService.recordUpsertEvent('Collection', edit)
const payload = this.getPayload()
const service = APIService.new().overrideURL(this.state.owner.url + 'collections/')
if(edit)
Expand Down
4 changes: 2 additions & 2 deletions src/components/collections/ExpansionForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
has, forEach
} from 'lodash';
import APIService from '../../services/APIService';
import { recordGAUpsertEvent } from '../../common/utils';
import GAService from '../../services/GAService';
import BetaLabel from '../common/BetaLabel';

const DEFAULT_TOOLTIP = 'This parameter is not yet supported.'
Expand Down Expand Up @@ -191,7 +191,7 @@ class ExpansionForm extends React.Component {
form.reportValidity()
const isFormValid = form.checkValidity()
if(selectedVersion && isFormValid) {
recordGAUpsertEvent('Expansion', false)
GAService.recordUpsertEvent('Expansion', false)

this.alert = alertifyjs.warning('Starting Expansion Creation. This might take few seconds.', 0)
fields = pickBy(fields, value => value)
Expand Down
4 changes: 2 additions & 2 deletions src/components/common/ConceptContainerVersionForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import moment from 'moment';
import { TextField, Button, FormControlLabel, Checkbox, Autocomplete } from '@mui/material';
import { set, get, cloneDeep, isEmpty, pickBy, startCase, isBoolean, isObject, values, map } from 'lodash';
import APIService from '../../services/APIService';
import { recordGAUpsertEvent } from '../../common/utils';
import GAService from '../../services/GAService';


class ConceptContainerVersionForm extends React.Component {
Expand Down Expand Up @@ -65,7 +65,7 @@ class ConceptContainerVersionForm extends React.Component {
const isFormValid = form.checkValidity()
if(parentURL && isFormValid) {
const isCollectionVersion = (resource || resourceType) === 'collection'
recordGAUpsertEvent(isCollectionVersion ? `Collection Version` : 'Source Version', edit)
GAService.recordUpsertEvent(isCollectionVersion ? `Collection Version` : 'Source Version', edit)
this.alert = alertifyjs.warning('Starting Version Creation. This might take few seconds.', 0)
fields = pickBy(fields, value => value)

Expand Down
5 changes: 3 additions & 2 deletions src/components/concepts/ConceptForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import {
set, get, map, cloneDeep, pullAt, filter, isEmpty, pick, isObject, values, startCase,
} from 'lodash';
import APIService from '../../services/APIService';
import GAService from '../../services/GAService';
import {
arrayToObject, fetchDatatypes, fetchNameTypes, sortValuesBySourceSummary,
fetchDescriptionTypes, fetchConceptClasses, recordGAUpsertEvent, toParentURI
fetchDescriptionTypes, fetchConceptClasses, toParentURI
} from '../../common/utils';
import { ERROR_RED } from '../../common/constants';
import LocaleForm from './LocaleForm';
Expand Down Expand Up @@ -279,7 +280,7 @@ class ConceptForm extends React.Component {

const isFormValid = form.checkValidity()
if(parentURL && isFormValid) {
recordGAUpsertEvent('Concept', edit)
GAService.recordUpsertEvent('Concept', edit)
if(edit)
fields.update_comment = fields.comment
fields.extras = arrayToObject(fields.extras)
Expand Down
11 changes: 6 additions & 5 deletions src/components/concepts/ConceptHome.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import Split from 'react-split'
import { CircularProgress } from '@mui/material';
import { get, isObject, isBoolean, has, flatten, values, isArray, find, map } from 'lodash';
import APIService from '../../services/APIService';
import { fetchAllVersions, toParentURI, currentUserHasAccess, recordGAAction, highlightTexts } from '../../common/utils'
import GAService from '../../services/GAService';
import { fetchAllVersions, toParentURI, currentUserHasAccess, highlightTexts } from '../../common/utils'
import NotFound from '../common/NotFound';
import AccessDenied from '../common/AccessDenied';
import PermissionDenied from '../common/PermissionDenied';
Expand Down Expand Up @@ -102,7 +103,7 @@ class ConceptHome extends React.Component {
.overrideURL(URL)
.get(null, null, {includeReferences: this.props.scoped === 'collection'})
.then(response => {
recordGAAction('Concept', 'split_view', `Concept - ${URL}`)
GAService.recordEvent('split_view', { event_category: 'Concept', event_label: `Concept - ${URL}` })
if(get(response, 'detail') === "Not found.")
this.setState({isLoading: false, concept: {}, notFound: true, accessDenied: false, permissionDenied: false})
else if(get(response, 'detail') === "Authentication credentials were not provided.")
Expand Down Expand Up @@ -279,7 +280,7 @@ class ConceptHome extends React.Component {
}

retireMapping = (mapping, comment, isDirect) => {
recordGAAction('Mapping Inline', 'retired_mapping', 'Retried Mapping from Concept Details using Quick Actions')
GAService.recordEvent('retired_mapping', { event_category: 'Mapping Inline', event_label: 'Retried Mapping from Concept Details using Quick Actions' })
APIService.new().overrideURL(mapping.url).delete({comment: comment}).then(response => {
if(get(response, 'status') === 204) {
isDirect ? this.getMappings(true) : this.getInverseMappings(true)
Expand All @@ -291,7 +292,7 @@ class ConceptHome extends React.Component {
}

unretireMapping = (mapping, comment, isDirect) => {
recordGAAction('Mapping Inline', 'unretired_mapping', 'Reactivated retired Mapping from Concept Details using Quick Actions')
GAService.recordEvent('unretired_mapping', { event_category: 'Mapping Inline', event_label: 'Reactivated retired Mapping from Concept Details using Quick Actions' })
APIService.new().overrideURL(mapping.url).appendToUrl('reactivate/').put({comment: comment}).then(response => {
if(get(response, 'status') === 204) {
isDirect ? this.getMappings(true) : this.getInverseMappings(true)
Expand All @@ -303,7 +304,7 @@ class ConceptHome extends React.Component {
}

onCreateNewMapping = (payload, targetConcept, isDirect, successCallback) => {
recordGAAction('Mapping Inline', 'create_mapping', 'Created Mapping from Concept Details using Quick Actions')
GAService.recordEvent('create_mapping', { event_category: 'Mapping Inline', event_label: 'Created Mapping from Concept Details using Quick Actions' })
const { concept, mappings, reverseMappings } = this.state
const URL = `${concept.owner_url}sources/${concept.source}/mappings/`
const targetSourceURL = payload?.to_source_url || payload?.from_source_url || toParentURI(targetConcept.url)
Expand Down
5 changes: 3 additions & 2 deletions src/components/mappings/MappingForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
set, get, cloneDeep, isEmpty, pickBy, pullAt, map
} from 'lodash';
import APIService from '../../services/APIService';
import { arrayToObject, fetchMapTypes, recordGAUpsertEvent } from '../../common/utils';
import GAService from '../../services/GAService';
import { arrayToObject, fetchMapTypes } from '../../common/utils';
import ExtrasForm from '../common/ExtrasForm';
import OwnerParentSelection from '../common/OwnerParentSelection';

Expand Down Expand Up @@ -221,7 +222,7 @@ class MappingForm extends React.Component {

const isFormValid = form.checkValidity()
if(parentURL && isFormValid) {
recordGAUpsertEvent('Mapping', edit)
GAService.recordUpsertEvent('Mapping', edit)
fields.extras = arrayToObject(fields.extras)
if(edit)
fields.update_comment = fields.comment
Expand Down
5 changes: 3 additions & 2 deletions src/components/mappings/MappingHome.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import React from 'react';
import { CircularProgress } from '@mui/material';
import { get, isObject, has } from 'lodash';
import APIService from '../../services/APIService';
import { fetchAllVersions, recordGAAction, highlightTexts } from '../../common/utils'
import GAService from '../../services/GAService';
import { fetchAllVersions, highlightTexts } from '../../common/utils'
import ScopeHeader from './ScopeHeader'
import MappingHomeDetails from './MappingHomeDetails';
import NotFound from '../common/NotFound';
Expand Down Expand Up @@ -69,7 +70,7 @@ class MappingHome extends React.Component {
.overrideURL(URL)
.get(null, null, {includeReferences: this.props.scoped === 'collection'})
.then(response => {
recordGAAction('Mapping', 'split_view', `Mapping - ${URL}`)
GAService.recordEvent('split_view', { event_category: 'Mapping', event_label: `Mapping - ${URL}` })
if(get(response, 'detail') === "Not found.")
this.setState({isLoading: false, mapping: {}, notFound: true, accessDenied: false, permissionDenied: false})
else if(get(response, 'detail') === "Authentication credentials were not provided.")
Expand Down
5 changes: 3 additions & 2 deletions src/components/orgs/OrgForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
set, get, cloneDeep, isEmpty, pullAt, map
} from 'lodash';
import APIService from '../../services/APIService';
import { arrayToObject, toFullURL, refreshCurrentUserCache, recordGAUpsertEvent } from '../../common/utils';
import GAService from '../../services/GAService';
import { arrayToObject, toFullURL, refreshCurrentUserCache } from '../../common/utils';
import ExtrasForm from '../common/ExtrasForm';
import RTEditor from '../common/RTEditor';

Expand Down Expand Up @@ -85,7 +86,7 @@ class OrgForm extends React.Component {

const isFormValid = form.checkValidity()
if(isFormValid) {
recordGAUpsertEvent('Organization', edit)
GAService.recordUpsertEvent('Organization', edit)
fields.extras = arrayToObject(fields.extras)
if(edit) {
APIService.orgs(fields.id).put(fields).then(response => this.handleSubmitResponse(response))
Expand Down
7 changes: 4 additions & 3 deletions src/components/sources/Breadcrumbs.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import {
} from '@mui/icons-material';
import { IconButton } from '@mui/material';
import { isEmpty, get, startCase } from 'lodash';
import { currentUserHasAccess, recordGAAction } from '../../common/utils';
import { currentUserHasAccess } from '../../common/utils';
import { WHITE, ORANGE, GREEN, BLUE } from '../../common/constants';
import APIService from '../../services/APIService';
import GAService from '../../services/GAService';
import OwnerButton from '../common/OwnerButton';
import SourceButton from '../common/SourceButton';
import CollectionButton from '../common/CollectionButton';
Expand Down Expand Up @@ -107,7 +108,7 @@ const Breadcrumbs = ({

const retire = comment => {
const resourceLabel = startCase(resourceType)
recordGAAction(resourceLabel, `retired_${resourceType}`, `Retired ${resourceLabel}`)
GAService.recordEvent(`retired_${resourceType}`, { event_category: resourceLabel, event_label: `Retired ${resourceLabel}` })
APIService.new().overrideURL(resourceEncodedURL.replace('#', '')).delete({comment: comment}).then(response => {
if(get(response, 'status') === 204)
alertifyjs.success(`${resourceLabel} Retired`, 1, () => window.location.reload())
Expand All @@ -118,7 +119,7 @@ const Breadcrumbs = ({

const unretire = comment => {
const resourceLabel = startCase(resourceType)
recordGAAction(resourceLabel, `unretired_${resourceType}`, `Reactivated ${resourceLabel}`)
GAService.recordEvent(`unretired_${resourceType}`, { event_category: resourceLabel, event_label: `Reactivated ${resourceLabel}` })
APIService.new().overrideURL(resourceEncodedURL.replace('#', '')).appendToUrl('reactivate/').put({comment: comment}).then(response => {
if(get(response, 'status') === 204)
alertifyjs.success(`${resourceLabel} Unretired`, 1, () => window.location.reload())
Expand Down
4 changes: 2 additions & 2 deletions src/components/sources/SourceForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import alertifyjs from 'alertifyjs';
import { Divider, Button } from '@mui/material';
import { orderBy, map, merge, cloneDeep, get, isEmpty } from 'lodash';
import APIService from '../../services/APIService';
import { recordGAUpsertEvent } from '../../common/utils';
import GAService from '../../services/GAService';
import { SOURCE_TYPES, WHITE } from '../../common/constants'
import FormHeader from '../common/conceptContainerFormComponents/FormHeader';
import NameAndDescription from '../common/conceptContainerFormComponents/NameAndDescription';
Expand Down Expand Up @@ -35,7 +35,7 @@ class SourceForm extends React.Component {
form.reportValidity()
const isFormValid = form.checkValidity()
if(isFormValid) {
recordGAUpsertEvent('Source', edit)
GAService.recordUpsertEvent('Source', edit)
const payload = this.getPayload()
const service = APIService.new().overrideURL(this.state.owner.url + 'sources/')
if(edit)
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 @@ -6,6 +6,7 @@ import {
refreshCurrentUserCache, consumeStoredPKCECodeVerifier, consumeAndValidateOAuthState
} from '../../common/utils';
import APIService from '../../services/APIService'
import GAService from '../../services/GAService'


class OIDLoginCallback extends React.Component {
Expand Down Expand Up @@ -37,11 +38,13 @@ 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)
this.cacheUserData()
} else {
GAService.clearSignupFlow()
alertifyjs.error(res.data)
}
})
Expand Down
5 changes: 3 additions & 2 deletions src/components/users/UserForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
set, get, isEmpty, cloneDeep, merge, map, pullAt, isEqual, uniq, without, includes,
} from 'lodash';
import APIService from '../../services/APIService';
import { arrayToObject, isAdminUser, getCurrentUserUsername, recordGAUpsertEvent } from '../../common/utils';
import GAService from '../../services/GAService';
import { arrayToObject, isAdminUser, getCurrentUserUsername } from '../../common/utils';
import { AUTH_GROUPS } from '../../common/constants';
import ExtrasForm from '../common/ExtrasForm';
const EXTRAS_MODEL = {key: '', value: ''}
Expand Down Expand Up @@ -108,7 +109,7 @@ class UserForm extends React.Component {

const isFormValid = form.checkValidity()
if(isFormValid) {
recordGAUpsertEvent('User', edit)
GAService.recordUpsertEvent('User', edit)

fields.extras = arrayToObject(fields.extras)
if(!this.canEditAuthGroups() || (edit && isEqual(user.auth_groups, fields.auth_groups)))
Expand Down
4 changes: 2 additions & 2 deletions src/services/APIService.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import axios from 'axios';
import {get, omit, isPlainObject, isString, defaults } from 'lodash';
import { currentUserToken, getAPIURL } from '../common/utils';
import packageJson from '../../package.json';
import { OCL_CLIENT } from '../common/constants';


const APIServiceProvider = {};
Expand Down Expand Up @@ -119,7 +119,7 @@ class APIService {
token = token || (token !== false ? currentUserToken() : token);
const obj = defaults(headers, this.headers);
if (token) obj['Authorization'] = `Token ${token}`;
obj['X-OCL-CLIENT'] = `oclweb2/${packageJson.version}`;
obj['X-OCL-CLIENT'] = OCL_CLIENT;
return obj;
}

Expand Down
Loading