From 0d65bbda3c6e4bdcd409608aea74e2a9cbd99551 Mon Sep 17 00:00:00 2001 From: Gustavo Santos Ferreira Date: Tue, 4 Dec 2018 11:12:17 -0200 Subject: [PATCH] move internal transactions real time to its own file and use redux for async loading --- .../assets/__tests__/pages/address.js | 118 -------------- .../pages/address/internal_transactions.js | 147 ++++++++++++++++++ apps/block_scout_web/assets/js/app.js | 1 + .../assets/js/pages/address.js | 67 +------- .../js/pages/address/internal_transactions.js | 96 ++++++++++++ 5 files changed, 246 insertions(+), 183 deletions(-) create mode 100644 apps/block_scout_web/assets/__tests__/pages/address/internal_transactions.js create mode 100644 apps/block_scout_web/assets/js/pages/address/internal_transactions.js diff --git a/apps/block_scout_web/assets/__tests__/pages/address.js b/apps/block_scout_web/assets/__tests__/pages/address.js index 035e415074..98172accee 100644 --- a/apps/block_scout_web/assets/__tests__/pages/address.js +++ b/apps/block_scout_web/assets/__tests__/pages/address.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, { diff --git a/apps/block_scout_web/assets/__tests__/pages/address/internal_transactions.js b/apps/block_scout_web/assets/__tests__/pages/address/internal_transactions.js new file mode 100644 index 0000000000..516bab5a65 --- /dev/null +++ b/apps/block_scout_web/assets/__tests__/pages/address/internal_transactions.js @@ -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']) + }) +}) diff --git a/apps/block_scout_web/assets/js/app.js b/apps/block_scout_web/assets/js/app.js index 30430547cb..9ad45d9465 100644 --- a/apps/block_scout_web/assets/js/app.js +++ b/apps/block_scout_web/assets/js/app.js @@ -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' diff --git a/apps/block_scout_web/assets/js/pages/address.js b/apps/block_scout_web/assets/js/pages/address.js index 615cefc394..28f1814e71 100644 --- a/apps/block_scout_web/assets/js/pages/address.js +++ b/apps/block_scout_web/assets/js/pages/address.js @@ -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', diff --git a/apps/block_scout_web/assets/js/pages/address/internal_transactions.js b/apps/block_scout_web/assets/js/pages/address/internal_transactions.js new file mode 100644 index 0000000000..aa95278c71 --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/address/internal_transactions.js @@ -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) + }))) +}