Fiddle deploy integration

Fiddle is properly deploying now, except the source code needs to be saved to the filesystem in order to be recalled later.

Fixes for handling errors on deploy and compilation.

Update contract state UI for determining state / interface / deployed.
This commit is contained in:
emizzle 2018-08-24 21:08:05 +10:00 committed by Pascal Precht
parent 609d4eb762
commit 59d3a3be83
No known key found for this signature in database
GPG Key ID: 0EE28D8D6FD85D7D
11 changed files with 205 additions and 106 deletions

View File

@ -170,14 +170,14 @@ export const ensRecords = {
export const FIDDLE = createRequestTypes('FIDDLE');
export const fiddle = {
request: (codeToCompile) => action(FIDDLE[REQUEST], {codeToCompile}),
post: (codeToCompile) => action(FIDDLE[REQUEST], {codeToCompile}),
success: (fiddle) => action(FIDDLE[SUCCESS], {fiddles: [fiddle]}),
failure: (error) => action(FIDDLE[FAILURE], {error})
};
export const FIDDLE_DEPLOY = createRequestTypes('FIDDLE_DEPLOY');
export const fiddleDeploy = {
request: (compiledCode) => action(FIDDLE_DEPLOY[REQUEST], {compiledCode}),
post: (compiledCode) => action(FIDDLE_DEPLOY[REQUEST], {compiledCode}),
success: (response) => {
return action(FIDDLE_DEPLOY[SUCCESS], {fiddleDeploys: [response.contractNames]});
},

View File

@ -6,40 +6,46 @@ import {
Card,
Table
} from "tabler-react";
import {formatContractForDisplay} from '../utils/presentation';
import {withRouter} from 'react-router-dom';
const Contract = ({contract}) => (
<Page.Content title="Contract">
<Grid.Row>
<Grid.Col>
<Card>
<Table
responsive
className="card-table table-vcenter text-nowrap"
>
<Table.Header>
<Table.Row>
<Table.ColHeader>Name</Table.ColHeader>
<Table.ColHeader>Address</Table.ColHeader>
<Table.ColHeader>State</Table.ColHeader>
</Table.Row>
</Table.Header>
<Table.Body>
<Table.Row>
<Table.Col>{(contract.name || contract.className)}</Table.Col>
<Table.Col>{(contract.address || contract.deployedAddress)}</Table.Col>
<Table.Col>{contract.deploy}</Table.Col>
</Table.Row>
</Table.Body>
</Table>
</Card>
</Grid.Col>
</Grid.Row>
</Page.Content>
);
Contract.propTypes = {
contract: PropTypes.object
const Contract = ({contract, match}) => {
const contractDisplay = formatContractForDisplay(contract);
return (
<Page.Content title={match.params.contractName + " Overview"}>
<Grid.Row>
<Grid.Col>
<Card>
<Table
responsive
className="card-table table-vcenter text-nowrap"
>
<Table.Header>
<Table.Row>
<Table.ColHeader>Name</Table.ColHeader>
<Table.ColHeader>Address</Table.ColHeader>
<Table.ColHeader>State</Table.ColHeader>
</Table.Row>
</Table.Header>
<Table.Body>
<Table.Row className={contractDisplay.stateColor}>
<Table.Col>{(contract.name || contract.className)}</Table.Col>
<Table.Col>{contractDisplay.address}</Table.Col>
<Table.Col>{contractDisplay.state}</Table.Col>
</Table.Row>
</Table.Body>
</Table>
</Card>
</Grid.Col>
</Grid.Row>
</Page.Content>
);
};
export default Contract;
Contract.propTypes = {
contract: PropTypes.object,
match: PropTypes.object
};
export default withRouter(Contract);

View File

@ -17,7 +17,7 @@ import ContractSourceContainer from '../containers/ContractSourceContainer';
const ContractLayout = ({match}) => (
<Grid.Row>
<Grid.Col md={3}>
<Page.Title className="my-5">Contract</Page.Title>
<Page.Title className="my-5">&nbsp;</Page.Title>
<div>
<List.Group transparent={true}>
<List.GroupItem
@ -26,7 +26,7 @@ const ContractLayout = ({match}) => (
icon="corner-left-up"
RootComponent={NavLink}
>
Back to {match.params.contractName}
Overview
</List.GroupItem>
<List.GroupItem
className="d-flex align-items-center"

View File

@ -7,6 +7,7 @@ import {
Table
} from "tabler-react";
import {Link} from 'react-router-dom';
import {formatContractForDisplay} from '../utils/presentation';
const Contracts = ({contracts}) => (
<Page.Content title="Contracts">
@ -28,11 +29,12 @@ const Contracts = ({contracts}) => (
<Table.Body>
{
contracts.map((contract) => {
const contractDisplay = formatContractForDisplay(contract);
return (
<Table.Row key={contract.className}>
<Table.Row key={contract.className} className={contractDisplay.stateColor}>
<Table.Col><Link to={`/embark/contracts/${contract.className}/overview`}>{contract.className}</Link></Table.Col>
<Table.Col>{contract.address || 'Interface or not set to deploy'}</Table.Col>
<Table.Col>{contract.deploy ? 'Deployed' : 'Not deployed'}</Table.Col>
<Table.Col>{contractDisplay.address}</Table.Col>
<Table.Col>{contractDisplay.state}</Table.Col>
</Table.Row>
);
})

View File

@ -57,29 +57,51 @@ class FiddleResults extends Component {
}
render() {
const {warnings, errors, fatal, isLoading, deployedContracts} = this.props;
const {warnings, errors, fatalFiddle, fatalFiddleDeploy, isLoading, deployedContracts} = this.props;
const hasFatal = fatalFiddle || fatalFiddleDeploy;
let renderings = [];
if(fatal){
renderings.push(
<React.Fragment key="fatal">
<a id="fatal" aria-hidden="true"/>
<Card
statusColor="danger"
statusSide="true"
className="fatal-card"
key="fatal-card">
<Card.Header>
<Card.Title color="danger"><Icon name="slash"/> Failed to compile</Card.Title>
</Card.Header>
<Card.Body>
<Dimmer active={isLoading ? "active" : ""} loader>
{fatal}
</Dimmer>
</Card.Body>
</Card>
</React.Fragment>
);
if(hasFatal){
if(fatalFiddle){
renderings.push(
<React.Fragment key="fatal-compile">
<a id="fatal-compile" aria-hidden="true"/>
<Card
statusColor="danger"
statusSide="true"
className="fatal-card">
<Card.Header>
<Card.Title color="danger"><Icon name="slash"/> Failed to compile</Card.Title>
</Card.Header>
<Card.Body>
<Dimmer active={isLoading ? "active" : ""} loader>
{fatalFiddle}
</Dimmer>
</Card.Body>
</Card>
</React.Fragment>
);
}
if(fatalFiddleDeploy){
renderings.push(
<React.Fragment key="fatal-deploy">
<a id="fatal-deploy" aria-hidden="true"/>
<Card
statusColor="danger"
statusSide="true"
className="fatal-card">
<Card.Header>
<Card.Title color="danger"><Icon name="slash"/> Failed to deploy</Card.Title>
</Card.Header>
<Card.Body>
<Dimmer active={isLoading ? "active" : ""} loader>
{fatalFiddleDeploy}
</Dimmer>
</Card.Body>
</Card>
</React.Fragment>
);
}
}
else if (deployedContracts){
renderings.push(
@ -128,9 +150,10 @@ class FiddleResults extends Component {
FiddleResults.propTypes = {
errors: PropTypes.array,
warnings: PropTypes.array,
fatal: PropTypes.string,
fatalFiddle: PropTypes.string,
fatalFiddleDeploy: PropTypes.string,
isLoading: PropTypes.bool,
deployedContracts: PropTypes.array
deployedContracts: PropTypes.object
};
export default FiddleResults;

View File

@ -6,40 +6,48 @@ import FiddleDeployButton from './FiddleDeployButton';
class FiddleResultsSummary extends Component{
render(){
const {warnings, errors, isLoading, loadingMessage, hasResult, fatal} = this.props;
const {warnings, errors, isLoading, loadingMessage, hasResult, fatalFiddle, fatalFiddleDeploy} = this.props;
let renderings = [];
if(fatal) {
renderings.push(
<React.Fragment key="errors">
<a className="badge-link" href="#fatal"><Badge color="danger"><Icon name="slash"/></Badge></a>
</React.Fragment>
);
}
else if(isLoading){
if(isLoading){
renderings.push(
<React.Fragment key="loading"><div className="loader"></div><span className="loader-text">{loadingMessage}</span></React.Fragment>
);
}
else {
if(hasResult && !errors.length){
renderings.push(
<React.Fragment key="success">
<Badge className="badge-link" color="success">Compiled</Badge>
<FiddleDeployButton onDeployClick={(e) => this.props.onDeployClick(e)} />
</React.Fragment>
);
}
if(errors.length) renderings.push(
if(fatalFiddle) {
renderings.push(
<React.Fragment key="errors">
<a className="badge-link" href="#errors"><Badge color="danger">{errors.length} error{errors.length > 1 ? "s" : ""}</Badge></a>
</React.Fragment>
);
if(warnings.length) renderings.push(
<React.Fragment key="warnings">
<a className="badge-link" href="#warnings"><Badge color="warning">{warnings.length} warning{warnings.length > 1 ? "s" : ""}</Badge></a>
<a className="badge-link" href="#fatal-compile"><Badge color="danger"><Icon name="slash"/> Compilation</Badge></a>
</React.Fragment>
);
}
if(fatalFiddleDeploy) {
renderings.push(
<React.Fragment key="errors">
<a className="badge-link" href="#fatal-deploy"><Badge color="danger"><Icon name="slash"/> Deployment</Badge></a>
</React.Fragment>
);
}
if(errors.length) renderings.push(
<React.Fragment key="errors">
<a className="badge-link" href="#errors"><Badge color="danger">{errors.length} error{errors.length > 1 ? "s" : ""}</Badge></a>
</React.Fragment>
);
if(warnings.length) renderings.push(
<React.Fragment key="warnings">
<a className="badge-link" href="#warnings"><Badge color="warning">{warnings.length} warning{warnings.length > 1 ? "s" : ""}</Badge></a>
</React.Fragment>
);
if(hasResult && !errors.length){
renderings.push(
<React.Fragment key="success">
<Badge className="badge-link" color="success">Compiled</Badge>
<FiddleDeployButton onDeployClick={(e) => this.props.onDeployClick(e)} />
</React.Fragment>
);
}
return (
<div className={"compilation-summary " + ((hasResult || isLoading) ? "visible" : "")}>
{renderings}
@ -55,7 +63,8 @@ FiddleResultsSummary.propTypes = {
isLoading: PropTypes.bool,
loadingMessage: PropTypes.string,
hasResult: PropTypes.bool,
fatal: PropTypes.string,
fatalFiddle: PropTypes.string,
fatalFiddleDeploy: PropTypes.string,
onDeployClick: PropTypes.func
};

View File

@ -82,7 +82,7 @@ class FiddleContainer extends Component {
}
render() {
const {fiddle, loading, error, deployedContracts} = this.props;
const {fiddle, loading, fiddleError, fiddleDeployError, deployedContracts} = this.props;
const {loadingMessage} = this.state;
let renderings = [];
let warnings = [];
@ -99,7 +99,8 @@ class FiddleContainer extends Component {
isLoading={loading}
loadingMessage={loadingMessage}
hasResult={Boolean(fiddle)}
fatal={error}
fatalFiddle={fiddleError}
fatalFiddleDeploy={fiddleDeployError}
onDeployClick={(e) => this._onDeployClick(e)}
/>
<Fiddle
@ -116,13 +117,14 @@ class FiddleContainer extends Component {
/>
</React.Fragment>
);
if (fiddle || (this.state.value && error)) {
if (fiddle || (this.state.value && (fiddleError || fiddleDeployError))) {
renderings.push(
<FiddleResults
key="results"
errors={errors}
warnings={warnings}
fatal={error}
fatalFiddle={fiddleError}
fatalFiddleDeploy={fiddleDeployError}
isLoading={loading}
deployedContracts={deployedContracts}
/>);
@ -143,23 +145,26 @@ function mapStateToProps(state) {
return {
fiddle: fiddle.data,
deployedContracts: deployedFiddle.data,
error: fiddle.error || deployedFiddle.error,
fiddleError: fiddle.error,
fiddleDeployError: deployedFiddle.error,
loading: state.loading
};
}
FiddleContainer.propTypes = {
fiddle: PropTypes.object,
error: PropTypes.string,
fiddleError: PropTypes.string,
fiddleDeployError: PropTypes.string,
loading: PropTypes.bool,
postFiddle: PropTypes.func,
postFiddleDeploy: PropTypes.func
postFiddleDeploy: PropTypes.func,
deployedContracts: PropTypes.object
};
export default connect(
mapStateToProps,
{
postFiddle: fiddleAction.request,
postFiddleDeploy: fiddleDeployAction.request
postFiddle: fiddleAction.post,
postFiddleDeploy: fiddleDeployAction.post
},
)(FiddleContainer);

View File

@ -107,14 +107,14 @@ export function getMessages(state) {
export function getFiddle(state) {
return {
data: _.last(state.entities.fiddles),
error: _.last(state.errorEntities.fiddles)
error: state.errorEntities.fiddles
};
}
export function getFiddleDeploy(state) {
return {
data: _.last(state.entities.fiddleDeploys),
error: _.last(state.errorEntities.fiddleDeploys)
error: state.errorEntities.fiddleDeploys
};
}

View File

@ -0,0 +1,19 @@
export function formatContractForDisplay(contract) {
let address = (contract.address || contract.deployedAddress);
let state = 'Deployed';
let stateColor = 'success';
if (contract.deploy === false) {
address = 'Interface or set to not deploy';
state = 'N/A';
stateColor = 'info';
} else if (contract.error) {
address = contract.error;
state = 'Error';
stateColor = 'danger';
} else if (!address) {
address = '...';
state = 'Pending';
stateColor = 'warning';
}
return {address, state, stateColor};
}

View File

@ -1,6 +1,7 @@
let toposort = require('toposort');
let async = require('async');
const cloneDeep = require('clone-deep');
const _ = require('lodash');
let utils = require('../../utils/utils.js');
@ -169,22 +170,44 @@ class ContractsManager {
'post',
'/embark-api/contract/deploy',
(req, res) => {
this.logger.trace(`POST request /embark-api/contract/deploy:\n ${JSON.stringify(req.body)}`);
self.compiledContracts = Object.assign(self.compiledContracts, req.body.compiledContract);
const contractNames = Object.keys(req.body.compiledContract);
self.build((err, _mgr) => {
const responseData = {errors: err, contractNames: Object.keys(req.body.compiledContract)};
if(err){
return res.send({error: err.message});
}
// pick the compiled contracts that have been built
const builtContracts = _.pick(self.contracts, contractNames);
// for each contract, deploy (in parallel)
async.eachOf(builtContracts, (contract, contractName, callback) => {
contract.args = []; /* TODO: override contract.args */
contract.className = contractName;
self.events.request("deploy:contract", contract, (err) => {
callback(err);
});
}, (err) => {
let responseData = {};
if(err){
responseData.error = err.message;
}
else responseData.result = contractNames;
this.logger.trace(`POST response /embark-api/contract/deploy:\n ${JSON.stringify(responseData)}`);
res.send(responseData);
}, false);
});
}, false, false);
}
);
}
build(done, useContractFiles = true) {
build(done, useContractFiles = true, resetContracts = true) {
let self = this;
self.contracts = {};
let compilerOptions = {disableOptimizations: this.disableOptimizations};
if(resetContracts) self.contracts = {};
async.waterfall([
function loadContractFiles(callback) {
self.events.request("config:contractsFiles", (contractsFiles) => {
@ -210,6 +233,12 @@ class ContractsManager {
},
function prepareContractsFromConfig(callback) {
self.events.emit("status", __("Building..."));
// if we are appending contracts (ie fiddle), we
// don't need to build a contract from config, so
// we can skip this entirely
if(!resetContracts) return callback();
let className, contract;
for (className in self.contractsConfig.contracts) {
contract = self.contractsConfig.contracts[className];

View File

@ -1,5 +1,6 @@
let async = require('../../utils/async_extend.js');
let SolcW = require('./solcW.js');
const fs = require('../../core/fs');
class Solidity {
@ -19,8 +20,12 @@ class Solidity {
'post',
'/embark-api/contract/compile',
(req, res) => {
const input = {'fiddler': {content: req.body.code.replace(/\r\n/g, '\n')}};
const input = {'fiddle': {content: req.body.code.replace(/\r\n/g, '\n')}};
this.compile_solidity_code(input, {}, true, (errors, compilationResult) => {
// write code to filesystem so we can view the source after page refresh
const filePath = `.embark/fiddles/${Object.keys(compilationResult).join('_')}.sol`;
fs.writeFile(filePath, req.body.code, 'utf8'); // async, do not need to wait
const responseData = {errors: errors, compilationResult: compilationResult};
this.logger.trace(`POST response /embark-api/contract/compile:\n ${JSON.stringify(responseData)}`);
res.send(responseData);
@ -122,7 +127,8 @@ class Solidity {
let contract = json[contractFile][contractName];
const className = contractName;
const filename = contractFile;
let filename = contractFile;
if(filename === 'fiddle') filename = `.embark/fiddles/${className}.sol`;
compiled_object[className] = {};
compiled_object[className].code = contract.evm.bytecode.object;