Skip to content

Commit 380e404

Browse files
committed
sortable asset list and transaction list
1 parent ed04c2a commit 380e404

File tree

3 files changed

+80
-42
lines changed

3 files changed

+80
-42
lines changed

service/src/main/kotlin/io/provenance/explorer/domain/entities/Transactions.kt

+24-13
Original file line numberDiff line numberDiff line change
@@ -223,39 +223,46 @@ class TxCacheRecord(id: EntityID<Int>) : IntEntity(id) {
223223
}
224224
}
225225

226-
fun pulseTransactionsWithValue(denom: String, afterDateTime: LocalDateTime, page: Int, count: Int): PagedResults<Map<String, kotlin.Any?>> = transaction {
226+
fun pulseTransactionsWithValue(
227+
denom: String, afterDateTime: LocalDateTime,
228+
page: Int, count: Int,
229+
sort: List<SortOrder>, sortColumn: List<String>
230+
): PagedResults<Map<String, kotlin.Any?>> = transaction {
231+
/* This is simultaneously the scariest and most beautiful query I've ever seen. */
227232
val query = """
228233
select tx.id as tx_id,
229234
tx.hash,
230235
tx.height,
231236
tx.tx_timestamp,
232-
mtype.category,
233237
mtype.type,
234-
mtype.proto_type,
235-
mtype.module,
236-
tme.event_type,
237-
attr.attr_key,
238-
attr.attr_value
238+
attr_denom_value.denom as denom,
239+
sum(attr_denom_value.value) as denom_total
239240
from tx_cache tx
240241
join tx_msg_event as tme on tx.id = tme.tx_hash_id
241-
join tx_msg_event_attr as attr on tme.id = attr.tx_msg_event_id
242242
join tx_message_type as mtype on mtype.id = tme.tx_msg_type_id
243243
join tx_marker_join as denom on denom.tx_hash_id = tx.id
244+
join
245+
(SELECT attr.tx_msg_event_id,
246+
substring(rec from '[0-9]+(.*)${'$'}') AS denom,
247+
substring(rec from '^[0-9]+')::bigint AS value
248+
FROM tx_msg_event_attr attr
249+
CROSS JOIN LATERAL unnest(string_to_array(attr.attr_value, ',')) AS rec
250+
where attr.attr_key = 'amount') as attr_denom_value
251+
on attr_denom_value.tx_msg_event_id = tme.id
244252
where tme.tx_msg_type_id IN
245253
(select id from tx_message_type where module in ('exchange', 'bank'))
246254
and tx.tx_timestamp > ?
247255
and tx.error_code is null
248256
and tx.codespace is null
249257
and denom.denom = ?
250258
and event_type = 'coin_spent'
251-
and attr_key = 'amount'
252-
and attr_value like ?
253-
order by height desc, tx_id
259+
and attr_denom_value.denom = ?
260+
group by tx_id, tx.hash, tx.height, tx.tx_timestamp, mtype.type, attr_denom_value.denom
254261
""".trimIndent()
255262
val arguments = mutableListOf<Pair<ColumnType, *>>(
256263
Pair(JavaLocalDateTimeColumnType(), afterDateTime),
257264
Pair(TextColumnType(), denom),
258-
Pair(TextColumnType(), "%$denom%"),
265+
Pair(TextColumnType(), denom),
259266
)
260267

261268
val countQuery = "select count(*) from ($query) as count"
@@ -266,7 +273,11 @@ class TxCacheRecord(id: EntityID<Int>) : IntEntity(id) {
266273
arguments.add(Pair(IntegerColumnType(), count))
267274
arguments.add(Pair(IntegerColumnType(), page * count))
268275

269-
"$query limit ? offset ?".execAndMap(arguments) {
276+
val sortExpr = "order by " + if (sort.isNotEmpty() && sort.size == sortColumn.size)
277+
List(sort.size) { i -> "${sortColumn[i]} ${sort[i]} " }.joinToString(",")
278+
else "height desc, tx_id "
279+
280+
"$query $sortExpr limit ? offset ?".execAndMap(arguments) {
270281
val map = mutableMapOf<String, kotlin.Any?>()
271282
(1..it.metaData.columnCount).forEach { index ->
272283
map[it.metaData.getColumnName(index)] = it.getObject(index)

service/src/main/kotlin/io/provenance/explorer/service/PulseMetricService.kt

+20-26
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import io.provenance.explorer.model.base.USD_LOWER
2626
import io.provenance.explorer.model.base.USD_UPPER
2727
import kotlinx.coroutines.async
2828
import kotlinx.coroutines.runBlocking
29+
import org.jetbrains.exposed.sql.SortOrder
2930
import org.jetbrains.exposed.sql.transactions.transaction
3031
import org.springframework.stereotype.Service
3132
import java.math.BigDecimal
@@ -388,7 +389,7 @@ class PulseMetricService(
388389
runBlocking {
389390
exchangeGrpcClient.totalCommitmentCount().toBigDecimal().let {
390391
PulseMetric.build(
391-
base = UTILITY_TOKEN,
392+
base = UTILITY_TOKEN, // this is just a placeholder denom, not the query
392393
amount = it
393394
)
394395
}
@@ -563,7 +564,7 @@ class PulseMetricService(
563564
val denomExp = denomExponent(denomMetadata) ?: 1
564565
val denomPow = inversePowerOfTen(denomExp)
565566

566-
val (commitments, market) = runBlocking {
567+
val (commitments, _) = runBlocking {
567568
val commitmentsDeferred =
568569
async { exchangeGrpcClient.getMarketCommitments(it.marketId) }
569570
val marketDeferred =
@@ -613,33 +614,21 @@ class PulseMetricService(
613614
fun transactionSummaries(
614615
denom: String,
615616
count: Int,
616-
page: Int
617+
page: Int,
618+
sort: List<SortOrder>,
619+
sortColumn: List<String>
617620
): PagedResults<TransactionSummary> =
618621
TxCacheRecord.pulseTransactionsWithValue(
619622
denom,
620623
LocalDateTime.now().minusDays(1).startOfDay(),
621624
page,
622-
count
625+
count,
626+
sort,
627+
sortColumn
623628
).let { pr ->
624629
val denomMetadata = pulseAssetDenomMetadata(denom)
625630
val denomExp = denomExponent(denomMetadata) ?: 1
626631
val denomPow = inversePowerOfTen(denomExp)
627-
/*
628-
map of denom value by tx hash - provenance also puts coin values
629-
in a comma-separated list in the event value and sometimes it's
630-
quoted, sometimes it's not - so parse that madness
631-
*/
632-
val denomVolumeMapByHash = pr.results.map { r ->
633-
r["hash"] as String to r["attr_value"].toString()
634-
.replace("\"", "")
635-
.split(",")
636-
.filter { f -> f.contains(denom) }
637-
.sumOf { v ->
638-
v.substringBefore(denom).toBigDecimal()
639-
}
640-
}.groupBy { it.first }
641-
.mapValues { e -> e.value.sumOf { it.second }.times(denomPow) }
642-
643632
val denomPrice =
644633
fromPulseMetricCache(
645634
LocalDateTime.now().minusDays(1).startOfDay().toLocalDate(),
@@ -648,16 +637,21 @@ class PulseMetricService(
648637

649638
PagedResults(
650639
pages = pr.pages,
651-
results = pr.results.distinctBy { it["hash"] }.map { tx ->
652-
val hash = tx["hash"] as String
640+
results = pr.results.map { tx ->
641+
val denomTotal =
642+
if (tx["denom_total"] != null)
643+
BigDecimal(tx["denom_total"].toString()).times(denomPow)
644+
else
645+
BigDecimal.ZERO
646+
647+
val denomQuoteValue = denomTotal.times(denomPrice)
653648
TransactionSummary(
654-
txHash = hash,
649+
txHash = tx["hash"].toString(),
655650
block = tx["height"] as Int,
656651
time = tx["tx_timestamp"].toString(),
657652
type = tx["type"] as String,
658-
value = denomVolumeMapByHash[hash] ?: BigDecimal.ZERO,
659-
quoteValue = (denomVolumeMapByHash[hash] ?: BigDecimal.ZERO)
660-
.times(denomPrice),
653+
value = denomTotal,
654+
quoteValue = denomQuoteValue,
661655
quoteDenom = USD_UPPER,
662656
details = emptyList()
663657
)

service/src/main/kotlin/io/provenance/explorer/web/pulse/PulseAssetController.kt

+36-3
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import io.provenance.explorer.model.base.PagedResults
77
import io.provenance.explorer.service.PulseMetricService
88
import io.swagger.v3.oas.annotations.Operation
99
import io.swagger.v3.oas.annotations.tags.Tag
10+
import org.jetbrains.exposed.sql.SortOrder
1011
import org.springframework.http.MediaType
1112
import org.springframework.web.bind.annotation.GetMapping
1213
import org.springframework.web.bind.annotation.RequestMapping
1314
import org.springframework.web.bind.annotation.RequestParam
1415
import org.springframework.web.bind.annotation.RestController
16+
import java.math.BigDecimal
1517

1618
@RestController
1719
@RequestMapping(
@@ -26,7 +28,11 @@ class PulseAssetController(private val pulseMetricService: PulseMetricService) {
2628

2729
@Operation(summary = "Exchange-traded Asset Summaries")
2830
@GetMapping("/summary/list")
29-
fun getPulseAssetSummaries(@RequestParam(required = false) search: String?): List<PulseAssetSummary> =
31+
fun getPulseAssetSummaries(
32+
@RequestParam(required = false) search: String?,
33+
@RequestParam(required = false) sortOrder: List<SortOrder>?,
34+
@RequestParam(required = false) sortColumn: List<String>?
35+
): List<PulseAssetSummary> =
3036
pulseMetricService.pulseAssetSummaries().filter {
3137
// this is a small list so we can get away with this
3238
search.isNullOrBlank() ||
@@ -35,7 +41,32 @@ class PulseAssetController(private val pulseMetricService: PulseMetricService) {
3541
it.display.contains(search, ignoreCase = true) ||
3642
it.base.contains(search, ignoreCase = true) ||
3743
it.description.contains(search, ignoreCase = true)
44+
}.sortedWith(
45+
Comparator { a, b ->
46+
if (sortColumn == null || sortOrder == null) return@Comparator 0
47+
48+
for (i in sortColumn.indices) {
49+
val column = sortColumn[i]
50+
val order = sortOrder[i]
51+
52+
val comparisonResult = when (column) {
53+
"name" -> compareValues(
54+
a.name.ifEmpty { a.base },
55+
b.name.ifEmpty { b.base }
56+
)
57+
"price" -> compareValues(a.priceTrend?.currentQuantity ?: BigDecimal.ZERO, b.priceTrend?.currentQuantity ?: BigDecimal.ZERO)
58+
"marketCap" -> compareValues(a.marketCap, b.marketCap)
59+
"volume" -> compareValues(a.volumeTrend?.currentQuantity ?: BigDecimal.ZERO, b.volumeTrend?.currentQuantity ?: BigDecimal.ZERO)
60+
else -> throw IllegalArgumentException("Invalid sort column: $column")
61+
}
62+
63+
if (comparisonResult != 0) {
64+
return@Comparator if (order == SortOrder.ASC) comparisonResult else -comparisonResult
65+
}
66+
}
67+
0
3868
}
69+
)
3970

4071
/**
4172
* Note that `denom` is a required request param instead of a path variable
@@ -55,7 +86,9 @@ class PulseAssetController(private val pulseMetricService: PulseMetricService) {
5586
fun getAssetTransactionSummary(
5687
@RequestParam(required = true) denom: String,
5788
@RequestParam(required = false) page: Int = 0,
58-
@RequestParam(required = false) count: Int = 10
89+
@RequestParam(required = false) count: Int = 10,
90+
@RequestParam(required = false) sortOrder: List<SortOrder>?,
91+
@RequestParam(required = false) sortColumn: List<String>?
5992
): PagedResults<TransactionSummary> =
60-
pulseMetricService.transactionSummaries(denom, count, page)
93+
pulseMetricService.transactionSummaries(denom, count, page, sortOrder.orEmpty(), sortColumn.orEmpty())
6194
}

0 commit comments

Comments
 (0)