Skip to content

Commit

Permalink
Merge pull request #51599 from Expensify/arosiclair-gtm-events
Browse files Browse the repository at this point in the history
Add GTM event tracking
  • Loading branch information
mountiny authored Nov 10, 2024
2 parents 479bca6 + 447d06f commit d688a59
Show file tree
Hide file tree
Showing 16 changed files with 250 additions and 34 deletions.
5 changes: 5 additions & 0 deletions __mocks__/@react-native-firebase/analytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default function analytics() {
return {
logEvent: jest.fn(),
};
}
2 changes: 1 addition & 1 deletion config/webpack/webpack.common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const getCommonConfiguration = ({file = '.env', platform = 'web'}: Environment):
isWeb: platform === 'web',
isProduction: file === '.env.production',
isStaging: file === '.env.staging',
useThirdPartyScripts: process.env.USE_THIRD_PARTY_SCRIPTS === 'true' || (platform === 'web' && file === '.env.production'),
useThirdPartyScripts: process.env.USE_THIRD_PARTY_SCRIPTS === 'true' || (platform === 'web' && ['.env.production', '.env.staging'].includes(file)),
}),
new PreloadWebpackPlugin({
rel: 'preload',
Expand Down
8 changes: 8 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6219,6 +6219,14 @@ const CONST = {
HAS_VIOLATIONS: 'hasViolations',
HAS_TRANSACTION_THREAD_VIOLATIONS: 'hasTransactionThreadViolations',
},

ANALYTICS: {
EVENT: {
SIGN_UP: 'sign_up',
WORKSPACE_CREATED: 'workspace_created',
PAID_ADOPTION: 'paid_adoption',
},
},
} as const;

type Country = keyof typeof CONST.ALL_COUNTRIES;
Expand Down
5 changes: 4 additions & 1 deletion src/libs/GoogleTagManager/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import type {ValueOf} from 'type-fest';
import type CONST from '@src/CONST';

/**
* An event that can be published to Google Tag Manager. New events must be configured in GTM before they can be used
* in the app.
*/
type GoogleTagManagerEvent = 'sign_up' | 'workspace_created' | 'paid_adoption';
type GoogleTagManagerEvent = ValueOf<typeof CONST.ANALYTICS.EVENT>;

type GoogleTagManagerModule = {
publishEvent: (event: GoogleTagManagerEvent, accountID: number) => void;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import {createStackNavigator} from '@react-navigation/stack';
import React, {useCallback} from 'react';
import React, {useCallback, useEffect} from 'react';
import {View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import NoDropZone from '@components/DragAndDrop/NoDropZone';
import FocusTrapForScreens from '@components/FocusTrap/FocusTrapForScreen';
import useKeyboardShortcut from '@hooks/useKeyboardShortcut';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import GoogleTagManager from '@libs/GoogleTagManager';
import OnboardingModalNavigatorScreenOptions from '@libs/Navigation/AppNavigator/OnboardingModalNavigatorScreenOptions';
import type {OnboardingModalNavigatorParamList} from '@libs/Navigation/types';
import OnboardingRefManager from '@libs/OnboardingRefManager';
Expand All @@ -14,6 +16,7 @@ import OnboardingEmployees from '@pages/OnboardingEmployees';
import OnboardingPersonalDetails from '@pages/OnboardingPersonalDetails';
import OnboardingPurpose from '@pages/OnboardingPurpose';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import SCREENS from '@src/SCREENS';
import Overlay from './Overlay';

Expand All @@ -23,6 +26,17 @@ function OnboardingModalNavigator() {
const styles = useThemeStyles();
const {onboardingIsMediumOrLargerScreenWidth} = useResponsiveLayout();
const outerViewRef = React.useRef<View>(null);
const [accountID] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.accountID ?? 0});

// Publish a sign_up event when we start the onboarding flow. This should track basic sign ups
// as well as Google and Apple SSO.
useEffect(() => {
if (!accountID) {
return;
}

GoogleTagManager.publishEvent(CONST.ANALYTICS.EVENT.SIGN_UP, accountID);
}, [accountID]);

const handleOuterClick = useCallback(() => {
OnboardingRefManager.handleOuterClick();
Expand Down
10 changes: 10 additions & 0 deletions src/libs/actions/IOU.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import DateUtils from '@libs/DateUtils';
import DistanceRequestUtils from '@libs/DistanceRequestUtils';
import * as ErrorUtils from '@libs/ErrorUtils';
import * as FileUtils from '@libs/fileDownload/FileUtils';
import GoogleTagManager from '@libs/GoogleTagManager';
import * as IOUUtils from '@libs/IOUUtils';
import * as LocalePhoneNumber from '@libs/LocalePhoneNumber';
import * as Localize from '@libs/Localize';
Expand Down Expand Up @@ -3422,6 +3423,7 @@ function categorizeTrackedExpense(
comment: string,
merchant: string,
created: string,
isDraftPolicy: boolean,
category?: string,
tag?: string,
taxCode = '',
Expand Down Expand Up @@ -3478,6 +3480,12 @@ function categorizeTrackedExpense(
};

API.write(WRITE_COMMANDS.CATEGORIZE_TRACKED_EXPENSE, parameters, {optimisticData, successData, failureData});

// If a draft policy was used, then the CategorizeTrackedExpense command will create a real one
// so let's track that conversion here
if (isDraftPolicy) {
GoogleTagManager.publishEvent(CONST.ANALYTICS.EVENT.WORKSPACE_CREATED, userAccountID);
}
}

function shareTrackedExpense(
Expand Down Expand Up @@ -3777,6 +3785,7 @@ function trackExpense(
payeeAccountID: number,
participant: Participant,
comment: string,
isDraftPolicy: boolean,
receipt?: Receipt,
category?: string,
tag?: string,
Expand Down Expand Up @@ -3872,6 +3881,7 @@ function trackExpense(
comment,
merchant,
created,
isDraftPolicy,
category,
tag,
taxCode,
Expand Down
28 changes: 18 additions & 10 deletions src/libs/actions/PaymentMethods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {
} from '@libs/API/parameters';
import {READ_COMMANDS, SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
import * as CardUtils from '@libs/CardUtils';
import GoogleTagManager from '@libs/GoogleTagManager';
import Navigation from '@libs/Navigation/Navigation';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
Expand Down Expand Up @@ -159,7 +160,7 @@ function makeDefaultPaymentMethod(bankAccountID: number, fundID: number, previou
* Calls the API to add a new card.
*
*/
function addPaymentCard(params: PaymentCardParams) {
function addPaymentCard(accountID: number, params: PaymentCardParams) {
const cardMonth = CardUtils.getMonthFromExpirationDateString(params.expirationDate);
const cardYear = CardUtils.getYearFromExpirationDateString(params.expirationDate);

Expand Down Expand Up @@ -203,21 +204,26 @@ function addPaymentCard(params: PaymentCardParams) {
successData,
failureData,
});

GoogleTagManager.publishEvent(CONST.ANALYTICS.EVENT.PAID_ADOPTION, accountID);
}

/**
* Calls the API to add a new card.
*
*/
function addSubscriptionPaymentCard(cardData: {
cardNumber: string;
cardYear: string;
cardMonth: string;
cardCVV: string;
addressName: string;
addressZip: string;
currency: ValueOf<typeof CONST.PAYMENT_CARD_CURRENCY>;
}) {
function addSubscriptionPaymentCard(
accountID: number,
cardData: {
cardNumber: string;
cardYear: string;
cardMonth: string;
cardCVV: string;
addressName: string;
addressZip: string;
currency: ValueOf<typeof CONST.PAYMENT_CARD_CURRENCY>;
},
) {
const {cardNumber, cardYear, cardMonth, cardCVV, addressName, addressZip, currency} = cardData;

const parameters: AddPaymentCardParams = {
Expand Down Expand Up @@ -265,6 +271,8 @@ function addSubscriptionPaymentCard(cardData: {
failureData,
});
}

GoogleTagManager.publishEvent(CONST.ANALYTICS.EVENT.PAID_ADOPTION, accountID);
}

/**
Expand Down
10 changes: 10 additions & 0 deletions src/libs/actions/Policy/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import * as CurrencyUtils from '@libs/CurrencyUtils';
import DateUtils from '@libs/DateUtils';
import * as ErrorUtils from '@libs/ErrorUtils';
import getIsNarrowLayout from '@libs/getIsNarrowLayout';
import GoogleTagManager from '@libs/GoogleTagManager';
import Log from '@libs/Log';
import * as NetworkStore from '@libs/Network/NetworkStore';
import * as NumberUtils from '@libs/NumberUtils';
Expand Down Expand Up @@ -1838,6 +1839,11 @@ function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName
const {optimisticData, failureData, successData, params} = buildPolicyData(policyOwnerEmail, makeMeAdmin, policyName, policyID, undefined, engagementChoice);
API.write(WRITE_COMMANDS.CREATE_WORKSPACE, params, {optimisticData, successData, failureData});

// Publish a workspace created event if this is their first policy
if (getAdminPolicies().length === 0) {
GoogleTagManager.publishEvent(CONST.ANALYTICS.EVENT.WORKSPACE_CREATED, sessionAccountID);
}

return params;
}

Expand Down Expand Up @@ -3743,6 +3749,10 @@ function setWorkspaceEReceiptsEnabled(policyID: string, eReceipts: boolean) {
API.write(WRITE_COMMANDS.SET_WORKSPACE_ERECEIPTS_ENABLED, parameters, onyxData);
}

function getAdminPolicies(): Policy[] {
return Object.values(allPolicies ?? {}).filter<Policy>((policy): policy is Policy => !!policy && policy.role === CONST.POLICY.ROLE.ADMIN && policy.type !== CONST.POLICY.TYPE.PERSONAL);
}

function getAdminPoliciesConnectedToSageIntacct(): Policy[] {
return Object.values(allPolicies ?? {}).filter<Policy>((policy): policy is Policy => !!policy && policy.role === CONST.POLICY.ROLE.ADMIN && !!policy?.connections?.intacct);
}
Expand Down
16 changes: 9 additions & 7 deletions src/pages/iou/request/step/IOURequestStepAmount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,24 +45,25 @@ type IOURequestStepAmountProps = WithCurrentUserPersonalDetailsProps &
function IOURequestStepAmount({
report,
route: {
params: {iouType, reportID, transactionID, backTo, pageIndex, action, currency: selectedCurrency = ''},
params: {iouType, reportID, transactionID = '-1', backTo, pageIndex, action, currency: selectedCurrency = ''},
},
transaction,
currentUserPersonalDetails,
shouldKeepUserInput = false,
}: IOURequestStepAmountProps) {
const [splitDraftTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID ?? -1}`);
const [draftTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID ?? -1}`);
const [skipConfirmation] = useOnyx(`${ONYXKEYS.COLLECTION.SKIP_CONFIRMATION}${transactionID ?? -1}`);
const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : -1}`);

const {translate} = useLocalize();
const textInput = useRef<BaseTextInputRef | null>(null);
const focusTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const isSaveButtonPressed = useRef(false);
const iouRequestType = getRequestType(transaction);
const policyID = report?.policyID ?? '-1';

const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID ?? -1}`);
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`);
const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
const [draftTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`);
const [splitDraftTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`);
const [skipConfirmation] = useOnyx(`${ONYXKEYS.COLLECTION.SKIP_CONFIRMATION}${transactionID}`);

const isEditing = action === CONST.IOU.ACTION.EDIT;
const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT;
Expand Down Expand Up @@ -236,6 +237,7 @@ function IOURequestStepAmount({
currentUserPersonalDetails.accountID,
participants.at(0) ?? {},
'',
false,
);
return;
}
Expand Down
3 changes: 3 additions & 0 deletions src/pages/iou/request/step/IOURequestStepConfirmation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ function IOURequestStepConfirmation({

const report = reportReal ?? reportDraft;
const policy = policyReal ?? policyDraft;
const isDraftPolicy = policy === policyDraft;
const policyCategories = policyCategoriesReal ?? policyCategoriesDraft;

const styles = useThemeStyles();
Expand Down Expand Up @@ -287,6 +288,7 @@ function IOURequestStepConfirmation({
currentUserPersonalDetails.accountID,
participant,
trimmedComment,
isDraftPolicy,
receiptObj,
transaction.category,
transaction.tag,
Expand Down Expand Up @@ -317,6 +319,7 @@ function IOURequestStepConfirmation({
policyCategories,
action,
customUnitRateID,
isDraftPolicy,
],
);

Expand Down
1 change: 1 addition & 0 deletions src/pages/iou/request/step/IOURequestStepDistance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ function IOURequestStepDistance({
currentUserPersonalDetails.accountID,
participant,
'',
false,
{},
'',
'',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ function IOURequestStepScan({
currentUserPersonalDetails.accountID,
participant,
'',
false,
receipt,
);
} else {
Expand Down Expand Up @@ -335,6 +336,7 @@ function IOURequestStepScan({
currentUserPersonalDetails.accountID,
participant,
'',
false,
receipt,
'',
'',
Expand Down
2 changes: 2 additions & 0 deletions src/pages/iou/request/step/IOURequestStepScan/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ function IOURequestStepScan({
currentUserPersonalDetails.accountID,
participant,
'',
false,
receipt,
);
} else {
Expand Down Expand Up @@ -365,6 +366,7 @@ function IOURequestStepScan({
currentUserPersonalDetails.accountID,
participant,
'',
false,
receipt,
'',
'',
Expand Down
28 changes: 16 additions & 12 deletions src/pages/settings/Subscription/PaymentCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ function AddPaymentCard() {
const styles = useThemeStyles();
const {translate} = useLocalize();
const [privateSubscription] = useOnyx(ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION);
const [accountID] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.accountID ?? 0});

const subscriptionPlan = useSubscriptionPlan();
const subscriptionPrice = useSubscriptionPrice();
Expand All @@ -43,18 +44,21 @@ function AddPaymentCard() {
};
}, []);

const addPaymentCard = useCallback((values: FormOnyxValues<typeof ONYXKEYS.FORMS.ADD_PAYMENT_CARD_FORM>) => {
const cardData = {
cardNumber: CardUtils.getMCardNumberString(values.cardNumber),
cardMonth: CardUtils.getMonthFromExpirationDateString(values.expirationDate),
cardYear: CardUtils.getYearFromExpirationDateString(values.expirationDate),
cardCVV: values.securityCode,
addressName: values.nameOnCard,
addressZip: values.addressZipCode,
currency: values.currency ?? CONST.PAYMENT_CARD_CURRENCY.USD,
};
PaymentMethods.addSubscriptionPaymentCard(cardData);
}, []);
const addPaymentCard = useCallback(
(values: FormOnyxValues<typeof ONYXKEYS.FORMS.ADD_PAYMENT_CARD_FORM>) => {
const cardData = {
cardNumber: CardUtils.getMCardNumberString(values.cardNumber),
cardMonth: CardUtils.getMonthFromExpirationDateString(values.expirationDate),
cardYear: CardUtils.getYearFromExpirationDateString(values.expirationDate),
cardCVV: values.securityCode,
addressName: values.nameOnCard,
addressZip: values.addressZipCode,
currency: values.currency ?? CONST.PAYMENT_CARD_CURRENCY.USD,
};
PaymentMethods.addSubscriptionPaymentCard(accountID ?? 0, cardData);
},
[accountID],
);

const [formData] = useOnyx(ONYXKEYS.FORMS.ADD_PAYMENT_CARD_FORM);
const prevFormDataSetupComplete = usePrevious(!!formData?.setupComplete);
Expand Down
Loading

0 comments on commit d688a59

Please sign in to comment.