Degrade gracefully when gas API is down (#13865)
When the gas API is down, the logic we use will no longer compute all of the data that the gas API returns in order to reduce the burden on Infura. Specifically, only estimated fees for different priority levels, as well as the latest base fee, will be available; all other data points, such as the latest and historical priority fee range and network stability, will be missing. This commit updates the frontend logic to account for this lack of data by merely hiding the relevant pieces of the UI that would otherwise be shown.feature/default_network_editable
parent
8e0f71a008
commit
f8f4397339
@ -1,42 +1,95 @@ |
||||
import React from 'react'; |
||||
import React, { useContext } from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
|
||||
import { isNullish } from '../../../../helpers/utils/util'; |
||||
import { formatGasFeeOrFeeRange } from '../../../../helpers/utils/gas'; |
||||
import { I18nContext } from '../../../../contexts/i18n'; |
||||
import Box from '../../../ui/box'; |
||||
import I18nValue from '../../../ui/i18n-value'; |
||||
import LoadingHeartBeat from '../../../ui/loading-heartbeat'; |
||||
|
||||
const AdvancedGasFeeInputSubtext = ({ latest, historical, feeTrend }) => { |
||||
function determineTrendInfo(trend, t) { |
||||
switch (trend) { |
||||
case 'up': |
||||
return { |
||||
className: 'advanced-gas-fee-input-subtext__up', |
||||
imageSrc: '/images/up-arrow.svg', |
||||
imageAlt: t('upArrow'), |
||||
}; |
||||
case 'down': |
||||
return { |
||||
className: 'advanced-gas-fee-input-subtext__down', |
||||
imageSrc: '/images/down-arrow.svg', |
||||
imageAlt: t('downArrow'), |
||||
}; |
||||
case 'level': |
||||
return { |
||||
className: 'advanced-gas-fee-input-subtext__level', |
||||
imageSrc: '/images/level-arrow.svg', |
||||
imageAlt: t('levelArrow'), |
||||
}; |
||||
default: |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
const AdvancedGasFeeInputSubtext = ({ latest, historical, trend }) => { |
||||
const t = useContext(I18nContext); |
||||
const trendInfo = determineTrendInfo(trend, t); |
||||
return ( |
||||
<Box className="advanced-gas-fee-input-subtext"> |
||||
<Box display="flex" alignItems="center"> |
||||
<span className="advanced-gas-fee-input-subtext__label"> |
||||
<I18nValue messageKey="currentTitle" /> |
||||
</span> |
||||
<span className="advanced-gas-fee-input-subtext__value"> |
||||
<LoadingHeartBeat /> |
||||
{latest} |
||||
</span> |
||||
<span className={`advanced-gas-fee-input-subtext__${feeTrend}`}> |
||||
<img src={`./images/${feeTrend}-arrow.svg`} alt="feeTrend-arrow" /> |
||||
</span> |
||||
</Box> |
||||
<Box> |
||||
<span className="advanced-gas-fee-input-subtext__label"> |
||||
<I18nValue messageKey="twelveHrTitle" /> |
||||
</span> |
||||
<span className="advanced-gas-fee-input-subtext__value"> |
||||
<LoadingHeartBeat /> |
||||
{historical} |
||||
</span> |
||||
</Box> |
||||
<Box |
||||
display="flex" |
||||
alignItems="center" |
||||
gap={4} |
||||
className="advanced-gas-fee-input-subtext" |
||||
> |
||||
{isNullish(latest) ? null : ( |
||||
<Box display="flex" alignItems="center" data-testid="latest"> |
||||
<span className="advanced-gas-fee-input-subtext__label"> |
||||
{t('currentTitle')} |
||||
</span> |
||||
<span className="advanced-gas-fee-input-subtext__value"> |
||||
<LoadingHeartBeat /> |
||||
{formatGasFeeOrFeeRange(latest)} |
||||
</span> |
||||
{trendInfo === null ? null : ( |
||||
<span className={trendInfo.className}> |
||||
<img |
||||
src={trendInfo.imageSrc} |
||||
alt={trendInfo.imageAlt} |
||||
data-testid="fee-arrow" |
||||
/> |
||||
</span> |
||||
)} |
||||
</Box> |
||||
)} |
||||
{isNullish(historical) ? null : ( |
||||
<Box> |
||||
<span |
||||
className="advanced-gas-fee-input-subtext__label" |
||||
data-testid="historical" |
||||
> |
||||
{t('twelveHrTitle')} |
||||
</span> |
||||
<span className="advanced-gas-fee-input-subtext__value"> |
||||
<LoadingHeartBeat /> |
||||
{formatGasFeeOrFeeRange(historical)} |
||||
</span> |
||||
</Box> |
||||
)} |
||||
</Box> |
||||
); |
||||
}; |
||||
|
||||
AdvancedGasFeeInputSubtext.propTypes = { |
||||
latest: PropTypes.string, |
||||
historical: PropTypes.string, |
||||
feeTrend: PropTypes.string.isRequired, |
||||
latest: PropTypes.oneOfType([ |
||||
PropTypes.string, |
||||
PropTypes.arrayOf(PropTypes.string), |
||||
]), |
||||
historical: PropTypes.oneOfType([ |
||||
PropTypes.string, |
||||
PropTypes.arrayOf(PropTypes.string), |
||||
]), |
||||
trend: PropTypes.oneOf(['up', 'down', 'level']), |
||||
}; |
||||
|
||||
export default AdvancedGasFeeInputSubtext; |
||||
|
@ -1,55 +1,144 @@ |
||||
import React from 'react'; |
||||
import { screen } from '@testing-library/react'; |
||||
|
||||
import { GAS_ESTIMATE_TYPES } from '../../../../../shared/constants/gas'; |
||||
import mockEstimates from '../../../../../test/data/mock-estimates.json'; |
||||
import mockState from '../../../../../test/data/mock-state.json'; |
||||
import { renderWithProvider } from '../../../../../test/lib/render-helpers'; |
||||
import { renderWithProvider, screen } from '../../../../../test/jest'; |
||||
import configureStore from '../../../../store/store'; |
||||
import AdvancedGasFeeInputSubtext from './advanced-gas-fee-input-subtext'; |
||||
|
||||
jest.mock('../../../../store/actions', () => ({ |
||||
disconnectGasFeeEstimatePoller: jest.fn(), |
||||
getGasFeeEstimatesAndStartPolling: jest |
||||
.fn() |
||||
.mockImplementation(() => Promise.resolve()), |
||||
getGasFeeEstimatesAndStartPolling: jest.fn().mockResolvedValue(null), |
||||
addPollingTokenToAppState: jest.fn(), |
||||
removePollingTokenFromAppState: jest.fn(), |
||||
})); |
||||
|
||||
const render = () => { |
||||
const store = configureStore({ |
||||
metamask: { |
||||
...mockState.metamask, |
||||
accounts: { |
||||
[mockState.metamask.selectedAddress]: { |
||||
address: mockState.metamask.selectedAddress, |
||||
balance: '0x1F4', |
||||
}, |
||||
}, |
||||
advancedGasFee: { priorityFee: 100 }, |
||||
featureFlags: { advancedInlineGas: true }, |
||||
gasFeeEstimates: |
||||
mockEstimates[GAS_ESTIMATE_TYPES.FEE_MARKET].gasFeeEstimates, |
||||
}, |
||||
}); |
||||
|
||||
return renderWithProvider( |
||||
<AdvancedGasFeeInputSubtext |
||||
latest="Latest Value" |
||||
historical="Historical value" |
||||
feeTrend="up" |
||||
/>, |
||||
store, |
||||
); |
||||
const renderComponent = ({ props = {}, state = {} } = {}) => { |
||||
const store = configureStore(state); |
||||
return renderWithProvider(<AdvancedGasFeeInputSubtext {...props} />, store); |
||||
}; |
||||
|
||||
describe('AdvancedGasFeeInputSubtext', () => { |
||||
it('should renders latest and historical values passed', () => { |
||||
render(); |
||||
describe('when "latest" is non-nullish', () => { |
||||
it('should render the latest fee if given a fee', () => { |
||||
renderComponent({ |
||||
props: { |
||||
latest: '123.12345', |
||||
}, |
||||
}); |
||||
|
||||
expect(screen.getByText('123.12 GWEI')).toBeInTheDocument(); |
||||
}); |
||||
|
||||
it('should render the latest fee range if given a fee range', () => { |
||||
renderComponent({ |
||||
props: { |
||||
latest: ['123.456', '456.789'], |
||||
}, |
||||
}); |
||||
|
||||
expect(screen.getByText('123.46 - 456.79 GWEI')).toBeInTheDocument(); |
||||
}); |
||||
|
||||
it('should render a fee trend arrow image if given "up" as the trend', () => { |
||||
renderComponent({ |
||||
props: { |
||||
latest: '123.12345', |
||||
trend: 'up', |
||||
}, |
||||
}); |
||||
|
||||
expect(screen.getByAltText('up arrow')).toBeInTheDocument(); |
||||
}); |
||||
|
||||
it('should render a fee trend arrow image if given "down" as the trend', () => { |
||||
renderComponent({ |
||||
props: { |
||||
latest: '123.12345', |
||||
trend: 'down', |
||||
}, |
||||
}); |
||||
|
||||
expect(screen.getByAltText('down arrow')).toBeInTheDocument(); |
||||
}); |
||||
|
||||
it('should render a fee trend arrow image if given "level" as the trend', () => { |
||||
renderComponent({ |
||||
props: { |
||||
latest: '123.12345', |
||||
trend: 'level', |
||||
}, |
||||
}); |
||||
|
||||
expect(screen.getByAltText('level arrow')).toBeInTheDocument(); |
||||
}); |
||||
|
||||
it('should not render a fee trend arrow image if given an invalid trend', () => { |
||||
// Suppress warning from PropTypes, which we expect
|
||||
jest.spyOn(console, 'error').mockImplementation(); |
||||
|
||||
renderComponent({ |
||||
props: { |
||||
latest: '123.12345', |
||||
trend: 'whatever', |
||||
}, |
||||
}); |
||||
|
||||
expect(screen.queryByTestId('fee-arrow')).not.toBeInTheDocument(); |
||||
}); |
||||
|
||||
it('should not render a fee trend arrow image if given a nullish trend', () => { |
||||
renderComponent({ |
||||
props: { |
||||
latest: '123.12345', |
||||
trend: null, |
||||
}, |
||||
}); |
||||
|
||||
expect(screen.queryByTestId('fee-arrow')).not.toBeInTheDocument(); |
||||
}); |
||||
}); |
||||
|
||||
describe('when "latest" is nullish', () => { |
||||
it('should not render the container for the latest fee', () => { |
||||
renderComponent({ |
||||
props: { |
||||
latest: null, |
||||
}, |
||||
}); |
||||
|
||||
expect(screen.queryByTestId('latest')).not.toBeInTheDocument(); |
||||
}); |
||||
}); |
||||
|
||||
describe('when "historical" is not nullish', () => { |
||||
it('should render the historical fee if given a fee', () => { |
||||
renderComponent({ |
||||
props: { |
||||
historical: '123.12345', |
||||
}, |
||||
}); |
||||
|
||||
expect(screen.getByText('123.12 GWEI')).toBeInTheDocument(); |
||||
}); |
||||
|
||||
it('should render the historical fee range if given a fee range', () => { |
||||
renderComponent({ |
||||
props: { |
||||
historical: ['123.456', '456.789'], |
||||
}, |
||||
}); |
||||
|
||||
expect(screen.getByText('123.46 - 456.79 GWEI')).toBeInTheDocument(); |
||||
}); |
||||
}); |
||||
|
||||
describe('when "historical" is nullish', () => { |
||||
it('should not render the container for the historical fee', () => { |
||||
renderComponent({ |
||||
props: { |
||||
historical: null, |
||||
}, |
||||
}); |
||||
|
||||
expect(screen.queryByText('Latest Value')).toBeInTheDocument(); |
||||
expect(screen.queryByText('Historical value')).toBeInTheDocument(); |
||||
expect(screen.queryByAltText('feeTrend-arrow')).toBeInTheDocument(); |
||||
expect(screen.queryByTestId('historical')).not.toBeInTheDocument(); |
||||
}); |
||||
}); |
||||
}); |
||||
|
@ -1,13 +0,0 @@ |
||||
import { uniq } from 'lodash'; |
||||
|
||||
import { roundToDecimalPlacesRemovingExtraZeroes } from '../../../../helpers/utils/util'; |
||||
|
||||
export const renderFeeRange = (feeRange) => { |
||||
if (feeRange) { |
||||
const formattedRange = uniq( |
||||
feeRange.map((fee) => roundToDecimalPlacesRemovingExtraZeroes(fee, 2)), |
||||
).join(' - '); |
||||
return `${formattedRange} GWEI`; |
||||
} |
||||
return null; |
||||
}; |
@ -1 +0,0 @@ |
||||
export { default } from './latest-priority-fee-field'; |
@ -1,35 +0,0 @@ |
||||
import React, { useMemo } from 'react'; |
||||
import { uniq } from 'lodash'; |
||||
import { roundToDecimalPlacesRemovingExtraZeroes } from '../../../../../helpers/utils/util'; |
||||
import { useGasFeeContext } from '../../../../../contexts/gasFee'; |
||||
import I18nValue from '../../../../ui/i18n-value'; |
||||
import { PriorityFeeTooltip } from '../tooltips'; |
||||
|
||||
export default function LatestPriorityFeeField() { |
||||
const { gasFeeEstimates } = useGasFeeContext(); |
||||
|
||||
const priorityFeeRange = useMemo(() => { |
||||
const { latestPriorityFeeRange } = gasFeeEstimates; |
||||
if (latestPriorityFeeRange) { |
||||
const formattedRange = uniq([ |
||||
roundToDecimalPlacesRemovingExtraZeroes(latestPriorityFeeRange[0], 1), |
||||
roundToDecimalPlacesRemovingExtraZeroes(latestPriorityFeeRange[1], 0), |
||||
]).join(' - '); |
||||
return `${formattedRange} GWEI`; |
||||
} |
||||
return null; |
||||
}, [gasFeeEstimates]); |
||||
|
||||
return ( |
||||
<div className="network-statistics__info__field latest-priority-fee-field"> |
||||
<PriorityFeeTooltip> |
||||
<span className="network-statistics__info__field-data"> |
||||
{priorityFeeRange} |
||||
</span> |
||||
<span className="network-statistics__info__field-label"> |
||||
<I18nValue messageKey="priorityFee" /> |
||||
</span> |
||||
</PriorityFeeTooltip> |
||||
</div> |
||||
); |
||||
} |
@ -1,31 +0,0 @@ |
||||
import React from 'react'; |
||||
|
||||
import { renderWithProvider } from '../../../../../../test/jest'; |
||||
import { GasFeeContext } from '../../../../../contexts/gasFee'; |
||||
import configureStore from '../../../../../store/store'; |
||||
|
||||
import LatestPriorityFeeField from './latest-priority-fee-field'; |
||||
|
||||
const renderComponent = (gasFeeEstimates) => { |
||||
const store = configureStore({}); |
||||
return renderWithProvider( |
||||
<GasFeeContext.Provider value={{ gasFeeEstimates }}> |
||||
<LatestPriorityFeeField /> |
||||
</GasFeeContext.Provider>, |
||||
store, |
||||
); |
||||
}; |
||||
|
||||
describe('LatestPriorityFeeField', () => { |
||||
it('should render a version of latest priority fee range pulled from context, lower range rounded to 1 decimal place', () => { |
||||
const { getByText } = renderComponent({ |
||||
latestPriorityFeeRange: ['1.000001668', '2.5634234'], |
||||
}); |
||||
expect(getByText('1 - 3 GWEI')).toBeInTheDocument(); |
||||
}); |
||||
|
||||
it('should render nothing if gasFeeEstimates are empty', () => { |
||||
const { queryByText } = renderComponent({}); |
||||
expect(queryByText('GWEI')).not.toBeInTheDocument(); |
||||
}); |
||||
}); |
Loading…
Reference in new issue