Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Safe creation AB test #1001

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/components/common/NetworkSelector/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ const NetworkSelector = (): ReactElement => {

trackEvent({ ...OVERVIEW_EVENTS.SWITCH_NETWORK, label: selectedChainId })

const shouldKeepPath = [AppRoutes.load, AppRoutes.open].includes(router.pathname)
const shouldKeepPath = [AppRoutes.load, AppRoutes.open, AppRoutes.newSafe.create, AppRoutes.newSafe.add].includes(
router.pathname,
)

const newRoute = {
pathname: shouldKeepPath ? router.pathname : '/',
Expand Down
4 changes: 3 additions & 1 deletion src/components/safe-apps/SafeAppLandingPage/AppActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { AppRoutes } from '@/config/routes'
import useOwnedSafes from '@/hooks/useOwnedSafes'
import { CTA_BUTTON_WIDTH, CTA_HEIGHT } from '@/components/safe-apps/SafeAppLandingPage/constants'
import CreateNewSafeSVG from '@/public/images/open/safe-creation.svg'
import useNewSafeRoutes from '@/hooks/useNewSafeRoutes'

type Props = {
appUrl: string
Expand All @@ -30,6 +31,7 @@ const AppActions = ({ wallet, onConnectWallet, chain, appUrl, app }: Props): Rea
const lastUsedSafe = useLastSafe()
const ownedSafes = useOwnedSafes()
const addressBook = useAppSelector(selectAllAddressBooks)
const { createSafe } = useNewSafeRoutes()
const chains = useAppSelector(selectChains)
const compatibleChains = app.chainIds

Expand Down Expand Up @@ -71,7 +73,7 @@ const AppActions = ({ wallet, onConnectWallet, chain, appUrl, app }: Props): Rea
case shouldCreateSafe:
const redirect = `${AppRoutes.apps}?appUrl=${appUrl}`
const createSafeHrefWithRedirect: UrlObject = {
pathname: AppRoutes.open,
pathname: createSafe,
query: { safeViewRedirectURL: redirect, chain: chain.shortName },
}
button = (
Expand Down
6 changes: 4 additions & 2 deletions src/components/welcome/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import { Button, Divider, Grid, Paper, Typography } from '@mui/material'
import { useRouter } from 'next/router'
import { CREATE_SAFE_EVENTS, LOAD_SAFE_EVENTS } from '@/services/analytics/events/createLoadSafe'
import Track from '../common/Track'
import useNewSafeRoutes from '@/hooks/useNewSafeRoutes'

const NewSafe = () => {
const router = useRouter()
const { createSafe, addSafe } = useNewSafeRoutes()

return (
<>
Expand All @@ -28,7 +30,7 @@ const NewSafe = () => {
for creating your new Safe.
</Typography>
<Track {...CREATE_SAFE_EVENTS.CREATE_BUTTON}>
<Button variant="contained" onClick={() => router.push('/open')}>
<Button variant="contained" onClick={() => router.push(createSafe)}>
+ Create new Safe
</Button>
</Track>
Expand All @@ -43,7 +45,7 @@ const NewSafe = () => {
address.
</Typography>
<Track {...LOAD_SAFE_EVENTS.LOAD_BUTTON}>
<Button variant="outlined" onClick={() => router.push('/load')}>
<Button variant="outlined" onClick={() => router.push(addSafe)}>
Add existing Safe
</Button>
</Track>
Expand Down
5 changes: 4 additions & 1 deletion src/config/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ export const AppRoutes = {
load: '/load',
index: '/',
home: '/home',
createSafe: '/create-safe',
apps: '/apps',
addressBook: '/address-book',
balances: {
nfts: '/balances/nfts',
index: '/balances',
},
newSafe: {
create: '/new-safe/create',
add: '/new-safe/add',
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was added in anticipation of the add Safe revamp.

},
settings: {
spendingLimits: '/settings/spending-limits',
setup: '/settings/setup',
Expand Down
18 changes: 18 additions & 0 deletions src/hooks/useABTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { localItem } from '@/services/local-storage/local'
import useLocalStorage from '@/services/local-storage/useLocalStorage'

const getAbTestKey = (name: string) => {
return `AB__${name}`
}

export const getAbTestIsB = (name: string) => {
return localItem<boolean>(getAbTestKey(name)).get()
}

const useABTest = (name: string): boolean => {
const [isB] = useLocalStorage<boolean>(getAbTestKey(name), Math.random() > 0.5, true)

return isB
}

export default useABTest
15 changes: 15 additions & 0 deletions src/hooks/useNewSafeRoutes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import useABTest from '@/hooks/useABTest'
import { AppRoutes } from '@/config/routes'

export const NEW_SAFE_AB_TEST_NAME = 'newSafe'

const useNewSafeRoutes = () => {
const shouldUseNewRoute = useABTest(NEW_SAFE_AB_TEST_NAME)

return {
createSafe: shouldUseNewRoute ? AppRoutes.newSafe.create : AppRoutes.open,
addSafe: shouldUseNewRoute ? AppRoutes.newSafe.add : AppRoutes.load,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppRoutes.newSafe.add uses the AppRoutes.load component for now.

}
}

export default useNewSafeRoutes
17 changes: 0 additions & 17 deletions src/pages/demo.tsx

This file was deleted.

3 changes: 3 additions & 0 deletions src/pages/new-safe/add.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Load from '../load'

export default <Load />
File renamed without changes.
18 changes: 18 additions & 0 deletions src/services/analytics/events/createLoadSafe.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,77 @@
import { getAbTestIsB } from '@/hooks/useABTest'
import { NEW_SAFE_AB_TEST_NAME } from '@/hooks/useNewSafeRoutes'
import { EventType } from '@/services/analytics/types'

export const CREATE_SAFE_CATEGORY = 'create-safe'

// We cannot read the value immediately as the option may not have been decided yet
const isB = () => {
return getAbTestIsB(NEW_SAFE_AB_TEST_NAME) ? 'New' : 'Old'
}

export const CREATE_SAFE_EVENTS = {
CREATE_BUTTON: {
action: 'Open stepper',
category: CREATE_SAFE_CATEGORY,
abTest: isB,
},
NAME_SAFE: {
event: EventType.META,
action: 'Name Safe',
category: CREATE_SAFE_CATEGORY,
abTest: isB,
},
OWNERS: {
event: EventType.META,
action: 'Owners',
category: CREATE_SAFE_CATEGORY,
abTest: isB,
},
THRESHOLD: {
event: EventType.META,
action: 'Threshold',
category: CREATE_SAFE_CATEGORY,
abTest: isB,
},
SUBMIT_CREATE_SAFE: {
event: EventType.META,
action: 'Submit Safe creation',
category: CREATE_SAFE_CATEGORY,
abTest: isB,
},
REJECT_CREATE_SAFE: {
event: EventType.META,
action: 'Reject Safe creation',
category: CREATE_SAFE_CATEGORY,
abTest: isB,
},
RETRY_CREATE_SAFE: {
event: EventType.META,
action: 'Retry Safe creation',
category: CREATE_SAFE_CATEGORY,
abTest: isB,
},
CANCEL_CREATE_SAFE: {
event: EventType.META,
action: 'Cancel Safe creation',
category: CREATE_SAFE_CATEGORY,
abTest: isB,
},
CREATED_SAFE: {
event: EventType.META,
action: 'Created Safe',
category: CREATE_SAFE_CATEGORY,
abTest: isB,
},
GET_STARTED: {
action: 'Load Safe',
category: CREATE_SAFE_CATEGORY,
abTest: isB,
},
GO_TO_SAFE: {
action: 'Open Safe',
category: CREATE_SAFE_CATEGORY,
abTest: isB,
},
}

Expand Down
5 changes: 5 additions & 0 deletions src/services/analytics/gtm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ type ActionGtmEvent = GtmEvent & {
eventCategory: string
eventAction: string
eventLabel?: EventLabel
abTest?: string
}

type PageviewGtmEvent = GtmEvent & {
Expand Down Expand Up @@ -118,6 +119,10 @@ export const gtmTrack = (eventData: AnalyticsEvent): void => {
gtmEvent.eventLabel = eventData.label
}

if (eventData.abTest) {
gtmEvent.abTest = eventData.abTest()
}

gtmSend(gtmEvent)
}

Expand Down
1 change: 1 addition & 0 deletions src/services/analytics/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type AnalyticsEvent = {
category: string
action: string
label?: EventLabel
abTest?: () => string
}

export type SafeAppEvent = {
Expand Down
39 changes: 29 additions & 10 deletions src/services/local-storage/useLocalStorage.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,38 @@
import type { Dispatch, SetStateAction } from 'react'
import { useState, useCallback, useEffect } from 'react'
import { useState, useCallback } from 'react'
import local from './local'

const useLocalStorage = <T>(key: string, initialState: T): [T, Dispatch<SetStateAction<T>>] => {
const [cache, setCache] = useState<T>(initialState)
/**
* Locally persisted equivalent of `useState` that saves to `localStorage` when `setNewValue` is called
* or (initially) when `shouldPersistInitialState` is `true`
*
* @param key `localStorage` key to store under
* @param initialState default state to return if no `localStorage` value exists
* @param shouldPersistInitialState if no `localStorage` value exists, persist the `initialState` in `localStorage`
* @returns persisted state if it exists, otherwise `initialState`
*/

useEffect(() => {
const initialValue = local.getItem<T>(key)
if (initialValue !== undefined) {
setCache(initialValue)
const useLocalStorage = <T>(
key: string,
initialState: T,
shouldPersistInitialState = false,
): [T, Dispatch<SetStateAction<T>>] => {
const [cache, setCache] = useState<T>(() => {
const value = local.getItem<T>(key)

if (value !== undefined) {
return value
}

if (shouldPersistInitialState) {
local.setItem(key, initialState)
}
}, [setCache, key])

const setNewValue = useCallback(
(value: T | ((prevState: T) => T)) => {
return initialState
})

const setNewValue: Dispatch<SetStateAction<T>> = useCallback(
(value) => {
setCache((prevState) => {
const newState = value instanceof Function ? value(prevState) : value

Expand Down