commit
5b5db39e62
@ -0,0 +1,25 @@ |
|||||||
|
import $ from 'jquery' |
||||||
|
import humps from 'humps' |
||||||
|
import numeral from 'numeral' |
||||||
|
import socket from '../socket' |
||||||
|
|
||||||
|
function tryUpdateIndexedStatus (el, indexedRatio = el.dataset.indexedRatio, indexingFinished = false) { |
||||||
|
if (indexingFinished) return $("[data-selector='indexed-status']").remove() |
||||||
|
const blocksPercentComplete = numeral(indexedRatio).format('0%') |
||||||
|
let indexedText |
||||||
|
if (blocksPercentComplete === '100%') { |
||||||
|
indexedText = window.localized['Indexing Tokens'] |
||||||
|
} else { |
||||||
|
indexedText = `${blocksPercentComplete} ${window.localized['Blocks Indexed']}` |
||||||
|
} |
||||||
|
if (indexedText !== el.innerHTML) el.innerHTML = indexedText |
||||||
|
} |
||||||
|
|
||||||
|
export function updateIndexStatus (msg = {}) { |
||||||
|
$('[data-indexed-ratio]').each((i, el) => tryUpdateIndexedStatus(el, msg.ratio, msg.finished)) |
||||||
|
} |
||||||
|
updateIndexStatus() |
||||||
|
|
||||||
|
const indexingChannel = socket.channel(`blocks:indexing`) |
||||||
|
indexingChannel.join() |
||||||
|
indexingChannel.on('index_status', (msg) => updateIndexStatus(humps.camelizeKeys(msg))) |
@ -0,0 +1,19 @@ |
|||||||
|
defmodule BlockScoutWeb.AppPage do |
||||||
|
@moduledoc false |
||||||
|
|
||||||
|
use Wallaby.DSL |
||||||
|
|
||||||
|
import Wallaby.Query, only: [css: 1, css: 2] |
||||||
|
|
||||||
|
def visit_page(session) do |
||||||
|
visit(session, "/") |
||||||
|
end |
||||||
|
|
||||||
|
def indexed_status(text) do |
||||||
|
css("[data-selector='indexed-status'] [data-indexed-ratio]", text: text) |
||||||
|
end |
||||||
|
|
||||||
|
def still_indexing?() do |
||||||
|
css("[data-selector='indexed-status']") |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,78 @@ |
|||||||
|
defmodule BlockScoutWeb.ViewingAppTest do |
||||||
|
@moduledoc false |
||||||
|
|
||||||
|
use BlockScoutWeb.FeatureCase, async: true |
||||||
|
|
||||||
|
alias BlockScoutWeb.{AppPage, Notifier} |
||||||
|
|
||||||
|
describe "loading bar when indexing" do |
||||||
|
test "shows blocks indexed percentage", %{session: session} do |
||||||
|
for index <- 6..10 do |
||||||
|
insert(:block, number: index) |
||||||
|
end |
||||||
|
|
||||||
|
session |
||||||
|
|> AppPage.visit_page() |
||||||
|
|> assert_has(AppPage.indexed_status("50% Blocks Indexed")) |
||||||
|
end |
||||||
|
|
||||||
|
test "shows tokens loading", %{session: session} do |
||||||
|
for index <- 1..10 do |
||||||
|
insert(:block, number: index) |
||||||
|
end |
||||||
|
|
||||||
|
session |
||||||
|
|> AppPage.visit_page() |
||||||
|
|> assert_has(AppPage.indexed_status("Indexing Tokens")) |
||||||
|
end |
||||||
|
|
||||||
|
test "live updates blocks indexed percentage", %{session: session} do |
||||||
|
for index <- 6..10 do |
||||||
|
insert(:block, number: index) |
||||||
|
end |
||||||
|
|
||||||
|
session |
||||||
|
|> AppPage.visit_page() |
||||||
|
|> assert_has(AppPage.indexed_status("50% Blocks Indexed")) |
||||||
|
|
||||||
|
insert(:block, number: 5) |
||||||
|
Notifier.handle_event({:chain_event, :blocks, :catchup, []}) |
||||||
|
|
||||||
|
assert_has(session, AppPage.indexed_status("60% Blocks Indexed")) |
||||||
|
end |
||||||
|
|
||||||
|
test "live updates when blocks are fully indexed", %{session: session} do |
||||||
|
for index <- 2..10 do |
||||||
|
insert(:block, number: index) |
||||||
|
end |
||||||
|
|
||||||
|
session |
||||||
|
|> AppPage.visit_page() |
||||||
|
|> assert_has(AppPage.indexed_status("90% Blocks Indexed")) |
||||||
|
|
||||||
|
insert(:block, number: 1) |
||||||
|
Notifier.handle_event({:chain_event, :blocks, :catchup, []}) |
||||||
|
|
||||||
|
assert_has(session, AppPage.indexed_status("Indexing Tokens")) |
||||||
|
end |
||||||
|
|
||||||
|
test "live removes message when chain is indexed", %{session: session} do |
||||||
|
[block | _] = |
||||||
|
for index <- 1..10 do |
||||||
|
insert(:block, number: index) |
||||||
|
end |
||||||
|
|
||||||
|
session |
||||||
|
|> AppPage.visit_page() |
||||||
|
|> assert_has(AppPage.indexed_status("Indexing Tokens")) |
||||||
|
|
||||||
|
:transaction |
||||||
|
|> insert() |
||||||
|
|> with_block(block, internal_transactions_indexed_at: DateTime.utc_now()) |
||||||
|
|
||||||
|
Notifier.handle_event({:chain_event, :blocks, :catchup, []}) |
||||||
|
|
||||||
|
refute_has(session, AppPage.still_indexing?()) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,94 @@ |
|||||||
|
defmodule Explorer.Counters.BlockValidationCounter do |
||||||
|
use GenServer |
||||||
|
|
||||||
|
@moduledoc """ |
||||||
|
Module responsible for fetching and consolidating the number of |
||||||
|
validations from an address. |
||||||
|
""" |
||||||
|
|
||||||
|
alias Explorer.Chain |
||||||
|
alias Explorer.Chain.Hash |
||||||
|
|
||||||
|
@table :block_validation_counter |
||||||
|
|
||||||
|
def table_name do |
||||||
|
@table |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Creates a process to continually monitor the validation counts. |
||||||
|
""" |
||||||
|
@spec start_link(term()) :: GenServer.on_start() |
||||||
|
def start_link(_) do |
||||||
|
GenServer.start_link(__MODULE__, :ok, name: __MODULE__) |
||||||
|
end |
||||||
|
|
||||||
|
## Server |
||||||
|
@impl true |
||||||
|
def init(args) do |
||||||
|
create_table() |
||||||
|
|
||||||
|
Task.start_link(&consolidate_blocks/0) |
||||||
|
|
||||||
|
Chain.subscribe_to_events(:blocks) |
||||||
|
|
||||||
|
{:ok, args} |
||||||
|
end |
||||||
|
|
||||||
|
def create_table do |
||||||
|
opts = [ |
||||||
|
:set, |
||||||
|
:named_table, |
||||||
|
:public, |
||||||
|
read_concurrency: true, |
||||||
|
write_concurrency: true |
||||||
|
] |
||||||
|
|
||||||
|
:ets.new(table_name(), opts) |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Consolidates the number of block validations grouped by `address_hash`. |
||||||
|
""" |
||||||
|
def consolidate_blocks do |
||||||
|
total_block_validations = Chain.group_block_validations_by_address() |
||||||
|
|
||||||
|
for {address_hash, total} <- total_block_validations do |
||||||
|
insert_or_update_counter(address_hash, total) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Fetches the number of validations related to an `address_hash`. |
||||||
|
""" |
||||||
|
@spec fetch(Hash.Address.t()) :: non_neg_integer |
||||||
|
def fetch(addr_hash) do |
||||||
|
do_fetch(:ets.lookup(table_name(), to_string(addr_hash))) |
||||||
|
end |
||||||
|
|
||||||
|
defp do_fetch([{_, result} | _]), do: result |
||||||
|
defp do_fetch([]), do: 0 |
||||||
|
|
||||||
|
@impl true |
||||||
|
def handle_info({:chain_event, :blocks, _type, blocks}, state) do |
||||||
|
blocks |
||||||
|
|> Enum.map(& &1.miner_hash) |
||||||
|
|> Enum.each(&insert_or_update_counter(&1, 1)) |
||||||
|
|
||||||
|
{:noreply, state} |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Inserts a new item into the `:ets` table. |
||||||
|
|
||||||
|
When the record exist, the counter will be incremented by one. When the |
||||||
|
record does not exist, the counter will be inserted with a default value. |
||||||
|
""" |
||||||
|
@spec insert_or_update_counter(Hash.Address.t(), non_neg_integer) :: term() |
||||||
|
def insert_or_update_counter(addr_hash, number) do |
||||||
|
string_addr = to_string(addr_hash) |
||||||
|
default = {string_addr, 0} |
||||||
|
|
||||||
|
:ets.update_counter(table_name(), string_addr, number, default) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,39 @@ |
|||||||
|
defmodule Explorer.Counters.BlockValidationCounterTest do |
||||||
|
use Explorer.DataCase |
||||||
|
|
||||||
|
alias Explorer.Counters.BlockValidationCounter |
||||||
|
|
||||||
|
describe "consolidate/0" do |
||||||
|
test "loads the address' validations consolidated info" do |
||||||
|
address = insert(:address) |
||||||
|
|
||||||
|
insert(:block, miner: address, miner_hash: address.hash) |
||||||
|
insert(:block, miner: address, miner_hash: address.hash) |
||||||
|
|
||||||
|
another_address = insert(:address) |
||||||
|
|
||||||
|
insert(:block, miner: another_address, miner_hash: another_address.hash) |
||||||
|
|
||||||
|
BlockValidationCounter.consolidate_blocks() |
||||||
|
|
||||||
|
assert BlockValidationCounter.fetch(address.hash) == 2 |
||||||
|
assert BlockValidationCounter.fetch(another_address.hash) == 1 |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
describe "fetch/1" do |
||||||
|
test "fetches the total block validations by a given address" do |
||||||
|
address = insert(:address) |
||||||
|
another_address = insert(:address) |
||||||
|
|
||||||
|
assert BlockValidationCounter.fetch(address.hash) == 0 |
||||||
|
assert BlockValidationCounter.fetch(another_address.hash) == 0 |
||||||
|
|
||||||
|
BlockValidationCounter.insert_or_update_counter(address.hash, 1) |
||||||
|
BlockValidationCounter.insert_or_update_counter(another_address.hash, 10) |
||||||
|
|
||||||
|
assert BlockValidationCounter.fetch(address.hash) == 1 |
||||||
|
assert BlockValidationCounter.fetch(another_address.hash) == 10 |
||||||
|
end |
||||||
|
end |
||||||
|
end |
Loading…
Reference in new issue