diff --git a/apps/block_scout_web/assets/README.md b/apps/block_scout_web/assets/README.md
new file mode 100644
index 0000000000..c4e098cbee
--- /dev/null
+++ b/apps/block_scout_web/assets/README.md
@@ -0,0 +1,9 @@
+# Javascript structure files
+
+## lib
+
+* This folder is used to place `component` files, that may span in multiple pages.
+
+## pages
+
+* This folder is used to place `page` specific files, that won't be reusable in other locations.
diff --git a/apps/block_scout_web/assets/js/pages/chain.js b/apps/block_scout_web/assets/js/pages/chain.js
index 185c52021b..c7dfe2ce86 100644
--- a/apps/block_scout_web/assets/js/pages/chain.js
+++ b/apps/block_scout_web/assets/js/pages/chain.js
@@ -17,6 +17,8 @@ export const initialState = {
averageBlockTime: null,
marketHistoryData: null,
blocks: [],
+ blocksLoading: true,
+ blocksError: false,
transactions: [],
transactionsBatch: [],
transactionsError: false,
@@ -52,6 +54,18 @@ function baseReducer (state = initialState, action) {
})
}
}
+ case 'START_BLOCKS_FETCH': {
+ return Object.assign({}, state, { blocksError: false, blocksLoading: true })
+ }
+ case 'BLOCKS_FINISH_REQUEST': {
+ return Object.assign({}, state, { blocksLoading: false })
+ }
+ case 'BLOCKS_FETCHED': {
+ return Object.assign({}, state, { blocks: [...action.msg.blocks] })
+ }
+ case 'BLOCKS_REQUEST_ERROR': {
+ return Object.assign({}, state, { blocksError: true })
+ }
case 'RECEIVED_NEW_EXCHANGE_RATE': {
return Object.assign({}, state, {
availableSupply: action.msg.exchangeRate.availableSupply,
@@ -159,17 +173,36 @@ const elements = {
'[data-selector="chain-block-list"]': {
load ($el) {
return {
- blocks: $el.children().map((index, el) => ({
- blockNumber: parseInt(el.dataset.blockNumber),
- chainBlockHtml: el.outerHTML
- })).toArray()
+ blocksPath: $el[0].dataset.url
}
},
render ($el, state, oldState) {
if (oldState.blocks === state.blocks) return
+
const container = $el[0]
- const newElements = _.map(state.blocks, ({ chainBlockHtml }) => $(chainBlockHtml)[0])
- listMorph(container, newElements, { key: 'dataset.blockNumber', horizontal: true })
+
+ if (state.blocksLoading === false) {
+ const blocks = _.map(state.blocks, ({ chainBlockHtml }) => $(chainBlockHtml)[0])
+ listMorph(container, blocks, { key: 'dataset.blockNumber', horizontal: true })
+ }
+ }
+ },
+ '[data-selector="chain-block-list"] [data-selector="error-message"]': {
+ render ($el, state, oldState) {
+ if (state.blocksError) {
+ $el.show()
+ } else {
+ $el.hide()
+ }
+ }
+ },
+ '[data-selector="chain-block-list"] [data-selector="loading-message"]': {
+ render ($el, state, oldState) {
+ if (state.blocksLoading) {
+ $el.show()
+ } else {
+ $el.hide()
+ }
}
},
'[data-selector="transactions-list"] [data-selector="error-message"]': {
@@ -207,8 +240,12 @@ const $chainDetailsPage = $('[data-page="chain-details"]')
if ($chainDetailsPage.length) {
const store = createStore(reducer)
connectElements({ store, elements })
+
loadTransactions(store)
- $('[data-selector="transactions-list"] [data-selector="error-message"]').on('click', _event => loadTransactions(store))
+ bindTransactionErrorMessage(store)
+
+ loadBlocks(store)
+ bindBlockErrorMessage(store)
exchangeRateChannel.on('new_rate', (msg) => store.dispatch({
type: 'RECEIVED_NEW_EXCHANGE_RATE',
@@ -246,6 +283,10 @@ function loadTransactions (store) {
.always(() => store.dispatch({type: 'FINISH_TRANSACTIONS_FETCH'}))
}
+function bindTransactionErrorMessage (store) {
+ $('[data-selector="transactions-list"] [data-selector="error-message"]').on('click', _event => loadTransactions(store))
+}
+
export function placeHolderBlock (blockNumber) {
return `
`
}
+
+function loadBlocks (store) {
+ const url = store.getState().blocksPath
+
+ store.dispatch({type: 'START_BLOCKS_FETCH'})
+
+ $.getJSON(url)
+ .done(response => {
+ store.dispatch({type: 'BLOCKS_FETCHED', msg: humps.camelizeKeys(response)})
+ })
+ .fail(() => store.dispatch({type: 'BLOCKS_REQUEST_ERROR'}))
+ .always(() => store.dispatch({type: 'BLOCKS_FINISH_REQUEST'}))
+}
+
+function bindBlockErrorMessage (store) {
+ $('[data-selector="chain-block-list"] [data-selector="error-message"]').on('click', _event => loadBlocks(store))
+}
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 568fa3040c..2a27bff4b6 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
@@ -1,17 +1,14 @@
defmodule BlockScoutWeb.ChainController do
use BlockScoutWeb, :controller
+ alias BlockScoutWeb.ChainView
alias Explorer.{Chain, PagingOptions, Repo}
alias Explorer.Chain.{Address, Block, Transaction}
alias Explorer.ExchangeRates.Token
alias Explorer.Market
+ alias Phoenix.View
def show(conn, _params) do
- blocks =
- [paging_options: %PagingOptions{page_size: 4}]
- |> Chain.list_blocks()
- |> Repo.preload([[miner: :names], :transactions, :rewards])
-
transaction_estimated_count = Chain.transaction_estimated_count()
exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null()
@@ -27,7 +24,6 @@ defmodule BlockScoutWeb.ChainController do
"show.html",
address_count: Chain.count_addresses_with_balance_from_cache(),
average_block_time: Chain.average_block_time(),
- blocks: blocks,
exchange_rate: exchange_rate,
available_supply: available_supply(Chain.supply_for_days(30), exchange_rate),
market_history_data: market_history_data,
@@ -49,6 +45,30 @@ defmodule BlockScoutWeb.ChainController do
end
end
+ def chain_blocks(conn, _params) do
+ if ajax?(conn) do
+ blocks =
+ [paging_options: %PagingOptions{page_size: 4}]
+ |> Chain.list_blocks()
+ |> Repo.preload([[miner: :names], :transactions, :rewards])
+ |> Enum.map(fn block ->
+ %{
+ chain_block_html:
+ View.render_to_string(
+ ChainView,
+ "_block.html",
+ block: block
+ ),
+ block_number: block.number
+ }
+ end)
+
+ json(conn, %{blocks: blocks})
+ else
+ unprocessable_entity(conn)
+ end
+ end
+
defp redirect_search_results(conn, %Address{} = item) do
redirect(conn, to: address_path(conn, :show, item))
end
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 4a589b1193..bd297e6c33 100644
--- a/apps/block_scout_web/lib/block_scout_web/router.ex
+++ b/apps/block_scout_web/lib/block_scout_web/router.ex
@@ -206,6 +206,8 @@ defmodule BlockScoutWeb.Router do
get("/search", ChainController, :search)
+ get("/chain_blocks", ChainController, :chain_blocks, as: :chain_blocks)
+
get("/api_docs", APIDocsController, :index)
end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex
index b05091828b..ad7c697f4c 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex
@@ -55,10 +55,19 @@
<%= link(gettext("View All Blocks →"), to: block_path(BlockScoutWeb.Endpoint, :index), class: "button button-secondary button-xsmall float-right") %>
<%= gettext "Blocks" %>
-
- <%= for block <- @blocks do %>
- <%= render BlockScoutWeb.ChainView, "_block.html", block: block %>
- <% end %>
+
+
+
+
+
+
+
+ <%= gettext("Loading...") %>
+
diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot
index 5e0b85aea5..afe4821af4 100644
--- a/apps/block_scout_web/priv/gettext/default.pot
+++ b/apps/block_scout_web/priv/gettext/default.pot
@@ -574,7 +574,7 @@ msgid "More internal transactions have come in"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/chain/show.html.eex:72
+#: lib/block_scout_web/templates/chain/show.html.eex:81
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:14
#: lib/block_scout_web/templates/transaction/index.html.eex:14
msgid "More transactions have come in"
@@ -985,7 +985,7 @@ msgstr ""
#: lib/block_scout_web/templates/block_transaction/index.html.eex:23
#: lib/block_scout_web/templates/block_transaction/index.html.eex:26
#: lib/block_scout_web/templates/block_transaction/index.html.eex:35
-#: lib/block_scout_web/templates/chain/show.html.eex:69
+#: lib/block_scout_web/templates/chain/show.html.eex:78
#: lib/block_scout_web/templates/layout/_topnav.html.eex:35
#: lib/block_scout_web/views/address_view.ex:253
msgid "Transactions"
@@ -1065,7 +1065,7 @@ msgid "View All Blocks →"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/chain/show.html.eex:68
+#: lib/block_scout_web/templates/chain/show.html.eex:77
msgid "View All Transactions →"
msgstr ""
@@ -1189,7 +1189,8 @@ msgstr ""
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:19
#: lib/block_scout_web/templates/address_validation/index.html.eex:63
#: lib/block_scout_web/templates/address_validation/index.html.eex:82
-#: lib/block_scout_web/templates/chain/show.html.eex:86
+#: lib/block_scout_web/templates/chain/show.html.eex:69
+#: lib/block_scout_web/templates/chain/show.html.eex:95
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:25
msgid "Loading..."
msgstr ""
@@ -1415,6 +1416,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:26
#: lib/block_scout_web/templates/address_transaction/index.html.eex:55
#: lib/block_scout_web/templates/address_validation/index.html.eex:70
+#: lib/block_scout_web/templates/chain/show.html.eex:61
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:23
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:23
#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:21
@@ -1535,6 +1537,6 @@ msgid "Emission Contract"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/chain/show.html.eex:78
+#: lib/block_scout_web/templates/chain/show.html.eex:87
msgid "Something went wrong, click to retry."
msgstr ""
diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
index b6a91afd7a..41d0f4e0c2 100644
--- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
+++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
@@ -574,7 +574,7 @@ msgid "More internal transactions have come in"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/chain/show.html.eex:72
+#: lib/block_scout_web/templates/chain/show.html.eex:81
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:14
#: lib/block_scout_web/templates/transaction/index.html.eex:14
msgid "More transactions have come in"
@@ -985,7 +985,7 @@ msgstr ""
#: lib/block_scout_web/templates/block_transaction/index.html.eex:23
#: lib/block_scout_web/templates/block_transaction/index.html.eex:26
#: lib/block_scout_web/templates/block_transaction/index.html.eex:35
-#: lib/block_scout_web/templates/chain/show.html.eex:69
+#: lib/block_scout_web/templates/chain/show.html.eex:78
#: lib/block_scout_web/templates/layout/_topnav.html.eex:35
#: lib/block_scout_web/views/address_view.ex:253
msgid "Transactions"
@@ -1065,7 +1065,7 @@ msgid "View All Blocks →"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/chain/show.html.eex:68
+#: lib/block_scout_web/templates/chain/show.html.eex:77
msgid "View All Transactions →"
msgstr ""
@@ -1189,7 +1189,8 @@ msgstr ""
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:19
#: lib/block_scout_web/templates/address_validation/index.html.eex:63
#: lib/block_scout_web/templates/address_validation/index.html.eex:82
-#: lib/block_scout_web/templates/chain/show.html.eex:86
+#: lib/block_scout_web/templates/chain/show.html.eex:69
+#: lib/block_scout_web/templates/chain/show.html.eex:95
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:25
msgid "Loading..."
msgstr ""
@@ -1415,6 +1416,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:26
#: lib/block_scout_web/templates/address_transaction/index.html.eex:55
#: lib/block_scout_web/templates/address_validation/index.html.eex:70
+#: lib/block_scout_web/templates/chain/show.html.eex:61
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:23
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:23
#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:21
@@ -1535,6 +1537,6 @@ msgid "Emission Contract"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/chain/show.html.eex:78
+#: lib/block_scout_web/templates/chain/show.html.eex:87
msgid "Something went wrong, click to retry."
msgstr ""
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 7fd54c4c60..e58db4c249 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
@@ -8,63 +8,71 @@ defmodule BlockScoutWeb.ChainControllerTest do
alias Explorer.Chain.{Block, Hash}
alias Explorer.Counters.AddressesWithBalanceCounter
+ setup do
+ start_supervised!(AddressesWithBalanceCounter)
+ AddressesWithBalanceCounter.consolidate()
+
+ :ok
+ end
+
describe "GET index/2" do
test "returns a welcome message", %{conn: conn} do
- start_supervised!(AddressesWithBalanceCounter)
- AddressesWithBalanceCounter.consolidate()
-
conn = get(conn, chain_path(BlockScoutWeb.Endpoint, :show))
assert(html_response(conn, 200) =~ "POA")
end
- test "returns a block", %{conn: conn} do
+ test "returns a block" do
insert(:block, %{number: 23})
- start_supervised!(AddressesWithBalanceCounter)
- AddressesWithBalanceCounter.consolidate()
+ conn =
+ build_conn()
+ |> put_req_header("x-requested-with", "xmlhttprequest")
+ |> get("/chain_blocks")
- conn = get(conn, "/")
+ response = json_response(conn, 200)
- assert(List.first(conn.assigns.blocks).number == 23)
+ assert(List.first(response["blocks"])["block_number"] == 23)
end
- test "excludes all but the most recent five blocks", %{conn: conn} do
+ test "excludes all but the most recent five blocks" do
old_block = insert(:block)
insert_list(5, :block)
- start_supervised!(AddressesWithBalanceCounter)
- AddressesWithBalanceCounter.consolidate()
+ conn =
+ build_conn()
+ |> put_req_header("x-requested-with", "xmlhttprequest")
+ |> get("/chain_blocks")
- conn = get(conn, "/")
+ response = json_response(conn, 200)
- refute(Enum.member?(conn.assigns.blocks, old_block))
+ refute(Enum.member?(response["blocks"], old_block))
end
test "returns market history data", %{conn: conn} do
today = Date.utc_today()
for day <- -40..0, do: insert(:market_history, date: Date.add(today, day))
- start_supervised!(AddressesWithBalanceCounter)
- AddressesWithBalanceCounter.consolidate()
-
conn = get(conn, "/")
assert Map.has_key?(conn.assigns, :market_history_data)
assert length(conn.assigns.market_history_data) == 30
end
- test "displays miner primary address names", %{conn: conn} do
+ test "displays miner primary address names" do
miner_name = "POA Miner Pool"
%{address: miner_address} = insert(:address_name, name: miner_name, primary: true)
insert(:block, miner: miner_address, miner_hash: nil)
- start_supervised!(AddressesWithBalanceCounter)
- AddressesWithBalanceCounter.consolidate()
+ conn =
+ build_conn()
+ |> put_req_header("x-requested-with", "xmlhttprequest")
+ |> get("/chain_blocks")
+
+ response = List.first(json_response(conn, 200)["blocks"])
- conn = get(conn, chain_path(conn, :show))
- assert html_response(conn, 200) =~ miner_name
+ assert response["chain_block_html"] =~ miner_name
end
end