move internal transactions real time to its own file and use redux for async loading

pull/1169/head
Gustavo Santos Ferreira 6 years ago
parent 86b6336ea6
commit 0d65bbda3c
  1. 118
      apps/block_scout_web/assets/__tests__/pages/address.js
  2. 147
      apps/block_scout_web/assets/__tests__/pages/address/internal_transactions.js
  3. 1
      apps/block_scout_web/assets/js/app.js
  4. 67
      apps/block_scout_web/assets/js/pages/address.js
  5. 96
      apps/block_scout_web/assets/js/pages/address/internal_transactions.js

@ -26,124 +26,6 @@ describe('RECEIVED_NEW_BLOCK', () => {
})
})
describe('RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH', () => {
test('with new internal transaction', () => {
const state = Object.assign({}, initialState, {
internalTransactions: [{ internalTransactionHtml: 'test 1' }]
})
const action = {
type: 'RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH',
msgs: [{ internalTransactionHtml: 'test 2' }]
}
const output = reducer(state, action)
expect(output.internalTransactions).toEqual([
{ internalTransactionHtml: 'test 2' },
{ internalTransactionHtml: 'test 1' }
])
})
test('with batch of new internal transactions', () => {
const state = Object.assign({}, initialState, {
internalTransactions: [{ internalTransactionHtml: 'test 1' }]
})
const action = {
type: 'RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH',
msgs: [
{ internalTransactionHtml: 'test 2' },
{ internalTransactionHtml: 'test 3' },
{ internalTransactionHtml: 'test 4' },
{ internalTransactionHtml: 'test 5' },
{ internalTransactionHtml: 'test 6' },
{ internalTransactionHtml: 'test 7' },
{ internalTransactionHtml: 'test 8' },
{ internalTransactionHtml: 'test 9' },
{ internalTransactionHtml: 'test 10' },
{ internalTransactionHtml: 'test 11' },
{ internalTransactionHtml: 'test 12' },
{ internalTransactionHtml: 'test 13' }
]
}
const output = reducer(state, action)
expect(output.internalTransactions).toEqual([
{ internalTransactionHtml: 'test 1' }
])
expect(output.internalTransactionsBatch).toEqual([
{ internalTransactionHtml: 'test 13' },
{ internalTransactionHtml: 'test 12' },
{ internalTransactionHtml: 'test 11' },
{ internalTransactionHtml: 'test 10' },
{ internalTransactionHtml: 'test 9' },
{ internalTransactionHtml: 'test 8' },
{ internalTransactionHtml: 'test 7' },
{ internalTransactionHtml: 'test 6' },
{ internalTransactionHtml: 'test 5' },
{ internalTransactionHtml: 'test 4' },
{ internalTransactionHtml: 'test 3' },
{ internalTransactionHtml: 'test 2' },
])
})
test('after batch of new internal transactions', () => {
const state = Object.assign({}, initialState, {
internalTransactionsBatch: [{ internalTransactionHtml: 'test 1' }]
})
const action = {
type: 'RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH',
msgs: [
{ internalTransactionHtml: 'test 2' }
]
}
const output = reducer(state, action)
expect(output.internalTransactionsBatch).toEqual([
{ internalTransactionHtml: 'test 2' },
{ internalTransactionHtml: 'test 1' }
])
})
test('when channel has been disconnected', () => {
const state = Object.assign({}, initialState, {
channelDisconnected: true,
internalTransactions: [{ internalTransactionHtml: 'test 1' }]
})
const action = {
type: 'RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH',
msgs: [{ internalTransactionHtml: 'test 2' }]
}
const output = reducer(state, action)
expect(output.internalTransactions).toEqual([
{ internalTransactionHtml: 'test 1' }
])
})
test('beyond page one', () => {
const state = Object.assign({}, initialState, {
beyondPageOne: true,
internalTransactions: [{ internalTransactionHtml: 'test 1' }]
})
const action = {
type: 'RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH',
msgs: [{ internalTransactionHtml: 'test 2' }]
}
const output = reducer(state, action)
expect(output.internalTransactions).toEqual([
{ internalTransactionHtml: 'test 1' }
])
})
test('with filtered out internal transaction', () => {
const state = Object.assign({}, initialState, {
filter: 'to'
})
const action = {
type: 'RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH',
msgs: [{ internalTransactionHtml: 'test 2' }]
}
const output = reducer(state, action)
expect(output.internalTransactions).toEqual([])
})
})
describe('RECEIVED_NEW_TRANSACTION', () => {
test('increment the transactions count', () => {
const state = Object.assign({}, initialState, {

@ -0,0 +1,147 @@
import { reducer, initialState } from '../../../js/pages/address/internal_transactions'
describe('RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH', () => {
test('with new internal transaction', () => {
const state = Object.assign({}, initialState, {
items: ['test 1']
})
const action = {
type: 'RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH',
msgs: [{ internalTransactionHtml: 'test 2' }]
}
const output = reducer(state, action)
expect(output.items).toEqual(['test 2', 'test 1'])
})
test('with batch of new internal transactions', () => {
const state = Object.assign({}, initialState, {
items: ['test 1']
})
const action = {
type: 'RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH',
msgs: [
{ internalTransactionHtml: 'test 2' },
{ internalTransactionHtml: 'test 3' },
{ internalTransactionHtml: 'test 4' },
{ internalTransactionHtml: 'test 5' },
{ internalTransactionHtml: 'test 6' },
{ internalTransactionHtml: 'test 7' },
{ internalTransactionHtml: 'test 8' },
{ internalTransactionHtml: 'test 9' },
{ internalTransactionHtml: 'test 10' },
{ internalTransactionHtml: 'test 11' },
{ internalTransactionHtml: 'test 12' },
{ internalTransactionHtml: 'test 13' }
]
}
const output = reducer(state, action)
expect(output.items).toEqual(['test 1'])
expect(output.internalTransactionsBatch).toEqual([
'test 13',
'test 12',
'test 11',
'test 10',
'test 9',
'test 8',
'test 7',
'test 6',
'test 5',
'test 4',
'test 3',
'test 2',
])
})
test('after batch of new internal transactions', () => {
const state = Object.assign({}, initialState, {
internalTransactionsBatch: ['test 1']
})
const action = {
type: 'RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH',
msgs: [
{ internalTransactionHtml: 'test 2' }
]
}
const output = reducer(state, action)
expect(output.internalTransactionsBatch).toEqual(['test 2', 'test 1'])
})
test('when channel has been disconnected', () => {
const state = Object.assign({}, initialState, {
channelDisconnected: true,
items: ['test 1']
})
const action = {
type: 'RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH',
msgs: [{ internalTransactionHtml: 'test 2' }]
}
const output = reducer(state, action)
expect(output.items).toEqual(['test 1'])
})
test('beyond page one', () => {
const state = Object.assign({}, initialState, {
beyondPageOne: true,
items: ['test 1']
})
const action = {
type: 'RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH',
msgs: [{ internalTransactionHtml: 'test 2' }]
}
const output = reducer(state, action)
expect(output.items).toEqual(['test 1'])
})
test('with filtered "to" internal transaction', () => {
const state = Object.assign({}, initialState, {
filter: 'to',
addressHash: '0x00',
items: []
})
const action = {
type: 'RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH',
msgs: [{
fromAddressHash: '0x00',
toAddressHash: '0x01',
internalTransactionHtml: 'test 2'
},
{
fromAddressHash: '0x01',
toAddressHash: '0x00',
internalTransactionHtml: 'test 3'
}]
}
const output = reducer(state, action)
expect(output.items).toEqual(['test 3'])
})
test('with filtered "from" internal transaction', () => {
const state = Object.assign({}, initialState, {
filter: 'from',
addressHash: '0x00',
items: []
})
const action = {
type: 'RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH',
msgs: [{
fromAddressHash: '0x00',
toAddressHash: '0x01',
internalTransactionHtml: 'test 2'
},
{
fromAddressHash: '0x01',
toAddressHash: '0x00',
internalTransactionHtml: 'test 3'
}]
}
const output = reducer(state, action)
expect(output.items).toEqual(['test 2'])
})
})

@ -23,6 +23,7 @@ import './locale'
import './pages/address'
import './pages/address/transactions'
import './pages/address/validations'
import './pages/address/internal_transactions'
import './pages/blocks'
import './pages/chain'
import './pages/pending_transactions'

@ -5,13 +5,9 @@ import humps from 'humps'
import numeral from 'numeral'
import socket from '../socket'
import { createStore, connectElements } from '../lib/redux_helpers.js'
import { batchChannel } from '../lib/utils'
import listMorph from '../lib/list_morph'
import { updateAllCalculatedUsdValues } from '../lib/currency.js'
import { loadTokenBalanceDropdown } from '../lib/token_balance_dropdown'
const BATCH_THRESHOLD = 10
export const initialState = {
channelDisconnected: false,
@ -20,12 +16,7 @@ export const initialState = {
balance: null,
transactionCount: null,
validationCount: null,
internalTransactions: [],
internalTransactionsBatch: [],
validatedBlocks: [],
beyondPageOne: false
validationCount: null
}
export function reducer (state = initialState, action) {
@ -38,8 +29,7 @@ export function reducer (state = initialState, action) {
if (state.beyondPageOne) return state
return Object.assign({}, state, {
channelDisconnected: true,
internalTransactionsBatch: []
channelDisconnected: true
})
}
case 'RECEIVED_NEW_BLOCK': {
@ -48,32 +38,6 @@ export function reducer (state = initialState, action) {
const validationCount = state.validationCount + 1
return Object.assign({}, state, { validationCount })
}
case 'RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH': {
if (state.channelDisconnected || state.beyondPageOne) return state
const incomingInternalTransactions = action.msgs
.filter(({toAddressHash, fromAddressHash}) => (
!state.filter ||
(state.filter === 'to' && toAddressHash === state.addressHash) ||
(state.filter === 'from' && fromAddressHash === state.addressHash)
))
if (!state.internalTransactionsBatch.length && incomingInternalTransactions.length < BATCH_THRESHOLD) {
return Object.assign({}, state, {
internalTransactions: [
...incomingInternalTransactions.reverse(),
...state.internalTransactions
]
})
} else {
return Object.assign({}, state, {
internalTransactionsBatch: [
...incomingInternalTransactions.reverse(),
...state.internalTransactionsBatch
]
})
}
}
case 'RECEIVED_NEW_TRANSACTION': {
if (state.channelDisconnected) return state
@ -125,29 +89,6 @@ const elements = {
if (oldState.validationCount === state.validationCount) return
$el.empty().append(numeral(state.validationCount).format())
}
},
'[data-selector="internal-transactions-list"]': {
load ($el) {
return {
internalTransactions: $el.children().map((index, el) => ({
internalTransactionHtml: el.outerHTML
})).toArray()
}
},
render ($el, state, oldState) {
if (oldState.internalTransactions === state.internalTransactions) return
const container = $el[0]
const newElements = _.map(state.internalTransactions, ({ internalTransactionHtml }) => $(internalTransactionHtml)[0])
listMorph(container, newElements, { key: 'dataset.key' })
}
},
'[data-selector="channel-batching-count"]': {
render ($el, state, oldState) {
const $channelBatching = $('[data-selector="channel-batching-message"]')
if (!state.internalTransactionsBatch.length) return $channelBatching.hide()
$channelBatching.show()
$el[0].innerHTML = numeral(state.internalTransactionsBatch.length).format()
}
}
}
@ -173,10 +114,6 @@ if ($addressDetailsPage.length) {
type: 'RECEIVED_UPDATED_BALANCE',
msg: humps.camelizeKeys(msg)
}))
addressChannel.on('internal_transaction', batchChannel((msgs) => store.dispatch({
type: 'RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH',
msgs: humps.camelizeKeys(msgs)
})))
addressChannel.on('transaction', (msg) => {
store.dispatch({
type: 'RECEIVED_NEW_TRANSACTION',

@ -0,0 +1,96 @@
import $ from 'jquery'
import _ from 'lodash'
import humps from 'humps'
import numeral from 'numeral'
import socket from '../../socket'
import { batchChannel } from '../../lib/utils'
import { connectElements } from '../../lib/redux_helpers.js'
import { createAsyncLoadStore } from '../../lib/async_listing_load'
const BATCH_THRESHOLD = 10
export const initialState = {
channelDisconnected: false,
addressHash: null,
filter: null,
internalTransactionsBatch: []
}
export function reducer (state, action) {
switch (action.type) {
case 'PAGE_LOAD':
case 'ELEMENTS_LOAD': {
return Object.assign({}, state, _.omit(action, 'type'))
}
case 'CHANNEL_DISCONNECTED': {
if (state.beyondPageOne) return state
return Object.assign({}, state, {
channelDisconnected: true,
internalTransactionsBatch: []
})
}
case 'RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH': {
if (state.channelDisconnected || state.beyondPageOne) return state
const incomingInternalTransactions = action.msgs
.filter(({toAddressHash, fromAddressHash}) => (
!state.filter ||
(state.filter === 'to' && toAddressHash === state.addressHash) ||
(state.filter === 'from' && fromAddressHash === state.addressHash)
)).map(msg => msg.internalTransactionHtml)
if (!state.internalTransactionsBatch.length && incomingInternalTransactions.length < BATCH_THRESHOLD) {
return Object.assign({}, state, {
items: [
...incomingInternalTransactions.reverse(),
...state.items
]
})
} else {
return Object.assign({}, state, {
internalTransactionsBatch: [
...incomingInternalTransactions.reverse(),
...state.internalTransactionsBatch
]
})
}
}
default:
return state
}
}
const elements = {
'[data-selector="channel-disconnected-message"]': {
render ($el, state) {
if (state.channelDisconnected) $el.show()
}
},
'[data-selector="channel-batching-count"]': {
render ($el, state, oldState) {
const $channelBatching = $('[data-selector="channel-batching-message"]')
if (!state.internalTransactionsBatch.length) return $channelBatching.hide()
$channelBatching.show()
$el[0].innerHTML = numeral(state.internalTransactionsBatch.length).format()
}
}
}
if ($('[data-page="address-internal-transactions"]').length) {
const store = createAsyncLoadStore(reducer, initialState, 'dataset.key')
const addressHash = $('[data-page="address-details"]')[0].dataset.pageAddressHash
store.dispatch({type: 'PAGE_LOAD', addressHash})
connectElements({ store, elements })
const addressChannel = socket.channel(`addresses:${addressHash}`, {})
addressChannel.join()
addressChannel.onError(() => store.dispatch({
type: 'CHANNEL_DISCONNECTED'
}))
addressChannel.on('internal_transaction', batchChannel((msgs) => store.dispatch({
type: 'RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH',
msgs: humps.camelizeKeys(msgs)
})))
}
Loading…
Cancel
Save