Setup live updates for address balance

pull/429/head
jimmay5469 6 years ago
parent e51e6c42b5
commit 4d612d59f0
  1. 13
      apps/explorer/lib/explorer/chain.ex
  2. 8
      apps/explorer/test/explorer/chain_test.exs
  3. 8
      apps/explorer_web/assets/__tests__/pages/address.js
  4. 6
      apps/explorer_web/assets/__tests__/pages/transaction.js
  5. 13
      apps/explorer_web/assets/js/pages/address.js
  6. 10
      apps/explorer_web/assets/js/pages/transaction.js
  7. 13
      apps/explorer_web/lib/explorer_web/channels/address_channel.ex
  8. 1
      apps/explorer_web/lib/explorer_web/event_handler.ex
  9. 40
      apps/explorer_web/lib/explorer_web/notifier.ex
  10. 10
      apps/explorer_web/lib/explorer_web/templates/address/_balance_card.html.eex
  11. 13
      apps/explorer_web/lib/explorer_web/templates/address/overview.html.eex
  12. 26
      apps/explorer_web/test/explorer_web/features/viewing_addresses_test.exs
  13. 2
      apps/explorer_web/test/explorer_web/features/viewing_transactions_test.exs

@ -362,10 +362,11 @@ defmodule Explorer.Chain do
]
) :: {:ok, [Hash.Address.t()]} | {:error, [Changeset.t()]}
def update_balances(addresses_params, options \\ []) when is_list(options) do
with {:ok, changes_list} <- changes_list(addresses_params, for: Address, with: :balance_changeset) do
timestamps = timestamps()
insert_addresses(changes_list, timeout: options[:timeout] || @transaction_timeout, timestamps: timestamps)
with {:ok, changes_list} <- changes_list(addresses_params, for: Address, with: :balance_changeset),
{:ok, address_hashes} <-
insert_addresses(changes_list, timeout: options[:timeout] || @transaction_timeout, timestamps: timestamps()) do
broadcast_events([{:balance_updates, address_hashes}])
{:ok, address_hashes}
end
end
@ -1734,7 +1735,7 @@ defmodule Explorer.Chain do
:ok
"""
@spec subscribe_to_events(chain_event()) :: :ok
def subscribe_to_events(event_type) when event_type in ~w(blocks logs transactions)a do
def subscribe_to_events(event_type) when event_type in ~w(balance_updates blocks logs transactions)a do
Registry.register(Registry.ChainEvents, event_type, [])
:ok
end
@ -1893,7 +1894,7 @@ defmodule Explorer.Chain do
end
defp broadcast_events(data) do
for {event_type, event_data} <- data, event_type in ~w(blocks logs transactions)a do
for {event_type, event_data} <- data, event_type in ~w(balance_updates blocks logs transactions)a do
broadcast_event_data(event_type, event_data)
end
end

@ -1189,4 +1189,12 @@ defmodule Explorer.ChainTest do
assert_received {:chain_event, :logs, [%Log{}]}
end
end
test "publishes update_balance data to subscribers on upsert" do
address = %Address{hash: address_hash} = insert(:address, fetched_balance: 3, fetched_balance_block_number: 3)
Chain.subscribe_to_events(:balance_updates)
Chain.update_balances([Map.from_struct(address)])
assert_received {:chain_event, :balance_updates, [^address_hash]}
end
end

@ -74,17 +74,17 @@ test('CHANNEL_DISCONNECTED', () => {
expect(output.batchCountAccumulator).toBe(0)
})
test('RECEIVED_UPDATED_OVERVIEW', () => {
test('RECEIVED_UPDATED_BALANCE', () => {
const state = initialState
const action = {
type: 'RECEIVED_UPDATED_OVERVIEW',
type: 'RECEIVED_UPDATED_BALANCE',
msg: {
overview: 'hello world'
balance: 'hello world'
}
}
const output = reducer(state, action)
expect(output.overview).toBe('hello world')
expect(output.balance).toBe('hello world')
})
describe('RECEIVED_NEW_TRANSACTION_BATCH', () => {

@ -1,14 +1,14 @@
import { reducer, initialState } from '../../js/pages/transaction'
test('RECEIVED_UPDATED_CONFIRMATIONS', () => {
const state = initialState
const state = { ...initialState, blockNumber: 1 }
const action = {
type: 'RECEIVED_UPDATED_CONFIRMATIONS',
msg: {
confirmations: 5
blockNumber: 5
}
}
const output = reducer(state, action)
expect(output.confirmations).toBe(5)
expect(output.confirmations).toBe(4)
})

@ -15,7 +15,7 @@ export const initialState = {
channelDisconnected: false,
filter: null,
newTransactions: [],
overview: null,
balance: null,
transactionCount: null
}
@ -37,9 +37,9 @@ export function reducer (state = initialState, action) {
batchCountAccumulator: 0
})
}
case 'RECEIVED_UPDATED_OVERVIEW': {
case 'RECEIVED_UPDATED_BALANCE': {
return Object.assign({}, state, {
overview: action.msg.overview
balance: action.msg.balance
})
}
case 'RECEIVED_NEW_TRANSACTION_BATCH': {
@ -53,7 +53,6 @@ export function reducer (state = initialState, action) {
))
if (!state.batchCountAccumulator && action.msgs.length < BATCH_THRESHOLD) {
console.log(state.transactionCount + action.msgs.length);
return Object.assign({}, state, {
newTransactions: [
...state.newTransactions,
@ -88,7 +87,7 @@ router.when('/addresses/:addressHash').then((params) => initRedux(reducer, {
.receive('ok', resp => { console.log('Joined successfully', `addresses:${addressHash}`, resp) })
.receive('error', resp => { console.log('Unable to join', `addresses:${addressHash}`, resp) })
channel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' }))
channel.on('overview', (msg) => store.dispatch({ type: 'RECEIVED_UPDATED_OVERVIEW', msg }))
channel.on('balance', (msg) => store.dispatch({ type: 'RECEIVED_UPDATED_BALANCE', msg }))
if (!blockNumber) channel.on('transaction', batchChannel((msgs) => store.dispatch({ type: 'RECEIVED_NEW_TRANSACTION_BATCH', msgs })))
},
render (state, oldState) {
@ -96,13 +95,13 @@ router.when('/addresses/:addressHash').then((params) => initRedux(reducer, {
const $channelBatchingCount = $('[data-selector="channel-batching-count"]')
const $channelDisconnected = $('[data-selector="channel-disconnected-message"]')
const $emptyTransactionsList = $('[data-selector="empty-transactions-list"]')
const $overview = $('[data-selector="overview"]')
const $balance = $('[data-selector="balance"]')
const $transactionCount = $('[data-selector="transaction-count"]')
const $transactionsList = $('[data-selector="transactions-list"]')
if ($emptyTransactionsList.length && state.newTransactions.length) window.location.reload()
if (state.channelDisconnected) $channelDisconnected.show()
if (oldState.overview !== state.overview) $overview.empty().append(state.overview)
if (oldState.balance !== state.balance) $balance.empty().append(state.balance)
if (oldState.transactionCount !== state.transactionCount) $transactionCount.empty().append(numeral(state.transactionCount).format())
if (state.batchCountAccumulator) {
$channelBatching.show()

@ -1,4 +1,5 @@
import $ from 'jquery'
import humps from 'humps'
import numeral from 'numeral'
import 'numeral/locales'
import socket from '../socket'
@ -18,9 +19,9 @@ export function reducer (state = initialState, action) {
})
}
case 'RECEIVED_UPDATED_CONFIRMATIONS': {
if ((action.msg.block_number - state.blockNumber) > state.confirmations) {
if ((action.msg.blockNumber - state.blockNumber) > state.confirmations) {
return Object.assign({}, state, {
confirmations: action.msg.block_number - state.blockNumber
confirmations: action.msg.blockNumber - state.blockNumber
})
} else return state
}
@ -29,9 +30,8 @@ export function reducer (state = initialState, action) {
}
}
router.when('/transactions/:transactionHash').then((params) => initRedux(reducer, {
router.when('/transactions/:transactionHash').then(({ locale }) => initRedux(reducer, {
main (store) {
const { transactionHash, locale } = params
const channel = socket.channel(`transactions:confirmations`, {})
const $transactionBlockNumber = $('[data-selector="block-number"]')
numeral.locale(locale)
@ -39,7 +39,7 @@ router.when('/transactions/:transactionHash').then((params) => initRedux(reducer
channel.join()
.receive('ok', resp => { console.log('Joined successfully', `transactions:confirmations`, resp) })
.receive('error', resp => { console.log('Unable to join', `transactions:confirmations`, resp) })
channel.on('update', (msg) => store.dispatch({ type: 'RECEIVED_UPDATED_CONFIRMATIONS', msg }))
channel.on('update', (msg) => store.dispatch({ type: 'RECEIVED_UPDATED_CONFIRMATIONS', msg: humps.camelizeKeys(msg) }))
},
render (state, oldState) {
const $blockConfirmations = $('[data-selector="block-confirmations"]')

@ -7,7 +7,7 @@ defmodule ExplorerWeb.AddressChannel do
alias ExplorerWeb.{AddressTransactionView, AddressView}
alias Phoenix.View
intercept(["overview", "transaction"])
intercept(["balance_update", "transaction"])
def join("addresses:" <> _address_hash, _params, socket) do
{:ok, %{}, socket}
@ -35,8 +35,8 @@ defmodule ExplorerWeb.AddressChannel do
end
def handle_out(
"overview",
%{address: address, exchange_rate: exchange_rate, transaction_count: transaction_count},
"balance_update",
%{address: address, exchange_rate: exchange_rate},
socket
) do
Gettext.put_locale(ExplorerWeb.Gettext, socket.assigns.locale)
@ -44,14 +44,13 @@ defmodule ExplorerWeb.AddressChannel do
rendered =
View.render_to_string(
AddressView,
"_values.html",
"_balance_card.html",
locale: socket.assigns.locale,
address: address,
exchange_rate: exchange_rate,
transaction_count: transaction_count
exchange_rate: exchange_rate
)
push(socket, "overview", %{overview: rendered})
push(socket, "balance", %{balance: rendered})
{:noreply, socket}
end
end

@ -14,6 +14,7 @@ defmodule ExplorerWeb.EventHandler do
def init([]) do
Chain.subscribe_to_events(:blocks)
Chain.subscribe_to_events(:transactions)
Chain.subscribe_to_events(:balance_updates)
{:ok, []}
end

@ -1,8 +1,9 @@
defmodule ExplorerWeb.Notifier do
alias Explorer.Chain
alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token
alias ExplorerWeb.Endpoint
def handle_event({:chain_event, :blocks, []}), do: IO.inspect "EMPTY BLOCKS"
def handle_event({:chain_event, :blocks, []}), do: IO.inspect("EMPTY BLOCKS")
def handle_event({:chain_event, :blocks, blocks}) do
max_numbered_block = Enum.max_by(blocks, & &1.number).number
@ -14,22 +15,39 @@ defmodule ExplorerWeb.Notifier do
|> Enum.each(&broadcast_transaction/1)
end
def handle_event({:chain_event, :balance_updates, address_hashes}) do
address_hashes
|> Enum.each(&broadcast_balance/1)
end
def handle_event(event), do: IO.inspect({:error, event})
defp broadcast_balance(address_hash) do
{:ok, address} = Chain.hash_to_address(address_hash)
ExplorerWeb.Endpoint.broadcast("addresses:#{address.hash}", "balance_update", %{
address: address,
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null()
})
end
defp broadcast_transaction(transaction_hash) do
{:ok, transaction} = Chain.hash_to_transaction(
transaction_hash,
necessity_by_association: %{
block: :required,
from_address: :optional,
to_address: :optional
}
)
{:ok, transaction} =
Chain.hash_to_transaction(
transaction_hash,
necessity_by_association: %{
block: :required,
from_address: :optional,
to_address: :optional
}
)
ExplorerWeb.Endpoint.broadcast("addresses:#{transaction.from_address_hash}", "transaction", %{
address: transaction.from_address,
transaction: transaction
})
if (transaction.from_address && transaction.to_address != transaction.from_address) do
if transaction.to_address && transaction.to_address != transaction.from_address do
ExplorerWeb.Endpoint.broadcast("addresses:#{transaction.to_address_hash}", "transaction", %{
address: transaction.to_address,
transaction: transaction

@ -0,0 +1,10 @@
<div class="card bg-primary" data-selector='balance'>
<div class="card-body">
<h2 class="card-title text-white"><%= gettext "Balance" %></h2>
<span></span>
<div class="text-right">
<h3 class="text-white" data-test="address_balance"><%= balance(@address) %></h3>
<span class="text-light"><%= formatted_usd(@address, @exchange_rate) %></span>
</div>
</div>
</div>

@ -1,5 +1,5 @@
<section>
<div class="row" data-selector='overview'>
<div class="row">
<div class="col-md-12 col-lg-8">
<div class="card">
<div class="card-body">
@ -53,16 +53,7 @@
</div>
</div>
<div class="col-md-6 col-lg-4">
<div class="card bg-primary">
<div class="card-body">
<h2 class="card-title text-white"><%= gettext "Balance" %></h2>
<span></span>
<div class="text-right">
<h3 class="text-white" data-test="address_balance"><%= balance(@address) %></h3>
<span class="text-light"><%= formatted_usd(@address, @exchange_rate) %></span>
</div>
</div>
</div>
<%= render ExplorerWeb.AddressView, "_balance_card.html", address: @address, exchange_rate: @exchange_rate %>
</div>
</div>
</section>

@ -1,7 +1,8 @@
defmodule ExplorerWeb.ViewingAddressesTest do
use ExplorerWeb.FeatureCase, async: true
alias Explorer.Chain.Wei
alias Explorer.Chain
alias Explorer.Chain.{Address, Wei}
alias ExplorerWeb.{AddressPage, HomePage, Notifier}
setup do
@ -261,6 +262,29 @@ defmodule ExplorerWeb.ViewingAddressesTest do
assert_text(session, AddressPage.transaction_count(), "3")
end
test "viewing updated balance via live update", %{session: session} do
address = %Address{hash: hash} = insert(:address, fetched_balance: 500)
session
|> AddressPage.visit_page(address)
|> assert_text(AddressPage.balance(), "0.0000000000000005 POA")
{:ok, [^hash]} =
Chain.update_balances([
%{
fetched_balance: 100,
fetched_balance_block_number: 1,
hash: hash
}
])
{:ok, updated_address} = Chain.hash_to_address(hash)
Notifier.handle_event({:chain_event, :balance_updates, [updated_address.hash]})
assert_text(session, AddressPage.balance(), "0.0000000000000001 POA")
end
test "contract creation is shown for to_address on list page", %{
addresses: addresses,
block: block,

@ -4,7 +4,7 @@ defmodule ExplorerWeb.ViewingTransactionsTest do
use ExplorerWeb.FeatureCase, async: true
alias Explorer.Chain.Wei
alias ExplorerWeb.{AddressPage, HomePage, Notifier,TransactionListPage, TransactionLogsPage, TransactionPage}
alias ExplorerWeb.{AddressPage, HomePage, Notifier, TransactionListPage, TransactionLogsPage, TransactionPage}
setup do
block =

Loading…
Cancel
Save