From f9e2d53e9b33246856cb2b0812c2bf07e7d2060c Mon Sep 17 00:00:00 2001 From: Sebastien Guillemot Date: Fri, 20 Dec 2019 16:32:54 +0900 Subject: [PATCH] Detect server error in account state (#1240) * error display on delegation page * better error display * version bump * better handling of first-boot --- .../staking/dashboard/StakingDashboard.js | 21 ++- .../staking/dashboard/StakingDashboard.scss | 4 + .../wallet/staking/StakingDashboardPage.js | 123 ++++++++++------- app/stores/ada/DelegationStore.js | 127 ++++++++++-------- chrome/constants.js | 2 +- 5 files changed, 167 insertions(+), 110 deletions(-) diff --git a/app/components/wallet/staking/dashboard/StakingDashboard.js b/app/components/wallet/staking/dashboard/StakingDashboard.js index 5a8871d4f3..db911aea22 100644 --- a/app/components/wallet/staking/dashboard/StakingDashboard.js +++ b/app/components/wallet/staking/dashboard/StakingDashboard.js @@ -11,6 +11,9 @@ import WarningBox from '../../../widgets/WarningBox'; import InformativeError from '../../../widgets/InformativeError'; import LoadingSpinner from '../../../widgets/LoadingSpinner'; import VerticallyCenteredLayout from '../../../layout/VerticallyCenteredLayout'; +import LocalizableError from '../../../../i18n/LocalizableError'; +import InvalidURIImg from '../../../../assets/images/uri/invalid-uri.inline.svg'; +import ErrorBlock from '../../../widgets/ErrorBlock'; const messages = defineMessages({ positionsLabel: { @@ -42,7 +45,7 @@ type Props = {| themeVars: Object, totalGraphData: Array, positionsGraphData: Array, - stakePools: null | Array, + stakePools: {| error: LocalizableError, |} | {| pools: null | Array |}, epochProgress: Node, userSummary: Node, rewardPopup: void | Node, @@ -121,14 +124,24 @@ export default class StakingDashboard extends Component { displayStakePools: void => Node = () => { const { intl } = this.context; - if (this.props.stakePools == null) { + if (this.props.stakePools.error) { + return ( +
+
+ +
+ ); + } + if (this.props.stakePools.pools === null) { return ( ); } - if (this.props.stakePools.length === 0) { + if (this.props.stakePools.pools.length === 0) { return ( { return (
- {this.props.stakePools} + {this.props.stakePools.pools}
); diff --git a/app/components/wallet/staking/dashboard/StakingDashboard.scss b/app/components/wallet/staking/dashboard/StakingDashboard.scss index 1130f76c93..017c070e86 100644 --- a/app/components/wallet/staking/dashboard/StakingDashboard.scss +++ b/app/components/wallet/staking/dashboard/StakingDashboard.scss @@ -17,6 +17,10 @@ flex: 1; } +.poolError { + margin-top: 42px; +} + .rewards { min-width: 420px; align-self: flex-end; diff --git a/app/containers/wallet/staking/StakingDashboardPage.js b/app/containers/wallet/staking/StakingDashboardPage.js index 6dadda767d..93e36990b0 100644 --- a/app/containers/wallet/staking/StakingDashboardPage.js +++ b/app/containers/wallet/staking/StakingDashboardPage.js @@ -16,6 +16,8 @@ import environment from '../../../environment'; import { LOVELACES_PER_ADA } from '../../../config/numbersConfig'; import { digetForHash } from '../../../api/ada/lib/storage/database/primitives/api/utils'; import { handleExternalLinkClick } from '../../../utils/routing'; +import { GetPoolInfoApiError } from '../../../api/ada/errors'; +import LocalizableError from '../../../i18n/LocalizableError'; import { formattedWalletAmount } from '../../../utils/formatters'; @@ -98,8 +100,15 @@ export default class StakingDashboardPage extends Component { const getTimeBasedElements = this.getTimeBasedElements(); - const stakePools = this.getStakePools(); + const errorIfPresent = this.getErrorInFetch(); + const stakePools = errorIfPresent == null + ? this.getStakePools() + : errorIfPresent; + const showRewardAmount = delegationStore.getCurrentDelegation.wasExecuted && + delegationStore.getCurrentDelegation.result != null && + delegationStore.getDelegatedBalance.wasExecuted && + errorIfPresent == null; const { getThemeVars } = this.props.stores.profile; return ( { ? undefined : hideOrFormat(publicDeriver.amount) } - totalRewards={delegationStore.getDelegatedBalance.result == null - ? undefined - : hideOrFormat( - delegationStore.getDelegatedBalance.result - .accountPart - .dividedBy(LOVELACES_PER_ADA) - )} + totalRewards={ + !showRewardAmount || delegationStore.getDelegatedBalance.result == null + ? undefined + : hideOrFormat( + delegationStore.getDelegatedBalance.result + .accountPart + .dividedBy(LOVELACES_PER_ADA) + ) + } totalDelegated={ - delegationStore.getDelegatedBalance.result == null + !showRewardAmount || delegationStore.getDelegatedBalance.result == null ? undefined : hideOrFormat( delegationStore.getDelegatedBalance.result.utxoPart.plus( @@ -428,55 +439,73 @@ export default class StakingDashboardPage extends Component { }; } - getStakePools: void => null | Array = () => { + getErrorInFetch: void => void | {| error: LocalizableError, |} = () => { + const delegationStore = this.props.stores.substores[environment.API].delegation; + if (delegationStore.error != null) { + return { error: delegationStore.error }; + } + const keyState = delegationStore.stakingKeyState; + if ( + keyState && + keyState.state.delegation.pools.length === 0 && + delegationStore.getCurrentDelegation.result != null + ) { + return { error: new GetPoolInfoApiError() }; + } + return undefined; + } + + getStakePools: void => {| pools: null | Array |} = () => { const delegationStore = this.props.stores.substores[environment.API].delegation; if ( !delegationStore.getCurrentDelegation.wasExecuted || delegationStore.getCurrentDelegation.isExecuting ) { - return null; + return { pools: null }; } if (delegationStore.stakingKeyState == null) { - return []; + return { pools: [] }; } const keyState = delegationStore.stakingKeyState; const { intl } = this.context; - return keyState.state.delegation.pools.map(pool => { - const meta = keyState.poolInfo.get(pool[0]); - if (meta == null) { - throw new Error(`${nameof(this.getStakePools)} no meta for ${pool[0]}`); - } - const name = meta.info?.name ?? intl.formatMessage(globalMessages.unknownPoolLabel); - - const moreInfo = meta.info?.homepage != null - ? { - openPoolPage: handleExternalLinkClick, - url: meta.info.homepage, + return { + pools: keyState.state.delegation.pools.map(pool => { + const meta = keyState.poolInfo.get(pool[0]); + if (meta == null) { + throw new Error(`${nameof(this.getStakePools)} no meta for ${pool[0]}`); } - : undefined; + const name = meta.info?.name ?? intl.formatMessage(globalMessages.unknownPoolLabel); - // TODO: implement this eventually - const stakePoolMeta = { - // percentage: '30', - // fullness: '18', - // margins: '12', - // created: '29/02/2019 12:42:41 PM', - // cost: '12,688.00000', - // stake: '9,688.00000', - // pledge: '85.567088', - // rewards: '81.000088', - // age: '23', - }; - return ( - - ); - }); + const moreInfo = meta.info?.homepage != null + ? { + openPoolPage: handleExternalLinkClick, + url: meta.info.homepage, + } + : undefined; + + // TODO: implement this eventually + const stakePoolMeta = { + // percentage: '30', + // fullness: '18', + // margins: '12', + // created: '29/02/2019 12:42:41 PM', + // cost: '12,688.00000', + // stake: '9,688.00000', + // pledge: '85.567088', + // rewards: '81.000088', + // age: '23', + }; + return ( + + ); + }) + }; } } diff --git a/app/stores/ada/DelegationStore.js b/app/stores/ada/DelegationStore.js index ed44cc3015..120f63ddfe 100644 --- a/app/stores/ada/DelegationStore.js +++ b/app/stores/ada/DelegationStore.js @@ -19,6 +19,7 @@ import type { AccountStateSuccess, RemotePoolMetaSuccess, } from '../../api/ada/lib/state-fetch/types'; +import LocalizableError from '../../i18n/LocalizableError'; export default class DelegationStore extends Store { @@ -28,6 +29,8 @@ export default class DelegationStore extends Store { @observable getCurrentDelegation: LocalizedRequest = new LocalizedRequest(getCurrentDelegation); + @observable error: LocalizableError | any; + @observable stakingKeyState: void | {| state: AccountStateSuccess, /** @@ -59,72 +62,79 @@ export default class DelegationStore extends Store { ], // $FlowFixMe error in mobx types async () => { - this.getDelegatedBalance.reset(); - this.getCurrentDelegation.reset(); - runInAction(() => { - this.stakingKeyState = undefined; - }); - - const publicDeriver = this.stores.substores.ada.wallets.selected; - if (publicDeriver == null) { - throw new Error(`${nameof(this._startWatch)} no public deriver selected`); - } - const withStakingKey = asGetAllAccounting(publicDeriver.self); - if (withStakingKey == null) { - throw new Error(`${nameof(this._startWatch)} missing staking key functionality`); - } + try { + this.getDelegatedBalance.reset(); + this.getCurrentDelegation.reset(); + runInAction(() => { + this.error = undefined; + this.stakingKeyState = undefined; + }); - const stakingKeyResp = await withStakingKey.getStakingKey(); + const publicDeriver = this.stores.substores.ada.wallets.selected; + if (publicDeriver == null) { + throw new Error(`${nameof(this._startWatch)} no public deriver selected`); + } + const withStakingKey = asGetAllAccounting(publicDeriver.self); + if (withStakingKey == null) { + throw new Error(`${nameof(this._startWatch)} missing staking key functionality`); + } - const stateFetcher = this.stores.substores[environment.API].stateFetchStore.fetcher; - const accountStateResp = await stateFetcher.getAccountState({ - addresses: [stakingKeyResp.addr.Hash], - }); - const stateForStakingKey = accountStateResp[stakingKeyResp.addr.Hash]; + const stakingKeyResp = await withStakingKey.getStakingKey(); - if (!stateForStakingKey.delegation) { - return runInAction(() => { - this.stakingKeyState = undefined; - throw new Error(`${nameof(this._startWatch)} stake key invalid - ${stateForStakingKey.comment}`); + const stateFetcher = this.stores.substores[environment.API].stateFetchStore.fetcher; + const accountStateResp = await stateFetcher.getAccountState({ + addresses: [stakingKeyResp.addr.Hash], }); - } - const poolInfoResp = await stateFetcher.getPoolInfo({ - ids: stateForStakingKey.delegation.pools.map(delegation => delegation[0]), - }); - const meta = new Map(stateForStakingKey.delegation.pools.map(delegation => { - const info = poolInfoResp[delegation[0]]; - if (!info.history) { + const stateForStakingKey = accountStateResp[stakingKeyResp.addr.Hash]; + + if (!stateForStakingKey.delegation) { return runInAction(() => { this.stakingKeyState = undefined; - throw new Error(`${nameof(this._startWatch)} pool info missing ${info.error}`); + throw new Error(`${nameof(this._startWatch)} stake key invalid - ${stateForStakingKey.comment}`); }); } - return [delegation[0], info]; - })); - runInAction(() => { - this.stakingKeyState = { - state: stateForStakingKey, - poolInfo: meta, - }; - }); - - const delegatedBalance = this.getDelegatedBalance.execute({ - publicDeriver: withStakingKey, - accountState: stateForStakingKey, - stakingPubKey: stakingKeyResp.addr.Hash, - }).promise; - if (delegatedBalance == null) throw new Error('Should never happen'); - - const currentDelegation = this.getCurrentDelegation.execute({ - publicDeriver: withStakingKey, - stakingKeyAddressId: stakingKeyResp.addr.AddressId, - }).promise; - if (currentDelegation == null) throw new Error('Should never happen'); - - await Promise.all([ - delegatedBalance, - currentDelegation, - ]); + const poolInfoResp = await stateFetcher.getPoolInfo({ + ids: stateForStakingKey.delegation.pools.map(delegation => delegation[0]), + }); + const meta = new Map(stateForStakingKey.delegation.pools.map(delegation => { + const info = poolInfoResp[delegation[0]]; + if (!info.history) { + return runInAction(() => { + this.stakingKeyState = undefined; + throw new Error(`${nameof(this._startWatch)} pool info missing ${info.error}`); + }); + } + return [delegation[0], info]; + })); + runInAction(() => { + this.stakingKeyState = { + state: stateForStakingKey, + poolInfo: meta, + }; + }); + + const delegatedBalance = this.getDelegatedBalance.execute({ + publicDeriver: withStakingKey, + accountState: stateForStakingKey, + stakingPubKey: stakingKeyResp.addr.Hash, + }).promise; + if (delegatedBalance == null) throw new Error('Should never happen'); + + const currentDelegation = this.getCurrentDelegation.execute({ + publicDeriver: withStakingKey, + stakingKeyAddressId: stakingKeyResp.addr.AddressId, + }).promise; + if (currentDelegation == null) throw new Error('Should never happen'); + + await Promise.all([ + delegatedBalance, + currentDelegation, + ]); + } catch (e) { + runInAction(() => { + this.error = e; + }); + } }, ); } @@ -136,5 +146,6 @@ export default class DelegationStore extends Store { this.getDelegatedBalance.reset(); this.getCurrentDelegation.reset(); this.stakingKeyState = undefined; + this.error = undefined; } } diff --git a/chrome/constants.js b/chrome/constants.js index 5426d75709..dae2648e02 100644 --- a/chrome/constants.js +++ b/chrome/constants.js @@ -8,7 +8,7 @@ import { import { SEIZA_URL, SEIZA_FOR_YOROI_URL } from './manifestEnvs'; export const Version = { - Shelley: '2.2.1', + Shelley: '2.2.2', Byron: '1.10.0', };