Use place holders when receiving out of order blocks realtime

pull/847/head
Stamates 6 years ago
parent 5e9cb5e141
commit f1623c1286
  1. 60
      apps/block_scout_web/assets/__tests__/pages/block.js
  2. 11
      apps/block_scout_web/assets/__tests__/pages/transaction.js
  3. 69
      apps/block_scout_web/assets/js/pages/block.js
  4. 2
      apps/block_scout_web/lib/block_scout_web/templates/block/_tile.html.eex
  5. 8
      apps/block_scout_web/test/block_scout_web/features/pages/block_list_page.ex
  6. 31
      apps/block_scout_web/test/block_scout_web/features/viewing_blocks_test.exs

@ -1,13 +1,59 @@
import { reducer, initialState } from '../../js/pages/block'
test('RECEIVED_NEW_BLOCK', () => {
test('CHANNEL_DISCONNECTED', () => {
const state = initialState
const action = {
type: 'RECEIVED_NEW_BLOCK',
msg: {
blockHtml: "test"
}
type: 'CHANNEL_DISCONNECTED'
}
const output = reducer(initialState, action)
const output = reducer(state, action)
expect(output.channelDisconnected).toBe(true)
})
describe('RECEIVED_NEW_BLOCK', () => {
test('receives new block', () => {
const action = {
type: 'RECEIVED_NEW_BLOCK',
msg: {
blockHtml: 'test',
blockNumber: 1
}
}
const output = reducer(initialState, action)
expect(output.newBlock).toBe('test')
expect(output.currentBlockNumber).toBe(1)
})
test('on page 2+', () => {
const state = Object.assign({}, initialState, {
beyondPageOne: true
})
const action = {
type: 'RECEIVED_NEW_BLOCK',
msgs: [{
blockHtml: 'test'
}]
}
const output = reducer(state, action)
expect(output.newBlock).toBe(null)
})
test('inserts place holders if block received out of order', () => {
const state = Object.assign({}, initialState, {
currentBlockNumber: 2
})
const action = {
type: 'RECEIVED_NEW_BLOCK',
msg: {
blockHtml: 'test5',
blockNumber: 5
}
}
const output = reducer(state, action)
expect(output.newBlock).toBe("test")
expect(output.newBlock).toBe('test5')
expect(output.currentBlockNumber).toBe(5)
expect(output.skippedBlockNumbers).toEqual([3, 4])
})
})

@ -1,5 +1,16 @@
import { reducer, initialState } from '../../js/pages/transaction'
test('CHANNEL_DISCONNECTED', () => {
const state = initialState
const action = {
type: 'CHANNEL_DISCONNECTED'
}
const output = reducer(state, action)
expect(output.channelDisconnected).toBe(true)
expect(output.batchCountAccumulator).toBe(0)
})
test('RECEIVED_NEW_BLOCK', () => {
const state = { ...initialState, blockNumber: 1 }
const action = {

@ -1,4 +1,5 @@
import $ from 'jquery'
import _ from 'lodash'
import URI from 'urijs'
import humps from 'humps'
import socket from '../socket'
@ -8,14 +9,22 @@ import { initRedux, prependWithClingBottom } from '../utils'
export const initialState = {
beyondPageOne: null,
channelDisconnected: false,
newBlock: null
currentBlockNumber: null,
newBlock: null,
replaceBlock: null,
skippedBlockNumbers: []
}
export function reducer (state = initialState, action) {
switch (action.type) {
case 'PAGE_LOAD': {
let blockNumber = parseInt(state.currentBlockNumber)
if (!action.beyondPageOne) {
blockNumber = parseInt(action.highestBlockNumber)
}
return Object.assign({}, state, {
beyondPageOne: action.beyondPageOne
beyondPageOne: action.beyondPageOne,
currentBlockNumber: blockNumber
})
}
case 'CHANNEL_DISCONNECTED': {
@ -24,10 +33,24 @@ export function reducer (state = initialState, action) {
})
}
case 'RECEIVED_NEW_BLOCK': {
if (state.channelDisconnected) return state
if (state.channelDisconnected || state.beyondPageOne) return state
let skippedBlockNumbers = state.skippedBlockNumbers.slice(0)
let replaceBlock = null
const blockNumber = parseInt(action.msg.blockNumber)
if (blockNumber > state.currentBlockNumber + 1) {
for (let i = state.currentBlockNumber + 1; i < action.msg.blockNumber; i++) {
skippedBlockNumbers.push(i)
}
} else if (_.indexOf(skippedBlockNumbers, blockNumber) != -1) {
skippedBlockNumbers = _.without(skippedBlockNumbers, blockNumber)
replaceBlock = blockNumber
}
return Object.assign({}, state, {
newBlock: action.msg.blockHtml
currentBlockNumber: blockNumber > state.currentBlockNumber ? blockNumber : state.currentBlockNumber,
newBlock: action.msg.blockHtml,
replaceBlock,
skippedBlockNumbers
})
}
default:
@ -41,7 +64,8 @@ if ($blockListPage.length) {
main (store) {
const state = store.dispatch({
type: 'PAGE_LOAD',
beyondPageOne: !!humps.camelizeKeys(URI(window.location).query(true)).blockNumber
beyondPageOne: !!humps.camelizeKeys(URI(window.location).query(true)).blockNumber,
highestBlockNumber: $('[data-selector="block-number"]').filter(':first').text()
})
if (!state.beyondPageOne) {
const blocksChannel = socket.channel(`blocks:new_block`, {})
@ -58,9 +82,42 @@ if ($blockListPage.length) {
if (state.channelDisconnected) $channelDisconnected.show()
if (oldState.newBlock !== state.newBlock) {
prependWithClingBottom($blocksList, state.newBlock)
if (oldState.skippedBlockNumbers !== state.skippedBlockNumbers) {
const newSkippedBlockNumbers = _.difference(state.skippedBlockNumbers, oldState.skippedBlockNumbers)
if (state.replaceBlock) {
const $placeHolder = $(`[data-selector="place-holder"][data-block-number="${state.replaceBlock}"]`)
$placeHolder.addClass('shrink-out')
setTimeout(() => $placeHolder.slideUp({
complete: () => {
$placeHolder.replaceWith(state.newBlock)
}
}), 400)
} else {
_.map(newSkippedBlockNumbers, (skippedBlockNumber) => {
prependWithClingBottom($blocksList, placeHolderBlock(skippedBlockNumber))
})
prependWithClingBottom($blocksList, state.newBlock)
}
} else {
prependWithClingBottom($blocksList, state.newBlock)
}
updateAllAges()
}
}
})
}
function placeHolderBlock(blockNumber) {
return `
<div class="tile tile-type-block fade-up" data-selector="place-holder" data-block-number="${blockNumber}">
<div class="row">
<div class="col-md-6">
<span>${blockNumber}</span>
<div>
<div class="col-md-6">
Block Mined, awaiting import...
<div>
</div>
</div>
`
}

@ -6,7 +6,7 @@
@block,
class: "tile-title",
to: block_path(BlockScoutWeb.Endpoint, :show, @block),
"data-test": "block_number",
"data-selector": "block-number",
"data-block-number": to_string(@block.number)
) %>
<div>

@ -3,7 +3,7 @@ defmodule BlockScoutWeb.BlockListPage do
use Wallaby.DSL
import Wallaby.Query, only: [css: 1]
import Wallaby.Query, only: [css: 1, css: 2]
alias Explorer.Chain.Block
@ -12,6 +12,10 @@ defmodule BlockScoutWeb.BlockListPage do
end
def block(%Block{number: block_number}) do
css("[data-test='block_number'][data-block-number='#{block_number}']")
css("[data-selector='block-number'][data-block-number='#{block_number}']")
end
def place_holder_blocks(count) do
css("[data-selector='place-holder']", count: count)
end
end

@ -1,7 +1,7 @@
defmodule BlockScoutWeb.ViewingBlocksTest do
use BlockScoutWeb.FeatureCase, async: true
alias BlockScoutWeb.{BlockListPage, BlockPage}
alias BlockScoutWeb.{BlockListPage, BlockPage, Notifier}
setup do
timestamp = Timex.now() |> Timex.shift(hours: -1)
@ -35,6 +35,35 @@ defmodule BlockScoutWeb.ViewingBlocksTest do
|> assert_has(BlockPage.detail_number(block))
end
test "inserts place holder blocks if out of order block received", %{session: session} do
BlockListPage.visit_page(session)
block = insert(:block, number: 315)
Notifier.handle_event({:chain_event, :blocks, [block]})
session
|> assert_has(BlockListPage.block(block))
|> assert_has(BlockListPage.place_holder_blocks(3))
end
test "replaces place holder block if skipped block received", %{session: session} do
BlockListPage.visit_page(session)
block = insert(:block, number: 315)
Notifier.handle_event({:chain_event, :blocks, [block]})
session
|> assert_has(BlockListPage.block(block))
|> assert_has(BlockListPage.place_holder_blocks(3))
skipped_block = insert(:block, number: 314)
Notifier.handle_event({:chain_event, :blocks, [skipped_block]})
session
|> assert_has(BlockListPage.block(skipped_block))
|> assert_has(BlockListPage.place_holder_blocks(2))
end
test "block detail page has transactions", %{session: session} do
block = insert(:block, number: 42)

Loading…
Cancel
Save