Add search backend

pull/42/head
Derek Barnes, Desmond Bowe and Matt Olenick 7 years ago committed by Derek Barnes and Matt Olenick
parent 70b00ed17a
commit 989ee117b5
  1. 50
      lib/explorer/resource.ex
  2. 25
      lib/explorer_web/controllers/chain_controller.ex
  3. 1
      lib/explorer_web/router.ex
  4. 5
      lib/explorer_web/templates/layout/_header.html.eex
  5. 45
      test/explorer/resource_test.exs
  6. 25
      test/explorer_web/controllers/chain_controller_test.exs
  7. 30
      test/explorer_web/features/contributor_browsing_test.exs
  8. 2
      test/support/factories/address_factory.ex
  9. 2
      test/support/factories/transaction_factory.ex

@ -0,0 +1,50 @@
defmodule Explorer.Resource do
@moduledoc "Looks up and fetches resource based on its handle (either an id or hash)"
import Ecto.Query, only: [from: 2]
alias Explorer.Block
alias Explorer.BlockForm
alias Explorer.Address
alias Explorer.Repo.NewRelic, as: Repo
alias Explorer.Transaction
def lookup(hash) when byte_size(hash) > 42, do: fetch_transaction(hash)
def lookup(hash) when byte_size(hash) == 42, do: fetch_address(hash)
def lookup(number), do: fetch_block(number)
def fetch_address(hash) do
query = from address in Address,
where: fragment("lower(?)", address.hash) == ^String.downcase(hash),
limit: 1
Repo.one(query)
end
def fetch_transaction(hash) do
query = from transaction in Transaction,
where: fragment("lower(?)", transaction.hash) == ^String.downcase(hash),
limit: 1
Repo.one(query)
end
def fetch_block(block_number) when is_bitstring(block_number) do
case Integer.parse(block_number) do
{number, ""} -> fetch_block(number)
_ -> nil
end
end
def fetch_block(number) when is_integer(number) do
query = from b in Block,
where: b.number == ^number,
limit: 1
Repo.one(query)
end
end

@ -2,8 +2,33 @@ defmodule ExplorerWeb.ChainController do
use ExplorerWeb, :controller use ExplorerWeb, :controller
alias Explorer.Servers.ChainStatistics alias Explorer.Servers.ChainStatistics
alias Explorer.Resource
def show(conn, _params) do def show(conn, _params) do
render(conn, "show.html", chain: ChainStatistics.fetch()) render(conn, "show.html", chain: ChainStatistics.fetch())
end end
def search(conn, %{"q" => query}) do
case Resource.lookup(query) do
nil ->
conn
|> render(ExplorerWeb.ErrorView, "404.html")
item ->
redirect_search_results(conn, item)
end
end
defp redirect_search_results(conn, %Explorer.Block{} = item) do
redirect conn, to: block_path(conn, :show, Gettext.get_locale, item.number)
end
defp redirect_search_results(conn, %Explorer.Transaction{} = item) do
redirect conn, to: transaction_path(
conn, :show, Gettext.get_locale, item.hash
)
end
defp redirect_search_results(conn, %Explorer.Address{} = item) do
redirect conn, to: address_path(conn, :show, Gettext.get_locale, item.hash)
end
end end

@ -75,5 +75,6 @@ defmodule ExplorerWeb.Router do
resources "/transactions_from", AddressTransactionFromController, resources "/transactions_from", AddressTransactionFromController,
only: [:index], as: :transaction_from only: [:index], as: :transaction_from
end end
get "/search", ChainController, :search
end end
end end

@ -6,6 +6,11 @@
<%= logo_image(@conn, alt: gettext("POA Network Explorer"), class: "header__logo") %> <%= logo_image(@conn, alt: gettext("POA Network Explorer"), class: "header__logo") %>
</a> </a>
</td> </td>
<td class="header__cell header__cell--search">
<%= form_for @conn, chain_path(@conn, :search, Gettext.get_locale), [class: "search-box", method: :get, enforce_utf8: false], fn f -> %>
<%= search_input f, :q, class: 'search-box__input' %>
<% end %>
</td>
<td class="header__cell header__cell--links" align="right"> <td class="header__cell header__cell--links" align="right">
<a href="<%= block_path(@conn, :index, Gettext.get_locale) %>" class="header__link"> <a href="<%= block_path(@conn, :index, Gettext.get_locale) %>" class="header__link">
<img class="header__link-image" src="<%= static_path(@conn, "/images/block.svg") %>" /> <img class="header__link-image" src="<%= static_path(@conn, "/images/block.svg") %>" />

@ -0,0 +1,45 @@
defmodule Explorer.ResourceTest do
use Explorer.DataCase
alias Explorer.Resource
describe "lookup/1" do
test "finds a block by block number with a valid block number" do
insert(:block, number: 37)
block = Resource.lookup("37")
assert block.number == 37
end
test "finds a transaction by hash" do
transaction = insert(:transaction)
resource = Resource.lookup(transaction.hash)
assert transaction.hash == resource.hash
end
test "finds an address by hash" do
address = insert(:address)
resource = Resource.lookup(address.hash)
assert address.hash == resource.hash
end
test "returns nil when garbage is passed in" do
item = Resource.lookup("any ol' thing")
assert is_nil(item)
end
test "returns nil when it does not find a match" do
transaction_hash = String.pad_trailing("0xnonsense", 43, "0")
address_hash = String.pad_trailing("0xbaddress", 42, "0")
assert is_nil(Resource.lookup("38999"))
assert is_nil(Resource.lookup(transaction_hash))
assert is_nil(Resource.lookup(address_hash))
end
end
end

@ -1,7 +1,7 @@
defmodule ExplorerWeb.ChainControllerTest do defmodule ExplorerWeb.ChainControllerTest do
use ExplorerWeb.ConnCase use ExplorerWeb.ConnCase
import ExplorerWeb.Router.Helpers, only: [chain_path: 3] import ExplorerWeb.Router.Helpers, only: [chain_path: 3, block_path: 4, transaction_path: 4, address_path: 4]
describe "GET index/2 without a locale" do describe "GET index/2 without a locale" do
test "redirects to the en locale", %{conn: conn} do test "redirects to the en locale", %{conn: conn} do
@ -53,4 +53,27 @@ defmodule ExplorerWeb.ChainControllerTest do
assert(List.first(conn.assigns.chain.transactions).hash == "0xDECAFBAD") assert(List.first(conn.assigns.chain.transactions).hash == "0xDECAFBAD")
end end
end end
describe "GET q/2" do
test "finds a block by block number", %{conn: conn} do
insert(:block, number: 37)
conn = get conn, "/en/search?q=37"
assert redirected_to(conn) == block_path(conn, :show, "en", "37")
end
test "finds a transaction by hash", %{conn: conn} do
transaction = insert(:transaction) |> with_block() |> with_addresses
conn = get conn, "/en/search?q=#{transaction.hash}"
assert redirected_to(conn) == transaction_path(conn, :show, "en", transaction.hash)
end
test "finds an address by hash", %{conn: conn} do
address = insert(:address)
conn = get conn, "en/search?q=#{address.hash}"
assert redirected_to(conn) == address_path(conn, :show, "en", address.hash)
end
end
end end

@ -18,6 +18,36 @@ defmodule ExplorerWeb.UserListTest do
|> assert_has(css("main", text: "Blocks")) |> assert_has(css("main", text: "Blocks"))
end end
test "search for blocks", %{session: session} do
insert(:block, number: 42, miner: "mittens")
session
|> visit("/")
|> fill_in(css(".search-box__input"), with: "42")
|> send_keys([:enter])
|> assert_has(css(".block__item", text: "mittens"))
end
test "search for transactions", %{session: session} do
insert(:transaction, hash: "0xdeadbeef000000000000000000000000000000000", input: "socks") |> with_addresses()
session
|> visit("/")
|> fill_in(css(".search-box__input"), with: "0xdeadbeef000000000000000000000000000000000")
|> send_keys([:enter])
|> assert_has(css(".transaction__item", text: "socks"))
end
test "search for address", %{session: session} do
insert(:address, hash: "0xBAADF00D00000000000000000000000000000000")
session
|> visit("/")
|> fill_in(css(".search-box__input"), with: "0xBAADF00D00000000000000000000000000000000")
|> send_keys([:enter])
|> assert_has(css(".address__subheading", text: "0xBAADF00D00000000000000000000000000000000"))
end
test "views blocks", %{session: session} do test "views blocks", %{session: session} do
insert_list(4, :block, %{number: 1, timestamp: Timex.now |> Timex.shift(hours: -1), gas_used: 10}) insert_list(4, :block, %{number: 1, timestamp: Timex.now |> Timex.shift(hours: -1), gas_used: 10})
fifth_block = insert(:block, %{ fifth_block = insert(:block, %{

@ -3,7 +3,7 @@ defmodule Explorer.AddressFactory do
quote do quote do
def address_factory do def address_factory do
%Explorer.Address{ %Explorer.Address{
hash: sequence("0x"), hash: String.pad_trailing(sequence("0x"), 42, "address")
} }
end end
end end

@ -7,7 +7,7 @@ defmodule Explorer.TransactionFactory do
def transaction_factory do def transaction_factory do
%Explorer.Transaction{ %Explorer.Transaction{
hash: sequence("0x"), hash: String.pad_trailing(sequence("0x"), 43, "action"),
value: Enum.random(1..100_000), value: Enum.random(1..100_000),
gas: Enum.random(21_000..100_000), gas: Enum.random(21_000..100_000),
gas_price: Enum.random(10..99) * 1_000_000_00, gas_price: Enum.random(10..99) * 1_000_000_00,

Loading…
Cancel
Save