Skip to content

Commit 8ce9d61

Browse files
Merge pull request Expensify#88759 from Expensify/youssef_pixel_events
[Payment due @huult] Send analytic events to Reddit, Meta, & Linkedin
2 parents fc258be + 3fe93ff commit 8ce9d61

12 files changed

Lines changed: 94 additions & 30 deletions

File tree

cspell.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -926,7 +926,10 @@
926926
"CARDFROZEN",
927927
"CARDUNFROZEN",
928928
"backgrounded",
929-
"Kolkata"
929+
"Kolkata",
930+
"lintrk",
931+
"Fbclid",
932+
"Gclid"
930933
],
931934
"ignorePaths": [
932935
".gitignore",

src/CONST/index.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8644,9 +8644,24 @@ const CONST = {
86448644

86458645
ANALYTICS: {
86468646
EVENT: {
8647-
SIGN_UP: 'sign_up',
8648-
WORKSPACE_CREATED: 'workspace_created',
8649-
PAID_ADOPTION: 'paid_adoption',
8647+
SIGN_UP: {
8648+
NAME: 'sign_up',
8649+
META: 'SignUp',
8650+
REDDIT: 'SignUp',
8651+
LINKEDIN: 507587661,
8652+
},
8653+
WORKSPACE_CREATED: {
8654+
NAME: 'workspace_created',
8655+
META: 'WorkspaceCreated',
8656+
REDDIT: 'Lead',
8657+
LINKEDIN: 25474804,
8658+
},
8659+
PAID_ADOPTION: {
8660+
NAME: 'paid_adoption',
8661+
META: 'PaidAdoption',
8662+
REDDIT: 'Purchase',
8663+
LINKEDIN: 25474820,
8664+
},
86508665
},
86518666
},
86528667

src/libs/GoogleTagManager/index.native.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import type GoogleTagManagerModule from './types';
66

77
const analytics = getAnalytics();
88

9-
function publishEvent(event: GoogleTagManagerEvent, accountID: number) {
10-
logEvent(analytics, event as string, {user_id: accountID});
11-
Log.info('[GTM] event published', false, {event, user_id: accountID});
9+
function publishEvent(event: GoogleTagManagerEvent, accountID: number, email: string) {
10+
logEvent(analytics, event as string, {user_id: accountID, email});
11+
Log.info('[GTM] event published', false, {event, user_id: accountID, user_data: {email}});
1212
}
1313

1414
const GoogleTagManager: GoogleTagManagerModule = {

src/libs/GoogleTagManager/index.ts

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,69 @@
11
/* eslint-disable @typescript-eslint/naming-convention */
22
import Log from '@libs/Log';
3+
import CONST from '@src/CONST';
34
import type {GoogleTagManagerEvent} from './types';
45
import type GoogleTagManagerModule from './types';
56

67
/**
78
* The dataLayer is added with a js snippet from Google in web/thirdPartyScripts.js. Set USE_THIRD_PARTY_SCRIPTS to true
89
* in your .env to enable this
910
*/
10-
type WindowWithDataLayer = Window & {
11+
type WindowWithPixels = Window & {
1112
dataLayer?: {
1213
push: (params: DataLayerPushParams) => void;
1314
};
15+
fbq?: (method: string, eventName: string, params?: Record<string, unknown>, options?: Record<string, unknown>) => void;
16+
rdt?: (method: string, eventType: string, params?: Record<string, string>) => void;
17+
lintrk?: (method: string, params: Record<string, unknown>) => void;
1418
};
1519

1620
type DataLayerPushParams = {
1721
event: GoogleTagManagerEvent;
1822
user_id: number;
23+
user_data: {email: string};
1924
};
2025

21-
declare const window: WindowWithDataLayer;
26+
declare const window: WindowWithPixels;
2227

23-
function publishEvent(event: GoogleTagManagerEvent, accountID: number) {
28+
const PIXEL_EVENTS = [CONST.ANALYTICS.EVENT.SIGN_UP, CONST.ANALYTICS.EVENT.WORKSPACE_CREATED, CONST.ANALYTICS.EVENT.PAID_ADOPTION] as const;
29+
30+
function publishEvent(event: GoogleTagManagerEvent, accountID: number, email: string) {
2431
if (!window.dataLayer) {
2532
return;
2633
}
2734

28-
const params = {event, user_id: accountID};
35+
const params = {event, user_id: accountID, user_data: {email}};
2936

3037
// Pass a copy of params here since the dataLayer modifies the object
3138
window.dataLayer.push({...params});
3239

3340
Log.info('[GTM] event published', false, params);
41+
42+
const pixelEvent = PIXEL_EVENTS.find((e) => e.NAME === event);
43+
if (!pixelEvent) {
44+
return;
45+
}
46+
47+
const eventID = `${accountID}-${event}`;
48+
49+
// Meta
50+
if (typeof window.fbq === 'function') {
51+
window.fbq('trackCustom', pixelEvent.META, {em: email}, {eventID});
52+
}
53+
54+
// Reddit
55+
if (typeof window.rdt === 'function') {
56+
window.rdt('track', pixelEvent.REDDIT, {
57+
conversionId: eventID,
58+
email,
59+
});
60+
}
61+
62+
// LinkedIn (uses numeric conversion IDs instead of named events)
63+
if (typeof window.lintrk === 'function') {
64+
window.lintrk('setUserData', {email});
65+
window.lintrk('track', {conversion_id: pixelEvent.LINKEDIN, event_id: eventID});
66+
}
3467
}
3568

3669
const GoogleTagManager: GoogleTagManagerModule = {

src/libs/GoogleTagManager/types.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ import type CONST from '@src/CONST';
55
* An event that can be published to Google Tag Manager. New events must be configured in GTM before they can be used
66
* in the app.
77
*/
8-
type GoogleTagManagerEvent = ValueOf<typeof CONST.ANALYTICS.EVENT>;
8+
type ExtractEventName<T> = T extends {NAME: infer N extends string} ? N : T extends string ? T : never;
9+
type GoogleTagManagerEvent = ExtractEventName<ValueOf<typeof CONST.ANALYTICS.EVENT>>;
910

1011
type GoogleTagManagerModule = {
11-
publishEvent: (event: GoogleTagManagerEvent, accountID: number) => void;
12+
publishEvent: (event: GoogleTagManagerEvent, accountID: number, email: string) => void;
1213
};
1314

1415
export default GoogleTagManagerModule;

src/libs/Navigation/AppNavigator/Navigators/OnboardingModalNavigator.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {CardStyleInterpolators} from '@react-navigation/stack';
2-
import {accountIDSelector} from '@selectors/Session';
2+
import {accountIDSelector, emailSelector} from '@selectors/Session';
33
import React, {useCallback, useEffect, useMemo} from 'react';
44
import {View} from 'react-native';
55
import type {ValueOf} from 'type-fest';
@@ -68,17 +68,20 @@ function OnboardingModalNavigator() {
6868
const [accountID] = useOnyx(ONYXKEYS.SESSION, {
6969
selector: accountIDSelector,
7070
});
71+
const [email] = useOnyx(ONYXKEYS.SESSION, {
72+
selector: emailSelector,
73+
});
7174

7275
// Publish a sign_up event when we start the onboarding flow. This should track basic sign ups
7376
// as well as Google and Apple SSO.
7477
useEffect(() => {
75-
if (!accountID || signUpEventPublishedForAccountID === accountID) {
78+
if (!accountID || !email || signUpEventPublishedForAccountID === accountID) {
7679
return;
7780
}
7881

7982
signUpEventPublishedForAccountID = accountID;
80-
GoogleTagManager.publishEvent(CONST.ANALYTICS.EVENT.SIGN_UP, accountID);
81-
}, [accountID]);
83+
GoogleTagManager.publishEvent(CONST.ANALYTICS.EVENT.SIGN_UP.NAME, accountID, email);
84+
}, [accountID, email]);
8285

8386
const handleOuterClick = useCallback(() => {
8487
OnboardingRefManager.handleOuterClick();

src/libs/actions/IOU/TrackExpense.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2070,7 +2070,7 @@ function convertBulkTrackedExpensesToIOU({
20702070
}
20712071

20722072
function categorizeTrackedExpense(trackedExpenseParams: TrackedExpenseParams) {
2073-
const {onyxData, reportInformation, transactionParams, policyParams, createdWorkspaceParams, currentUserAccountID} = trackedExpenseParams;
2073+
const {onyxData, reportInformation, transactionParams, policyParams, createdWorkspaceParams, currentUserAccountID, currentUserEmail} = trackedExpenseParams;
20742074
const {optimisticData, successData, failureData} = onyxData ?? {};
20752075
const {transactionID} = transactionParams;
20762076
const {isDraftPolicy} = policyParams;
@@ -2128,7 +2128,7 @@ function categorizeTrackedExpense(trackedExpenseParams: TrackedExpenseParams) {
21282128
// If a draft policy was used, then the CategorizeTrackedExpense command will create a real one
21292129
// so let's track that conversion here
21302130
if (isDraftPolicy) {
2131-
GoogleTagManager.publishEvent(CONST.ANALYTICS.EVENT.WORKSPACE_CREATED, currentUserAccountID);
2131+
GoogleTagManager.publishEvent(CONST.ANALYTICS.EVENT.WORKSPACE_CREATED.NAME, currentUserAccountID, currentUserEmail ?? '');
21322132
}
21332133
}
21342134

@@ -2513,6 +2513,7 @@ function trackExpense(params: CreateTrackExpenseParams) {
25132513
policyParams,
25142514
createdWorkspaceParams,
25152515
currentUserAccountID: currentUserAccountIDParam,
2516+
currentUserEmail: currentUserEmailParam,
25162517
};
25172518

25182519
categorizeTrackedExpense(trackedExpenseParams);
@@ -2565,6 +2566,7 @@ function trackExpense(params: CreateTrackExpenseParams) {
25652566
createdWorkspaceParams,
25662567
accountantParams,
25672568
currentUserAccountID: currentUserAccountIDParam,
2569+
currentUserEmail: currentUserEmailParam,
25682570
};
25692571
shareTrackedExpense(trackedExpenseParams);
25702572
break;

src/libs/actions/IOU/types/TrackedExpenseParams.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ type TrackedExpenseParams = {
6565
createdWorkspaceParams?: CreateWorkspaceParams;
6666
accountantParams?: TrackExpenseAccountantParams;
6767
currentUserAccountID: number;
68+
currentUserEmail?: string;
6869
};
6970

7071
export type {TrackedExpenseParams, TrackedExpensePolicyParams, TrackedExpenseTransactionParams, TrackedExpenseReportInformation, BuildOnyxDataForTrackExpenseKeys};

src/libs/actions/PaymentMethods.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import * as CardUtils from '@libs/CardUtils';
1919
import GoogleTagManager from '@libs/GoogleTagManager';
2020
import Log from '@libs/Log';
2121
import Navigation from '@libs/Navigation/Navigation';
22+
import {getCurrentUserEmail} from '@libs/Network/NetworkStore';
2223
import {isPolicyUser} from '@libs/PolicyUtils';
2324
import {getCardForSubscriptionBilling} from '@libs/SubscriptionUtils';
2425
import CONST from '@src/CONST';
@@ -204,7 +205,7 @@ function addPaymentCard(accountID: number, params: PaymentCardParams) {
204205
failureData,
205206
});
206207

207-
GoogleTagManager.publishEvent(CONST.ANALYTICS.EVENT.PAID_ADOPTION, accountID);
208+
GoogleTagManager.publishEvent(CONST.ANALYTICS.EVENT.PAID_ADOPTION.NAME, accountID, getCurrentUserEmail() ?? '');
208209
}
209210

210211
/**
@@ -272,9 +273,9 @@ function addSubscriptionPaymentCard(
272273
});
273274
}
274275
if (getCardForSubscriptionBilling(fundList)) {
275-
Log.info(`[GTM] Not logging ${CONST.ANALYTICS.EVENT.PAID_ADOPTION} because a card was already added`);
276+
Log.info(`[GTM] Not logging ${CONST.ANALYTICS.EVENT.PAID_ADOPTION.NAME} because a card was already added`);
276277
} else {
277-
GoogleTagManager.publishEvent(CONST.ANALYTICS.EVENT.PAID_ADOPTION, accountID);
278+
GoogleTagManager.publishEvent(CONST.ANALYTICS.EVENT.PAID_ADOPTION.NAME, accountID, getCurrentUserEmail() ?? '');
278279
}
279280
}
280281

src/libs/actions/Policy/Policy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3085,7 +3085,7 @@ function createWorkspace(options: CreateWorkspaceDataOptions): CreateWorkspacePa
30853085

30863086
// Publish a workspace created event if this is their first policy
30873087
if (!options.hasActiveAdminPolicies) {
3088-
GoogleTagManager.publishEvent(CONST.ANALYTICS.EVENT.WORKSPACE_CREATED, options.currentUserAccountIDParam ?? CONST.DEFAULT_NUMBER_ID);
3088+
GoogleTagManager.publishEvent(CONST.ANALYTICS.EVENT.WORKSPACE_CREATED.NAME, options.currentUserAccountIDParam ?? CONST.DEFAULT_NUMBER_ID, options.currentUserEmailParam);
30893089
}
30903090

30913091
return params;

0 commit comments

Comments
 (0)