diff --git a/bin/populate_env b/bin/populate_env
index d99de9b6..892eac63 100755
--- a/bin/populate_env
+++ b/bin/populate_env
@@ -11,11 +11,13 @@ search_enabled="${REACT_APP_SEMANTIC_SEARCH_ENABLED:-true}"
search_url="${REACT_APP_HELX_SEARCH_URL}"
brand_name="${REACT_APP_UI_BRAND_NAME}"
tranql_url="${REACT_APP_TRANQL_URL:-\/tranql}"
-analytics="${REACT_APP_ANALYTICS:-}"
hidden_support_sections="${REACT_APP_HIDDEN_SUPPORT_SECTIONS}"
hidden_result_tabs="${REACT_APP_HIDDEN_RESULT_TABS}"
deployment_namespace="${REACT_APP_DEPLOYMENT_NAMESPACE}"
appstore_asset_branch="${REACT_APP_APPSTORE_ASSET_BRANCH}"
+analytics_enabled="${REACT_APP_ANALYTICS_ENABLED:-false}"
+analytics_platform="${REACT_APP_ANALYTICS_PLATFORM}"
+analytics_token="${REACT_APP_ANALYTICS_TOKEN}"
meta_title="${META_TITLE:-HeLx UI}"
meta_description="${META_DESCRIPTION:-HeLx UI}"
@@ -28,12 +30,9 @@ template='{
"workspaces_enabled": "%WORKSPACES_ENABLED%",
"tranql_url": "%TRANQL_URL%",
"analytics": {
- "enabled": true,
- "platform": "mixpanel",
- "auth": {
- "mixpanel_token": "%ANALYTICS%",
- "ga_property": ""
- }
+ "enabled": %ANALYTICS_ENABLED%,
+ "platform": "%ANALYTICS_PLATFORM%",
+ "token": "%ANALYTICS_TOKEN%"
},
"hidden_support_sections": "%HIDDEN_SUPPORT_SECTIONS%",
"hidden_result_tabs": "%HIDDEN_RESULT_TABS%",
@@ -52,10 +51,12 @@ echo "$template" | sed \
-e "s/%BRAND%/$brand_name/" \
-e "s/%HIDDEN_SUPPORT_SECTIONS%/$hidden_support_sections/" \
-e "s/%HIDDEN_RESULT_TABS%/$hidden_result_tabs/" \
- -e "s/%ANALYTICS%/$analytics/" \
-e "s/%TRANQL_URL%/$tranql_url/" \
-e "s/%DEPLOYMENT_NAMESPACE%/$deployment_namespace/" \
-e "s/%APPSTORE_ASSET_BRANCH%/$appstore_asset_branch/" \
+ -e "s/%ANALYTICS_ENABLED%/$analytics_enabled/" \
+ -e "s/%ANALYTICS_PLATFORM%/$analytics_platform/" \
+ -e "s/%ANALYTICS_TOKEN%/$analytics_token/" \
-e "s/%META_TITLE%/$meta_title/" \
-e "s/%META_DESCRIPTION%/$meta_description/" \
> $1
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 536e8705..a77ad695 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,7 +7,7 @@
"name": "helx-ui",
"dependencies": {
"@ant-design/plots": "^1.0.9",
- "@ant-design/pro-form": "^1.65.0",
+ "@ant-design/pro-form": "^1.74.7",
"@antv/g2plot": "^2.4.8",
"@gatsbyjs/reach-router": "^1.3.9",
"@testing-library/jest-dom": "^5.11.4",
@@ -24,7 +24,7 @@
"elasticlunr": "^0.9.5",
"es6-error": "^4.1.1",
"exponential-backoff": "^3.1.0",
- "helx-analytics": "^1.0.14",
+ "helx-analytics": "^2.0.0",
"js-cookie": "^3.0.1",
"lunr": "^2.3.9",
"nanoevents": "^6.0.2",
@@ -78,7 +78,7 @@
"type-fest": "^3.8.0",
"typescript": "^4.7.4",
"webpack": "^5.80.0",
- "webpack-cli": "^5.0.1",
+ "webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.13.3",
"webpack-merge": "^5.8.0"
}
@@ -8243,15 +8243,14 @@
}
},
"node_modules/helx-analytics": {
- "version": "1.0.14",
- "resolved": "https://registry.npmjs.org/helx-analytics/-/helx-analytics-1.0.14.tgz",
- "integrity": "sha512-TKjgPEFsm4y7ELsv3Ze1JIj/WcTRXfOHDmlyw/Kg71BNVnw5TqVqry/+A0vJZCAOrdkep/y4bKfNCIg1xILDhg==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/helx-analytics/-/helx-analytics-2.0.0.tgz",
+ "integrity": "sha512-9lY6/+e78Zsiwpw9VcCvRNQt7wJZC2N7z2EAF1TKX8dXwWeAEo1mIy92kvHGi5MfRob8o9tj3w2jTXNwPbCDOQ==",
"dependencies": {
"@babel/plugin-proposal-class-properties": "^7.14.5",
"@types/mixpanel-browser": "^2.35.7",
"mixpanel-browser": "^2.41.0",
- "react": "^17.0.2",
- "react-ga": "^3.3.0"
+ "react-ga4": "^2.1.0"
}
},
"node_modules/highlight-words-core": {
@@ -12852,14 +12851,10 @@
"react": "17.0.2"
}
},
- "node_modules/react-ga": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/react-ga/-/react-ga-3.3.1.tgz",
- "integrity": "sha512-4Vc0W5EvXAXUN/wWyxvsAKDLLgtJ3oLmhYYssx+YzphJpejtOst6cbIHCIyF50Fdxuf5DDKqRYny24yJ2y7GFQ==",
- "peerDependencies": {
- "prop-types": "^15.6.0",
- "react": "^15.6.2 || ^16.0 || ^17 || ^18"
- }
+ "node_modules/react-ga4": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/react-ga4/-/react-ga4-2.1.0.tgz",
+ "integrity": "sha512-ZKS7PGNFqqMd3PJ6+C2Jtz/o1iU9ggiy8Y8nUeksgVuvNISbmrQtJiZNvC/TjDsqD0QlU5Wkgs7i+w9+OjHhhQ=="
},
"node_modules/react-highlight-words": {
"version": "0.18.0",
diff --git a/package.json b/package.json
index 984865c6..6903c593 100644
--- a/package.json
+++ b/package.json
@@ -21,7 +21,7 @@
"elasticlunr": "^0.9.5",
"es6-error": "^4.1.1",
"exponential-backoff": "^3.1.0",
- "helx-analytics": "^1.0.14",
+ "helx-analytics": "^2.0.0",
"js-cookie": "^3.0.1",
"lunr": "^2.3.9",
"nanoevents": "^6.0.2",
diff --git a/src/app.js b/src/app.js
index 570595f9..4c5aea6a 100644
--- a/src/app.js
+++ b/src/app.js
@@ -12,15 +12,15 @@ const ContextProviders = ({ children }) => {
-
-
+
+
{children}
-
-
+
+
diff --git a/src/components/layout/layout.js b/src/components/layout/layout.js
index 59e475a0..330d15c0 100644
--- a/src/components/layout/layout.js
+++ b/src/components/layout/layout.js
@@ -24,12 +24,11 @@ export const Layout = ({ children }) => {
const logout = async () => {
setLoggingOut(true)
- analyticsEvents.logout()
+ analyticsEvents.workspacesLogout()
try {
await api.logout()
} catch (e) {}
setLoggingOut(false)
- // logoutHandler(helxAppstoreUrl)
}
const removeTrailingSlash = (url) => url.endsWith("/") ? url.slice(0, url.length - 1) : url
const activeRoutes = routes.filter((route) => (
@@ -97,11 +96,10 @@ export const Layout = ({ children }) => {
context?.brand === 'heal'
?
:
}
-
)
}
\ No newline at end of file
diff --git a/src/components/layout/menu/mobile-menu.js b/src/components/layout/menu/mobile-menu.js
index 65f51369..bb3aee39 100644
--- a/src/components/layout/menu/mobile-menu.js
+++ b/src/components/layout/menu/mobile-menu.js
@@ -1,23 +1,28 @@
import React, { useState } from 'react'
import { Button, Drawer, Menu } from 'antd'
import { MenuOutlined } from '@ant-design/icons'
-import { logoutHandler } from '../../../api/'
import { Link } from '@gatsbyjs/reach-router'
import { useEnvironment } from '../../../contexts/environment-context'
-import { useAnalytics } from '../../../contexts'
+import { useAnalytics, useWorkspacesAPI } from '../../../contexts'
import '../layout.css'
export const MobileMenu = ({menu}) => {
const [visible, setVisible] = useState(false);
- const { helxAppstoreUrl, context } = useEnvironment();
+ const [loggingOut, setLoggingOut] = useState(false)
+ const { context } = useEnvironment();
+ const { api, loggedIn, loading: apiLoading } = useWorkspacesAPI()
const { analyticsEvents } = useAnalytics();
const baseLinkPath = context.workspaces_enabled === 'true' ? '/helx' : ''
- const logout = () => {
- analyticsEvents.logout();
- logoutHandler(helxAppstoreUrl);
- }
+ const logout = async () => {
+ setLoggingOut(true)
+ analyticsEvents.workspacesLogout()
+ try {
+ await api.logout()
+ } catch (e) {}
+ setLoggingOut(false)
+ }
return (
@@ -34,8 +39,13 @@ export const MobileMenu = ({menu}) => {
visible={visible}
>
diff --git a/src/components/search/concept-card/studies-tab.js b/src/components/search/concept-card/studies-tab.js
index bef27899..4c09ec89 100644
--- a/src/components/search/concept-card/studies-tab.js
+++ b/src/components/search/concept-card/studies-tab.js
@@ -1,11 +1,13 @@
import { Fragment, useCallback, useEffect, useMemo, useState } from 'react'
import { List, Spin, Space, Tag, Typography, Divider } from 'antd'
+import { useAnalytics } from '../../../contexts'
import { Link } from '../../link'
const { Text } = Typography
const { CheckableTag: CheckableFacet } = Tag
export const StudiesTab = ({ studies }) => {
+ const { analyticsEvents } = useAnalytics()
const [facets, setFacets] = useState([])
const [selectedFacets, setSelectedFacets] = useState([])
@@ -28,6 +30,10 @@ export const StudiesTab = ({ studies }) => {
setSelectedFacets([...newSelection])
}, [selectedFacets])
+ const studyLinkClicked = (studyId) => {
+ analyticsEvents.studyLinkClicked(studyId)
+ }
+
useEffect(() => {
const facets = (studies ?? []).reduce((acc, study) => {
if (!acc.includes(study.type)) acc.push(study.type)
@@ -93,7 +99,7 @@ export const StudiesTab = ({ studies }) => {
{ (study.c_id && study.c_link) && (
{ ` ` }
- ({ study.c_id })
+ ( studyLinkClicked(study.c_id) }>{ study.c_id })
)}
diff --git a/src/components/search/concept-modal/concept-modal.js b/src/components/search/concept-modal/concept-modal.js
index 5b58a108..c9e50e35 100644
--- a/src/components/search/concept-modal/concept-modal.js
+++ b/src/components/search/concept-modal/concept-modal.js
@@ -1,4 +1,4 @@
-import { useEffect, useRef, useState } from 'react'
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Button, Menu, Modal, Result, Space, Spin, Typography, Tooltip, Divider } from 'antd'
import CustomIcon, {
InfoCircleOutlined as OverviewIcon,
@@ -113,19 +113,21 @@ export const ConceptModalBody = ({ result }) => {
delete links[tab]
})
- const setCurrentTab = (() => {
+ const setCurrentTab = useCallback((() => {
let oldTime = Date.now();
return (tabName) => {
const newTime = Date.now();
const elapsed = newTime - oldTime;
- if (tabName !== currentTab) {
- // Make sure we only track events when the tab actually changes.
- analyticsEvents.resultTabSelected(tabs[tabName].title, tabs[currentTab].title, elapsed)
- }
+ _setCurrentTab((currentTab) => {
+ if (tabName !== currentTab) {
+ // Make sure we only track events when the tab actually changes.
+ analyticsEvents.resultTabSelected(tabs[tabName].title, tabs[currentTab].title, elapsed)
+ }
+ return tabName
+ })
oldTime = newTime;
- _setCurrentTab(tabName);
}
- })()
+ })(), [tabs])
useEffect(() => {
setCurrentTab('overview')
diff --git a/src/components/search/concept-modal/tabs/cdes/cde-item.js b/src/components/search/concept-modal/tabs/cdes/cde-item.js
index 13d96cbe..ee7c2719 100644
--- a/src/components/search/concept-modal/tabs/cdes/cde-item.js
+++ b/src/components/search/concept-modal/tabs/cdes/cde-item.js
@@ -4,6 +4,7 @@ import { ExportOutlined } from '@ant-design/icons'
import _Highlighter from 'react-highlight-words'
import { RelatedConceptsList } from './related-concepts'
import { SideCollapse } from '../../../..'
+import { useAnalytics } from '../../../../../contexts'
const { Text, Link } = Typography
const { Panel } = Collapse
@@ -20,6 +21,8 @@ const Section = ({ title, children }) => (
export const CdeItem = ({ cde, cdeRelatedConcepts, highlight }) => {
const [collapsed, setCollapsed] = useState(false)
+
+ const { analyticsEvents } = useAnalytics()
const relatedConceptsSource = useMemo(() => (
cdeRelatedConcepts[cde.id]
@@ -36,7 +39,10 @@ export const CdeItem = ({ cde, cdeRelatedConcepts, highlight }) => {
>
{
+ analyticsEvents.cdeToggled(cde.id, !collapsed)
+ setCollapsed(collapsed)
+ } }
header={
@@ -75,6 +81,9 @@ export const CdeItem = ({ cde, cdeRelatedConcepts, highlight }) => {
{
+ analyticsEvents.cdeRelatedConceptsToggled(cde.id, expanded)
+ } }
/>
{false &&
diff --git a/src/components/search/concept-modal/tabs/cdes/related-concepts/related-concept-options.js b/src/components/search/concept-modal/tabs/cdes/related-concepts/related-concept-options.js
index 2b2792da..ee06e8d4 100644
--- a/src/components/search/concept-modal/tabs/cdes/related-concepts/related-concept-options.js
+++ b/src/components/search/concept-modal/tabs/cdes/related-concepts/related-concept-options.js
@@ -1,9 +1,11 @@
import { Menu, Typography } from 'antd'
import { ExportOutlined, SearchOutlined } from '@ant-design/icons'
+import { useAnalytics } from '../../../../../../contexts'
const { Text } = Typography
export const RelatedConceptOptions = ({ concept, openSearch, openConcept, setClosed }) => {
+ const { analyticsEvents } = useAnalytics()
return (
),
icon: ,
- onClick: openConcept,
+ onClick: () => {
+ analyticsEvents.cdeRelatedConceptOpened(concept.id)
+ openConcept()
+ },
},
{
key: 1,
@@ -25,7 +30,10 @@ export const RelatedConceptOptions = ({ concept, openSearch, openConcept, setClo
Search
),
icon: ,
- onClick: openSearch
+ onClick: () => {
+ analyticsEvents.cdeRelatedConceptSearched(concept.id)
+ openSearch()
+ }
},
]}
/>
diff --git a/src/components/search/concept-modal/tabs/cdes/related-concepts/related-concept-tag.css b/src/components/search/concept-modal/tabs/cdes/related-concepts/related-concept-tag.css
index 9d014223..92f0a1a1 100644
--- a/src/components/search/concept-modal/tabs/cdes/related-concepts/related-concept-tag.css
+++ b/src/components/search/concept-modal/tabs/cdes/related-concepts/related-concept-tag.css
@@ -4,6 +4,7 @@
}
.related-concept-popover-overlay .ant-popover-inner-content {
padding: 0;
+ width: 100%;
}
.related-concept-popover-overlay .ant-popover-title {
padding: 8px 12px;
diff --git a/src/components/search/concept-modal/tabs/cdes/related-concepts/related-concepts-list.js b/src/components/search/concept-modal/tabs/cdes/related-concepts/related-concepts-list.js
index f3ea72df..f63418e8 100644
--- a/src/components/search/concept-modal/tabs/cdes/related-concepts/related-concepts-list.js
+++ b/src/components/search/concept-modal/tabs/cdes/related-concepts/related-concepts-list.js
@@ -7,7 +7,7 @@ const { Text } = Typography
const SHOW_COUNT = 8
-export const RelatedConceptsList = ({ concepts, highlight }) => {
+export const RelatedConceptsList = ({ concepts, highlight, onShowMore }) => {
const [showingAll, setShowingAll] = useState(false)
const failed = useMemo(() => concepts === null || concepts.length === 0, [concepts])
@@ -56,8 +56,10 @@ export const RelatedConceptsList = ({ concepts, highlight }) => {
size="small"
type="link"
style={{ fontSize: "12px" }}
- onClick={ () => setShowingAll(!showingAll)
- }
+ onClick={ () => {
+ onShowMore(!showingAll)
+ setShowingAll(!showingAll)
+ } }
>
{ !showingAll ? `Show ${hiddenConcepts.length - hiddenHighlighted.length} more results` : "Show less" }
diff --git a/src/components/search/concept-modal/tabs/studies/study-variable.js b/src/components/search/concept-modal/tabs/studies/study-variable.js
index c9ddeee3..9620de5c 100644
--- a/src/components/search/concept-modal/tabs/studies/study-variable.js
+++ b/src/components/search/concept-modal/tabs/studies/study-variable.js
@@ -1,16 +1,24 @@
import { Typography } from 'antd'
import Highlighter from 'react-highlight-words'
+import { useAnalytics } from '../../../../../contexts'
const { Text } = Typography
-export const StudyVariable = ({ variable, highlight, ...props }) => (
+export const StudyVariable = ({ variable, highlight, ...props }) => {
+ const { analyticsEvents } = useAnalytics()
+
+ const variableLinkClicked = () => {
+ analyticsEvents.variableLinkClicked(variable.id)
+ }
+ return (
- )
\ No newline at end of file
+ )
+}
\ No newline at end of file
diff --git a/src/components/search/concept-modal/tabs/studies/study.js b/src/components/search/concept-modal/tabs/studies/study.js
index c3fb3d53..b99ce8a6 100644
--- a/src/components/search/concept-modal/tabs/studies/study.js
+++ b/src/components/search/concept-modal/tabs/studies/study.js
@@ -4,11 +4,16 @@ import Highlighter from 'react-highlight-words'
import { Link } from '../../../../link'
import { StudyVariables } from './study-variables'
import { StudyVariable } from './study-variable'
+import { useAnalytics } from '../../../../../contexts'
const { Text, Paragraph } = Typography
const { Panel } = Collapse
export const Study = ({ study, highlight, collapsed, ...panelProps }) => {
+ const { analyticsEvents } = useAnalytics()
+ const studyLinkClicked = () => {
+ analyticsEvents.studyLinkClicked(study.c_id)
+ }
const highlightedVariables = study.elements.filter((variable) => {
return highlight.some((token) => (
variable.name.includes(token) ||
@@ -21,7 +26,7 @@ export const Study = ({ study, highlight, collapsed, ...panelProps }) => {
header={
{ ` ` }
- ({ study.c_id })
+ ({ study.c_id })
}
extra={ { study.elements.length } variable{ study.elements.length === 1 ? '' : 's' } }
diff --git a/src/components/search/context.js b/src/components/search/context.js
index a388101e..8d1292ab 100644
--- a/src/components/search/context.js
+++ b/src/components/search/context.js
@@ -264,7 +264,7 @@ export const HelxSearch = ({ children }) => {
setTotalConcepts(result.total_items)
// setConcepts(hits.valid)
setIsLoadingConcepts(false)
- analyticsEvents.searchExecuted(query, Date.now() - startTime, result.total_items)
+ // analyticsEvents.searchExecuted(query, Date.now() - startTime, result.total_items)
} else {
const newConceptPages = { ...conceptPages }
newConceptPages[currentPage] = []
@@ -272,7 +272,7 @@ export const HelxSearch = ({ children }) => {
setConceptTypes({})
setTotalConcepts(0)
setIsLoadingConcepts(false)
- analyticsEvents.searchExecuted(query, Date.now() - startTime, 0)
+ // analyticsEvents.searchExecuted(query, Date.now() - startTime, 0)
}
} catch (error) {
if (error.name !== "CanceledError") {
@@ -280,7 +280,7 @@ export const HelxSearch = ({ children }) => {
setError({ message: 'An error occurred!' })
setTotalConcepts(0)
setIsLoadingConcepts(false)
- analyticsEvents.searchExecuted(query, Date.now() - startTime, 0, error)
+ // analyticsEvents.searchExecuted(query, Date.now() - startTime, 0, error)
}
}
}
diff --git a/src/components/search/form/form.js b/src/components/search/form/form.js
index 154091b6..7ec79e65 100644
--- a/src/components/search/form/form.js
+++ b/src/components/search/form/form.js
@@ -6,7 +6,7 @@ import Highlighter from 'react-highlight-words'
import Select from 'rc-select'
import { useHelxSearch, SearchLayout } from '../'
import { SearchCompletion } from './search-suggestion'
-import { useEnvironment } from '../../../contexts'
+import { useAnalytics, useEnvironment } from '../../../contexts'
import { useAbortController } from '../../../hooks'
import './form.css'
@@ -49,6 +49,7 @@ const highlightedSearchTerms = (highlightedSearch) => {
export const SearchForm = ({ type=undefined, ...props }) => {
const { context } = useEnvironment()
const { doSearch, inputRef, query, totalConcepts, searchHistory, layout, setLayout } = useHelxSearch()
+ const { analyticsEvents } = useAnalytics()
const [searchTerm, setSearchTerm] = useState(query)
const [searchSuggestions, setSearchSuggestions] = useState(null) // null = don't appear, [] = "no results found", [...] = show suggestions
const [loadingSuggestions, setLoadingSuggestions] = useState(false) // true = loading...
@@ -264,6 +265,7 @@ export const SearchForm = ({ type=undefined, ...props }) => {
) : null
}
onChange={ (e) => {
+ analyticsEvents.searchTypeChanged(e.target.value)
switch (e.target.value) {
case "concepts":
setLayout(SearchLayout.GRID)
diff --git a/src/components/search/results/results-header/index.js b/src/components/search/results/results-header/index.js
index a1e41d40..2b8a97f3 100644
--- a/src/components/search/results/results-header/index.js
+++ b/src/components/search/results/results-header/index.js
@@ -76,7 +76,10 @@ export const ResultsHeader = ({ variables=false, type=FULL, ...props }) => {
Filter type: