Align contract event log l&f with transactions (#2812)
* Event into own component, align with transactions * Pass value & type through from event log params * Reformat display columns
This commit is contained in:
parent
8d0cff3599
commit
7eacf07629
@ -150,7 +150,9 @@ export default class Contract {
|
||||
log.event = event.name;
|
||||
|
||||
decoded.params.forEach((param) => {
|
||||
log.params[param.name] = param.token.value;
|
||||
const { type, value } = param.token;
|
||||
|
||||
log.params[param.name] = { type, value };
|
||||
});
|
||||
|
||||
return log;
|
||||
|
@ -110,6 +110,10 @@ export default class Events extends Component {
|
||||
|
||||
logToEvent = (log) => {
|
||||
log.key = api.util.sha3(JSON.stringify(log));
|
||||
log.params = Object.keys(log.params).reduce((params, name) => {
|
||||
params[name] = log.params[name].value;
|
||||
return params;
|
||||
}, {});
|
||||
|
||||
return log;
|
||||
}
|
||||
|
@ -114,6 +114,10 @@ export default class Events extends Component {
|
||||
|
||||
logToEvent = (log) => {
|
||||
log.key = api.util.sha3(JSON.stringify(log));
|
||||
log.params = Object.keys(log.params).reduce((params, name) => {
|
||||
params[name] = log.params[name].value;
|
||||
return params;
|
||||
}, {});
|
||||
|
||||
return log;
|
||||
}
|
||||
@ -139,7 +143,6 @@ export default class Events extends Component {
|
||||
.concat(pendingEvents)
|
||||
.filter((log) => !minedNew.find((event) => event.transactionHash === log.transactionHash));
|
||||
const events = [].concat(pendingNew).concat(minedNew);
|
||||
console.log('*** events', events.map((event) => event.address));
|
||||
this.setState({ loading: false, events, minedEvents: minedNew, pendingEvents: pendingNew });
|
||||
}
|
||||
}
|
||||
|
@ -106,7 +106,10 @@ export default class Events extends Component {
|
||||
logIndex,
|
||||
transactionHash,
|
||||
transactionIndex,
|
||||
params,
|
||||
params: Object.keys(params).reduce((data, name) => {
|
||||
data[name] = params[name].value;
|
||||
return data;
|
||||
}, {}),
|
||||
key
|
||||
};
|
||||
};
|
||||
|
@ -48,9 +48,9 @@ const renderEvent = (classNames, verb) => (e, accounts, contacts) => {
|
||||
|
||||
return (
|
||||
<tr key={ e.key } className={ classes }>
|
||||
<td>{ renderAddress(e.parameters.owner, accounts, contacts) }</td>
|
||||
<td>{ renderAddress(e.parameters.owner.value, accounts, contacts) }</td>
|
||||
<td><abbr title={ e.transaction }>{ verb }</abbr></td>
|
||||
<td><code>{ renderHash(bytesToHex(e.parameters.name)) }</code></td>
|
||||
<td><code>{ renderHash(bytesToHex(e.parameters.name.value)) }</code></td>
|
||||
<td>{ renderStatus(e.timestamp, e.state === 'pending') }</td>
|
||||
</tr>
|
||||
);
|
||||
@ -64,10 +64,10 @@ const renderDataChanged = (e, accounts, contacts) => {
|
||||
|
||||
return (
|
||||
<tr key={ e.key } className={ classNames }>
|
||||
<td>{ renderAddress(e.parameters.owner, accounts, contacts) }</td>
|
||||
<td>{ renderAddress(e.parameters.owner.value, accounts, contacts) }</td>
|
||||
<td><abbr title={ e.transaction }>updated</abbr></td>
|
||||
<td>
|
||||
key <code>{ new Buffer(e.parameters.plainKey).toString('utf8') }</code> of <code>{ renderHash(bytesToHex(e.parameters.name)) }</code>
|
||||
key <code>{ new Buffer(e.parameters.plainKey.value).toString('utf8') }</code> of <code>{ renderHash(bytesToHex(e.parameters.name.value)) }</code>
|
||||
</td>
|
||||
<td>{ renderStatus(e.timestamp, e.state === 'pending') }</td>
|
||||
</tr>
|
||||
|
@ -30,7 +30,10 @@ const logToEvent = (log) => {
|
||||
logIndex,
|
||||
transactionHash,
|
||||
transactionIndex,
|
||||
params,
|
||||
params: Object.keys(params).reduce((data, name) => {
|
||||
data[name] = params[name].value;
|
||||
return data;
|
||||
}, {}),
|
||||
key
|
||||
};
|
||||
};
|
||||
|
@ -148,27 +148,27 @@ export const subscribeEvents = () => (dispatch, getState) => {
|
||||
return dispatch(setTokenData(params.id.toNumber(), {
|
||||
tla: '...',
|
||||
base: -1,
|
||||
address: params.addr,
|
||||
name: params.name,
|
||||
address: params.addr.value,
|
||||
name: params.name.value,
|
||||
isPending: true
|
||||
}));
|
||||
}
|
||||
|
||||
if (event === 'Registered' && type === 'mined') {
|
||||
return dispatch(loadToken(params.id.toNumber()));
|
||||
return dispatch(loadToken(params.id.value.toNumber()));
|
||||
}
|
||||
|
||||
if (event === 'Unregistered' && type === 'pending') {
|
||||
return dispatch(setTokenPending(params.id.toNumber(), true));
|
||||
return dispatch(setTokenPending(params.id.value.toNumber(), true));
|
||||
}
|
||||
|
||||
if (event === 'Unregistered' && type === 'mined') {
|
||||
return dispatch(deleteToken(params.id.toNumber()));
|
||||
return dispatch(deleteToken(params.id.value.toNumber()));
|
||||
}
|
||||
|
||||
if (event === 'MetaChanged' && type === 'pending') {
|
||||
return dispatch(setTokenData(
|
||||
params.id.toNumber(),
|
||||
params.id.value.toNumber(),
|
||||
{ metaPending: true, metaMined: false }
|
||||
));
|
||||
}
|
||||
@ -176,13 +176,13 @@ export const subscribeEvents = () => (dispatch, getState) => {
|
||||
if (event === 'MetaChanged' && type === 'mined') {
|
||||
setTimeout(() => {
|
||||
dispatch(setTokenData(
|
||||
params.id.toNumber(),
|
||||
params.id.value.toNumber(),
|
||||
{ metaPending: false, metaMined: false }
|
||||
));
|
||||
}, 5000);
|
||||
|
||||
return dispatch(setTokenData(
|
||||
params.id.toNumber(),
|
||||
params.id.value.toNumber(),
|
||||
{ metaPending: false, metaMined: true }
|
||||
));
|
||||
}
|
||||
|
183
js/src/views/Contract/Events/Event/event.js
Normal file
183
js/src/views/Contract/Events/Event/event.js
Normal file
@ -0,0 +1,183 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import BigNumber from 'bignumber.js';
|
||||
import moment from 'moment';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import { fetchBlock, fetchTransaction } from '../../../../redux/providers/blockchainActions';
|
||||
import { IdentityIcon, IdentityName, Input, InputAddress } from '../../../../ui';
|
||||
|
||||
import styles from '../../contract.css';
|
||||
|
||||
class Event extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
event: PropTypes.object.isRequired,
|
||||
blocks: PropTypes.object,
|
||||
transactions: PropTypes.object,
|
||||
isTest: PropTypes.bool,
|
||||
fetchBlock: PropTypes.func.isRequired,
|
||||
fetchTransaction: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.retrieveTransaction();
|
||||
}
|
||||
|
||||
render () {
|
||||
const { event, blocks, transactions, isTest } = this.props;
|
||||
|
||||
const block = blocks[event.blockNumber.toString()];
|
||||
const transaction = transactions[event.transactionHash] || {};
|
||||
const classes = `${styles.event} ${styles[event.state]}`;
|
||||
const url = `https://${isTest ? 'testnet.' : ''}etherscan.io/tx/${event.transactionHash}`;
|
||||
const keys = Object.keys(event.params).join(', ');
|
||||
const values = Object.keys(event.params).map((name, index) => {
|
||||
const param = event.params[name];
|
||||
|
||||
return (
|
||||
<div className={ styles.eventValue } key={ `${event.key}_val_${index}` }>
|
||||
{ this.renderParam(name, param) }
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<tr className={ classes }>
|
||||
<td className={ styles.timestamp }>
|
||||
<div>{ event.state === 'pending' ? 'pending' : this.formatBlockTimestamp(block) }</div>
|
||||
<div>{ this.formatNumber(transaction.blockNumber) }</div>
|
||||
</td>
|
||||
<td className={ styles.txhash }>
|
||||
{ this.renderAddressName(transaction.from) }
|
||||
</td>
|
||||
<td className={ styles.txhash }>
|
||||
<div className={ styles.eventType }>
|
||||
{ event.type }({ keys })
|
||||
</div>
|
||||
<a href={ url } target='_blank'>{ this.formatHash(event.transactionHash) }</a>
|
||||
</td>
|
||||
<td className={ styles.eventDetails }>
|
||||
<div className={ styles.eventParams }>
|
||||
{ values }
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
formatHash (hash) {
|
||||
if (!hash || hash.length <= 16) {
|
||||
return hash;
|
||||
}
|
||||
|
||||
return `${hash.substr(2, 6)}...${hash.slice(-6)}`;
|
||||
}
|
||||
|
||||
renderAddressName (address, withName = true) {
|
||||
return (
|
||||
<span className={ styles.eventAddress }>
|
||||
<IdentityIcon center inline address={ address } className={ styles.eventIdentityicon } />
|
||||
{ withName ? <IdentityName address={ address } /> : address }
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
renderParam (name, param) {
|
||||
const { api } = this.context;
|
||||
|
||||
switch (param.type) {
|
||||
case 'address':
|
||||
return (
|
||||
<InputAddress
|
||||
disabled
|
||||
text
|
||||
className={ styles.input }
|
||||
value={ param.value }
|
||||
label={ name } />
|
||||
);
|
||||
|
||||
default:
|
||||
let value;
|
||||
if (api.util.isInstanceOf(param.value, BigNumber)) {
|
||||
value = param.value.toFormat(0);
|
||||
} else if (api.util.isArray(param.value)) {
|
||||
value = api.util.bytesToHex(param.value);
|
||||
} else {
|
||||
value = param.value.toString();
|
||||
}
|
||||
|
||||
return (
|
||||
<Input
|
||||
disabled
|
||||
className={ styles.input }
|
||||
value={ value }
|
||||
label={ name } />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
formatBlockTimestamp (block) {
|
||||
if (!block) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return moment(block.timestamp).fromNow();
|
||||
}
|
||||
|
||||
formatNumber (number) {
|
||||
if (!number) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BigNumber(number).toFormat();
|
||||
}
|
||||
|
||||
retrieveTransaction () {
|
||||
const { event, fetchBlock, fetchTransaction } = this.props;
|
||||
|
||||
fetchBlock(event.blockNumber);
|
||||
fetchTransaction(event.transactionHash);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { isTest } = state.nodeStatus;
|
||||
const { blocks, transactions } = state.blockchain;
|
||||
|
||||
return {
|
||||
isTest,
|
||||
blocks,
|
||||
transactions
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({
|
||||
fetchBlock, fetchTransaction
|
||||
}, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(Event);
|
17
js/src/views/Contract/Events/Event/index.js
Normal file
17
js/src/views/Contract/Events/Event/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
export default from './event';
|
@ -14,11 +14,11 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import BigNumber from 'bignumber.js';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
import { Container, ContainerTitle } from '../../../ui';
|
||||
|
||||
import Event from './Event';
|
||||
import styles from '../contract.css';
|
||||
|
||||
export default class Events extends Component {
|
||||
@ -27,106 +27,23 @@ export default class Events extends Component {
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
events: PropTypes.array,
|
||||
isTest: PropTypes.bool
|
||||
}
|
||||
|
||||
state = {
|
||||
transactions: {}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.componentWillReceiveProps(this.props);
|
||||
}
|
||||
|
||||
componentWillReceiveProps (newProps) {
|
||||
this.retrieveTransactions(newProps.events);
|
||||
events: PropTypes.array
|
||||
}
|
||||
|
||||
render () {
|
||||
const { events, isTest } = this.props;
|
||||
const { transactions } = this.state;
|
||||
const { events } = this.props;
|
||||
|
||||
if (!events || !events.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rows = events.map((event) => {
|
||||
const transaction = transactions[event.transactionHash] || {};
|
||||
const classes = `${styles.event} ${styles[event.state]}`;
|
||||
const url = `https://${isTest ? 'testnet.' : ''}etherscan.io/tx/${event.transactionHash}`;
|
||||
const keys = Object.keys(event.params).map((key, index) => {
|
||||
return <div className={ styles.key } key={ `${event.key}_key_${index}` }>{ key }</div>;
|
||||
});
|
||||
const values = Object.values(event.params).map((value, index) => {
|
||||
return (
|
||||
<div className={ styles.value } key={ `${event.key}_val_${index}` }>
|
||||
{ this.renderValue(value) }
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<tr className={ classes } key={ event.key }>
|
||||
<td>{ event.state === 'pending' ? 'pending' : event.blockNumber.toFormat(0) }</td>
|
||||
<td className={ styles.txhash }>
|
||||
<div>{ transaction.from }</div>
|
||||
<a href={ url } target='_blank'>{ event.transactionHash }</a>
|
||||
</td>
|
||||
<td>
|
||||
<div>{ event.type } =></div>
|
||||
{ keys }
|
||||
</td>
|
||||
<td>
|
||||
<div> </div>
|
||||
{ values }
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<ContainerTitle title='events' />
|
||||
<table className={ styles.events }>
|
||||
<tbody>{ rows }</tbody>
|
||||
<tbody>{ events.map((event) => <Event event={ event } key={ event.key } />) }</tbody>
|
||||
</table>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
renderValue (value) {
|
||||
const { api } = this.context;
|
||||
|
||||
if (api.util.isInstanceOf(value, BigNumber)) {
|
||||
return value.toFormat(0);
|
||||
} else if (api.util.isArray(value)) {
|
||||
return api.util.bytesToHex(value);
|
||||
}
|
||||
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
retrieveTransactions (events) {
|
||||
const { api } = this.context;
|
||||
const { transactions } = this.state;
|
||||
const hashes = {};
|
||||
|
||||
events.forEach((event) => {
|
||||
if (!hashes[event.transactionHash] && !transactions[event.transactionHash]) {
|
||||
hashes[event.transactionHash] = true;
|
||||
}
|
||||
});
|
||||
|
||||
Promise
|
||||
.all(Object.keys(hashes).map((hash) => api.eth.getTransactionByHash(hash)))
|
||||
.then((newTransactions) => {
|
||||
this.setState({
|
||||
transactions: newTransactions.reduce((store, transaction) => {
|
||||
transactions[transaction.hash] = transaction;
|
||||
return transactions;
|
||||
}, transactions)
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -24,9 +24,12 @@
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
.event {
|
||||
.events tr {
|
||||
line-height: 32px;
|
||||
vertical-align: top;
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
.event {
|
||||
}
|
||||
|
||||
.event td {
|
||||
@ -43,9 +46,6 @@
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.value {
|
||||
}
|
||||
|
||||
.event td div {
|
||||
white-space: nowrap;
|
||||
}
|
||||
@ -56,3 +56,35 @@
|
||||
.pending {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
padding-top: 1.5em;
|
||||
text-align: right;
|
||||
line-height: 1.5em;
|
||||
opacity: 0.5;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.eventDetails {
|
||||
}
|
||||
|
||||
.eventType {
|
||||
}
|
||||
|
||||
.eventParams {
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
.eventValue {
|
||||
margin-top: -16px;
|
||||
}
|
||||
|
||||
.eventAddress {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.eventIdentityicon {
|
||||
margin-bottom: -10px;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
@ -116,7 +116,6 @@ class Contract extends Component {
|
||||
contract={ contract }
|
||||
values={ queryValues } />
|
||||
<Events
|
||||
isTest={ isTest }
|
||||
events={ allEvents } />
|
||||
</Page>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user