Merge pull request #871 from poanetwork/865-fill-in-holes

Fill-in skipped blocks on page render for blocks list
pull/890/merge v1.0-beta
John Stamates 6 years ago committed by GitHub
commit a9be1d4da6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 62
      apps/block_scout_web/assets/__tests__/pages/block.js
  2. 39
      apps/block_scout_web/assets/__tests__/pages/chain.js
  3. 6
      apps/block_scout_web/assets/css/components/_tile.scss
  4. 29
      apps/block_scout_web/assets/js/pages/block.js
  5. 47
      apps/block_scout_web/assets/js/pages/chain.js
  6. 31
      apps/block_scout_web/assets/js/utils.js
  7. 2
      apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex
  8. 11
      apps/block_scout_web/lib/block_scout_web/views/address_view.ex
  9. 20
      apps/block_scout_web/priv/gettext/default.pot
  10. 22
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  11. 4
      apps/block_scout_web/test/block_scout_web/features/pages/address_page.ex
  12. 2
      apps/block_scout_web/test/block_scout_web/features/pages/block_list_page.ex
  13. 10
      apps/block_scout_web/test/block_scout_web/features/pages/chain_page.ex
  14. 31
      apps/block_scout_web/test/block_scout_web/features/viewing_blocks_test.exs
  15. 10
      apps/block_scout_web/test/block_scout_web/features/viewing_chain_test.exs

@ -1,6 +1,5 @@
import { reducer, initialState } from '../../js/pages/block' import { reducer, initialState } from '../../js/pages/block'
test('CHANNEL_DISCONNECTED', () => { test('CHANNEL_DISCONNECTED', () => {
const state = initialState const state = initialState
const action = { const action = {
@ -11,6 +10,61 @@ test('CHANNEL_DISCONNECTED', () => {
expect(output.channelDisconnected).toBe(true) expect(output.channelDisconnected).toBe(true)
}) })
describe('PAGE_LOAD', () => {
test('page 1 loads block numbers', () => {
const state = initialState
const action = {
type: 'PAGE_LOAD',
beyondPageOne: false,
blockNumbers: [2, 1]
}
const output = reducer(state, action)
expect(output.beyondPageOne).toBe(false)
expect(output.blockNumbers).toEqual([2, 1])
expect(output.skippedBlockNumbers).toEqual([])
})
test('page 2 loads block numbers', () => {
const state = initialState
const action = {
type: 'PAGE_LOAD',
beyondPageOne: true,
blockNumbers: [2, 1]
}
const output = reducer(state, action)
expect(output.beyondPageOne).toBe(true)
expect(output.blockNumbers).toEqual([2, 1])
expect(output.skippedBlockNumbers).toEqual([])
})
test('page 1 with skipped blocks', () => {
const state = initialState
const action = {
type: 'PAGE_LOAD',
beyondPageOne: false,
blockNumbers: [4, 1]
}
const output = reducer(state, action)
expect(output.beyondPageOne).toBe(false)
expect(output.blockNumbers).toEqual([4, 3, 2, 1])
expect(output.skippedBlockNumbers).toEqual([3, 2])
})
test('page 2 with skipped blocks', () => {
const state = initialState
const action = {
type: 'PAGE_LOAD',
beyondPageOne: true,
blockNumbers: [4, 1]
}
const output = reducer(state, action)
expect(output.beyondPageOne).toBe(true)
expect(output.blockNumbers).toEqual([4, 3, 2, 1])
expect(output.skippedBlockNumbers).toEqual([3, 2])
})
})
describe('RECEIVED_NEW_BLOCK', () => { describe('RECEIVED_NEW_BLOCK', () => {
test('receives new block', () => { test('receives new block', () => {
const action = { const action = {
@ -56,12 +110,12 @@ describe('RECEIVED_NEW_BLOCK', () => {
expect(output.newBlock).toBe('test5') expect(output.newBlock).toBe('test5')
expect(output.blockNumbers).toEqual([5, 4, 3, 2]) expect(output.blockNumbers).toEqual([5, 4, 3, 2])
expect(output.skippedBlockNumbers).toEqual([3, 4]) expect(output.skippedBlockNumbers).toEqual([4, 3])
}) })
test('replaces skipped block', () => { test('replaces skipped block', () => {
const state = Object.assign({}, initialState, { const state = Object.assign({}, initialState, {
blockNumbers: [5, 4, 3, 2, 1], blockNumbers: [5, 4, 3, 2, 1],
skippedBlockNumbers: [1, 3, 4] skippedBlockNumbers: [4, 3, 1]
}) })
const action = { const action = {
type: 'RECEIVED_NEW_BLOCK', type: 'RECEIVED_NEW_BLOCK',
@ -74,7 +128,7 @@ describe('RECEIVED_NEW_BLOCK', () => {
expect(output.newBlock).toBe('test3') expect(output.newBlock).toBe('test3')
expect(output.blockNumbers).toEqual([5, 4, 3, 2, 1]) expect(output.blockNumbers).toEqual([5, 4, 3, 2, 1])
expect(output.skippedBlockNumbers).toEqual([1, 4]) expect(output.skippedBlockNumbers).toEqual([4, 1])
}) })
test('replaces duplicated block', () => { test('replaces duplicated block', () => {
const state = Object.assign({}, initialState, { const state = Object.assign({}, initialState, {

@ -1,5 +1,30 @@
import { reducer, initialState } from '../../js/pages/chain' import { reducer, initialState } from '../../js/pages/chain'
describe('PAGE_LOAD', () => {
test('loads block numbers', () => {
const state = initialState
const action = {
type: 'PAGE_LOAD',
blockNumbers: [2, 1]
}
const output = reducer(state, action)
expect(output.blockNumbers).toEqual([2, 1])
expect(output.skippedBlockNumbers).toEqual([])
})
test('loads with skipped blocks', () => {
const state = initialState
const action = {
type: 'PAGE_LOAD',
blockNumbers: [4, 1]
}
const output = reducer(state, action)
expect(output.blockNumbers).toEqual([4, 3, 2, 1])
expect(output.skippedBlockNumbers).toEqual([3, 2])
})
})
test('RECEIVED_NEW_ADDRESS_COUNT', () => { test('RECEIVED_NEW_ADDRESS_COUNT', () => {
const state = Object.assign({}, initialState, { const state = Object.assign({}, initialState, {
addressCount: '1,000' addressCount: '1,000'
@ -54,12 +79,12 @@ describe('RECEIVED_NEW_BLOCK', () => {
expect(output.averageBlockTime).toEqual('5 seconds') expect(output.averageBlockTime).toEqual('5 seconds')
expect(output.newBlock).toBe('test5') expect(output.newBlock).toBe('test5')
expect(output.blockNumbers).toEqual([5, 4, 3, 2]) expect(output.blockNumbers).toEqual([5, 4, 3, 2])
expect(output.skippedBlockNumbers).toEqual([3, 4]) expect(output.skippedBlockNumbers).toEqual([4, 3])
}) })
test('replaces skipped block', () => { test('replaces skipped block', () => {
const state = Object.assign({}, initialState, { const state = Object.assign({}, initialState, {
blockNumbers: [4, 3, 2, 1], blockNumbers: [4, 3, 2, 1],
skippedBlockNumbers: [1, 2, 3] skippedBlockNumbers: [3, 2, 1]
}) })
const action = { const action = {
type: 'RECEIVED_NEW_BLOCK', type: 'RECEIVED_NEW_BLOCK',
@ -74,7 +99,7 @@ describe('RECEIVED_NEW_BLOCK', () => {
expect(output.averageBlockTime).toEqual('5 seconds') expect(output.averageBlockTime).toEqual('5 seconds')
expect(output.newBlock).toBe('test2') expect(output.newBlock).toBe('test2')
expect(output.blockNumbers).toEqual([4, 3, 2, 1]) expect(output.blockNumbers).toEqual([4, 3, 2, 1])
expect(output.skippedBlockNumbers).toEqual([1, 3]) expect(output.skippedBlockNumbers).toEqual([3, 1])
}) })
test('replaces duplicated block', () => { test('replaces duplicated block', () => {
const state = Object.assign({}, initialState, { const state = Object.assign({}, initialState, {
@ -116,7 +141,7 @@ describe('RECEIVED_NEW_BLOCK', () => {
test('only tracks 4 blocks based on page display limit', () => { test('only tracks 4 blocks based on page display limit', () => {
const state = Object.assign({}, initialState, { const state = Object.assign({}, initialState, {
blockNumbers: [5, 4, 3, 2], blockNumbers: [5, 4, 3, 2],
skippedBlockNumbers: [2, 3, 4] skippedBlockNumbers: [4, 3, 2]
}) })
const action = { const action = {
type: 'RECEIVED_NEW_BLOCK', type: 'RECEIVED_NEW_BLOCK',
@ -129,12 +154,12 @@ describe('RECEIVED_NEW_BLOCK', () => {
expect(output.newBlock).toBe('test6') expect(output.newBlock).toBe('test6')
expect(output.blockNumbers).toEqual([6, 5, 4, 3]) expect(output.blockNumbers).toEqual([6, 5, 4, 3])
expect(output.skippedBlockNumbers).toEqual([3, 4]) expect(output.skippedBlockNumbers).toEqual([4, 3])
}) })
test('skipped blocks list replaced when another block comes in with +3 blockheight', () => { test('skipped blocks list replaced when another block comes in with +3 blockheight', () => {
const state = Object.assign({}, initialState, { const state = Object.assign({}, initialState, {
blockNumbers: [5, 4, 3, 2], blockNumbers: [5, 4, 3, 2],
skippedBlockNumbers: [2, 3, 4] skippedBlockNumbers: [4, 3, 2]
}) })
const action = { const action = {
type: 'RECEIVED_NEW_BLOCK', type: 'RECEIVED_NEW_BLOCK',
@ -147,7 +172,7 @@ describe('RECEIVED_NEW_BLOCK', () => {
expect(output.newBlock).toBe('test10') expect(output.newBlock).toBe('test10')
expect(output.blockNumbers).toEqual([10, 9, 8, 7]) expect(output.blockNumbers).toEqual([10, 9, 8, 7])
expect(output.skippedBlockNumbers).toEqual([7, 8, 9]) expect(output.skippedBlockNumbers).toEqual([9, 8, 7])
}) })
}) })

@ -17,7 +17,7 @@
&-type { &-type {
&-Block { &-block {
border-left: 4px solid $indigo; border-left: 4px solid $indigo;
.tile-label { .tile-label {
@ -25,7 +25,7 @@
} }
} }
&-Uncle { &-uncle {
border-left: 4px solid $cyan; border-left: 4px solid $cyan;
.tile-label { .tile-label {
@ -33,7 +33,7 @@
} }
} }
&-Reorg { &-reorg {
border-left: 4px solid $purple; border-left: 4px solid $purple;
.tile-label { .tile-label {

@ -4,7 +4,7 @@ import URI from 'urijs'
import humps from 'humps' import humps from 'humps'
import socket from '../socket' import socket from '../socket'
import { updateAllAges } from '../lib/from_now' import { updateAllAges } from '../lib/from_now'
import { initRedux, prependWithClingBottom } from '../utils' import { buildFullBlockList, initRedux, beforeWithClingBottom, skippedBlockListBuilder } from '../utils'
export const initialState = { export const initialState = {
blockNumbers: [], blockNumbers: [],
@ -18,9 +18,12 @@ export const initialState = {
export function reducer (state = initialState, action) { export function reducer (state = initialState, action) {
switch (action.type) { switch (action.type) {
case 'PAGE_LOAD': { case 'PAGE_LOAD': {
const blockNumbers = buildFullBlockList(action.blockNumbers)
const skippedBlockNumbers = _.difference(blockNumbers, action.blockNumbers)
return Object.assign({}, state, { return Object.assign({}, state, {
beyondPageOne: action.beyondPageOne, beyondPageOne: action.beyondPageOne,
blockNumbers: action.blockNumbers blockNumbers,
skippedBlockNumbers
}) })
} }
case 'CHANNEL_DISCONNECTED': { case 'CHANNEL_DISCONNECTED': {
@ -43,9 +46,7 @@ export function reducer (state = initialState, action) {
} else { } else {
let skippedBlockNumbers = state.skippedBlockNumbers.slice(0) let skippedBlockNumbers = state.skippedBlockNumbers.slice(0)
if (blockNumber > state.blockNumbers[0] + 1) { if (blockNumber > state.blockNumbers[0] + 1) {
for (let i = state.blockNumbers[0] + 1; i < blockNumber; i++) { skippedBlockListBuilder(skippedBlockNumbers, blockNumber, state.blockNumbers[0])
skippedBlockNumbers.push(i)
}
} }
const newBlockNumbers = _.chain([blockNumber]) const newBlockNumbers = _.chain([blockNumber])
.union(skippedBlockNumbers, state.blockNumbers) .union(skippedBlockNumbers, state.blockNumbers)
@ -85,22 +86,21 @@ if ($blockListPage.length) {
}, },
render (state, oldState) { render (state, oldState) {
const $channelDisconnected = $('[data-selector="channel-disconnected-message"]') const $channelDisconnected = $('[data-selector="channel-disconnected-message"]')
const $blocksList = $('[data-selector="blocks-list"]') const skippedBlockNumbers = _.difference(state.skippedBlockNumbers, oldState.skippedBlockNumbers)
if (state.channelDisconnected) $channelDisconnected.show() if (state.channelDisconnected) $channelDisconnected.show()
if (oldState.newBlock !== state.newBlock || (state.replaceBlock && oldState.replaceBlock !== state.replaceBlock)) { if ((state.newBlock && oldState.newBlock !== state.newBlock) || skippedBlockNumbers.length) {
if (state.replaceBlock && oldState.replaceBlock !== state.replaceBlock) { if (state.replaceBlock && oldState.replaceBlock !== state.replaceBlock) {
const $replaceBlock = $(`[data-block-number="${state.replaceBlock}"]`) const $replaceBlock = $(`[data-block-number="${state.replaceBlock}"]`)
$replaceBlock.addClass('shrink-out') $replaceBlock.addClass('shrink-out')
setTimeout(() => $replaceBlock.replaceWith(state.newBlock), 400) setTimeout(() => $replaceBlock.replaceWith(state.newBlock), 400)
} else { } else {
if (oldState.skippedBlockNumbers !== state.skippedBlockNumbers) { if (skippedBlockNumbers.length) {
const newSkippedBlockNumbers = _.difference(state.skippedBlockNumbers, oldState.skippedBlockNumbers) _.forEachRight(skippedBlockNumbers, (skippedBlockNumber) => {
_.map(newSkippedBlockNumbers, (skippedBlockNumber) => { beforeWithClingBottom($(`[data-block-number="${skippedBlockNumber - 1}"]`), placeHolderBlock(skippedBlockNumber))
prependWithClingBottom($blocksList, placeHolderBlock(skippedBlockNumber))
}) })
} }
prependWithClingBottom($blocksList, state.newBlock) beforeWithClingBottom($(`[data-block-number="${state.blockNumbers[0] - 1}"]`), state.newBlock)
} }
updateAllAges() updateAllAges()
} }
@ -110,11 +110,10 @@ if ($blockListPage.length) {
function placeHolderBlock (blockNumber) { function placeHolderBlock (blockNumber) {
return ` return `
<div class="my-3"> <div class="my-3" style="height: 98px;" data-selector="place-holder" data-block-number="${blockNumber}">
<div <div
class="tile tile-type-block d-flex align-items-center fade-up" class="tile tile-type-block d-flex align-items-center fade-up"
data-selector="place-holder" style="height: 98px;"
data-block-number="${blockNumber}"
> >
<span class="loading-spinner-small ml-1 mr-4"> <span class="loading-spinner-small ml-1 mr-4">
<span class="loading-spinner-block-1"></span> <span class="loading-spinner-block-1"></span>

@ -5,7 +5,7 @@ import numeral from 'numeral'
import socket from '../socket' import socket from '../socket'
import { updateAllAges } from '../lib/from_now' import { updateAllAges } from '../lib/from_now'
import { exchangeRateChannel, formatUsdValue } from '../lib/currency' import { exchangeRateChannel, formatUsdValue } from '../lib/currency'
import { batchChannel, initRedux, slideDownPrepend } from '../utils' import { batchChannel, buildFullBlockList, initRedux, skippedBlockListBuilder, slideDownPrepend } from '../utils'
import { createMarketHistoryChart } from '../lib/market_history_chart' import { createMarketHistoryChart } from '../lib/market_history_chart'
const BATCH_THRESHOLD = 10 const BATCH_THRESHOLD = 10
@ -28,9 +28,13 @@ export const initialState = {
export function reducer (state = initialState, action) { export function reducer (state = initialState, action) {
switch (action.type) { switch (action.type) {
case 'PAGE_LOAD': { case 'PAGE_LOAD': {
const fullBlockNumberList = buildFullBlockList(action.blockNumbers)
const fullSkippedBlockNumberList = _.difference(fullBlockNumberList, action.blockNumbers)
const blockNumbers = fullBlockNumberList.slice(0, 4)
return Object.assign({}, state, { return Object.assign({}, state, {
blockNumbers: action.blockNumbers, blockNumbers,
transactionCount: numeral(action.transactionCount).value() transactionCount: numeral(action.transactionCount).value(),
skippedBlockNumbers: _.intersection(fullSkippedBlockNumberList, blockNumbers)
}) })
} }
case 'RECEIVED_NEW_ADDRESS_COUNT': { case 'RECEIVED_NEW_ADDRESS_COUNT': {
@ -52,21 +56,18 @@ export function reducer (state = initialState, action) {
} else { } else {
let skippedBlockNumbers = state.skippedBlockNumbers.slice(0) let skippedBlockNumbers = state.skippedBlockNumbers.slice(0)
if (blockNumber > state.blockNumbers[0] + 1) { if (blockNumber > state.blockNumbers[0] + 1) {
let lastPlaceholder = state.blockNumbers[0] + 1 let oldestBlock = state.blockNumbers[0]
if (blockNumber - lastPlaceholder > 3) { if (blockNumber - oldestBlock >= 3) {
lastPlaceholder = blockNumber - 3
skippedBlockNumbers = [] skippedBlockNumbers = []
if (blockNumber - oldestBlock > 3) oldestBlock = blockNumber - 4
} }
for (let i = lastPlaceholder; i < blockNumber; i++) { skippedBlockListBuilder(skippedBlockNumbers, blockNumber, oldestBlock)
skippedBlockNumbers.push(i)
}
} }
const newBlockNumbers = _.chain([blockNumber]) const newBlockNumbers = _.chain([blockNumber])
.union(skippedBlockNumbers, state.blockNumbers) .union(skippedBlockNumbers, state.blockNumbers)
.orderBy([], ['desc']) .orderBy([], ['desc'])
.slice(0, 4) .slice(0, 4)
.value() .value()
const newSkippedBlockNumbers = _.intersection(skippedBlockNumbers, newBlockNumbers) const newSkippedBlockNumbers = _.intersection(skippedBlockNumbers, newBlockNumbers)
return Object.assign({}, state, { return Object.assign({}, state, {
averageBlockTime: action.msg.averageBlockTime, averageBlockTime: action.msg.averageBlockTime,
@ -142,6 +143,8 @@ if ($chainDetailsPage.length) {
const $marketCap = $('[data-selector="market-cap"]') const $marketCap = $('[data-selector="market-cap"]')
const $transactionsList = $('[data-selector="transactions-list"]') const $transactionsList = $('[data-selector="transactions-list"]')
const $transactionCount = $('[data-selector="transaction-count"]') const $transactionCount = $('[data-selector="transaction-count"]')
const newTransactions = _.difference(state.newTransactions, oldState.newTransactions)
const skippedBlockNumbers = _.difference(state.skippedBlockNumbers, oldState.skippedBlockNumbers)
if (oldState.addressCount !== state.addressCount) { if (oldState.addressCount !== state.addressCount) {
$addressCount.empty().append(state.addressCount) $addressCount.empty().append(state.addressCount)
@ -152,24 +155,23 @@ if ($chainDetailsPage.length) {
if (oldState.usdMarketCap !== state.usdMarketCap) { if (oldState.usdMarketCap !== state.usdMarketCap) {
$marketCap.empty().append(formatUsdValue(state.usdMarketCap)) $marketCap.empty().append(formatUsdValue(state.usdMarketCap))
} }
if (state.newBlock && oldState.newBlock !== state.newBlock) { if ((state.newBlock && oldState.newBlock !== state.newBlock) || skippedBlockNumbers.length) {
if (state.replaceBlock && oldState.replaceBlock !== state.replaceBlock) { if (state.replaceBlock && oldState.replaceBlock !== state.replaceBlock) {
const $replaceBlock = $(`[data-block-number="${state.replaceBlock}"]`) const $replaceBlock = $(`[data-block-number="${state.replaceBlock}"]`)
$replaceBlock.addClass('shrink-out') $replaceBlock.addClass('shrink-out')
setTimeout(() => $replaceBlock.replaceWith(state.newBlock), 400) setTimeout(() => $replaceBlock.replaceWith(state.newBlock), 400)
} else { } else {
if (oldState.skippedBlockNumbers !== state.skippedBlockNumbers) { if (state.newBlock) {
const newSkippedBlockNumbers = _.chain(state.skippedBlockNumbers)
.difference(oldState.skippedBlockNumbers)
.intersection(state.blockNumbers)
.value()
_.map(newSkippedBlockNumbers, (skippedBlockNumber) => {
$blockList.children().last().remove() $blockList.children().last().remove()
$blockList.prepend(placeHolderBlock(skippedBlockNumber)) $blockList.prepend(newBlockHtml(state.newBlock))
})
} }
if (skippedBlockNumbers.length) {
const newSkippedBlockNumbers = _.intersection(skippedBlockNumbers, state.blockNumbers)
_.each(newSkippedBlockNumbers, (skippedBlockNumber) => {
$blockList.children().last().remove() $blockList.children().last().remove()
$blockList.prepend(newBlockHtml(state.newBlock)) $(`[data-block-number="${skippedBlockNumber + 1}"]`).parent().after(placeHolderBlock(skippedBlockNumber))
})
}
} }
updateAllAges() updateAllAges()
} }
@ -180,7 +182,7 @@ if ($chainDetailsPage.length) {
} else { } else {
$channelBatching.hide() $channelBatching.hide()
} }
if (oldState.newTransactions !== state.newTransactions) { if (newTransactions.length) {
const newTransactionsToInsert = state.newTransactions.slice(oldState.newTransactions.length) const newTransactionsToInsert = state.newTransactions.slice(oldState.newTransactions.length)
$transactionsList $transactionsList
.children() .children()
@ -190,7 +192,6 @@ if ($chainDetailsPage.length) {
updateAllAges() updateAllAges()
} }
if (oldState.availableSupply !== state.availableSupply || oldState.marketHistoryData !== state.marketHistoryData) { if (oldState.availableSupply !== state.availableSupply || oldState.marketHistoryData !== state.marketHistoryData) {
chart.update(state.availableSupply, state.marketHistoryData) chart.update(state.availableSupply, state.marketHistoryData)
} }
@ -214,9 +215,9 @@ function placeHolderBlock (blockNumber) {
> >
<div <div
class="tile tile-type-block d-flex align-items-center fade-up" class="tile tile-type-block d-flex align-items-center fade-up"
style="height: 100px;"
data-selector="place-holder" data-selector="place-holder"
data-block-number="${blockNumber}" data-block-number="${blockNumber}"
style="height: 100px;"
> >
<span class="loading-spinner-small ml-1 mr-4"> <span class="loading-spinner-small ml-1 mr-4">
<span class="loading-spinner-block-1"></span> <span class="loading-spinner-block-1"></span>

@ -14,6 +14,12 @@ export function batchChannel (func) {
} }
} }
export function buildFullBlockList (blockNumbers) {
const newestBlock = _.first(blockNumbers)
const oldestBlock = _.last(blockNumbers)
return skippedBlockListBuilder([], newestBlock + 1, oldestBlock - 1)
}
export function initRedux (reducer, { main, render, debug } = {}) { export function initRedux (reducer, { main, render, debug } = {}) {
if (!reducer) { if (!reducer) {
console.error('initRedux: You need a reducer to initialize Redux.') console.error('initRedux: You need a reducer to initialize Redux.')
@ -34,16 +40,33 @@ export function initRedux (reducer, { main, render, debug } = {}) {
if (main) main(store) if (main) main(store)
} }
export function skippedBlockListBuilder (skippedBlockNumbers, newestBlock, oldestBlock) {
for (let i = newestBlock - 1; i > oldestBlock; i--) skippedBlockNumbers.push(i)
return skippedBlockNumbers
}
export function slideDownPrepend ($el, content, callback) { export function slideDownPrepend ($el, content, callback) {
const $content = $(content) const $content = $(content)
$el.prepend($content.hide()) $el.prepend($content.hide())
$content.slideDown({ complete: callback }) $content.slideDown({ complete: callback })
} }
export function slideDownBefore ($el, content, callback) {
const $content = $(content)
$el.before($content.hide())
$content.slideDown({ complete: callback })
}
export function prependWithClingBottom ($el, content) { export function prependWithClingBottom ($el, content) {
return slideDownPrepend($el, content, clingBottom($el, content))
}
export function beforeWithClingBottom ($el, content) {
return slideDownBefore($el, content, clingBottom($el, content))
}
function clingBottom ($el, content) {
function userAtTop () { function userAtTop () {
return window.scrollY < $('[data-selector="navbar"]').outerHeight() return window.scrollY < $('[data-selector="navbar"]').outerHeight()
} }
if (userAtTop()) return slideDownPrepend($el, content) if (userAtTop()) return true
let isAnimating let isAnimating
function setIsAnimating () { function setIsAnimating () {
@ -73,8 +96,10 @@ export function prependWithClingBottom ($el, content) {
$el.off('animationend animationcancel', stopClinging) $el.off('animationend animationcancel', stopClinging)
} }
return slideDownPrepend($el, content, () => { return {
function () {
$el.on('animationend animationcancel', stopClinging) $el.on('animationend animationcancel', stopClinging)
setTimeout(() => !isAnimating && stopClinging(), 100) setTimeout(() => !isAnimating && stopClinging(), 100)
}) }
}
} }

@ -31,7 +31,7 @@
</div> </div>
<script> <script>
window.localized = { window.localized = {
'Block Processing': '<%= gettext("Block Mined, awaiting processing...") %>', 'Block Processing': '<%= gettext("Block Mined, awaiting import...") %>',
'Less than': '<%= gettext("Less than") %>', 'Less than': '<%= gettext("Less than") %>',
'Market Cap': '<%= gettext("Market Cap") %>', 'Market Cap': '<%= gettext("Market Cap") %>',
'Price': '<%= gettext("Price") %>', 'Price': '<%= gettext("Price") %>',

@ -141,17 +141,6 @@ defmodule BlockScoutWeb.AddressView do
|> Base.encode64() |> Base.encode64()
end end
def render_partial(%{partial: partial, address: address, contract: contract?, truncate: truncate}) do
render(
partial,
address: address,
contract: contract?,
truncate: truncate
)
end
def render_partial(text), do: text
def smart_contract_verified?(%Address{smart_contract: %SmartContract{}}), do: true def smart_contract_verified?(%Address{smart_contract: %SmartContract{}}), do: true
def smart_contract_verified?(%Address{smart_contract: nil}), do: false def smart_contract_verified?(%Address{smart_contract: nil}), do: false

@ -157,7 +157,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_validation/index.html.eex:90 #: lib/block_scout_web/templates/address_validation/index.html.eex:90
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:122 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:122
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:141 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:141
#: lib/block_scout_web/views/address_view.ex:223 #: lib/block_scout_web/views/address_view.ex:212
msgid "Code" msgid "Code"
msgstr "" msgstr ""
@ -423,7 +423,7 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:14 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:14
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:43 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:43
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:10 #: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:10
#: lib/block_scout_web/views/address_view.ex:222 #: lib/block_scout_web/views/address_view.ex:211
#: lib/block_scout_web/views/transaction_view.ex:176 #: lib/block_scout_web/views/transaction_view.ex:176
msgid "Internal Transactions" msgid "Internal Transactions"
msgstr "" msgstr ""
@ -639,7 +639,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_validation/index.html.eex:53 #: lib/block_scout_web/templates/address_validation/index.html.eex:53
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:33 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:33
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:75 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:75
#: lib/block_scout_web/views/address_view.ex:224 #: lib/block_scout_web/views/address_view.ex:213
#: lib/block_scout_web/views/tokens/overview_view.ex:37 #: lib/block_scout_web/views/tokens/overview_view.ex:37
msgid "Read Contract" msgid "Read Contract"
msgstr "" msgstr ""
@ -839,7 +839,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_validation/index.html.eex:18 #: lib/block_scout_web/templates/address_validation/index.html.eex:18
#: lib/block_scout_web/templates/address_validation/index.html.eex:62 #: lib/block_scout_web/templates/address_validation/index.html.eex:62
#: lib/block_scout_web/templates/address_validation/index.html.eex:70 #: lib/block_scout_web/templates/address_validation/index.html.eex:70
#: lib/block_scout_web/views/address_view.ex:220 #: lib/block_scout_web/views/address_view.ex:209
msgid "Tokens" msgid "Tokens"
msgstr "" msgstr ""
@ -889,7 +889,7 @@ msgstr ""
#: lib/block_scout_web/templates/layout/_topnav.html.eex:35 #: lib/block_scout_web/templates/layout/_topnav.html.eex:35
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:56 #: lib/block_scout_web/templates/pending_transaction/index.html.eex:56
#: lib/block_scout_web/templates/transaction/index.html.eex:56 #: lib/block_scout_web/templates/transaction/index.html.eex:56
#: lib/block_scout_web/views/address_view.ex:221 #: lib/block_scout_web/views/address_view.ex:210
msgid "Transactions" msgid "Transactions"
msgstr "" msgstr ""
@ -1056,11 +1056,6 @@ msgstr ""
msgid "%{subnetwork} %{network} Explorer" msgid "%{subnetwork} %{network} Explorer"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/app.html.eex:34
msgid "Block Mined, awaiting processing..."
msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_metatags.html.eex:3 #: lib/block_scout_web/templates/address/_metatags.html.eex:3
msgid "%{address} - %{subnetwork} Explorer" msgid "%{address} - %{subnetwork} Explorer"
@ -1161,3 +1156,8 @@ msgstr ""
#: lib/block_scout_web/templates/layout/_topnav.html.eex:23 #: lib/block_scout_web/templates/layout/_topnav.html.eex:23
msgid "Uncles" msgid "Uncles"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/app.html.eex:34
msgid "Block Mined, awaiting import..."
msgstr ""

@ -157,7 +157,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_validation/index.html.eex:90 #: lib/block_scout_web/templates/address_validation/index.html.eex:90
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:122 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:122
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:141 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:141
#: lib/block_scout_web/views/address_view.ex:223 #: lib/block_scout_web/views/address_view.ex:212
msgid "Code" msgid "Code"
msgstr "" msgstr ""
@ -423,7 +423,7 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:14 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:14
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:43 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:43
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:10 #: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:10
#: lib/block_scout_web/views/address_view.ex:222 #: lib/block_scout_web/views/address_view.ex:211
#: lib/block_scout_web/views/transaction_view.ex:176 #: lib/block_scout_web/views/transaction_view.ex:176
msgid "Internal Transactions" msgid "Internal Transactions"
msgstr "" msgstr ""
@ -639,7 +639,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_validation/index.html.eex:53 #: lib/block_scout_web/templates/address_validation/index.html.eex:53
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:33 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:33
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:75 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:75
#: lib/block_scout_web/views/address_view.ex:224 #: lib/block_scout_web/views/address_view.ex:213
#: lib/block_scout_web/views/tokens/overview_view.ex:37 #: lib/block_scout_web/views/tokens/overview_view.ex:37
msgid "Read Contract" msgid "Read Contract"
msgstr "" msgstr ""
@ -839,7 +839,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_validation/index.html.eex:18 #: lib/block_scout_web/templates/address_validation/index.html.eex:18
#: lib/block_scout_web/templates/address_validation/index.html.eex:62 #: lib/block_scout_web/templates/address_validation/index.html.eex:62
#: lib/block_scout_web/templates/address_validation/index.html.eex:70 #: lib/block_scout_web/templates/address_validation/index.html.eex:70
#: lib/block_scout_web/views/address_view.ex:220 #: lib/block_scout_web/views/address_view.ex:209
msgid "Tokens" msgid "Tokens"
msgstr "" msgstr ""
@ -889,7 +889,7 @@ msgstr ""
#: lib/block_scout_web/templates/layout/_topnav.html.eex:35 #: lib/block_scout_web/templates/layout/_topnav.html.eex:35
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:56 #: lib/block_scout_web/templates/pending_transaction/index.html.eex:56
#: lib/block_scout_web/templates/transaction/index.html.eex:56 #: lib/block_scout_web/templates/transaction/index.html.eex:56
#: lib/block_scout_web/views/address_view.ex:221 #: lib/block_scout_web/views/address_view.ex:210
msgid "Transactions" msgid "Transactions"
msgstr "" msgstr ""
@ -1056,11 +1056,6 @@ msgstr ""
msgid "%{subnetwork} %{network} Explorer" msgid "%{subnetwork} %{network} Explorer"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/app.html.eex:34
msgid "Block Mined, awaiting processing..."
msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_metatags.html.eex:3 #: lib/block_scout_web/templates/address/_metatags.html.eex:3
msgid "%{address} - %{subnetwork} Explorer" msgid "%{address} - %{subnetwork} Explorer"
@ -1136,7 +1131,7 @@ msgstr ""
msgid "%{block_type}s" msgid "%{block_type}s"
msgstr "" msgstr ""
#, elixir-format, fuzzy #, elixir-format
#: lib/block_scout_web/templates/block/overview.html.eex:13 #: lib/block_scout_web/templates/block/overview.html.eex:13
msgid "Block Height: %{height}" msgid "Block Height: %{height}"
msgstr "" msgstr ""
@ -1161,3 +1156,8 @@ msgstr ""
#: lib/block_scout_web/templates/layout/_topnav.html.eex:23 #: lib/block_scout_web/templates/layout/_topnav.html.eex:23
msgid "Uncles" msgid "Uncles"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/app.html.eex:34
msgid "Block Mined, awaiting import..."
msgstr ""

@ -113,10 +113,6 @@ defmodule BlockScoutWeb.AddressPage do
css("[data-transaction-hash='#{hash}'] [data-test='address_hash_link'] [data-address-hash='#{address_hash}']") css("[data-transaction-hash='#{hash}'] [data-test='address_hash_link'] [data-address-hash='#{address_hash}']")
end end
def transaction_count do
css("[data-selector='transaction-count']")
end
def transaction_status(%Transaction{hash: transaction_hash}) do def transaction_status(%Transaction{hash: transaction_hash}) do
css("[data-transaction-hash='#{transaction_hash}'] [data-test='transaction_status']") css("[data-transaction-hash='#{transaction_hash}'] [data-test='transaction_status']")
end end

@ -20,7 +20,7 @@ defmodule BlockScoutWeb.BlockListPage do
end end
def block(%Block{number: block_number}) do def block(%Block{number: block_number}) do
css("[data-selector='block-tile'][data-block-number='#{block_number}']") css("[data-block-number='#{block_number}']")
end end
def place_holder_blocks(count) do def place_holder_blocks(count) do

@ -5,7 +5,11 @@ defmodule BlockScoutWeb.ChainPage do
import Wallaby.Query, only: [css: 1, css: 2] import Wallaby.Query, only: [css: 1, css: 2]
alias Explorer.Chain.Transaction alias Explorer.Chain.{Block, Transaction}
def block(%Block{number: block_number}) do
css("[data-block-number='#{block_number}']")
end
def blocks(count: count) do def blocks(count: count) do
css("[data-selector='chain-block']", count: count) css("[data-selector='chain-block']", count: count)
@ -15,6 +19,10 @@ defmodule BlockScoutWeb.ChainPage do
css("[data-test='contract-creation'] [data-address-hash='#{hash}']") css("[data-test='contract-creation'] [data-address-hash='#{hash}']")
end end
def place_holder_blocks(count) do
css("[data-selector='place-holder']", count: count)
end
def search(session, text) do def search(session, text) do
session session
|> fill_in(css("[data-test='search_input']"), with: text) |> fill_in(css("[data-test='search_input']"), with: text)

@ -1,7 +1,8 @@
defmodule BlockScoutWeb.ViewingBlocksTest do defmodule BlockScoutWeb.ViewingBlocksTest do
use BlockScoutWeb.FeatureCase, async: true use BlockScoutWeb.FeatureCase, async: true
alias BlockScoutWeb.{BlockListPage, BlockPage, Notifier} alias BlockScoutWeb.{BlockListPage, BlockPage}
alias Explorer.Chain.Block
setup do setup do
timestamp = Timex.now() |> Timex.shift(hours: -1) timestamp = Timex.now() |> Timex.shift(hours: -1)
@ -131,33 +132,13 @@ defmodule BlockScoutWeb.ViewingBlocksTest do
|> assert_has(BlockListPage.block(block)) |> assert_has(BlockListPage.block(block))
end end
test "inserts place holder blocks if out of order block received", %{session: session} do test "inserts place holder blocks on render for out of order blocks", %{session: session} do
BlockListPage.visit_page(session) insert(:block, number: 315)
block = insert(:block, number: 315)
Notifier.handle_event({:chain_event, :blocks, :realtime, [block]})
session session
|> assert_has(BlockListPage.block(block)) |> BlockListPage.visit_page()
|> assert_has(BlockListPage.place_holder_blocks(3)) |> assert_has(BlockListPage.block(%Block{number: 314}))
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, :realtime, [block]})
session
|> assert_has(BlockListPage.block(block))
|> assert_has(BlockListPage.place_holder_blocks(3)) |> assert_has(BlockListPage.place_holder_blocks(3))
skipped_block = insert(:block, number: 314)
Notifier.handle_event({:chain_event, :blocks, :realtime, [skipped_block]})
session
|> assert_has(BlockListPage.block(skipped_block))
|> assert_has(BlockListPage.place_holder_blocks(2))
end end
end end

@ -4,6 +4,7 @@ defmodule BlockScoutWeb.ViewingChainTest do
use BlockScoutWeb.FeatureCase, async: true use BlockScoutWeb.FeatureCase, async: true
alias BlockScoutWeb.{AddressPage, BlockPage, ChainPage, TransactionPage} alias BlockScoutWeb.{AddressPage, BlockPage, ChainPage, TransactionPage}
alias Explorer.Chain.Block
setup do setup do
Enum.map(401..404, &insert(:block, number: &1)) Enum.map(401..404, &insert(:block, number: &1))
@ -50,6 +51,15 @@ defmodule BlockScoutWeb.ViewingChainTest do
|> ChainPage.visit_page() |> ChainPage.visit_page()
|> assert_has(ChainPage.blocks(count: 4)) |> assert_has(ChainPage.blocks(count: 4))
end end
test "inserts place holder blocks on render for out of order blocks", %{session: session} do
insert(:block, number: 409)
session
|> ChainPage.visit_page()
|> assert_has(ChainPage.block(%Block{number: 408}))
|> assert_has(ChainPage.place_holder_blocks(3))
end
end end
describe "viewing transactions" do describe "viewing transactions" do

Loading…
Cancel
Save