Merge pull request #1041 from embark-framework/feature/debug-button

feat: add debug button to transaction and contract log
This commit is contained in:
Iuri Matias 2018-11-09 04:34:03 -05:00 committed by GitHub
commit 13f7a3ff41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 201 additions and 95 deletions

View File

@ -7,14 +7,23 @@ import {
Button
} from "reactstrap";
import ReactJson from 'react-json-view';
import DebugButton from './DebugButton';
class ContractDebugger extends Component {
handleChange(e) {
this.setState({txHash: e.target.value});
constructor(props) {
super(props);
this.state = {txHash: ''};
}
debug(_e) {
this.props.startDebug(this.state.txHash);
componentDidMount() {
if (this.props.debuggerTransactionHash) {
this.setState({txHash: this.props.debuggerTransactionHash});
this.props.startDebug(this.props.debuggerTransactionHash);
}
}
handleChange(e) {
this.setState({txHash: e.target.value});
}
debugJumpBack(_e) {
@ -46,8 +55,8 @@ class ContractDebugger extends Component {
<div>
<Row>
<Col>
<Input name="txHash" id="txHash" onChange={(e) => this.handleChange(e)}/>
<Button color="primary" onClick={(e) => this.debug(e)}>Debug Tx</Button>
<Input name="txHash" id="txHash" value={this.state.txHash} onChange={(e) => this.handleChange(e)}/>
<DebugButton forceDebuggable transaction={{hash: this.state.txHash}} />
</Col>
</Row>
<Row>
@ -74,7 +83,7 @@ class ContractDebugger extends Component {
}
ContractDebugger.propTypes = {
contract: PropTypes.object.isRequired,
debuggerTransactionHash: PropTypes.string,
startDebug: PropTypes.func,
debugJumpBack: PropTypes.func,
debugJumpForward: PropTypes.func,

View File

@ -7,7 +7,6 @@ import classnames from 'classnames';
import ContractDetail from '../components/ContractDetail';
import ContractTransactionsContainer from '../containers/ContractTransactionsContainer';
import ContractOverviewContainer from '../containers/ContractOverviewContainer';
import ContractDebuggerContainer from '../containers/ContractDebuggerContainer';
class ContractLayout extends React.Component {
constructor(props) {
@ -57,14 +56,6 @@ class ContractLayout extends React.Component {
<FontAwesomeIcon className="mr-2" name="list-alt" />Transactions
</NavLink>
</NavItem>
<NavItem>
<NavLink
className={classnames({ active: this.state.activeTab === '4' })}
onClick={() => { this.toggle('4'); }}
>
<FontAwesomeIcon className="mr-2" name="bug" />Debugger
</NavLink>
</NavItem>
</Nav>
<TabContent activeTab={this.state.activeTab}>
<TabPane tabId="1">
@ -76,9 +67,6 @@ class ContractLayout extends React.Component {
<TabPane tabId="3">
<ContractTransactionsContainer contract={this.props.contract} />
</TabPane>
<TabPane tabId="4">
<ContractDebuggerContainer contract={this.props.contract} />
</TabPane>
</TabContent>
</CardBody>
</Card>

View File

@ -2,6 +2,8 @@ import PropTypes from "prop-types";
import React from 'react';
import {Row, Col, Table, FormGroup, Label, Input, Form} from 'reactstrap';
import DebugButton from './DebugButton'
const TX_STATES = {Success: '0x1', Fail: '0x0', Any: ''};
const EVENT = 'event';
const FUNCTION = 'function';
@ -107,6 +109,7 @@ class ContractTransactions extends React.Component {
<Table>
<thead>
<tr>
<th></th>
<th>Call</th>
<th>Events</th>
<th>Gas Used</th>
@ -120,6 +123,7 @@ class ContractTransactions extends React.Component {
this.dataToDisplay().map((log, index) => {
return (
<tr key={'log-' + index}>
<td><DebugButton forceDebuggable transaction={{hash: log.transactionHash}}/></td>
<td>{`${log.name}.${log.functionName}(${log.paramString})`}</td>
<td>{log.events.join(', ')}</td>
<td>{log.gasUsed}</td>

View File

@ -0,0 +1,42 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Button} from "reactstrap";
import FontAwesome from 'react-fontawesome';
import {withRouter} from "react-router-dom";
class DebugButton extends React.Component {
onClick() {
this.props.history.push(`/embark/editor?debuggerTransactionHash=${this.props.transaction.hash}`);
}
isDebuggable() {
return this.props.forceDebuggable ||
(this.props.contracts && this.props.contracts.find(contract => contract.address === this.props.transaction.to));
}
render() {
if (!this.isDebuggable()) {
return <React.Fragment />
}
return (
<Button color="primary" onClick={() => this.onClick()}>
<FontAwesome className="mr-2" name="bug"/>
Debug
</Button>
);
}
}
DebugButton.defaultProps = {
forceDebuggable: false
}
DebugButton.propTypes = {
forceDebuggable: PropTypes.bool,
history: PropTypes.object,
transaction: PropTypes.object,
contracts: PropTypes.arrayOf(PropTypes.object)
};
export default withRouter(DebugButton);

View File

@ -123,13 +123,14 @@ class TextEditor extends React.Component {
}
componentDidUpdate(prevProps) {
if (this.props.currentFile.content !== prevProps.currentFile.content) {
const isNewContent = this.props.currentFile.content !== prevProps.currentFile.content;
if (isNewContent) {
editor.setValue(this.props.currentFile.content || '');
}
this.updateMarkers();
const expectedDecorationsLength = this.props.debuggerLine ? this.props.breakpoints.length + 1 : this.props.breakpoints.length;
if (expectedDecorationsLength !== this.state.decorations.length || this.props.debuggerLine !== prevProps.debuggerLine) {
if (expectedDecorationsLength !== this.state.decorations.length || this.props.debuggerLine !== prevProps.debuggerLine || isNewContent) {
this.updateDecorations();
}

View File

@ -22,6 +22,10 @@ class TextEditorToolbar extends Component {
return tab === TextEditorToolbarTabs.Browser;
}
isDebuggerTab(tab) {
return tab === TextEditorToolbarTabs.Debugger;
}
renderTab(tab) {
return (
<NavLink key={tab.label} className={classnames('btn', { active: this.isActiveTab(tab)})} onClick={() => this.props.openAsideTab(tab)}>
@ -45,7 +49,8 @@ class TextEditorToolbar extends Component {
</li>
<li className="breadcrumb-menu">
<Nav className="btn-group">
{this.props.isContract && Object.values(TextEditorToolbarTabs).map(tab => !this.isBrowserTab(tab) && this.renderTab(tab))}
{this.props.isContract && Object.values(TextEditorToolbarTabs).map(tab => !this.isBrowserTab(tab) && !this.isDebuggerTab(tab) && this.renderTab(tab))}
{this.renderTab(TextEditorToolbarTabs.Debugger)}
{this.renderTab(TextEditorToolbarTabs.Browser)}
</Nav>
</li>

View File

@ -3,17 +3,23 @@ import {Link} from 'react-router-dom';
import {Row, Col, Card, CardHeader, CardBody} from 'reactstrap';
import PropTypes from 'prop-types';
import DebugButton from './DebugButton';
import Description from './Description';
import CardTitleIdenticon from './CardTitleIdenticon';
import {utils} from 'web3';
const Transaction = ({transaction}) => (
const Transaction = ({transaction, contracts}) => (
<Row>
<Col>
<Card>
<CardHeader>
<CardTitleIdenticon id={transaction.hash}>Transaction {transaction.hash}</CardTitleIdenticon>
<CardTitleIdenticon id={transaction.hash}>
Transaction {transaction.hash}
<div className="float-right">
<DebugButton contracts={contracts} transaction={transaction} />
</div>
</CardTitleIdenticon>
</CardHeader>
<CardBody>
<dl className="row">
@ -33,6 +39,7 @@ const Transaction = ({transaction}) => (
);
Transaction.propTypes = {
contracts: PropTypes.arrayOf(PropTypes.object),
transaction: PropTypes.object
};

View File

@ -3,10 +3,11 @@ import {Link} from "react-router-dom";
import {Row, Col, Card, CardHeader, CardBody} from 'reactstrap';
import PropTypes from 'prop-types';
import DebugButton from './DebugButton';
import CardTitleIdenticon from './CardTitleIdenticon';
import Pagination from "./Pagination";
const Transactions = ({transactions, changePage, currentPage, numberOfPages}) => (
const Transactions = ({transactions, contracts, changePage, currentPage, numberOfPages}) => (
<Row>
<Col>
<Card>
@ -21,6 +22,11 @@ const Transactions = ({transactions, changePage, currentPage, numberOfPages}) =>
{transaction.hash}
</Link>
</CardTitleIdenticon>
<Row>
<Col>
<DebugButton transaction={transaction} contracts={contracts} />
</Col>
</Row>
<Row>
<Col md={6}>
<strong>Block number</strong>
@ -50,6 +56,7 @@ const Transactions = ({transactions, changePage, currentPage, numberOfPages}) =>
Transactions.propTypes = {
transactions: PropTypes.arrayOf(PropTypes.object),
contracts: PropTypes.arrayOf(PropTypes.object),
changePage: PropTypes.func,
currentPage: PropTypes.number,
numberOfPages: PropTypes.number

View File

@ -1,42 +1,43 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import {startDebug, debugJumpBack, debugJumpForward, debugStepOverForward, debugStepOverBackward, debugStepIntoForward, debugStepIntoBackward} from '../actions';
import {
startDebug,
debugJumpBack,
debugJumpForward,
debugStepOverForward,
debugStepOverBackward,
debugStepIntoForward,
debugStepIntoBackward
} from '../actions';
import ContractDebugger from '../components/ContractDebugger';
import DataWrapper from "../components/DataWrapper";
import {getContractLogsByContract, debuggerInfo} from "../reducers/selectors";
import {getDebuggerInfo} from "../reducers/selectors";
class ContractDebuggerContainer extends Component {
render() {
return (
<DataWrapper shouldRender={this.props.contractLogs !== undefined } {...this.props} render={() => (
<ContractDebugger contract={this.props.contract} startDebug={this.props.startDebug}
debugJumpBack={this.props.debugJumpBack} debugJumpForward={this.props.debugJumpForward}
debugStepOverForward={this.props.debugStepOverForward}
debugStepOverBackward={this.props.debugStepOverBackward}
debugStepIntoForward={this.props.debugStepIntoForward}
debugStepIntoBackward={this.props.debugStepIntoBackward}
debuggerInfo={this.props.debuggerInfo}
/>
)} />
<ContractDebugger debuggerTransactionHash={this.props.debuggerTransactionHash}
startDebug={this.props.startDebug}
debugJumpBack={this.props.debugJumpBack}
debugJumpForward={this.props.debugJumpForward}
debugStepOverForward={this.props.debugStepOverForward}
debugStepOverBackward={this.props.debugStepOverBackward}
debugStepIntoForward={this.props.debugStepIntoForward}
debugStepIntoBackward={this.props.debugStepIntoBackward}
debuggerInfo={this.props.debuggerInfo}/>
);
}
}
function mapStateToProps(state, props) {
return {
contractLogs: getContractLogsByContract(state, props.contract.className),
debuggerInfo: debuggerInfo(state)
debuggerInfo: getDebuggerInfo(state)
};
}
ContractDebuggerContainer.propTypes = {
contractLogs: PropTypes.array,
fetchContractLogs: PropTypes.func,
listenToContractLogs: PropTypes.func,
match: PropTypes.object,
contract: PropTypes.object,
debuggerTransactionHash: PropTypes.string,
startDebug: PropTypes.func,
debugJumpBack: PropTypes.func,
debugJumpForward: PropTypes.func,

View File

@ -1,13 +1,20 @@
import React from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {withRouter} from "react-router-dom";
import {Row, Col} from 'reactstrap';
import TextEditorAsideContainer from './TextEditorAsideContainer';
import TextEditorContainer from './TextEditorContainer';
import FileExplorerContainer from './FileExplorerContainer';
import TextEditorToolbarContainer from './TextEditorToolbarContainer';
import {fetchEditorTabs as fetchEditorTabsAction} from '../actions';
import {getCurrentFile} from '../reducers/selectors';
import {
fetchEditorTabs as fetchEditorTabsAction,
contracts as contractsAction,
file as fileAction,
transaction as transactionAction
} from '../actions';
import {getCurrentFile, getContracts, getTransaction} from '../reducers/selectors';
import {getDebuggerTransactionHash} from '../utils/utils';
import classnames from 'classnames';
import Resizable from 're-resizable';
import {OPERATIONS} from '../constants';
@ -24,16 +31,21 @@ class EditorContainer extends React.Component {
this.windowWidth = window.innerWidth;
this.state = {
currentAsideTab: {}, showHiddenFiles: false, currentFile: this.props.currentFile,
currentAsideTab: {},
showHiddenFiles: false,
currentFile: this.props.currentFile,
editorHeight: this.DEFAULT_HEIGHT,
editorWidth: ((this.windowWidth < this.SMALL_SIZE) ? this.DEFAULT_EDITOR_WIDTH_SMALL : this.DEFAULT_EDITOR_WIDTH) + '%',
asideHeight: '100%', asideWidth: '25%',
asideHeight: '100%',
asideWidth: '25%',
isSmallSize: (this.windowWidth < this.SMALL_SIZE)
};
}
componentDidMount() {
this.props.fetchEditorTabs();
this.props.fetchContracts();
this.props.fetchTransaction(this.props.debuggerTransactionHash);
window.addEventListener("resize", this.updateDimensions.bind(this));
}
@ -56,6 +68,14 @@ class EditorContainer extends React.Component {
if (this.props.currentFile.path !== prevProps.currentFile.path) {
this.setState({currentFile: this.props.currentFile});
}
if(this.props.contracts && this.props.transaction !== prevProps.transaction) {
const debuggingContract = this.props.contracts.find(contract => contract.address === this.props.transaction.to)
if (debuggingContract) {
this.setState({currentAsideTab: 'debugger'})
this.props.fetchFile({path: debuggingContract.path});
}
}
}
isContract() {
@ -129,6 +149,7 @@ class EditorContainer extends React.Component {
const aside = (
<div className="editor-aside">
<TextEditorAsideContainer currentAsideTab={this.state.currentAsideTab}
debuggerTransactionHash={this.props.debuggerTransactionHash}
currentFile={this.props.currentFile}/>
</div>
);
@ -188,21 +209,36 @@ class EditorContainer extends React.Component {
}
}
function mapStateToProps(state, _props) {
function mapStateToProps(state, props) {
const currentFile = getCurrentFile(state);
const debuggerTransactionHash = getDebuggerTransactionHash(props.location);
return {
currentFile
currentFile,
debuggerTransactionHash,
transaction: getTransaction(state, debuggerTransactionHash),
contracts: getContracts(state)
};
}
EditorContainer.propTypes = {
debuggerTransactionHash: PropTypes.string,
contracts: PropTypes.array,
transaction: PropTypes.object,
fetchContracts: PropTypes.func,
fetchFile: PropTypes.func,
fetchTransaction: PropTypes.func,
currentFile: PropTypes.object,
fetchEditorTabs: PropTypes.func
};
export default connect(
export default withRouter(connect(
mapStateToProps,
{fetchEditorTabs: fetchEditorTabsAction.request}
)(EditorContainer);
{
fetchEditorTabs: fetchEditorTabsAction.request,
fetchTransaction: transactionAction.request,
fetchFile: fileAction.request,
fetchContracts: contractsAction.request
},
)(EditorContainer));

View File

@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
import {Card, CardBody} from 'reactstrap';
import Preview from '../components/Preview';
import {contracts as contractsAction} from '../actions';
import {getContractsByPath} from "../reducers/selectors";
import ContractDetail from '../components/ContractDetail';
import ContractTransactionsContainer from './ContractTransactionsContainer';
@ -13,19 +12,8 @@ import ContractDebuggerContainer from '../containers/ContractDebuggerContainer';
import { TextEditorToolbarTabs } from '../components/TextEditorToolbar';
class TextEditorAsideContainer extends Component {
componentDidMount() {
this.props.fetchContracts();
}
renderContent(contract, index) {
switch (this.props.currentAsideTab.label) {
case TextEditorToolbarTabs.Debugger.label:
return (
<React.Fragment>
<h2>{contract.className} - Debugger</h2>
<ContractDebuggerContainer key={index} contract={contract}/>
</React.Fragment>
);
switch (this.props.currentAsideTab) {
case TextEditorToolbarTabs.Details.label:
return (
<React.Fragment>
@ -56,15 +44,21 @@ class TextEditorAsideContainer extends Component {
if (this.props.currentAsideTab.label === TextEditorToolbarTabs.Browser.label) {
return <Preview/>;
}
return this.props.contracts.map((contract, index) => {
if (this.props.currentAsideTab.label === TextEditorToolbarTabs.Debugger.label) {
return (
<Card key={'contract-' + index} className="editor-aside-card rounded-0 border-top-0">
<CardBody>
{this.renderContent(contract, index)}
</CardBody>
</Card>
<React.Fragment>
<h2>Debugger</h2>
<ContractDebuggerContainer debuggerTransactionHash={this.props.debuggerTransactionHash}/>
</React.Fragment>
);
});
}
return this.props.contracts.map((contract, index) => (
<Card key={'contract-' + index} className="editor-aside-card rounded-0 border-top-0">
<CardBody>
{this.renderContent(contract, index)}
</CardBody>
</Card>
));
}
}
@ -76,15 +70,12 @@ function mapStateToProps(state, props) {
TextEditorAsideContainer.propTypes = {
currentFile: PropTypes.object,
currentAsideTab: PropTypes.object,
contract: PropTypes.array,
fetchContracts: PropTypes.func,
debuggerTransactionHash: PropTypes.string,
currentAsideTab: PropTypes.string,
contracts: PropTypes.array
};
export default connect(
mapStateToProps,
{
fetchContracts: contractsAction.request
}
{}
)(TextEditorAsideContainer);

View File

@ -3,20 +3,21 @@ import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import {withRouter} from 'react-router-dom';
import {transaction as transactionAction} from '../actions';
import {transaction as transactionAction, contracts as contractsAction} from '../actions';
import Transaction from '../components/Transaction';
import DataWrapper from "../components/DataWrapper";
import {getTransaction} from "../reducers/selectors";
import {getTransaction, getContracts} from "../reducers/selectors";
class TransactionContainer extends Component {
componentDidMount() {
this.props.fetchContracts();
this.props.fetchTransaction(this.props.match.params.hash);
}
render() {
return (
<DataWrapper shouldRender={this.props.transaction !== undefined } {...this.props} render={({transaction}) => (
<Transaction transaction={transaction} />
<Transaction contracts={this.props.contracts} transaction={transaction} />
)} />
);
}
@ -25,6 +26,7 @@ class TransactionContainer extends Component {
function mapStateToProps(state, props) {
return {
transaction: getTransaction(state, props.match.params.hash),
contracts: getContracts(state),
error: state.errorMessage,
loading: state.loading
};
@ -33,7 +35,8 @@ function mapStateToProps(state, props) {
TransactionContainer.propTypes = {
match: PropTypes.object,
transaction: PropTypes.object,
transactions: PropTypes.arrayOf(PropTypes.object),
contracts: PropTypes.arrayOf(PropTypes.object),
fetchContracts: PropTypes.func,
fetchTransaction: PropTypes.func,
error: PropTypes.string
};
@ -41,6 +44,7 @@ TransactionContainer.propTypes = {
export default withRouter(connect(
mapStateToProps,
{
fetchContracts: contractsAction.request,
fetchTransaction: transactionAction.request
}
)(TransactionContainer));

View File

@ -2,10 +2,10 @@ import React, {Component} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import {transactions as transactionsAction, initBlockHeader, stopBlockHeader} from '../actions';
import {transactions as transactionsAction, initBlockHeader, stopBlockHeader, contracts as contractsAction} from '../actions';
import Transactions from '../components/Transactions';
import DataWrapper from "../components/DataWrapper";
import {getTransactions} from "../reducers/selectors";
import {getTransactions, getContracts} from "../reducers/selectors";
const MAX_TXS = 10; // TODO use same constant as API
@ -20,6 +20,7 @@ class TransactionsContainer extends Component {
componentDidMount() {
this.props.fetchTransactions();
this.props.fetchContracts();
this.props.initBlockHeader();
}
@ -59,7 +60,9 @@ class TransactionsContainer extends Component {
return (
<React.Fragment>
<DataWrapper shouldRender={this.currentTxs.length > 0} {...this.props} render={() => (
<Transactions transactions={this.currentTxs} numberOfPages={this.getNumberOfPages()}
<Transactions transactions={this.currentTxs}
contracts={this.props.contracts}
numberOfPages={this.getNumberOfPages()}
changePage={(newPage) => this.changePage(newPage)}
currentPage={this.state.currentPage || this.getNumberOfPages()} />
)} />
@ -69,12 +72,19 @@ class TransactionsContainer extends Component {
}
function mapStateToProps(state) {
return {transactions: getTransactions(state), error: state.errorMessage, loading: state.loading};
return {
transactions: getTransactions(state),
contracts: getContracts(state),
error: state.errorMessage,
loading: state.loading
};
}
TransactionsContainer.propTypes = {
transactions: PropTypes.arrayOf(PropTypes.object),
contracts: PropTypes.arrayOf(PropTypes.object),
fetchTransactions: PropTypes.func,
fetchContracts: PropTypes.func,
initBlockHeader: PropTypes.func,
stopBlockHeader: PropTypes.func,
error: PropTypes.string,
@ -85,6 +95,7 @@ export default connect(
mapStateToProps,
{
fetchTransactions: transactionsAction.request,
fetchContracts: contractsAction.request,
initBlockHeader,
stopBlockHeader
},

View File

@ -225,7 +225,7 @@ export function getWeb3Deployments(state) {
return state.web3.deployments;
}
export function debuggerInfo(state) {
export function getDebuggerInfo(state) {
return state.debuggerInfo;
}

View File

@ -25,6 +25,10 @@ export function getQueryToken(location) {
return qs.parse(location.search, {ignoreQueryPrefix: true}).token;
}
export function getDebuggerTransactionHash(location) {
return qs.parse(location.search, {ignoreQueryPrefix: true}).debuggerTransactionHash;
}
export function stripQueryToken(location) {
const _location = Object.assign({}, location);
_location.search = _location.search.replace(

View File

@ -607,12 +607,8 @@ class BlockchainConnector {
return this.web3.eth.net.getId();
}
//TODO: fix me, why is this gasPrice??
getTransaction(hash, cb) {
const self = this;
this.onReady(() => {
self.web3.eth.getGasPrice(cb);
});
return this.web3.eth.getTransaction(hash, cb);
}
ContractObject(params) {