Live reload transactions on transaction list page (with batch messages)

pull/497/head
Stamates 6 years ago committed by jimmay5469
parent f5db43b995
commit 0531c46adb
  1. 116
      apps/explorer_web/assets/__tests__/pages/transaction.js
  2. 1
      apps/explorer_web/assets/js/pages/chain.js
  3. 57
      apps/explorer_web/assets/js/pages/transaction.js
  4. 13
      apps/explorer_web/lib/explorer_web/channels/transaction_channel.ex
  5. 33
      apps/explorer_web/lib/explorer_web/templates/transaction/_transaction.html.eex
  6. 43
      apps/explorer_web/lib/explorer_web/templates/transaction/index.html.eex
  7. 4
      apps/explorer_web/test/explorer_web/features/pages/transaction_list_page.ex
  8. 30
      apps/explorer_web/test/explorer_web/features/viewing_transactions_test.exs

@ -12,3 +12,119 @@ test('RECEIVED_NEW_BLOCK', () => {
expect(output.confirmations).toBe(4)
})
describe('RECEIVED_NEW_TRANSACTION_BATCH', () => {
test('single transaction', () => {
const state = initialState
const action = {
type: 'RECEIVED_NEW_TRANSACTION_BATCH',
msgs: [{
transactionHtml: 'test'
}]
}
const output = reducer(state, action)
expect(output.newTransactions).toEqual(['test'])
expect(output.batchCountAccumulator).toEqual(0)
})
test('large batch of transactions', () => {
const state = initialState
const action = {
type: 'RECEIVED_NEW_TRANSACTION_BATCH',
msgs: [{
transactionHtml: 'test 1'
},{
transactionHtml: 'test 2'
},{
transactionHtml: 'test 3'
},{
transactionHtml: 'test 4'
},{
transactionHtml: 'test 5'
},{
transactionHtml: 'test 6'
},{
transactionHtml: 'test 7'
},{
transactionHtml: 'test 8'
},{
transactionHtml: 'test 9'
},{
transactionHtml: 'test 10'
},{
transactionHtml: 'test 11'
}]
}
const output = reducer(state, action)
expect(output.newTransactions).toEqual([])
expect(output.batchCountAccumulator).toEqual(11)
})
test('single transaction after single transaction', () => {
const state = Object.assign({}, initialState, {
newTransactions: ['test 1']
})
const action = {
type: 'RECEIVED_NEW_TRANSACTION_BATCH',
msgs: [{
transactionHtml: 'test 2'
}]
}
const output = reducer(state, action)
expect(output.newTransactions).toEqual(['test 1', 'test 2'])
expect(output.batchCountAccumulator).toEqual(0)
})
test('single transaction after large batch of transactions', () => {
const state = Object.assign({}, initialState, {
newTransactions: [],
batchCountAccumulator: 11
})
const action = {
type: 'RECEIVED_NEW_TRANSACTION_BATCH',
msgs: [{
transactionHtml: 'test 12'
}]
}
const output = reducer(state, action)
expect(output.newTransactions).toEqual([])
expect(output.batchCountAccumulator).toEqual(12)
})
test('large batch of transactions after large batch of transactions', () => {
const state = Object.assign({}, initialState, {
newTransactions: [],
batchCountAccumulator: 11
})
const action = {
type: 'RECEIVED_NEW_TRANSACTION_BATCH',
msgs: [{
transactionHtml: 'test 12'
},{
transactionHtml: 'test 13'
},{
transactionHtml: 'test 14'
},{
transactionHtml: 'test 15'
},{
transactionHtml: 'test 16'
},{
transactionHtml: 'test 17'
},{
transactionHtml: 'test 18'
},{
transactionHtml: 'test 19'
},{
transactionHtml: 'test 20'
},{
transactionHtml: 'test 21'
},{
transactionHtml: 'test 22'
}]
}
const output = reducer(state, action)
expect(output.newTransactions).toEqual([])
expect(output.batchCountAccumulator).toEqual(22)
})
})

@ -11,7 +11,6 @@ const BATCH_THRESHOLD = 10
export const initialState = {
batchCountAccumulator: 0,
channelDisconnected: false,
newBlock: null,
newTransactions: []
}

@ -4,11 +4,16 @@ import numeral from 'numeral'
import 'numeral/locales'
import socket from '../socket'
import router from '../router'
import { initRedux } from '../utils'
import { updateAllAges } from '../lib/from_now'
import { batchChannel, initRedux } from '../utils'
const BATCH_THRESHOLD = 10
export const initialState = {
batchCountAccumulator: 0,
blockNumber: null,
confirmations: null
confirmations: null,
newTransactions: []
}
export function reducer (state = initialState, action) {
@ -25,6 +30,21 @@ export function reducer (state = initialState, action) {
})
} else return state
}
case 'RECEIVED_NEW_TRANSACTION_BATCH': {
debugger
if (!state.batchCountAccumulator && action.msgs.length < BATCH_THRESHOLD) {
return Object.assign({}, state, {
newTransactions: [
...state.newTransactions,
...action.msgs.map(({transactionHtml}) => transactionHtml)
]
})
} else {
return Object.assign({}, state, {
batchCountAccumulator: state.batchCountAccumulator + action.msgs.length
})
}
}
default:
return state
}
@ -41,8 +61,41 @@ router.when('/transactions/:transactionHash').then(({ locale }) => initRedux(red
},
render (state, oldState) {
const $blockConfirmations = $('[data-selector="block-confirmations"]')
if (oldState.confirmations !== state.confirmations) {
$blockConfirmations.empty().append(numeral(state.confirmations).format())
}
}
}))
router.when('/transactions', { exactPathMatch: true }).then(({ locale }) => initRedux(reducer, {
main (store) {
const transactionsChannel = socket.channel(`transactions:new_transaction`)
transactionsChannel.join()
transactionsChannel.on('new_transaction', batchChannel((msgs) =>
store.dispatch({ type: 'RECEIVED_NEW_TRANSACTION_BATCH', msgs: humps.camelizeKeys(msgs) }))
)
},
render (state, oldState) {
const $channelBatching = $('[data-selector="channel-batching-message"]')
const $channelBatchingCount = $('[data-selector="channel-batching-count"]')
const $transactionsList = $('[data-selector="transactions-list"]')
if (state.batchCountAccumulator) {
$channelBatching.show()
$channelBatchingCount[0].innerHTML = numeral(state.batchCountAccumulator).format()
} else {
$channelBatching.hide()
}
if (oldState.newTransactions !== state.newTransactions) {
const newTransactionsToInsert = state.newTransactions.slice(oldState.newTransactions.length)
$transactionsList
.children()
.slice($transactionsList.children().length - newTransactionsToInsert.length, $transactionsList.children().length)
.remove()
$transactionsList.prepend(newTransactionsToInsert.reverse().join(''))
updateAllAges()
}
}
}))

@ -4,7 +4,7 @@ defmodule ExplorerWeb.TransactionChannel do
"""
use ExplorerWeb, :channel
alias ExplorerWeb.ChainView
alias ExplorerWeb.{ChainView, TransactionView}
alias Phoenix.View
intercept(["new_transaction"])
@ -24,8 +24,17 @@ defmodule ExplorerWeb.TransactionChannel do
transaction: transaction
)
rendered_transaction =
View.render_to_string(
TransactionView,
"_transaction.html",
locale: socket.assigns.locale,
transaction: transaction
)
push(socket, "new_transaction", %{
homepage_transaction_html: rendered_homepage_transaction
homepage_transaction_html: rendered_homepage_transaction,
transaction_html: rendered_transaction
})
{:noreply, socket}

@ -0,0 +1,33 @@
<div class="tile tile-type-<%= ExplorerWeb.TransactionView.type_suffix(@transaction) %> tile-status--<%= ExplorerWeb.TransactionView.status(@transaction) %>" data-test="<%= ExplorerWeb.TransactionView.type_suffix(@transaction) %>" data-transaction-hash="<%= @transaction.hash %>">
<div class="row" data-test="chain_transaction">
<div class="col-md-2 d-flex flex-column align-items-left justify-content-start justify-content-lg-center">
<div class="ml-4">
<span class="tile-label" data-test="transaction_type"> <%= ExplorerWeb.TransactionView.transaction_display_type(@transaction) %></span>
<div class="tile-status-label" data-test="transaction_status"><%= ExplorerWeb.TransactionView.formatted_status(@transaction) %></div>
</div>
</div>
<div class="col-md-7 col-lg-8 d-flex flex-column">
<%= render ExplorerWeb.TransactionView, "_link.html", locale: @locale, transaction_hash: @transaction.hash %>
<span class="text-nowrap">
<%= render ExplorerWeb.AddressView, "_link.html", address_hash: @transaction.from_address_hash, contract: ExplorerWeb.AddressView.contract?(@transaction.from_address), locale: @locale %>
&rarr;
<%= render ExplorerWeb.AddressView, "_link.html", address_hash: ExplorerWeb.TransactionView.to_address_hash(@transaction), contract: ExplorerWeb.AddressView.contract?(@transaction.to_address), locale: @locale %>
</span>
<span>
<span class="ml-1" data-from-now="<%= @transaction.block.timestamp %>"></span>
<span class="ml-1">
<%= link(
gettext("Block #%{number}", number: to_string(@transaction.block.number)),
to: block_path(ExplorerWeb.Endpoint, :show, @locale, @transaction.block)
) %>
</span>
</span>
</div>
<div class="col-md-3 col-lg-2 d-flex flex-row flex-md-column justify-content-start text-md-right">
<span class="tile-title">
<%= ExplorerWeb.TransactionView.value(@transaction, include_label: false) %> <%= gettext "Ether" %>
</span>
<span><%= ExplorerWeb.TransactionView.formatted_fee(@transaction, denomination: :ether) %> <%= gettext "Fee" %></span>
</div>
</div>
</div>

@ -43,42 +43,19 @@
</div>
<div class="card-body">
<div data-selector="channel-batching-message" style="display:none;">
<div data-selector="reload-button" class="alert alert-info">
<a href="#" class="alert-link"><span data-selector="channel-batching-count"></span> <%= gettext "More transactions have come in" %></a>
</div>
</div>
<h2 class="card-title mb-0"><%= gettext "Transactions" %></h2>
<p><%= gettext("Showing %{count} Validated Transactions", count: Cldr.Number.to_string!(@transaction_estimated_count, format: "#,###")) %></p>
<span data-selector="transactions-list">
<%= for transaction <- @transactions do %>
<%= render ExplorerWeb.TransactionView, "_transaction.html", locale: @locale, transaction: transaction %>
<% end %>
</span>
<%= for transaction <- @transactions do %>
<div class="tile tile-type-<%= ExplorerWeb.TransactionView.type_suffix(transaction) %> tile-status--<%= ExplorerWeb.TransactionView.status(transaction) %>" data-test="<%= ExplorerWeb.TransactionView.type_suffix(transaction) %>" data-transaction-hash="<%= transaction.hash %>">
<div class="row justify-content-end" data-test="chain_transaction">
<div class="col-md-3 col-lg-2 d-flex align-items-lg-center">
<div class="d-flex flex-md-column ml-md-4">
<span class="tile-label mr-1 mr-md-0" data-test="transaction_type"> <%= ExplorerWeb.TransactionView.transaction_display_type(transaction) %></span>
<div class="tile-status-label" data-test="transaction_status"><%= ExplorerWeb.TransactionView.formatted_status(transaction) %></div>
</div>
</div>
<div class="col-md-9 col-lg-7 d-flex flex-column">
<%= render ExplorerWeb.TransactionView, "_link.html", locale: @locale, transaction_hash: transaction.hash %>
<span>
<%= render ExplorerWeb.AddressView, "_link.html", address_hash: transaction.from_address_hash, contract: ExplorerWeb.AddressView.contract?(transaction.from_address), locale: @locale %>
&rarr;
<%= render ExplorerWeb.AddressView, "_link.html", address_hash: ExplorerWeb.TransactionView.to_address_hash(transaction), contract: ExplorerWeb.AddressView.contract?(transaction.to_address), locale: @locale %>
</span>
<span>
<span data-from-now="<%= transaction.block.timestamp %>"></span>
<span class="ml-1">
<%= link(
gettext("Block #%{number}", number: to_string(transaction.block.number)),
to: block_path(@conn, :show, @conn.assigns.locale, transaction.block)
) %>
</span>
</span>
</div>
<div class="col-md-9 col-lg-3 d-flex flex-column flex-md-row flex-lg-column justify-content-start text-lg-right mt-3 mt-lg-0">
<span class="tile-title mr-1 mr-lg-0"><%= ExplorerWeb.TransactionView.value(transaction, include_label: false) %> <%= gettext "Ether" %></span>
<span> <%= ExplorerWeb.TransactionView.formatted_fee(transaction, denomination: :ether) %> <%= gettext "Fee" %></span>
</div>
</div>
</div>
<% end %>
<%= if @next_page_params do %>
<%= link(
gettext("Older"),

@ -19,6 +19,10 @@ defmodule ExplorerWeb.TransactionListPage do
css("[data-transaction-hash='#{hash}'] [data-test='transaction_type']", text: "Contract Creation")
end
def non_loaded_transaction_count(count) do
css("[data-selector='channel-batching-count']", text: count)
end
def transaction(%Transaction{hash: transaction_hash}) do
css("[data-transaction-hash='#{transaction_hash}']")
end

@ -113,8 +113,7 @@ defmodule ExplorerWeb.ViewingTransactionsTest do
Notifier.handle_event({:chain_event, :transactions, transaction_hashes})
session
|> assert_has(AddressPage.non_loaded_transaction_count("30"))
assert_has(session, AddressPage.non_loaded_transaction_count("30"))
end
test "contract creation is shown for to_address on home page", %{session: session} do
@ -163,6 +162,33 @@ defmodule ExplorerWeb.ViewingTransactionsTest do
|> TransactionListPage.visit_page()
|> assert_has(TransactionListPage.contract_creation(transaction))
end
test "viewing new transactions via live update on list page", %{session: session} do
TransactionListPage.visit_page(session)
transaction =
:transaction
|> insert()
|> with_block()
Notifier.handle_event({:chain_event, :transactions, [transaction.hash]})
assert_has(session, TransactionListPage.transaction(transaction))
end
test "count of non-loaded transactions on list page live update when batch overflow", %{session: session} do
transaction_hashes =
30
|> insert_list(:transaction)
|> with_block()
|> Enum.map(& &1.hash)
TransactionListPage.visit_page(session)
Notifier.handle_event({:chain_event, :transactions, transaction_hashes})
assert_has(session, TransactionListPage.non_loaded_transaction_count("30"))
end
end
describe "viewing a transaction page" do

Loading…
Cancel
Save