feat(@desktop/wallet): implement collectibles error states and retry

Fixes #11636
This commit is contained in:
Dario Gabriel Lipicar 2023-09-25 14:51:01 -03:00 committed by dlipicar
parent 1699189cec
commit b7d4cb9605
8 changed files with 162 additions and 51 deletions

View File

@ -62,7 +62,7 @@ QtObject:
proc refreshCollectiblesValues*(self: View) {.slot.} =
self.delegate.refreshCollectiblesValues()
proc networkConnectionStatusUpdate*(self: View, website: string, completelyDown: bool, connectionState: int, chainIds: string, lastCheckedAt: int) {.signal.}
proc networkConnectionStatusUpdate*(self: View, website: string, completelyDown: bool, connectionState: int, chainIds: string, lastCheckedAt: float) {.signal.}
proc updateNetworkConnectionStatus*(self: View, website: string, completelyDown: bool, connectionState: int, chainIds: string, lastCheckedAt: int) =
case website:
@ -75,5 +75,5 @@ QtObject:
of MARKET:
self.marketValuesNetworkConnection.updateValues(completelyDown, connectionState, chainIds, lastCheckedAt)
self.marketValuesNetworkConnectionChanged()
self.networkConnectionStatusUpdate(website, completelyDown, connectionState, chainIds, lastCheckedAt)
self.networkConnectionStatusUpdate(website, completelyDown, connectionState, chainIds, float(lastCheckedAt))

View File

@ -7,6 +7,8 @@ import ../../../app/core/signals/types
import ../wallet_account/service as wallet_service
import ../network/service as network_service
import ../node/service as node_service
import backend/connection_status as connection_status_backend
import backend/collectibles as collectibles_backend
logScope:
topics = "network-connection-service"
@ -23,7 +25,6 @@ type ConnectionStatus* = ref object of RootObj
lastCheckedAt*: int
const SIGNAL_CONNECTION_UPDATE* = "signalConnectionUpdate"
const SIGNAL_REFRESH_COLLECTIBLES* = "signalRefreshCollectibles"
type NetworkConnectionsArgs* = ref object of Args
website*: string
@ -43,7 +44,7 @@ proc newConnectionStatus(): ConnectionStatus =
connectionState: ConnectionState.Successful,
completelyDown: false,
chainIds: @[],
lastCheckedAt: 0,
lastCheckedAt: connection_status_backend.INVALID_TIMESTAMP,
)
QtObject:
@ -56,10 +57,11 @@ QtObject:
connectionStatus: Table[string, ConnectionStatus]
# Forward declaration
proc updateBlockchainsStatus(self: Service, completelyDown: bool, chaindIdsDown: seq[int], at: int)
proc updateMarketOrCollectibleStatus(self: Service, website: string, isDown: bool, at: int)
proc getChainIdsDown(self: Service, message: string): (bool, seq[int])
proc getIsDown(self: Service,message: string): bool
proc updateSimpleStatus(self: Service, website: string, isDown: bool, at: int)
proc updateMultichainStatus(self: Service, website: string, completelyDown: bool, chaindIdsDown: seq[int], at: int)
proc getChainIdsDown(self: Service, chainStatusTable: ConnectionStatusNotification): (bool, bool, seq[int], int)
proc getIsDown(message: string): bool
proc getChainStatusTable(message: string): ConnectionStatusNotification
proc delete*(self: Service) =
self.closingApp = true
@ -88,42 +90,67 @@ QtObject:
case data.eventType:
of "wallet-blockchain-status-changed":
if self.nodeService.isConnected():
let (allDown, chainsDown) = self.getChainIdsDown(data.message)
self.updateBlockchainsStatus(allDown, chainsDown, data.at)
let chainStateTable = getChainStatusTable(data.message)
let (allKnown, allDown, chainsDown, at) = self.getChainIdsDown(chainStateTable)
self.updateMultichainStatus(BLOCKCHAINS, allDown, chainsDown, data.at)
of "wallet-market-status-changed":
if self.nodeService.isConnected():
self.updateMarketOrCollectibleStatus(MARKET, self.getIsDown(data.message), data.at)
self.updateSimpleStatus(MARKET, getIsDown(data.message), data.at)
of "wallet-collectible-status-changed":
if self.nodeService.isConnected():
self.updateMarketOrCollectibleStatus(COLLECTIBLES, self.getIsDown(data.message), data.at)
let chainStateTable = fromJson(parseJson(data.message), ConnectionStatusNotification)
let (allKnown, allDown, chainsDown, at) = self.getChainIdsDown(chainStateTable)
if allKnown:
self.updateMultichainStatus(COLLECTIBLES, allDown, chainsDown, at)
self.events.on(SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT) do(e:Args):
if self.connectionStatus.hasKey(MARKET):
let connectionStatus = self.connectionStatus[MARKET]
self.updateMarketOrCollectibleStatus(MARKET, connectionStatus.completelyDown, connectionStatus.lastCheckedAt)
self.updateSimpleStatus(MARKET, connectionStatus.completelyDown, connectionStatus.lastCheckedAt)
if self.connectionStatus.hasKey(BLOCKCHAINS):
let connectionStatus = self.connectionStatus[BLOCKCHAINS]
self.updateBlockchainsStatus(connectionStatus.completelyDown, connectionStatus.chainIds, connectionStatus.lastCheckedAt)
self.updateMultichainStatus(BLOCKCHAINS, connectionStatus.completelyDown, connectionStatus.chainIds, connectionStatus.lastCheckedAt)
proc getIsDown(self: Service, message: string): bool =
proc getIsDown(message: string): bool =
result = message == "down"
proc getChainIdsDown(self: Service, message: string): (bool, seq[int]) =
let chainStatusTable = parseJson(message)
proc getStateValue(message: string): connection_status_backend.StateValue =
if message == "down":
return connection_status_backend.StateValue.Disconnected
elif message == "up":
return connection_status_backend.StateValue.Connected
else:
return connection_status_backend.StateValue.Unknown
proc getChainStatusTable(message: string): ConnectionStatusNotification =
result = initCustomStatusNotification()
let chainStatusTable = parseJson(message)
if chainStatusTable.kind != JNull:
for k, v in chainStatusTable.pairs:
result[k] = connection_status_backend.initConnectionState(
value = getStateValue(v.getStr)
)
proc getChainIdsDown(self: Service, chainStatusTable: ConnectionStatusNotification): (bool, bool, seq[int], int) =
var allKnown: bool = true
var allDown: bool = true
var chaindIdsDown: seq[int] = @[]
var lastSuccessAt: int = connection_status_backend.INVALID_TIMESTAMP # latest succesful connectinon between the down chains
let allChainIds = self.networkService.getNetworks().map(a => a.chainId)
if chainStatusTable.kind != JNull:
for id in allChainIds:
if chainStatusTable[$id].kind != JNull:
let isDown = self.getIsDown(chainStatusTable[$id].getStr)
if isDown:
chaindIdsDown.add(id)
else:
allDown = false
return (allDown, chaindIdsDown)
for id in allChainIds:
if chainStatusTable.hasKey($id) and chainStatusTable[$id].value != connection_status_backend.StateValue.Unknown:
if chainStatusTable[$id].value == connection_status_backend.StateValue.Connected:
allDown = false
else:
chaindIdsDown.add(id)
lastSuccessAt = max(lastSuccessAt, chainStatusTable[$id].lastSuccessAt)
else:
allKnown = false
return (allKnown, allDown, chaindIdsDown, lastSuccessAt)
proc getFormattedStringForChainIds(self: Service, chainIds: seq[int]): string =
for chainId in chainIds:
@ -142,6 +169,9 @@ QtObject:
lastCheckedAt: connectionStatus.lastCheckedAt
)
proc resetConnectionStatus(self: Service, website: string) =
self.connectionStatus[website] = newConnectionStatus()
proc updateConnectionStatus(self: Service,
website: string,
connectionState: ConnectionState,
@ -155,7 +185,7 @@ QtObject:
self.connectionStatus[website].chainIds = chainIds
self.connectionStatus[website].lastCheckedAt = lastCheckedAt
proc updateMarketOrCollectibleStatus(self: Service, website: string, isDown: bool, at: int) =
proc updateSimpleStatus(self: Service, website: string, isDown: bool, at: int) =
if self.connectionStatus.hasKey(website):
if isDown:
# trigger event
@ -167,24 +197,23 @@ QtObject:
self.connectionStatus[website] = newConnectionStatus()
self.events.emit(SIGNAL_CONNECTION_UPDATE, self.convertConnectionStatusToNetworkConnectionsArgs(website, self.connectionStatus[website]))
proc updateBlockchainsStatus(self: Service, completelyDown: bool, chaindIdsDown: seq[int], at: int) =
if self.connectionStatus.hasKey(BLOCKCHAINS):
# if all the networks are down for the BLOCKCHAINS, trigger event
proc updateMultichainStatus(self: Service, website: string, completelyDown: bool, chaindIdsDown: seq[int], at: int) =
if self.connectionStatus.hasKey(website):
# if all the networks are down for the website, trigger event
if completelyDown:
self.updateConnectionStatus(BLOCKCHAINS, ConnectionState.Failed, true, chaindIdsDown, at)
self.events.emit(SIGNAL_CONNECTION_UPDATE, self.convertConnectionStatusToNetworkConnectionsArgs(BLOCKCHAINS, self.connectionStatus[BLOCKCHAINS]))
self.updateConnectionStatus(website, ConnectionState.Failed, true, chaindIdsDown, at)
self.events.emit(SIGNAL_CONNECTION_UPDATE, self.convertConnectionStatusToNetworkConnectionsArgs(website, self.connectionStatus[website]))
# if all the networks are not down for the website
else:
# case where a down website is back up
if self.connectionStatus[BLOCKCHAINS].completelyDown or (chaindIdsDown.len == 0 and self.connectionStatus[BLOCKCHAINS].chainIds.len != 0):
self.connectionStatus[BLOCKCHAINS] = newConnectionStatus()
self.events.emit(SIGNAL_CONNECTION_UPDATE, self.convertConnectionStatusToNetworkConnectionsArgs(BLOCKCHAINS, self.connectionStatus[BLOCKCHAINS]))
if self.connectionStatus[website].completelyDown or (chaindIdsDown.len == 0 and self.connectionStatus[website].chainIds.len != 0):
self.resetConnectionStatus(website)
self.events.emit(SIGNAL_CONNECTION_UPDATE, self.convertConnectionStatusToNetworkConnectionsArgs(website, self.connectionStatus[website]))
# case where a some of networks on the website are down, trigger event
if chaindIdsDown.len > 0:
self.updateConnectionStatus(BLOCKCHAINS, ConnectionState.Failed, false, chaindIdsDown, at)
self.events.emit(SIGNAL_CONNECTION_UPDATE, self.convertConnectionStatusToNetworkConnectionsArgs(BLOCKCHAINS, self.connectionStatus[BLOCKCHAINS]))
self.updateConnectionStatus(website, ConnectionState.Failed, false, chaindIdsDown, at)
self.events.emit(SIGNAL_CONNECTION_UPDATE, self.convertConnectionStatusToNetworkConnectionsArgs(website, self.connectionStatus[website]))
proc blockchainsRetry*(self: Service) {.slot.} =
if(self.connectionStatus.hasKey(BLOCKCHAINS)):
@ -202,12 +231,12 @@ QtObject:
if(self.connectionStatus.hasKey(COLLECTIBLES)):
self.connectionStatus[COLLECTIBLES].connectionState = ConnectionState.Retrying
self.events.emit(SIGNAL_CONNECTION_UPDATE, self.convertConnectionStatusToNetworkConnectionsArgs(COLLECTIBLES, self.connectionStatus[COLLECTIBLES]))
self.events.emit(SIGNAL_REFRESH_COLLECTIBLES, Args())
discard collectibles_backend.refetchOwnedCollectibles()
proc networkConnected*(self: Service, connected: bool) =
if connected:
self.walletService.reloadAccountTokens()
self.events.emit(SIGNAL_REFRESH_COLLECTIBLES, Args())
discard collectibles_backend.refetchOwnedCollectibles()
else:
if(self.connectionStatus.hasKey(BLOCKCHAINS)):
self.connectionStatus[BLOCKCHAINS] = newConnectionStatus()

View File

@ -138,3 +138,6 @@ rpc(filterOwnedCollectiblesAsync, "wallet"):
rpc(getCollectiblesDetailsAsync, "wallet"):
requestId: int32
uniqueIds: seq[CollectibleUniqueID]
rpc(refetchOwnedCollectibles, "wallet"):
discard

View File

@ -0,0 +1,50 @@
import json, strformat, tables
const INVALID_TIMESTAMP* = -1
type
# Mirrors services/wallet/connection/types.go StateValue
StateValue* {.pure.} = enum
Unknown,
Connected,
Disconnected
# Mirrors services/wallet/connection/types.go State
ConnectionState* = ref object of RootObj
value*: StateValue
lastCheckedAt*: int
lastSuccessAt*: int
# Mirrors services/wallet/connection/status_notifier.go StatusNotification
ConnectionStatusNotification* = Table[string, ConnectionState]
# ConnectionState
proc initConnectionState*(value: StateValue, lastCheckedAt: int = INVALID_TIMESTAMP, lastSuccessAt: int = INVALID_TIMESTAMP): ConnectionState =
result = ConnectionState()
result.value = value
result.lastCheckedAt = lastCheckedAt
result.lastSuccessAt = lastSuccessAt
proc `$`*(self: ConnectionState): string =
return fmt"""ConnectionState(
value:{self.value},
lastCheckedAt:{self.lastCheckedAt},
lastSuccessAt:{self.lastSuccessAt}
)"""
proc fromJson*(t: JsonNode, T: typedesc[ConnectionState]): ConnectionState {.inline.} =
result = ConnectionState()
result.value = StateValue(t["value"].getInt())
result.lastCheckedAt = t["last_checked_at"].getInt()
result.lastSuccessAt = t["last_success_at"].getInt()
# ConnectionStatusNotification
proc initCustomStatusNotification*(): ConnectionStatusNotification =
result = initTable[string, ConnectionState]()
proc fromJson*(t: JsonNode, T: typedesc[ConnectionStatusNotification]): ConnectionStatusNotification {.inline.} =
result = initCustomStatusNotification()
if t.kind != JNull:
for k, v in t.pairs:
if v.kind != JNull:
result[k] = fromJson(v, ConnectionState)

View File

@ -925,21 +925,49 @@ Item {
objectName: "walletCollectiblesConnectionBanner"
Layout.fillWidth: true
websiteDown: Constants.walletConnections.collectibles
withCache: networkConnectionStore.collectiblesCache
withCache: lastCheckedAtUnix > 0
networkConnectionStore: appMain.networkConnectionStore
tooltipMessage: {
if(withCache)
return qsTr("Collectibles providers are currently unavailable for %1. Collectibles for those chains are as of %2.").arg(jointChainIdString).arg(lastCheckedAt)
else
return qsTr("Collectibles providers are currently unavailable for %1.").arg(jointChainIdString)
}
toastText: {
switch(connectionState) {
case Constants.ConnectionStatus.Success:
return qsTr("Opensea connection successful")
return qsTr("Collectibles providers connection successful")
case Constants.ConnectionStatus.Failure:
if(withCache){
return qsTr("Opensea down. Collectibles are as of %1.").arg(lastCheckedAt)
if(completelyDown) {
if(withCache)
return qsTr("Collectibles providers down. Collectibles are as of %1.").arg(lastCheckedAt)
else
return qsTr("Collectibles providers down. Collectibles cannot be retrieved.")
}
else {
return qsTr("Opensea down.")
else if(chainIdsDown.length > 0) {
if(chainIdsDown.length > 2) {
if(withCache)
return qsTr("Collectibles providers down for <a href='#'>multiple chains</a>. Collectibles for these chains are as of %1.".arg(lastCheckedAt))
else
return qsTr("Collectibles providers down for <a href='#'>multiple chains</a>. Collectibles for these chains cannot be retrieved.")
}
else if(chainIdsDown.length === 1) {
if(withCache)
return qsTr("Collectibles providers down for %1. Collectibles for this chain are as of %2.").arg(jointChainIdString).arg(lastCheckedAt)
else
return qsTr("Collectibles providers down for %1. Collectibles for this chain cannot be retrieved.").arg(jointChainIdString)
}
else {
if(withCache)
return qsTr("Collectibles providers down for %1. Collectibles for these chains are as of %2.").arg(jointChainIdString).arg(lastCheckedAt)
else
return qsTr("Collectibles providers down for %1. Collectibles for these chains cannot be retrieved.").arg(jointChainIdString)
}
}
else
return ""
case Constants.ConnectionStatus.Retrying:
return qsTr("Retrying connection to Opensea...")
return qsTr("Retrying connection to collectibles providers...")
default:
return ""
}

View File

@ -15,6 +15,7 @@ Loader {
property int connectionState: -1
property var chainIdsDown: []
property bool completelyDown: false
property double lastCheckedAtUnix: -1
property string lastCheckedAt
property bool withCache: false
property string tooltipMessage
@ -57,12 +58,13 @@ Loader {
Connections {
target: networkConnectionStore.networkConnectionModuleInst
function onNetworkConnectionStatusUpdate(website: string, completelyDown: bool, connectionState: int, chainIds: string, lastCheckedAt: int) {
function onNetworkConnectionStatusUpdate(website: string, completelyDown: bool, connectionState: int, chainIds: string, lastCheckedAtUnix: double) {
if (website === websiteDown) {
root.connectionState = connectionState
root.chainIdsDown = chainIds.split(";")
root.completelyDown = completelyDown
root.lastCheckedAt = LocaleUtils.formatDateTime(new Date(lastCheckedAt*1000))
root.lastCheckedAtUnix = lastCheckedAtUnix
root.lastCheckedAt = LocaleUtils.formatDateTime(new Date(lastCheckedAtUnix*1000))
root.updateBanner()
}
}

View File

@ -13,7 +13,6 @@ QtObject {
readonly property bool balanceCache: walletSectionAssets.hasBalanceCache
readonly property bool marketValuesCache: walletSectionAssets.hasMarketValuesCache
readonly property bool collectiblesCache: false // TODO: Issue #11636
readonly property var blockchainNetworksDown: !!networkConnectionModule.blockchainNetworkConnection.chainIds ? networkConnectionModule.blockchainNetworkConnection.chainIds.split(";") : []
readonly property bool atleastOneBlockchainNetworkAvailable: blockchainNetworksDown.length < networksModule.all.count

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit 2afe5a269de11bad5412a64e27d33bd49a28d588
Subproject commit bd6f9b098b018aba59d89d40343e4aee09e0531e