Add Contract Events to UI

This commit is contained in:
Anthony Laibe 2018-10-09 15:42:46 +01:00 committed by Pascal Precht
parent 6ce78dfce3
commit 96c3575e75
No known key found for this signature in database
GPG Key ID: 0EE28D8D6FD85D7D
7 changed files with 124 additions and 35 deletions

View File

@ -152,6 +152,13 @@ export const contractLogs = {
failure: (error) => action(CONTRACT_LOGS[FAILURE], {error}) failure: (error) => action(CONTRACT_LOGS[FAILURE], {error})
}; };
export const CONTRACT_EVENTS = createRequestTypes('CONTRACT_EVENTS');
export const contractEvents = {
request: () => action(CONTRACT_EVENTS[REQUEST]),
success: (contractEvents) => action(CONTRACT_EVENTS[SUCCESS], {contractEvents}),
failure: (error) => action(CONTRACT_EVENTS[FAILURE], {error})
};
export const CONTRACTS = createRequestTypes('CONTRACTS'); export const CONTRACTS = createRequestTypes('CONTRACTS');
export const contracts = { export const contracts = {
request: () => action(CONTRACTS[REQUEST]), request: () => action(CONTRACTS[REQUEST]),
@ -299,6 +306,7 @@ export const gasOracle = {
export const WATCH_NEW_PROCESS_LOGS = 'WATCH_NEW_PROCESS_LOGS'; export const WATCH_NEW_PROCESS_LOGS = 'WATCH_NEW_PROCESS_LOGS';
export const STOP_NEW_PROCESS_LOGS = 'STOP_NEW_PROCESS_LOGS'; export const STOP_NEW_PROCESS_LOGS = 'STOP_NEW_PROCESS_LOGS';
export const WATCH_NEW_CONTRACT_LOGS = 'WATCH_NEW_CONTRACT_LOGS'; export const WATCH_NEW_CONTRACT_LOGS = 'WATCH_NEW_CONTRACT_LOGS';
export const WATCH_NEW_CONTRACT_EVENTS = 'WATCH_NEW_CONTRACT_EVENTS';
export const INIT_BLOCK_HEADER = 'INIT_BLOCK_HEADER'; export const INIT_BLOCK_HEADER = 'INIT_BLOCK_HEADER';
export const STOP_BLOCK_HEADER = 'STOP_BLOCK_HEADER'; export const STOP_BLOCK_HEADER = 'STOP_BLOCK_HEADER';
export const WATCH_GAS_ORACLE = 'WATCH_GAS_ORACLE'; export const WATCH_GAS_ORACLE = 'WATCH_GAS_ORACLE';
@ -324,6 +332,12 @@ export function listenToContractLogs() {
}; };
} }
export function listenToContractEvents() {
return {
type: WATCH_NEW_CONTRACT_EVENTS
};
}
export function initBlockHeader(){ export function initBlockHeader(){
return { return {
type: INIT_BLOCK_HEADER type: INIT_BLOCK_HEADER

View File

@ -7,7 +7,14 @@ import {
Form Form
} from "tabler-react"; } from "tabler-react";
const ANY_STATE = 'Any';
const TX_STATES = ['Success', 'Fail', ANY_STATE];
class ContractLogger extends React.Component { class ContractLogger extends React.Component {
constructor(props) {
super(props);
this.state = {method: '', event: '', status: ANY_STATE};
}
getMethods() { getMethods() {
if (!this.props.contract.abiDefinition) { if (!this.props.contract.abiDefinition) {
@ -26,45 +33,60 @@ class ContractLogger extends React.Component {
return this.props.contract.abiDefinition.filter(method => method.type === 'event'); return this.props.contract.abiDefinition.filter(method => method.type === 'event');
} }
updateState(key, value) {
this.setState({[key]: value});
}
dataToDisplay() {
return this.props.contractLogs.map(contractLog => {
const events = this.props.contractEvents
.filter(contractEvent => contractEvent.transactionHash === contractLog.transactionHash)
.map(contractEvent => contractEvent.event);
contractLog.events = events;
return contractLog;
}).filter(contractLog => {
if (this.state.method || this.state.event || this.state.status !== ANY_STATE) {
return contractLog.status === '0x1' && this.state.status === 'Success' &&
(this.state.method === contractLog.functionName || contractLog.events.includes(this.state.event));
}
return true;
});
}
render() { render() {
return ( return (
<Page.Content title={this.props.contractName + ' Logger'}> <Page.Content title={this.props.contract.className + ' Logger'}>
<Form> <Form>
<Grid.Row> <Grid.Row>
<Grid.Col md={6}> <Grid.Col md={6}>
<Form.Group label="Functions"> <Form.Group label="Functions">
<Form.Select> <Form.Select onChange={(event) => this.updateState('method', event.target.value)} value={this.state.method}>
{this.getMethods().map((method, index) => <option key={index}>{method.name}</option>)} <option value=""></option>
{this.getMethods().map((method, index) => <option value={method.name} key={index}>{method.name}</option>)}
</Form.Select> </Form.Select>
</Form.Group> </Form.Group>
</Grid.Col> </Grid.Col>
<Grid.Col md={6}> <Grid.Col md={6}>
<Form.Group label="Events"> <Form.Group label="Events">
<Form.Select> <Form.Select onChange={(event) => this.updateState('event', event.target.value)} value={this.state.event}>
{this.getEvents().map((event, index) => <option key={index}>{event.name}</option>)} <option value=""></option>
{this.getEvents().map((event, index) => <option value={event.name} key={index}>{event.name}</option>)}
</Form.Select> </Form.Select>
</Form.Group> </Form.Group>
</Grid.Col> </Grid.Col>
<Grid.Col> <Grid.Col>
<Form.Group label="Tx Status"> <Form.Group label="Tx Status">
<Form.Radio {TX_STATES.map(state => (
isInline <Form.Radio
label="Failed" key={state}
name="example-inline-radios" isInline
value="option1" label={state}
/> value={state}
<Form.Radio onChange={(event) => this.updateState('status', event.target.value)}
isInline checked={state === this.state.status}
label="Success" />
name="example-inline-radios" ))}
value="option2"
/>
<Form.Radio
isInline
label="Any"
name="example-inline-radios"
value="option3"
/>
</Form.Group> </Form.Group>
</Grid.Col> </Grid.Col>
</Grid.Row> </Grid.Row>
@ -78,23 +100,25 @@ class ContractLogger extends React.Component {
className="text-nowrap"> className="text-nowrap">
<Table.Header> <Table.Header>
<Table.Row> <Table.Row>
<Table.ColHeader>call</Table.ColHeader> <Table.ColHeader>Call</Table.ColHeader>
<Table.ColHeader>Transaction hash</Table.ColHeader> <Table.ColHeader>Events</Table.ColHeader>
<Table.ColHeader>Gas Used</Table.ColHeader> <Table.ColHeader>Gas Used</Table.ColHeader>
<Table.ColHeader>Block number</Table.ColHeader> <Table.ColHeader>Block number</Table.ColHeader>
<Table.ColHeader>Status</Table.ColHeader> <Table.ColHeader>Status</Table.ColHeader>
<Table.ColHeader>Transaction hash</Table.ColHeader>
</Table.Row> </Table.Row>
</Table.Header> </Table.Header>
<Table.Body> <Table.Body>
{ {
this.props.contractLogs.map((log, index) => { this.dataToDisplay().map((log, index) => {
return ( return (
<Table.Row key={'log-' + index}> <Table.Row key={'log-' + index}>
<Table.Col>{`${log.name}.${log.functionName}(${log.paramString})`}</Table.Col> <Table.Col>{`${log.name}.${log.functionName}(${log.paramString})`}</Table.Col>
<Table.Col>{log.transactionHash}</Table.Col> <Table.Col>{log.events.join(' ')}</Table.Col>
<Table.Col>{log.gasUsed}</Table.Col> <Table.Col>{log.gasUsed}</Table.Col>
<Table.Col>{log.blockNumber}</Table.Col> <Table.Col>{log.blockNumber}</Table.Col>
<Table.Col>{log.status}</Table.Col> <Table.Col>{log.status}</Table.Col>
<Table.Col>{log.transactionHash}</Table.Col>
</Table.Row> </Table.Row>
); );
}) })
@ -109,8 +133,8 @@ class ContractLogger extends React.Component {
} }
ContractLogger.propTypes = { ContractLogger.propTypes = {
contractName: PropTypes.string.isRequired, contractLogs: PropTypes.array,
contractLogs: PropTypes.array.isRequired.Header, contractEvents: PropTypes.array,
contract: PropTypes.object.isRequired contract: PropTypes.object.isRequired
}; };

View File

@ -1,24 +1,31 @@
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 {contractLogs as contractLogsAction, listenToContractLogs} from '../actions'; import {contractEvents as contractEventsAction, contractLogs as contractLogsAction, listenToContractLogs, listenToContractEvents} from '../actions';
import ContractLogger from '../components/ContractLogger'; import ContractLogger from '../components/ContractLogger';
import DataWrapper from "../components/DataWrapper"; import DataWrapper from "../components/DataWrapper";
import {getContractLogsByContract} from "../reducers/selectors"; import {getContractLogsByContract, getContractEventsByContract} from "../reducers/selectors";
class ContractLoggerContainer extends Component { class ContractLoggerContainer extends Component {
componentDidMount() { componentDidMount() {
if (this.props.contractLogs.length === 0) { if (this.props.contractLogs.length === 0) {
this.props.listenToContractLogs(); this.props.listenToContractLogs();
this.props.fetchContractLogs(this.props.contract.className); this.props.fetchContractLogs();
}
if (this.props.contractEvents.length === 0) {
this.props.listenToContractEvents();
this.props.fetchContractEvents();
} }
} }
render() { render() {
return ( return (
<DataWrapper shouldRender={this.props.contractLogs !== undefined } {...this.props} render={() => ( <DataWrapper shouldRender={this.props.contractLogs !== undefined } {...this.props} render={() => (
<ContractLogger contractLogs={this.props.contractLogs} contractName={this.props.contract.className}/> <ContractLogger contractLogs={this.props.contractLogs}
contractEvents={this.props.contractEvents}
contract={this.props.contract}/>
)} /> )} />
); );
} }
@ -26,15 +33,19 @@ class ContractLoggerContainer extends Component {
function mapStateToProps(state, props) { function mapStateToProps(state, props) {
return { return {
contractLogs: getContractLogsByContract(state, props.contract.className) contractLogs: getContractLogsByContract(state, props.contract.className),
contractEvents: getContractEventsByContract(state, props.match.params.contractName)
}; };
} }
ContractLoggerContainer.propTypes = { ContractLoggerContainer.propTypes = {
contract: PropTypes.object, contract: PropTypes.object,
contractLogs: PropTypes.array, contractLogs: PropTypes.array,
contractEvents: PropTypes.array,
fetchContractLogs: PropTypes.func, fetchContractLogs: PropTypes.func,
listenToContractLogs: PropTypes.func, listenToContractLogs: PropTypes.func,
fetchContractEvents: PropTypes.func,
listenToContractEvents: PropTypes.func,
match: PropTypes.object match: PropTypes.object
}; };
@ -42,6 +53,8 @@ export default connect(
mapStateToProps, mapStateToProps,
{ {
fetchContractLogs: contractLogsAction.request, fetchContractLogs: contractLogsAction.request,
listenToContractLogs: listenToContractLogs listenToContractLogs: listenToContractLogs,
fetchContractEvents: contractEventsAction.request,
listenToContractEvents: listenToContractEvents
} }
)(ContractLoggerContainer); )(ContractLoggerContainer);

View File

@ -21,6 +21,11 @@ const entitiesDefaultState = {
contractDeploys: [], contractDeploys: [],
contractCompiles: [], contractCompiles: [],
contractLogs: [], contractLogs: [],
<<<<<<< HEAD
=======
contractEvents: [],
commands: [],
>>>>>>> Add Contract Events to UI
messages: [], messages: [],
messageChannels: [], messageChannels: [],
versions: [], versions: [],

View File

@ -64,6 +64,10 @@ export function getContractLogsByContract(state, contractName) {
return state.entities.contractLogs.filter((contractLog => contractLog.name === contractName)); return state.entities.contractLogs.filter((contractLog => contractLog.name === contractName));
} }
export function getContractEventsByContract(state, contractName) {
return state.entities.contractEvents.filter((contractEvent => contractEvent.name === contractName));
}
export function getContracts(state) { export function getContracts(state) {
return state.entities.contracts.filter(contract => !contract.isFiddle); return state.entities.contracts.filter(contract => !contract.isFiddle);
} }

View File

@ -27,6 +27,7 @@ export const fetchProcesses = doRequest.bind(null, actions.processes, api.fetchP
export const postCommand = doRequest.bind(null, actions.commands, api.postCommand); export const postCommand = doRequest.bind(null, actions.commands, api.postCommand);
export const postCommandSuggestions = doRequest.bind(null, actions.commandSuggestions, api.postCommandSuggestions); export const postCommandSuggestions = doRequest.bind(null, actions.commandSuggestions, api.postCommandSuggestions);
export const fetchProcessLogs = doRequest.bind(null, actions.processLogs, api.fetchProcessLogs); export const fetchProcessLogs = doRequest.bind(null, actions.processLogs, api.fetchProcessLogs);
export const fetchContractEvents = doRequest.bind(null, actions.contractEvents, api.fetchContractEvents);
export const fetchContractLogs = doRequest.bind(null, actions.contractLogs, api.fetchContractLogs); export const fetchContractLogs = doRequest.bind(null, actions.contractLogs, api.fetchContractLogs);
export const fetchContracts = doRequest.bind(null, actions.contracts, api.fetchContracts); export const fetchContracts = doRequest.bind(null, actions.contracts, api.fetchContracts);
export const fetchContract = doRequest.bind(null, actions.contract, api.fetchContract); export const fetchContract = doRequest.bind(null, actions.contract, api.fetchContract);
@ -98,6 +99,10 @@ export function *watchFetchContractLogs() {
yield takeEvery(actions.CONTRACT_LOGS[actions.REQUEST], fetchContractLogs); yield takeEvery(actions.CONTRACT_LOGS[actions.REQUEST], fetchContractLogs);
} }
export function *watchFetchContractEvents() {
yield takeEvery(actions.CONTRACT_EVENTS[actions.REQUEST], fetchContractEvents);
}
export function *watchFetchContract() { export function *watchFetchContract() {
yield takeEvery(actions.CONTRACT[actions.REQUEST], fetchContract); yield takeEvery(actions.CONTRACT[actions.REQUEST], fetchContract);
} }
@ -278,6 +283,20 @@ export function *watchListenToContractLogs() {
yield takeEvery(actions.WATCH_NEW_CONTRACT_LOGS, listenToContractLogs); yield takeEvery(actions.WATCH_NEW_CONTRACT_LOGS, listenToContractLogs);
} }
export function *listenToContractEvents() {
const credentials = yield select(getCredentials);
const socket = api.webSocketContractEvents(credentials);
const channel = yield call(createChannel, socket);
while (true) {
const contractEvent = yield take(channel);
yield put(actions.contractEvents.success([contractEvent]));
}
}
export function *watchListenToContractEvents() {
yield takeEvery(actions.WATCH_NEW_CONTRACT_EVENTS, listenToContractEvents);
}
export function *listenGasOracle() { export function *listenGasOracle() {
const credentials = yield select(getCredentials); const credentials = yield select(getCredentials);
const socket = api.websocketGasOracle(credentials); const socket = api.websocketGasOracle(credentials);
@ -318,8 +337,10 @@ export default function *root() {
fork(watchFetchProcesses), fork(watchFetchProcesses),
fork(watchFetchProcessLogs), fork(watchFetchProcessLogs),
fork(watchFetchContractLogs), fork(watchFetchContractLogs),
fork(watchFetchContractEvents),
fork(watchListenToProcessLogs), fork(watchListenToProcessLogs),
fork(watchListenToContractLogs), fork(watchListenToContractLogs),
fork(watchListenToContractEvents),
fork(watchFetchBlock), fork(watchFetchBlock),
fork(watchFetchTransactions), fork(watchFetchTransactions),
fork(watchPostCommand), fork(watchPostCommand),

View File

@ -94,6 +94,10 @@ export function fetchContractLogs() {
return get(`/contracts/logs`, ...arguments); return get(`/contracts/logs`, ...arguments);
} }
export function fetchContractEvents() {
return get(`/blockchain/contracts/events`, ...arguments);
}
export function fetchContracts() { export function fetchContracts() {
return get('/contracts', ...arguments); return get('/contracts', ...arguments);
} }
@ -179,6 +183,10 @@ export function webSocketContractLogs(credentials) {
return new WebSocket(`ws://${credentials.host}/embark-api/contracts/logs`, [credentials.token]); return new WebSocket(`ws://${credentials.host}/embark-api/contracts/logs`, [credentials.token]);
} }
export function webSocketContractEvents(credentials) {
return new WebSocket(`ws://${credentials.host}/embark-api/blockchain/contracts/event`, [credentials.token]);
}
export function webSocketBlockHeader(credentials) { export function webSocketBlockHeader(credentials) {
return new WebSocket(`ws://${credentials.host}/embark-api/blockchain/blockHeader`, [credentials.token]); return new WebSocket(`ws://${credentials.host}/embark-api/blockchain/blockHeader`, [credentials.token]);
} }