Skip to content

Commit

Permalink
feat: add analytics events around hooks preview mode (#3953)
Browse files Browse the repository at this point in the history
### Description

Title says it all ;)

### Test plan

- Updated unit tests

### Related issues

- Fixes RET-750

### Backwards compatibility

Yes
  • Loading branch information
jeanregisser authored Jul 13, 2023
1 parent da51471 commit 721d464
Show file tree
Hide file tree
Showing 15 changed files with 93 additions and 19 deletions.
3 changes: 2 additions & 1 deletion src/account/SupportContact.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ describe('Contact', () => {
expect(Mailer.mail).toBeCalledWith(
expect.objectContaining({
isHTML: true,
body: 'Test Message<br/><br/><b>{"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"}</b><br/><br/><b>Support logs are attached...</b>',
body: 'Test Message<br/><br/><b>{"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"}</b><br/><br/><b>Support logs are attached...</b>',
recipients: [CELO_SUPPORT_EMAIL_ADDRESS],
subject: i18n.t('supportEmailSubject', { appName: APP_NAME, user: '+1415555XXXX' }),
attachments: logAttachments,
Expand Down Expand Up @@ -134,6 +134,7 @@ describe('Contact', () => {
deviceBrand: 'someBrand',
deviceId: 'someDeviceId',
deviceModel: 'someModel',
hooksPreviewEnabled: false,
network: 'alfajores',
numberVerifiedCentralized: false,
os: 'android',
Expand Down
3 changes: 3 additions & 0 deletions src/account/SupportContact.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -100,6 +102,7 @@ function SupportContact({ route }: Props) {
address: currentAccount,
sessionId,
numberVerifiedCentralized,
hooksPreviewEnabled: !!hooksPreviewApiUrl,
network: DEFAULT_TESTNET,
}
const userId = e164PhoneNumber ? anonymizedPhone(e164PhoneNumber) : t('unknown')
Expand Down
9 changes: 9 additions & 0 deletions src/analytics/Events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -636,3 +644,4 @@ export type AnalyticsEventType =
| CeloNewsEvents
| TokenBottomSheetEvents
| AssetsEvents
| BuilderHooksEvents
17 changes: 16 additions & 1 deletion src/analytics/Properties.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
AppEvents,
AssetsEvents,
AuthenticationEvents,
BuilderHooksEvents,
CeloExchangeEvents,
CeloNewsEvents,
CICOEvents,
Expand Down Expand Up @@ -44,6 +45,7 @@ import {
import {
BackQuizProgress,
DappRequestOrigin,
HooksEnablePreviewOrigin,
ScrollDirection,
SendOrigin,
WalletConnectPairingOrigin,
Expand Down Expand Up @@ -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 &
Expand Down Expand Up @@ -1330,4 +1344,5 @@ export type AnalyticsPropertiesList = AppEventsProperties &
QrScreenProperties &
TokenBottomSheetEventsProperties &
AssetsEventsProperties &
NftsEventsProperties
NftsEventsProperties &
BuilderHooksProperties
1 change: 1 addition & 0 deletions src/analytics/ValoraAnalytics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ const defaultSuperProperties = {
sHasCompletedBackup: false,
sHasVerifiedNumber: false,
sHasVerifiedNumberCPV: true,
sHooksPreviewEnabled: false,
sLanguage: 'es-419',
sLocalCurrencyCode: 'PHP',
sNetWorthUsd: 43.910872728527195,
Expand Down
1 change: 1 addition & 0 deletions src/analytics/selectors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ describe('getCurrentUserTraits', () => {
hasCompletedBackup: false,
hasVerifiedNumber: false,
hasVerifiedNumberCPV: true,
hooksPreviewEnabled: false,
language: 'es-419',
localCurrencyCode: 'PHP',
netWorthUsd: 5764.949123945,
Expand Down
8 changes: 6 additions & 2 deletions src/analytics/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -25,8 +26,8 @@ const tokensSelector = createSelector(
)

const positionsAnalyticsSelector = createSelector(
[positionsByBalanceUsdSelector, totalPositionsBalanceUsdSelector],
(positionsByUsdBalance, totalPositionsBalanceUsd) => {
[positionsByBalanceUsdSelector, totalPositionsBalanceUsdSelector, hooksPreviewApiUrlSelector],
(positionsByUsdBalance, totalPositionsBalanceUsd, hooksPreviewApiUrl) => {
const appsByBalanceUsd: Record<string, BigNumber> = {}
for (const position of positionsByUsdBalance) {
const appId = position.appId
Expand Down Expand Up @@ -61,6 +62,7 @@ const positionsAnalyticsSelector = createSelector(
.slice(0, 10)
.map(([appId, balanceUsd]) => `${appId}:${balanceUsd.toFixed(2)}`)
.join(','),
hooksPreviewEnabled: !!hooksPreviewApiUrl,
}
}
)
Expand Down Expand Up @@ -93,6 +95,7 @@ export const getCurrentUserTraits = createSelector(
topTenPositions,
positionsAppsCount,
positionsTopTenApps,
hooksPreviewEnabled,
},
localCurrencyCode,
{ numberVerifiedDecentralized, numberVerifiedCentralized },
Expand Down Expand Up @@ -152,6 +155,7 @@ export const getCurrentUserTraits = createSelector(
topTenPositions,
positionsAppsCount,
positionsTopTenApps,
hooksPreviewEnabled,
localCurrencyCode,
hasVerifiedNumber: numberVerifiedDecentralized,
hasVerifiedNumberCPV: numberVerifiedCentralized,
Expand Down
6 changes: 6 additions & 0 deletions src/analytics/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
}
7 changes: 5 additions & 2 deletions src/app/saga.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -211,7 +211,10 @@ describe('handleDeepLink', () => {
.provide([[select(allowHooksPreviewSelector), true]])
.run()

expect(handleEnableHooksPreviewDeepLink).toHaveBeenCalledWith(deepLink)
expect(handleEnableHooksPreviewDeepLink).toHaveBeenCalledWith(
deepLink,
HooksEnablePreviewOrigin.Deeplink
)
})
})

Expand Down
3 changes: 2 additions & 1 deletion src/app/saga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
}
}
}
Expand Down
12 changes: 8 additions & 4 deletions src/positions/HooksPreviewModeBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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
}
Expand All @@ -36,10 +43,7 @@ export default function HooksPreviewModeBanner() {
entering={SlideInUp}
exiting={SlideOutUp}
>
<Touchable
onPress={() => dispatch(previewModeDisabled())}
hitSlop={{ top: 10, right: 10, bottom: 10, left: 10 }}
>
<Touchable onPress={onPress} hitSlop={{ top: 10, right: 10, bottom: 10, left: 10 }}>
<Text style={styles.text} numberOfLines={1}>
{t('hooksPreview.bannerTitle')}
</Text>
Expand Down
13 changes: 9 additions & 4 deletions src/positions/saga.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -175,29 +176,33 @@ 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()
})

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()
Expand Down
15 changes: 14 additions & 1 deletion src/positions/saga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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')
Expand All @@ -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) {
Expand All @@ -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)
}
}

Expand Down
6 changes: 5 additions & 1 deletion src/qrcode/utils.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -63,6 +64,9 @@ describe('handleBarcode', () => {
.provide([[select(allowHooksPreviewSelector), true]])
.run()

expect(handleEnableHooksPreviewDeepLink).toHaveBeenCalledWith(link)
expect(handleEnableHooksPreviewDeepLink).toHaveBeenCalledWith(
link,
HooksEnablePreviewOrigin.Scan
)
})
})
8 changes: 6 additions & 2 deletions src/qrcode/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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
}

Expand Down

0 comments on commit 721d464

Please sign in to comment.