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})
};
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 = {
request: () => action(CONTRACTS[REQUEST]),
@ -299,6 +306,7 @@ export const gasOracle = {
export const WATCH_NEW_PROCESS_LOGS = 'WATCH_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_EVENTS = 'WATCH_NEW_CONTRACT_EVENTS';
export const INIT_BLOCK_HEADER = 'INIT_BLOCK_HEADER';
export const STOP_BLOCK_HEADER = 'STOP_BLOCK_HEADER';
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(){
return {
type: INIT_BLOCK_HEADER

View File

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

View File

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

View File

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

View File

@ -64,6 +64,10 @@ export function getContractLogsByContract(state, 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) {
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 postCommandSuggestions = doRequest.bind(null, actions.commandSuggestions, api.postCommandSuggestions);
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 fetchContracts = doRequest.bind(null, actions.contracts, api.fetchContracts);
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);
}
export function *watchFetchContractEvents() {
yield takeEvery(actions.CONTRACT_EVENTS[actions.REQUEST], fetchContractEvents);
}
export function *watchFetchContract() {
yield takeEvery(actions.CONTRACT[actions.REQUEST], fetchContract);
}
@ -278,6 +283,20 @@ export function *watchListenToContractLogs() {
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() {
const credentials = yield select(getCredentials);
const socket = api.websocketGasOracle(credentials);
@ -318,8 +337,10 @@ export default function *root() {
fork(watchFetchProcesses),
fork(watchFetchProcessLogs),
fork(watchFetchContractLogs),
fork(watchFetchContractEvents),
fork(watchListenToProcessLogs),
fork(watchListenToContractLogs),
fork(watchListenToContractEvents),
fork(watchFetchBlock),
fork(watchFetchTransactions),
fork(watchPostCommand),

View File

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