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