Add /api/v2/search/quick method

pull/8218/head
Nikita Pozdniakov 1 year ago
parent 14c2a7cc7e
commit 13274d6068
No known key found for this signature in database
GPG Key ID: F344106F9804FE5F
  1. 1
      CHANGELOG.md
  2. 1
      apps/block_scout_web/lib/block_scout_web/api_router.ex
  3. 14
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/search_controller.ex
  4. 7
      apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex
  5. 41
      apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs
  6. 121
      apps/explorer/lib/explorer/chain.ex
  7. 12
      apps/explorer/test/support/factory.ex

@ -4,6 +4,7 @@
### Features
- [#8218](https://github.com/blockscout/blockscout/pull/8218) - Add `/api/v2/search/quick` method
- [#8156](https://github.com/blockscout/blockscout/pull/8156) - Add `is_verified_via_admin_panel` property to tokens table
- [#8165](https://github.com/blockscout/blockscout/pull/8165), [#8201](https://github.com/blockscout/blockscout/pull/8201) - Add broadcast of updated address_current_token_balances
- [#7952](https://github.com/blockscout/blockscout/pull/7952) - Add parsing constructor arguments for sourcify contracts

@ -124,6 +124,7 @@ defmodule BlockScoutWeb.ApiRouter do
scope "/search" do
get("/", V2.SearchController, :search)
get("/check-redirect", V2.SearchController, :check_redirect)
get("/quick", V2.SearchController, :quick_search)
end
scope "/config" do

@ -3,7 +3,9 @@ defmodule BlockScoutWeb.API.V2.SearchController do
import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1, from_param: 1]
alias Explorer.Chain
alias Explorer.{Chain, PagingOptions}
@api_true [api?: true]
def search(conn, %{"q" => query} = params) do
[paging_options: paging_options] = paging_options(params)
@ -11,7 +13,7 @@ defmodule BlockScoutWeb.API.V2.SearchController do
search_results_plus_one =
paging_options
|> Chain.joint_search(offset, query, api?: true)
|> Chain.joint_search(offset, query, @api_true)
{search_results, next_page} = split_list_by_page(search_results_plus_one)
@ -32,4 +34,12 @@ defmodule BlockScoutWeb.API.V2.SearchController do
|> put_status(200)
|> render(:search_results, %{result: result})
end
def quick_search(conn, %{"q" => query}) do
search_results = Chain.balanced_unpaginated_search(%PagingOptions{page_size: 50}, query, @api_true)
conn
|> put_status(200)
|> render(:search_results, %{search_results: search_results})
end
end

@ -2,12 +2,16 @@ defmodule BlockScoutWeb.API.V2.SearchView do
use BlockScoutWeb, :view
alias BlockScoutWeb.Endpoint
alias Explorer.Chain.{Address, Block, Transaction}
alias Explorer.Chain.{Address, Block, Hash, Transaction}
def render("search_results.json", %{search_results: search_results, next_page_params: next_page_params}) do
%{"items" => Enum.map(search_results, &prepare_search_result/1), "next_page_params" => next_page_params}
end
def render("search_results.json", %{search_results: search_results}) do
Enum.map(search_results, &prepare_search_result/1)
end
def render("search_results.json", %{result: {:ok, result}}) do
Map.merge(%{"redirect" => true}, redirect_search_results(result))
end
@ -69,6 +73,7 @@ defmodule BlockScoutWeb.API.V2.SearchView do
}
end
defp hash_to_string(%Hash{bytes: bytes}), do: hash_to_string(bytes)
defp hash_to_string(hash), do: "0x" <> Base.encode16(hash, case: :lower)
defp redirect_search_results(%Address{} = item) do

@ -3,6 +3,7 @@ defmodule BlockScoutWeb.API.V2.SearchControllerTest do
alias Explorer.Chain.{Address, Block}
alias Explorer.Repo
alias Explorer.Tags.AddressTag
setup do
insert(:block)
@ -288,4 +289,44 @@ defmodule BlockScoutWeb.API.V2.SearchControllerTest do
%{"redirect" => false, "type" => nil, "parameter" => nil} = json_response(request, 200)
end
end
describe "/search/quick" do
test "check that all categories are in response list", %{conn: conn} do
name = "156000"
tags =
for _ <- 0..50 do
insert(:address_to_tag, tag: build(:address_tag, display_name: name))
end
contracts = insert_list(50, :smart_contract, name: name)
tokens = insert_list(50, :token, name: name)
blocks = [insert(:block, number: name, consensus: false), insert(:block, number: name)]
request = get(conn, "/api/v2/search/quick?q=#{name}")
assert response = json_response(request, 200)
assert Enum.count(response) == 50
assert response |> Enum.filter(fn x -> x["type"] == "label" end) |> Enum.map(fn x -> x["address"] end) ==
tags |> Enum.reverse() |> Enum.take(16) |> Enum.map(fn tag -> Address.checksum(tag.address.hash) end)
assert response |> Enum.filter(fn x -> x["type"] == "contract" end) |> Enum.map(fn x -> x["address"] end) ==
contracts
|> Enum.reverse()
|> Enum.take(16)
|> Enum.map(fn contract -> Address.checksum(contract.address_hash) end)
assert response |> Enum.filter(fn x -> x["type"] == "token" end) |> Enum.map(fn x -> x["address"] end) ==
tokens
|> Enum.reverse()
|> Enum.sort_by(fn x -> x.is_verified_via_admin_panel end, :desc)
|> Enum.take(16)
|> Enum.map(fn token -> Address.checksum(token.contract_address_hash) end)
block_hashes = response |> Enum.filter(fn x -> x["type"] == "block" end) |> Enum.map(fn x -> x["block_hash"] end)
assert block_hashes == blocks |> Enum.reverse() |> Enum.map(fn block -> to_string(block.hash) end) ||
block_hashes == blocks |> Enum.map(fn block -> to_string(block.hash) end)
end
end
end

@ -1658,6 +1658,127 @@ defmodule Explorer.Chain do
end
end
def balanced_unpaginated_search(paging_options, raw_search_query, options \\ []) do
search_query = String.trim(raw_search_query)
case prepare_search_term(search_query) do
{:some, term} ->
tokens_result =
term
|> search_token_query()
|> order_by([token],
desc_nulls_last: token.circulating_market_cap,
desc_nulls_last: token.fiat_value,
desc_nulls_last: token.is_verified_via_admin_panel,
desc_nulls_last: token.holder_count,
asc: token.name,
desc: token.inserted_at
)
|> limit(^paging_options.page_size)
|> select_repo(options).all()
contracts_result =
term
|> search_contract_query()
|> order_by([items], asc: items.name, desc: items.inserted_at)
|> limit(^paging_options.page_size)
|> select_repo(options).all()
labels_result =
term
|> search_label_query()
|> order_by([att, at], asc: at.display_name, desc: att.inserted_at)
|> limit(^paging_options.page_size)
|> select_repo(options).all()
tx_result =
if query = search_tx_query(search_query) do
query
|> select_repo(options).all()
else
[]
end
address_result =
if query = search_address_query(search_query) do
query
|> select_repo(options).all()
else
[]
end
blocks_result =
if query = search_block_query(search_query) do
query
|> limit(^paging_options.page_size)
|> select_repo(options).all()
else
[]
end
non_empty_lists =
[tokens_result, contracts_result, labels_result, tx_result, address_result, blocks_result]
|> Enum.filter(fn list -> Enum.count(list) > 0 end)
|> Enum.sort_by(fn list -> Enum.count(list) end, :asc)
to_take =
non_empty_lists
|> Enum.map(fn list -> Enum.count(list) end)
|> take_all_categories(List.duplicate(0, Enum.count(non_empty_lists)), paging_options.page_size)
non_empty_lists
|> Enum.zip_reduce(to_take, [], fn x, y, acc -> acc ++ Enum.take(x, y) end)
|> Enum.map(fn result ->
result
|> compose_result_checksummed_address_hash()
|> format_timestamp()
end)
_ ->
[]
end
end
defp take_all_categories(lengths, taken_lengths, remained) do
non_zero_count = count_non_zero(lengths)
target = if(remained < non_zero_count, do: 1, else: div(remained, non_zero_count))
{lengths_updated, %{result: taken_lengths_reversed}} =
Enum.map_reduce(lengths, %{result: [], sum: 0}, fn el, acc ->
taken =
cond do
acc[:sum] >= remained ->
0
el < target ->
el
true ->
target
end
{el - taken, %{result: [taken | acc[:result]], sum: acc[:sum] + taken}}
end)
taken_lengths =
taken_lengths
|> Enum.zip_reduce(Enum.reverse(taken_lengths_reversed), [], fn x, y, acc -> [x + y | acc] end)
|> Enum.reverse()
remained = remained - Enum.sum(taken_lengths_reversed)
if remained > 0 and count_non_zero(lengths_updated) > 0 do
take_all_categories(lengths_updated, taken_lengths, remained)
else
taken_lengths
end
end
defp count_non_zero(list) do
Enum.reduce(list, 0, fn el, acc -> acc + if el > 0, do: 1, else: 0 end)
end
defp compose_result_checksummed_address_hash(result) do
if result.address_hash do
result

@ -142,14 +142,18 @@ defmodule Explorer.Factory do
def address_to_tag_factory do
%AddressToTag{
tag: %AddressTag{
label: sequence("label"),
display_name: sequence("display_name")
},
tag: build(:address_tag),
address: build(:address)
}
end
def address_tag_factory do
%AddressTag{
label: sequence("label"),
display_name: sequence("display_name")
}
end
def account_watchlist_address_factory do
hash = build(:address).hash

Loading…
Cancel
Save