Browse Source

Add 'Outdated' concept

Outdated rarely happen: it's only if you have not yet pulled for a time and you had no network condition. because otherwise, our pull interval is always smaller than the time we consider 'outdated' (set at block time + 5mn to be safe)
In case an error occur during the sync, we won't show it until one of the account is also outdated, which i think will lighten a bit the cases we have errors. we can decide later to be smarter and more verbose (like being able to know which account have a problem, which one is not sync etc..) but we'll need to have a design for it
master
Gaëtan Renaudeau 7 years ago
parent
commit
d6f3a183a9
  1. 78
      src/components/TopBar/ActivityIndicator.js
  2. 2
      src/config/constants.js
  3. 16
      src/reducers/accounts.js
  4. 1
      static/i18n/en/app.yml

78
src/components/TopBar/ActivityIndicator.js

@ -10,6 +10,7 @@ import type { T } from 'types/common'
import type { AsyncState } from 'reducers/bridgeSync'
import { globalSyncStateSelector } from 'reducers/bridgeSync'
import { isUpToDateSelector } from 'reducers/accounts'
import { BridgeSyncConsumer } from 'bridge/BridgeSyncContext'
import CounterValues from 'helpers/countervalues'
@ -24,78 +25,45 @@ import ItemContainer from './ItemContainer'
const mapStateToProps = createStructuredSelector({
globalSyncState: globalSyncStateSelector,
isUpToDate: isUpToDateSelector,
})
type Props = {
// FIXME: eslint should see that it is used in static method
isGlobalSyncStatePending: boolean, // eslint-disable-line react/no-unused-prop-types
error: ?Error,
isPending: boolean,
isError: boolean,
isUpToDate: boolean,
t: T,
cvPoll: *,
setSyncBehavior: *,
}
type State = {
hasClicked: boolean,
isGlobalSyncStatePending: boolean,
isFirstSync: boolean,
}
class ActivityIndicatorInner extends PureComponent<Props, State> {
state = {
hasClicked: false,
isFirstSync: true,
// FIXME: eslint should see that it is used in static method
isGlobalSyncStatePending: false, // eslint-disable-line react/no-unused-state
}
static getDerivedStateFromProps(nextProps: Props, prevState: State) {
const nextState = {
...prevState,
isGlobalSyncStatePending: nextProps.isGlobalSyncStatePending,
}
if (prevState.isGlobalSyncStatePending && !nextProps.isGlobalSyncStatePending) {
nextState.isFirstSync = false
nextState.hasClicked = false
}
return nextState
}
class ActivityIndicatorInner extends PureComponent<Props> {
onClick = () => {
this.props.cvPoll()
this.props.setSyncBehavior({ type: 'SYNC_ALL_ACCOUNTS', priority: 5 })
}
handleRefresh = () => {
this.setState({ hasClicked: true })
this.onClick()
}
render() {
const { isPending, isError, error, t } = this.props
const { hasClicked, isFirstSync } = this.state
const isDisabled = isError || (isPending && (isFirstSync || hasClicked))
const isRotating = isPending && (hasClicked || isFirstSync)
const { isUpToDate, isPending, isError, error, t } = this.props
const isDisabled = isError || isPending
const isRotating = isPending
const content = (
<ItemContainer disabled={isDisabled} onClick={isDisabled ? undefined : this.handleRefresh}>
<ItemContainer disabled={isDisabled} onClick={isDisabled ? undefined : this.onClick}>
<Rotating
size={16}
isRotating={isRotating}
color={isError ? 'alertRed' : isRotating ? 'grey' : 'positiveGreen'}
color={isError ? 'alertRed' : isRotating ? 'grey' : isUpToDate ? 'positiveGreen' : 'grey'}
>
{isError ? (
<IconExclamationCircle size={16} />
) : isRotating ? (
<IconLoader size={16} />
) : (
) : isUpToDate ? (
<IconCheckCircle size={16} />
) : (
<IconExclamationCircle size={16} />
)}
</Rotating>
<Box
@ -115,19 +83,21 @@ class ActivityIndicatorInner extends PureComponent<Props, State> {
ml={2}
cursor="pointer"
style={{ textDecoration: 'underline', pointerEvents: 'all' }}
onClick={this.handleRefresh}
onClick={this.onClick}
>
{t('app:common.sync.refresh')}
</Box>
</Fragment>
) : (
) : isUpToDate ? (
t('app:common.sync.upToDate')
) : (
t('app:common.sync.outdated')
)}
</Box>
</ItemContainer>
)
if (error) {
if (isError && error) {
return (
<Tooltip
tooltipBg="alertRed"
@ -146,7 +116,15 @@ class ActivityIndicatorInner extends PureComponent<Props, State> {
}
}
const ActivityIndicator = ({ globalSyncState, t }: { globalSyncState: AsyncState, t: T }) => (
const ActivityIndicator = ({
globalSyncState,
t,
isUpToDate,
}: {
globalSyncState: AsyncState,
t: T,
isUpToDate: boolean,
}) => (
<BridgeSyncConsumer>
{setSyncBehavior => (
<CounterValues.PollingConsumer>
@ -156,9 +134,9 @@ const ActivityIndicator = ({ globalSyncState, t }: { globalSyncState: AsyncState
return (
<ActivityIndicatorInner
t={t}
isUpToDate={isUpToDate}
isPending={isPending}
isGlobalSyncStatePending={globalSyncState.pending}
isError={!!isError}
isError={!!isError && !isUpToDate} // we only show error if it's not up to date. this hide a bit error that happen from time to time
error={isError ? globalSyncState.error : null}
cvPoll={cvPolling.poll}
setSyncBehavior={setSyncBehavior}

2
src/config/constants.js

@ -23,6 +23,7 @@ export const SYNC_BOOT_DELAY = 2 * 1000
export const SYNC_ALL_INTERVAL = 120 * 1000
export const GENUINE_TIMEOUT = intFromEnv('GENUINE_TIMEOUT', 120 * 1000)
export const SYNC_TIMEOUT = intFromEnv('SYNC_TIMEOUT', 30 * 1000)
export const OUTDATED_CONSIDERED_DELAY = intFromEnv('OUTDATED_CONSIDERED_DELAY', 5 * 60 * 1000)
export const CHECK_APP_INTERVAL_WHEN_INVALID = 600
export const CHECK_APP_INTERVAL_WHEN_VALID = 1200
@ -60,6 +61,7 @@ export const DEBUG_ACTION = boolFromEnv('DEBUG_ACTION')
export const DEBUG_TAB_KEY = boolFromEnv('DEBUG_TAB_KEY')
export const DEBUG_LIBCORE = boolFromEnv('DEBUG_LIBCORE')
export const DEBUG_WS = boolFromEnv('DEBUG_WS')
export const DEBUG_SYNC = boolFromEnv('DEBUG_SYNC')
export const LEDGER_RESET_ALL = boolFromEnv('LEDGER_RESET_ALL')
export const LEDGER_DEBUG_ALL_LANGS = boolFromEnv('LEDGER_DEBUG_ALL_LANGS')
export const SKIP_GENUINE = boolFromEnv('SKIP_GENUINE')

16
src/reducers/accounts.js

@ -4,8 +4,8 @@ import { createSelector } from 'reselect'
import { handleActions } from 'redux-actions'
import { createAccountModel } from '@ledgerhq/live-common/lib/models/account'
import logger from 'logger'
import type { Account, AccountRaw } from '@ledgerhq/live-common/lib/types'
import { OUTDATED_CONSIDERED_DELAY, DEBUG_SYNC } from 'config/constants'
export type AccountsState = Account[]
const state: AccountsState = []
@ -62,6 +62,20 @@ const handlers: Object = {
export const accountsSelector = (state: { accounts: AccountsState }): Account[] => state.accounts
export const isUpToDateSelector = createSelector(accountsSelector, accounts =>
accounts.every(a => {
const { lastSyncDate } = a
const { blockAvgTime } = a.currency
if (!blockAvgTime) return true
const outdated =
Date.now() - (lastSyncDate || 0) > blockAvgTime * 1000 + OUTDATED_CONSIDERED_DELAY
if (outdated && DEBUG_SYNC) {
logger.log('account not up to date', a)
}
return !outdated
}),
)
export const hasAccountsSelector = createSelector(accountsSelector, accounts => accounts.length > 0)
export const currenciesSelector = createSelector(accountsSelector, accounts =>

1
static/i18n/en/app.yml

@ -40,6 +40,7 @@ common:
sync:
syncing: Synchronizing...
upToDate: Up to date
outdated: Outdated
error: Synchronization error
refresh: Refresh
ago: Synced {{time}}

Loading…
Cancel
Save