From 11fc709675974b4d0ae04650c0f1d410f923f2ab Mon Sep 17 00:00:00 2001 From: Lynn Yu Date: Wed, 6 Jul 2022 15:33:52 -0400 Subject: [PATCH 01/28] init commit --- .env.production | 1 + .../AmplitudeAnalytics/constants.ts | 27 ++++++++++ src/components/AmplitudeAnalytics/index.ts | 53 +++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 src/components/AmplitudeAnalytics/constants.ts create mode 100644 src/components/AmplitudeAnalytics/index.ts diff --git a/.env.production b/.env.production index 1727e01ac0e..6c5d55a583a 100644 --- a/.env.production +++ b/.env.production @@ -1,3 +1,4 @@ +REACT_APP_AMPLITUDE_KEY="1c694b28cd089acc2c386d518f93a775" REACT_APP_INFURA_KEY="099fc58e0de9451d80b18d7c74caa7c1" REACT_APP_FORTMATIC_KEY="pk_live_F937DF033A1666BF" REACT_APP_GOOGLE_ANALYTICS_ID="G-KDP9B6W4H8" diff --git a/src/components/AmplitudeAnalytics/constants.ts b/src/components/AmplitudeAnalytics/constants.ts new file mode 100644 index 00000000000..8612ebad96d --- /dev/null +++ b/src/components/AmplitudeAnalytics/constants.ts @@ -0,0 +1,27 @@ +/** + * Event names that can occur in this application. + * + * Subject to change as new features are added and new events are defined + * and logged. + */ +export enum EventName { + SWAP_SUBMITTED = 'Swap Submitted', + PAGE_VIEWED = 'Page Viewed', +} + +/** + * User Model property operations available via Amplitude's Identify API. + * + * See https://www.docs.developers.amplitude.com/data/sdks/typescript-browser/#user-properties + * for detailed documentation. + */ +export enum UserPropertyOperations { + SET, + SET_ONCE, + ADD, + ARRAY_PREPEND, + ARRAY_APPEND, + ARRAY_PREINSERT, + ARRAY_POSTINSERT, + ARRAY_REMOVE, +} diff --git a/src/components/AmplitudeAnalytics/index.ts b/src/components/AmplitudeAnalytics/index.ts new file mode 100644 index 00000000000..e98a12aa1c1 --- /dev/null +++ b/src/components/AmplitudeAnalytics/index.ts @@ -0,0 +1,53 @@ +import { Identify, identify, init, track } from '@amplitude/analytics-browser' + +import { UserPropertyOperations } from './constants' + +export function initializeAnalytics() { + if (process.env.NODE_ENV === 'development') return + + const API_KEY = process.env.REACT_APP_AMPLITUDE_KEY + if (typeof API_KEY === 'undefined') { + throw new Error(`REACT_APP_AMPLITUDE_KEY must be a defined environment variable`) + } + + init(API_KEY) +} + +export function sendAnalyticsEvent(eventName: string, eventProperties?: Record) { + track(eventName, eventProperties) +} + +export function updateAnalyticsUserModel( + operation: UserPropertyOperations, + propertyName: string, + propertyValue: string | number +) { + const identifyObj = new Identify() + switch (operation) { + case UserPropertyOperations.SET: + identifyObj.set(propertyName, propertyValue) + break + case UserPropertyOperations.SET_ONCE: + identifyObj.setOnce(propertyName, propertyValue) + break + case UserPropertyOperations.ADD: + identifyObj.add(propertyName, typeof propertyValue === 'number' ? propertyValue : 0) + break + case UserPropertyOperations.ARRAY_PREPEND: + identifyObj.prepend(propertyName, propertyValue) + break + case UserPropertyOperations.ARRAY_APPEND: + identifyObj.append(propertyName, propertyValue) + break + case UserPropertyOperations.ARRAY_PREINSERT: + identifyObj.preInsert(propertyName, propertyValue) + break + case UserPropertyOperations.ARRAY_POSTINSERT: + identifyObj.postInsert(propertyName, propertyValue) + break + case UserPropertyOperations.ARRAY_REMOVE: + identifyObj.remove(propertyName, propertyValue) + break + } + identify(identifyObj) +} From 0864ee4a9850f654508615001790c9e6847b5401 Mon Sep 17 00:00:00 2001 From: Lynn Yu Date: Wed, 6 Jul 2022 15:41:54 -0400 Subject: [PATCH 02/28] add amplitude ts sdk to package.json --- package.json | 1 + yarn.lock | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/package.json b/package.json index 8de35c6ec76..5ed64978aaf 100644 --- a/package.json +++ b/package.json @@ -106,6 +106,7 @@ "typescript": "^4.4.3" }, "dependencies": { + "@amplitude/analytics-browser": "^0.5.1", "@coinbase/wallet-sdk": "^3.3.0", "@fontsource/ibm-plex-mono": "^4.5.1", "@fontsource/inter": "^4.5.1", diff --git a/yarn.lock b/yarn.lock index f01692a7c28..538b6ad26c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,34 @@ # yarn lockfile v1 +"@amplitude/analytics-browser@^0.5.1": + version "0.5.1" + resolved "https://registry.yarnpkg.com/@amplitude/analytics-browser/-/analytics-browser-0.5.1.tgz#0d7304a64251b9b65e826948e5afd311d6118576" + integrity sha512-CDeV9S+clElWsO56L8+hGyG/nFSDb56cSlHZhnTEovA1H6YNqI7/NXPDTVE619YEKIY/boNKSSqgNIcf4NN0VQ== + dependencies: + "@amplitude/analytics-core" "^0.4.1" + "@amplitude/analytics-types" "^0.3.0" + "@amplitude/ua-parser-js" "^0.7.31" + tslib "^2.3.1" + +"@amplitude/analytics-core@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@amplitude/analytics-core/-/analytics-core-0.4.1.tgz#0e1a7ae6470bcec277a0389e16dd110519f6750e" + integrity sha512-letpuJVuWI6TAkaZ0Szi4FxqD2xukjvOHQ+D/GHBpspUiK9ETpjT9vXNrczw5Zs1B5hgLHfNOlhMfdWHoG+3qg== + dependencies: + "@amplitude/analytics-types" "^0.3.0" + tslib "^2.3.1" + +"@amplitude/analytics-types@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@amplitude/analytics-types/-/analytics-types-0.3.0.tgz#3626f2907e4dd353f0ca3a21b847d7560a1c4da9" + integrity sha512-Hu/emt6mEpSGmOnLyqZGe75a5BNH3j9WJWBDFMBOZ1gLsbrxKtrb6w3EQy6yRROZ8Uyn04wC0veVUYyETwH3vg== + +"@amplitude/ua-parser-js@^0.7.31": + version "0.7.31" + resolved "https://registry.yarnpkg.com/@amplitude/ua-parser-js/-/ua-parser-js-0.7.31.tgz#749bf7cb633cfcc7ff3c10805bad7c5f6fbdbc61" + integrity sha512-+z8UGRaj13Pt5NDzOnkTBy49HE2CX64jeL0ArB86HAtilpnfkPB7oqkigN7Lf2LxscMg4QhFD7mmCfedh3rqTg== + "@ardatan/aggregate-error@0.0.6": version "0.0.6" resolved "https://registry.yarnpkg.com/@ardatan/aggregate-error/-/aggregate-error-0.0.6.tgz#fe6924771ea40fc98dc7a7045c2e872dc8527609" @@ -17705,6 +17733,11 @@ tslib@^2, tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@~2.3.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== +tslib@^2.3.1: + version "2.4.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== + tslib@~2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.3.tgz#8e0741ac45fc0c226e58a17bfc3e64b9bc6ca61c" From f9f013da02d167878786ba838338da01301826fa Mon Sep 17 00:00:00 2001 From: Lynn Yu Date: Wed, 6 Jul 2022 16:02:54 -0400 Subject: [PATCH 03/28] add more comments and documentation --- src/components/AmplitudeAnalytics/index.ts | 30 +++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/components/AmplitudeAnalytics/index.ts b/src/components/AmplitudeAnalytics/index.ts index e98a12aa1c1..c405dafd567 100644 --- a/src/components/AmplitudeAnalytics/index.ts +++ b/src/components/AmplitudeAnalytics/index.ts @@ -2,6 +2,12 @@ import { Identify, identify, init, track } from '@amplitude/analytics-browser' import { UserPropertyOperations } from './constants' +/** + * Initializes Amplitude with API key for project. + * + * Uniswap has two Amplitude projects: test and production. You must be a + * member of the organization on Amplitude to view details. + */ export function initializeAnalytics() { if (process.env.NODE_ENV === 'development') return @@ -10,13 +16,35 @@ export function initializeAnalytics() { throw new Error(`REACT_APP_AMPLITUDE_KEY must be a defined environment variable`) } - init(API_KEY) + init( + API_KEY, + undefined, // User ID should be undefined to let Amplitude default to Device ID + { + // Disable tracking of private user information by Amplitude + trackingOptions: { + ipAddress: false, + carrier: false, + city: false, + region: false, + country: false, + dma: false, // Disables designated market area tracking + }, + } + ) } +/** Sends an event to Amplitude. */ export function sendAnalyticsEvent(eventName: string, eventProperties?: Record) { track(eventName, eventProperties) } +/** + * Updates the User Model's properties in Amplitude that represents + * the current session's user. + * + * See https://help.amplitude.com/hc/en-us/articles/115002380567-User-properties-and-event-properties + * for details. + */ export function updateAnalyticsUserModel( operation: UserPropertyOperations, propertyName: string, From 561aadfdf42a740aed40f49ccc7f063165dff707 Mon Sep 17 00:00:00 2001 From: Lynn Yu Date: Wed, 6 Jul 2022 18:09:17 -0400 Subject: [PATCH 04/28] respond to vm comments --- src/components/AmplitudeAnalytics/index.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/components/AmplitudeAnalytics/index.ts b/src/components/AmplitudeAnalytics/index.ts index c405dafd567..994ec53ef4f 100644 --- a/src/components/AmplitudeAnalytics/index.ts +++ b/src/components/AmplitudeAnalytics/index.ts @@ -18,8 +18,8 @@ export function initializeAnalytics() { init( API_KEY, - undefined, // User ID should be undefined to let Amplitude default to Device ID - { + /* userId= */ undefined, // User ID should be undefined to let Amplitude default to Device ID + /* options= */ { // Disable tracking of private user information by Amplitude trackingOptions: { ipAddress: false, @@ -35,6 +35,10 @@ export function initializeAnalytics() { /** Sends an event to Amplitude. */ export function sendAnalyticsEvent(eventName: string, eventProperties?: Record) { + if (process.env.NODE_ENV === 'development') { + console.log('amplitude event log:', `${eventName}: ${JSON.stringify(eventProperties)}`) + } + track(eventName, eventProperties) } @@ -50,6 +54,10 @@ export function updateAnalyticsUserModel( propertyName: string, propertyValue: string | number ) { + if (process.env.NODE_ENV === 'development') { + console.log(`amplitude user model update with operation ${operation}:`, `${propertyName}: ${propertyValue}`) + } + const identifyObj = new Identify() switch (operation) { case UserPropertyOperations.SET: From 682d713d5809eb9862cb4ebf71ce85f2da7f960c Mon Sep 17 00:00:00 2001 From: Lynn Yu Date: Thu, 7 Jul 2022 15:17:24 -0400 Subject: [PATCH 05/28] respond to cmcewen comments --- src/components/AmplitudeAnalytics/index.ts | 98 +++++++++++++--------- 1 file changed, 60 insertions(+), 38 deletions(-) diff --git a/src/components/AmplitudeAnalytics/index.ts b/src/components/AmplitudeAnalytics/index.ts index 994ec53ef4f..f59acdd428c 100644 --- a/src/components/AmplitudeAnalytics/index.ts +++ b/src/components/AmplitudeAnalytics/index.ts @@ -1,7 +1,5 @@ import { Identify, identify, init, track } from '@amplitude/analytics-browser' -import { UserPropertyOperations } from './constants' - /** * Initializes Amplitude with API key for project. * @@ -43,47 +41,71 @@ export function sendAnalyticsEvent(eventName: string, eventProperties?: Record Date: Thu, 7 Jul 2022 15:29:34 -0400 Subject: [PATCH 06/28] fix: remove unused constants --- src/components/AmplitudeAnalytics/constants.ts | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/components/AmplitudeAnalytics/constants.ts b/src/components/AmplitudeAnalytics/constants.ts index 8612ebad96d..45d9cf28687 100644 --- a/src/components/AmplitudeAnalytics/constants.ts +++ b/src/components/AmplitudeAnalytics/constants.ts @@ -8,20 +8,3 @@ export enum EventName { SWAP_SUBMITTED = 'Swap Submitted', PAGE_VIEWED = 'Page Viewed', } - -/** - * User Model property operations available via Amplitude's Identify API. - * - * See https://www.docs.developers.amplitude.com/data/sdks/typescript-browser/#user-properties - * for detailed documentation. - */ -export enum UserPropertyOperations { - SET, - SET_ONCE, - ADD, - ARRAY_PREPEND, - ARRAY_APPEND, - ARRAY_PREINSERT, - ARRAY_POSTINSERT, - ARRAY_REMOVE, -} From 6e5119df4fd81e0bd7bfc3109143af63d13ab240 Mon Sep 17 00:00:00 2001 From: Lynn Yu Date: Thu, 7 Jul 2022 18:53:15 -0400 Subject: [PATCH 07/28] init commit --- src/components/AmplitudeAnalytics/Trace.tsx | 53 +++++++++++++ .../AmplitudeAnalytics/TraceEvent.tsx | 74 +++++++++++++++++++ .../AmplitudeAnalytics/constants.ts | 42 +++++++++++ 3 files changed, 169 insertions(+) create mode 100644 src/components/AmplitudeAnalytics/Trace.tsx create mode 100644 src/components/AmplitudeAnalytics/TraceEvent.tsx diff --git a/src/components/AmplitudeAnalytics/Trace.tsx b/src/components/AmplitudeAnalytics/Trace.tsx new file mode 100644 index 00000000000..b65e4a7a990 --- /dev/null +++ b/src/components/AmplitudeAnalytics/Trace.tsx @@ -0,0 +1,53 @@ +import { createContext, memo, PropsWithChildren, useContext, useEffect, useMemo, useState } from 'react' + +import { sendAnalyticsEvent } from '.' +import { ElementName, EventName, ModalName, PageName, SectionName } from './constants' + +export interface ITraceContext { + // Page name, such as Swap or Explore page. Highest order context. + page?: PageName + + // Enclosed section name. Can be as wide or narrow as necessary to + // provide context. + section?: SectionName | ModalName + + // Element name mostly used to identify events sources + // Does not need to be unique given the higher order page and section. + elementName?: ElementName | string +} + +export const TraceContext = createContext({}) + +type TraceProps = { + logImpression?: boolean // whether to log impression on mount +} & ITraceContext + +/** + * Analytics instrumentation component that combines parent context + * with its own context to provide children a richer context. + */ +function _Trace({ children, logImpression, page, section, elementName }: PropsWithChildren) { + const [hasAlreadyLoggedImpression, setHasAlreadyLoggedImpression] = useState(false) + const parentTrace = useContext(TraceContext) + + // Component props are destructured to ensure shallow comparison + const combinedProps = useMemo( + () => ({ + ...parentTrace, + // removes `undefined` values + ...JSON.parse(JSON.stringify({ page, section, elementName })), + }), + [elementName, parentTrace, page, section] + ) + + useEffect(() => { + if (logImpression && !hasAlreadyLoggedImpression) { + sendAnalyticsEvent(EventName.PAGE_VIEWED, combinedProps) + setHasAlreadyLoggedImpression(true) + } + }, [combinedProps, hasAlreadyLoggedImpression, logImpression]) + + return {children} +} + +export const Trace = memo(_Trace) diff --git a/src/components/AmplitudeAnalytics/TraceEvent.tsx b/src/components/AmplitudeAnalytics/TraceEvent.tsx new file mode 100644 index 00000000000..d66abcc5ee8 --- /dev/null +++ b/src/components/AmplitudeAnalytics/TraceEvent.tsx @@ -0,0 +1,74 @@ +import React, { PropsWithChildren } from 'react' +import { SyntheticEvent } from 'react' + +import { sendAnalyticsEvent } from '.' +import { EventName, PartialActionProps } from './constants' +import { ITraceContext, Trace, TraceContext } from './Trace' + +type TraceEventProps = { + actionProps: PartialActionProps + eventName: EventName +} & ITraceContext + +/** + * Telemetry instrumentation component that wraps event callbacks with logging logic. + * + * @example + * + * + * + */ +function _TraceEvent(props: PropsWithChildren) { + const { eventName, actionProps, children, ...logEventProps } = props + + return ( + + + {(consumedProps) => + React.Children.map(children, (child) => { + if (!React.isValidElement(child)) { + return child + } + + // For each child, augment event handlers defined in `actionProps` with event tracing + return React.cloneElement(child, getEventHandlers(child, consumedProps, actionProps, eventName)) + }) + } + + + ) +} + +export const TraceEvent = React.memo(_TraceEvent) + +function getKeys(obj: T) { + return Object.keys(obj) as Array +} + +/** + * Given a set of child element and action props, returns a spreadabble + * object of the event handlers augmented with telemetry logging. + */ +function getEventHandlers( + child: React.ReactElement, + consumedProps: ITraceContext, + actionProps: PartialActionProps, + eventName: EventName +) { + const eventHandlers: Partial) => void>> = {} + + for (const eventHandlerName of getKeys(actionProps)) { + eventHandlers[eventHandlerName] = (eventHandlerArgs: unknown) => { + // call child event handler with original arguments + child.props[eventHandlerName]?.apply(child, eventHandlerArgs) + + // augment handler with analytics logging + sendAnalyticsEvent(actionProps[eventHandlerName]?.action ?? eventName, { + ...consumedProps, + }) + } + } + + // return a spreadable event handler object + return eventHandlers +} diff --git a/src/components/AmplitudeAnalytics/constants.ts b/src/components/AmplitudeAnalytics/constants.ts index 45d9cf28687..ab9c1b127c7 100644 --- a/src/components/AmplitudeAnalytics/constants.ts +++ b/src/components/AmplitudeAnalytics/constants.ts @@ -8,3 +8,45 @@ export enum EventName { SWAP_SUBMITTED = 'Swap Submitted', PAGE_VIEWED = 'Page Viewed', } + +export const enum PageName { + SWAP_PAGE = 'swap-page', +} + +/** + * Known sections to provide telemetry context. + * Can help disambiguate low-level elements that may share a name. + * For example, a `back` button in a modal will have the same + * `elementName`, but a different `section`. + */ +export const enum SectionName { + CURRENCY_INPUT_PANEL = 'swap-currency-input', +} + +/** Known modals for telemetry purposes. */ +export const enum ModalName { + SWAP = 'swap-modal', +} + +/** + * Known element names for telemetry purposes. + * Use to identify low-level components given a TraceContext + */ + +export const enum ElementName { + CONFIRM_SWAP_BUTTON = 'confirm-swap-or-send', + SWAP_BUTTON = 'swap-button', +} + +/** + * Known actions and their properties. + * Use destructure assignments to pick properties. + * @example + * const buttonProps = (({ onPress, onLongPress }) => ({ onPress, onLongPress }))(ActionProps) + */ +export const ActionProps = { + onClick: { action: 'click' }, + // more to be added +} + +export type PartialActionProps = Partial From 8f185b396af4f735dd1722a84f179122e4cbfe60 Mon Sep 17 00:00:00 2001 From: Lynn Yu Date: Thu, 7 Jul 2022 20:38:24 -0400 Subject: [PATCH 08/28] adapt to web --- src/components/AmplitudeAnalytics/Trace.tsx | 9 +++++---- src/components/AmplitudeAnalytics/TraceEvent.tsx | 15 +++++++++------ src/components/AmplitudeAnalytics/constants.ts | 6 +++--- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/components/AmplitudeAnalytics/Trace.tsx b/src/components/AmplitudeAnalytics/Trace.tsx index b65e4a7a990..83d5fca290e 100644 --- a/src/components/AmplitudeAnalytics/Trace.tsx +++ b/src/components/AmplitudeAnalytics/Trace.tsx @@ -20,13 +20,14 @@ export const TraceContext = createContext({}) type TraceProps = { logImpression?: boolean // whether to log impression on mount + eventName?: EventName } & ITraceContext /** * Analytics instrumentation component that combines parent context - * with its own context to provide children a richer context. + * with its own context. */ -function _Trace({ children, logImpression, page, section, elementName }: PropsWithChildren) { +function _Trace({ children, logImpression, page, section, elementName, eventName }: PropsWithChildren) { const [hasAlreadyLoggedImpression, setHasAlreadyLoggedImpression] = useState(false) const parentTrace = useContext(TraceContext) @@ -42,10 +43,10 @@ function _Trace({ children, logImpression, page, section, elementName }: PropsWi useEffect(() => { if (logImpression && !hasAlreadyLoggedImpression) { - sendAnalyticsEvent(EventName.PAGE_VIEWED, combinedProps) + sendAnalyticsEvent(eventName ?? EventName.PAGE_VIEWED, combinedProps) setHasAlreadyLoggedImpression(true) } - }, [combinedProps, hasAlreadyLoggedImpression, logImpression]) + }, [combinedProps, hasAlreadyLoggedImpression, logImpression, eventName]) return {children} } diff --git a/src/components/AmplitudeAnalytics/TraceEvent.tsx b/src/components/AmplitudeAnalytics/TraceEvent.tsx index d66abcc5ee8..1b0c20d0698 100644 --- a/src/components/AmplitudeAnalytics/TraceEvent.tsx +++ b/src/components/AmplitudeAnalytics/TraceEvent.tsx @@ -8,6 +8,7 @@ import { ITraceContext, Trace, TraceContext } from './Trace' type TraceEventProps = { actionProps: PartialActionProps eventName: EventName + eventProperties?: Record } & ITraceContext /** @@ -19,7 +20,7 @@ type TraceEventProps = { * */ function _TraceEvent(props: PropsWithChildren) { - const { eventName, actionProps, children, ...logEventProps } = props + const { eventName, eventProperties, actionProps, children, ...logEventProps } = props return ( @@ -31,7 +32,10 @@ function _TraceEvent(props: PropsWithChildren) { } // For each child, augment event handlers defined in `actionProps` with event tracing - return React.cloneElement(child, getEventHandlers(child, consumedProps, actionProps, eventName)) + return React.cloneElement( + child, + getEventHandlers(child, consumedProps, actionProps, eventName, eventProperties) + ) }) } @@ -53,7 +57,8 @@ function getEventHandlers( child: React.ReactElement, consumedProps: ITraceContext, actionProps: PartialActionProps, - eventName: EventName + eventName: EventName, + eventProperties?: Record ) { const eventHandlers: Partial) => void>> = {} @@ -63,9 +68,7 @@ function getEventHandlers( child.props[eventHandlerName]?.apply(child, eventHandlerArgs) // augment handler with analytics logging - sendAnalyticsEvent(actionProps[eventHandlerName]?.action ?? eventName, { - ...consumedProps, - }) + sendAnalyticsEvent(eventName, { ...consumedProps, ...eventProperties }) } } diff --git a/src/components/AmplitudeAnalytics/constants.ts b/src/components/AmplitudeAnalytics/constants.ts index ab9c1b127c7..46a6771a998 100644 --- a/src/components/AmplitudeAnalytics/constants.ts +++ b/src/components/AmplitudeAnalytics/constants.ts @@ -14,7 +14,7 @@ export const enum PageName { } /** - * Known sections to provide telemetry context. + * Known sections to provide analytics context. * Can help disambiguate low-level elements that may share a name. * For example, a `back` button in a modal will have the same * `elementName`, but a different `section`. @@ -29,7 +29,7 @@ export const enum ModalName { } /** - * Known element names for telemetry purposes. + * Known element names for analytics purposes. * Use to identify low-level components given a TraceContext */ @@ -42,7 +42,7 @@ export const enum ElementName { * Known actions and their properties. * Use destructure assignments to pick properties. * @example - * const buttonProps = (({ onPress, onLongPress }) => ({ onPress, onLongPress }))(ActionProps) + * const buttonProps = (({ onClick }) => ({ onClick }))(ActionProps) */ export const ActionProps = { onClick: { action: 'click' }, From 02b927c13e36578811b0ee37dcb9485d6ccecc1d Mon Sep 17 00:00:00 2001 From: Lynn Yu Date: Thu, 7 Jul 2022 20:41:10 -0400 Subject: [PATCH 09/28] add optional event properties to trace --- src/components/AmplitudeAnalytics/Trace.tsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/components/AmplitudeAnalytics/Trace.tsx b/src/components/AmplitudeAnalytics/Trace.tsx index 83d5fca290e..42bf693bccf 100644 --- a/src/components/AmplitudeAnalytics/Trace.tsx +++ b/src/components/AmplitudeAnalytics/Trace.tsx @@ -21,13 +21,22 @@ export const TraceContext = createContext({}) type TraceProps = { logImpression?: boolean // whether to log impression on mount eventName?: EventName + eventProperties?: Record } & ITraceContext /** * Analytics instrumentation component that combines parent context * with its own context. */ -function _Trace({ children, logImpression, page, section, elementName, eventName }: PropsWithChildren) { +function _Trace({ + children, + logImpression, + page, + section, + elementName, + eventName, + eventProperties, +}: PropsWithChildren) { const [hasAlreadyLoggedImpression, setHasAlreadyLoggedImpression] = useState(false) const parentTrace = useContext(TraceContext) @@ -43,10 +52,10 @@ function _Trace({ children, logImpression, page, section, elementName, eventName useEffect(() => { if (logImpression && !hasAlreadyLoggedImpression) { - sendAnalyticsEvent(eventName ?? EventName.PAGE_VIEWED, combinedProps) + sendAnalyticsEvent(eventName ?? EventName.PAGE_VIEWED, { ...combinedProps, ...eventProperties }) setHasAlreadyLoggedImpression(true) } - }, [combinedProps, hasAlreadyLoggedImpression, logImpression, eventName]) + }, [combinedProps, hasAlreadyLoggedImpression, logImpression, eventName, eventProperties]) return {children} } From f5ad7f157a1d5ce69964d48a7b82f112a17b0def Mon Sep 17 00:00:00 2001 From: Lynn Yu Date: Thu, 7 Jul 2022 20:49:20 -0400 Subject: [PATCH 10/28] correct telemetry to analytics --- src/components/AmplitudeAnalytics/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AmplitudeAnalytics/constants.ts b/src/components/AmplitudeAnalytics/constants.ts index 46a6771a998..ef4c46050b3 100644 --- a/src/components/AmplitudeAnalytics/constants.ts +++ b/src/components/AmplitudeAnalytics/constants.ts @@ -23,7 +23,7 @@ export const enum SectionName { CURRENCY_INPUT_PANEL = 'swap-currency-input', } -/** Known modals for telemetry purposes. */ +/** Known modals for analytics purposes. */ export const enum ModalName { SWAP = 'swap-modal', } From 4fe3aa2fe3913bee75f7a777b7f5e6598660a037 Mon Sep 17 00:00:00 2001 From: Lynn Yu Date: Thu, 7 Jul 2022 21:11:13 -0400 Subject: [PATCH 11/28] init commit --- .../AmplitudeAnalytics/TraceEvent.tsx | 4 +-- src/components/swap/SwapModalFooter.tsx | 29 +++++++++++++------ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/components/AmplitudeAnalytics/TraceEvent.tsx b/src/components/AmplitudeAnalytics/TraceEvent.tsx index 1b0c20d0698..ca75bd691ff 100644 --- a/src/components/AmplitudeAnalytics/TraceEvent.tsx +++ b/src/components/AmplitudeAnalytics/TraceEvent.tsx @@ -12,7 +12,7 @@ type TraceEventProps = { } & ITraceContext /** - * Telemetry instrumentation component that wraps event callbacks with logging logic. + * Analytics instrumentation component that wraps event callbacks with logging logic. * * @example * @@ -51,7 +51,7 @@ function getKeys(obj: T) { /** * Given a set of child element and action props, returns a spreadabble - * object of the event handlers augmented with telemetry logging. + * object of the event handlers augmented with analytics logging. */ function getEventHandlers( child: React.ReactElement, diff --git a/src/components/swap/SwapModalFooter.tsx b/src/components/swap/SwapModalFooter.tsx index 97dd9950223..ab33556f7bb 100644 --- a/src/components/swap/SwapModalFooter.tsx +++ b/src/components/swap/SwapModalFooter.tsx @@ -1,6 +1,8 @@ import { Trans } from '@lingui/macro' import { Trade } from '@uniswap/router-sdk' import { Currency, TradeType } from '@uniswap/sdk-core' +import { EventName } from 'components/AmplitudeAnalytics/constants' +import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent' import { ReactNode } from 'react' import { Text } from 'rebass' @@ -18,19 +20,28 @@ export default function SwapModalFooter({ swapErrorMessage: ReactNode | undefined disabledConfirm: boolean }) { + const eventProperties = {} + return ( <> - - - Confirm Swap - - + + + Confirm Swap + + + {swapErrorMessage ? : null} From 2c40799bce8d0fbb35b8b0c4722ac2dcab8ef449 Mon Sep 17 00:00:00 2001 From: Lynn Yu Date: Thu, 7 Jul 2022 21:14:20 -0400 Subject: [PATCH 12/28] change telemetry to analytics in doc --- src/components/AmplitudeAnalytics/TraceEvent.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/AmplitudeAnalytics/TraceEvent.tsx b/src/components/AmplitudeAnalytics/TraceEvent.tsx index 1b0c20d0698..ca75bd691ff 100644 --- a/src/components/AmplitudeAnalytics/TraceEvent.tsx +++ b/src/components/AmplitudeAnalytics/TraceEvent.tsx @@ -12,7 +12,7 @@ type TraceEventProps = { } & ITraceContext /** - * Telemetry instrumentation component that wraps event callbacks with logging logic. + * Analytics instrumentation component that wraps event callbacks with logging logic. * * @example * @@ -51,7 +51,7 @@ function getKeys(obj: T) { /** * Given a set of child element and action props, returns a spreadabble - * object of the event handlers augmented with telemetry logging. + * object of the event handlers augmented with analytics logging. */ function getEventHandlers( child: React.ReactElement, From 84e96837691e5b9e4b45019b5a4d2f60ed88a7e3 Mon Sep 17 00:00:00 2001 From: Lynn Yu Date: Thu, 7 Jul 2022 23:06:16 -0400 Subject: [PATCH 13/28] init commit --- src/components/swap/SwapModalFooter.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/swap/SwapModalFooter.tsx b/src/components/swap/SwapModalFooter.tsx index ab33556f7bb..a89a38c73c6 100644 --- a/src/components/swap/SwapModalFooter.tsx +++ b/src/components/swap/SwapModalFooter.tsx @@ -2,6 +2,7 @@ import { Trans } from '@lingui/macro' import { Trade } from '@uniswap/router-sdk' import { Currency, TradeType } from '@uniswap/sdk-core' import { EventName } from 'components/AmplitudeAnalytics/constants' +import { ActionProps } from 'components/AmplitudeAnalytics/constants' import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent' import { ReactNode } from 'react' import { Text } from 'rebass' @@ -10,6 +11,8 @@ import { ButtonError } from '../Button' import { AutoRow } from '../Row' import { SwapCallbackError } from './styleds' +const ButtonActionProps = (({ onClick }) => ({ onClick }))(ActionProps) + export default function SwapModalFooter({ onConfirm, swapErrorMessage, From 42fddee3787dd4c72e56d3381262e9c13afe836c Mon Sep 17 00:00:00 2001 From: Lynn Yu Date: Thu, 7 Jul 2022 23:40:44 -0400 Subject: [PATCH 14/28] fix: respond to cmcewen comments + initialize analytics in app.tsx + add missing return statement --- src/components/AmplitudeAnalytics/index.ts | 14 ++++++-------- src/pages/App.tsx | 2 ++ yarn.lock | 12 ++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/components/AmplitudeAnalytics/index.ts b/src/components/AmplitudeAnalytics/index.ts index f59acdd428c..d62524408b1 100644 --- a/src/components/AmplitudeAnalytics/index.ts +++ b/src/components/AmplitudeAnalytics/index.ts @@ -35,30 +35,26 @@ export function initializeAnalytics() { export function sendAnalyticsEvent(eventName: string, eventProperties?: Record) { if (process.env.NODE_ENV === 'development') { console.log('amplitude event log:', `${eventName}: ${JSON.stringify(eventProperties)}`) + return } track(eventName, eventProperties) } /** - * Singleton that exposes methods to modify the User Model's properties in + * Class that exposes methods to modify the User Model's properties in * Amplitude that represents the current session's user. * * See https://help.amplitude.com/hc/en-us/articles/115002380567-User-properties-and-event-properties * for details. */ -export class UserModel { - private static _instance: UserModel +class UserModel { private _isDevEnvironemnt = true - private constructor() { + constructor() { process.env.NODE_ENV === 'development' ? (this._isDevEnvironemnt = true) : (this._isDevEnvironemnt = false) } - public static get Instance() { - return this._instance || (this._instance = new this()) - } - public set(propertyName: string, propertyValue: string | number) { if (this._isDevEnvironemnt) { console.log('amplitude user model update with operation set:', `${propertyName}: ${propertyValue}`) @@ -109,3 +105,5 @@ export class UserModel { identify(identifyObj) } } + +export const userModel = new UserModel() diff --git a/src/pages/App.tsx b/src/pages/App.tsx index e6e681fea44..9a9644896e4 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -1,3 +1,4 @@ +import { initializeAnalytics } from 'components/AmplitudeAnalytics' import Loader from 'components/Loader' import TopLevelModals from 'components/TopLevelModals' import ApeModeQueryParamReader from 'hooks/useApeModeQueryParamReader' @@ -66,6 +67,7 @@ const Marginer = styled.div` export default function App() { const history = useHistory() useAnalyticsReporter(useLocation()) + initializeAnalytics() useEffect(() => { const unlisten = history.listen(() => { diff --git a/yarn.lock b/yarn.lock index 538b6ad26c9..002a2e8ff72 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17728,12 +17728,7 @@ tslib@^1.0.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2, tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@~2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" - integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== - -tslib@^2.3.1: +tslib@^2, tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1: version "2.4.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== @@ -17753,6 +17748,11 @@ tslib@~2.2.0: resolved "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz" integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w== +tslib@~2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + tsutils@^3.17.1, tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" From 5ee155d260b3567b258a78be43c2c85e1f623af9 Mon Sep 17 00:00:00 2001 From: Lynn Yu Date: Fri, 8 Jul 2022 00:05:21 -0400 Subject: [PATCH 15/28] add element name constant --- src/components/swap/SwapModalFooter.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/swap/SwapModalFooter.tsx b/src/components/swap/SwapModalFooter.tsx index a89a38c73c6..9ba4548d5ea 100644 --- a/src/components/swap/SwapModalFooter.tsx +++ b/src/components/swap/SwapModalFooter.tsx @@ -1,7 +1,7 @@ import { Trans } from '@lingui/macro' import { Trade } from '@uniswap/router-sdk' import { Currency, TradeType } from '@uniswap/sdk-core' -import { EventName } from 'components/AmplitudeAnalytics/constants' +import { ElementName, EventName } from 'components/AmplitudeAnalytics/constants' import { ActionProps } from 'components/AmplitudeAnalytics/constants' import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent' import { ReactNode } from 'react' @@ -30,7 +30,7 @@ export default function SwapModalFooter({ @@ -38,7 +38,8 @@ export default function SwapModalFooter({ onClick={onConfirm} disabled={disabledConfirm} style={{ margin: '10px 0 0 0' }} - id="confirm-swap-or-send" + className={ElementName.CONFIRM_SWAP_BUTTON} + id={ElementName.CONFIRM_SWAP_BUTTON} > Confirm Swap From 9232948f81124a3e826696b9918d1bd71bb8a403 Mon Sep 17 00:00:00 2001 From: Lynn Yu Date: Tue, 19 Jul 2022 22:52:40 -0400 Subject: [PATCH 16/28] init commit --- src/components/swap/ConfirmSwapModal.tsx | 4 +- src/components/swap/SwapModalFooter.tsx | 69 ++++++++++++++++++++---- 2 files changed, 62 insertions(+), 11 deletions(-) diff --git a/src/components/swap/ConfirmSwapModal.tsx b/src/components/swap/ConfirmSwapModal.tsx index 0ce10bf7d6e..fb366f4fdf9 100644 --- a/src/components/swap/ConfirmSwapModal.tsx +++ b/src/components/swap/ConfirmSwapModal.tsx @@ -59,11 +59,13 @@ export default function ConfirmSwapModal({ ) : null - }, [onConfirm, showAcceptChanges, swapErrorMessage, trade]) + }, [onConfirm, showAcceptChanges, swapErrorMessage, trade, allowedSlippage, txHash]) // text to show while loading const pendingText = ( diff --git a/src/components/swap/SwapModalFooter.tsx b/src/components/swap/SwapModalFooter.tsx index 9ba4548d5ea..79046113a8b 100644 --- a/src/components/swap/SwapModalFooter.tsx +++ b/src/components/swap/SwapModalFooter.tsx @@ -1,38 +1,87 @@ import { Trans } from '@lingui/macro' -import { Trade } from '@uniswap/router-sdk' -import { Currency, TradeType } from '@uniswap/sdk-core' +import { Currency, Percent, TradeType } from '@uniswap/sdk-core' import { ElementName, EventName } from 'components/AmplitudeAnalytics/constants' -import { ActionProps } from 'components/AmplitudeAnalytics/constants' +import { Event } from 'components/AmplitudeAnalytics/constants' import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent' +import { useStablecoinValue } from 'hooks/useStablecoinPrice' +import useTransactionDeadline from 'hooks/useTransactionDeadline' import { ReactNode } from 'react' import { Text } from 'rebass' +import { InterfaceTrade } from 'state/routing/types' +import { useUserSlippageTolerance } from 'state/user/hooks' import { ButtonError } from '../Button' import { AutoRow } from '../Row' import { SwapCallbackError } from './styleds' -const ButtonActionProps = (({ onClick }) => ({ onClick }))(ActionProps) +const formatAnalyticsEventProperties = ( + trade: InterfaceTrade, + txHash: string | undefined, + allowedSlippage: Percent, + transactionDeadlineSecondsSinceEpoch: number | undefined, + isAutoSlippage: boolean, + tokenInAmountUsd: string | undefined, + tokenOutAmountUsd: string | undefined +) => ({ + estimated_network_fee_usd: trade.gasUseEstimateUSD ? parseFloat(trade.gasUseEstimateUSD?.toFixed(2)) : undefined, + transaction_hash: txHash, + transaction_deadline_seconds: transactionDeadlineSecondsSinceEpoch + ? /** durationInSeconds= */ transactionDeadlineSecondsSinceEpoch - new Date().getTime() / 1000 + : undefined, + token_in_amount_usd: tokenInAmountUsd ? parseFloat(tokenInAmountUsd) : undefined, + token_out_amount_usd: tokenOutAmountUsd ? parseFloat(tokenOutAmountUsd) : undefined, + token_in_address: trade.inputAmount.currency.isToken ? trade.inputAmount.currency.address : undefined, + token_out_address: trade.outputAmount.currency.isToken ? trade.outputAmount.currency.address : undefined, + token_in_symbol: trade.inputAmount.currency.symbol, + token_out_symbol: trade.outputAmount.currency.symbol, + token_in_amount: trade.inputAmount.currency.decimals, + token_out_amount: trade.outputAmount.currency.decimals, + price_impact_percentage: parseFloat(trade.priceImpact.toFixed(2)), + allowed_slippage_percentage: parseFloat(allowedSlippage.toFixed(2)), + is_auto_slippage: isAutoSlippage, + chain_id: + trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId + ? trade.inputAmount.currency.chainId + : undefined, + // TODO(lynnshaoyu): implement duration_from_first_quote_to_swap_submission_seconds +}) export default function SwapModalFooter({ + trade, + allowedSlippage, + txHash, onConfirm, swapErrorMessage, disabledConfirm, }: { - trade: Trade + trade: InterfaceTrade + txHash: string | undefined + allowedSlippage: Percent onConfirm: () => void swapErrorMessage: ReactNode | undefined disabledConfirm: boolean }) { - const eventProperties = {} + const transactionDeadlineSecondsSinceEpoch = useTransactionDeadline()?.toNumber() // in seconds since epoch + const isAutoSlippage = useUserSlippageTolerance() === 'auto' + const tokenInAmountUsd = useStablecoinValue(trade.inputAmount)?.toFixed(2) + const tokenOutAmountUsd = useStablecoinValue(trade.outputAmount)?.toFixed(2) return ( <> Date: Wed, 20 Jul 2022 11:24:39 -0400 Subject: [PATCH 17/28] correct price_impact calculation --- src/components/swap/AdvancedSwapDetails.tsx | 6 +++++- src/components/swap/SwapModalFooter.tsx | 11 ++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/components/swap/AdvancedSwapDetails.tsx b/src/components/swap/AdvancedSwapDetails.tsx index 9b006d40578..ea41d8c123d 100644 --- a/src/components/swap/AdvancedSwapDetails.tsx +++ b/src/components/swap/AdvancedSwapDetails.tsx @@ -45,6 +45,10 @@ function TextWithLoadingPlaceholder({ ) } +export function getPriceImpact(lpFeePercent: Percent, trade: InterfaceTrade): Percent { + return trade.priceImpact.subtract(lpFeePercent) +} + export function AdvancedSwapDetails({ trade, allowedSlippage, @@ -59,7 +63,7 @@ export function AdvancedSwapDetails({ if (!trade) return { expectedOutputAmount: undefined, priceImpact: undefined } const expectedOutputAmount = trade.outputAmount const realizedLpFeePercent = computeRealizedLPFeePercent(trade) - const priceImpact = trade.priceImpact.subtract(realizedLpFeePercent) + const priceImpact = getPriceImpact(realizedLpFeePercent, trade) return { expectedOutputAmount, priceImpact } }, [trade]) diff --git a/src/components/swap/SwapModalFooter.tsx b/src/components/swap/SwapModalFooter.tsx index 79046113a8b..3e1021b624c 100644 --- a/src/components/swap/SwapModalFooter.tsx +++ b/src/components/swap/SwapModalFooter.tsx @@ -9,9 +9,11 @@ import { ReactNode } from 'react' import { Text } from 'rebass' import { InterfaceTrade } from 'state/routing/types' import { useUserSlippageTolerance } from 'state/user/hooks' +import { computeRealizedLPFeePercent } from 'utils/prices' import { ButtonError } from '../Button' import { AutoRow } from '../Row' +import { getPriceImpact } from './AdvancedSwapDetails' import { SwapCallbackError } from './styleds' const formatAnalyticsEventProperties = ( @@ -21,7 +23,8 @@ const formatAnalyticsEventProperties = ( transactionDeadlineSecondsSinceEpoch: number | undefined, isAutoSlippage: boolean, tokenInAmountUsd: string | undefined, - tokenOutAmountUsd: string | undefined + tokenOutAmountUsd: string | undefined, + lpFeePercent: Percent ) => ({ estimated_network_fee_usd: trade.gasUseEstimateUSD ? parseFloat(trade.gasUseEstimateUSD?.toFixed(2)) : undefined, transaction_hash: txHash, @@ -36,7 +39,7 @@ const formatAnalyticsEventProperties = ( token_out_symbol: trade.outputAmount.currency.symbol, token_in_amount: trade.inputAmount.currency.decimals, token_out_amount: trade.outputAmount.currency.decimals, - price_impact_percentage: parseFloat(trade.priceImpact.toFixed(2)), + price_impact_percentage: parseFloat(getPriceImpact(lpFeePercent, trade).toFixed(2)), allowed_slippage_percentage: parseFloat(allowedSlippage.toFixed(2)), is_auto_slippage: isAutoSlippage, chain_id: @@ -65,6 +68,7 @@ export default function SwapModalFooter({ const isAutoSlippage = useUserSlippageTolerance() === 'auto' const tokenInAmountUsd = useStablecoinValue(trade.inputAmount)?.toFixed(2) const tokenOutAmountUsd = useStablecoinValue(trade.outputAmount)?.toFixed(2) + const lpFeePercent = computeRealizedLPFeePercent(trade) return ( <> @@ -80,7 +84,8 @@ export default function SwapModalFooter({ transactionDeadlineSecondsSinceEpoch, isAutoSlippage, tokenInAmountUsd, - tokenOutAmountUsd + tokenOutAmountUsd, + lpFeePercent )} > Date: Wed, 20 Jul 2022 11:34:12 -0400 Subject: [PATCH 18/28] resolve vm comments --- src/components/swap/SwapModalFooter.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/swap/SwapModalFooter.tsx b/src/components/swap/SwapModalFooter.tsx index 3e1021b624c..17fcdd9b8b2 100644 --- a/src/components/swap/SwapModalFooter.tsx +++ b/src/components/swap/SwapModalFooter.tsx @@ -92,7 +92,6 @@ export default function SwapModalFooter({ onClick={onConfirm} disabled={disabledConfirm} style={{ margin: '10px 0 0 0' }} - className={ElementName.CONFIRM_SWAP_BUTTON} id={ElementName.CONFIRM_SWAP_BUTTON} > From 23ab7afbb2e3f3adb121a36e0b09e28bc8fe651f Mon Sep 17 00:00:00 2001 From: Lynn Yu Date: Wed, 20 Jul 2022 14:09:07 -0400 Subject: [PATCH 19/28] fixes in response to comments --- src/components/swap/SwapModalFooter.tsx | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/components/swap/SwapModalFooter.tsx b/src/components/swap/SwapModalFooter.tsx index 17fcdd9b8b2..3075805ef08 100644 --- a/src/components/swap/SwapModalFooter.tsx +++ b/src/components/swap/SwapModalFooter.tsx @@ -1,5 +1,5 @@ import { Trans } from '@lingui/macro' -import { Currency, Percent, TradeType } from '@uniswap/sdk-core' +import { Currency, CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core' import { ElementName, EventName } from 'components/AmplitudeAnalytics/constants' import { Event } from 'components/AmplitudeAnalytics/constants' import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent' @@ -8,7 +8,7 @@ import useTransactionDeadline from 'hooks/useTransactionDeadline' import { ReactNode } from 'react' import { Text } from 'rebass' import { InterfaceTrade } from 'state/routing/types' -import { useUserSlippageTolerance } from 'state/user/hooks' +import { useClientSideRouter, useUserSlippageTolerance } from 'state/user/hooks' import { computeRealizedLPFeePercent } from 'utils/prices' import { ButtonError } from '../Button' @@ -16,21 +16,27 @@ import { AutoRow } from '../Row' import { getPriceImpact } from './AdvancedSwapDetails' import { SwapCallbackError } from './styleds' +function getDurationTillTimestampSinceEpoch(futureTimestampSinceEpoch?: number): number | undefined { + if (!futureTimestampSinceEpoch) return undefined + return futureTimestampSinceEpoch - new Date().getTime() / 1000 +} + +const getFormattedNumber = (initialValue: Percent | CurrencyAmount) => parseFloat(initialValue.toFixed(2)) + const formatAnalyticsEventProperties = ( trade: InterfaceTrade, txHash: string | undefined, allowedSlippage: Percent, transactionDeadlineSecondsSinceEpoch: number | undefined, isAutoSlippage: boolean, + isAutoRouterApi: boolean, tokenInAmountUsd: string | undefined, tokenOutAmountUsd: string | undefined, lpFeePercent: Percent ) => ({ - estimated_network_fee_usd: trade.gasUseEstimateUSD ? parseFloat(trade.gasUseEstimateUSD?.toFixed(2)) : undefined, + estimated_network_fee_usd: trade.gasUseEstimateUSD ? getFormattedNumber(trade.gasUseEstimateUSD) : undefined, transaction_hash: txHash, - transaction_deadline_seconds: transactionDeadlineSecondsSinceEpoch - ? /** durationInSeconds= */ transactionDeadlineSecondsSinceEpoch - new Date().getTime() / 1000 - : undefined, + transaction_deadline_seconds: getDurationTillTimestampSinceEpoch(transactionDeadlineSecondsSinceEpoch), token_in_amount_usd: tokenInAmountUsd ? parseFloat(tokenInAmountUsd) : undefined, token_out_amount_usd: tokenOutAmountUsd ? parseFloat(tokenOutAmountUsd) : undefined, token_in_address: trade.inputAmount.currency.isToken ? trade.inputAmount.currency.address : undefined, @@ -39,8 +45,9 @@ const formatAnalyticsEventProperties = ( token_out_symbol: trade.outputAmount.currency.symbol, token_in_amount: trade.inputAmount.currency.decimals, token_out_amount: trade.outputAmount.currency.decimals, - price_impact_percentage: parseFloat(getPriceImpact(lpFeePercent, trade).toFixed(2)), - allowed_slippage_percentage: parseFloat(allowedSlippage.toFixed(2)), + price_impact_percentage: getFormattedNumber(getPriceImpact(lpFeePercent, trade)), + allowed_slippage_percentage: getFormattedNumber(allowedSlippage), + is_auto_router_api: isAutoRouterApi, is_auto_slippage: isAutoSlippage, chain_id: trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId @@ -66,6 +73,7 @@ export default function SwapModalFooter({ }) { const transactionDeadlineSecondsSinceEpoch = useTransactionDeadline()?.toNumber() // in seconds since epoch const isAutoSlippage = useUserSlippageTolerance() === 'auto' + const [clientSideRouter] = useClientSideRouter() const tokenInAmountUsd = useStablecoinValue(trade.inputAmount)?.toFixed(2) const tokenOutAmountUsd = useStablecoinValue(trade.outputAmount)?.toFixed(2) const lpFeePercent = computeRealizedLPFeePercent(trade) @@ -83,6 +91,7 @@ export default function SwapModalFooter({ allowedSlippage, transactionDeadlineSecondsSinceEpoch, isAutoSlippage, + /** isAutoRouterApi= */ !clientSideRouter, tokenInAmountUsd, tokenOutAmountUsd, lpFeePercent From d314f770edd23960715849152db106e5ffe4e26c Mon Sep 17 00:00:00 2001 From: Lynn Yu Date: Wed, 20 Jul 2022 14:24:07 -0400 Subject: [PATCH 20/28] respond to vm --- src/components/swap/SwapModalFooter.tsx | 40 ++++++++++++++++--------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/src/components/swap/SwapModalFooter.tsx b/src/components/swap/SwapModalFooter.tsx index 3075805ef08..d1abb7ddc4e 100644 --- a/src/components/swap/SwapModalFooter.tsx +++ b/src/components/swap/SwapModalFooter.tsx @@ -23,17 +23,29 @@ function getDurationTillTimestampSinceEpoch(futureTimestampSinceEpoch?: number): const getFormattedNumber = (initialValue: Percent | CurrencyAmount) => parseFloat(initialValue.toFixed(2)) -const formatAnalyticsEventProperties = ( - trade: InterfaceTrade, - txHash: string | undefined, - allowedSlippage: Percent, - transactionDeadlineSecondsSinceEpoch: number | undefined, - isAutoSlippage: boolean, - isAutoRouterApi: boolean, - tokenInAmountUsd: string | undefined, - tokenOutAmountUsd: string | undefined, +interface AnalyticsEventProps { + trade: InterfaceTrade + txHash: string | undefined + allowedSlippage: Percent + transactionDeadlineSecondsSinceEpoch: number | undefined + isAutoSlippage: boolean + isAutoRouterApi: boolean + tokenInAmountUsd: string | undefined + tokenOutAmountUsd: string | undefined lpFeePercent: Percent -) => ({ +} + +const formatAnalyticsEventProperties = ({ + trade, + txHash, + allowedSlippage, + transactionDeadlineSecondsSinceEpoch, + isAutoSlippage, + isAutoRouterApi, + tokenInAmountUsd, + tokenOutAmountUsd, + lpFeePercent, +}: AnalyticsEventProps) => ({ estimated_network_fee_usd: trade.gasUseEstimateUSD ? getFormattedNumber(trade.gasUseEstimateUSD) : undefined, transaction_hash: txHash, transaction_deadline_seconds: getDurationTillTimestampSinceEpoch(transactionDeadlineSecondsSinceEpoch), @@ -85,17 +97,17 @@ export default function SwapModalFooter({ events={[Event.onClick]} element={ElementName.CONFIRM_SWAP_BUTTON} name={EventName.SWAP_SUBMITTED} - properties={formatAnalyticsEventProperties( + properties={formatAnalyticsEventProperties({ trade, txHash, allowedSlippage, transactionDeadlineSecondsSinceEpoch, isAutoSlippage, - /** isAutoRouterApi= */ !clientSideRouter, + isAutoRouterApi: !clientSideRouter, tokenInAmountUsd, tokenOutAmountUsd, - lpFeePercent - )} + lpFeePercent, + })} > Date: Wed, 20 Jul 2022 15:10:37 -0400 Subject: [PATCH 21/28] use ALL significant digits for token amounts --- src/components/swap/SwapModalFooter.tsx | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/components/swap/SwapModalFooter.tsx b/src/components/swap/SwapModalFooter.tsx index d1abb7ddc4e..ad320696ff9 100644 --- a/src/components/swap/SwapModalFooter.tsx +++ b/src/components/swap/SwapModalFooter.tsx @@ -21,7 +21,10 @@ function getDurationTillTimestampSinceEpoch(futureTimestampSinceEpoch?: number): return futureTimestampSinceEpoch - new Date().getTime() / 1000 } -const getFormattedNumber = (initialValue: Percent | CurrencyAmount) => parseFloat(initialValue.toFixed(2)) +const getNumberFormattedToDecimalPlace = ( + intialNumberObject: Percent | CurrencyAmount, + decimalPlace: number +): number => parseFloat(intialNumberObject.toFixed(decimalPlace)) interface AnalyticsEventProps { trade: InterfaceTrade @@ -46,7 +49,9 @@ const formatAnalyticsEventProperties = ({ tokenOutAmountUsd, lpFeePercent, }: AnalyticsEventProps) => ({ - estimated_network_fee_usd: trade.gasUseEstimateUSD ? getFormattedNumber(trade.gasUseEstimateUSD) : undefined, + estimated_network_fee_usd: trade.gasUseEstimateUSD + ? getNumberFormattedToDecimalPlace(trade.gasUseEstimateUSD, 2) + : undefined, transaction_hash: txHash, transaction_deadline_seconds: getDurationTillTimestampSinceEpoch(transactionDeadlineSecondsSinceEpoch), token_in_amount_usd: tokenInAmountUsd ? parseFloat(tokenInAmountUsd) : undefined, @@ -55,10 +60,10 @@ const formatAnalyticsEventProperties = ({ token_out_address: trade.outputAmount.currency.isToken ? trade.outputAmount.currency.address : undefined, token_in_symbol: trade.inputAmount.currency.symbol, token_out_symbol: trade.outputAmount.currency.symbol, - token_in_amount: trade.inputAmount.currency.decimals, - token_out_amount: trade.outputAmount.currency.decimals, - price_impact_percentage: getFormattedNumber(getPriceImpact(lpFeePercent, trade)), - allowed_slippage_percentage: getFormattedNumber(allowedSlippage), + token_in_amount: getNumberFormattedToDecimalPlace(trade.inputAmount, trade.inputAmount.currency.decimals), + token_out_amount: getNumberFormattedToDecimalPlace(trade.outputAmount, trade.outputAmount.currency.decimals), + price_impact_percentage: getNumberFormattedToDecimalPlace(getPriceImpact(lpFeePercent, trade), 2), + allowed_slippage_percentage: getNumberFormattedToDecimalPlace(allowedSlippage, 2), is_auto_router_api: isAutoRouterApi, is_auto_slippage: isAutoSlippage, chain_id: From 519b1cbbff7df29a23a77bbc4e8bc68082381273 Mon Sep 17 00:00:00 2001 From: Lynn Yu Date: Thu, 21 Jul 2022 12:51:14 -0400 Subject: [PATCH 22/28] init commit --- src/components/swap/SwapDetailsDropdown.tsx | 31 +++++++++++++++++---- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/components/swap/SwapDetailsDropdown.tsx b/src/components/swap/SwapDetailsDropdown.tsx index 140c30f71d6..2e4ec30330c 100644 --- a/src/components/swap/SwapDetailsDropdown.tsx +++ b/src/components/swap/SwapDetailsDropdown.tsx @@ -1,6 +1,8 @@ import { Trans } from '@lingui/macro' import { Currency, Percent, TradeType } from '@uniswap/sdk-core' import { useWeb3React } from '@web3-react/core' +import { EventName, SectionName } from 'components/AmplitudeAnalytics/constants' +import { Trace } from 'components/AmplitudeAnalytics/Trace' import AnimatedDropdown from 'components/AnimatedDropdown' import Card, { OutlineCard } from 'components/Card' import { AutoColumn } from 'components/Column' @@ -118,6 +120,18 @@ interface SwapDetailsInlineProps { allowedSlippage: Percent } +const formatAnalyticsEventProperties = (trade: InterfaceTrade) => ({ + token_in_symbol: trade.inputAmount.currency.symbol, + token_out_symbol: trade.outputAmount.currency.symbol, + token_in_address: trade.inputAmount.currency.isToken ? trade.inputAmount.currency.address : undefined, + token_out_address: trade.outputAmount.currency.isToken ? trade.outputAmount.currency.address : undefined, +}) + +// price_impact_percentage +// estimated_network_fee_usd +// token_out_amount +// token_in_amount + export default function SwapDetailsDropdown({ trade, syncing, @@ -166,11 +180,18 @@ export default function SwapDetailsDropdown({ )} {trade ? ( - + + + ) : loading || syncing ? ( From 0e6c7dff7e12672b9c78913d169833348db89de5 Mon Sep 17 00:00:00 2001 From: Lynn Yu Date: Thu, 21 Jul 2022 14:03:41 -0400 Subject: [PATCH 23/28] logged all properties --- .../AmplitudeAnalytics/constants.ts | 1 + src/components/swap/SwapDetailsDropdown.tsx | 36 +++++++++++++------ src/components/swap/SwapModalFooter.tsx | 2 +- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/components/AmplitudeAnalytics/constants.ts b/src/components/AmplitudeAnalytics/constants.ts index 916b2bee083..fa19b792ea7 100644 --- a/src/components/AmplitudeAnalytics/constants.ts +++ b/src/components/AmplitudeAnalytics/constants.ts @@ -10,6 +10,7 @@ export enum EventName { SWAP_AUTOROUTER_VISUALIZATION_EXPANDED = 'Swap Autorouter Visualization Expanded', SWAP_DETAILS_EXPANDED = 'Swap Details Expanded', SWAP_MAX_TOKEN_AMOUNT_SELECTED = 'Swap Max Token Amount Selected', + SWAP_QUOTE_RECEIVED = 'Swap Quote Received', SWAP_SUBMITTED = 'Swap Submitted', SWAP_TOKENS_REVERSED = 'Swap Tokens Reversed', TOKEN_IMPORTED = 'Token Imported', diff --git a/src/components/swap/SwapDetailsDropdown.tsx b/src/components/swap/SwapDetailsDropdown.tsx index 2cd9ce5aba3..03fe5c5267c 100644 --- a/src/components/swap/SwapDetailsDropdown.tsx +++ b/src/components/swap/SwapDetailsDropdown.tsx @@ -1,8 +1,9 @@ import { Trans } from '@lingui/macro' import { Currency, Percent, TradeType } from '@uniswap/sdk-core' import { useWeb3React } from '@web3-react/core' -import { ElementName, Event, EventName, SectionName } from 'components/AmplitudeAnalytics/constants' -import { Trace, TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent' +import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants' +import { Trace } from 'components/AmplitudeAnalytics/Trace' +import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent' import AnimatedDropdown from 'components/AnimatedDropdown' import Card, { OutlineCard } from 'components/Card' import { AutoColumn } from 'components/Column' @@ -16,10 +17,13 @@ import { ChevronDown, Info } from 'react-feather' import { InterfaceTrade } from 'state/routing/types' import styled, { keyframes, useTheme } from 'styled-components/macro' import { HideSmall, ThemedText } from 'theme' +import { computeRealizedLPFeePercent } from 'utils/prices' +import { getPriceImpact } from './AdvancedSwapDetails' import { AdvancedSwapDetails } from './AdvancedSwapDetails' import GasEstimateBadge from './GasEstimateBadge' import { ResponsiveTooltipContainer } from './styleds' +import { getNumberFormattedToDecimalPlace } from './SwapModalFooter' import SwapRoute from './SwapRoute' import TradePrice from './TradePrice' @@ -120,18 +124,27 @@ interface SwapDetailsInlineProps { allowedSlippage: Percent } -const formatAnalyticsEventProperties = (trade: InterfaceTrade) => ({ +const formatAnalyticsEventProperties = ( + trade: InterfaceTrade, + lpFeePercent: Percent | undefined +) => ({ token_in_symbol: trade.inputAmount.currency.symbol, token_out_symbol: trade.outputAmount.currency.symbol, token_in_address: trade.inputAmount.currency.isToken ? trade.inputAmount.currency.address : undefined, token_out_address: trade.outputAmount.currency.isToken ? trade.outputAmount.currency.address : undefined, + price_impact_percentage: lpFeePercent ? getPriceImpact(lpFeePercent, trade) : undefined, + estimated_network_fee_usd: trade.gasUseEstimateUSD + ? getNumberFormattedToDecimalPlace(trade.gasUseEstimateUSD, 2) + : undefined, + chain_id: + trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId + ? trade.inputAmount.currency.chainId + : undefined, + token_in_amount: getNumberFormattedToDecimalPlace(trade.inputAmount, trade.inputAmount.currency.decimals), + token_out_amount: getNumberFormattedToDecimalPlace(trade.outputAmount, trade.outputAmount.currency.decimals), + // TODO(lynnshaoyu): Implement quote_latency_milliseconds. }) -// price_impact_percentage -// estimated_network_fee_usd -// token_out_amount -// token_in_amount - export default function SwapDetailsDropdown({ trade, syncing, @@ -143,6 +156,7 @@ export default function SwapDetailsDropdown({ const theme = useTheme() const { chainId } = useWeb3React() const [showDetails, setShowDetails] = useState(false) + const lpFeePercent = trade ? computeRealizedLPFeePercent(trade) : undefined return ( @@ -187,9 +201,9 @@ export default function SwapDetailsDropdown({ {trade ? ( , decimalPlace: number ): number => parseFloat(intialNumberObject.toFixed(decimalPlace)) From dc6fa77215bf4eb0cfa0c30de97e6fcc66c0dad1 Mon Sep 17 00:00:00 2001 From: Lynn Yu Date: Thu, 21 Jul 2022 16:29:56 -0400 Subject: [PATCH 24/28] create helper function getPriceImpactPercentageNumber --- src/components/swap/SwapModalFooter.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/swap/SwapModalFooter.tsx b/src/components/swap/SwapModalFooter.tsx index ad320696ff9..939ff1e0c49 100644 --- a/src/components/swap/SwapModalFooter.tsx +++ b/src/components/swap/SwapModalFooter.tsx @@ -26,6 +26,14 @@ const getNumberFormattedToDecimalPlace = ( decimalPlace: number ): number => parseFloat(intialNumberObject.toFixed(decimalPlace)) +export const getPriceImpactPercentageNumber = ( + trade?: InterfaceTrade, + lpFeePercent?: Percent +): number | undefined => { + if (!trade || !lpFeePercent) return undefined + return getNumberFormattedToDecimalPlace(getPriceImpact(lpFeePercent, trade), 4) +} + interface AnalyticsEventProps { trade: InterfaceTrade txHash: string | undefined @@ -62,7 +70,7 @@ const formatAnalyticsEventProperties = ({ token_out_symbol: trade.outputAmount.currency.symbol, token_in_amount: getNumberFormattedToDecimalPlace(trade.inputAmount, trade.inputAmount.currency.decimals), token_out_amount: getNumberFormattedToDecimalPlace(trade.outputAmount, trade.outputAmount.currency.decimals), - price_impact_percentage: getNumberFormattedToDecimalPlace(getPriceImpact(lpFeePercent, trade), 2), + price_impact_percentage: getPriceImpactPercentageNumber(trade, lpFeePercent), allowed_slippage_percentage: getNumberFormattedToDecimalPlace(allowedSlippage, 2), is_auto_router_api: isAutoRouterApi, is_auto_slippage: isAutoSlippage, From 34a72c049d787f75314a0210067151868cc50119 Mon Sep 17 00:00:00 2001 From: Lynn Yu Date: Thu, 21 Jul 2022 16:38:04 -0400 Subject: [PATCH 25/28] 4 decimal points for percentages --- src/components/swap/SwapModalFooter.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/swap/SwapModalFooter.tsx b/src/components/swap/SwapModalFooter.tsx index 939ff1e0c49..941739346d9 100644 --- a/src/components/swap/SwapModalFooter.tsx +++ b/src/components/swap/SwapModalFooter.tsx @@ -71,7 +71,7 @@ const formatAnalyticsEventProperties = ({ token_in_amount: getNumberFormattedToDecimalPlace(trade.inputAmount, trade.inputAmount.currency.decimals), token_out_amount: getNumberFormattedToDecimalPlace(trade.outputAmount, trade.outputAmount.currency.decimals), price_impact_percentage: getPriceImpactPercentageNumber(trade, lpFeePercent), - allowed_slippage_percentage: getNumberFormattedToDecimalPlace(allowedSlippage, 2), + allowed_slippage_percentage: getNumberFormattedToDecimalPlace(allowedSlippage, 4), is_auto_router_api: isAutoRouterApi, is_auto_slippage: isAutoSlippage, chain_id: From cf83cea9555dc78d4b02f4aa0b69bfad82199a73 Mon Sep 17 00:00:00 2001 From: Lynn Yu Date: Thu, 21 Jul 2022 17:07:48 -0400 Subject: [PATCH 26/28] price percentage fn --- src/components/AmplitudeAnalytics/constants.ts | 1 + src/components/swap/SwapDetailsDropdown.tsx | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/AmplitudeAnalytics/constants.ts b/src/components/AmplitudeAnalytics/constants.ts index fa19b792ea7..85d1f909bf3 100644 --- a/src/components/AmplitudeAnalytics/constants.ts +++ b/src/components/AmplitudeAnalytics/constants.ts @@ -69,6 +69,7 @@ export const enum ElementName { SWAP_BUTTON = 'swap-button', SWAP_DETAILS_DROPDOWN = 'swap-details-dropdown', SWAP_TOKENS_REVERSE_ARROW_BUTTON = 'swap-tokens-reverse-arrow-button', + SWAP_TRADE_PRICE_ROW = 'swap-trade-price-row', TOKEN_SELECTOR_ROW = 'token-selector-row', WALLET_TYPE_OPTION = 'wallet-type-option', // alphabetize additional element names. diff --git a/src/components/swap/SwapDetailsDropdown.tsx b/src/components/swap/SwapDetailsDropdown.tsx index 03fe5c5267c..c5c63ac0850 100644 --- a/src/components/swap/SwapDetailsDropdown.tsx +++ b/src/components/swap/SwapDetailsDropdown.tsx @@ -19,10 +19,10 @@ import styled, { keyframes, useTheme } from 'styled-components/macro' import { HideSmall, ThemedText } from 'theme' import { computeRealizedLPFeePercent } from 'utils/prices' -import { getPriceImpact } from './AdvancedSwapDetails' import { AdvancedSwapDetails } from './AdvancedSwapDetails' import GasEstimateBadge from './GasEstimateBadge' import { ResponsiveTooltipContainer } from './styleds' +import { getPriceImpactPercentageNumber } from './SwapModalFooter' import { getNumberFormattedToDecimalPlace } from './SwapModalFooter' import SwapRoute from './SwapRoute' import TradePrice from './TradePrice' @@ -132,7 +132,7 @@ const formatAnalyticsEventProperties = ( token_out_symbol: trade.outputAmount.currency.symbol, token_in_address: trade.inputAmount.currency.isToken ? trade.inputAmount.currency.address : undefined, token_out_address: trade.outputAmount.currency.isToken ? trade.outputAmount.currency.address : undefined, - price_impact_percentage: lpFeePercent ? getPriceImpact(lpFeePercent, trade) : undefined, + price_impact_percentage: getPriceImpactPercentageNumber(trade, lpFeePercent), estimated_network_fee_usd: trade.gasUseEstimateUSD ? getNumberFormattedToDecimalPlace(trade.gasUseEstimateUSD, 2) : undefined, From 4c18c398762b3c5df7777486617716aafb62536c Mon Sep 17 00:00:00 2001 From: Lynn Yu Date: Thu, 21 Jul 2022 18:55:41 -0400 Subject: [PATCH 27/28] only log event on FIRST price fetch --- src/components/swap/SwapDetailsDropdown.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/swap/SwapDetailsDropdown.tsx b/src/components/swap/SwapDetailsDropdown.tsx index c9ac23fcc0c..a186781bbf2 100644 --- a/src/components/swap/SwapDetailsDropdown.tsx +++ b/src/components/swap/SwapDetailsDropdown.tsx @@ -12,7 +12,7 @@ import Row, { RowBetween, RowFixed } from 'components/Row' import { MouseoverTooltipContent } from 'components/Tooltip' import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains' import { darken } from 'polished' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { ChevronDown, Info } from 'react-feather' import { InterfaceTrade } from 'state/routing/types' import styled, { keyframes, useTheme } from 'styled-components/macro' @@ -159,8 +159,13 @@ export default function SwapDetailsDropdown({ const theme = useTheme() const { chainId } = useWeb3React() const [showDetails, setShowDetails] = useState(false) + const [isFirstPriceFetch, setIsFirstPriceFetch] = useState(true) const lpFeePercent = trade ? computeRealizedLPFeePercent(trade) : undefined + useEffect(() => { + if (isFirstPriceFetch && syncing) setIsFirstPriceFetch(false) + }, [isFirstPriceFetch, syncing]) + return ( @@ -207,7 +212,7 @@ export default function SwapDetailsDropdown({ name={EventName.SWAP_QUOTE_RECEIVED} element={ElementName.SWAP_TRADE_PRICE_ROW} properties={formatAnalyticsEventProperties(trade, lpFeePercent)} - shouldLogImpression={!loading && !syncing} + shouldLogImpression={!loading && !syncing && isFirstPriceFetch} > Date: Fri, 22 Jul 2022 14:14:15 -0400 Subject: [PATCH 28/28] respond to cmcewen comments --- src/components/AmplitudeAnalytics/utils.ts | 13 ++++++ src/components/swap/SwapDetailsDropdown.tsx | 48 ++++++++++----------- src/components/swap/SwapModalFooter.tsx | 19 +++----- 3 files changed, 42 insertions(+), 38 deletions(-) create mode 100644 src/components/AmplitudeAnalytics/utils.ts diff --git a/src/components/AmplitudeAnalytics/utils.ts b/src/components/AmplitudeAnalytics/utils.ts new file mode 100644 index 00000000000..f72136f16b5 --- /dev/null +++ b/src/components/AmplitudeAnalytics/utils.ts @@ -0,0 +1,13 @@ +import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core' + +export const getDurationTillTimestampSinceEpoch = (futureTimestampSinceEpoch?: number): number | undefined => { + if (!futureTimestampSinceEpoch) return undefined + return futureTimestampSinceEpoch - new Date().getTime() / 1000 +} + +export const getNumberFormattedToDecimalPlace = ( + intialNumberObject: Percent | CurrencyAmount, + decimalPlace: number +): number => parseFloat(intialNumberObject.toFixed(decimalPlace)) + +export const formatPercentInBasisPointsNumber = (percent: Percent): number => parseFloat(percent.toFixed(2)) * 100 diff --git a/src/components/swap/SwapDetailsDropdown.tsx b/src/components/swap/SwapDetailsDropdown.tsx index a186781bbf2..9c9bbecb010 100644 --- a/src/components/swap/SwapDetailsDropdown.tsx +++ b/src/components/swap/SwapDetailsDropdown.tsx @@ -4,6 +4,7 @@ import { useWeb3React } from '@web3-react/core' import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants' import { Trace } from 'components/AmplitudeAnalytics/Trace' import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent' +import { formatPercentInBasisPointsNumber, getNumberFormattedToDecimalPlace } from 'components/AmplitudeAnalytics/utils' import AnimatedDropdown from 'components/AnimatedDropdown' import Card, { OutlineCard } from 'components/Card' import { AutoColumn } from 'components/Column' @@ -23,8 +24,6 @@ import { AdvancedSwapDetails } from './AdvancedSwapDetails' import { getPriceImpactPercent } from './AdvancedSwapDetails' import GasEstimateBadge from './GasEstimateBadge' import { ResponsiveTooltipContainer } from './styleds' -import { formatPercentInBasisPointsNumber } from './SwapModalFooter' -import { getNumberFormattedToDecimalPlace } from './SwapModalFooter' import SwapRoute from './SwapRoute' import TradePrice from './TradePrice' @@ -125,28 +124,28 @@ interface SwapDetailsInlineProps { allowedSlippage: Percent } -const formatAnalyticsEventProperties = ( - trade: InterfaceTrade, - lpFeePercent: Percent | undefined -) => ({ - token_in_symbol: trade.inputAmount.currency.symbol, - token_out_symbol: trade.outputAmount.currency.symbol, - token_in_address: trade.inputAmount.currency.isToken ? trade.inputAmount.currency.address : undefined, - token_out_address: trade.outputAmount.currency.isToken ? trade.outputAmount.currency.address : undefined, - price_impact_basis_points: lpFeePercent - ? formatPercentInBasisPointsNumber(getPriceImpactPercent(lpFeePercent, trade)) - : undefined, - estimated_network_fee_usd: trade.gasUseEstimateUSD - ? getNumberFormattedToDecimalPlace(trade.gasUseEstimateUSD, 2) - : undefined, - chain_id: - trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId - ? trade.inputAmount.currency.chainId +const formatAnalyticsEventProperties = (trade: InterfaceTrade) => { + const lpFeePercent = trade ? computeRealizedLPFeePercent(trade) : undefined + return { + token_in_symbol: trade.inputAmount.currency.symbol, + token_out_symbol: trade.outputAmount.currency.symbol, + token_in_address: trade.inputAmount.currency.isToken ? trade.inputAmount.currency.address : undefined, + token_out_address: trade.outputAmount.currency.isToken ? trade.outputAmount.currency.address : undefined, + price_impact_basis_points: lpFeePercent + ? formatPercentInBasisPointsNumber(getPriceImpactPercent(lpFeePercent, trade)) + : undefined, + estimated_network_fee_usd: trade.gasUseEstimateUSD + ? getNumberFormattedToDecimalPlace(trade.gasUseEstimateUSD, 2) : undefined, - token_in_amount: getNumberFormattedToDecimalPlace(trade.inputAmount, trade.inputAmount.currency.decimals), - token_out_amount: getNumberFormattedToDecimalPlace(trade.outputAmount, trade.outputAmount.currency.decimals), - // TODO(lynnshaoyu): Implement quote_latency_milliseconds. -}) + chain_id: + trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId + ? trade.inputAmount.currency.chainId + : undefined, + token_in_amount: getNumberFormattedToDecimalPlace(trade.inputAmount, trade.inputAmount.currency.decimals), + token_out_amount: getNumberFormattedToDecimalPlace(trade.outputAmount, trade.outputAmount.currency.decimals), + // TODO(lynnshaoyu): Implement quote_latency_milliseconds. + } +} export default function SwapDetailsDropdown({ trade, @@ -160,7 +159,6 @@ export default function SwapDetailsDropdown({ const { chainId } = useWeb3React() const [showDetails, setShowDetails] = useState(false) const [isFirstPriceFetch, setIsFirstPriceFetch] = useState(true) - const lpFeePercent = trade ? computeRealizedLPFeePercent(trade) : undefined useEffect(() => { if (isFirstPriceFetch && syncing) setIsFirstPriceFetch(false) @@ -211,7 +209,7 @@ export default function SwapDetailsDropdown({ , - decimalPlace: number -): number => parseFloat(intialNumberObject.toFixed(decimalPlace)) - -export const formatPercentInBasisPointsNumber = (percent: Percent): number => parseFloat(percent.toFixed(2)) * 100 - interface AnalyticsEventProps { trade: InterfaceTrade txHash: string | undefined