From 877e000da6c77c0e07c1a6b51686e8a4061ae28e Mon Sep 17 00:00:00 2001 From: eddie <66155195+just-toby@users.noreply.github.com> Date: Fri, 18 Aug 2023 11:47:45 -0700 Subject: [PATCH] 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 --- ...ToSwap.test.ts => swapFlowLogging.test.ts} | 35 ++++++++++++++++--- src/state/routing/slice.ts | 2 ++ src/tracing/SwapEventTimestampTracker.ts | 1 + src/tracing/swapFlowLoggers.ts | 23 ++++++++++++ 4 files changed, 57 insertions(+), 4 deletions(-) rename cypress/e2e/swap/{timeToSwap.test.ts => swapFlowLogging.test.ts} (54%) diff --git a/cypress/e2e/swap/timeToSwap.test.ts b/cypress/e2e/swap/swapFlowLogging.test.ts similarity index 54% rename from cypress/e2e/swap/timeToSwap.test.ts rename to cypress/e2e/swap/swapFlowLogging.test.ts index 05234bb4e9..170573fc50 100644 --- a/cypress/e2e/swap/timeToSwap.test.ts +++ b/cypress/e2e/swap/swapFlowLogging.test.ts @@ -3,8 +3,8 @@ 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', () => { +describe('swap flow logging', () => { + 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.hardhat() @@ -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-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 cy.get('#swap-button').click() cy.contains('Confirm swap').click() @@ -31,10 +49,19 @@ describe('time-to-swap logging', () => { }) // Second swap in the session: - // Enter amount to swap - cy.get('#swap-currency-output .token-amount-input').clear().type('1').should('have.value', '1') + // Enter amount to swap (different from first trade, to trigger a new quote request) + 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', '') + // 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 cy.get('#swap-button').click() cy.contains('Confirm swap').click() diff --git a/src/state/routing/slice.ts b/src/state/routing/slice.ts index d9b81e9755..a99f02a931 100644 --- a/src/state/routing/slice.ts +++ b/src/state/routing/slice.ts @@ -4,6 +4,7 @@ import { TradeType } from '@uniswap/sdk-core' import { isUniswapXSupportedChain } from 'constants/chains' import { getClientSideQuote } from 'lib/hooks/routing/clientSideSmartOrderRouter' import ms from 'ms' +import { logSwapQuoteRequest } from 'tracing/swapFlowLoggers' import { trace } from 'tracing/trace' import { @@ -119,6 +120,7 @@ export const routingApi = createApi({ }, async queryFn(args, _api, _extraOptions, fetch) { let fellBack = false + logSwapQuoteRequest(args.tokenInChainId, args.routerPreference) const quoteStartMark = performance.mark(`quote-fetch-start-${Date.now()}`) if (shouldUseAPIRouter(args)) { fellBack = true diff --git a/src/tracing/SwapEventTimestampTracker.ts b/src/tracing/SwapEventTimestampTracker.ts index 103594233e..26d24cbe5a 100644 --- a/src/tracing/SwapEventTimestampTracker.ts +++ b/src/tracing/SwapEventTimestampTracker.ts @@ -1,5 +1,6 @@ import { calculateElapsedTimeWithPerformanceMark } from './utils' +// These events should happen in this order. export enum SwapEventType { /** * Full list of actions that can trigger the FIRST_SWAP_ACTION moment: diff --git a/src/tracing/swapFlowLoggers.ts b/src/tracing/swapFlowLoggers.ts index 380623bc8f..e33438c9de 100644 --- a/src/tracing/swapFlowLoggers.ts +++ b/src/tracing/swapFlowLoggers.ts @@ -1,6 +1,7 @@ import { SwapEventName } from '@uniswap/analytics-events' import { ITraceContext } from 'analytics' import { sendAnalyticsEvent } from 'analytics' +import { INTERNAL_ROUTER_PREFERENCE_PRICE, RouterPreference } from 'state/routing/types' import { SwapEventTimestampTracker, SwapEventType } from './SwapEventTimestampTracker' @@ -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, + }) +}