feat: time-to-swap metric (#7051)
* test(cypress): disable infura from browser * build: typecheck cypress * build: es5 * build: rm cypress videos * fix failing tests * skip nft failure and rm infra-175 * feat: implement tts metric * wip e2e test * fix: improve v2 network support (#7012) * fix: improve v2 network support * add an unsupported message to all v2 pages * test: add v2 pool tests * add guard on transaction callbacks * fix: dep array --------- Co-authored-by: eddie <66155195+just-toby@users.noreply.github.com> * test: adjust test options * fix: move to helper method * fix: merge and make code style change * fix: use local variable to track first event * fix: amplitude cypress command * fix: use file-level var * fix: clear input in test --------- Co-authored-by: Zach Pomerantz <zzmp@uniswap.org> Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com>
This commit is contained in:
parent
6a1f17ab5a
commit
715555f340
45
cypress/e2e/swap/timeToSwap.test.ts
Normal file
45
cypress/e2e/swap/timeToSwap.test.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { SwapEventName } from '@uniswap/analytics-events'
|
||||||
|
|
||||||
|
import { USDC_MAINNET } from '../../../src/constants/tokens'
|
||||||
|
import { getTestSelector } from '../../utils'
|
||||||
|
|
||||||
|
describe('time-to-swap logging', () => {
|
||||||
|
it('completes two swaps and verifies the TTS logging for the first', () => {
|
||||||
|
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${USDC_MAINNET.address}`)
|
||||||
|
cy.hardhat()
|
||||||
|
|
||||||
|
// First swap in the session:
|
||||||
|
// Enter amount to swap
|
||||||
|
cy.get('#swap-currency-output .token-amount-input').type('1').should('have.value', '1')
|
||||||
|
cy.get('#swap-currency-input .token-amount-input').should('not.have.value', '')
|
||||||
|
|
||||||
|
// Submit transaction
|
||||||
|
cy.get('#swap-button').click()
|
||||||
|
cy.contains('Confirm swap').click()
|
||||||
|
cy.get(getTestSelector('confirmation-close-icon')).click()
|
||||||
|
|
||||||
|
cy.get(getTestSelector('popups')).contains('Swapped')
|
||||||
|
|
||||||
|
// Verify logging
|
||||||
|
cy.waitForAmplitudeEvent(SwapEventName.SWAP_TRANSACTION_COMPLETED).then((event: any) => {
|
||||||
|
cy.wrap(event.event_properties).should('have.property', 'time_to_swap')
|
||||||
|
cy.wrap(event.event_properties.time_to_swap).should('be.a', 'number')
|
||||||
|
cy.wrap(event.event_properties.time_to_swap).should('be.gte', 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Second swap in the session:
|
||||||
|
// Enter amount to swap
|
||||||
|
cy.get('#swap-currency-output .token-amount-input').clear().type('1').should('have.value', '1')
|
||||||
|
cy.get('#swap-currency-input .token-amount-input').should('not.have.value', '')
|
||||||
|
|
||||||
|
// Submit transaction
|
||||||
|
cy.get('#swap-button').click()
|
||||||
|
cy.contains('Confirm swap').click()
|
||||||
|
cy.get(getTestSelector('confirmation-close-icon')).click()
|
||||||
|
|
||||||
|
cy.get(getTestSelector('popups')).contains('Swapped')
|
||||||
|
cy.waitForAmplitudeEvent(SwapEventName.SWAP_TRANSACTION_COMPLETED).then((event: any) => {
|
||||||
|
cy.wrap(event.event_properties).should('not.have.property', 'time_to_swap')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -12,6 +12,16 @@ declare global {
|
|||||||
interface ApplicationWindow {
|
interface ApplicationWindow {
|
||||||
ethereum: Eip1193Bridge
|
ethereum: Eip1193Bridge
|
||||||
}
|
}
|
||||||
|
interface Chainable<Subject> {
|
||||||
|
/**
|
||||||
|
* Wait for a specific event to be sent to amplitude. If the event is found, the subject will be the event.
|
||||||
|
*
|
||||||
|
* @param {string} eventName - The type of the event to search for e.g. SwapEventName.SWAP_TRANSACTION_COMPLETED
|
||||||
|
* @param {number} timeout - The maximum amount of time (in ms) to wait for the event.
|
||||||
|
* @returns {Chainable<Subject>}
|
||||||
|
*/
|
||||||
|
waitForAmplitudeEvent(eventName: string, timeout?: number): Chainable<Subject>
|
||||||
|
}
|
||||||
interface VisitOptions {
|
interface VisitOptions {
|
||||||
serviceWorker?: true
|
serviceWorker?: true
|
||||||
featureFlags?: Array<FeatureFlag>
|
featureFlags?: Array<FeatureFlag>
|
||||||
@ -66,3 +76,23 @@ Cypress.Commands.overwrite(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Cypress.Commands.add('waitForAmplitudeEvent', (eventName, timeout = 5000 /* 5s */) => {
|
||||||
|
const startTime = new Date().getTime()
|
||||||
|
|
||||||
|
function checkRequest() {
|
||||||
|
return cy.wait('@amplitude', { timeout }).then((interception) => {
|
||||||
|
const events = interception.request.body.events
|
||||||
|
const event = events.find((event: any) => event.event_type === eventName)
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
return cy.wrap(event)
|
||||||
|
} else if (new Date().getTime() - startTime > timeout) {
|
||||||
|
throw new Error(`Event ${eventName} not found within the specified timeout`)
|
||||||
|
} else {
|
||||||
|
return checkRequest()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return checkRequest()
|
||||||
|
})
|
||||||
|
@ -19,6 +19,7 @@ beforeEach(() => {
|
|||||||
cy.intercept('https://api.uniswap.org/v1/amplitude-proxy', (req) => {
|
cy.intercept('https://api.uniswap.org/v1/amplitude-proxy', (req) => {
|
||||||
const requestBody = JSON.stringify(req.body)
|
const requestBody = JSON.stringify(req.body)
|
||||||
const byteSize = new Blob([requestBody]).size
|
const byteSize = new Blob([requestBody]).size
|
||||||
|
req.alias = 'amplitude'
|
||||||
req.reply(
|
req.reply(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
code: 200,
|
code: 200,
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { TransactionReceipt } from '@ethersproject/abstract-provider'
|
import { TransactionReceipt } from '@ethersproject/abstract-provider'
|
||||||
|
import { SwapEventName } from '@uniswap/analytics-events'
|
||||||
import { useWeb3React } from '@web3-react/core'
|
import { useWeb3React } from '@web3-react/core'
|
||||||
|
import { sendAnalyticsEvent, useTrace } from 'analytics'
|
||||||
import { DEFAULT_TXN_DISMISS_MS, L2_TXN_DISMISS_MS } from 'constants/misc'
|
import { DEFAULT_TXN_DISMISS_MS, L2_TXN_DISMISS_MS } from 'constants/misc'
|
||||||
import LibUpdater from 'lib/hooks/transactions/updater'
|
import LibUpdater from 'lib/hooks/transactions/updater'
|
||||||
import { useCallback, useMemo } from 'react'
|
import { useCallback, useMemo } from 'react'
|
||||||
@ -25,7 +27,20 @@ export function toSerializableReceipt(receipt: TransactionReceipt): Serializable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We only log the time-to-swap metric for the first swap of a session.
|
||||||
|
let hasReportedTimeToSwap = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the time elapsed between page load and now,
|
||||||
|
* if the time-to-swap mark doesn't already exist.
|
||||||
|
*/
|
||||||
|
function getElapsedTime(): number {
|
||||||
|
const timeToSwap = performance.mark('time-to-swap')
|
||||||
|
return timeToSwap.startTime
|
||||||
|
}
|
||||||
|
|
||||||
export default function Updater() {
|
export default function Updater() {
|
||||||
|
const analyticsContext = useTrace()
|
||||||
const { chainId } = useWeb3React()
|
const { chainId } = useWeb3React()
|
||||||
const addPopup = useAddPopup()
|
const addPopup = useAddPopup()
|
||||||
// speed up popup dismisall time if on L2
|
// speed up popup dismisall time if on L2
|
||||||
@ -55,6 +70,17 @@ export default function Updater() {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const elapsedTime = getElapsedTime()
|
||||||
|
|
||||||
|
sendAnalyticsEvent(SwapEventName.SWAP_TRANSACTION_COMPLETED, {
|
||||||
|
// if timeToSwap was already set, we already logged this session
|
||||||
|
time_to_swap: hasReportedTimeToSwap ? undefined : elapsedTime,
|
||||||
|
hash,
|
||||||
|
...analyticsContext,
|
||||||
|
})
|
||||||
|
|
||||||
|
hasReportedTimeToSwap = true
|
||||||
|
|
||||||
addPopup(
|
addPopup(
|
||||||
{
|
{
|
||||||
type: PopupType.Transaction,
|
type: PopupType.Transaction,
|
||||||
@ -64,7 +90,7 @@ export default function Updater() {
|
|||||||
isL2 ? L2_TXN_DISMISS_MS : DEFAULT_TXN_DISMISS_MS
|
isL2 ? L2_TXN_DISMISS_MS : DEFAULT_TXN_DISMISS_MS
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
[addPopup, dispatch, isL2]
|
[addPopup, analyticsContext, dispatch, isL2]
|
||||||
)
|
)
|
||||||
|
|
||||||
return <LibUpdater pendingTransactions={pendingTransactions} onCheck={onCheck} onReceipt={onReceipt} />
|
return <LibUpdater pendingTransactions={pendingTransactions} onCheck={onCheck} onReceipt={onReceipt} />
|
||||||
|
Loading…
Reference in New Issue
Block a user