feat(wallet): make filter API async
Bump status-go to include the async API changes Handle the wallet-activity-filtering-done event Propagate the has more flag Extend the debugging UX Update #10994
This commit is contained in:
parent
c21c7cf705
commit
43c7258328
|
@ -7,6 +7,9 @@ import entry
|
|||
import ../transactions/item
|
||||
import ../transactions/module as transactions_module
|
||||
|
||||
import app/core/eventemitter
|
||||
import app/core/signals/types
|
||||
|
||||
import backend/activity as backend_activity
|
||||
import backend/backend as backend
|
||||
import backend/transactions
|
||||
|
@ -17,12 +20,20 @@ proc toRef*[T](obj: T): ref T =
|
|||
new(result)
|
||||
result[] = obj
|
||||
|
||||
const FETCH_BATCH_COUNT_DEFAULT = 10
|
||||
|
||||
QtObject:
|
||||
type
|
||||
Controller* = ref object of QObject
|
||||
model: Model
|
||||
transactionsModule: transactions_module.AccessInterface
|
||||
currentActivityFilter: backend_activity.ActivityFilter
|
||||
|
||||
events: EventEmitter
|
||||
|
||||
loadingData: bool
|
||||
errorCode: backend_activity.ErrorCode
|
||||
|
||||
# TODO remove chains and addresses to use the app one
|
||||
addresses: seq[string]
|
||||
chainIds: seq[int]
|
||||
|
@ -33,27 +44,20 @@ QtObject:
|
|||
proc delete*(self: Controller) =
|
||||
self.QObject.delete
|
||||
|
||||
proc newController*(transactionsModule: transactions_module.AccessInterface): Controller =
|
||||
new(result, delete)
|
||||
result.model = newModel()
|
||||
result.transactionsModule = transactionsModule
|
||||
result.currentActivityFilter = backend_activity.getIncludeAllActivityFilter()
|
||||
result.setup()
|
||||
|
||||
proc getModel*(self: Controller): QVariant {.slot.} =
|
||||
return newQVariant(self.model)
|
||||
|
||||
QtProperty[QVariant] model:
|
||||
read = getModel
|
||||
|
||||
# TODO: move it to service, make it async and lazy load details for transactions
|
||||
proc backendToPresentation(self: Controller, backendEntities: seq[backend_activity.ActivityEntry]): seq[entry.ActivityEntry] =
|
||||
var multiTransactionsIds: seq[int] = @[]
|
||||
var transactionIdentities: seq[backend.TransactionIdentity] = @[]
|
||||
var pendingTransactionIdentities: seq[backend.TransactionIdentity] = @[]
|
||||
|
||||
# Extract metadata required to fetch details
|
||||
# TODO: temporary here to show the working API. Will be done as required on a detail request from UI
|
||||
# TODO: temporary here to show the working API. Details for each entry will be done as required
|
||||
# on a detail request from UI after metadata is extended to include the required info
|
||||
for backendEntry in backendEntities:
|
||||
case backendEntry.payloadType:
|
||||
of MultiTransaction:
|
||||
|
@ -122,25 +126,38 @@ QtObject:
|
|||
error "failed to find pending transaction with identity: ", identity
|
||||
ptIndex += 1
|
||||
|
||||
proc refreshData(self: Controller) =
|
||||
proc loadingDataChanged*(self: Controller) {.signal.}
|
||||
|
||||
# result type is RpcResponse
|
||||
let response = backend_activity.getActivityEntries(self.addresses, self.chainIds, self.currentActivityFilter, 0, 10)
|
||||
# RPC returns null for result in case of empty array
|
||||
if response.error != nil or (response.result.kind != JArray and response.result.kind != JNull):
|
||||
proc setLoadingData(self: Controller, loadingData: bool) =
|
||||
self.loadingData = loadingData
|
||||
self.loadingDataChanged()
|
||||
|
||||
proc errorCodeChanged*(self: Controller) {.signal.}
|
||||
|
||||
proc setErrorCode(self: Controller, errorCode: int) =
|
||||
self.errorCode = backend_activity.ErrorCode(errorCode)
|
||||
self.errorCodeChanged()
|
||||
|
||||
proc refreshData(self: Controller) =
|
||||
let response = backend_activity.filterActivityAsync(self.addresses, self.chainIds, self.currentActivityFilter, 0, FETCH_BATCH_COUNT_DEFAULT)
|
||||
if response.error != nil:
|
||||
error "error fetching activity entries: ", response.error
|
||||
return
|
||||
|
||||
if response.result.kind == JNull:
|
||||
self.model.setEntries(@[])
|
||||
self.setLoadingData(true)
|
||||
|
||||
proc processResponse(self: Controller, response: JsonNode) =
|
||||
let res = fromJson(response, backend_activity.FilterResponse)
|
||||
|
||||
defer: self.setErrorCode(res.errorCode.int)
|
||||
|
||||
if res.errorCode != ErrorCodeSuccess:
|
||||
error "error fetching activity entries: ", res.errorCode
|
||||
return
|
||||
|
||||
var backendEntities = newSeq[backend_activity.ActivityEntry](response.result.len)
|
||||
for i in 0 ..< response.result.len:
|
||||
backendEntities[i] = fromJson(response.result[i], backend_activity.ActivityEntry)
|
||||
|
||||
let entries = self.backendToPresentation(backendEntities)
|
||||
self.model.setEntries(entries)
|
||||
let entries = self.backendToPresentation(res.activities)
|
||||
self.model.setEntries(entries, res.thereMightBeMore)
|
||||
self.setLoadingData(false)
|
||||
|
||||
proc updateFilter*(self: Controller) {.slot.} =
|
||||
self.refreshData()
|
||||
|
@ -160,6 +177,31 @@ QtObject:
|
|||
|
||||
self.currentActivityFilter.types = types
|
||||
|
||||
proc newController*(transactionsModule: transactions_module.AccessInterface, events: EventEmitter): Controller =
|
||||
new(result, delete)
|
||||
result.model = newModel()
|
||||
result.transactionsModule = transactionsModule
|
||||
result.currentActivityFilter = backend_activity.getIncludeAllActivityFilter()
|
||||
result.events = events
|
||||
result.setup()
|
||||
|
||||
let controller = result
|
||||
|
||||
proc handleEvent(e: Args) =
|
||||
var data = WalletSignal(e)
|
||||
case data.eventType:
|
||||
of backend_activity.eventActivityFilteringDone:
|
||||
let responseJson = parseJson(data.message)
|
||||
if responseJson.kind != JObject:
|
||||
error "unexpected json type", responseJson.kind
|
||||
return
|
||||
|
||||
controller.processResponse(responseJson)
|
||||
else:
|
||||
discard
|
||||
|
||||
result.events.on(SignalType.Wallet.event, handleEvent)
|
||||
|
||||
proc setFilterStatus*(self: Controller, statusesArrayJsonString: string) {.slot.} =
|
||||
let statusesJson = parseJson(statusesArrayJsonString)
|
||||
if statusesJson.kind != JArray:
|
||||
|
@ -221,3 +263,17 @@ QtObject:
|
|||
chainIds[i] = chainIdsJson[i].getInt()
|
||||
|
||||
self.chainIds = chainIds
|
||||
|
||||
proc getLoadingData*(self: Controller): bool {.slot.} =
|
||||
return self.loadingData
|
||||
|
||||
QtProperty[bool] loadingData:
|
||||
read = getLoadingData
|
||||
notify = loadingDataChanged
|
||||
|
||||
proc getErrorCode*(self: Controller): int {.slot.} =
|
||||
return self.errorCode.int
|
||||
|
||||
QtProperty[int] errorCode:
|
||||
read = getErrorCode
|
||||
notify = errorCodeChanged
|
|
@ -65,26 +65,24 @@ QtObject:
|
|||
of ModelRole.ActivityEntryRole:
|
||||
result = newQVariant(entry)
|
||||
|
||||
proc setEntries*(self: Model, entries: seq[ActivityEntry]) =
|
||||
proc hasMoreChanged*(self: Model) {.signal.}
|
||||
|
||||
proc setHasMore(self: Model, hasMore: bool) {.slot.} =
|
||||
self.hasMore = hasMore
|
||||
self.hasMoreChanged()
|
||||
|
||||
proc setEntries*(self: Model, entries: seq[ActivityEntry], hasMore: bool) =
|
||||
self.beginResetModel()
|
||||
self.entries = entries
|
||||
self.endResetModel()
|
||||
self.countChanged()
|
||||
|
||||
# TODO: update data
|
||||
self.setHasMore(hasMore)
|
||||
|
||||
# TODO: fetch more
|
||||
|
||||
proc hasMoreChanged*(self: Model) {.signal.}
|
||||
|
||||
proc getHasMore*(self: Model): bool {.slot.} =
|
||||
return self.hasMore
|
||||
|
||||
proc setHasMore*(self: Model, hasMore: bool) {.slot.} =
|
||||
self.hasMore = hasMore
|
||||
self.hasMoreChanged()
|
||||
|
||||
QtProperty[bool] hasMore:
|
||||
read = getHasMore
|
||||
write = setHasMore
|
||||
notify = hasMoreChanged
|
||||
|
|
|
@ -101,7 +101,7 @@ proc newModule*(
|
|||
result.networksModule = networks_module.newModule(result, events, networkService, walletAccountService, settingsService)
|
||||
result.filter = initFilter(result.controller)
|
||||
|
||||
result.activityController = activityc.newController(result.transactionsModule)
|
||||
result.activityController = activityc.newController(result.transactionsModule, events)
|
||||
result.view = newView(result, result.activityController)
|
||||
|
||||
|
||||
|
|
|
@ -10,6 +10,9 @@ export response_type
|
|||
# see status-go/services/wallet/activity/filter.go NoLimitTimestampForPeriod
|
||||
const noLimitTimestampForPeriod = 0
|
||||
|
||||
# Declared in services/wallet/activity/service.go
|
||||
const eventActivityFilteringDone*: string = "wallet-activity-filtering-done"
|
||||
|
||||
# TODO: consider using common status-go types via protobuf
|
||||
# TODO: consider using flags instead of list of enums
|
||||
type
|
||||
|
@ -146,6 +149,17 @@ type
|
|||
activityStatus*: ActivityStatus
|
||||
tokenType*: TokenType
|
||||
|
||||
# Mirrors services/wallet/activity/service.go ErrorCode
|
||||
ErrorCode* = enum
|
||||
ErrorCodeSuccess = 1,
|
||||
ErrorCodeFilterCanceled,
|
||||
ErrorCodeFilterFailed
|
||||
|
||||
FilterResponse* = object
|
||||
activities*: seq[ActivityEntry]
|
||||
thereMightBeMore*: bool
|
||||
errorCode*: ErrorCode
|
||||
|
||||
# Define toJson proc for PayloadType
|
||||
proc toJson*(ae: ActivityEntry): JsonNode {.inline.} =
|
||||
return %*(ae)
|
||||
|
@ -174,7 +188,22 @@ proc `$`*(self: ActivityEntry): string =
|
|||
tokenType* {$self.tokenType},
|
||||
)"""
|
||||
|
||||
rpc(getActivityEntries, "wallet"):
|
||||
proc fromJson*(e: JsonNode, T: typedesc[FilterResponse]): FilterResponse {.inline.} =
|
||||
var backendEntities: seq[ActivityEntry]
|
||||
if e.hasKey("activities"):
|
||||
let jsonEntries = e["activities"]
|
||||
backendEntities = newSeq[ActivityEntry](jsonEntries.len)
|
||||
for i in 0 ..< jsonEntries.len:
|
||||
backendEntities[i] = fromJson(jsonEntries[i], ActivityEntry)
|
||||
|
||||
result = T(
|
||||
activities: backendEntities,
|
||||
thereMightBeMore: if e.hasKey("thereMightBeMore"): e["thereMightBeMore"].getBool()
|
||||
else: false,
|
||||
errorCode: ErrorCode(e["errorCode"].getInt())
|
||||
)
|
||||
|
||||
rpc(filterActivityAsync, "wallet"):
|
||||
addresses: seq[string]
|
||||
chainIds: seq[int]
|
||||
filter: ActivityFilter
|
||||
|
|
|
@ -142,7 +142,7 @@ Control {
|
|||
id: timeFilterLayout
|
||||
|
||||
RowLayout {
|
||||
Label { text: "Past Days Span: 100" }
|
||||
Label { text: qsTr("Past Days Span: 100") }
|
||||
Slider {
|
||||
id: fromSlider
|
||||
|
||||
|
@ -170,12 +170,12 @@ Control {
|
|||
stepSize: 1
|
||||
value: 0
|
||||
}
|
||||
Label { text: "0" }
|
||||
Label { text: qsTr("0") }
|
||||
}
|
||||
Label { text: `Interval: ${d.start > 0 ? root.epochToDateStr(d.start) : "all time"} - ${d.end > 0 ? root.epochToDateStr(d.end) : "now"}` }
|
||||
Label { text: `Interval: ${d.start > 0 ? root.epochToDateStr(d.start) : qsTr("all time")} - ${d.end > 0 ? root.epochToDateStr(d.end) : qsTr("now")}` }
|
||||
}
|
||||
RowLayout {
|
||||
Label { text: "Type" }
|
||||
Label { text: qsTr("Type") }
|
||||
// Models the ActivityType
|
||||
ListModel {
|
||||
id: typeModel
|
||||
|
@ -198,7 +198,7 @@ Control {
|
|||
delegate: ItemOnOffDelegate {}
|
||||
}
|
||||
|
||||
Label { text: "Status" }
|
||||
Label { text: qsTr("Status") }
|
||||
// ActivityStatus
|
||||
ListModel {
|
||||
id: statusModel
|
||||
|
@ -219,7 +219,7 @@ Control {
|
|||
delegate: ItemOnOffDelegate {}
|
||||
}
|
||||
|
||||
Label { text: "To addresses" }
|
||||
Label { text: qsTr("To addresses") }
|
||||
TextField {
|
||||
id: toAddressesInput
|
||||
|
||||
|
@ -235,7 +235,7 @@ Control {
|
|||
}
|
||||
RowLayout {
|
||||
|
||||
Label { text: "Addresses" }
|
||||
Label { text: qsTr("Addresses") }
|
||||
TextField {
|
||||
id: addressesInput
|
||||
|
||||
|
@ -244,7 +244,7 @@ Control {
|
|||
placeholderText: qsTr("0x1234, 0x5678, ...")
|
||||
}
|
||||
|
||||
Label { text: "Chains" }
|
||||
Label { text: qsTr("Chains") }
|
||||
ComboBox {
|
||||
displayText: qsTr("Select chains")
|
||||
|
||||
|
@ -256,7 +256,7 @@ Control {
|
|||
delegate: ItemOnOffDelegate {}
|
||||
}
|
||||
|
||||
Label { text: "Assets" }
|
||||
Label { text: qsTr("Assets") }
|
||||
ComboBox {
|
||||
displayText: assetsLoader.status != Loader.Ready ? qsTr("Loading...") : qsTr("Select an asset")
|
||||
|
||||
|
@ -321,6 +321,16 @@ Control {
|
|||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: loadingText
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
text: qsTr("Loading...")
|
||||
visible: controller.loadingData
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
ListView {
|
||||
id: listView
|
||||
|
||||
|
@ -328,6 +338,7 @@ Control {
|
|||
Layout.fillHeight: true
|
||||
|
||||
model: controller.model
|
||||
visible: !controller.loadingData
|
||||
|
||||
delegate: Item {
|
||||
width: parent ? parent.width : 0
|
||||
|
@ -339,25 +350,25 @@ Control {
|
|||
id: itemLayout
|
||||
anchors.fill: parent
|
||||
|
||||
Label { text: entry.isMultiTransaction ? "MT" : entry.isPendingTransaction ? "PT" : " T" }
|
||||
Label { text: entry.isMultiTransaction ? qsTr("MT") : entry.isPendingTransaction ? qsTr("PT") : qsTr(" T") }
|
||||
Label { text: `[${root.epochToDateStr(entry.timestamp)}] ` }
|
||||
Label { text: entry.isMultiTransaction ? entry.fromAmount : entry.amount }
|
||||
Label { text: "from"; Layout.leftMargin: 5; Layout.rightMargin: 5 }
|
||||
Label { text: qsTr("from"); Layout.leftMargin: 5; Layout.rightMargin: 5 }
|
||||
Label { text: entry.sender; Layout.maximumWidth: 200; elide: Text.ElideMiddle }
|
||||
Label { text: "to"; Layout.leftMargin: 5; Layout.rightMargin: 5 }
|
||||
Label { text: qsTr("to"); Layout.leftMargin: 5; Layout.rightMargin: 5 }
|
||||
Label { text: entry.recipient; Layout.maximumWidth: 200; elide: Text.ElideMiddle }
|
||||
Label { text: "got"; Layout.leftMargin: 5; Layout.rightMargin: 5; visible: entry.isMultiTransaction }
|
||||
Label { text: qsTr("got"); Layout.leftMargin: 5; Layout.rightMargin: 5; visible: entry.isMultiTransaction }
|
||||
Label { text: entry.toAmount; Layout.leftMargin: 5; Layout.rightMargin: 5; visible: entry.isMultiTransaction }
|
||||
Label {
|
||||
text: `{${
|
||||
function() {
|
||||
switch (entry.status) {
|
||||
case Constants.TransactionStatus.Failed: return "F";
|
||||
case Constants.TransactionStatus.Pending: return "P";
|
||||
case Constants.TransactionStatus.Complete: return "C";
|
||||
case Constants.TransactionStatus.Finalized: return "FZ";
|
||||
case Constants.TransactionStatus.Failed: return qsTr("F");
|
||||
case Constants.TransactionStatus.Pending: return qsTr("P");
|
||||
case Constants.TransactionStatus.Complete: return qsTr("C");
|
||||
case Constants.TransactionStatus.Finalized: return qsTr("FZ");
|
||||
}
|
||||
return "-"
|
||||
return qsTr("-")
|
||||
}()}}`
|
||||
Layout.leftMargin: 5;
|
||||
}
|
||||
|
@ -365,6 +376,30 @@ Control {
|
|||
RowLayout {} // Spacer
|
||||
}
|
||||
}
|
||||
|
||||
footer: Component {
|
||||
Item {
|
||||
width: listView.width
|
||||
height: footerText.implicitHeight
|
||||
|
||||
Text {
|
||||
id: footerText
|
||||
text: qsTr("Loading more items...")
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
visible: controller.model.hasMore
|
||||
|
||||
// Load more items when this footer comes into view.
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
controller.loadMoreItems();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 1a2ca21070456e153aac27b8a14afa14006652ef
|
||||
Subproject commit d8eb038d7d7026aeece4775771bbb4bcf00c88dc
|
Loading…
Reference in New Issue