From bcbd74db2e508390c0ed40dea8b2b9f6ee1fcd1f Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:31:15 -0500 Subject: [PATCH 1/5] feat add banner for if tx is taking longer than 3 hours --- app/_locales/en/messages.json | 9 +++ .../transaction-details.tsx | 56 ++++++++++++++++++- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 07dc59d6c895..390bcd5d8dc5 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -931,6 +931,15 @@ "bridgeTxDetailsBridgeType": { "message": "Bridge type" }, + "bridgeTxDetailsDelayedDescription": { + "message": "Reach out to " + }, + "bridgeTxDetailsDelayedDescriptionSupport": { + "message": "MetaMask Support." + }, + "bridgeTxDetailsDelayedTitle": { + "message": "Has it been longer than 3 hours?" + }, "bridgeTxDetailsGasLimit": { "message": "Gas limit (units)" }, diff --git a/ui/pages/bridge/transaction-details/transaction-details.tsx b/ui/pages/bridge/transaction-details/transaction-details.tsx index 20ee8fb48754..6da73fa212a5 100644 --- a/ui/pages/bridge/transaction-details/transaction-details.tsx +++ b/ui/pages/bridge/transaction-details/transaction-details.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useContext } from 'react'; import { useSelector } from 'react-redux'; import { useHistory, useParams } from 'react-router-dom'; import { NetworkConfiguration } from '@metamask/network-controller'; @@ -6,9 +6,12 @@ import { TransactionMeta } from '@metamask/transaction-controller'; import { AvatarNetwork, AvatarNetworkSize, + BannerAlert, + BannerAlertSeverity, Box, ButtonIcon, ButtonIconSize, + ButtonLink, IconName, Text, } from '../../../components/component-library'; @@ -21,7 +24,10 @@ import { MetaMaskReduxState } from '../../../store/store'; import { hexToDecimal } from '../../../../shared/modules/conversion.utils'; import UserPreferencedCurrencyDisplay from '../../../components/app/user-preferenced-currency-display/user-preferenced-currency-display.component'; import { EtherDenomination } from '../../../../shared/constants/common'; -import { PRIMARY } from '../../../helpers/constants/common'; +import { + PRIMARY, + SUPPORT_REQUEST_LINK, +} from '../../../helpers/constants/common'; import CurrencyDisplay from '../../../components/ui/currency-display/currency-display.component'; import { BridgeHistoryItem, @@ -39,6 +45,12 @@ import { ConfirmInfoRowDivider as Divider } from '../../../components/app/confir import { useI18nContext } from '../../../hooks/useI18nContext'; import { CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP } from '../../../../shared/constants/network'; import { selectedAddressTxListSelector } from '../../../selectors'; +import { + MetaMetricsContextProp, + MetaMetricsEventCategory, + MetaMetricsEventName, +} from '../../../../shared/constants/metametrics'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; import TransactionDetailRow from './transaction-detail-row'; import BridgeExplorerLinks from './bridge-explorer-links'; import BridgeStepList from './bridge-step-list'; @@ -88,6 +100,7 @@ const StatusToColorMap: Record = { const CrossChainSwapTxDetails = () => { const t = useI18nContext(); + const trackEvent = useContext(MetaMetricsContext); const rootState = useSelector((state) => state); const history = useHistory(); const { srcTxMetaId } = useParams<{ srcTxMetaId: string }>(); @@ -142,6 +155,8 @@ const CrossChainSwapTxDetails = () => { const bridgeAmount = getBridgeAmount({ bridgeHistoryItem }); + const isDelayed = true; + return (
@@ -164,6 +179,43 @@ const CrossChainSwapTxDetails = () => { flexDirection={FlexDirection.Column} gap={4} > + {isDelayed && ( + + + {t('bridgeTxDetailsDelayedDescription')}{' '} + { + global.platform.openTab({ + url: + SUPPORT_REQUEST_LINK || + 'https://support.metamask.io/', + }); + trackEvent( + { + category: MetaMetricsEventCategory.Home, + event: MetaMetricsEventName.SupportLinkClicked, + properties: { + url: SUPPORT_REQUEST_LINK, + location: 'Bridge Tx Details', + }, + }, + { + contextPropsIntoEventProperties: [ + MetaMetricsContextProp.PageTitle, + ], + }, + ); + }} + > + {t('bridgeTxDetailsDelayedDescriptionSupport')} + + + + )} + {status !== StatusTypes.COMPLETE && (bridgeHistoryItem || srcChainTxMeta) && ( Date: Mon, 2 Dec 2024 15:38:21 -0500 Subject: [PATCH 2/5] chore: properly calculate if a bridge is delayed --- shared/types/bridge-status.ts | 2 +- .../transaction-details/transaction-details.tsx | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/shared/types/bridge-status.ts b/shared/types/bridge-status.ts index 845a3c7cec65..568b0fe420c5 100644 --- a/shared/types/bridge-status.ts +++ b/shared/types/bridge-status.ts @@ -119,7 +119,7 @@ export type BridgeHistoryItem = { txMetaId: string; // Need this to handle STX that might not have a txHash immediately quote: Quote; status: StatusResponse; - startTime?: number; + startTime?: number; // timestamp in ms estimatedProcessingTimeInSeconds: number; slippagePercentage: number; completionTime?: number; diff --git a/ui/pages/bridge/transaction-details/transaction-details.tsx b/ui/pages/bridge/transaction-details/transaction-details.tsx index 6da73fa212a5..b6c4ec774edf 100644 --- a/ui/pages/bridge/transaction-details/transaction-details.tsx +++ b/ui/pages/bridge/transaction-details/transaction-details.tsx @@ -91,6 +91,19 @@ const getBridgeAmount = ({ return undefined; }; +const getIsDelayed = ( + status: StatusTypes, + bridgeHistoryItem: BridgeHistoryItem, +) => { + return ( + status === StatusTypes.PENDING && + bridgeHistoryItem.startTime && + Date.now() > + bridgeHistoryItem.startTime + + bridgeHistoryItem.estimatedProcessingTimeInSeconds * 1000 + ); +}; + const StatusToColorMap: Record = { [StatusTypes.PENDING]: TextColor.warningDefault, [StatusTypes.COMPLETE]: TextColor.successDefault, @@ -154,8 +167,7 @@ const CrossChainSwapTxDetails = () => { : undefined; const bridgeAmount = getBridgeAmount({ bridgeHistoryItem }); - - const isDelayed = true; + const isDelayed = getIsDelayed(status, bridgeHistoryItem); return (
From b7f0f3f65e24c4fc74621ad1b7c48d1e5c0d6ab1 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:48:06 -0500 Subject: [PATCH 3/5] chore: improve styling and use more semantic ButtonLink --- app/_locales/en/messages.json | 4 ++-- .../transaction-details/transaction-details.tsx | 16 +++++++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 390bcd5d8dc5..04946ea75818 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -932,10 +932,10 @@ "message": "Bridge type" }, "bridgeTxDetailsDelayedDescription": { - "message": "Reach out to " + "message": "Reach out to" }, "bridgeTxDetailsDelayedDescriptionSupport": { - "message": "MetaMask Support." + "message": "MetaMask Support" }, "bridgeTxDetailsDelayedTitle": { "message": "Has it been longer than 3 hours?" diff --git a/ui/pages/bridge/transaction-details/transaction-details.tsx b/ui/pages/bridge/transaction-details/transaction-details.tsx index b6c4ec774edf..888f84b4ad41 100644 --- a/ui/pages/bridge/transaction-details/transaction-details.tsx +++ b/ui/pages/bridge/transaction-details/transaction-details.tsx @@ -93,11 +93,11 @@ const getBridgeAmount = ({ const getIsDelayed = ( status: StatusTypes, - bridgeHistoryItem: BridgeHistoryItem, + bridgeHistoryItem?: BridgeHistoryItem, ) => { return ( status === StatusTypes.PENDING && - bridgeHistoryItem.startTime && + bridgeHistoryItem?.startTime && Date.now() > bridgeHistoryItem.startTime + bridgeHistoryItem.estimatedProcessingTimeInSeconds * 1000 @@ -196,15 +196,12 @@ const CrossChainSwapTxDetails = () => { title={t('bridgeTxDetailsDelayedTitle')} severity={BannerAlertSeverity.Warning} > - - {t('bridgeTxDetailsDelayedDescription')}{' '} + + {t('bridgeTxDetailsDelayedDescription')}  { - global.platform.openTab({ - url: - SUPPORT_REQUEST_LINK || - 'https://support.metamask.io/', - }); trackEvent( { category: MetaMetricsEventCategory.Home, @@ -224,6 +221,7 @@ const CrossChainSwapTxDetails = () => { > {t('bridgeTxDetailsDelayedDescriptionSupport')} + . )} From e9007c15f7313433b9376bf7758d771fd9ae1d8e Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:52:29 -0500 Subject: [PATCH 4/5] chore: add tests --- .../transaction-details.test.tsx | 50 +++++++++++++++++++ .../transaction-details.tsx | 12 ++--- 2 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 ui/pages/bridge/transaction-details/transaction-details.test.tsx diff --git a/ui/pages/bridge/transaction-details/transaction-details.test.tsx b/ui/pages/bridge/transaction-details/transaction-details.test.tsx new file mode 100644 index 000000000000..3807a05905cb --- /dev/null +++ b/ui/pages/bridge/transaction-details/transaction-details.test.tsx @@ -0,0 +1,50 @@ +import { + BridgeHistoryItem, + StatusTypes, +} from '../../../../shared/types/bridge-status'; +import { getIsDelayed } from './transaction-details'; + +describe('transaction-details', () => { + describe('getIsDelayed', () => { + it('returns false when status is not PENDING', () => { + const result = getIsDelayed(StatusTypes.COMPLETE, { + startTime: Date.now(), + estimatedProcessingTimeInSeconds: 60, + } as BridgeHistoryItem); + expect(result).toBe(false); + }); + + it('returns false when bridgeHistoryItem is undefined', () => { + const result = getIsDelayed(StatusTypes.PENDING, undefined); + expect(result).toBe(false); + }); + + it('returns false when startTime is undefined', () => { + const result = getIsDelayed(StatusTypes.PENDING, { + startTime: undefined, + estimatedProcessingTimeInSeconds: 60, + } as BridgeHistoryItem); + expect(result).toBe(false); + }); + + it('returns false when current time is less than estimated completion time', () => { + const result = getIsDelayed(StatusTypes.PENDING, { + startTime: Date.now() - 1000, + estimatedProcessingTimeInSeconds: 60, + } as BridgeHistoryItem); + + expect(result).toBe(false); + }); + + it('returns true when current time exceeds estimated completion time', () => { + const startTime = Date.now() - 61 * 1000; + + const result = getIsDelayed(StatusTypes.PENDING, { + startTime, + estimatedProcessingTimeInSeconds: 60, + } as BridgeHistoryItem); + + expect(result).toBe(true); + }); + }); +}); diff --git a/ui/pages/bridge/transaction-details/transaction-details.tsx b/ui/pages/bridge/transaction-details/transaction-details.tsx index 888f84b4ad41..069f52bb37d7 100644 --- a/ui/pages/bridge/transaction-details/transaction-details.tsx +++ b/ui/pages/bridge/transaction-details/transaction-details.tsx @@ -91,16 +91,16 @@ const getBridgeAmount = ({ return undefined; }; -const getIsDelayed = ( +export const getIsDelayed = ( status: StatusTypes, bridgeHistoryItem?: BridgeHistoryItem, ) => { - return ( + return Boolean( status === StatusTypes.PENDING && - bridgeHistoryItem?.startTime && - Date.now() > - bridgeHistoryItem.startTime + - bridgeHistoryItem.estimatedProcessingTimeInSeconds * 1000 + bridgeHistoryItem?.startTime && + Date.now() > + bridgeHistoryItem.startTime + + bridgeHistoryItem.estimatedProcessingTimeInSeconds * 1000, ); }; From 7b1624424f50340818b1197a688966083ae71347 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:54:40 -0500 Subject: [PATCH 5/5] chore: add jsdoc --- ui/pages/bridge/transaction-details/transaction-details.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ui/pages/bridge/transaction-details/transaction-details.tsx b/ui/pages/bridge/transaction-details/transaction-details.tsx index 069f52bb37d7..fe81e8e3b3dd 100644 --- a/ui/pages/bridge/transaction-details/transaction-details.tsx +++ b/ui/pages/bridge/transaction-details/transaction-details.tsx @@ -91,6 +91,11 @@ const getBridgeAmount = ({ return undefined; }; +/** + * @param status - The status of the bridge history item + * @param bridgeHistoryItem - The bridge history item + * @returns Whether the bridge history item is delayed + */ export const getIsDelayed = ( status: StatusTypes, bridgeHistoryItem?: BridgeHistoryItem,