diff --git a/apps/block_scout_web/assets/__tests__/pages/chain.js b/apps/block_scout_web/assets/__tests__/pages/chain.js
index a27487756d..e7f69fc861 100644
--- a/apps/block_scout_web/assets/__tests__/pages/chain.js
+++ b/apps/block_scout_web/assets/__tests__/pages/chain.js
@@ -214,8 +214,8 @@ test('RECEIVED_NEW_EXCHANGE_RATE', () => {
})
describe('RECEIVED_NEW_TRANSACTION_BATCH', () => {
- test('single transaction', () => {
- const state = initialState
+ test('single transaction with no loading or errors', () => {
+ const state = Object.assign(initialState, { transactionsLoading: false, transactionError: false } )
const action = {
type: 'RECEIVED_NEW_TRANSACTION_BATCH',
msgs: [{
@@ -228,6 +228,34 @@ describe('RECEIVED_NEW_TRANSACTION_BATCH', () => {
expect(output.transactionsBatch.length).toEqual(0)
expect(output.transactionCount).toEqual(1)
})
+ test('single transaction with error loading first transactions', () => {
+ const state = Object.assign({}, initialState, { transactionsError: true } )
+ const action = {
+ type: 'RECEIVED_NEW_TRANSACTION_BATCH',
+ msgs: [{
+ transactionHtml: 'test'
+ }]
+ }
+ const output = reducer(state, action)
+
+ expect(output.transactions).toEqual([])
+ expect(output.transactionsBatch.length).toEqual(0)
+ expect(output.transactionCount).toEqual(1)
+ })
+ test('single transaction while loading', () => {
+ const state = Object.assign({}, initialState, { transactionsLoading: true } )
+ const action = {
+ type: 'RECEIVED_NEW_TRANSACTION_BATCH',
+ msgs: [{
+ transactionHtml: 'test'
+ }]
+ }
+ const output = reducer(state, action)
+
+ expect(output.transactions).toEqual([])
+ expect(output.transactionsBatch.length).toEqual(0)
+ expect(output.transactionCount).toEqual(1)
+ })
test('large batch of transactions', () => {
const state = initialState
const action = {
diff --git a/apps/block_scout_web/assets/js/pages/chain.js b/apps/block_scout_web/assets/js/pages/chain.js
index 17217f4b47..185c52021b 100644
--- a/apps/block_scout_web/assets/js/pages/chain.js
+++ b/apps/block_scout_web/assets/js/pages/chain.js
@@ -19,6 +19,8 @@ export const initialState = {
blocks: [],
transactions: [],
transactionsBatch: [],
+ transactionsError: false,
+ transactionsLoading: true,
transactionCount: null,
usdMarketCap: null
}
@@ -62,6 +64,10 @@ function baseReducer (state = initialState, action) {
const transactionCount = state.transactionCount + action.msgs.length
+ if (state.transactionsLoading || state.transactionsError) {
+ return Object.assign({}, state, { transactionCount })
+ }
+
if (!state.transactionsBatch.length && action.msgs.length < BATCH_THRESHOLD) {
return Object.assign({}, state, {
transactions: [
@@ -80,6 +86,14 @@ function baseReducer (state = initialState, action) {
})
}
}
+ case 'START_TRANSACTIONS_FETCH':
+ return Object.assign({}, state, { transactionsError: false, transactionsLoading: true })
+ case 'TRANSACTIONS_FETCHED':
+ return Object.assign({}, state, { transactions: [...action.msg.transactions] })
+ case 'TRANSACTIONS_FETCH_ERROR':
+ return Object.assign({}, state, { transactionsError: true })
+ case 'FINISH_TRANSACTIONS_FETCH':
+ return Object.assign({}, state, { transactionsLoading: false })
default:
return state
}
@@ -158,14 +172,19 @@ const elements = {
listMorph(container, newElements, { key: 'dataset.blockNumber', horizontal: true })
}
},
+ '[data-selector="transactions-list"] [data-selector="error-message"]': {
+ render ($el, state, oldState) {
+ $el.toggle(state.transactionsError)
+ }
+ },
+ '[data-selector="transactions-list"] [data-selector="loading-message"]': {
+ render ($el, state, oldState) {
+ $el.toggle(state.transactionsLoading)
+ }
+ },
'[data-selector="transactions-list"]': {
load ($el) {
- return {
- transactions: $el.children().map((index, el) => ({
- transactionHash: el.dataset.identifierHash,
- transactionHtml: el.outerHTML
- })).toArray()
- }
+ return { transactionsPath: $el[0].dataset.transactionsPath }
},
render ($el, state, oldState) {
if (oldState.transactions === state.transactions) return
@@ -188,6 +207,8 @@ 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))
exchangeRateChannel.on('new_rate', (msg) => store.dispatch({
type: 'RECEIVED_NEW_EXCHANGE_RATE',
@@ -216,6 +237,15 @@ if ($chainDetailsPage.length) {
})))
}
+function loadTransactions (store) {
+ const path = store.getState().transactionsPath
+ store.dispatch({type: 'START_TRANSACTIONS_FETCH'})
+ $.getJSON(path)
+ .done(response => store.dispatch({type: 'TRANSACTIONS_FETCHED', msg: humps.camelizeKeys(response)}))
+ .fail(() => store.dispatch({type: 'TRANSACTIONS_FETCH_ERROR'}))
+ .always(() => store.dispatch({type: 'FINISH_TRANSACTIONS_FETCH'}))
+}
+
export function placeHolderBlock (blockNumber) {
return `
:required,
- [created_contract_address: :names] => :optional,
- [from_address: :names] => :required,
- [to_address: :names] => :optional
- },
- paging_options: %PagingOptions{page_size: 5}
- )
-
exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null()
market_history_data =
@@ -43,7 +32,7 @@ defmodule BlockScoutWeb.ChainController do
available_supply: available_supply(Chain.supply_for_days(30), exchange_rate),
market_history_data: market_history_data,
transaction_estimated_count: transaction_estimated_count,
- transactions: transactions
+ transactions_path: recent_transactions_path(conn, :index)
)
end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/recent_transactions_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/recent_transactions_controller.ex
new file mode 100644
index 0000000000..4f66597bcb
--- /dev/null
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/recent_transactions_controller.ex
@@ -0,0 +1,35 @@
+defmodule BlockScoutWeb.RecentTransactionsController do
+ use BlockScoutWeb, :controller
+
+ alias Explorer.{Chain, PagingOptions}
+ alias Explorer.Chain.Hash
+ alias Phoenix.View
+
+ def index(conn, _params) do
+ with true <- ajax?(conn) do
+ recent_transactions =
+ Chain.recent_collated_transactions(
+ necessity_by_association: %{
+ :block => :required,
+ [created_contract_address: :names] => :optional,
+ [from_address: :names] => :required,
+ [to_address: :names] => :optional
+ },
+ paging_options: %PagingOptions{page_size: 5}
+ )
+
+ transactions =
+ Enum.map(recent_transactions, fn transaction ->
+ %{
+ transaction_hash: Hash.to_string(transaction.hash),
+ transaction_html:
+ View.render_to_string(BlockScoutWeb.TransactionView, "_tile.html", transaction: transaction)
+ }
+ end)
+
+ json(conn, %{transactions: transactions})
+ else
+ _ -> unprocessable_entity(conn)
+ end
+ end
+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 c17327f0c5..4a589b1193 100644
--- a/apps/block_scout_web/lib/block_scout_web/router.ex
+++ b/apps/block_scout_web/lib/block_scout_web/router.ex
@@ -76,6 +76,8 @@ defmodule BlockScoutWeb.Router do
resources("/pending_transactions", PendingTransactionController, only: [:index])
+ resources("/recent_transactions", RecentTransactionsController, only: [:index])
+
get("/txs", TransactionController, :index)
resources "/tx", TransactionController, only: [:show] do
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 7fc1df3efd..b05091828b 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
@@ -72,10 +72,19 @@
<%= gettext "More transactions have come in" %>
-
- <%= for transaction <- @transactions do %>
- <%= render BlockScoutWeb.TransactionView, "_tile.html", transaction: transaction %>
- <% end %>
+
+
+
+
+
+
+
+ <%= gettext("Loading...") %>
+
diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot
index faee112aad..5e0b85aea5 100644
--- a/apps/block_scout_web/priv/gettext/default.pot
+++ b/apps/block_scout_web/priv/gettext/default.pot
@@ -1189,6 +1189,7 @@ 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/tokens/read_contract/index.html.eex:25
msgid "Loading..."
msgstr ""
@@ -1532,3 +1533,8 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:5
msgid "Emission Contract"
msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/chain/show.html.eex:78
+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 a44e25393a..b6a91afd7a 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
@@ -1189,6 +1189,7 @@ 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/tokens/read_contract/index.html.eex:25
msgid "Loading..."
msgstr ""
@@ -1532,3 +1533,8 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:5
msgid "Emission Contract"
msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/chain/show.html.eex:78
+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 1a71add242..7fd54c4c60 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
@@ -5,7 +5,7 @@ defmodule BlockScoutWeb.ChainControllerTest do
import BlockScoutWeb.Router.Helpers, only: [chain_path: 2, block_path: 3, transaction_path: 3, address_path: 3]
- alias Explorer.Chain.Block
+ alias Explorer.Chain.{Block, Hash}
alias Explorer.Counters.AddressesWithBalanceCounter
describe "GET index/2" do
@@ -41,39 +41,6 @@ defmodule BlockScoutWeb.ChainControllerTest do
refute(Enum.member?(conn.assigns.blocks, old_block))
end
- test "only returns transactions with an associated block", %{conn: conn} do
- associated =
- :transaction
- |> insert()
- |> with_block()
-
- unassociated = insert(:transaction)
-
- start_supervised!(AddressesWithBalanceCounter)
- AddressesWithBalanceCounter.consolidate()
-
- conn = get(conn, "/")
-
- transaction_hashes = Enum.map(conn.assigns.transactions, fn transaction -> transaction.hash end)
-
- assert(Enum.member?(transaction_hashes, associated.hash))
- refute(Enum.member?(transaction_hashes, unassociated.hash))
- end
-
- test "returns a transaction", %{conn: conn} do
- transaction =
- :transaction
- |> insert()
- |> with_block()
-
- start_supervised!(AddressesWithBalanceCounter)
- AddressesWithBalanceCounter.consolidate()
-
- conn = get(conn, "/")
-
- assert(List.first(conn.assigns.transactions).hash == transaction.hash)
- 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))
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/recent_transactions_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/recent_transactions_controller_test.exs
new file mode 100644
index 0000000000..a35ea19491
--- /dev/null
+++ b/apps/block_scout_web/test/block_scout_web/controllers/recent_transactions_controller_test.exs
@@ -0,0 +1,54 @@
+defmodule BlockScoutWeb.RecentTransactionsControllerTest do
+ use BlockScoutWeb.ConnCase
+
+ import BlockScoutWeb.Router.Helpers, only: [recent_transactions_path: 2]
+
+ alias Explorer.Chain.Hash
+
+ describe "GET index/2" do
+ test "returns a transaction", %{conn: conn} do
+ transaction =
+ :transaction
+ |> insert()
+ |> with_block()
+
+ conn =
+ conn
+ |> put_req_header("x-requested-with", "xmlhttprequest")
+ |> get(recent_transactions_path(conn, :index))
+
+ assert response = json_response(conn, 200)["transactions"]
+
+ response_hashes = Enum.map(response, & &1["transaction_hash"])
+
+ assert Enum.member?(response_hashes, Hash.to_string(transaction.hash))
+ end
+
+ test "only returns transactions with an associated block", %{conn: conn} do
+ associated =
+ :transaction
+ |> insert()
+ |> with_block()
+
+ unassociated = insert(:transaction)
+
+ conn =
+ conn
+ |> put_req_header("x-requested-with", "xmlhttprequest")
+ |> get(recent_transactions_path(conn, :index))
+
+ assert response = json_response(conn, 200)["transactions"]
+
+ response_hashes = Enum.map(response, & &1["transaction_hash"])
+
+ assert Enum.member?(response_hashes, Hash.to_string(associated.hash))
+ refute Enum.member?(response_hashes, Hash.to_string(unassociated.hash))
+ end
+
+ test "only responds to ajax requests", %{conn: conn} do
+ conn = get(conn, recent_transactions_path(conn, :index))
+
+ assert conn.status == 422
+ end
+ end
+end