create endpoing with csv

pull/2206/head
Ayrat Badykov 6 years ago
parent 4bfc37ba2c
commit f984c9129e
No known key found for this signature in database
GPG Key ID: B44668E265E9396F
  1. 21
      apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex
  2. 2
      apps/block_scout_web/lib/block_scout_web/router.ex
  3. 18
      apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs
  4. 100
      apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex
  5. 52
      apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs

@ -10,6 +10,7 @@ defmodule BlockScoutWeb.AddressTransactionController do
alias BlockScoutWeb.TransactionView alias BlockScoutWeb.TransactionView
alias Explorer.{Chain, Market} alias Explorer.{Chain, Market}
alias Explorer.Chain.AddressTransactionCsvExporter
alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Token
alias Indexer.Fetcher.CoinBalanceOnDemand alias Indexer.Fetcher.CoinBalanceOnDemand
alias Phoenix.View alias Phoenix.View
@ -106,4 +107,24 @@ defmodule BlockScoutWeb.AddressTransactionController do
not_found(conn) not_found(conn)
end end
end end
def transactions_csv(conn, %{"address_id" => address_hash_string}) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do
address
|> AddressTransactionCsvExporter.export()
|> Enum.into(
conn
|> put_resp_content_type("application/csv")
|> put_resp_header("content-disposition", "attachment; filename=transactions.csv")
|> send_chunked(200)
)
else
:error ->
unprocessable_entity(conn)
{:error, :not_found} ->
not_found(conn)
end
end
end end

@ -240,6 +240,8 @@ defmodule BlockScoutWeb.Router do
get("/search_logs", AddressLogsController, :search_logs) get("/search_logs", AddressLogsController, :search_logs)
get("/transactions_csv", AddressTransactionController, :transactions_csv)
get("/token_autocomplete", ChainController, :token_autocomplete) get("/token_autocomplete", ChainController, :token_autocomplete)
get("/chain_blocks", ChainController, :chain_blocks, as: :chain_blocks) get("/chain_blocks", ChainController, :chain_blocks, as: :chain_blocks)

@ -132,4 +132,22 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do
end) end)
end end
end end
describe "GET transactions_csv/2" do
test "download csv file with transactions", %{conn: conn} do
address = insert(:address)
:transaction
|> insert(from_address: address)
|> with_block()
:transaction
|> insert(from_address: address)
|> with_block()
conn = get(conn, "/transactions_csv", %{"address_id" => to_string(address.hash)})
assert conn.resp_body |> String.split("\n") |> Enum.count() == 3
end
end
end end

@ -31,14 +31,14 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do
@paging_options %PagingOptions{page_size: @page_size + 1} @paging_options %PagingOptions{page_size: @page_size + 1}
@spec export(Address.t()) :: String.t() @spec export(Address.t()) :: Enumerable.t()
def export(address) do def export(address) do
exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null() exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null()
address address
|> fetch_all_transactions(@paging_options) |> fetch_all_transactions(@paging_options)
|> to_csv_format(address, exchange_rate) |> to_csv_format(address, exchange_rate)
|> dump_data_to_csv() |> dump_to_stream()
end end
defp fetch_all_transactions(address, paging_options, acc \\ []) do defp fetch_all_transactions(address, paging_options, acc \\ []) do
@ -58,61 +58,53 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do
end end
end end
defp dump_data_to_csv(transactions) do defp dump_to_stream(transactions) do
{:ok, path} = Briefly.create() transactions
|> RFC4180.dump_to_stream()
_ =
transactions
|> RFC4180.dump_to_stream()
|> Stream.into(File.stream!(path))
|> Enum.to_list()
path
end end
defp to_csv_format(transactions, address, exchange_rate) do defp to_csv_format(transactions, address, exchange_rate) do
# , "ETHCurrentPrice", "ETHPriceAtTxDate", "TxFee", "Status", "ErrCode"] # row_names = [
row_names = [ # "TxHash",
"TxHash", # "BlockNumber",
"BlockNumber", # "UnixTimestamp",
"UnixTimestamp", # "FromAddress",
"FromAddress", # "ToAddress",
"ToAddress", # "ContractAddress",
"ContractAddress", # "Type",
"Type", # "Value",
"Value", # "Fee",
"Fee", # "Status",
"Status", # "ErrCode",
"ErrCode", # "CurrentPrice",
"CurrentPrice", # "TxDateOpeningPrice",
"TxDateOpeningPrice", # "TxDateClosingPrice"
"TxDateClosingPrice" # ]
]
# transaction_lists =
transaction_lists = transactions
transactions |> Stream.map(fn transaction ->
|> Stream.map(fn transaction -> {opening_price, closing_price} = price_at_date(transaction.block.timestamp)
{opening_price, closing_price} = price_at_date(transaction.block.timestamp)
[
[ to_string(transaction.hash),
to_string(transaction.hash), transaction.block_number,
transaction.block_number, transaction.block.timestamp,
transaction.block.timestamp, to_string(transaction.from_address),
to_string(transaction.from_address), to_string(transaction.to_address),
to_string(transaction.to_address), to_string(transaction.created_contract_address),
to_string(transaction.created_contract_address), type(transaction, address),
type(transaction, address), Wei.to(transaction.value, :wei),
Wei.to(transaction.value, :wei), fee(transaction),
fee(transaction), transaction.status,
transaction.status, transaction.error,
transaction.error, exchange_rate.usd_value,
exchange_rate.usd_value, opening_price,
opening_price, closing_price
closing_price ]
] end)
end)
# Stream.concat([row_names], transaction_lists)
Stream.concat([row_names], transaction_lists)
end end
defp type(%Transaction{from_address_hash: from_address}, %Address{hash: from_address}), do: "OUT" defp type(%Transaction{from_address_hash: from_address}, %Address{hash: from_address}), do: "OUT"

@ -16,24 +16,37 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do
[result] = [result] =
address address
|> AddressTransactionCsvExporter.export() |> AddressTransactionCsvExporter.export()
|> File.stream!() |> Enum.to_list()
|> NimbleCSV.RFC4180.parse_stream() |> Enum.map(fn [
|> Stream.map(fn [ hash,
hash, _,
block_number, block_number,
timestamp, _,
from_address, timestamp,
to_address, _,
created_address, from_address,
type, _,
value, to_address,
fee, _,
status, created_address,
error, _,
cur_price, type,
op_price, _,
cl_price value,
] -> _,
fee,
_,
status,
_,
error,
_,
cur_price,
_,
op_price,
_,
cl_price,
_
] ->
%{ %{
hash: hash, hash: hash,
block_number: block_number, block_number: block_number,
@ -51,7 +64,6 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do
closing_price: cl_price closing_price: cl_price
} }
end) end)
|> Enum.to_list()
assert result.block_number == to_string(transaction.block_number) assert result.block_number == to_string(transaction.block_number)
assert result.timestamp assert result.timestamp
@ -83,8 +95,6 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do
result = result =
address address
|> AddressTransactionCsvExporter.export() |> AddressTransactionCsvExporter.export()
|> File.stream!()
|> NimbleCSV.RFC4180.parse_stream()
|> Enum.to_list() |> Enum.to_list()
assert Enum.count(result) == 200 assert Enum.count(result) == 200

Loading…
Cancel
Save