From edec3a1575fe21083f87aa256658467d026cfd97 Mon Sep 17 00:00:00 2001 From: Valentin Syrovatskiy Date: Sat, 19 Jun 2021 04:25:09 +0300 Subject: [PATCH] [4274] Fix/improve search token-autocomplete --- .../controllers/chain_controller.ex | 20 +---- .../controllers/chain_controller_test.exs | 32 +++++++ apps/explorer/lib/explorer/chain.ex | 87 +++++++++++-------- 3 files changed, 86 insertions(+), 53 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 38c5207433..338c9096e0 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 @@ -94,23 +94,11 @@ defmodule BlockScoutWeb.ChainController do def search(conn, _), do: not_found(conn) def token_autocomplete(conn, %{"q" => term}) when is_binary(term) do - if term == "" do - json(conn, "{}") - else - result_tokens = - term - |> String.trim() - |> Chain.search_token() - - result_contracts = - term - |> String.trim() - |> Chain.search_contract() + result_tokens = Chain.search_token(term) + result_contracts = Chain.search_contract(term) + result = result_tokens ++ result_contracts - result = result_tokens ++ result_contracts - - json(conn, result) - end + json(conn, result) end def token_autocomplete(conn, _) do 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 72796af6cf..1c6985bd81 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 @@ -128,6 +128,38 @@ defmodule BlockScoutWeb.ChainControllerTest do assert Enum.count(json_response(conn, 200)) == 4 end + + test "find by several words" do + insert(:token, name: "first Token") + insert(:token, name: "second Token") + + conn = + build_conn() + |> get("/token-autocomplete?q=fir+tok") + + assert Enum.count(json_response(conn, 200)) == 1 + end + + test "find by empty query" do + insert(:token, name: "MaGiCt0k3n") + insert(:smart_contract, name: "MagicContract") + + conn = + build_conn() + |> get("/token-autocomplete?q=") + + assert Enum.count(json_response(conn, 200)) == 0 + end + + test "find by non-latin characters" do + insert(:token, name: "someToken") + + conn = + build_conn() + |> get("/token-autocomplete?q=%E0%B8%B5%E0%B8%AB") + + assert Enum.count(json_response(conn, 200)) == 0 + end end describe "GET search/2" do diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 780cc9a872..ccc72d88a8 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -1083,51 +1083,64 @@ defmodule Explorer.Chain do end end - @spec search_token(String.t()) :: [Token.t()] - def search_token(word) do - term = - word - |> String.replace(~r/[^a-zA-Z0-9]/, " ") - |> String.replace(~r/ +/, " & ") + defp prepare_search_term(string) do + case Regex.scan(~r/[a-zA-Z0-9]+/, string) do + [_ | _] = words -> + term_final = + words + |> Enum.map(fn [word] -> word <> ":*" end) + |> Enum.join(" & ") - term_final = term <> ":*" + {:some, term_final} - query = - from(token in Token, - where: fragment("to_tsvector('english', symbol || ' ' || name ) @@ to_tsquery(?)", ^term_final), - select: %{ - contract_address_hash: token.contract_address_hash, - symbol: token.symbol, - name: - fragment( - "'' || coalesce(?, '') || '' || ' (' || coalesce(?, '') || ') ' || '' || coalesce(?::varchar(255), '') || ' holder(s)' || ''", - token.name, - token.symbol, - token.holder_count - ) - }, - order_by: [desc: token.holder_count] - ) + _ -> + :none + end + end - Repo.all(query) + @spec search_token(String.t()) :: [Token.t()] + def search_token(string) do + case prepare_search_term(string) do + {:some, term} -> + query = + from(token in Token, + where: fragment("to_tsvector('english', symbol || ' ' || name ) @@ to_tsquery(?)", ^term), + select: %{ + contract_address_hash: token.contract_address_hash, + symbol: token.symbol, + name: + fragment( + "'' || coalesce(?, '') || '' || ' (' || coalesce(?, '') || ') ' || '' || coalesce(?::varchar(255), '') || ' holder(s)' || ''", + token.name, + token.symbol, + token.holder_count + ) + }, + order_by: [desc: token.holder_count] + ) + + Repo.all(query) + + _ -> + [] + end end @spec search_contract(String.t()) :: [SmartContract.t()] - def search_contract(word) do - term = - word - |> String.replace(~r/[^a-zA-Z0-9]/, " ") - |> String.replace(~r/ +/, " & ") + def search_contract(string) do + case prepare_search_term(string) do + {:some, term} -> + query = + from(smart_contract in SmartContract, + where: fragment("to_tsvector('english', name ) @@ to_tsquery(?)", ^term), + select: %{contract_address_hash: smart_contract.address_hash, name: smart_contract.name} + ) - term_final = term <> ":*" + Repo.all(query) - query = - from(smart_contract in SmartContract, - where: fragment("to_tsvector('english', name ) @@ to_tsquery(?)", ^term_final), - select: %{contract_address_hash: smart_contract.address_hash, name: smart_contract.name} - ) - - Repo.all(query) + _ -> + [] + end end @doc """