From 989ee117b596f12ebe4ad4d2ee410be76b555e51 Mon Sep 17 00:00:00 2001 From: "Derek Barnes, Desmond Bowe and Matt Olenick" Date: Thu, 22 Feb 2018 15:18:38 -0800 Subject: [PATCH] Add search backend --- lib/explorer/resource.ex | 50 +++++++++++++++++++ .../controllers/chain_controller.ex | 25 ++++++++++ lib/explorer_web/router.ex | 1 + .../templates/layout/_header.html.eex | 5 ++ test/explorer/resource_test.exs | 45 +++++++++++++++++ .../controllers/chain_controller_test.exs | 25 +++++++++- .../features/contributor_browsing_test.exs | 30 +++++++++++ test/support/factories/address_factory.ex | 2 +- test/support/factories/transaction_factory.ex | 2 +- 9 files changed, 182 insertions(+), 3 deletions(-) create mode 100644 lib/explorer/resource.ex create mode 100644 test/explorer/resource_test.exs diff --git a/lib/explorer/resource.ex b/lib/explorer/resource.ex new file mode 100644 index 0000000000..a991755a1e --- /dev/null +++ b/lib/explorer/resource.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 diff --git a/lib/explorer_web/controllers/chain_controller.ex b/lib/explorer_web/controllers/chain_controller.ex index fd9c440001..433c1e5a90 100644 --- a/lib/explorer_web/controllers/chain_controller.ex +++ b/lib/explorer_web/controllers/chain_controller.ex @@ -2,8 +2,33 @@ defmodule ExplorerWeb.ChainController do use ExplorerWeb, :controller alias Explorer.Servers.ChainStatistics + alias Explorer.Resource def show(conn, _params) do render(conn, "show.html", chain: ChainStatistics.fetch()) 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 diff --git a/lib/explorer_web/router.ex b/lib/explorer_web/router.ex index c4525d69db..dcaca42237 100644 --- a/lib/explorer_web/router.ex +++ b/lib/explorer_web/router.ex @@ -75,5 +75,6 @@ defmodule ExplorerWeb.Router do resources "/transactions_from", AddressTransactionFromController, only: [:index], as: :transaction_from end + get "/search", ChainController, :search end end diff --git a/lib/explorer_web/templates/layout/_header.html.eex b/lib/explorer_web/templates/layout/_header.html.eex index b76e6d1e17..9cd76dc672 100644 --- a/lib/explorer_web/templates/layout/_header.html.eex +++ b/lib/explorer_web/templates/layout/_header.html.eex @@ -6,6 +6,11 @@ <%= logo_image(@conn, alt: gettext("POA Network Explorer"), class: "header__logo") %> + + <%= 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 %> + " /> diff --git a/test/explorer/resource_test.exs b/test/explorer/resource_test.exs new file mode 100644 index 0000000000..e38ab5a2c9 --- /dev/null +++ b/test/explorer/resource_test.exs @@ -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 diff --git a/test/explorer_web/controllers/chain_controller_test.exs b/test/explorer_web/controllers/chain_controller_test.exs index 3055ceb791..1a06843575 100644 --- a/test/explorer_web/controllers/chain_controller_test.exs +++ b/test/explorer_web/controllers/chain_controller_test.exs @@ -1,7 +1,7 @@ defmodule ExplorerWeb.ChainControllerTest do 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 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") 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 diff --git a/test/explorer_web/features/contributor_browsing_test.exs b/test/explorer_web/features/contributor_browsing_test.exs index 21a48e9344..c5a1d10439 100644 --- a/test/explorer_web/features/contributor_browsing_test.exs +++ b/test/explorer_web/features/contributor_browsing_test.exs @@ -18,6 +18,36 @@ defmodule ExplorerWeb.UserListTest do |> assert_has(css("main", text: "Blocks")) 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 insert_list(4, :block, %{number: 1, timestamp: Timex.now |> Timex.shift(hours: -1), gas_used: 10}) fifth_block = insert(:block, %{ diff --git a/test/support/factories/address_factory.ex b/test/support/factories/address_factory.ex index 2b9fd56692..59e4ac5012 100644 --- a/test/support/factories/address_factory.ex +++ b/test/support/factories/address_factory.ex @@ -3,7 +3,7 @@ defmodule Explorer.AddressFactory do quote do def address_factory do %Explorer.Address{ - hash: sequence("0x"), + hash: String.pad_trailing(sequence("0x"), 42, "address") } end end diff --git a/test/support/factories/transaction_factory.ex b/test/support/factories/transaction_factory.ex index 1de23398ae..dde9f38325 100644 --- a/test/support/factories/transaction_factory.ex +++ b/test/support/factories/transaction_factory.ex @@ -7,7 +7,7 @@ defmodule Explorer.TransactionFactory do def transaction_factory do %Explorer.Transaction{ - hash: sequence("0x"), + hash: String.pad_trailing(sequence("0x"), 43, "action"), value: Enum.random(1..100_000), gas: Enum.random(21_000..100_000), gas_price: Enum.random(10..99) * 1_000_000_00,