feat: implement connect wallet category events (#4111)

* init commit

* wallet connected event init commit

* add received_swap_quote event property

* add page context, connect wallet event log

* add received_swap_quote property

* fix typo

* respond to cmcewen comments

* respond to vm comments

* move trace to app.tsx from header

* respond to vm comments
This commit is contained in:
lynn 2022-07-18 17:26:29 -04:00 committed by GitHub
parent 47fe9911fa
commit f41580e43c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 228 additions and 93 deletions

@ -5,14 +5,22 @@
* and logged.
*/
export enum EventName {
CONNECT_WALLET_BUTTON_CLICKED = 'Connect Wallet Button Clicked',
PAGE_VIEWED = 'Page Viewed',
SWAP_SUBMITTED = 'Swap Submitted',
TOKEN_IMPORTED = 'Token Imported',
TOKEN_SELECTED = 'Token Selected',
TOKEN_SELECTOR_OPENED = 'Token Selector Opened',
WALLET_CONNECT_TXN_COMPLETED = 'Wallet Connect Transaction Completed',
WALLET_SELECTED = 'Wallet Selected',
// alphabetize additional event names.
}
export enum WALLET_CONNECTION_RESULT {
SUCCEEDED = 'Succeeded',
FAILED = 'Failed',
}
/**
* Known pages in the app. Highest order context.
*/
@ -49,9 +57,11 @@ export const enum ModalName {
export const enum ElementName {
COMMON_BASES_CURRENCY_BUTTON = 'common-bases-currency-button',
CONFIRM_SWAP_BUTTON = 'confirm-swap-or-send',
CONNECT_WALLET_BUTTON = 'connect-wallet-button',
IMPORT_TOKEN_BUTTON = 'import-token-button',
SWAP_BUTTON = 'swap-button',
TOKEN_SELECTOR_ROW = 'token-selector-row',
WALLET_TYPE_OPTION = 'wallet-type-option',
// alphabetize additional element names.
}

@ -1,3 +1,5 @@
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
import React from 'react'
import styled from 'styled-components/macro'
@ -112,32 +114,39 @@ export default function Option({
id: string
}) {
const content = (
<OptionCardClickable
id={id}
onClick={onClick}
clickable={clickable && !isActive}
active={isActive}
data-testid="wallet-modal-option"
<TraceEvent
events={[Event.onClick]}
name={EventName.WALLET_SELECTED}
properties={{ wallet_type: header }}
element={ElementName.WALLET_TYPE_OPTION}
>
<OptionCardLeft>
<HeaderText color={color}>
{isActive ? (
<CircleWrapper>
<GreenCircle>
<div />
</GreenCircle>
</CircleWrapper>
) : (
''
)}
{header}
</HeaderText>
{subheader && <SubHeader>{subheader}</SubHeader>}
</OptionCardLeft>
<IconWrapper size={size}>
<img src={icon} alt={'Icon'} />
</IconWrapper>
</OptionCardClickable>
<OptionCardClickable
id={id}
onClick={onClick}
clickable={clickable && !isActive}
active={isActive}
data-testid="wallet-modal-option"
>
<OptionCardLeft>
<HeaderText color={color}>
{isActive ? (
<CircleWrapper>
<GreenCircle>
<div />
</GreenCircle>
</CircleWrapper>
) : (
''
)}
{header}
</HeaderText>
{subheader && <SubHeader>{subheader}</SubHeader>}
</OptionCardLeft>
<IconWrapper size={size}>
<img src={icon} alt={'Icon'} />
</IconWrapper>
</OptionCardClickable>
</TraceEvent>
)
if (link) {
return <ExternalLink href={link}>{content}</ExternalLink>

@ -1,11 +1,13 @@
import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import { Connector } from '@web3-react/types'
import { sendAnalyticsEvent } from 'components/AmplitudeAnalytics'
import { EventName, WALLET_CONNECTION_RESULT } from 'components/AmplitudeAnalytics/constants'
import { sendEvent } from 'components/analytics'
import { AutoColumn } from 'components/Column'
import { AutoRow } from 'components/Row'
import { ConnectionType } from 'connection'
import { getConnection, getIsCoinbaseWallet, getIsInjected, getIsMetaMask } from 'connection/utils'
import { getConnection, getConnectionName, getIsCoinbaseWallet, getIsInjected, getIsMetaMask } from 'connection/utils'
import { useCallback, useEffect, useState } from 'react'
import { ArrowLeft } from 'react-feather'
import { updateConnectionError } from 'state/connection/reducer'
@ -121,9 +123,10 @@ export default function WalletModal({
ENSName?: string
}) {
const dispatch = useAppDispatch()
const { account } = useWeb3React()
const { connector, account } = useWeb3React()
const [walletView, setWalletView] = useState(WALLET_VIEWS.ACCOUNT)
const [lastActiveWalletAddress, setLastActiveWalletAddress] = useState<string | undefined>(account)
const [pendingConnector, setPendingConnector] = useState<Connector | undefined>()
const pendingError = useAppSelector((state) =>
@ -150,6 +153,19 @@ export default function WalletModal({
}
}, [pendingConnector, walletView])
// When new wallet is successfully set by the user, trigger logging of Amplitude analytics event.
useEffect(() => {
if (account && account !== lastActiveWalletAddress) {
sendAnalyticsEvent(EventName.WALLET_CONNECT_TXN_COMPLETED, {
result: WALLET_CONNECTION_RESULT.SUCCEEDED,
wallet_address: account,
wallet_type: getConnectionName(getConnection(connector).type, getIsMetaMask()),
// TODO(lynnshaoyu): Send correct is_reconnect value after modifying user state.
})
}
setLastActiveWalletAddress(account)
}, [lastActiveWalletAddress, account, connector])
const tryActivation = useCallback(
async (connector: Connector) => {
const connectionType = getConnection(connector).type
@ -178,6 +194,11 @@ export default function WalletModal({
} catch (error) {
console.debug(`web3-react connection error: ${error}`)
dispatch(updateConnectionError({ connectionType, error: error.message }))
sendAnalyticsEvent(EventName.WALLET_CONNECT_TXN_COMPLETED, {
result: WALLET_CONNECTION_RESULT.FAILED,
wallet_type: getConnectionName(connectionType, getIsMetaMask()),
})
}
},
[dispatch, toggleWalletModal]

@ -1,11 +1,15 @@
// eslint-disable-next-line no-restricted-imports
import { t, Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
import { getConnection } from 'connection/utils'
import { getIsValidSwapQuote } from 'pages/Swap'
import { darken } from 'polished'
import { useMemo } from 'react'
import { Activity } from 'react-feather'
import { useAppSelector } from 'state/hooks'
import { useDerivedSwapInfo } from 'state/swap/hooks'
import styled, { css } from 'styled-components/macro'
import { isChainAllowed } from 'utils/switchChain'
@ -123,6 +127,11 @@ function Sock() {
function Web3StatusInner() {
const { account, connector, chainId, ENSName } = useWeb3React()
const connectionType = getConnection(connector).type
const {
trade: { state: tradeState, trade },
inputError: swapInputError,
} = useDerivedSwapInfo()
const validSwapQuote = getIsValidSwapQuote(trade, tradeState, swapInputError)
const error = useAppSelector((state) => state.connection.errorByConnectionType[getConnection(connector).type])
@ -186,11 +195,18 @@ function Web3StatusInner() {
)
} else {
return (
<Web3StatusConnect onClick={toggleWalletModal} faded={!account}>
<Text>
<Trans>Connect Wallet</Trans>
</Text>
</Web3StatusConnect>
<TraceEvent
events={[Event.onClick]}
name={EventName.CONNECT_WALLET_BUTTON_CLICKED}
properties={{ received_swap_quote: validSwapQuote }}
element={ElementName.CONNECT_WALLET_BUTTON}
>
<Web3StatusConnect onClick={toggleWalletModal} faded={!account}>
<Text>
<Trans>Connect Wallet</Trans>
</Text>
</Web3StatusConnect>
</TraceEvent>
)
}
}

@ -4,6 +4,8 @@ import { Trans } from '@lingui/macro'
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
import { FeeAmount, NonfungiblePositionManager } from '@uniswap/v3-sdk'
import { useWeb3React } from '@web3-react/core'
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
import { sendEvent } from 'components/analytics'
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
import useParsedQueryString from 'hooks/useParsedQueryString'
@ -433,9 +435,16 @@ export default function AddLiquidity({
</ThemedText.Main>
</ButtonPrimary>
) : !account ? (
<ButtonLight onClick={toggleWalletModal} $borderRadius="12px" padding={'12px'}>
<Trans>Connect Wallet</Trans>
</ButtonLight>
<TraceEvent
events={[Event.onClick]}
name={EventName.CONNECT_WALLET_BUTTON_CLICKED}
properties={{ received_swap_quote: false }}
element={ElementName.CONNECT_WALLET_BUTTON}
>
<ButtonLight onClick={toggleWalletModal} $borderRadius="12px" padding={'12px'}>
<Trans>Connect Wallet</Trans>
</ButtonLight>
</TraceEvent>
) : (
<AutoColumn gap={'md'}>
{(approvalA === ApprovalState.NOT_APPROVED ||

@ -3,6 +3,8 @@ import { TransactionResponse } from '@ethersproject/providers'
import { Trans } from '@lingui/macro'
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
import { sendEvent } from 'components/analytics'
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
import { SwitchLocaleLink } from 'components/SwitchLocaleLink'
@ -435,9 +437,16 @@ export default function AddLiquidity({
</ThemedText.Main>
</ButtonPrimary>
) : !account ? (
<ButtonLight onClick={toggleWalletModal}>
<Trans>Connect Wallet</Trans>
</ButtonLight>
<TraceEvent
events={[Event.onClick]}
name={EventName.CONNECT_WALLET_BUTTON_CLICKED}
properties={{ received_swap_quote: false }}
element={ElementName.CONNECT_WALLET_BUTTON}
>
<ButtonLight onClick={toggleWalletModal}>
<Trans>Connect Wallet</Trans>
</ButtonLight>
</TraceEvent>
) : (
<AutoColumn gap={'md'}>
{(approvalA === ApprovalState.NOT_APPROVED ||

@ -1,4 +1,6 @@
import { initializeAnalytics } from 'components/AmplitudeAnalytics'
import { PageName } from 'components/AmplitudeAnalytics/constants'
import { Trace } from 'components/AmplitudeAnalytics/Trace'
import Loader from 'components/Loader'
import TopLevelModals from 'components/TopLevelModals'
import ApeModeQueryParamReader from 'hooks/useApeModeQueryParamReader'
@ -64,8 +66,23 @@ const Marginer = styled.div`
margin-top: 5rem;
`
function getCurrentPageFromLocation(locationPathname: string): PageName | undefined {
switch (locationPathname) {
case '/swap':
return PageName.SWAP_PAGE
case '/vote':
return PageName.VOTE_PAGE
case '/pool':
return PageName.POOL_PAGE
default:
return undefined
}
}
export default function App() {
const history = useHistory()
const location = useLocation()
const currentPage = getCurrentPageFromLocation(location.pathname)
useAnalyticsReporter(useLocation())
initializeAnalytics()
@ -83,58 +100,65 @@ export default function App() {
<Route component={DarkModeQueryParamReader} />
<Route component={ApeModeQueryParamReader} />
<AppWrapper>
<HeaderWrapper>
<Header />
</HeaderWrapper>
<BodyWrapper>
<Popups />
<Polling />
<TopLevelModals />
<Suspense fallback={<Loader />}>
<Switch>
<Route strict path="/vote" component={Vote} />
<Route exact strict path="/create-proposal">
<Redirect to="/vote/create-proposal" />
</Route>
<Route exact strict path="/claim" component={OpenClaimAddressModalAndRedirectToSwap} />
<Route exact strict path="/uni" component={Earn} />
<Route exact strict path="/uni/:currencyIdA/:currencyIdB" component={Manage} />
<Trace page={currentPage}>
<HeaderWrapper>
<Header />
</HeaderWrapper>
<BodyWrapper>
<Popups />
<Polling />
<TopLevelModals />
<Suspense fallback={<Loader />}>
<Switch>
<Route path="/vote" component={Vote} />
<Route exact strict path="/create-proposal">
<Redirect to="/vote/create-proposal" />
</Route>
<Route exact strict path="/claim" component={OpenClaimAddressModalAndRedirectToSwap} />
<Route exact strict path="/uni" component={Earn} />
<Route exact strict path="/uni/:currencyIdA/:currencyIdB" component={Manage} />
<Route exact strict path="/send" component={RedirectPathToSwapOnly} />
<Route exact strict path="/swap/:outputCurrency" component={RedirectToSwap} />
<Route exact strict path="/swap" component={Swap} />
<Route exact strict path="/send" component={RedirectPathToSwapOnly} />
<Route exact strict path="/swap/:outputCurrency" component={RedirectToSwap} />
<Route path="/swap" component={Swap} />
<Route exact strict path="/pool/v2/find" component={PoolFinder} />
<Route exact strict path="/pool/v2" component={PoolV2} />
<Route exact strict path="/pool" component={Pool} />
<Route exact strict path="/pool/:tokenId" component={PositionPage} />
<Route exact strict path="/pool/v2/find" component={PoolFinder} />
<Route exact strict path="/pool/v2" component={PoolV2} />
<Route exact strict path="/pool" component={Pool} />
<Route exact strict path="/pool/:tokenId" component={PositionPage} />
<Route exact strict path="/add/v2/:currencyIdA?/:currencyIdB?" component={RedirectDuplicateTokenIdsV2} />
<Route
exact
strict
path="/add/:currencyIdA?/:currencyIdB?/:feeAmount?"
component={RedirectDuplicateTokenIds}
/>
<Route
exact
strict
path="/add/v2/:currencyIdA?/:currencyIdB?"
component={RedirectDuplicateTokenIdsV2}
/>
<Route
exact
strict
path="/add/:currencyIdA?/:currencyIdB?/:feeAmount?"
component={RedirectDuplicateTokenIds}
/>
<Route
exact
strict
path="/increase/:currencyIdA?/:currencyIdB?/:feeAmount?/:tokenId?"
component={AddLiquidity}
/>
<Route
exact
strict
path="/increase/:currencyIdA?/:currencyIdB?/:feeAmount?/:tokenId?"
component={AddLiquidity}
/>
<Route exact strict path="/remove/v2/:currencyIdA/:currencyIdB" component={RemoveLiquidity} />
<Route exact strict path="/remove/:tokenId" component={RemoveLiquidityV3} />
<Route exact strict path="/remove/v2/:currencyIdA/:currencyIdB" component={RemoveLiquidity} />
<Route exact strict path="/remove/:tokenId" component={RemoveLiquidityV3} />
<Route exact strict path="/migrate/v2" component={MigrateV2} />
<Route exact strict path="/migrate/v2/:address" component={MigrateV2Pair} />
<Route exact strict path="/migrate/v2" component={MigrateV2} />
<Route exact strict path="/migrate/v2/:address" component={MigrateV2Pair} />
<Route component={RedirectPathToSwapOnly} />
</Switch>
</Suspense>
<Marginer />
</BodyWrapper>
<Route component={RedirectPathToSwapOnly} />
</Switch>
</Suspense>
<Marginer />
</BodyWrapper>
</Trace>
</AppWrapper>
</ErrorBoundary>
)

@ -1,7 +1,9 @@
import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import { PageName } from 'components/AmplitudeAnalytics/constants'
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
import { Trace } from 'components/AmplitudeAnalytics/Trace'
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
import { ButtonGray, ButtonPrimary, ButtonText } from 'components/Button'
import { AutoColumn } from 'components/Column'
import { FlyoutAlignment, NewMenu } from 'components/Menu'
@ -311,9 +313,16 @@ export default function Pool() {
</ButtonText>
)}
{showConnectAWallet && (
<ButtonPrimary style={{ marginTop: '2em', padding: '8px 16px' }} onClick={toggleWalletModal}>
<Trans>Connect a wallet</Trans>
</ButtonPrimary>
<TraceEvent
events={[Event.onClick]}
name={EventName.CONNECT_WALLET_BUTTON_CLICKED}
properties={{ received_swap_quote: false }}
element={ElementName.CONNECT_WALLET_BUTTON}
>
<ButtonPrimary style={{ marginTop: '2em', padding: '8px 16px' }} onClick={toggleWalletModal}>
<Trans>Connect a wallet</Trans>
</ButtonPrimary>
</TraceEvent>
)}
</ErrorContainer>
)}

@ -4,6 +4,8 @@ import { TransactionResponse } from '@ethersproject/providers'
import { Trans } from '@lingui/macro'
import { Currency, Percent } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
import { sendEvent } from 'components/analytics'
import { useV2LiquidityTokenPermit } from 'hooks/useV2LiquidityTokenPermit'
import { useCallback, useContext, useMemo, useState } from 'react'
@ -625,9 +627,16 @@ export default function RemoveLiquidity({
)}
<div style={{ position: 'relative' }}>
{!account ? (
<ButtonLight onClick={toggleWalletModal}>
<Trans>Connect Wallet</Trans>
</ButtonLight>
<TraceEvent
events={[Event.onClick]}
name={EventName.CONNECT_WALLET_BUTTON_CLICKED}
properties={{ received_swap_quote: false }}
element={ElementName.CONNECT_WALLET_BUTTON}
>
<ButtonLight onClick={toggleWalletModal}>
<Trans>Connect Wallet</Trans>
</ButtonLight>
</TraceEvent>
) : (
<RowBetween>
<ButtonConfirmed

@ -4,8 +4,10 @@ import { Currency, CurrencyAmount, Token, TradeType } from '@uniswap/sdk-core'
import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade } from '@uniswap/v3-sdk'
import { useWeb3React } from '@web3-react/core'
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
import { PageName, SectionName } from 'components/AmplitudeAnalytics/constants'
import { Trace } from 'components/AmplitudeAnalytics/Trace'
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
import { sendEvent } from 'components/analytics'
import { NetworkAlert } from 'components/NetworkAlert/NetworkAlert'
import SwapDetailsDropdown from 'components/swap/SwapDetailsDropdown'
@ -15,9 +17,12 @@ import { useSwapCallback } from 'hooks/useSwapCallback'
import useTransactionDeadline from 'hooks/useTransactionDeadline'
import JSBI from 'jsbi'
import { Context, useCallback, useContext, useEffect, useMemo, useState } from 'react'
import { ReactNode } from 'react'
import { ArrowDown, CheckCircle, HelpCircle } from 'react-feather'
import { RouteComponentProps } from 'react-router-dom'
import { Text } from 'rebass'
import { useToggleWalletModal } from 'state/application/hooks'
import { InterfaceTrade } from 'state/routing/types'
import { TradeState } from 'state/routing/types'
import styled, { DefaultTheme, ThemeContext } from 'styled-components/macro'
@ -44,7 +49,6 @@ import useIsArgentWallet from '../../hooks/useIsArgentWallet'
import { useIsSwapUnsupported } from '../../hooks/useIsSwapUnsupported'
import { useStablecoinValue } from '../../hooks/useStablecoinPrice'
import useWrapCallback, { WrapErrorText, WrapType } from '../../hooks/useWrapCallback'
import { useToggleWalletModal } from '../../state/application/hooks'
import { Field } from '../../state/swap/actions'
import {
useDefaultsFromURLSearch,
@ -65,6 +69,14 @@ const AlertWrapper = styled.div`
width: 100%;
`
export function getIsValidSwapQuote(
trade: InterfaceTrade<Currency, Currency, TradeType> | undefined,
tradeState: TradeState,
swapInputError?: ReactNode
): boolean {
return !!swapInputError && !!trade && (tradeState === TradeState.VALID || tradeState === TradeState.SYNCING)
}
export default function Swap({ history }: RouteComponentProps) {
const { account, chainId } = useWeb3React()
const loadedUrlParams = useDefaultsFromURLSearch()
@ -510,9 +522,16 @@ export default function Swap({ history }: RouteComponentProps) {
</ThemedText.Main>
</ButtonPrimary>
) : !account ? (
<ButtonLight onClick={toggleWalletModal}>
<Trans>Connect Wallet</Trans>
</ButtonLight>
<TraceEvent
events={[Event.onClick]}
name={EventName.CONNECT_WALLET_BUTTON_CLICKED}
properties={{ received_swap_quote: getIsValidSwapQuote(trade, tradeState, swapInputError) }}
element={ElementName.CONNECT_WALLET_BUTTON}
>
<ButtonLight onClick={toggleWalletModal}>
<Trans>Connect Wallet</Trans>
</ButtonLight>
</TraceEvent>
) : showWrap ? (
<ButtonPrimary disabled={Boolean(wrapInputError)} onClick={onWrap}>
{wrapInputError ? (