Commit 877e000d authored by eddie's avatar eddie Committed by GitHub

feat: swap quote event (#7174)

* wip: more metrics

* wip: SWAP_INPUT_FIRST_USED

* feat: track elapsed times

* feat: add e2e test

* fix: order of logging

* feat: swap quote request logging

* feat: e2e test

* feat: another property

* test: test events separately

* fix: dont log for price quotes
parent 3f05a884
...@@ -3,8 +3,8 @@ import { SwapEventName } from '@uniswap/analytics-events' ...@@ -3,8 +3,8 @@ import { SwapEventName } from '@uniswap/analytics-events'
import { USDC_MAINNET } from '../../../src/constants/tokens' import { USDC_MAINNET } from '../../../src/constants/tokens'
import { getTestSelector } from '../../utils' import { getTestSelector } from '../../utils'
describe('time-to-swap logging', () => { describe('swap flow logging', () => {
it('completes two swaps and verifies the TTS logging for the first', () => { it('completes two swaps and verifies the TTS logging for the first, plus all intermediate steps along the way', () => {
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${USDC_MAINNET.address}`) cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${USDC_MAINNET.address}`)
cy.hardhat() cy.hardhat()
...@@ -13,6 +13,24 @@ describe('time-to-swap logging', () => { ...@@ -13,6 +13,24 @@ describe('time-to-swap logging', () => {
cy.get('#swap-currency-output .token-amount-input').type('1').should('have.value', '1') 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', '') cy.get('#swap-currency-input .token-amount-input').should('not.have.value', '')
// Verify first swap action
cy.waitForAmplitudeEvent(SwapEventName.SWAP_FIRST_ACTION).then((event: any) => {
cy.wrap(event.event_properties).should('have.property', 'time_to_first_swap_action')
cy.wrap(event.event_properties.time_to_first_swap_action).should('be.a', 'number')
cy.wrap(event.event_properties.time_to_first_swap_action).should('be.gte', 0)
})
// Verify Swap Quote
cy.waitForAmplitudeEvent(SwapEventName.SWAP_QUOTE_FETCH).then((event: any) => {
// Price quotes don't include these values, so we only verify the types if they exist
if (event.event_properties.time_to_first_quote_request) {
cy.wrap(event.event_properties.time_to_first_quote_request).should('be.a', 'number')
cy.wrap(event.event_properties.time_to_first_quote_request).should('be.gte', 0)
cy.wrap(event.event_properties.time_to_first_quote_request_since_first_input).should('be.a', 'number')
cy.wrap(event.event_properties.time_to_first_quote_request_since_first_input).should('be.gte', 0)
}
})
// Submit transaction // Submit transaction
cy.get('#swap-button').click() cy.get('#swap-button').click()
cy.contains('Confirm swap').click() cy.contains('Confirm swap').click()
...@@ -31,10 +49,19 @@ describe('time-to-swap logging', () => { ...@@ -31,10 +49,19 @@ describe('time-to-swap logging', () => {
}) })
// Second swap in the session: // Second swap in the session:
// Enter amount to swap // Enter amount to swap (different from first trade, to trigger a new quote request)
cy.get('#swap-currency-output .token-amount-input').clear().type('1').should('have.value', '1') cy.get('#swap-currency-output .token-amount-input').clear().type('10').should('have.value', '10')
cy.get('#swap-currency-input .token-amount-input').should('not.have.value', '') cy.get('#swap-currency-input .token-amount-input').should('not.have.value', '')
// Verify second Swap Quote
cy.waitForAmplitudeEvent(SwapEventName.SWAP_QUOTE_FETCH).then((event: any) => {
// Price quotes don't include these values, so we only verify the types if they exist
if (event.event_properties.time_to_first_quote_request) {
cy.wrap(event.event_properties.time_to_first_quote_request).should('be.undefined')
cy.wrap(event.event_properties.time_to_first_quote_request_since_first_input).should('be.undefined')
}
})
// Submit transaction // Submit transaction
cy.get('#swap-button').click() cy.get('#swap-button').click()
cy.contains('Confirm swap').click() cy.contains('Confirm swap').click()
......
...@@ -4,6 +4,7 @@ import { TradeType } from '@uniswap/sdk-core' ...@@ -4,6 +4,7 @@ import { TradeType } from '@uniswap/sdk-core'
import { isUniswapXSupportedChain } from 'constants/chains' import { isUniswapXSupportedChain } from 'constants/chains'
import { getClientSideQuote } from 'lib/hooks/routing/clientSideSmartOrderRouter' import { getClientSideQuote } from 'lib/hooks/routing/clientSideSmartOrderRouter'
import ms from 'ms' import ms from 'ms'
import { logSwapQuoteRequest } from 'tracing/swapFlowLoggers'
import { trace } from 'tracing/trace' import { trace } from 'tracing/trace'
import { import {
...@@ -119,6 +120,7 @@ export const routingApi = createApi({ ...@@ -119,6 +120,7 @@ export const routingApi = createApi({
}, },
async queryFn(args, _api, _extraOptions, fetch) { async queryFn(args, _api, _extraOptions, fetch) {
let fellBack = false let fellBack = false
logSwapQuoteRequest(args.tokenInChainId, args.routerPreference)
const quoteStartMark = performance.mark(`quote-fetch-start-${Date.now()}`) const quoteStartMark = performance.mark(`quote-fetch-start-${Date.now()}`)
if (shouldUseAPIRouter(args)) { if (shouldUseAPIRouter(args)) {
fellBack = true fellBack = true
......
import { calculateElapsedTimeWithPerformanceMark } from './utils' import { calculateElapsedTimeWithPerformanceMark } from './utils'
// These events should happen in this order.
export enum SwapEventType { export enum SwapEventType {
/** /**
* Full list of actions that can trigger the FIRST_SWAP_ACTION moment: * Full list of actions that can trigger the FIRST_SWAP_ACTION moment:
......
import { SwapEventName } from '@uniswap/analytics-events' import { SwapEventName } from '@uniswap/analytics-events'
import { ITraceContext } from 'analytics' import { ITraceContext } from 'analytics'
import { sendAnalyticsEvent } from 'analytics' import { sendAnalyticsEvent } from 'analytics'
import { INTERNAL_ROUTER_PREFERENCE_PRICE, RouterPreference } from 'state/routing/types'
import { SwapEventTimestampTracker, SwapEventType } from './SwapEventTimestampTracker' import { SwapEventTimestampTracker, SwapEventType } from './SwapEventTimestampTracker'
...@@ -32,3 +33,25 @@ export function maybeLogFirstSwapAction(analyticsContext: ITraceContext) { ...@@ -32,3 +33,25 @@ export function maybeLogFirstSwapAction(analyticsContext: ITraceContext) {
}) })
} }
} }
export function logSwapQuoteRequest(
chainId: number,
routerPreference: RouterPreference | typeof INTERNAL_ROUTER_PREFERENCE_PRICE
) {
let performanceMetrics = {}
if (routerPreference !== INTERNAL_ROUTER_PREFERENCE_PRICE) {
const hasSetSwapQuote = tracker.hasTimestamp(SwapEventType.FIRST_QUOTE_FETCH_STARTED)
const elapsedTime = tracker.setElapsedTime(SwapEventType.FIRST_QUOTE_FETCH_STARTED)
performanceMetrics = {
// We only log the time_to_first_quote_request metric for the first quote request of a session.
time_to_first_quote_request: hasSetSwapQuote ? undefined : elapsedTime,
time_to_first_quote_request_since_first_input: hasSetSwapQuote
? undefined
: tracker.getElapsedTime(SwapEventType.FIRST_QUOTE_FETCH_STARTED, SwapEventType.FIRST_SWAP_ACTION),
}
}
sendAnalyticsEvent(SwapEventName.SWAP_QUOTE_FETCH, {
chainId,
...performanceMetrics,
})
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment