Skip to content

Commit

Permalink
Update due to comments, refactor Google Analytic tracker
Browse files Browse the repository at this point in the history
Signed-off-by: Mykhailo Semenchenko <mykhailo.semenchenko@logz.io>
  • Loading branch information
th3M1ke committed Feb 11, 2021
1 parent 605f980 commit 3f71ca3
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 128 deletions.
5 changes: 5 additions & 0 deletions packages/jaeger-ui/src/types/tracking.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@

import { RavenStatic } from 'raven-js';
import { TNil } from '.';
import { Config } from './config';

export interface IWebAnalyticsFunc {
(config: Config, versionShort: string, versionLong: string): IWebAnalytics;
}

export default interface IWebAnalytics {
init: () => void;
Expand Down
225 changes: 108 additions & 117 deletions packages/jaeger-ui/src/utils/tracking/ga.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2021 Uber Technologies, Inc.
// Copyright (c) 2021 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -18,70 +18,111 @@ import ReactGA from 'react-ga';
import Raven, { RavenOptions, RavenTransportOptions } from 'raven-js';

import convRavenToGa from './conv-raven-to-ga';
import { TNil, IWebAnalytics } from '../../types';
import { TNil } from '../../types';
import { Config } from '../../types/config';
import { IWebAnalyticsFunc } from '../../types/tracking';

export default class GA implements IWebAnalytics {
isDebugMode = false;
context = null;
isProd = process.env.NODE_ENV === 'production';
isDev = process.env.NODE_ENV === 'development';
isTest = process.env.NODE_ENV === 'test';
gaID = null;
isErrorsEnabled = false;
const isTruish = (value?: string | string[]) => {
return Boolean(value) && value !== '0' && value !== 'false';
};

private cookiesToDimensions = undefined;
private EVENT_LENGTHS = {
const logTrackingCalls = () => {
const calls = ReactGA.testModeAPI.calls;
for (let i = 0; i < calls.length; i++) {
// eslint-disable-next-line no-console
console.log('[react-ga]', ...calls[i]);
}
calls.length = 0;
};

const GA: IWebAnalyticsFunc = (config: Config, versionShort: string, versionLong: string) => {
const isProd = process.env.NODE_ENV === 'production';
const isDev = process.env.NODE_ENV === 'development';
const isTest = process.env.NODE_ENV === 'test';
const isDebugMode =
(isDev && isTruish(process.env.REACT_APP_GA_DEBUG)) ||
isTruish(queryString.parse(_get(window, 'location.search'))['ga-debug']);

const gaID = _get(config, 'tracking.gaID');
const isErrorsEnabled = isDebugMode || Boolean(_get(config, 'tracking.trackErrors'));
const cookiesToDimensions = _get(config, 'tracking.cookiesToDimensions');
const context = isErrorsEnabled ? Raven : (null as any);
const EVENT_LENGTHS = {
action: 499,
category: 149,
label: 499,
};

constructor(config: any) {
this.isDebugMode =
(this.isDev && GA.isTruish(process.env.REACT_APP_GA_DEBUG)) ||
GA.isTruish(queryString.parse(_get(window, 'location.search'))['ga-debug']);
const isEnabled = () => {
return isTest || isDebugMode || (isProd && Boolean(gaID));
};

this.gaID = _get(config, 'tracking.gaID');
this.isErrorsEnabled = this.isDebugMode || Boolean(_get(config, 'tracking.trackErrors'));
this.cookiesToDimensions = _get(config, 'tracking.cookiesToDimensions');
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();
}
};

this.context = this.isErrorsEnabled ? Raven : (null as any);
}
const trackEvent = (
category: string,
action: string,
labelOrValue?: string | number | TNil,
value?: number | TNil
) => {
const event: {
action: string;
category: string;
label?: string;
value?: number;
} = {
category: !/^jaeger/i.test(category)
? `jaeger/${category}`.slice(0, EVENT_LENGTHS.category)
: category.slice(0, EVENT_LENGTHS.category),
action: action.slice(0, EVENT_LENGTHS.action),
};
if (labelOrValue != null) {
if (typeof labelOrValue === 'string') {
event.label = labelOrValue.slice(0, EVENT_LENGTHS.action);
} else {
// value should be an int
event.value = Math.round(labelOrValue);
}
}
if (value != null) {
event.value = Math.round(value);
}
ReactGA.event(event);
if (isDebugMode) {
logTrackingCalls();
}
};

isEnabled() {
return this.isTest || this.isDebugMode || (this.isProd && Boolean(this.gaID));
}
const trackRavenError = (ravenData: RavenTransportOptions) => {
const { message, category, action, label, value } = convRavenToGa(ravenData);
trackError(message);
trackEvent(category, action, label, value);
};

init() {
let versionShort;
let versionLong;
if (process.env.REACT_APP_VSN_STATE) {
try {
const data = JSON.parse(process.env.REACT_APP_VSN_STATE);
const joiner = [data.objName];
if (data.changed.hasChanged) {
joiner.push(data.changed.pretty);
}
versionShort = joiner.join(' ');
versionLong = data.pretty;
} catch (_) {
versionShort = process.env.REACT_APP_VSN_STATE;
versionLong = process.env.REACT_APP_VSN_STATE;
}
versionLong = versionLong.length > 99 ? `${versionLong.slice(0, 96)}...` : versionLong;
} else {
versionShort = 'unknown';
versionLong = 'unknown';
const init = () => {
if (!isEnabled()) {
return;
}
const gaConfig = { testMode: this.isTest || this.isDebugMode, titleCase: false, debug: true };
ReactGA.initialize(this.gaID || 'debug-mode', gaConfig);

const gaConfig = { testMode: isTest || isDebugMode, titleCase: false, debug: true };
ReactGA.initialize(gaID || 'debug-mode', gaConfig);
ReactGA.set({
appId: 'github.com/jaegertracing/jaeger-ui',
appName: 'Jaeger UI',
appVersion: versionLong,
});
if (this.cookiesToDimensions !== undefined) {
((this.cookiesToDimensions as unknown) as Array<{ cookie: string; dimension: string }>).forEach(
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] });
Expand All @@ -90,7 +131,7 @@ export default class GA implements IWebAnalytics {
}
);
}
if (this.isErrorsEnabled) {
if (isErrorsEnabled) {
const ravenConfig: RavenOptions = {
autoBreadcrumbs: {
xhr: true,
Expand All @@ -99,7 +140,7 @@ export default class GA implements IWebAnalytics {
location: true,
},
environment: process.env.NODE_ENV || 'unkonwn',
transport: this.trackRavenError.bind(this),
transport: trackRavenError,
};
if (versionShort && versionShort !== 'unknown') {
ravenConfig.tags = {
Expand All @@ -111,77 +152,27 @@ export default class GA implements IWebAnalytics {
Raven.captureException(evt.reason);
};
}
if (this.isDebugMode) {
this.logTrackingCalls();
if (isDebugMode) {
logTrackingCalls();
}
}
};

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

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 (this.isDebugMode) {
this.logTrackingCalls();
}
}

trackEvent(category: string, action: string, labelOrValue?: string | number | TNil, value?: number | TNil) {
const event: {
action: string;
category: string;
label?: string;
value?: number;
} = {
category: !/^jaeger/i.test(category)
? `jaeger/${category}`.slice(0, this.EVENT_LENGTHS.category)
: category.slice(0, this.EVENT_LENGTHS.category),
action: action.slice(0, this.EVENT_LENGTHS.action),
};
if (labelOrValue != null) {
if (typeof labelOrValue === 'string') {
event.label = labelOrValue.slice(0, this.EVENT_LENGTHS.action);
} else {
// value should be an int
event.value = Math.round(labelOrValue);
}
}
if (value != null) {
event.value = Math.round(value);
}
ReactGA.event(event);
if (this.isDebugMode) {
this.logTrackingCalls();
}
}

// eslint-disable-next-line class-methods-use-this
private logTrackingCalls() {
const calls = ReactGA.testModeAPI.calls;
for (let i = 0; i < calls.length; i++) {
// eslint-disable-next-line no-console
console.log('[react-ga]', ...calls[i]);
}
calls.length = 0;
}
};

private trackRavenError(ravenData: RavenTransportOptions) {
const { message, category, action, label, value } = convRavenToGa(ravenData);
this.trackError(message);
this.trackEvent(category, action, label, value);
}
return {
isEnabled,
init,
context,
trackPageView,
trackError,
trackEvent,
};
};

static isTruish(value?: string | string[]) {
return Boolean(value) && value !== '0' && value !== 'false';
}
}
export default GA;
41 changes: 30 additions & 11 deletions packages/jaeger-ui/src/utils/tracking/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,35 +12,54 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import { TNil, IWebAnalytics } from '../../types';
import { TNil } from '../../types';
import { IWebAnalyticsFunc } from '../../types/tracking';
import GA from './ga';
import getConfig from '../config/get-config';

const TrackingImplementation = () => {
const config = getConfig();
let versionShort;
let versionLong;

const GenericWebAnalytics: IWebAnalytics = {
if (process.env.REACT_APP_VSN_STATE) {
try {
const data = JSON.parse(process.env.REACT_APP_VSN_STATE);
const joiner = [data.objName];
if (data.changed.hasChanged) {
joiner.push(data.changed.pretty);
}
versionShort = joiner.join(' ');
versionLong = data.pretty;
} catch (_) {
versionShort = process.env.REACT_APP_VSN_STATE;
versionLong = process.env.REACT_APP_VSN_STATE;
}
versionLong = versionLong.length > 99 ? `${versionLong.slice(0, 96)}...` : versionLong;
} else {
versionShort = 'unknown';
versionLong = 'unknown';
}

const NoopWebAnalytics: IWebAnalyticsFunc = () => ({
init: () => {},
trackPageView: () => {},
trackError: () => {},
trackEvent: () => {},
context: null,
isEnabled: () => false,
};
});

let webAnalytics = GenericWebAnalytics;
let webAnalyticsFunc = NoopWebAnalytics;

if (config.tracking && config.tracking.customWebAnalytics) {
webAnalytics = config.tracking.customWebAnalytics(config) as IWebAnalytics;
webAnalyticsFunc = config.tracking.customWebAnalytics as IWebAnalyticsFunc;
} else if (config.tracking && config.tracking.gaID) {
webAnalytics = new GA(config);
webAnalyticsFunc = GA;
}

if (webAnalytics.isEnabled()) {
webAnalytics.init();

return webAnalytics;
}
const webAnalytics = webAnalyticsFunc(config, versionShort, versionLong);
webAnalytics.init();

return webAnalytics;
};
Expand Down

0 comments on commit 3f71ca3

Please sign in to comment.