Merge pull request #1116 from poanetwork/wsa-async-load-in-token-transfers-at-adress-page

Async load in token transfers at adress page
pull/1148/head
Andrew Cravenho 6 years ago committed by GitHub
commit b949a1df99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 59
      apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex
  2. 53
      apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex
  3. 7
      apps/block_scout_web/priv/gettext/default.pot
  4. 7
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  5. 159
      apps/block_scout_web/test/block_scout_web/controllers/address_token_transfer_controller_test.exs
  6. 1
      apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs

@ -1,8 +1,10 @@
defmodule BlockScoutWeb.AddressTokenTransferController do defmodule BlockScoutWeb.AddressTokenTransferController do
use BlockScoutWeb, :controller use BlockScoutWeb, :controller
alias Explorer.{Chain, Market} alias BlockScoutWeb.TransactionView
alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Token
alias Explorer.{Chain, Market}
alias Phoenix.View
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1] import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
@ -11,12 +13,16 @@ defmodule BlockScoutWeb.AddressTokenTransferController do
def index( def index(
conn, conn,
%{"address_id" => address_hash_string, "address_token_id" => token_hash_string} = params %{
"address_id" => address_hash_string,
"address_token_id" => token_hash_string,
"type" => "JSON"
} = params
) do ) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, token_hash} <- Chain.string_to_address_hash(token_hash_string), {:ok, token_hash} <- Chain.string_to_address_hash(token_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash), {:ok, address} <- Chain.hash_to_address(address_hash),
{:ok, token} <- Chain.token_from_address_hash(token_hash) do {:ok, _} <- Chain.token_from_address_hash(token_hash) do
transactions = transactions =
Chain.address_to_transactions_with_token_transfers( Chain.address_to_transactions_with_token_transfers(
address_hash, address_hash,
@ -26,15 +32,58 @@ defmodule BlockScoutWeb.AddressTokenTransferController do
{transactions_paginated, next_page} = split_list_by_page(transactions) {transactions_paginated, next_page} = split_list_by_page(transactions)
next_page_path =
case next_page_params(next_page, transactions_paginated, params) do
nil ->
nil
next_page_params ->
address_token_transfers_path(
conn,
:index,
address_hash_string,
token_hash_string,
Map.delete(next_page_params, "type")
)
end
transfers_json =
Enum.map(transactions_paginated, fn transaction ->
View.render_to_string(
TransactionView,
"_tile.html",
conn: conn,
transaction: transaction,
current_address: address
)
end)
json(conn, %{items: transfers_json, next_page_path: next_page_path})
else
:error ->
unprocessable_entity(conn)
{:error, :not_found} ->
not_found(conn)
end
end
def index(
conn,
%{"address_id" => address_hash_string, "address_token_id" => token_hash_string}
) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, token_hash} <- Chain.string_to_address_hash(token_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash),
{:ok, token} <- Chain.token_from_address_hash(token_hash) do
render( render(
conn, conn,
"index.html", "index.html",
address: address, address: address,
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
next_page_params: next_page_params(next_page, transactions_paginated, params), current_path: current_path(conn),
token: token, token: token,
transaction_count: transaction_count(address), transaction_count: transaction_count(address),
transactions: transactions_paginated,
validation_count: validation_count(address) validation_count: validation_count(address)
) )
else else

@ -7,41 +7,36 @@
<%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %> <%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %>
</div> </div>
<div class="card-body"> <div data-async-listing="<%= @current_path %>" class="card-body">
<h2 class="card-title"> <h2 class="card-title">
<span class="text-muted"><%= gettext "Tokens" %></span> / <%= token_name(@token) %> <span class="text-muted"><%= gettext "Tokens" %></span> / <%= token_name(@token) %>
</h2> </h2>
<div data-loading-message class="tile tile-muted text-center mt-3">
<%= if Enum.any?(@transactions) do %> <span class="loading-spinner-small mr-2">
<span data-selector="transactions-list"> <span class="loading-spinner-block-1"></span>
<%= for transaction <- @transactions do %> <span class="loading-spinner-block-2"></span>
<%= render(
BlockScoutWeb.TransactionView,
"_tile.html",
transaction: transaction,
current_address: @address
) %>
<% end %>
</span> </span>
<% else %> <%= gettext("Loading...") %>
<div class="tile tile-muted text-center"> </div>
<div data-empty-response-message class="tile tile-muted text-center" style="display: none;">
<span><%= gettext "There are no token transfers for this address." %></span> <span><%= gettext "There are no token transfers for this address." %></span>
</div> </div>
<% end %> <button data-error-message class="alert alert-danger col-12 text-left" style="display: none;">
<span class="alert-link">
<%= if @next_page_params do %> <%= gettext "Something went wrong, click to reload." %>
<%= link( </span>
gettext("Next"), </button>
class: "button button-secondary button-sm float-right mt-3", <div data-items></div>
to: address_token_transfers_path( <a data-next-page-button href="#" class="button button-secondary button-small float-right mt-4" style="display: none;">
@conn, <%= gettext("Older") %>
:index, </a>
@address.hash, <div data-loading-button class="button button-secondary button-small float-right mt-4" style="display: none;">
@token.contract_address_hash, <span class="loading-spinner-small mr-2">
@next_page_params <span class="loading-spinner-block-1"></span>
) <span class="loading-spinner-block-2"></span>
) %> </span>
<% end %> <%= gettext("Loading") %>...
</div>
</div> </div>
</div> </div>
</section> </section>

@ -610,7 +610,6 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_token/index.html.eex:25 #: lib/block_scout_web/templates/address_token/index.html.eex:25
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:34
msgid "Next" msgid "Next"
msgstr "" msgstr ""
@ -639,6 +638,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:76 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:76
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:31
#: lib/block_scout_web/templates/address_validation/index.html.eex:117 #: lib/block_scout_web/templates/address_validation/index.html.eex:117
#: lib/block_scout_web/templates/block/index.html.eex:30 #: lib/block_scout_web/templates/block/index.html.eex:30
#: lib/block_scout_web/templates/block_transaction/index.html.eex:50 #: lib/block_scout_web/templates/block_transaction/index.html.eex:50
@ -831,7 +831,7 @@ msgid "There are no logs for this transaction."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:28 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:22
msgid "There are no token transfers for this address." msgid "There are no token transfers for this address."
msgstr "" msgstr ""
@ -1187,6 +1187,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_read_contract/index.html.eex:17 #: lib/block_scout_web/templates/address_read_contract/index.html.eex:17
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:19
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:25 #: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:25
msgid "Loading..." msgid "Loading..."
msgstr "" msgstr ""
@ -1216,6 +1217,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:72 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:72
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:83 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:83
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:38
#: lib/block_scout_web/templates/address_transaction/index.html.eex:61 #: lib/block_scout_web/templates/address_transaction/index.html.eex:61
#: lib/block_scout_web/templates/block/index.html.eex:22 #: lib/block_scout_web/templates/block/index.html.eex:22
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:33 #: lib/block_scout_web/templates/pending_transaction/index.html.eex:33
@ -1412,6 +1414,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:60 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:60
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:26
#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:21 #: lib/block_scout_web/templates/tokens/transfer/index.html.eex:21
msgid "Something went wrong, click to reload." msgid "Something went wrong, click to reload."
msgstr "" msgstr ""

@ -610,7 +610,6 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_token/index.html.eex:25 #: lib/block_scout_web/templates/address_token/index.html.eex:25
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:34
msgid "Next" msgid "Next"
msgstr "" msgstr ""
@ -639,6 +638,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:76 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:76
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:31
#: lib/block_scout_web/templates/address_validation/index.html.eex:117 #: lib/block_scout_web/templates/address_validation/index.html.eex:117
#: lib/block_scout_web/templates/block/index.html.eex:30 #: lib/block_scout_web/templates/block/index.html.eex:30
#: lib/block_scout_web/templates/block_transaction/index.html.eex:50 #: lib/block_scout_web/templates/block_transaction/index.html.eex:50
@ -831,7 +831,7 @@ msgid "There are no logs for this transaction."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:28 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:22
msgid "There are no token transfers for this address." msgid "There are no token transfers for this address."
msgstr "" msgstr ""
@ -1187,6 +1187,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_read_contract/index.html.eex:17 #: lib/block_scout_web/templates/address_read_contract/index.html.eex:17
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:19
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:25 #: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:25
msgid "Loading..." msgid "Loading..."
msgstr "" msgstr ""
@ -1216,6 +1217,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:72 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:72
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:83 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:83
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:38
#: lib/block_scout_web/templates/address_transaction/index.html.eex:61 #: lib/block_scout_web/templates/address_transaction/index.html.eex:61
#: lib/block_scout_web/templates/block/index.html.eex:22 #: lib/block_scout_web/templates/block/index.html.eex:22
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:33 #: lib/block_scout_web/templates/pending_transaction/index.html.eex:33
@ -1412,6 +1414,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:60 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:60
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:26
#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:21 #: lib/block_scout_web/templates/tokens/transfer/index.html.eex:21
msgid "Something went wrong, click to reload." msgid "Something went wrong, click to reload."
msgstr "" msgstr ""

@ -1,7 +1,8 @@
defmodule BlockScoutWeb.AddressTokenTransferControllerTest do defmodule BlockScoutWeb.AddressTokenTransferControllerTest do
use BlockScoutWeb.ConnCase use BlockScoutWeb.ConnCase
import BlockScoutWeb.Router.Helpers, only: [address_token_transfers_path: 4] import BlockScoutWeb.Router.Helpers,
only: [address_token_transfers_path: 4, address_token_transfers_path: 5]
alias Explorer.Chain.{Address, Token} alias Explorer.Chain.{Address, Token}
@ -15,6 +16,7 @@ defmodule BlockScoutWeb.AddressTokenTransferControllerTest do
test "with invalid token hash", %{conn: conn} do test "with invalid token hash", %{conn: conn} do
address_hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" address_hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"
conn = get(conn, address_token_transfers_path(conn, :index, address_hash, "invalid_address")) conn = get(conn, address_token_transfers_path(conn, :index, address_hash, "invalid_address"))
assert html_response(conn, 422) assert html_response(conn, 422)
@ -35,25 +37,33 @@ defmodule BlockScoutWeb.AddressTokenTransferControllerTest do
assert html_response(conn, 404) assert html_response(conn, 404)
end end
end
describe "GET index/2 JSON" do
test "without token transfers for a token", %{conn: conn} do test "without token transfers for a token", %{conn: conn} do
%Address{hash: address_hash} = insert(:address) %Address{hash: address_hash} = insert(:address)
%Token{contract_address_hash: token_hash} = insert(:token) %Token{contract_address_hash: token_hash} = insert(:token)
conn = get(conn, address_token_transfers_path(conn, :index, address_hash, token_hash)) conn =
get(conn, address_token_transfers_path(conn, :index, address_hash, token_hash), %{
type: "JSON"
})
assert html_response(conn, 200) assert json_response(conn, 200) == %{"items" => [], "next_page_path" => nil}
assert conn.assigns.transactions == []
end end
test "returns the transactions that have token transfers for the given address and token", %{conn: conn} do test "returns the correct number of transactions", %{conn: conn} do
address = insert(:address) address = insert(:address)
token = insert(:token) token = insert(:token)
inserted_transactions =
Enum.map(1..5, fn index ->
block = insert(:block, number: 1000 - index)
transaction = transaction =
:transaction :transaction
|> insert() |> insert()
|> with_block() |> with_block(block)
insert( insert(
:token_transfer, :token_transfer,
@ -62,19 +72,68 @@ defmodule BlockScoutWeb.AddressTokenTransferControllerTest do
token_contract_address: token.contract_address token_contract_address: token.contract_address
) )
conn = get(conn, address_token_transfers_path(conn, :index, address.hash, token.contract_address_hash)) transaction
end)
conn =
get(
conn,
address_token_transfers_path(conn, :index, address.hash, token.contract_address_hash),
%{type: "JSON"}
)
response_items =
conn
|> json_response(200)
|> Map.get("items")
items_length = length(response_items)
assert items_length == 5
assert Enum.all?(inserted_transactions, fn transaction ->
Enum.any?(response_items, fn item ->
String.contains?(
item,
"data-transaction-hash=\"#{to_string(transaction.hash)}\""
)
end)
end)
end
test "returns next_page_path as null when there are no more pages", %{conn: conn} do
address = insert(:address)
token = insert(:token)
block = insert(:block, number: 1000)
transaction =
:transaction
|> insert()
|> with_block(block)
insert(
:token_transfer,
to_address: address,
transaction: transaction,
token_contract_address: token.contract_address
)
transaction_hashes = Enum.map(conn.assigns.transactions, & &1.hash) conn =
get(
conn,
address_token_transfers_path(conn, :index, address.hash, token.contract_address_hash),
%{type: "JSON"}
)
assert html_response(conn, 200) assert Map.get(json_response(conn, 200), "next_page_path") == nil
assert transaction_hashes == [transaction.hash]
end end
test "returns next page of results based on last seen transactions", %{conn: conn} do test "returns next_page_path when there are more items", %{conn: conn} do
address = insert(:address) address = insert(:address)
token = insert(:token) token = insert(:token)
second_page_transactions = page_last_transfer =
1..50 1..50
|> Enum.map(fn index -> |> Enum.map(fn index ->
block = insert(:block, number: 1000 - index) block = insert(:block, number: 1000 - index)
@ -93,22 +152,84 @@ defmodule BlockScoutWeb.AddressTokenTransferControllerTest do
transaction transaction
end) end)
|> Enum.map(& &1.hash) |> List.last()
Enum.each(51..60, fn index ->
block = insert(:block, number: 1000 - index)
transaction = transaction =
:transaction :transaction
|> insert() |> insert()
|> with_block(insert(:block, number: 1002)) |> with_block(block)
insert(
:token_transfer,
to_address: address,
transaction: transaction,
token_contract_address: token.contract_address
)
end)
conn =
get(
conn,
address_token_transfers_path(conn, :index, address.hash, token.contract_address_hash),
%{type: "JSON"}
)
expected_path =
address_token_transfers_path(conn, :index, address.hash, token.contract_address_hash, %{
block_number: page_last_transfer.block_number,
index: page_last_transfer.index
})
assert Map.get(json_response(conn, 200), "next_page_path") == expected_path
end
test "with invalid address hash", %{conn: conn} do
token_hash = "0xc8982771dd50285389c352c175ada74d074427c7"
conn =
get(conn, address_token_transfers_path(conn, :index, "invalid_address", token_hash), %{
type: "JSON"
})
assert html_response(conn, 422)
end
test "with invalid token hash", %{conn: conn} do
address_hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"
conn =
get(conn, address_token_transfers_path(conn, :index, address_hash, "invalid_address"), %{
type: "JSON"
})
assert html_response(conn, 422)
end
test "with an address that doesn't exist in our database", %{conn: conn} do
address_hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"
%Token{contract_address_hash: token_hash} = insert(:token)
conn = conn =
get(conn, address_token_transfers_path(conn, :index, address.hash, token.contract_address_hash), %{ get(conn, address_token_transfers_path(conn, :index, address_hash, token_hash), %{
"block_number" => Integer.to_string(transaction.block_number), type: "JSON"
"index" => Integer.to_string(transaction.index)
}) })
actual_transactions = Enum.map(conn.assigns.transactions, & &1.hash) assert html_response(conn, 404)
end
assert second_page_transactions == actual_transactions test "with a token that doesn't exist in our database", %{conn: conn} do
%Address{hash: address_hash} = insert(:address)
token_hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"
conn =
get(conn, address_token_transfers_path(conn, :index, address_hash, token_hash), %{
type: "JSON"
})
assert html_response(conn, 404)
end end
end end
end end

@ -364,6 +364,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
end end
describe "viewing token transfers from a specific token" do describe "viewing token transfers from a specific token" do
@tag :skip
test "list token transfers related to the address", %{ test "list token transfers related to the address", %{
addresses: addresses, addresses: addresses,
block: block, block: block,

Loading…
Cancel
Save