From 4d612d59f06f73d11dc4b5d335b3e4bcb20ec6b2 Mon Sep 17 00:00:00 2001 From: jimmay5469 Date: Tue, 17 Jul 2018 16:34:39 -0400 Subject: [PATCH] Setup live updates for address balance --- apps/explorer/lib/explorer/chain.ex | 13 +++--- apps/explorer/test/explorer/chain_test.exs | 8 ++++ .../assets/__tests__/pages/address.js | 8 ++-- .../assets/__tests__/pages/transaction.js | 6 +-- apps/explorer_web/assets/js/pages/address.js | 13 +++--- .../assets/js/pages/transaction.js | 10 ++--- .../explorer_web/channels/address_channel.ex | 13 +++--- .../lib/explorer_web/event_handler.ex | 1 + .../explorer_web/lib/explorer_web/notifier.ex | 40 ++++++++++++++----- .../templates/address/_balance_card.html.eex | 10 +++++ .../templates/address/overview.html.eex | 13 +----- .../features/viewing_addresses_test.exs | 26 +++++++++++- .../features/viewing_transactions_test.exs | 2 +- 13 files changed, 107 insertions(+), 56 deletions(-) create mode 100644 apps/explorer_web/lib/explorer_web/templates/address/_balance_card.html.eex diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 63ed708faf..7fc977437f 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -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 diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 54039e7cfd..082fffb558 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -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 diff --git a/apps/explorer_web/assets/__tests__/pages/address.js b/apps/explorer_web/assets/__tests__/pages/address.js index 92f1ef81cc..c4a55398df 100644 --- a/apps/explorer_web/assets/__tests__/pages/address.js +++ b/apps/explorer_web/assets/__tests__/pages/address.js @@ -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', () => { diff --git a/apps/explorer_web/assets/__tests__/pages/transaction.js b/apps/explorer_web/assets/__tests__/pages/transaction.js index ee4cf72e33..ec380fbe01 100644 --- a/apps/explorer_web/assets/__tests__/pages/transaction.js +++ b/apps/explorer_web/assets/__tests__/pages/transaction.js @@ -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) }) diff --git a/apps/explorer_web/assets/js/pages/address.js b/apps/explorer_web/assets/js/pages/address.js index 0d44670748..b88e5913bc 100644 --- a/apps/explorer_web/assets/js/pages/address.js +++ b/apps/explorer_web/assets/js/pages/address.js @@ -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() diff --git a/apps/explorer_web/assets/js/pages/transaction.js b/apps/explorer_web/assets/js/pages/transaction.js index b3c22abfd9..7f8ba8a25f 100644 --- a/apps/explorer_web/assets/js/pages/transaction.js +++ b/apps/explorer_web/assets/js/pages/transaction.js @@ -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"]') diff --git a/apps/explorer_web/lib/explorer_web/channels/address_channel.ex b/apps/explorer_web/lib/explorer_web/channels/address_channel.ex index 4c9b388c21..b949f54f6e 100644 --- a/apps/explorer_web/lib/explorer_web/channels/address_channel.ex +++ b/apps/explorer_web/lib/explorer_web/channels/address_channel.ex @@ -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 diff --git a/apps/explorer_web/lib/explorer_web/event_handler.ex b/apps/explorer_web/lib/explorer_web/event_handler.ex index 25284e2fb2..36fc424a52 100644 --- a/apps/explorer_web/lib/explorer_web/event_handler.ex +++ b/apps/explorer_web/lib/explorer_web/event_handler.ex @@ -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 diff --git a/apps/explorer_web/lib/explorer_web/notifier.ex b/apps/explorer_web/lib/explorer_web/notifier.ex index 3ef33208fe..447e7ecd24 100644 --- a/apps/explorer_web/lib/explorer_web/notifier.ex +++ b/apps/explorer_web/lib/explorer_web/notifier.ex @@ -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 diff --git a/apps/explorer_web/lib/explorer_web/templates/address/_balance_card.html.eex b/apps/explorer_web/lib/explorer_web/templates/address/_balance_card.html.eex new file mode 100644 index 0000000000..cce5d55898 --- /dev/null +++ b/apps/explorer_web/lib/explorer_web/templates/address/_balance_card.html.eex @@ -0,0 +1,10 @@ +
+
+

<%= gettext "Balance" %>

+ +
+

<%= balance(@address) %>

+ <%= formatted_usd(@address, @exchange_rate) %> +
+
+
diff --git a/apps/explorer_web/lib/explorer_web/templates/address/overview.html.eex b/apps/explorer_web/lib/explorer_web/templates/address/overview.html.eex index 00f01a5887..f088466f52 100644 --- a/apps/explorer_web/lib/explorer_web/templates/address/overview.html.eex +++ b/apps/explorer_web/lib/explorer_web/templates/address/overview.html.eex @@ -1,5 +1,5 @@
-
+
@@ -53,16 +53,7 @@
-
-
-

<%= gettext "Balance" %>

- -
-

<%= balance(@address) %>

- <%= formatted_usd(@address, @exchange_rate) %> -
-
-
+ <%= render ExplorerWeb.AddressView, "_balance_card.html", address: @address, exchange_rate: @exchange_rate %>
diff --git a/apps/explorer_web/test/explorer_web/features/viewing_addresses_test.exs b/apps/explorer_web/test/explorer_web/features/viewing_addresses_test.exs index 549956d358..383b6b9911 100644 --- a/apps/explorer_web/test/explorer_web/features/viewing_addresses_test.exs +++ b/apps/explorer_web/test/explorer_web/features/viewing_addresses_test.exs @@ -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, diff --git a/apps/explorer_web/test/explorer_web/features/viewing_transactions_test.exs b/apps/explorer_web/test/explorer_web/features/viewing_transactions_test.exs index e9e83d73fd..7f69fe9084 100644 --- a/apps/explorer_web/test/explorer_web/features/viewing_transactions_test.exs +++ b/apps/explorer_web/test/explorer_web/features/viewing_transactions_test.exs @@ -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 =