From a3e4ac723569bf3485fe58dd6d7c1e58ed5604de Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Wed, 3 Apr 2019 11:52:39 +0300 Subject: [PATCH 01/18] add postgres full text search by tokens --- apps/explorer/lib/explorer/chain.ex | 10 ++++++ ...0403080447_add_full_text_search_tokens.exs | 16 +++++++++ apps/explorer/test/explorer/chain_test.exs | 35 +++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 apps/explorer/priv/repo/migrations/20190403080447_add_full_text_search_tokens.exs diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index d882e56b51..fb4d9aeeef 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -701,6 +701,16 @@ defmodule Explorer.Chain do end end + @spec search_token(String.t()) :: [Token.t()] + def search_token(word) do + term = String.replace(word, ~r/\W/u, "") <> ":*" + + query = + from(token in Token, where: fragment("to_tsvector('english', name || ' ' || symbol) @@ to_tsquery(?)", ^term)) + + Repo.all(query) + end + @doc """ Converts `t:Explorer.Chain.Address.t/0` `hash` to the `t:Explorer.Chain.Address.t/0` with that `hash`. diff --git a/apps/explorer/priv/repo/migrations/20190403080447_add_full_text_search_tokens.exs b/apps/explorer/priv/repo/migrations/20190403080447_add_full_text_search_tokens.exs new file mode 100644 index 0000000000..4c774b7f10 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20190403080447_add_full_text_search_tokens.exs @@ -0,0 +1,16 @@ +defmodule Explorer.Repo.Migrations.AddFullTextSearchTokens do + use Ecto.Migration + + def up do + execute("CREATE EXTENSION pg_trgm") + + execute(""" + CREATE INDEX tokens_trgm_idx ON tokens USING GIN (to_tsvector('english', name || ' ' || symbol)) + """) + end + + def down do + execute("DROP INDEX tokens_trgm_idx") + execute("DROP EXTENSION pg_trgm") + end +end diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index ebb4b880dd..f2a420b142 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -3334,6 +3334,41 @@ defmodule Explorer.ChainTest do end end + describe "search_token/1" do + test "finds by part of the name" do + token = insert(:token, name: "magic token", symbol: "MAGIC") + + [result] = Chain.search_token("magic") + + assert result.contract_address_hash == token.contract_address_hash + end + + test "finds multiple results in different columns" do + insert(:token, name: "magic token", symbol: "TOKEN") + insert(:token, name: "token", symbol: "MAGIC") + + result = Chain.search_token("magic") + + assert Enum.count(result) == 2 + end + + test "do not returns wrong tokens" do + insert(:token, name: "token", symbol: "TOKEN") + + result = Chain.search_token("magic") + + assert Enum.empty?(result) + end + + test "finds record by the term in the second word" do + insert(:token, name: "token magic", symbol: "TOKEN") + + result = Chain.search_token("magic") + + assert Enum.count(result) == 1 + end + end + describe "transaction_has_token_transfers?/1" do test "returns true if transaction has token transfers" do transaction = insert(:transaction) From db00fdf45c3b288d0c3e18f790bf9b7fe6e6c9ef Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Wed, 3 Apr 2019 15:23:08 +0300 Subject: [PATCH 02/18] add token autocomplete endpoint --- .../block_scout_web/controllers/chain_controller.ex | 9 +++++++++ apps/block_scout_web/lib/block_scout_web/router.ex | 2 ++ apps/block_scout_web/mix.exs | 3 ++- .../controllers/chain_controller_test.exs | 12 +++++++++++- apps/explorer/lib/explorer/chain.ex | 5 ++++- apps/explorer/lib/explorer/chain/token.ex | 8 ++++++++ mix.lock | 1 + 7 files changed, 37 insertions(+), 3 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex index 3658045c5a..2911658d43 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex @@ -39,6 +39,15 @@ defmodule BlockScoutWeb.ChainController do end end + def token_autocomplete(conn, %{"q" => term}) do + result = + term + |> String.trim() + |> Chain.search_token() + + json(conn, result) + end + def chain_blocks(conn, _params) do if ajax?(conn) do blocks = diff --git a/apps/block_scout_web/lib/block_scout_web/router.ex b/apps/block_scout_web/lib/block_scout_web/router.ex index bf687134dd..59cf06e7c1 100644 --- a/apps/block_scout_web/lib/block_scout_web/router.ex +++ b/apps/block_scout_web/lib/block_scout_web/router.ex @@ -220,6 +220,8 @@ defmodule BlockScoutWeb.Router do get("/search", ChainController, :search) + get("/token_autocomplete", ChainController, :token_autocomplete) + get("/chain_blocks", ChainController, :chain_blocks, as: :chain_blocks) get("/api_docs", APIDocsController, :index) diff --git a/apps/block_scout_web/mix.exs b/apps/block_scout_web/mix.exs index dbaee13d48..136778582d 100644 --- a/apps/block_scout_web/mix.exs +++ b/apps/block_scout_web/mix.exs @@ -128,7 +128,8 @@ defmodule BlockScoutWeb.Mixfile do {:timex, "~> 3.4"}, {:wallaby, "~> 0.20", only: [:test], runtime: false}, # `:cowboy` `~> 2.0` and Phoenix 1.4 compatibility - {:wobserver, "~> 0.2.0", github: "KronicDeth/wobserver", ref: "99683a936c75c0a94ebb884cef019f7ed0b97112"} + {:wobserver, "~> 0.2.0", github: "KronicDeth/wobserver", ref: "99683a936c75c0a94ebb884cef019f7ed0b97112"}, + {:phoenix_form_awesomplete, "~> 0.1.4"} ] end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/chain_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/chain_controller_test.exs index 293c77590f..d76bba629c 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/chain_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/chain_controller_test.exs @@ -66,7 +66,17 @@ defmodule BlockScoutWeb.ChainControllerTest do end end - describe "GET q/2" do + describe "GET token_autocomplete/2" do + test "finds matching tokens" do + insert(:token, name: "MaGiC") + + conn = get(conn(), "/token_autocomplete?q=magic") + + Enum.count(json_response(conn, 200)) == 1 + end + end + + describe "GET search/2" do test "finds a consensus block by block number", %{conn: conn} do insert(:block, number: 37) conn = get(conn, "/search?q=37") diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index fb4d9aeeef..4e9a76371f 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -706,7 +706,10 @@ defmodule Explorer.Chain do term = String.replace(word, ~r/\W/u, "") <> ":*" query = - from(token in Token, where: fragment("to_tsvector('english', name || ' ' || symbol) @@ to_tsquery(?)", ^term)) + from(token in Token, + where: fragment("to_tsvector('english', name || ' ' || symbol) @@ to_tsquery(?)", ^term), + limit: 5 + ) Repo.all(query) end diff --git a/apps/explorer/lib/explorer/chain/token.ex b/apps/explorer/lib/explorer/chain/token.ex index cb01cdeba2..275fd73659 100644 --- a/apps/explorer/lib/explorer/chain/token.ex +++ b/apps/explorer/lib/explorer/chain/token.ex @@ -48,6 +48,14 @@ defmodule Explorer.Chain.Token do holder_count: non_neg_integer() | nil } + @derive {Poison.Encoder, + except: [ + :__meta__, + :contract_address, + :inserted_at, + :updated_at + ]} + @primary_key false schema "tokens" do field(:name, :string) diff --git a/mix.lock b/mix.lock index 25c9621cc2..7bd1a1da21 100644 --- a/mix.lock +++ b/mix.lock @@ -78,6 +78,7 @@ "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, "phoenix": {:hex, :phoenix, "1.4.0", "56fe9a809e0e735f3e3b9b31c1b749d4b436e466d8da627b8d82f90eaae714d2", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.0.0", "c43117a136e7399ea04ecaac73f8f23ee0ffe3e07acfcb8062fe5f4c9f0f6531", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, + "phoenix_form_awesomplete": {:hex, :phoenix_form_awesomplete, "0.1.4", "4af0603d8d41ca638e70f74d6defff331e4db106dd85f75f125579ca27bd8b64", [:mix], [{:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_html": {:hex, :phoenix_html, "2.13.1", "fa8f034b5328e2dfa0e4131b5569379003f34bc1fafdaa84985b0b9d2f12e68b", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.0", "3bb31a9fbd40ffe8652e60c8660dffd72dd231efcdf49b744fb75b9ef7db5dd2", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.1", "6668d787e602981f24f17a5fbb69cc98f8ab085114ebfac6cc36e10a90c8e93c", [:mix], [], "hexpm"}, From b821fa045cda07f14324f5683e832d92494023ef Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Wed, 3 Apr 2019 16:46:54 +0300 Subject: [PATCH 03/18] add autocomplete modules --- apps/block_scout_web/lib/block_scout_web.ex | 2 ++ .../block_scout_web/templates/layout/app.html.eex | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/apps/block_scout_web/lib/block_scout_web.ex b/apps/block_scout_web/lib/block_scout_web.ex index 9a4c6cab09..4e1f857526 100644 --- a/apps/block_scout_web/lib/block_scout_web.ex +++ b/apps/block_scout_web/lib/block_scout_web.ex @@ -54,6 +54,8 @@ defmodule BlockScoutWeb do Views.ScriptHelpers, WeiHelpers } + + import PhoenixFormAwesomplete end end diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex index 72ff59d213..cc089ac405 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex @@ -16,6 +16,21 @@ "> + + + + + <%= render_existing(@view_module, "_metatags.html", assigns) || render("_default_title.html") %> From 8adcd8a92f9e7b75fd53afd066dc354461211dc8 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 4 Apr 2019 12:05:37 +0300 Subject: [PATCH 04/18] add basic autocomplete --- .../controllers/chain_controller.ex | 20 +++++++++----- .../lib/block_scout_web/csp_header.ex | 4 +-- .../templates/layout/_topnav.html.eex | 27 ++++++++++++++----- apps/explorer/lib/explorer/chain.ex | 2 +- ...0403080447_add_full_text_search_tokens.exs | 2 +- 5 files changed, 38 insertions(+), 17 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex index 2911658d43..e84b0a3438 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex @@ -39,13 +39,21 @@ defmodule BlockScoutWeb.ChainController do end end - def token_autocomplete(conn, %{"q" => term}) do - result = - term - |> String.trim() - |> Chain.search_token() + def token_autocomplete(conn, %{"q" => term}) when is_binary(term) do + if term == "" do + json(conn, "{}") + else + result = + term + |> String.trim() + |> Chain.search_token() + + json(conn, result) + end + end - json(conn, result) + def token_autocomplete(conn, _) do + json(conn, "{}") end def chain_blocks(conn, _params) do diff --git a/apps/block_scout_web/lib/block_scout_web/csp_header.ex b/apps/block_scout_web/lib/block_scout_web/csp_header.ex index 199ee9f189..4b42547129 100644 --- a/apps/block_scout_web/lib/block_scout_web/csp_header.ex +++ b/apps/block_scout_web/lib/block_scout_web/csp_header.ex @@ -13,8 +13,8 @@ defmodule BlockScoutWeb.CSPHeader do "content-security-policy" => "\ connect-src 'self' #{websocket_endpoints(conn)}; \ default-src 'self';\ - script-src 'self' 'unsafe-inline' 'unsafe-eval';\ - style-src 'self' 'unsafe-inline' 'unsafe-eval' https://fonts.googleapis.com;\ + script-src 'self' 'unsafe-inline' 'unsafe-eval' http://nico-amsterdam.github.io;\ + style-src 'self' 'unsafe-inline' 'unsafe-eval' https://fonts.googleapis.com http://nico-amsterdam.github.io;\ img-src 'self' 'unsafe-inline' 'unsafe-eval' data:;\ font-src 'self' 'unsafe-inline' 'unsafe-eval' https://fonts.gstatic.com data:;\ " diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex index 1eaffa3834..c030870c22 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex @@ -78,14 +78,27 @@
- <%= form_for @conn, chain_path(@conn, :search), [class: "form-inline my-2 my-lg-0", method: :get, enforce_utf8: false], fn f -> %> + <%= form_for @conn, chain_path(@conn, :search), [class: "form-inline my-2 my-lg-0", method: :get, enforce_utf8: false], fn f -> %>
- <%= search_input f, :q, class: 'form-control mr-auto', placeholder: gettext("Search by address, token symbol name, transaction hash, or block number"), "aria-describedby": "search-icon", "aria-label": gettext("Search"), "data-test": "search_input" %> -
- -
+ <%= awesomplete(f, :q, + [ + class: "form-control me auto", + placeholder: gettext("Search by address, token symbol name, transaction hash, or block number"), + "aria-describedby": "search-icon", + "aria-label": gettext("Search"), + "data-test": "search_input" + ], + [ url: "#{chain_path(@conn, :token_autocomplete)}?q=", + prepop: true, + minChars: 3, + maxItems: 8, + value: "name" + ]) %> +
+ +
<% end %> diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 4e9a76371f..152a4ecf8a 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -707,7 +707,7 @@ defmodule Explorer.Chain do query = from(token in Token, - where: fragment("to_tsvector('english', name || ' ' || symbol) @@ to_tsquery(?)", ^term), + where: fragment("to_tsvector('english', symbol || ' ' || name ) @@ to_tsquery(?)", ^term), limit: 5 ) diff --git a/apps/explorer/priv/repo/migrations/20190403080447_add_full_text_search_tokens.exs b/apps/explorer/priv/repo/migrations/20190403080447_add_full_text_search_tokens.exs index 4c774b7f10..a4d642f54e 100644 --- a/apps/explorer/priv/repo/migrations/20190403080447_add_full_text_search_tokens.exs +++ b/apps/explorer/priv/repo/migrations/20190403080447_add_full_text_search_tokens.exs @@ -5,7 +5,7 @@ defmodule Explorer.Repo.Migrations.AddFullTextSearchTokens do execute("CREATE EXTENSION pg_trgm") execute(""" - CREATE INDEX tokens_trgm_idx ON tokens USING GIN (to_tsvector('english', name || ' ' || symbol)) + CREATE INDEX tokens_trgm_idx ON tokens USING GIN (to_tsvector('english', symbol || ' ' || name)) """) end From 841e77feb30486e5f404cbe5f5c51fabe3c094f2 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 4 Apr 2019 13:09:42 +0300 Subject: [PATCH 05/18] add contract_address_hash description --- .../lib/block_scout_web/templates/layout/_topnav.html.eex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex index c030870c22..efa8d100e0 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex @@ -92,7 +92,9 @@ prepop: true, minChars: 3, maxItems: 8, - value: "name" + value: "symbol", + descr: "contract_address_hash" + ]) %>