From 8cb1557f1c12a31bfaf78fcf6ad9de8e54085870 Mon Sep 17 00:00:00 2001 From: ryanml Date: Thu, 22 Jul 2021 16:13:40 -0700 Subject: [PATCH] Adding method to capture one-time Sentry exceptions (#11553) --- .../app/transaction-icon/transaction-icon.js | 11 ++++++----- ui/ducks/app/app.js | 12 ++++++++++++ ui/hooks/useTransactionDisplayData.js | 9 +++++---- ui/hooks/useTransactionDisplayData.test.js | 3 +++ ui/store/actionConstants.js | 1 + ui/store/actions.js | 14 ++++++++++++++ 6 files changed, 41 insertions(+), 9 deletions(-) diff --git a/ui/components/app/transaction-icon/transaction-icon.js b/ui/components/app/transaction-icon/transaction-icon.js index adec0de16..7aedfd7f4 100644 --- a/ui/components/app/transaction-icon/transaction-icon.js +++ b/ui/components/app/transaction-icon/transaction-icon.js @@ -1,6 +1,6 @@ import React from 'react'; +import { useDispatch } from 'react-redux'; import PropTypes from 'prop-types'; -import { captureException } from '@sentry/browser'; import Approve from '../../ui/icon/approve-icon.component'; import Interaction from '../../ui/icon/interaction-icon.component'; import Receive from '../../ui/icon/receive-icon.component'; @@ -12,6 +12,7 @@ import { TRANSACTION_GROUP_STATUSES, TRANSACTION_STATUSES, } from '../../../../shared/constants/transaction'; +import { captureSingleException } from '../../../store/actions'; const ICON_MAP = { [TRANSACTION_GROUP_CATEGORIES.APPROVAL]: Approve, @@ -38,17 +39,17 @@ const COLOR_MAP = { }; export default function TransactionIcon({ status, category }) { - const color = COLOR_MAP[status] || OK_COLOR; + const dispatch = useDispatch(); + const color = COLOR_MAP[status] || OK_COLOR; const Icon = ICON_MAP[category]; if (!Icon) { - captureException( - Error( + dispatch( + captureSingleException( `The category prop passed to TransactionIcon is not supported. The prop is: ${category}`, ), ); - return
; } diff --git a/ui/ducks/app/app.js b/ui/ducks/app/app.js index b19072d18..e3b3e275f 100644 --- a/ui/ducks/app/app.js +++ b/ui/ducks/app/app.js @@ -50,6 +50,9 @@ export default function reduceApp(state = {}, action) { openMetaMaskTabs: {}, currentWindowTab: {}, showWhatsNewPopup: true, + singleExceptions: { + testKey: null, + }, ...state, }; @@ -346,6 +349,15 @@ export default function reduceApp(state = {}, action) { showWhatsNewPopup: false, }; + case actionConstants.CAPTURE_SINGLE_EXCEPTION: + return { + ...appState, + singleExceptions: { + ...appState.singleExceptions, + [action.value]: null, + }, + }; + default: return appState; } diff --git a/ui/hooks/useTransactionDisplayData.js b/ui/hooks/useTransactionDisplayData.js index 1fad5d2c7..0677e611e 100644 --- a/ui/hooks/useTransactionDisplayData.js +++ b/ui/hooks/useTransactionDisplayData.js @@ -1,5 +1,4 @@ -import { useSelector } from 'react-redux'; -import { captureException } from '@sentry/browser'; +import { useDispatch, useSelector } from 'react-redux'; import { getKnownMethodData } from '../selectors/selectors'; import { getStatusKey, @@ -23,6 +22,7 @@ import { TRANSACTION_GROUP_CATEGORIES, TRANSACTION_STATUSES, } from '../../shared/constants/transaction'; +import { captureSingleException } from '../store/actions'; import { useI18nContext } from './useI18nContext'; import { useTokenFiatAmount } from './useTokenFiatAmount'; import { useUserPreferencedCurrency } from './useUserPreferencedCurrency'; @@ -58,6 +58,7 @@ import { useCurrentAsset } from './useCurrentAsset'; export function useTransactionDisplayData(transactionGroup) { // To determine which primary currency to display for swaps transactions we need to be aware // of which asset, if any, we are viewing at present + const dispatch = useDispatch(); const currentAsset = useCurrentAsset(); const knownTokens = useSelector(getTokens); const t = useI18nContext(); @@ -222,8 +223,8 @@ export function useTransactionDisplayData(transactionGroup) { title = t('send'); subtitle = t('toAddress', [shortenAddress(recipientAddress)]); } else { - captureException( - Error( + dispatch( + captureSingleException( `useTransactionDisplayData does not recognize transaction type. Type received is: ${type}`, ), ); diff --git a/ui/hooks/useTransactionDisplayData.test.js b/ui/hooks/useTransactionDisplayData.test.js index 3b36c255b..fad81f0e0 100644 --- a/ui/hooks/useTransactionDisplayData.test.js +++ b/ui/hooks/useTransactionDisplayData.test.js @@ -132,6 +132,8 @@ const renderHookWithRouter = (cb, tokenAddress) => { }; describe('useTransactionDisplayData', () => { + const dispatch = sinon.spy(); + beforeAll(() => { useSelector = sinon.stub(reactRedux, 'useSelector'); useTokenFiatAmount = sinon.stub( @@ -169,6 +171,7 @@ describe('useTransactionDisplayData', () => { } return null; }); + sinon.stub(reactRedux, 'useDispatch').returns(dispatch); }); afterAll(() => { diff --git a/ui/store/actionConstants.js b/ui/store/actionConstants.js index 807865ddd..8046ea05d 100644 --- a/ui/store/actionConstants.js +++ b/ui/store/actionConstants.js @@ -30,6 +30,7 @@ export const LOCK_METAMASK = 'LOCK_METAMASK'; // error handling export const DISPLAY_WARNING = 'DISPLAY_WARNING'; export const HIDE_WARNING = 'HIDE_WARNING'; +export const CAPTURE_SINGLE_EXCEPTION = 'CAPTURE_SINGLE_EXCEPTION'; // accounts screen export const SHOW_ACCOUNT_DETAIL = 'SHOW_ACCOUNT_DETAIL'; export const SHOW_ACCOUNTS_PAGE = 'SHOW_ACCOUNTS_PAGE'; diff --git a/ui/store/actions.js b/ui/store/actions.js index 312324990..84175fd6a 100644 --- a/ui/store/actions.js +++ b/ui/store/actions.js @@ -1,5 +1,6 @@ import pify from 'pify'; import log from 'loglevel'; +import { captureException } from '@sentry/browser'; import { capitalize, isEqual } from 'lodash'; import getBuyEthUrl from '../../app/scripts/lib/buy-eth-url'; import { @@ -2715,6 +2716,19 @@ export function setLedgerLivePreference(value) { }; } +export function captureSingleException(error) { + return async (dispatch, getState) => { + const { singleExceptions } = getState().appState; + if (!(error in singleExceptions)) { + dispatch({ + type: actionConstants.CAPTURE_SINGLE_EXCEPTION, + value: error, + }); + captureException(Error(error)); + } + }; +} + // Wrappers around promisifedBackground /** * The "actions" below are not actions nor action creators. They cannot use