Convert to GA4 format

- It is recommended to use "lowercase + underscore format" for events to keep things neat, since the dashboard will be mixed with Automated and Recommended events.

- GA4 event structure is no longer the same as UA's, and the recommendation is to retructure rather than trying to mimic the old pattern.

- Always check the Recommended events to see if there is an equivalent, and use the exact name. GA4 might add automated features for these events in the future, and we'll benefit from it without code changes and invalidating existing data.

- pageView: use default snippet behavior instead of manually sending
Start converting to GA4...

- Outbound click are automatically handled.
This commit is contained in:
infinite-persistence 2021-10-16 21:13:35 +08:00
parent dab1ca1cb7
commit f6e60abbf5
No known key found for this signature in database
GPG key ID: B9C3252EDC3D0AA0
5 changed files with 46 additions and 84 deletions

View file

@ -60,7 +60,6 @@
"match-sorter": "^6.3.0",
"parse-duration": "^1.0.0",
"react-datetime-picker": "^3.2.1",
"react-ga": "^3.3.0",
"react-plastic": "^1.1.1",
"react-top-loading-bar": "^2.0.1",
"remove-markdown": "^0.3.0",

View file

@ -1,6 +1,19 @@
<!DOCTYPE html>
<html dir="ltr">
<head>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-BB8DNPB73F"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('consent', 'default', {
'ad_storage': 'denied',
'analytics_storage': 'denied',
});
gtag('js', new Date());
gtag('config', 'G-BB8DNPB73F');
</script>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Pragma" content="no-cache" />

View file

@ -1,8 +1,6 @@
// @flow
import { Lbryio } from 'lbryinc';
import ReactGA from 'react-ga';
import * as Sentry from '@sentry/browser';
import { history } from './store';
import { SDK_API_PATH } from './index';
// import getConnectionSpeed from 'util/detect-user-bandwidth';
@ -16,7 +14,6 @@ import { SDK_API_PATH } from './index';
const isProduction = process.env.NODE_ENV === 'production';
const devInternalApis = process.env.LBRY_API_URL && process.env.LBRY_API_URL.includes('dev');
const ODYSEE_UA_ID = 'UA-60403362-12';
export const SHARE_INTERNAL = 'shareInternal';
@ -26,7 +23,6 @@ const SEND_DATA_TO_WATCHMAN_INTERVAL = 10; // in seconds
type Analytics = {
error: (string) => Promise<any>,
sentryError: ({} | string, {}) => Promise<any>,
pageView: (string, ?string) => void,
setUser: (Object) => void,
toggleInternal: (boolean, ?boolean) => void,
apiLogView: (string, string, string, ?number, ?() => void) => Promise<any>,
@ -248,7 +244,7 @@ const analytics: Analytics = {
bitrateAsBitsPerSecond = videoBitrate;
sendPromMetric('time_to_start', timeToStartVideo);
sendGaEvent('Media', 'TimeToStart', claimId, timeToStartVideo);
sendGaEvent('video_time_to_start', { claim_id: claimId, time: timeToStartVideo });
},
error: (message) => {
return new Promise((resolve) => {
@ -274,16 +270,9 @@ const analytics: Analytics = {
}
});
},
pageView: (path, search) => {
if (internalAnalyticsEnabled) {
ReactGA.pageview(path);
}
},
setUser: (userId) => {
if (internalAnalyticsEnabled && userId) {
ReactGA.set({
userId,
});
if (internalAnalyticsEnabled && userId && window.gtag) {
window.gtag('set', { user_id: userId });
}
},
toggleInternal: (enabled: boolean): void => {
@ -348,72 +337,57 @@ const analytics: Analytics = {
}
},
adsFetchedEvent: () => {
sendGaEvent('Media', 'AdsFetched');
sendGaEvent('ad_fetched');
},
adsReceivedEvent: (response) => {
sendGaEvent('Media', 'AdsReceived', JSON.stringify(response));
sendGaEvent('ad_received', { response: JSON.stringify(response) });
},
adsErrorEvent: (response) => {
sendGaEvent('Media', 'AdsError', JSON.stringify(response));
sendGaEvent('ad_error', { response: JSON.stringify(response) });
},
playerLoadedEvent: (embedded) => {
sendGaEvent('Player', 'Loaded', embedded ? 'embedded' : 'onsite');
sendGaEvent('player', { action: 'loaded', type: embedded ? 'embedded' : 'onsite' });
},
playerStartedEvent: (embedded) => {
sendGaEvent('Player', 'Started', embedded ? 'embedded' : 'onsite');
sendGaEvent('player', { action: 'started', type: embedded ? 'embedded' : 'onsite' });
},
tagFollowEvent: (tag, following) => {
sendGaEvent('Tag', following ? 'Tag-Follow' : 'Tag-Unfollow', tag);
sendGaEvent(following ? 'tag_follow' : 'tag_unfollow', { tag });
},
channelBlockEvent: (uri, blocked, location) => {
sendGaEvent(blocked ? 'Channel-Hidden' : 'Channel-Unhidden', uri);
sendGaEvent(blocked ? 'channel_hidden' : 'channel_unhidden', { uri });
},
emailProvidedEvent: () => {
sendGaEvent('Engagement', 'Email-Provided');
sendGaEvent('engagement', { type: 'email_provided' });
},
emailVerifiedEvent: () => {
sendGaEvent('Engagement', 'Email-Verified');
sendGaEvent('engagement', { type: 'email_verified' });
},
rewardEligibleEvent: () => {
sendGaEvent('Engagement', 'Reward-Eligible');
sendGaEvent('engagement', { type: 'reward_eligible' });
},
openUrlEvent: (url: string) => {
sendGaEvent('Engagement', 'Open-Url', url);
sendGaEvent('engagement', { type: 'open_url', url });
},
trendingAlgorithmEvent: (trendingAlgorithm: string) => {
sendGaEvent('Engagement', 'Trending-Algorithm', trendingAlgorithm);
sendGaEvent('engagement', { type: 'trending_algorithm', trending_algorithm: trendingAlgorithm });
},
startupEvent: () => {
sendGaEvent('Startup', 'Startup');
// TODO: This can be removed (use the automated 'session_start' instead).
// sendGaEvent('startup', 'startup');
},
readyEvent: (timeToReady: number) => {
sendGaEvent('Startup', 'App-Ready');
sendGaTimingEvent('Startup', 'App-Ready', timeToReady);
readyEvent: (timeToReadyMs: number) => {
sendGaEvent('startup_app_ready', { time_to_ready_ms: timeToReadyMs });
},
purchaseEvent: (purchaseInt: number) => {
sendGaEvent('Purchase', 'Purchase-Complete', undefined, purchaseInt);
// https://developers.google.com/analytics/devguides/collection/ga4/reference/events#purchase
sendGaEvent('purchase', { value: purchaseInt });
},
};
function sendGaEvent(category, action, label, value) {
if (internalAnalyticsEnabled && isProduction) {
ReactGA.event({
category,
action,
...(label ? { label } : {}),
...(value ? { value } : {}),
});
}
}
function sendGaTimingEvent(category: string, action: string, timeInMs: number, label?: string) {
if (internalAnalyticsEnabled && isProduction) {
ReactGA.timing({
category,
variable: action,
value: timeInMs,
...(label ? { label } : {}),
});
function sendGaEvent(event: string, params?: { [string]: string | number }) {
if (internalAnalyticsEnabled && isProduction && window.gtag) {
window.gtag('event', event, params);
}
}
@ -426,27 +400,12 @@ function sendPromMetric(name: string, value?: number) {
}
}
const gaTrackers = [{ trackingId: ODYSEE_UA_ID }];
ReactGA.initialize(gaTrackers, {
testMode: process.env.NODE_ENV !== 'production',
cookieDomain: 'auto',
siteSpeedSampleRate: 100,
// un-comment to see events as they are sent to google
// debug: true,
});
// Manually call the first page view
// React Router doesn't include this on `history.listen`
analytics.pageView(window.location.pathname + window.location.search, window.location.search);
// Listen for url changes and report
// This will include search queries
history.listen((location) => {
const { pathname, search } = location;
const page = `${pathname}${search}`;
analytics.pageView(page, search);
});
// Activate
if (internalAnalyticsEnabled && isProduction && window.gtag) {
window.gtag('consent', 'update', {
ad_storage: 'granted',
analytics_storage: 'granted',
});
}
export default analytics;

View file

@ -5,7 +5,6 @@ import Icon from 'component/common/icon';
import classnames from 'classnames';
import { NavLink } from 'react-router-dom';
import { formatLbryUrlForWeb } from 'util/url';
import { OutboundLink } from 'react-ga';
import * as PAGES from 'constants/pages';
import useCombinedRefs from 'effects/use-combined-refs';
@ -147,9 +146,7 @@ const Button = forwardRef<any, {}>((props: Props, ref: any) => {
if (href || (navigate && navigate.startsWith('http'))) {
// TODO: replace the below with an outbound link tracker for matomo
return (
<OutboundLink
eventLabel="outboundClick"
to={href || navigate}
<a
target="_blank"
rel="noopener noreferrer"
href={href || navigate}
@ -158,10 +155,9 @@ const Button = forwardRef<any, {}>((props: Props, ref: any) => {
onClick={onClick}
aria-label={ariaLabel}
disabled={disabled} // is there a reason this wasn't here before?
{...otherProps}
>
{content}
</OutboundLink>
</a>
);
}

View file

@ -13480,11 +13480,6 @@ react-fit@^1.0.3:
detect-element-overflow "^1.2.0"
prop-types "^15.6.0"
react-ga@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/react-ga/-/react-ga-3.3.0.tgz#c91f407198adcb3b49e2bc5c12b3fe460039b3ca"
integrity sha512-o8RScHj6Lb8cwy3GMrVH6NJvL+y0zpJvKtc0+wmH7Bt23rszJmnqEQxRbyrqUzk9DTJIHoP42bfO5rswC9SWBQ==
react-google-recaptcha@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/react-google-recaptcha/-/react-google-recaptcha-2.0.1.tgz#3276b29659493f7ca2a5b7739f6c239293cdf1d8"