Skip to content

Commit 15dae6a

Browse files
LoremIPsummermozesl
authored andcommitted
Google analytics integration in the new webgui (Ericsson#642)
1 parent b146272 commit 15dae6a

File tree

14 files changed

+316
-4
lines changed

14 files changed

+316
-4
lines changed

webgui-new/package-lock.json

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webgui-new/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"react": "^18.2.0",
4646
"react-diff-viewer-continued": "^3.2.6",
4747
"react-dom": "^18.2.0",
48+
"react-ga4": "^2.1.0",
4849
"react-i18next": "^13.0.1",
4950
"react-icons": "^4.8.0",
5051
"react-toastify": "^9.1.2",

webgui-new/src/components/codebites/codebites-node.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { IconButton, Tooltip } from '@mui/material';
1818
import { Close } from '@mui/icons-material';
1919
import { AppContext } from 'global-context/app-context';
2020
import dagre from 'dagre';
21+
import { sendGAEvent } from 'utils/analytics';
2122

2223
type CodeBitesElement = {
2324
astNodeInfo: AstNodeInfo;
@@ -94,6 +95,7 @@ class CustomOffsetGutterMarker extends GutterMarker {
9495

9596
export const CodeBitesNode = ({ data }: NodeProps<DataProps>): JSX.Element => {
9697
const { diagramGenId: initialNodeId } = useContext(AppContext);
98+
const appCtx = useContext(AppContext);
9799
const { theme } = useContext(ThemeContext);
98100

99101
const [fileInfo, setFileInfo] = useState<FileInfo | undefined>(undefined);
@@ -108,11 +110,16 @@ export const CodeBitesNode = ({ data }: NodeProps<DataProps>): JSX.Element => {
108110
const init = async () => {
109111
const initFileInfo = await getFileInfo(data.astNodeInfo.range?.file as string);
110112
const initText = await getCppSourceText(data.astNodeInfo.id as string);
113+
sendGAEvent({
114+
event_action: 'code_bites',
115+
event_category: appCtx.workspaceId,
116+
event_label: `${initFileInfo?.name}: ${initText}`,
117+
});
111118
setFileInfo(initFileInfo);
112119
setText(initText);
113120
};
114121
init();
115-
}, [data.astNodeInfo]);
122+
}, [appCtx.workspaceId, data.astNodeInfo]);
116123

117124
const handleClick = async () => {
118125
if (!editorRef.current) return;

webgui-new/src/components/codemirror-editor/codemirror-editor.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { RouterQueryType } from 'utils/types';
1818
import { Tooltip, alpha } from '@mui/material';
1919
import * as SC from './styled-components';
2020
import { useTranslation } from 'react-i18next';
21+
import { sendGAEvent } from 'utils/analytics';
2122

2223
export const CodeMirrorEditor = (): JSX.Element => {
2324
const { t } = useTranslation();
@@ -111,6 +112,11 @@ export const CodeMirrorEditor = (): JSX.Element => {
111112
? null
112113
: await getCppAstNodeInfoByPosition(fileInfo?.id as string, line.number, column);
113114
if (astNodeInfo) {
115+
sendGAEvent({
116+
event_action: 'click_on_word',
117+
event_category: appCtx.workspaceId,
118+
event_label: `${fileInfo?.name}: ${astNodeInfo.astNodeValue}`,
119+
});
114120
dispatchSelection(astNodeInfo?.range?.range as Range);
115121
router.push({
116122
pathname: '/project',
@@ -132,6 +138,11 @@ export const CodeMirrorEditor = (): JSX.Element => {
132138
column: line.length + 1,
133139
}),
134140
});
141+
sendGAEvent({
142+
event_action: 'click_on_word',
143+
event_category: appCtx.workspaceId,
144+
event_label: `${fileInfo?.name}: ${convertSelectionRangeToString(range)}`,
145+
});
135146
dispatchSelection(range);
136147
router.push({
137148
pathname: '/project',
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import React, { useEffect, useState } from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
import { getStore, setStore } from 'utils/store';
4+
import ReactGA from 'react-ga4';
5+
import {
6+
Paper,
7+
Typography,
8+
Button,
9+
IconButton,
10+
Snackbar,
11+
Dialog,
12+
DialogTitle,
13+
DialogContent,
14+
DialogActions,
15+
TableContainer,
16+
TableRow,
17+
TableCell,
18+
Table,
19+
TableHead,
20+
TableBody,
21+
Link,
22+
} from '@mui/material';
23+
import CloseIcon from '@mui/icons-material/Close';
24+
import { useRouter } from 'next/router';
25+
26+
export const CookieNotice = (): JSX.Element => {
27+
const { t } = useTranslation();
28+
const router = useRouter();
29+
const [isCookieConsent, setIsCookieConsent] = useState<boolean | undefined>(undefined);
30+
const [gaTrackingCode, setGaTrackingCode] = useState<string | undefined>(undefined);
31+
const [openPolicyModal, setOpenPolicyModal] = useState(false);
32+
const [showNoticeSnackbar, setShowNoticeSnackbar] = useState(true);
33+
34+
useEffect(() => {
35+
const fetchGaTrackingCode = async () => {
36+
try {
37+
const res = await fetch(`/ga.txt`);
38+
if (res.status !== 200) {
39+
setGaTrackingCode(undefined);
40+
return;
41+
}
42+
const gaCode = await res.text();
43+
setGaTrackingCode(gaCode);
44+
} catch (e) {
45+
// network-related error
46+
setGaTrackingCode(undefined);
47+
}
48+
const store = getStore();
49+
setIsCookieConsent(store.storedCookieConsent);
50+
};
51+
fetchGaTrackingCode();
52+
}, []);
53+
54+
useEffect(() => {
55+
if (!isCookieConsent || !gaTrackingCode) {
56+
ReactGA.reset();
57+
return;
58+
}
59+
if (!ReactGA.isInitialized) {
60+
ReactGA.initialize(gaTrackingCode);
61+
console.log(`Google Analytics initialized - ${gaTrackingCode}`);
62+
}
63+
64+
const handleRouteChange = (url: string) => {
65+
ReactGA.send({ hitType: 'pageview', page: url, title: window.document.title });
66+
};
67+
router.events.on('routeChangeComplete', handleRouteChange);
68+
69+
return () => {
70+
router.events.off('routeChangeComplete', handleRouteChange);
71+
};
72+
}, [isCookieConsent, gaTrackingCode, router.events]);
73+
74+
const handleCookieAccept = () => {
75+
setIsCookieConsent(true);
76+
setShowNoticeSnackbar(false);
77+
setStore({ storedCookieConsent: true });
78+
};
79+
80+
if (!isCookieConsent && gaTrackingCode)
81+
return (
82+
<>
83+
<Snackbar
84+
open={showNoticeSnackbar}
85+
autoHideDuration={null}
86+
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
87+
>
88+
<Paper
89+
sx={{
90+
p: 2,
91+
display: 'flex',
92+
alignItems: 'center',
93+
justifyContent: 'space-between',
94+
}}
95+
>
96+
<Typography variant="body2">
97+
{t('cookie.INTRO_TEXT')}
98+
<Button color="primary" size="small" onClick={() => setOpenPolicyModal(true)}>
99+
{t('cookie.LEARN_MORE')}
100+
</Button>
101+
</Typography>
102+
<Button
103+
color="primary"
104+
size="small"
105+
variant="contained"
106+
onClick={() => {
107+
handleCookieAccept();
108+
}}
109+
>
110+
{t('cookie.ACCEPT')}
111+
</Button>
112+
<IconButton
113+
size="small"
114+
color="inherit"
115+
sx={{ ml: 2 }}
116+
onClick={() => setShowNoticeSnackbar(false)}
117+
>
118+
<CloseIcon fontSize="small" />
119+
</IconButton>
120+
</Paper>
121+
</Snackbar>
122+
<Dialog open={openPolicyModal} maxWidth="lg" onClose={() => setOpenPolicyModal(false)}>
123+
<DialogTitle>{t('cookiePolicy.TITLE')}</DialogTitle>
124+
<DialogContent>
125+
<>
126+
<Typography sx={{ marginBottom: '0.8rem' }} variant="body1">
127+
{t('cookiePolicy.sections.cookies.WHAT_COOKIES')}
128+
</Typography>
129+
<Typography sx={{ marginBottom: '0.8rem' }} variant="body2">
130+
{t('cookiePolicy.sections.cookies.WHAT_COOKIES_DESCRIPTION')}
131+
</Typography>
132+
<Typography sx={{ marginBottom: '0.8rem' }} variant="body1">
133+
{t('cookiePolicy.sections.cookies.HOW_USE_TITLE')}
134+
</Typography>
135+
<TableContainer component={Paper} sx={{ marginBottom: '0.8rem' }}>
136+
<Table sx={{ minWidth: 650 }} aria-label="cookie-table">
137+
<TableHead>
138+
<TableRow>
139+
<TableCell>{t('cookiePolicy.sections.cookies.SERVICE')}</TableCell>
140+
<TableCell>{t('cookiePolicy.sections.cookies.COOKIE_NAMES')}</TableCell>
141+
<TableCell>{t('cookiePolicy.sections.cookies.DURATION')}</TableCell>
142+
<TableCell>{t('cookiePolicy.sections.cookies.PURPOSE')}</TableCell>
143+
<TableCell>{t('cookiePolicy.sections.cookies.MORE_INFORMATION')}</TableCell>
144+
</TableRow>
145+
</TableHead>
146+
<TableBody>
147+
<TableRow key={1} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
148+
<TableCell component="th" scope="row">
149+
<b>{t('cookiePolicy.sections.cookies.googleAnalytics.SERVICE')}</b>
150+
</TableCell>
151+
<TableCell>
152+
<b>{t('cookiePolicy.sections.cookies.googleAnalytics.COOKIE_NAMES')}</b>
153+
</TableCell>
154+
<TableCell>
155+
<b>{t('cookiePolicy.sections.cookies.googleAnalytics.DURATION')}</b>
156+
</TableCell>
157+
<TableCell>
158+
<b>{t('cookiePolicy.sections.cookies.googleAnalytics.PURPOSE')}</b>
159+
</TableCell>
160+
<TableCell>
161+
<Link
162+
target="_blank"
163+
rel="noreferrer"
164+
href={t('cookiePolicy.sections.cookies.googleAnalytics.MORE_INFORMATION')}
165+
>
166+
<b>{t('cookiePolicy.sections.cookies.MORE_INFORMATION')}</b>
167+
</Link>
168+
</TableCell>
169+
</TableRow>
170+
</TableBody>
171+
</Table>
172+
</TableContainer>
173+
<Typography variant="body2">
174+
{t('cookiePolicy.sections.cookies.CONCLUSION')}
175+
</Typography>
176+
</>
177+
</DialogContent>
178+
<DialogActions>
179+
<Button onClick={() => setOpenPolicyModal(false)} color="primary"></Button>
180+
</DialogActions>
181+
</Dialog>
182+
</>
183+
);
184+
185+
return <></>;
186+
};

webgui-new/src/components/diagrams/diagrams.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { convertSelectionRangeToString } from 'utils/utils';
2121
import { useRouter } from 'next/router';
2222
import { RouterQueryType } from 'utils/types';
2323
import { useTranslation } from 'react-i18next';
24+
import { sendGAEvent } from 'utils/analytics';
2425

2526
export const Diagrams = (): JSX.Element => {
2627
const { t } = useTranslation();
@@ -67,7 +68,18 @@ export const Diagrams = (): JSX.Element => {
6768
: initDiagramInfo instanceof AstNodeInfo
6869
? await getCppDiagram(appCtx.diagramGenId, parseInt(appCtx.diagramTypeId))
6970
: '';
70-
71+
72+
sendGAEvent({
73+
event_action: `load_diagram: ${appCtx.diagramTypeId}`,
74+
event_category: appCtx.workspaceId,
75+
event_label:
76+
initDiagramInfo instanceof FileInfo
77+
? initDiagramInfo.name
78+
: initDiagramInfo instanceof AstNodeInfo
79+
? initDiagramInfo.astNodeValue
80+
: '',
81+
});
82+
7183
const parser = new DOMParser();
7284
const parsedDiagram = parser.parseFromString(diagram, 'text/xml');
7385
const diagramSvg = parsedDiagram.getElementsByTagName('svg')[0];
@@ -81,7 +93,7 @@ export const Diagrams = (): JSX.Element => {
8193
setDiagramInfo(initDiagramInfo);
8294
};
8395
init();
84-
}, [appCtx.diagramGenId, appCtx.diagramTypeId, appCtx.diagramType]);
96+
}, [appCtx.diagramGenId, appCtx.diagramTypeId, appCtx.diagramType, appCtx.workspaceId]);
8597

8698
const generateDiagram = async (e: MouseEvent) => {
8799
const parentNode = (e.target as HTMLElement)?.parentElement;

webgui-new/src/components/editor-context-menu/editor-context-menu.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { useRouter } from 'next/router';
2121
import { RouterQueryType } from 'utils/types';
2222
import { useTranslation } from 'react-i18next';
2323
import { diagramTypeArray } from 'enums/entity-types';
24+
import { sendGAEvent } from 'utils/analytics';
2425

2526
export const EditorContextMenu = ({
2627
contextMenu,
@@ -69,8 +70,14 @@ export const EditorContextMenu = ({
6970

7071
const getDocs = async () => {
7172
const initDocs = await getCppDocumentation(astNodeInfo?.id as string);
73+
const fileInfo = await getFileInfo(appCtx.projectFileId as string);
7274
const parser = new DOMParser();
7375
const parsedHTML = parser.parseFromString(initDocs, 'text/html');
76+
sendGAEvent({
77+
event_action: 'documentation',
78+
event_category: appCtx.workspaceId,
79+
event_label: `${fileInfo?.name}: ${astNodeInfo?.astNodeValue}`,
80+
});
7481
setModalOpen(true);
7582
setContextMenu(null);
7683
if (!docsContainerRef.current) return;
@@ -87,6 +94,12 @@ export const EditorContextMenu = ({
8794
const initAstHTML = await getAsHTMLForNode(astNodeInfo?.id as string);
8895
const parser = new DOMParser();
8996
const parsedHTML = parser.parseFromString(initAstHTML, 'text/html');
97+
const fileInfo = await getFileInfo(appCtx.projectFileId as string);
98+
sendGAEvent({
99+
event_action: 'cpp_reparse_node',
100+
event_category: appCtx.workspaceId,
101+
event_label: `${fileInfo?.name}: ${astNodeInfo?.astNodeValue}`,
102+
});
90103
setModalOpen(true);
91104
setContextMenu(null);
92105
if (!astHTMLContainerRef.current) return;
@@ -115,6 +128,12 @@ export const EditorContextMenu = ({
115128
}
116129

117130
const fileId = def.range?.file as string;
131+
const fileInfo = await getFileInfo(appCtx.projectFileId as string);
132+
sendGAEvent({
133+
event_action: 'jump_to_def',
134+
event_category: appCtx.workspaceId,
135+
event_label: `${fileInfo?.name}: ${astNodeInfo.astNodeValue}`,
136+
});
118137

119138
router.push({
120139
pathname: '/project',
@@ -145,6 +164,11 @@ export const EditorContextMenu = ({
145164
currentRepo?.repoPath as string,
146165
fileInfo?.id as string
147166
);
167+
sendGAEvent({
168+
event_action: 'git_blame',
169+
event_category: appCtx.workspaceId,
170+
event_label: fileInfo?.name,
171+
});
148172
return blameInfo;
149173
};
150174

webgui-new/src/components/file-context-menu/file-context-menu.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { useRouter } from 'next/router';
1212
import { RouterQueryType } from 'utils/types';
1313
import { useTranslation } from 'react-i18next';
1414
import { diagramTypeArray } from 'enums/entity-types';
15+
import { sendGAEvent } from 'utils/analytics';
1516

1617
export const FileContextMenu = ({
1718
contextMenu,
@@ -67,6 +68,11 @@ export const FileContextMenu = ({
6768
{fileInfo && fileInfo.isDirectory && (
6869
<MenuItem
6970
onClick={async () => {
71+
sendGAEvent({
72+
event_action: 'metrics',
73+
event_category: appCtx.workspaceId,
74+
event_label: fileInfo.name,
75+
});
7076
setContextMenu(null);
7177
router.push({
7278
pathname: '/project',

0 commit comments

Comments
 (0)