mirror of
https://github.com/embarklabs/embark.git
synced 2025-02-03 09:24:25 +00:00
fix(embark-ui): correctly calculate which transactions to display
Revise calculations related to transactions and pagination in the transactions explorer and explorers overview. Make the number of transactions to display per page configurable and set it to 3 in the explorers overview. Display a "No transactions..." message instead of "0" when there are no transactions to display. Introduce a `blocksFull` prop, action, api, etc. for calculating lists of transactions relative to block objects that contain full transaction objects instead of hash strings (the function backing the blocks endpoint already supports that) and transaction receipts. In the future, the receipts can be used to filter out constructor transactions for silent contracts. Make pagination display conditional within the blocks explorer, same as in the transactions explorer.
This commit is contained in:
parent
b1881719e0
commit
fbeea47a6e
@ -82,6 +82,15 @@ export const blocks = {
|
|||||||
failure: (error) => action(BLOCKS[FAILURE], {error})
|
failure: (error) => action(BLOCKS[FAILURE], {error})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const BLOCKS_FULL = createRequestTypes('BLOCKS_FULL');
|
||||||
|
export const blocksFull = {
|
||||||
|
request: (from, limit) => {
|
||||||
|
return action(BLOCKS_FULL[REQUEST], {from, limit, txObjects: true, txReceipts: true});
|
||||||
|
},
|
||||||
|
success: (blocksFull) => action(BLOCKS_FULL[SUCCESS], {blocksFull}),
|
||||||
|
failure: (error) => action(BLOCKS_FULL[FAILURE], {error})
|
||||||
|
};
|
||||||
|
|
||||||
export const BLOCK = createRequestTypes('BLOCK');
|
export const BLOCK = createRequestTypes('BLOCK');
|
||||||
export const block = {
|
export const block = {
|
||||||
request: (blockNumber) => action(BLOCK[REQUEST], {blockNumber}),
|
request: (blockNumber) => action(BLOCK[REQUEST], {blockNumber}),
|
||||||
|
@ -38,7 +38,7 @@ const Blocks = ({blocks, changePage, currentPage, numberOfPages}) => (
|
|||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<Pagination changePage={changePage} currentPage={currentPage} numberOfPages={numberOfPages}/>
|
{numberOfPages > 0 && <Pagination changePage={changePage} currentPage={currentPage} numberOfPages={numberOfPages}/>}
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -25,7 +25,7 @@ const ExplorerDashboardLayout = () => (
|
|||||||
<BlocksContainer numBlocksToDisplay={5} overridePageHead={false} />
|
<BlocksContainer numBlocksToDisplay={5} overridePageHead={false} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col xl={6}>
|
<Col xl={6}>
|
||||||
<TransactionsContainer overridePageHead={false} />
|
<TransactionsContainer numTxsToDisplay={3} overridePageHead={false} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,6 +15,7 @@ const Transactions = ({transactions, contracts, changePage, currentPage, numberO
|
|||||||
<h2>Transactions</h2>
|
<h2>Transactions</h2>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
|
{!transactions.length && "No transactions to display"}
|
||||||
{transactions.map(transaction => (
|
{transactions.map(transaction => (
|
||||||
<div className="explorer-row border-top" key={transaction.hash}>
|
<div className="explorer-row border-top" key={transaction.hash}>
|
||||||
<CardTitleIdenticon id={transaction.hash}>Transaction
|
<CardTitleIdenticon id={transaction.hash}>Transaction
|
||||||
@ -47,7 +48,7 @@ const Transactions = ({transactions, contracts, changePage, currentPage, numberO
|
|||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{numberOfPages && <Pagination changePage={changePage} currentPage={currentPage} numberOfPages={numberOfPages}/>}
|
{numberOfPages > 0 && <Pagination changePage={changePage} currentPage={currentPage} numberOfPages={numberOfPages}/>}
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import {blocksFull as blocksAction,
|
||||||
import {transactions as transactionsAction, initBlockHeader, stopBlockHeader, contracts as contractsAction} from '../actions';
|
contracts as contractsAction,
|
||||||
|
initBlockHeader,
|
||||||
|
stopBlockHeader} from '../actions';
|
||||||
import Transactions from '../components/Transactions';
|
import Transactions from '../components/Transactions';
|
||||||
import DataWrapper from "../components/DataWrapper";
|
import DataWrapper from "../components/DataWrapper";
|
||||||
import PageHead from "../components/PageHead";
|
import PageHead from "../components/PageHead";
|
||||||
import {getTransactions, getContracts} from "../reducers/selectors";
|
import {getBlocksFull, getContracts} from "../reducers/selectors";
|
||||||
|
|
||||||
const MAX_TXS = 10; // TODO use same constant as API
|
const MAX_TXS = 10; // TODO use same constant as API
|
||||||
|
|
||||||
@ -14,13 +16,13 @@ class TransactionsContainer extends Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {currentPage: 0};
|
this.numTxsToDisplay = this.props.numTxsToDisplay || MAX_TXS;
|
||||||
this.numberOfTxs = 0;
|
this.numBlocksToFetch = this.numTxsToDisplay;
|
||||||
this.currentTxs = [];
|
this.state = {currentPage: 1};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.fetchTransactions();
|
this.props.fetchBlocksFull(null, this.numBlocksToFetch);
|
||||||
this.props.fetchContracts();
|
this.props.fetchContracts();
|
||||||
this.props.initBlockHeader();
|
this.props.initBlockHeader();
|
||||||
}
|
}
|
||||||
@ -29,44 +31,78 @@ class TransactionsContainer extends Component {
|
|||||||
this.props.stopBlockHeader();
|
this.props.stopBlockHeader();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get numberOfBlocks() {
|
||||||
|
const blocks = this.props.blocks;
|
||||||
|
return !blocks.length ? 0 : blocks[0].number + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
get estNumberOfTxs() {
|
||||||
|
const blocks = this.props.blocks;
|
||||||
|
const numBlocksInProps = blocks.length;
|
||||||
|
const numTxsInPropsBlocks = blocks.reduce((txCount, block) => (
|
||||||
|
txCount + block.transactions.length
|
||||||
|
), 0);
|
||||||
|
const missingNumBlocks = this.numberOfBlocks - numBlocksInProps;
|
||||||
|
return missingNumBlocks + numTxsInPropsBlocks;
|
||||||
|
}
|
||||||
|
|
||||||
getNumberOfPages() {
|
getNumberOfPages() {
|
||||||
if (!this.numberOfTxs) {
|
return Math.ceil(this.estNumberOfTxs / this.numTxsToDisplay);
|
||||||
let transactions = this.props.transactions;
|
|
||||||
if (transactions.length === 0) {
|
|
||||||
this.numberOfTxs = 0;
|
|
||||||
} else {
|
|
||||||
this.numberOfTxs = transactions[transactions.length - 1].blockNumber - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Math.ceil(this.numberOfTxs / MAX_TXS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
changePage(newPage) {
|
changePage(newPage) {
|
||||||
this.setState({currentPage: newPage});
|
this.setState({currentPage: newPage});
|
||||||
|
this.props.fetchBlocksFull(
|
||||||
this.props.fetchTransactions((newPage * MAX_TXS) + MAX_TXS);
|
this.numberOfBlocks - 1 - (this.numBlocksToFetch * (newPage - 1)),
|
||||||
|
this.numBlocksToFetch
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentTransactions() {
|
getCurrentTransactions() {
|
||||||
const currentPage = this.state.currentPage || this.getNumberOfPages();
|
if (!this.props.blocks.length) return [];
|
||||||
return this.props.transactions.filter(tx => tx.blockNumber <= (currentPage * MAX_TXS) + MAX_TXS &&
|
let relativeBlock = this.numberOfBlocks - 1;
|
||||||
tx.blockNumber > currentPage * MAX_TXS);
|
let offset = 0;
|
||||||
|
let txs = this.props.blocks.reduce((txs, block) => {
|
||||||
|
offset = relativeBlock - block.number;
|
||||||
|
if (offset <= 1) {
|
||||||
|
offset = 0;
|
||||||
|
}
|
||||||
|
relativeBlock = block.number;
|
||||||
|
const txsLength = txs.length;
|
||||||
|
block.transactions.forEach((tx, idx) => {
|
||||||
|
txs[txsLength + idx + offset] = tx;
|
||||||
|
});
|
||||||
|
return txs;
|
||||||
|
}, []);
|
||||||
|
const estNumberOfTxs = this.estNumberOfTxs;
|
||||||
|
return txs.filter((tx, idx) => {
|
||||||
|
const txNumber = estNumberOfTxs - idx;
|
||||||
|
const index = (
|
||||||
|
(estNumberOfTxs -
|
||||||
|
(this.numTxsToDisplay * (this.state.currentPage - 1))) -
|
||||||
|
txNumber + 1
|
||||||
|
);
|
||||||
|
return index <= this.numTxsToDisplay && index > 0;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const newTxs = this.getCurrentTransactions();
|
const newTxs = this.getCurrentTransactions();
|
||||||
if (newTxs.length) {
|
|
||||||
this.currentTxs = newTxs;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<PageHead title="Transactions" enabled={this.props.overridePageHead} description="Summary view of all transactions occurring on the node configured for Embark" />
|
<PageHead
|
||||||
<DataWrapper shouldRender={this.currentTxs.length > 0} {...this.props} render={() => (
|
title="Transactions"
|
||||||
<Transactions transactions={this.currentTxs}
|
enabled={this.props.overridePageHead}
|
||||||
|
description="Summary view of all transactions occurring on the node configured for Embark" />
|
||||||
|
<DataWrapper
|
||||||
|
shouldRender={true}
|
||||||
|
{...this.props}
|
||||||
|
render={() => (
|
||||||
|
<Transactions transactions={newTxs}
|
||||||
contracts={this.props.contracts}
|
contracts={this.props.contracts}
|
||||||
numberOfPages={this.getNumberOfPages()}
|
numberOfPages={this.getNumberOfPages()}
|
||||||
changePage={(newPage) => this.changePage(newPage)}
|
changePage={(newPage) => this.changePage(newPage)}
|
||||||
currentPage={this.state.currentPage || this.getNumberOfPages()} />
|
currentPage={this.state.currentPage} />
|
||||||
)} />
|
)} />
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
@ -75,7 +111,7 @@ class TransactionsContainer extends Component {
|
|||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
return {
|
return {
|
||||||
transactions: getTransactions(state),
|
blocks: getBlocksFull(state),
|
||||||
contracts: getContracts(state),
|
contracts: getContracts(state),
|
||||||
error: state.errorMessage,
|
error: state.errorMessage,
|
||||||
loading: state.loading
|
loading: state.loading
|
||||||
@ -83,9 +119,9 @@ function mapStateToProps(state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TransactionsContainer.propTypes = {
|
TransactionsContainer.propTypes = {
|
||||||
transactions: PropTypes.arrayOf(PropTypes.object),
|
blocks: PropTypes.arrayOf(PropTypes.object),
|
||||||
contracts: PropTypes.arrayOf(PropTypes.object),
|
contracts: PropTypes.arrayOf(PropTypes.object),
|
||||||
fetchTransactions: PropTypes.func,
|
fetchBlocksFull: PropTypes.func,
|
||||||
fetchContracts: PropTypes.func,
|
fetchContracts: PropTypes.func,
|
||||||
initBlockHeader: PropTypes.func,
|
initBlockHeader: PropTypes.func,
|
||||||
stopBlockHeader: PropTypes.func,
|
stopBlockHeader: PropTypes.func,
|
||||||
@ -97,7 +133,7 @@ TransactionsContainer.propTypes = {
|
|||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
{
|
{
|
||||||
fetchTransactions: transactionsAction.request,
|
fetchBlocksFull: blocksAction.request,
|
||||||
fetchContracts: contractsAction.request,
|
fetchContracts: contractsAction.request,
|
||||||
initBlockHeader,
|
initBlockHeader,
|
||||||
stopBlockHeader
|
stopBlockHeader
|
||||||
|
@ -18,6 +18,7 @@ const PROCESS_LOGS_LIMIT = ELEMENTS_LIMIT * 2;
|
|||||||
const entitiesDefaultState = {
|
const entitiesDefaultState = {
|
||||||
accounts: [],
|
accounts: [],
|
||||||
blocks: [],
|
blocks: [],
|
||||||
|
blocksFull: [],
|
||||||
transactions: [],
|
transactions: [],
|
||||||
processes: [],
|
processes: [],
|
||||||
services: [],
|
services: [],
|
||||||
@ -43,6 +44,9 @@ const sorter = {
|
|||||||
blocks: function(a, b) {
|
blocks: function(a, b) {
|
||||||
return b.number - a.number;
|
return b.number - a.number;
|
||||||
},
|
},
|
||||||
|
blocksFull: function(a, b) {
|
||||||
|
return b.number - a.number;
|
||||||
|
},
|
||||||
transactions: function(a, b) {
|
transactions: function(a, b) {
|
||||||
return ((BN_FACTOR * b.blockNumber) + b.transactionIndex) - ((BN_FACTOR * a.blockNumber) + a.transactionIndex);
|
return ((BN_FACTOR * b.blockNumber) + b.transactionIndex) - ((BN_FACTOR * a.blockNumber) + a.transactionIndex);
|
||||||
},
|
},
|
||||||
@ -120,6 +124,13 @@ const filters = {
|
|||||||
|
|
||||||
return index === self.findIndex((t) => t.number === block.number);
|
return index === self.findIndex((t) => t.number === block.number);
|
||||||
},
|
},
|
||||||
|
blocksFull: function(block, index, self) {
|
||||||
|
if (index > ELEMENTS_LIMIT) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return index === self.findIndex((t) => t.number === block.number);
|
||||||
|
},
|
||||||
transactions: function(tx, index, self) {
|
transactions: function(tx, index, self) {
|
||||||
if (index > ELEMENTS_LIMIT) {
|
if (index > ELEMENTS_LIMIT) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -44,6 +44,10 @@ export function getBlocks(state) {
|
|||||||
return state.entities.blocks;
|
return state.entities.blocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getBlocksFull(state) {
|
||||||
|
return state.entities.blocksFull;
|
||||||
|
}
|
||||||
|
|
||||||
export function getLastBlock(state) {
|
export function getLastBlock(state) {
|
||||||
return state.entities.blocks[0];
|
return state.entities.blocks[0];
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,7 @@ export const fetchBlock = doRequest.bind(null, actions.block, api.fetchBlock);
|
|||||||
export const fetchTransaction = doRequest.bind(null, actions.transaction, api.fetchTransaction);
|
export const fetchTransaction = doRequest.bind(null, actions.transaction, api.fetchTransaction);
|
||||||
export const fetchAccounts = doRequest.bind(null, actions.accounts, api.fetchAccounts);
|
export const fetchAccounts = doRequest.bind(null, actions.accounts, api.fetchAccounts);
|
||||||
export const fetchBlocks = doRequest.bind(null, actions.blocks, api.fetchBlocks);
|
export const fetchBlocks = doRequest.bind(null, actions.blocks, api.fetchBlocks);
|
||||||
|
export const fetchBlocksFull = doRequest.bind(null, actions.blocksFull, api.fetchBlocks);
|
||||||
export const fetchTransactions = doRequest.bind(null, actions.transactions, api.fetchTransactions);
|
export const fetchTransactions = doRequest.bind(null, actions.transactions, api.fetchTransactions);
|
||||||
export const fetchProcesses = doRequest.bind(null, actions.processes, api.fetchProcesses);
|
export const fetchProcesses = doRequest.bind(null, actions.processes, api.fetchProcesses);
|
||||||
export const fetchServices = doRequest.bind(null, actions.services, api.fetchServices);
|
export const fetchServices = doRequest.bind(null, actions.services, api.fetchServices);
|
||||||
@ -120,6 +121,10 @@ export function *watchFetchBlocks() {
|
|||||||
yield takeEvery(actions.BLOCKS[actions.REQUEST], fetchBlocks);
|
yield takeEvery(actions.BLOCKS[actions.REQUEST], fetchBlocks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function *watchFetchBlocksFull() {
|
||||||
|
yield takeEvery(actions.BLOCKS_FULL[actions.REQUEST], fetchBlocksFull);
|
||||||
|
}
|
||||||
|
|
||||||
export function *watchFetchAccount() {
|
export function *watchFetchAccount() {
|
||||||
yield takeEvery(actions.ACCOUNT[actions.REQUEST], fetchAccount);
|
yield takeEvery(actions.ACCOUNT[actions.REQUEST], fetchAccount);
|
||||||
}
|
}
|
||||||
@ -389,6 +394,7 @@ export function *initBlockHeader() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
yield put({type: actions.BLOCKS[actions.REQUEST]});
|
yield put({type: actions.BLOCKS[actions.REQUEST]});
|
||||||
|
yield put({type: actions.BLOCKS_FULL[actions.REQUEST], txObjects: true, txReceipts: true});
|
||||||
yield put({type: actions.TRANSACTIONS[actions.REQUEST]});
|
yield put({type: actions.TRANSACTIONS[actions.REQUEST]});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -568,6 +574,7 @@ export default function *root() {
|
|||||||
fork(watchFetchVersions),
|
fork(watchFetchVersions),
|
||||||
fork(watchFetchPlugins),
|
fork(watchFetchPlugins),
|
||||||
fork(watchFetchBlocks),
|
fork(watchFetchBlocks),
|
||||||
|
fork(watchFetchBlocksFull),
|
||||||
fork(watchFetchContracts),
|
fork(watchFetchContracts),
|
||||||
fork(watchFetchContractProfile),
|
fork(watchFetchContractProfile),
|
||||||
fork(watchPostContractFunction),
|
fork(watchPostContractFunction),
|
||||||
|
@ -387,9 +387,9 @@ class BlockchainConnector {
|
|||||||
'get',
|
'get',
|
||||||
'/embark-api/blockchain/blocks',
|
'/embark-api/blockchain/blocks',
|
||||||
(req, res) => {
|
(req, res) => {
|
||||||
let from = parseInt(req.query.from, 10);
|
const from = parseInt(req.query.from, 10);
|
||||||
let limit = req.query.limit || 10;
|
const limit = req.query.limit || 10;
|
||||||
self.getBlocks(from, limit, false, res.send.bind(res));
|
self.getBlocks(from, limit, !!req.query.txObjects, !!req.query.txReceipts, res.send.bind(res));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -565,7 +565,7 @@ class BlockchainConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTransactions(blockFrom, blockLimit, callback) {
|
getTransactions(blockFrom, blockLimit, callback) {
|
||||||
this.getBlocks(blockFrom, blockLimit, true, (blocks) => {
|
this.getBlocks(blockFrom, blockLimit, true, true, (blocks) => {
|
||||||
let transactions = blocks.reduce((acc, block) => {
|
let transactions = blocks.reduce((acc, block) => {
|
||||||
if (!block || !block.transactions) {
|
if (!block || !block.transactions) {
|
||||||
return acc;
|
return acc;
|
||||||
@ -576,7 +576,7 @@ class BlockchainConnector {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getBlocks(from, limit, returnTransactionObjects, callback) {
|
getBlocks(from, limit, returnTransactionObjects, includeTransactionReceipts, callback) {
|
||||||
let self = this;
|
let self = this;
|
||||||
let blocks = [];
|
let blocks = [];
|
||||||
async.waterfall([
|
async.waterfall([
|
||||||
@ -597,12 +597,32 @@ class BlockchainConnector {
|
|||||||
function(next) {
|
function(next) {
|
||||||
async.times(limit, function(n, eachCb) {
|
async.times(limit, function(n, eachCb) {
|
||||||
self.web3.eth.getBlock(from - n, returnTransactionObjects, function(err, block) {
|
self.web3.eth.getBlock(from - n, returnTransactionObjects, function(err, block) {
|
||||||
if (err && err.message) {
|
if (err) {
|
||||||
// FIXME Returns an error because we are too low
|
// FIXME Returns an error because we are too low
|
||||||
return eachCb();
|
return eachCb();
|
||||||
}
|
}
|
||||||
|
if (!block) {
|
||||||
|
return eachCb();
|
||||||
|
}
|
||||||
|
if (!(returnTransactionObjects && includeTransactionReceipts) ||
|
||||||
|
!(block.transactions && block.transactions.length)) {
|
||||||
|
blocks.push(block);
|
||||||
|
return eachCb();
|
||||||
|
}
|
||||||
|
return Promise.all(block.transactions.map(tx => (
|
||||||
|
self.web3.eth.getTransactionReceipt(tx.hash)
|
||||||
|
)))
|
||||||
|
.then(receipts => {
|
||||||
|
block.transactions.forEach((tx, index) => {
|
||||||
|
tx['receipt'] = receipts[index];
|
||||||
|
});
|
||||||
blocks.push(block);
|
blocks.push(block);
|
||||||
eachCb();
|
eachCb();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
self.logger.error(err.message || err);
|
||||||
|
eachCb();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}, next);
|
}, next);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user