Fix tokens pagination

pull/7611/head
Maxim Filonov 1 year ago
parent f4aa26afc3
commit bed088dc8f
  1. 1
      CHANGELOG.md
  2. 41
      apps/block_scout_web/lib/block_scout_web/chain.ex
  3. 3
      apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex
  4. 156
      apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs
  5. 72
      apps/explorer/lib/explorer/chain.ex

@ -12,6 +12,7 @@
### Fixes
- [#7611](https://github.com/blockscout/blockscout/pull/7611) - Fix tokens pagination
- [#7566](https://github.com/blockscout/blockscout/pull/7566) - Account: check composed email beofre sending
- [#7564](https://github.com/blockscout/blockscout/pull/7564) - Return contract type in address view
- [#7562](https://github.com/blockscout/blockscout/pull/7562) - Remove fallback from Read methods

@ -16,6 +16,8 @@ defmodule BlockScoutWeb.Chain do
token_contract_address_from_token_name: 1
]
import Explorer.Helper, only: [parse_integer: 1]
alias Explorer.Chain.Block.Reward
alias Explorer.Chain.{
@ -23,6 +25,7 @@ defmodule BlockScoutWeb.Chain do
Address.CoinBalance,
Address.CurrentTokenBalance,
Block,
Hash,
InternalTransaction,
Log,
SmartContract,
@ -141,16 +144,30 @@ defmodule BlockScoutWeb.Chain do
]
end
def paging_options(%{"market_cap" => market_cap, "holder_count" => holder_count, "name" => token_name}) do
def paging_options(%{
"market_cap" => market_cap,
"holder_count" => holder_count_str,
"name" => name,
"contract_address_hash" => contract_address_hash_str,
"is_name_null" => is_name_null
}) do
market_cap_decimal =
case Decimal.parse(market_cap) do
{decimal, ""} -> Decimal.round(decimal, 16)
_ -> nil
end
case Integer.parse(holder_count) do
{holder_count, ""} ->
[paging_options: %{@default_paging_options | key: {market_cap_decimal, holder_count, token_name}}]
holder_count = parse_integer(holder_count_str)
token_name = if is_name_null == "true", do: nil, else: name
case Hash.Address.cast(contract_address_hash_str) do
{:ok, contract_address_hash} ->
[
paging_options: %{
@default_paging_options
| key: {market_cap_decimal, holder_count, token_name, contract_address_hash}
}
]
_ ->
[paging_options: @default_paging_options]
@ -377,18 +394,22 @@ defmodule BlockScoutWeb.Chain do
end
defp paging_params(%Token{
contract_address_hash: contract_address_hash,
circulating_market_cap: circulating_market_cap,
holder_count: holder_count,
name: token_name
}) do
%{"market_cap" => circulating_market_cap, "holder_count" => holder_count, "name" => token_name}
%{
"market_cap" => circulating_market_cap,
"holder_count" => holder_count,
"contract_address_hash" => contract_address_hash,
"name" => token_name,
"is_name_null" => is_nil(token_name)
}
end
defp paging_params([
%Token{circulating_market_cap: circulating_market_cap, holder_count: holder_count, name: token_name},
_
]) do
%{"market_cap" => circulating_market_cap, "holder_count" => holder_count, "name" => token_name}
defp paging_params([%Token{} = token, _]) do
paging_params(token)
end
defp paging_params({%Reward{block: %{number: number}}, _}) do

@ -16,7 +16,8 @@ defmodule BlockScoutWeb.API.V2.TokenView do
"holders" => token.holder_count && to_string(token.holder_count),
"exchange_rate" => exchange_rate(token),
"total_supply" => token.total_supply,
"icon_url" => token.icon_url
"icon_url" => token.icon_url,
"circulating_market_cap" => token.circulating_market_cap
}
end

@ -393,26 +393,170 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do
end
describe "/tokens" do
defp check_tokens_pagination(tokens, conn) do
request = get(conn, "/api/v2/tokens")
assert response = json_response(request, 200)
request_2nd_page = get(conn, "/api/v2/tokens", response["next_page_params"])
assert response_2nd_page = json_response(request_2nd_page, 200)
check_paginated_response(response, response_2nd_page, tokens)
end
test "get empty list", %{conn: conn} do
request = get(conn, "/api/v2/tokens")
assert %{"items" => [], "next_page_params" => nil} = json_response(request, 200)
end
test "check pagination", %{conn: conn} do
# these tests that tokens paginates by each parameter separately and by any combination of them
test "pagination by address", %{conn: conn} do
tokens =
for i <- 0..50 do
insert(:token, name: nil)
end
|> Enum.reverse()
check_tokens_pagination(tokens, conn)
end
test "pagination by name", %{conn: conn} do
tokens =
for i <- 0..48 do
insert(:token, holder_count: i)
end
request = get(conn, "/api/v2/tokens")
assert response = json_response(request, 200)
empty_named_token = insert(:token, name: "")
named_token = insert(:token)
request_2nd_page = get(conn, "/api/v2/tokens", response["next_page_params"])
tokens = [named_token, empty_named_token | tokens]
assert response_2nd_page = json_response(request_2nd_page, 200)
check_tokens_pagination(tokens, conn)
end
check_paginated_response(response, response_2nd_page, tokens)
test "pagination by holders", %{conn: conn} do
tokens =
for i <- 0..50 do
insert(:token, holder_count: i, name: nil)
end
check_tokens_pagination(tokens, conn)
end
test "pagination by circulating_market_cap", %{conn: conn} do
tokens =
for i <- 0..50 do
insert(:token, circulating_market_cap: i, name: nil)
end
check_tokens_pagination(tokens, conn)
end
test "pagination by name and address", %{conn: conn} do
tokens =
for i <- 0..50 do
insert(:token)
end
|> Enum.reverse()
check_tokens_pagination(tokens, conn)
end
test "pagination by holders and address", %{conn: conn} do
tokens =
for i <- 0..50 do
insert(:token, holder_count: 1, name: nil)
end
|> Enum.reverse()
check_tokens_pagination(tokens, conn)
end
test "pagination by circulating_market_cap and address", %{conn: conn} do
tokens =
for i <- 0..50 do
insert(:token, circulating_market_cap: 1, name: nil)
end
|> Enum.reverse()
check_tokens_pagination(tokens, conn)
end
test "pagination by holders and name", %{conn: conn} do
tokens =
for i <- 1..51 do
insert(:token, holder_count: 1, name: List.to_string([i]))
end
|> Enum.reverse()
check_tokens_pagination(tokens, conn)
end
test "pagination by circulating_market_cap and name", %{conn: conn} do
tokens =
for i <- 1..51 do
insert(:token, circulating_market_cap: 1, name: List.to_string([i]))
end
|> Enum.reverse()
check_tokens_pagination(tokens, conn)
end
test "pagination by circulating_market_cap and holders", %{conn: conn} do
tokens =
for i <- 0..50 do
insert(:token, circulating_market_cap: 1, holder_count: i, name: nil)
end
check_tokens_pagination(tokens, conn)
end
test "pagination by holders, name and address", %{conn: conn} do
tokens =
for i <- 0..50 do
insert(:token, holder_count: 1)
end
|> Enum.reverse()
check_tokens_pagination(tokens, conn)
end
test "pagination by circulating_market_cap, name and address", %{conn: conn} do
tokens =
for i <- 0..50 do
insert(:token, circulating_market_cap: 1)
end
|> Enum.reverse()
check_tokens_pagination(tokens, conn)
end
test "pagination by circulating_market_cap, holders and address", %{conn: conn} do
tokens =
for i <- 0..50 do
insert(:token, circulating_market_cap: 1, holder_count: 1, name: nil)
end
|> Enum.reverse()
check_tokens_pagination(tokens, conn)
end
test "pagination by circulating_market_cap, holders and name", %{conn: conn} do
tokens =
for i <- 1..51 do
insert(:token, circulating_market_cap: 1, holder_count: 1, name: List.to_string([i]))
end
|> Enum.reverse()
check_tokens_pagination(tokens, conn)
end
test "pagination by circulating_market_cap, holders, name and address", %{conn: conn} do
tokens =
for i <- 0..50 do
insert(:token, holder_count: 1, circulating_market_cap: 1)
end
|> Enum.reverse()
check_tokens_pagination(tokens, conn)
end
test "check nil", %{conn: conn} do

@ -2556,7 +2556,12 @@ defmodule Explorer.Chain do
defp base_token_query(empty_type) when empty_type in [nil, []] do
from(t in Token,
order_by: [desc_nulls_last: t.circulating_market_cap, desc_nulls_last: t.holder_count, asc: t.name],
order_by: [
desc_nulls_last: t.circulating_market_cap,
desc_nulls_last: t.holder_count,
asc: t.name,
asc: t.contract_address_hash
],
preload: [:contract_address]
)
end
@ -2564,7 +2569,12 @@ defmodule Explorer.Chain do
defp base_token_query(token_types) when is_list(token_types) do
from(t in Token,
where: t.type in ^token_types,
order_by: [desc_nulls_last: t.circulating_market_cap, desc_nulls_last: t.holder_count, asc: t.name],
order_by: [
desc_nulls_last: t.circulating_market_cap,
desc_nulls_last: t.holder_count,
asc: t.name,
asc: t.contract_address_hash
],
preload: [:contract_address]
)
end
@ -4673,25 +4683,59 @@ defmodule Explorer.Chain do
defp page_tokens(query, %PagingOptions{key: nil}), do: query
defp page_tokens(query, %PagingOptions{key: {nil, holder_count, name}}) do
defp page_tokens(query, %PagingOptions{key: {circulating_market_cap, holder_count, name, contract_address_hash}}) do
from(token in query,
where:
is_nil(token.circulating_market_cap) and
(token.holder_count < ^holder_count or (token.holder_count == ^holder_count and token.name > ^name))
where: ^page_tokens_circulating_market_cap(circulating_market_cap, holder_count, name, contract_address_hash)
)
end
defp page_tokens(query, %PagingOptions{key: {circulating_market_cap, holder_count, name}}) do
from(token in query,
where:
is_nil(token.circulating_market_cap) or
(token.circulating_market_cap < ^circulating_market_cap or
(token.circulating_market_cap == ^circulating_market_cap and token.holder_count < ^holder_count) or
(token.circulating_market_cap == ^circulating_market_cap and token.holder_count == ^holder_count and
token.name > ^name))
defp page_tokens_circulating_market_cap(nil, holder_count, name, contract_address_hash) do
dynamic(
[t],
is_nil(t.circulating_market_cap) and ^page_tokens_holder_count(holder_count, name, contract_address_hash)
)
end
defp page_tokens_circulating_market_cap(circulating_market_cap, holder_count, name, contract_address_hash) do
dynamic(
[t],
is_nil(t.circulating_market_cap) or t.circulating_market_cap < ^circulating_market_cap or
(t.circulating_market_cap == ^circulating_market_cap and
^page_tokens_holder_count(holder_count, name, contract_address_hash))
)
end
defp page_tokens_holder_count(nil, name, contract_address_hash) do
dynamic(
[t],
is_nil(t.holder_count) and ^page_tokens_name(name, contract_address_hash)
)
end
defp page_tokens_holder_count(holder_count, name, contract_address_hash) do
dynamic(
[t],
is_nil(t.holder_count) or t.holder_count < ^holder_count or
(t.holder_count == ^holder_count and ^page_tokens_name(name, contract_address_hash))
)
end
defp page_tokens_name(nil, contract_address_hash) do
dynamic([t], is_nil(t.name) and ^page_tokens_contract_address_hash(contract_address_hash))
end
defp page_tokens_name(name, contract_address_hash) do
dynamic(
[t],
is_nil(t.name) or
(t.name > ^name or (t.name == ^name and ^page_tokens_contract_address_hash(contract_address_hash)))
)
end
defp page_tokens_contract_address_hash(contract_address_hash) do
dynamic([t], t.contract_address_hash > ^contract_address_hash)
end
defp page_blocks(query, %PagingOptions{key: nil}), do: query
defp page_blocks(query, %PagingOptions{key: {block_number}}) do

Loading…
Cancel
Save