refactor(@cockpit/explorer): revise transactions explorer per techniques of PRs #1492, #1494

In addition to introducing improvements per #1492 and #1494, adjust the
transactions request action to allow for a `blockLimit` argument, since that
API parameter is supported by embark.

Revise `changePage` to invoke `fetchTransactions` correctly, i.e. with
arguments pertaining to a starting block number and a block limit.

These changes together fix most of the pagination problems related to exploring
transactions, i.e. badly mis-ordered results are no longer displayed.

However, a *wart* still remains related to estimation of the total number of
transactions and pages. Sometimes the calculated number of pages for the
transactions explorer doesn't match up to the actual number of transactions on
the blockchain (owing to estimation). The pagination controls and display of
transactions will temporarily behave a little strangely if one jumps ahead in
the pages, e.g. a jump from cockpit explorer overview's transactions page 1 to
page 5 for embark's demo, if an additional transaction has been added and the
explorer overview is freshly loaded. This behavior is related to the fact that
actions such as `fetchBlocksFull` and `fetchTransactions` are async without any
means to determine when all actions have completed and React re-/rendering has
settled down.

There are probably some architectural changes that could improve the situation,
but they're outside the scope of this PR and in no way easy to solve by means
of React lifecycle methods. In fact, attempts to make an improvement with
`componentDidUpdate` (and watching what happens in the debugger) revealed the
nature of the problem, as described above.
This commit is contained in:
Michael Bradley, Jr 2019-05-05 18:44:14 -05:00 committed by Michael Bradley
parent fa2807b335
commit 2f2c0eefbf
2 changed files with 58 additions and 36 deletions

View File

@ -100,7 +100,7 @@ export const block = {
export const TRANSACTIONS = createRequestTypes('TRANSACTIONS'); export const TRANSACTIONS = createRequestTypes('TRANSACTIONS');
export const transactions = { export const transactions = {
request: (blockFrom) => action(TRANSACTIONS[REQUEST], {blockFrom}), request: (blockFrom, blockLimit) => action(TRANSACTIONS[REQUEST], {blockFrom, blockLimit}),
success: (transactions) => action(TRANSACTIONS[SUCCESS], {transactions}), success: (transactions) => action(TRANSACTIONS[SUCCESS], {transactions}),
failure: (error) => action(TRANSACTIONS[FAILURE], {error}) failure: (error) => action(TRANSACTIONS[FAILURE], {error})
}; };

View File

@ -7,9 +7,8 @@ import {blocksFull as blocksAction,
stopBlockHeader, stopBlockHeader,
transactions as transactionsAction} from '../actions'; transactions as transactionsAction} from '../actions';
import Transactions from '../components/Transactions'; import Transactions from '../components/Transactions';
import DataWrapper from "../components/DataWrapper"; import PageHead from '../components/PageHead';
import PageHead from "../components/PageHead"; import {getBlocksFull, getContracts, getTransactions} 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
@ -19,6 +18,7 @@ class TransactionsContainer extends Component {
this.numTxsToDisplay = this.props.numTxsToDisplay || MAX_TXS; this.numTxsToDisplay = this.props.numTxsToDisplay || MAX_TXS;
this.numBlocksToFetch = this.numTxsToDisplay; this.numBlocksToFetch = this.numTxsToDisplay;
this.resetNums();
this.state = {currentPage: 1}; this.state = {currentPage: 1};
} }
@ -34,34 +34,57 @@ class TransactionsContainer extends Component {
} }
get numberOfBlocks() { get numberOfBlocks() {
if (this._numberOfBlocks === null) {
const blocks = this.props.blocks; const blocks = this.props.blocks;
return !blocks.length ? 0 : blocks[0].number + 1; this._numberOfBlocks = !blocks.length ? 0 : blocks[0].number + 1;
}
return this._numberOfBlocks;
} }
get estNumberOfTxs() { get estNumberOfTxs() {
if (this._estNumberOfTxs !== null) {
return this._estNumberOfTxs;
}
const blocks = this.props.blocks; const blocks = this.props.blocks;
const numBlocksInProps = blocks.length; const numBlocksInProps = blocks.length;
const numTxsInPropsBlocks = blocks.reduce((txCount, block) => ( const numTxsInPropsBlocks = blocks.reduce(
txCount + block.transactions.length (txCount, block) => txCount + block.transactions.length,
), 0); 0
);
const missingNumBlocks = this.numberOfBlocks - numBlocksInProps; const missingNumBlocks = this.numberOfBlocks - numBlocksInProps;
return missingNumBlocks + numTxsInPropsBlocks; this._estNumberOfTxs = missingNumBlocks + numTxsInPropsBlocks;
return this._estNumberOfTxs;
} }
getNumberOfPages() { get numberOfPages() {
return Math.ceil(this.estNumberOfTxs / this.numTxsToDisplay); if (this._numberOfPages === null) {
this._numberOfPages = Math.ceil(
this.estNumberOfTxs / this.numTxsToDisplay
);
}
return this._numberOfPages;
}
resetNums() {
this._numberOfBlocks = null;
this._estNumberOfTxs = null;
this._numberOfPages = null;
} }
changePage(newPage) { changePage(newPage) {
this.setState({currentPage: newPage}); if (newPage <= 0) {
this.props.fetchBlocksFull( newPage = 1;
this.numberOfBlocks - 1 - (this.numBlocksToFetch * (newPage - 1)), } else if (newPage > this.numberOfPages) {
this.numBlocksToFetch newPage = this.numberOfPages;
); }
this.props.fetchTransactions((newPage * MAX_TXS) + MAX_TXS); this.setState({ currentPage: newPage });
const blockFrom =
this.numberOfBlocks - 1 - this.numBlocksToFetch * (newPage - 1);
this.props.fetchBlocksFull(blockFrom, this.numBlocksToFetch);
this.props.fetchTransactions(blockFrom, this.numTxsToDisplay);
} }
getCurrentTransactions() { get currentTransactions() {
if (!this.props.blocks.length) return []; if (!this.props.blocks.length) return [];
let relativeBlock = this.numberOfBlocks - 1; let relativeBlock = this.numberOfBlocks - 1;
let offset = 0; let offset = 0;
@ -81,33 +104,28 @@ class TransactionsContainer extends Component {
const estNumberOfTxs = this.estNumberOfTxs; const estNumberOfTxs = this.estNumberOfTxs;
return txs.filter((tx, idx) => { return txs.filter((tx, idx) => {
const txNumber = estNumberOfTxs - idx; const txNumber = estNumberOfTxs - idx;
const index = ( const index =
(estNumberOfTxs - estNumberOfTxs -
(this.numTxsToDisplay * (this.state.currentPage - 1))) - this.numTxsToDisplay * (this.state.currentPage - 1) -
txNumber + 1 txNumber + 1;
);
return index <= this.numTxsToDisplay && index > 0; return index <= this.numTxsToDisplay && index > 0;
}); });
} }
render() { render() {
const newTxs = this.getCurrentTransactions(); this.resetNums();
return ( return (
<React.Fragment> <React.Fragment>
<PageHead <PageHead
title="Transactions" title="Transactions"
enabled={this.props.overridePageHead} enabled={this.props.overridePageHead}
description="Summary view of all transactions occurring on the node configured for Embark" /> description="Summary view of all transactions occurring on the node configured for Embark" />
<DataWrapper <Transactions
shouldRender={true} transactions={this.currentTransactions}
{...this.props}
render={() => (
<Transactions transactions={newTxs}
contracts={this.props.contracts} contracts={this.props.contracts}
numberOfPages={this.getNumberOfPages()} changePage={newPage => this.changePage(newPage)}
changePage={(newPage) => this.changePage(newPage)} currentPage={this.state.currentPage}
currentPage={this.state.currentPage} /> numberOfPages={this.numberOfPages} />
)} />
</React.Fragment> </React.Fragment>
); );
} }
@ -117,6 +135,7 @@ function mapStateToProps(state) {
return { return {
blocks: getBlocksFull(state), blocks: getBlocksFull(state),
contracts: getContracts(state), contracts: getContracts(state),
transactions: getTransactions(state),
error: state.errorMessage, error: state.errorMessage,
loading: state.loading loading: state.loading
}; };
@ -125,8 +144,11 @@ function mapStateToProps(state) {
TransactionsContainer.propTypes = { TransactionsContainer.propTypes = {
blocks: PropTypes.arrayOf(PropTypes.object), blocks: PropTypes.arrayOf(PropTypes.object),
contracts: PropTypes.arrayOf(PropTypes.object), contracts: PropTypes.arrayOf(PropTypes.object),
transactions: PropTypes.arrayOf(PropTypes.object),
fetchBlocksFull: PropTypes.func, fetchBlocksFull: PropTypes.func,
fetchContracts: PropTypes.func, fetchContracts: PropTypes.func,
fetchTransactions: PropTypes.func,
numTxsToDisplay: PropTypes.number,
initBlockHeader: PropTypes.func, initBlockHeader: PropTypes.func,
stopBlockHeader: PropTypes.func, stopBlockHeader: PropTypes.func,
error: PropTypes.string, error: PropTypes.string,
@ -142,5 +164,5 @@ export default connect(
fetchTransactions: transactionsAction.request, fetchTransactions: transactionsAction.request,
initBlockHeader, initBlockHeader,
stopBlockHeader stopBlockHeader
}, }
)(TransactionsContainer); )(TransactionsContainer);