Skip to content

Commit

Permalink
feat: remove ReactGA and migrate to GA4 for tracking (#2071)
Browse files Browse the repository at this point in the history
  • Loading branch information
EshaanAgg committed Dec 26, 2023
1 parent 65b01d4 commit cdd251b
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 113 deletions.
1 change: 0 additions & 1 deletion packages/jaeger-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@
"react": "^18.2.0",
"react-circular-progressbar": "^2.1.0",
"react-dom": "^18.2.0",
"react-ga": "^3.3.1",
"react-helmet": "^6.1.0",
"react-icons": "^4.10.1",
"react-is": "^18.2.0",
Expand Down
78 changes: 40 additions & 38 deletions packages/jaeger-ui/src/utils/tracking/ga.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import ReactGA from 'react-ga';
import * as GA from './ga';
import * as utils from './utils';
import { getVersionInfo, getAppEnvironment } from '../constants';
import { getAppEnvironment } from '../constants';

jest.mock('./conv-raven-to-ga', () => () => ({
category: 'jaeger/a',
Expand All @@ -35,7 +33,6 @@ function getStr(len) {
}

describe('google analytics tracking', () => {
let calls;
let tracking;

beforeAll(() => {
Expand All @@ -54,16 +51,18 @@ describe('google analytics tracking', () => {
});

beforeEach(() => {
getVersionInfo.mockReturnValue('{}');
calls = ReactGA.testModeAPI.calls;
calls.length = 0;
jest.useFakeTimers();
// Set an arbitrary date so that we can test the date-based dimension
jest.setSystemTime(new Date('2023-01-01'));
window.dataLayer = [];
});

describe('init', () => {
it('check init function (no cookies)', () => {
tracking.init();
expect(calls).toEqual([
['create', 'UA-123456', 'auto'],
expect(window.dataLayer).toEqual([
['js', new Date()],
['config', 'UA-123456'],
[
'set',
{
Expand All @@ -78,8 +77,9 @@ describe('google analytics tracking', () => {
it('check init function (no cookies)', () => {
document.cookie = 'page=1;';
tracking.init();
expect(calls).toEqual([
['create', 'UA-123456', 'auto'],
expect(window.dataLayer).toEqual([
['js', new Date()],
['config', 'UA-123456'],
[
'set',
{
Expand All @@ -96,33 +96,33 @@ describe('google analytics tracking', () => {
describe('trackPageView', () => {
it('tracks a page view', () => {
tracking.trackPageView('a', 'b');
expect(calls).toEqual([['send', { hitType: 'pageview', page: 'ab' }]]);
expect(window.dataLayer).toEqual([['event', 'page_view', { page_path: 'ab' }]]);
});

it('ignores search when it is falsy', () => {
tracking.trackPageView('a');
expect(calls).toEqual([['send', { hitType: 'pageview', page: 'a' }]]);
expect(window.dataLayer).toEqual([['event', 'page_view', { page_path: 'a' }]]);
});
});

describe('trackError', () => {
it('tracks an error', () => {
tracking.trackError('a');
expect(calls).toEqual([
['send', { hitType: 'exception', exDescription: expect.any(String), exFatal: false }],
expect(window.dataLayer).toEqual([
['event', 'exception', { description: expect.any(String), fatal: false }],
]);
});

it('ensures "jaeger" is prepended', () => {
tracking.trackError('a');
expect(calls).toEqual([['send', { hitType: 'exception', exDescription: 'jaeger/a', exFatal: false }]]);
expect(window.dataLayer).toEqual([['event', 'exception', { description: 'jaeger/a', fatal: false }]]);
});

it('truncates if needed', () => {
const str = `jaeger/${getStr(200)}`;
tracking.trackError(str);
expect(calls).toEqual([
['send', { hitType: 'exception', exDescription: str.slice(0, 149), exFatal: false }],
expect(window.dataLayer).toEqual([
['event', 'exception', { description: str.slice(0, 149), fatal: false }],
]);
});
});
Expand All @@ -132,13 +132,12 @@ describe('google analytics tracking', () => {
const category = 'jaeger/some-category';
const action = 'some-action';
tracking.trackEvent(category, action);
expect(calls).toEqual([
expect(window.dataLayer).toEqual([
[
'send',
'event',
'some-action',
{
hitType: 'event',
eventCategory: category,
eventAction: action,
event_category: category,
},
],
]);
Expand All @@ -148,33 +147,32 @@ describe('google analytics tracking', () => {
const category = 'some-category';
const action = 'some-action';
tracking.trackEvent(category, action);
expect(calls).toEqual([
['send', { hitType: 'event', eventCategory: `jaeger/${category}`, eventAction: action }],
]);
expect(window.dataLayer).toEqual([['event', 'some-action', { event_category: `jaeger/${category}` }]]);
});

it('truncates values, if needed', () => {
const str = `jaeger/${getStr(600)}`;
tracking.trackEvent(str, str, str);
expect(calls).toEqual([
expect(window.dataLayer).toEqual([
[
'send',
'event',
str.slice(0, 499),
{
hitType: 'event',
eventCategory: str.slice(0, 149),
eventAction: str.slice(0, 499),
eventLabel: str.slice(0, 499),
event_category: str.slice(0, 149),
event_label: str.slice(0, 499),
},
],
]);
});
});

it('converting raven-js errors', () => {
window.onunhandledrejection({ reason: new Error('abc') });
expect(calls).toEqual([
['send', { hitType: 'exception', exDescription: expect.any(String), exFatal: false }],
['send', { hitType: 'event', eventCategory: expect.any(String), eventAction: expect.any(String) }],
window.onunhandledrejection({
reason: new Error('abc'),
});
expect(window.dataLayer).toEqual([
['event', 'exception', { description: expect.any(String), fatal: false }],
['event', expect.any(String), { event_category: expect.any(String) }],
]);
});

Expand Down Expand Up @@ -206,14 +204,18 @@ describe('google analytics tracking', () => {
);
});

/* eslint-disable no-console */
it('isDebugMode = true', () => {
// eslint-disable-next-line no-import-assign
utils.logTrackingCalls = jest.fn();
console.log = jest.fn();

trackingDebug.init();
expect(console.log).toHaveBeenCalledTimes(4);

trackingDebug.trackError();
trackingDebug.trackEvent('jaeger/some-category', 'some-action');
trackingDebug.trackPageView('a', 'b');
expect(utils.logTrackingCalls).toHaveBeenCalledTimes(4);
expect(console.log).toHaveBeenCalledTimes(7);
});
});
});
77 changes: 56 additions & 21 deletions packages/jaeger-ui/src/utils/tracking/ga.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,25 @@
// limitations under the License.

import _get from 'lodash/get';
import ReactGA from 'react-ga';
import Raven, { RavenOptions, RavenTransportOptions } from 'raven-js';

import convRavenToGa from './conv-raven-to-ga';
import { TNil } from '../../types';
import { Config } from '../../types/config';
import { IWebAnalyticsFunc } from '../../types/tracking';
import { logTrackingCalls } from './utils';
import { getAppEnvironment, shouldDebugGoogleAnalytics } from '../constants';
import parseQuery from '../parseQuery';

// Modify the `window` object to have an additional attribute `dataLayer`
// This is required by the gtag.js script to work

// eslint-disable-next-line @typescript-eslint/naming-convention
interface WindowWithGATracking extends Window {
dataLayer: (string | object)[][] | undefined;
}

declare let window: WindowWithGATracking;

const isTruish = (value?: string | string[]) => {
return Boolean(value) && value !== '0' && value !== 'false';
};
Expand All @@ -50,16 +58,29 @@ const GA: IWebAnalyticsFunc = (config: Config, versionShort: string, versionLong
return isTest || isDebugMode || (isProd && Boolean(gaID));
};

// Function to add a new event to the Google Analytics dataLayer
const gtag = (...args: (string | object)[]) => {
if (window !== undefined)
if (window.dataLayer !== undefined && Array.isArray(window.dataLayer)) {
window.dataLayer.push(args);
if (isDebugMode) {
// eslint-disable-next-line no-console
console.log('[GA Tracking]', ...args);
}
}
};

const trackError = (description: string) => {
let msg = description;
if (!/^jaeger/i.test(msg)) {
msg = `jaeger/${msg}`;
}
msg = msg.slice(0, 149);
ReactGA.exception({ description: msg, fatal: false });
if (isDebugMode) {
logTrackingCalls();
}

gtag('event', 'exception', {
description: msg,
fatal: false,
});
};

const trackEvent = (
Expand Down Expand Up @@ -90,10 +111,12 @@ const GA: IWebAnalyticsFunc = (config: Config, versionShort: string, versionLong
if (value != null) {
event.value = Math.round(value);
}
ReactGA.event(event);
if (isDebugMode) {
logTrackingCalls();
}

gtag('event', event.action, {
event_category: event.category,
...(event.label && { event_label: event.label }),
...(event.value && { event_value: event.value }),
});
};

const trackRavenError = (ravenData: RavenTransportOptions) => {
Expand All @@ -107,18 +130,34 @@ const GA: IWebAnalyticsFunc = (config: Config, versionShort: string, versionLong
return;
}

const gaConfig = { testMode: isTest || isDebugMode, titleCase: false, debug: true };
ReactGA.initialize(gaID || 'debug-mode', gaConfig);
ReactGA.set({
const gtagUrl = 'https://www.googletagmanager.com/gtag/js';
const GA_MEASUREMENT_ID = gaID || 'debug-mode';

// Load the script asynchronously
const script = document.createElement('script');
script.async = true;
script.src = `${gtagUrl}?id=${GA_MEASUREMENT_ID}`;
document.body.appendChild(script);

// Initialize the dataLayer and send initial configuration data
window.dataLayer = window.dataLayer || [];
gtag('js', new Date());
gtag('config', GA_MEASUREMENT_ID);
gtag('set', {
appId: 'github.com/jaegertracing/jaeger-ui',
appName: 'Jaeger UI',
appVersion: versionLong,
});

if (cookiesToDimensions !== undefined) {
(cookiesToDimensions as unknown as Array<{ cookie: string; dimension: string }>).forEach(
({ cookie, dimension }: { cookie: string; dimension: string }) => {
const match = ` ${document.cookie}`.match(new RegExp(`[; ]${cookie}=([^\\s;]*)`));
if (match) ReactGA.set({ [dimension]: match[1] });
if (match) {
gtag('set', {
[dimension]: match[1],
});
}
// eslint-disable-next-line no-console
else console.warn(`${cookie} not present in cookies, could not set dimension: ${dimension}`);
}
Expand All @@ -145,17 +184,13 @@ const GA: IWebAnalyticsFunc = (config: Config, versionShort: string, versionLong
Raven.captureException(evt.reason);
};
}
if (isDebugMode) {
logTrackingCalls();
}
};

const trackPageView = (pathname: string, search: string | TNil) => {
const pagePath = search ? `${pathname}${search}` : pathname;
ReactGA.pageview(pagePath);
if (isDebugMode) {
logTrackingCalls();
}
gtag('event', 'page_view', {
page_path: pagePath,
});
};

return {
Expand Down
23 changes: 0 additions & 23 deletions packages/jaeger-ui/src/utils/tracking/utils.test.js

This file was deleted.

25 changes: 0 additions & 25 deletions packages/jaeger-ui/src/utils/tracking/utils.tsx

This file was deleted.

5 changes: 0 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8504,11 +8504,6 @@ react-fast-compare@^3.1.1:
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==

react-ga@^3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/react-ga/-/react-ga-3.3.1.tgz#d8e1f4e05ec55ed6ff944dcb14b99011dfaf9504"
integrity sha512-4Vc0W5EvXAXUN/wWyxvsAKDLLgtJ3oLmhYYssx+YzphJpejtOst6cbIHCIyF50Fdxuf5DDKqRYny24yJ2y7GFQ==

react-helmet@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-6.1.0.tgz#a750d5165cb13cf213e44747502652e794468726"
Expand Down

0 comments on commit cdd251b

Please sign in to comment.