Restore timing function (#8774)
* restore and enhance the time est feature background: we had a feature for showing a time estimate on pending txs that was accidently removed during the redesign implementation. This PR restores that feature and also enhances it: 1. Displays the time estimate on all views instead of just fullscreen 2. Uses Intl.RelativeTimeFormat to format the time 3. Adds a way to toggle the feature flag. 4. Uses a hook to calculate the time remaining instead of a component * Update app/_locales/en/messages.json Co-authored-by: Mark Stacey <markjstacey@gmail.com> * do not display on test nets Co-authored-by: Mark Stacey <markjstacey@gmail.com>feature/default_network_editable
parent
5aabe2ac75
commit
2f50e9fd72
@ -1 +0,0 @@ |
|||||||
export { default } from './transaction-time-remaining.container' |
|
@ -1,52 +0,0 @@ |
|||||||
import React, { PureComponent } from 'react' |
|
||||||
import PropTypes from 'prop-types' |
|
||||||
import { calcTransactionTimeRemaining } from './transaction-time-remaining.util' |
|
||||||
|
|
||||||
export default class TransactionTimeRemaining extends PureComponent { |
|
||||||
static propTypes = { |
|
||||||
className: PropTypes.string, |
|
||||||
initialTimeEstimate: PropTypes.number, |
|
||||||
submittedTime: PropTypes.number, |
|
||||||
} |
|
||||||
|
|
||||||
constructor (props) { |
|
||||||
super(props) |
|
||||||
const { initialTimeEstimate, submittedTime } = props |
|
||||||
this.state = { |
|
||||||
timeRemaining: calcTransactionTimeRemaining(initialTimeEstimate, submittedTime), |
|
||||||
} |
|
||||||
this.interval = setInterval( |
|
||||||
() => this.setState({ timeRemaining: calcTransactionTimeRemaining(initialTimeEstimate, submittedTime) }), |
|
||||||
1000 |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
componentDidUpdate (prevProps) { |
|
||||||
const { initialTimeEstimate, submittedTime } = this.props |
|
||||||
if (initialTimeEstimate !== prevProps.initialTimeEstimate) { |
|
||||||
clearInterval(this.interval) |
|
||||||
const calcedTimeRemaining = calcTransactionTimeRemaining(initialTimeEstimate, submittedTime) |
|
||||||
this.setState({ timeRemaining: calcedTimeRemaining }) |
|
||||||
this.interval = setInterval( |
|
||||||
() => this.setState({ timeRemaining: calcTransactionTimeRemaining(initialTimeEstimate, submittedTime) }), |
|
||||||
1000 |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
componentWillUnmount () { |
|
||||||
clearInterval(this.interval) |
|
||||||
} |
|
||||||
|
|
||||||
render () { |
|
||||||
const { className } = this.props |
|
||||||
const { timeRemaining } = this.state |
|
||||||
|
|
||||||
return ( |
|
||||||
<div className={className}> |
|
||||||
{ timeRemaining } |
|
||||||
</div> |
|
||||||
|
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
@ -1,33 +0,0 @@ |
|||||||
import { connect } from 'react-redux' |
|
||||||
import TransactionTimeRemaining from './transaction-time-remaining.component' |
|
||||||
import { |
|
||||||
getEstimatedGasPrices, |
|
||||||
getEstimatedGasTimes, |
|
||||||
} from '../../../selectors' |
|
||||||
import { getRawTimeEstimateData } from '../../../helpers/utils/gas-time-estimates.util' |
|
||||||
import { hexWEIToDecGWEI } from '../../../helpers/utils/conversions.util' |
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => { |
|
||||||
const { transaction } = ownProps |
|
||||||
const { gasPrice: currentGasPrice } = transaction.txParams |
|
||||||
const customGasPrice = calcCustomGasPrice(currentGasPrice) |
|
||||||
const gasPrices = getEstimatedGasPrices(state) |
|
||||||
const estimatedTimes = getEstimatedGasTimes(state) |
|
||||||
|
|
||||||
const { |
|
||||||
newTimeEstimate: initialTimeEstimate, |
|
||||||
} = getRawTimeEstimateData(customGasPrice, gasPrices, estimatedTimes) |
|
||||||
|
|
||||||
const submittedTime = transaction.submittedTime |
|
||||||
|
|
||||||
return { |
|
||||||
initialTimeEstimate, |
|
||||||
submittedTime, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
export default connect(mapStateToProps)(TransactionTimeRemaining) |
|
||||||
|
|
||||||
function calcCustomGasPrice (customGasPriceInHex) { |
|
||||||
return Number(hexWEIToDecGWEI(customGasPriceInHex)) |
|
||||||
} |
|
@ -1,13 +0,0 @@ |
|||||||
import { formatTimeEstimate } from '../../../helpers/utils/gas-time-estimates.util' |
|
||||||
|
|
||||||
export function calcTransactionTimeRemaining (initialTimeEstimate, submittedTime) { |
|
||||||
const currentTime = (new Date()).getTime() |
|
||||||
const timeElapsedSinceSubmission = (currentTime - submittedTime) / 1000 |
|
||||||
const timeRemainingOnEstimate = initialTimeEstimate - timeElapsedSinceSubmission |
|
||||||
|
|
||||||
const renderingTimeRemainingEstimate = timeRemainingOnEstimate < 30 |
|
||||||
? '< 30 s' |
|
||||||
: formatTimeEstimate(timeRemainingOnEstimate) |
|
||||||
|
|
||||||
return renderingTimeRemainingEstimate |
|
||||||
} |
|
@ -0,0 +1,18 @@ |
|||||||
|
import React from 'react' |
||||||
|
import classnames from 'classnames' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
|
||||||
|
export default function IconWithLabel ({ icon, label, className }) { |
||||||
|
return ( |
||||||
|
<div className={classnames('icon-with-label', className)}> |
||||||
|
{icon} |
||||||
|
{label && <span className="icon-with-label__label">{label}</span>} |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
IconWithLabel.propTypes = { |
||||||
|
icon: PropTypes.node.isRequired, |
||||||
|
className: PropTypes.string, |
||||||
|
label: PropTypes.string, |
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
export { default } from './icon-with-label' |
@ -0,0 +1,10 @@ |
|||||||
|
.icon-with-label { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
|
||||||
|
&__label { |
||||||
|
font-size: 10px; |
||||||
|
margin-left: 4px; |
||||||
|
color: $Grey-500; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,98 @@ |
|||||||
|
import { getEstimatedGasPrices, getEstimatedGasTimes, getFeatureFlags, getIsMainnet } from '../selectors' |
||||||
|
import { hexWEIToDecGWEI } from '../helpers/utils/conversions.util' |
||||||
|
import { useSelector } from 'react-redux' |
||||||
|
import { useRef, useEffect, useState, useMemo } from 'react' |
||||||
|
import { isEqual } from 'lodash' |
||||||
|
import { getRawTimeEstimateData } from '../helpers/utils/gas-time-estimates.util' |
||||||
|
import { getCurrentLocale } from '../ducks/metamask/metamask' |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Calculate the number of minutes remaining until the transaction completes. |
||||||
|
* @param {number} initialTimeEstimate - timestamp for the projected completion time |
||||||
|
* @param {number} submittedTime - timestamp of when the tx was submitted |
||||||
|
* @return {number} minutes remaining |
||||||
|
*/ |
||||||
|
function calcTransactionTimeRemaining (initialTimeEstimate, submittedTime) { |
||||||
|
const currentTime = (new Date()).getTime() |
||||||
|
const timeElapsedSinceSubmission = (currentTime - submittedTime) / 1000 |
||||||
|
const timeRemainingOnEstimate = initialTimeEstimate - timeElapsedSinceSubmission |
||||||
|
|
||||||
|
const renderingTimeRemainingEstimate = Math.round(timeRemainingOnEstimate / 60) |
||||||
|
return renderingTimeRemainingEstimate |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* returns a string representing the number of minutes predicted for the transaction to be |
||||||
|
* completed. Only returns this prediction if the transaction is the earliest pending |
||||||
|
* transaction, and the feature flag for showing timing is enabled. |
||||||
|
* @param {bool} isPending - is the transaction currently pending |
||||||
|
* @param {bool} isEarliestNonce - is this transaction the earliest nonce in list |
||||||
|
* @param {number} submittedTime - the timestamp for when the transaction was submitted |
||||||
|
* @param {number} currentGasPrice - gas price to use for calculation of time |
||||||
|
* @returns {string | undefined} i18n formatted string if applicable |
||||||
|
*/ |
||||||
|
export function useTransactionTimeRemaining ( |
||||||
|
isPending, |
||||||
|
isEarliestNonce, |
||||||
|
submittedTime, |
||||||
|
currentGasPrice |
||||||
|
) { |
||||||
|
// the following two selectors return the result of mapping over an array, as such they
|
||||||
|
// will always be new objects and trigger effects. To avoid this, we use isEqual as the
|
||||||
|
// equalityFn to only update when the data is new.
|
||||||
|
const gasPrices = useSelector(getEstimatedGasPrices, isEqual) |
||||||
|
const estimatedTimes = useSelector(getEstimatedGasTimes, isEqual) |
||||||
|
const locale = useSelector(getCurrentLocale) |
||||||
|
const isMainNet = useSelector(getIsMainnet) |
||||||
|
const interval = useRef() |
||||||
|
const [timeRemaining, setTimeRemaining] = useState(null) |
||||||
|
const featureFlags = useSelector(getFeatureFlags) |
||||||
|
const transactionTimeFeatureActive = featureFlags?.transactionTime |
||||||
|
|
||||||
|
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto', style: 'narrow' }) |
||||||
|
|
||||||
|
// Memoize this value so it can be used as a dependency in the effect below
|
||||||
|
const initialTimeEstimate = useMemo(() => { |
||||||
|
const customGasPrice = Number(hexWEIToDecGWEI(currentGasPrice)) |
||||||
|
const { |
||||||
|
newTimeEstimate, |
||||||
|
} = getRawTimeEstimateData(customGasPrice, gasPrices, estimatedTimes) |
||||||
|
return newTimeEstimate |
||||||
|
}, [ currentGasPrice, gasPrices, estimatedTimes ]) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if ( |
||||||
|
isMainNet && |
||||||
|
transactionTimeFeatureActive && |
||||||
|
isPending && |
||||||
|
isEarliestNonce && |
||||||
|
!isNaN(initialTimeEstimate) |
||||||
|
) { |
||||||
|
clearInterval(interval.current) |
||||||
|
setTimeRemaining( |
||||||
|
calcTransactionTimeRemaining(initialTimeEstimate, submittedTime) |
||||||
|
) |
||||||
|
interval.current = setInterval(() => { |
||||||
|
setTimeRemaining( |
||||||
|
calcTransactionTimeRemaining(initialTimeEstimate, submittedTime) |
||||||
|
) |
||||||
|
}, 10000) |
||||||
|
return () => clearInterval(interval.current) |
||||||
|
} |
||||||
|
}, [ |
||||||
|
isMainNet, |
||||||
|
transactionTimeFeatureActive, |
||||||
|
isEarliestNonce, |
||||||
|
isPending, |
||||||
|
submittedTime, |
||||||
|
initialTimeEstimate, |
||||||
|
]) |
||||||
|
|
||||||
|
// there are numerous checks to determine if time should be displayed.
|
||||||
|
// if any of the following are true, the timeRemaining will be null
|
||||||
|
// User is currently not on the mainnet
|
||||||
|
// User does not have the transactionTime feature flag enabled
|
||||||
|
// The transaction is not pending, or isn't the earliest nonce
|
||||||
|
return timeRemaining ? rtf.format(timeRemaining, 'minute') : undefined |
||||||
|
} |
Loading…
Reference in new issue