Live reload blocks on homepage

Co-authored-by: Stamates <stamates@hotmail.com>
Co-authored-by: Tim Mecklem <timothy@mecklem.com>
pull/462/head
jimmay5469 6 years ago committed by Stamates
parent e3f6737e3a
commit c81cca4552
  1. 26
      apps/explorer_web/assets/__tests__/pages/chain.js
  2. 1
      apps/explorer_web/assets/js/app.js
  3. 2
      apps/explorer_web/assets/js/lib/from_now.js
  4. 1
      apps/explorer_web/assets/js/pages/address.js
  5. 48
      apps/explorer_web/assets/js/pages/chain.js
  6. 5
      apps/explorer_web/assets/js/router.js
  7. 33
      apps/explorer_web/lib/explorer_web/channels/block_channel.ex
  8. 1
      apps/explorer_web/lib/explorer_web/channels/user_socket.ex
  9. 9
      apps/explorer_web/lib/explorer_web/notifier.ex
  10. 4
      apps/explorer_web/lib/explorer_web/templates/chain/_block.html.eex
  11. 2
      apps/explorer_web/lib/explorer_web/templates/chain/show.html.eex
  12. 8
      apps/explorer_web/test/explorer_web/features/pages/home_page.ex
  13. 25
      apps/explorer_web/test/explorer_web/features/viewing_blocks_test.exs

@ -0,0 +1,26 @@
import { reducer, initialState } from '../../js/pages/chain'
test('CHANNEL_DISCONNECTED', () => {
const state = initialState
const action = {
type: 'CHANNEL_DISCONNECTED'
}
const output = reducer(state, action)
expect(output.channelDisconnected).toBe(true)
})
test('RECEIVED_NEW_BLOCK', () => {
const state = Object.assign({}, initialState, {
newBlock: 'last new block'
})
const action = {
type: 'RECEIVED_NEW_BLOCK',
msg: {
homepageBlockHtml: 'new block'
}
}
const output = reducer(state, action)
expect(output.newBlock).toEqual('new block')
})

@ -26,4 +26,5 @@ import './lib/tooltip'
import './lib/smart_contract/read_function'
import './pages/address'
import './pages/chain'
import './pages/transaction'

@ -11,7 +11,7 @@ moment.relativeTimeThreshold('m', 60)
moment.relativeTimeThreshold('s', 60)
moment.relativeTimeThreshold('ss', 1)
function updateAllAges () {
export function updateAllAges () {
$('[data-from-now]').each((i, el) => tryUpdateAge(el))
}
function tryUpdateAge (el) {

@ -73,7 +73,6 @@ export function reducer (state = initialState, action) {
}
router.when('/addresses/:addressHash').then((params) => initRedux(reducer, {
debug: true,
main (store) {
const { addressHash, blockNumber, locale } = params
const channel = socket.channel(`addresses:${addressHash}`, {})

@ -0,0 +1,48 @@
import $ from 'jquery'
import humps from 'humps'
import router from '../router'
import socket from '../socket'
import { updateAllAges } from '../lib/from_now'
import { initRedux } from '../utils'
export const initialState = {
newBlock: null,
channelDisconnected: false
}
export function reducer (state = initialState, action) {
switch (action.type) {
case 'CHANNEL_DISCONNECTED': {
return Object.assign({}, state, {
channelDisconnected: true
})
}
case 'RECEIVED_NEW_BLOCK': {
return Object.assign({}, state, {
newBlock: humps.camelizeKeys(action.msg).homepageBlockHtml
})
}
default:
return state
}
}
router.when('', { exactPathMatch: true }).then(() => initRedux(reducer, {
main (store) {
const blocksChannel = socket.channel(`blocks:new_block`)
blocksChannel.join()
.receive('ok', resp => { console.log('Joined successfully', 'blocks:new_block', resp) })
.receive('error', resp => { console.log('Unable to join', 'blocks:new_block', resp) })
blocksChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' }))
blocksChannel.on('new_block', msg => store.dispatch({ type: 'RECEIVED_NEW_BLOCK', msg }))
},
render (state, oldState) {
const $blockList = $('[data-selector="chain-block-list"]')
if (oldState.newBlock !== state.newBlock) {
$blockList.children().last().remove()
$blockList.prepend(state.newBlock)
updateAllAges()
}
}
}))

@ -6,9 +6,10 @@ const { locale } = Path.createPath('/:locale').partialTest(window.location.pathn
export default {
locale,
when (pattern) {
when (pattern, { exactPathMatch } = { exactPathMatch: false }) {
return new Promise((resolve) => {
const match = Path.createPath(`/:locale${pattern}`).partialTest(window.location.pathname)
const path = Path.createPath(`/:locale${pattern}`)
const match = exactPathMatch ? path.test(window.location.pathname) : path.partialTest(window.location.pathname)
if (match) {
const routeParams = humps.camelizeKeys(match)
const queryParams = humps.camelizeKeys(URI(window.location).query(true))

@ -0,0 +1,33 @@
defmodule ExplorerWeb.BlockChannel do
@moduledoc """
Establishes pub/sub channel for live updates of block events.
"""
use ExplorerWeb, :channel
alias ExplorerWeb.ChainView
alias Phoenix.View
intercept(["new_block"])
def join("blocks:new_block", _params, socket) do
{:ok, %{}, socket}
end
def handle_out("new_block", %{block: block}, socket) do
Gettext.put_locale(ExplorerWeb.Gettext, socket.assigns.locale)
rendered_homepage_block =
View.render_to_string(
ChainView,
"_block.html",
locale: socket.assigns.locale,
block: block
)
push(socket, "new_block", %{
homepage_block_html: rendered_homepage_block
})
{:noreply, socket}
end
end

@ -2,6 +2,7 @@ defmodule ExplorerWeb.UserSocket do
use Phoenix.Socket
channel("addresses:*", ExplorerWeb.AddressChannel)
channel("blocks:*", ExplorerWeb.BlockChannel)
channel("transactions:*", ExplorerWeb.TransactionChannel)
transport(:websocket, Phoenix.Transports.WebSocket, timeout: 45_000)

@ -3,7 +3,7 @@ defmodule ExplorerWeb.Notifier do
Responds to events from EventHandler by sending appropriate channel updates to front-end.
"""
alias Explorer.{Chain, Market}
alias Explorer.{Chain, Market, Repo}
alias Explorer.Chain.Address
alias Explorer.ExchangeRates.Token
alias ExplorerWeb.Endpoint
@ -17,6 +17,8 @@ defmodule ExplorerWeb.Notifier do
def handle_event({:chain_event, :blocks, blocks}) do
max_numbered_block = Enum.max_by(blocks, & &1.number).number
Endpoint.broadcast("transactions:confirmations", "update", %{block_number: max_numbered_block})
Enum.each(blocks, &broadcast_block/1)
end
def handle_event({:chain_event, :transactions, transaction_hashes}) do
@ -34,6 +36,11 @@ defmodule ExplorerWeb.Notifier do
})
end
defp broadcast_block(block) do
preloaded_block = Repo.preload(block, [:miner, :transactions])
Endpoint.broadcast("blocks:new_block", "new_block", %{block: preloaded_block})
end
defp broadcast_transaction(transaction) do
Endpoint.broadcast("addresses:#{transaction.from_address_hash}", "transaction", %{
address: transaction.from_address,

@ -1,5 +1,5 @@
<div class="col-sm-3">
<div class="tile d-flex flex-column" data-test="chain_block">
<div class="col-sm-3" data-selector="chain-block" data-block-number="<%= @block.number %>">
<div class="tile d-flex flex-column">
<%= link(@block, to: block_path(ExplorerWeb.Endpoint, :show, @locale, @block), class: "tile-title") %>
<div>
<%= @block.transactions |> Enum.count %> Transactions

@ -56,7 +56,7 @@
<div class="card-body">
<%= link(gettext("View All Blocks →"), to: block_path(@conn, :index, Gettext.get_locale), class: "button button--secondary button--xsmall float-right") %>
<h2 class="card-title"><%= gettext "Blocks" %></h2>
<div class="row">
<div class="row" data-selector="chain-block-list">
<%= for block <- @chain.blocks do %>
<%= render ExplorerWeb.ChainView, "_block.html", locale: @locale, block: block %>
<% end %>

@ -5,10 +5,14 @@ defmodule ExplorerWeb.HomePage do
import Wallaby.Query, only: [css: 1, css: 2]
alias Explorer.Chain.{InternalTransaction, Transaction}
alias Explorer.Chain.{Block, InternalTransaction, Transaction}
def block(%Block{number: number}) do
css("[data-selector='chain-block'][data-block-number='#{number}']")
end
def blocks(count: count) do
css("[data-test='chain_block']", count: count)
css("[data-selector='chain-block']", count: count)
end
def contract_creation(%InternalTransaction{created_contract_address_hash: hash}) do

@ -1,13 +1,13 @@
defmodule ExplorerWeb.ViewingBlocksTest do
use ExplorerWeb.FeatureCase, async: true
alias ExplorerWeb.{BlockListPage, BlockPage, HomePage}
alias ExplorerWeb.{BlockListPage, BlockPage, HomePage, Notifier}
setup do
timestamp = Timex.now() |> Timex.shift(hours: -1)
Enum.map(307..310, &insert(:block, number: &1, timestamp: timestamp, gas_used: 10))
[oldest_block | _] = Enum.map(308..310, &insert(:block, number: &1, timestamp: timestamp, gas_used: 10))
block =
newest_block =
insert(:block, %{
gas_limit: 5_030_101,
gas_used: 1_010_101,
@ -17,7 +17,7 @@ defmodule ExplorerWeb.ViewingBlocksTest do
timestamp: timestamp
})
{:ok, block: block}
{:ok, first_shown_block: newest_block, last_shown_block: oldest_block}
end
test "viewing blocks on the home page", %{session: session} do
@ -26,6 +26,21 @@ defmodule ExplorerWeb.ViewingBlocksTest do
|> assert_has(HomePage.blocks(count: 4))
end
test "viewing new blocks via live update on homepage", %{session: session, last_shown_block: last_shown_block} do
session
|> HomePage.visit_page()
|> assert_has(HomePage.blocks(count: 4))
block = insert(:block, number: 42)
Notifier.handle_event({:chain_event, :blocks, [block]})
session
|> assert_has(HomePage.blocks(count: 4))
|> assert_has(HomePage.block(block))
|> refute_has(HomePage.block(last_shown_block))
end
test "search for blocks from home page", %{session: session} do
block = insert(:block, number: 42)
@ -73,7 +88,7 @@ defmodule ExplorerWeb.ViewingBlocksTest do
|> assert_has(BlockPage.contract_creation(internal_transaction))
end
test "viewing the blocks index page", %{block: block, session: session} do
test "viewing the blocks index page", %{first_shown_block: block, session: session} do
session
|> BlockListPage.visit_page()
|> assert_has(BlockListPage.block(block))

Loading…
Cancel
Save