diff --git a/src/account/SupportContact.test.tsx b/src/account/SupportContact.test.tsx
index 9db3bd0524f..0dd97aa293b 100644
--- a/src/account/SupportContact.test.tsx
+++ b/src/account/SupportContact.test.tsx
@@ -81,7 +81,7 @@ describe('Contact', () => {
expect(Mailer.mail).toBeCalledWith(
expect.objectContaining({
isHTML: true,
- body: 'Test Message
{"version":"0.0.1","buildNumber":"1","apiLevel":-1,"os":"android","country":"US","region":null,"deviceId":"someDeviceId","deviceBrand":"someBrand","deviceModel":"someModel","address":"0x0000000000000000000000000000000000007e57","sessionId":"","numberVerifiedCentralized":false,"network":"alfajores"}
Support logs are attached...',
+ body: 'Test Message
{"version":"0.0.1","buildNumber":"1","apiLevel":-1,"os":"android","country":"US","region":null,"deviceId":"someDeviceId","deviceBrand":"someBrand","deviceModel":"someModel","address":"0x0000000000000000000000000000000000007e57","sessionId":"","numberVerifiedCentralized":false,"hooksPreviewEnabled":false,"network":"alfajores"}
Support logs are attached...',
recipients: [CELO_SUPPORT_EMAIL_ADDRESS],
subject: i18n.t('supportEmailSubject', { appName: APP_NAME, user: '+1415555XXXX' }),
attachments: logAttachments,
@@ -134,6 +134,7 @@ describe('Contact', () => {
deviceBrand: 'someBrand',
deviceId: 'someDeviceId',
deviceModel: 'someModel',
+ hooksPreviewEnabled: false,
network: 'alfajores',
numberVerifiedCentralized: false,
os: 'android',
diff --git a/src/account/SupportContact.tsx b/src/account/SupportContact.tsx
index 6186f62e4ba..83718cdbda1 100644
--- a/src/account/SupportContact.tsx
+++ b/src/account/SupportContact.tsx
@@ -20,6 +20,7 @@ import { navigateBack } from 'src/navigator/NavigationService'
import { Screens } from 'src/navigator/Screens'
import { StackParamList } from 'src/navigator/types'
import { userLocationDataSelector } from 'src/networkInfo/selectors'
+import { hooksPreviewApiUrlSelector } from 'src/positions/selectors'
import { getFeatureGate } from 'src/statsig'
import { StatsigFeatureGates } from 'src/statsig/types'
import colors from 'src/styles/colors'
@@ -71,6 +72,7 @@ function SupportContact({ route }: Props) {
const sessionId = useSelector(sessionIdSelector)
const numberVerifiedCentralized = useSelector(numberVerifiedCentrallySelector)
const { countryCodeAlpha2: country, region } = useSelector(userLocationDataSelector)
+ const hooksPreviewApiUrl = useSelector(hooksPreviewApiUrlSelector)
const dispatch = useDispatch()
const prefilledText = route.params?.prefilledText
@@ -100,6 +102,7 @@ function SupportContact({ route }: Props) {
address: currentAccount,
sessionId,
numberVerifiedCentralized,
+ hooksPreviewEnabled: !!hooksPreviewApiUrl,
network: DEFAULT_TESTNET,
}
const userId = e164PhoneNumber ? anonymizedPhone(e164PhoneNumber) : t('unknown')
diff --git a/src/analytics/Events.tsx b/src/analytics/Events.tsx
index 2f3d5febf27..d505e29e546 100644
--- a/src/analytics/Events.tsx
+++ b/src/analytics/Events.tsx
@@ -606,6 +606,14 @@ export enum NftEvents {
nft_image_load = 'nft_image_load', // When an NFT attempted to load contains error boolean for success or failure
}
+export enum BuilderHooksEvents {
+ hooks_enable_preview_propose = 'hooks_enable_preview_propose', // When a user scans a QR code or opens a deep link to enable hooks preview
+ hooks_enable_preview_cancel = 'hooks_enable_preview_cancel', // When a user cancels the hooks preview flow
+ hooks_enable_preview_confirm = 'hooks_enable_preview_confirm', // When a user confirms enabling hooks preview
+ hooks_enable_preview_error = 'hooks_enable_preview_error', // When a user encounters an error enabling hooks preview
+ hooks_disable_preview = 'hooks_disable_preview', // When a user disables hooks preview
+}
+
export type AnalyticsEventType =
| AppEvents
| HomeEvents
@@ -636,3 +644,4 @@ export type AnalyticsEventType =
| CeloNewsEvents
| TokenBottomSheetEvents
| AssetsEvents
+ | BuilderHooksEvents
diff --git a/src/analytics/Properties.tsx b/src/analytics/Properties.tsx
index c9d23c1733d..fbc24e69843 100644
--- a/src/analytics/Properties.tsx
+++ b/src/analytics/Properties.tsx
@@ -10,6 +10,7 @@ import {
AppEvents,
AssetsEvents,
AuthenticationEvents,
+ BuilderHooksEvents,
CeloExchangeEvents,
CeloNewsEvents,
CICOEvents,
@@ -44,6 +45,7 @@ import {
import {
BackQuizProgress,
DappRequestOrigin,
+ HooksEnablePreviewOrigin,
ScrollDirection,
SendOrigin,
WalletConnectPairingOrigin,
@@ -1298,6 +1300,18 @@ interface NftsEventsProperties {
}
}
+interface BuilderHooksProperties {
+ [BuilderHooksEvents.hooks_enable_preview_propose]: {
+ origin: HooksEnablePreviewOrigin
+ }
+ [BuilderHooksEvents.hooks_enable_preview_confirm]: undefined
+ [BuilderHooksEvents.hooks_enable_preview_cancel]: undefined
+ [BuilderHooksEvents.hooks_enable_preview_error]: {
+ error: string
+ }
+ [BuilderHooksEvents.hooks_disable_preview]: undefined
+}
+
export type AnalyticsPropertiesList = AppEventsProperties &
HomeEventsProperties &
SettingsEventsProperties &
@@ -1330,4 +1344,5 @@ export type AnalyticsPropertiesList = AppEventsProperties &
QrScreenProperties &
TokenBottomSheetEventsProperties &
AssetsEventsProperties &
- NftsEventsProperties
+ NftsEventsProperties &
+ BuilderHooksProperties
diff --git a/src/analytics/ValoraAnalytics.test.ts b/src/analytics/ValoraAnalytics.test.ts
index bcd6f562b3c..3947236544d 100644
--- a/src/analytics/ValoraAnalytics.test.ts
+++ b/src/analytics/ValoraAnalytics.test.ts
@@ -129,6 +129,7 @@ const defaultSuperProperties = {
sHasCompletedBackup: false,
sHasVerifiedNumber: false,
sHasVerifiedNumberCPV: true,
+ sHooksPreviewEnabled: false,
sLanguage: 'es-419',
sLocalCurrencyCode: 'PHP',
sNetWorthUsd: 43.910872728527195,
diff --git a/src/analytics/selectors.test.ts b/src/analytics/selectors.test.ts
index 789185c52a2..4cceb087de4 100644
--- a/src/analytics/selectors.test.ts
+++ b/src/analytics/selectors.test.ts
@@ -245,6 +245,7 @@ describe('getCurrentUserTraits', () => {
hasCompletedBackup: false,
hasVerifiedNumber: false,
hasVerifiedNumberCPV: true,
+ hooksPreviewEnabled: false,
language: 'es-419',
localCurrencyCode: 'PHP',
netWorthUsd: 5764.949123945,
diff --git a/src/analytics/selectors.ts b/src/analytics/selectors.ts
index 28d6b339341..b2da33a626c 100644
--- a/src/analytics/selectors.ts
+++ b/src/analytics/selectors.ts
@@ -12,6 +12,7 @@ import { getLocalCurrencyCode } from 'src/localCurrency/selectors'
import { userLocationDataSelector } from 'src/networkInfo/selectors'
import { getPositionBalanceUsd } from 'src/positions/getPositionBalanceUsd'
import {
+ hooksPreviewApiUrlSelector,
positionsByBalanceUsdSelector,
totalPositionsBalanceUsdSelector,
} from 'src/positions/selectors'
@@ -25,8 +26,8 @@ const tokensSelector = createSelector(
)
const positionsAnalyticsSelector = createSelector(
- [positionsByBalanceUsdSelector, totalPositionsBalanceUsdSelector],
- (positionsByUsdBalance, totalPositionsBalanceUsd) => {
+ [positionsByBalanceUsdSelector, totalPositionsBalanceUsdSelector, hooksPreviewApiUrlSelector],
+ (positionsByUsdBalance, totalPositionsBalanceUsd, hooksPreviewApiUrl) => {
const appsByBalanceUsd: Record = {}
for (const position of positionsByUsdBalance) {
const appId = position.appId
@@ -61,6 +62,7 @@ const positionsAnalyticsSelector = createSelector(
.slice(0, 10)
.map(([appId, balanceUsd]) => `${appId}:${balanceUsd.toFixed(2)}`)
.join(','),
+ hooksPreviewEnabled: !!hooksPreviewApiUrl,
}
}
)
@@ -93,6 +95,7 @@ export const getCurrentUserTraits = createSelector(
topTenPositions,
positionsAppsCount,
positionsTopTenApps,
+ hooksPreviewEnabled,
},
localCurrencyCode,
{ numberVerifiedDecentralized, numberVerifiedCentralized },
@@ -152,6 +155,7 @@ export const getCurrentUserTraits = createSelector(
topTenPositions,
positionsAppsCount,
positionsTopTenApps,
+ hooksPreviewEnabled,
localCurrencyCode,
hasVerifiedNumber: numberVerifiedDecentralized,
hasVerifiedNumberCPV: numberVerifiedCentralized,
diff --git a/src/analytics/types.ts b/src/analytics/types.ts
index cdc5a905be6..71d4dd4d0ae 100644
--- a/src/analytics/types.ts
+++ b/src/analytics/types.ts
@@ -26,3 +26,9 @@ export enum DappRequestOrigin {
InAppWebView = 'in_app_web_view',
External = 'external',
}
+
+// Origin of Hooks enable preview
+export enum HooksEnablePreviewOrigin {
+ Scan = 'scan',
+ Deeplink = 'deeplink',
+}
diff --git a/src/app/saga.test.ts b/src/app/saga.test.ts
index 912dce85fe4..b028dd3c190 100644
--- a/src/app/saga.test.ts
+++ b/src/app/saga.test.ts
@@ -8,7 +8,7 @@ import { EffectProviders, StaticProvider } from 'redux-saga-test-plan/providers'
import { call, select } from 'redux-saga/effects'
import { e164NumberSelector } from 'src/account/selectors'
import { AppEvents, InviteEvents } from 'src/analytics/Events'
-import { WalletConnectPairingOrigin } from 'src/analytics/types'
+import { HooksEnablePreviewOrigin, WalletConnectPairingOrigin } from 'src/analytics/types'
import ValoraAnalytics from 'src/analytics/ValoraAnalytics'
import {
appLock,
@@ -211,7 +211,10 @@ describe('handleDeepLink', () => {
.provide([[select(allowHooksPreviewSelector), true]])
.run()
- expect(handleEnableHooksPreviewDeepLink).toHaveBeenCalledWith(deepLink)
+ expect(handleEnableHooksPreviewDeepLink).toHaveBeenCalledWith(
+ deepLink,
+ HooksEnablePreviewOrigin.Deeplink
+ )
})
})
diff --git a/src/app/saga.ts b/src/app/saga.ts
index ec32f60823d..7be9f401813 100644
--- a/src/app/saga.ts
+++ b/src/app/saga.ts
@@ -23,6 +23,7 @@ import {
} from 'redux-saga/effects'
import { e164NumberSelector } from 'src/account/selectors'
import { AppEvents, InviteEvents } from 'src/analytics/Events'
+import { HooksEnablePreviewOrigin } from 'src/analytics/types'
import ValoraAnalytics from 'src/analytics/ValoraAnalytics'
import {
Actions,
@@ -373,7 +374,7 @@ export function* handleDeepLink(action: OpenDeepLink) {
(yield select(allowHooksPreviewSelector)) &&
rawParams.pathname === '/hooks/enablePreview'
) {
- yield call(handleEnableHooksPreviewDeepLink, deepLink)
+ yield call(handleEnableHooksPreviewDeepLink, deepLink, HooksEnablePreviewOrigin.Deeplink)
}
}
}
diff --git a/src/positions/HooksPreviewModeBanner.tsx b/src/positions/HooksPreviewModeBanner.tsx
index 4b9d8e06b30..c60cef7d726 100644
--- a/src/positions/HooksPreviewModeBanner.tsx
+++ b/src/positions/HooksPreviewModeBanner.tsx
@@ -4,6 +4,8 @@ import { StyleSheet, Text } from 'react-native'
import Animated, { SlideInUp, SlideOutUp } from 'react-native-reanimated'
import { SafeAreaView } from 'react-native-safe-area-context'
import { useDispatch, useSelector } from 'react-redux'
+import { BuilderHooksEvents } from 'src/analytics/Events'
+import ValoraAnalytics from 'src/analytics/ValoraAnalytics'
import Touchable from 'src/components/Touchable'
import { hooksPreviewApiUrlSelector, hooksPreviewStatusSelector } from 'src/positions/selectors'
import { previewModeDisabled } from 'src/positions/slice'
@@ -25,6 +27,11 @@ export default function HooksPreviewModeBanner() {
const { t } = useTranslation()
const status = useSelector(hooksPreviewStatusSelector)
+ function onPress() {
+ ValoraAnalytics.track(BuilderHooksEvents.hooks_disable_preview)
+ dispatch(previewModeDisabled())
+ }
+
if (!hooksPreviewApiUrl) {
return null
}
@@ -36,10 +43,7 @@ export default function HooksPreviewModeBanner() {
entering={SlideInUp}
exiting={SlideOutUp}
>
- dispatch(previewModeDisabled())}
- hitSlop={{ top: 10, right: 10, bottom: 10, left: 10 }}
- >
+
{t('hooksPreview.bannerTitle')}
diff --git a/src/positions/saga.test.ts b/src/positions/saga.test.ts
index ff93af48c82..c13581509ad 100644
--- a/src/positions/saga.test.ts
+++ b/src/positions/saga.test.ts
@@ -2,6 +2,7 @@ import { FetchMock } from 'jest-fetch-mock/types'
import { Platform } from 'react-native'
import { expectSaga } from 'redux-saga-test-plan'
import { call, select } from 'redux-saga/effects'
+import { HooksEnablePreviewOrigin } from 'src/analytics/types'
import {
fetchPositionsSaga,
fetchShortcutsSaga,
@@ -175,7 +176,7 @@ describe(handleEnableHooksPreviewDeepLink, () => {
it('enables hooks preview if the deep link is valid and the user confirms', async () => {
Platform.OS = 'android'
- await expectSaga(handleEnableHooksPreviewDeepLink, deepLink)
+ await expectSaga(handleEnableHooksPreviewDeepLink, deepLink, HooksEnablePreviewOrigin.Deeplink)
.provide([[call(_confirmEnableHooksPreview), true]])
.put(previewModeEnabled('http://192.168.0.42.sslip.io:18000/')) // Uses sslip.io for Android
.run()
@@ -183,21 +184,25 @@ describe(handleEnableHooksPreviewDeepLink, () => {
it('uses the direct IP on iOS if the deep link is valid and the user confirms', async () => {
Platform.OS = 'ios'
- await expectSaga(handleEnableHooksPreviewDeepLink, deepLink)
+ await expectSaga(handleEnableHooksPreviewDeepLink, deepLink, HooksEnablePreviewOrigin.Deeplink)
.provide([[call(_confirmEnableHooksPreview), true]])
.put(previewModeEnabled('http://192.168.0.42:18000'))
.run()
})
it('does nothing if the deep link is invalid', async () => {
- await expectSaga(handleEnableHooksPreviewDeepLink, 'invalid-link')
+ await expectSaga(
+ handleEnableHooksPreviewDeepLink,
+ 'invalid-link',
+ HooksEnablePreviewOrigin.Deeplink
+ )
.provide([[call(_confirmEnableHooksPreview), true]])
.not.put.actionType(previewModeEnabled.type)
.run()
})
it("does nothing if the user doesn't confirm", async () => {
- await expectSaga(handleEnableHooksPreviewDeepLink, deepLink)
+ await expectSaga(handleEnableHooksPreviewDeepLink, deepLink, HooksEnablePreviewOrigin.Deeplink)
.provide([[call(_confirmEnableHooksPreview), false]])
.not.put.actionType(previewModeEnabled.type)
.run()
diff --git a/src/positions/saga.ts b/src/positions/saga.ts
index 75c88352ad5..7473ab5dacc 100644
--- a/src/positions/saga.ts
+++ b/src/positions/saga.ts
@@ -3,6 +3,9 @@ import path from 'path'
import { Alert, Platform } from 'react-native'
import { call, put, select, spawn, takeLeading } from 'redux-saga/effects'
import { showError } from 'src/alert/actions'
+import { BuilderHooksEvents } from 'src/analytics/Events'
+import { HooksEnablePreviewOrigin } from 'src/analytics/types'
+import ValoraAnalytics from 'src/analytics/ValoraAnalytics'
import { ErrorMessages } from 'src/app/ErrorMessages'
import { DEFAULT_TESTNET } from 'src/config'
import i18n from 'src/i18n'
@@ -144,7 +147,11 @@ function confirmEnableHooksPreview() {
// Export for testing
export const _confirmEnableHooksPreview = confirmEnableHooksPreview
-export function* handleEnableHooksPreviewDeepLink(deeplink: string) {
+export function* handleEnableHooksPreviewDeepLink(
+ deeplink: string,
+ origin: HooksEnablePreviewOrigin
+) {
+ ValoraAnalytics.track(BuilderHooksEvents.hooks_enable_preview_propose, { origin })
let hooksPreviewApiUrl: string | null = null
try {
hooksPreviewApiUrl = new URL(deeplink).searchParams.get('hooksApiUrl')
@@ -161,6 +168,9 @@ export function* handleEnableHooksPreviewDeepLink(deeplink: string) {
}
} catch (error) {
Logger.warn(TAG, 'Unable to parse hooks preview deeplink', error)
+ ValoraAnalytics.track(BuilderHooksEvents.hooks_enable_preview_error, {
+ error: error?.message || error?.toString(),
+ })
}
if (!hooksPreviewApiUrl) {
@@ -170,8 +180,11 @@ export function* handleEnableHooksPreviewDeepLink(deeplink: string) {
const confirm = yield call(confirmEnableHooksPreview)
if (confirm) {
+ ValoraAnalytics.track(BuilderHooksEvents.hooks_enable_preview_confirm)
Logger.info(TAG, `Enabling hooks preview mode with API URL: ${hooksPreviewApiUrl}`)
yield put(previewModeEnabled(hooksPreviewApiUrl))
+ } else {
+ ValoraAnalytics.track(BuilderHooksEvents.hooks_enable_preview_cancel)
}
}
diff --git a/src/qrcode/utils.test.tsx b/src/qrcode/utils.test.tsx
index 9159c59534b..1040e1f09ed 100644
--- a/src/qrcode/utils.test.tsx
+++ b/src/qrcode/utils.test.tsx
@@ -4,6 +4,7 @@ import 'react-native'
import { View } from 'react-native'
import { expectSaga } from 'redux-saga-test-plan'
import { select } from 'redux-saga/effects'
+import { HooksEnablePreviewOrigin } from 'src/analytics/types'
import { handleEnableHooksPreviewDeepLink } from 'src/positions/saga'
import { allowHooksPreviewSelector } from 'src/positions/selectors'
import { urlFromUriData } from 'src/qrcode/schema'
@@ -63,6 +64,9 @@ describe('handleBarcode', () => {
.provide([[select(allowHooksPreviewSelector), true]])
.run()
- expect(handleEnableHooksPreviewDeepLink).toHaveBeenCalledWith(link)
+ expect(handleEnableHooksPreviewDeepLink).toHaveBeenCalledWith(
+ link,
+ HooksEnablePreviewOrigin.Scan
+ )
})
})
diff --git a/src/qrcode/utils.ts b/src/qrcode/utils.ts
index 58f409b0421..aacad4f1899 100644
--- a/src/qrcode/utils.ts
+++ b/src/qrcode/utils.ts
@@ -4,7 +4,11 @@ import Share from 'react-native-share'
import { call, fork, put, select } from 'redux-saga/effects'
import { showError, showMessage } from 'src/alert/actions'
import { SendEvents } from 'src/analytics/Events'
-import { SendOrigin, WalletConnectPairingOrigin } from 'src/analytics/types'
+import {
+ HooksEnablePreviewOrigin,
+ SendOrigin,
+ WalletConnectPairingOrigin,
+} from 'src/analytics/types'
import ValoraAnalytics from 'src/analytics/ValoraAnalytics'
import { ErrorMessages } from 'src/app/ErrorMessages'
import { paymentDeepLinkHandlerSelector, phoneNumberVerifiedSelector } from 'src/app/selectors'
@@ -150,7 +154,7 @@ export function* handleBarcode(
(yield select(allowHooksPreviewSelector)) &&
barcode.data.startsWith('celo://wallet/hooks/enablePreview')
) {
- yield call(handleEnableHooksPreviewDeepLink, barcode.data)
+ yield call(handleEnableHooksPreviewDeepLink, barcode.data, HooksEnablePreviewOrigin.Scan)
return
}