Minimise transactions progress (#4942)

* Watch the requests and display them throughout the app

* Linting

* Showing Requests

* Fully working Transaction Requests Display

* Add FormattedMessage to Requests

* Clean-up the Transfer dialog

* Update Validations

* Cleanup Create Wallet

* Clean Deploy Contract Dialog

* Cleanup Contract Execution

* Fix Requests

* Cleanup Wallet Settings

* Don't show stepper in Portal if less than 2 steps

* WIP local storage requests

* Caching requests and saving contract deployments

* Add Historic prop to Requests MethodDecoding

* Fix tests

* Add Contract address to MethodDecoding

* PR Grumbles - Part I

* PR Grumbles - Part II

* Use API Subscription methods

* Linting

* Move SavedRequests and add tests

* Added tests for Requests Actions

* Fixing tests

* PR Grumbles + Playground fix

* Revert Playground changes

* PR Grumbles

* Better showEth in MethodDecoding
This commit is contained in:
Nicolas Gotchac
2017-03-28 14:34:31 +02:00
committed by Jaco Greeff
parent e28c477075
commit a99721004b
40 changed files with 1382 additions and 1216 deletions

View File

@@ -25,6 +25,7 @@ export blockchainReducer from './blockchainReducer';
export workerReducer from './workerReducer';
export imagesReducer from './imagesReducer';
export personalReducer from './personalReducer';
export requestsReducer from './requestsReducer';
export signerReducer from './signerReducer';
export snackbarReducer from './snackbarReducer';
export statusReducer from './statusReducer';

View File

@@ -0,0 +1,171 @@
// Copyright 2015-2017 Parity Technologies (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 { outTransaction } from '~/api/format/output';
import { trackRequest as trackRequestUtil, parseTransactionReceipt } from '~/util/tx';
import SavedRequests from '~/views/Application/Requests/savedRequests';
const savedRequests = new SavedRequests();
export const init = (api) => (dispatch) => {
api.subscribe('parity_postTransaction', (error, request) => {
if (error) {
return console.error(error);
}
dispatch(watchRequest(request));
});
api.on('connected', () => {
savedRequests.load(api).then((requests) => {
requests.forEach((request) => dispatch(watchRequest(request)));
});
});
};
export const watchRequest = (request) => (dispatch, getState) => {
const { requestId } = request;
// Convert value to BigNumber
request.transaction = outTransaction(request.transaction);
dispatch(setRequest(requestId, request));
dispatch(trackRequest(requestId, request));
};
export const trackRequest = (requestId, { transactionHash = null } = {}) => (dispatch, getState) => {
const { api } = getState();
trackRequestUtil(api, { requestId, transactionHash }, (error, data) => {
if (error) {
console.error(error);
return dispatch(setRequest(requestId, { error }));
}
// Hide the request after 6 mined blocks
if (data.transactionReceipt) {
const { transactionReceipt } = data;
const { requests } = getState();
const requestData = requests[requestId];
let blockSubscriptionId = -1;
// If the request was a contract deployment,
// then add the contract with the saved metadata to the account
if (requestData.metadata && requestData.metadata.deployment) {
const { metadata } = requestData;
const options = {
...requestData.transaction,
metadata
};
parseTransactionReceipt(api, options, data.transactionReceipt)
.then((contractAddress) => {
// No contract address given, might need some confirmations
// from the wallet owners...
if (!contractAddress || /^(0x)?0*$/.test(contractAddress)) {
return false;
}
metadata.blockNumber = data.transactionReceipt
? data.transactionReceipt.blockNumber.toNumber()
: null;
const prevRequest = getState().requests[requestId];
const nextTransaction = {
...prevRequest.transaction,
creates: contractAddress
};
dispatch(setRequest(requestId, { transaction: nextTransaction }));
return Promise.all([
api.parity.setAccountName(contractAddress, metadata.name),
api.parity.setAccountMeta(contractAddress, metadata)
]);
})
.catch((error) => {
console.error(error);
});
}
api
.subscribe('eth_blockNumber', (error, blockNumber) => {
if (error || !blockNumber) {
return;
}
// Transaction included in `blockHeight` blocks
const blockHeight = blockNumber.minus(transactionReceipt.blockNumber).plus(1);
const nextData = { blockHeight };
// Hide the transaction after 6 blocks
if (blockHeight.gt(6)) {
return dispatch(hideRequest(requestId));
}
return dispatch(setRequest(requestId, nextData, false));
})
.then((subId) => {
blockSubscriptionId = subId;
return dispatch(setRequest(requestId, { blockSubscriptionId }, false));
});
}
return dispatch(setRequest(requestId, data));
});
};
export const hideRequest = (requestId) => (dispatch, getState) => {
const { api, requests } = getState();
const request = requests[requestId];
dispatch(setRequest(requestId, { show: false }));
// Delete it if an error occured or if completed
if (request.error || request.transactionReceipt) {
// Wait for the animation to be done to delete the request
setTimeout(() => {
dispatch(deleteRequest(requestId));
}, 1000);
}
// Unsubscribe to eth-blockNumber if subscribed
if (request.blockSubscriptionId) {
api.unsubscribe(request.blockSubscriptionId);
dispatch(setRequest(requestId, { blockSubscriptionId: null }, false));
}
};
export const setRequest = (requestId, requestData, autoSetShow = true) => {
if (autoSetShow && requestData.show === undefined) {
requestData.show = true;
}
savedRequests.save(requestId, requestData);
return {
type: 'setRequest',
requestId, requestData
};
};
export const deleteRequest = (requestId) => {
savedRequests.remove(requestId);
return {
type: 'deleteRequest',
requestId
};
};

View File

@@ -0,0 +1,111 @@
// Copyright 2015-2017 Parity Technologies (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 sinon from 'sinon';
import { hideRequest, trackRequest, watchRequest } from './requestsActions';
const TX_HASH = '0x123456';
const BASE_REQUEST = {
requestId: '0x1',
transaction: {
from: '0x0',
to: '0x1'
}
};
let api;
let store;
let dispatcher;
function createApi () {
return {
pollMethod: (method, data) => {
switch (method) {
case 'parity_checkRequest':
return Promise.resolve(TX_HASH);
default:
return Promise.resolve();
}
}
};
}
function createRedux (dispatcher) {
return {
dispatch: (arg) => {
if (typeof arg === 'function') {
return arg(store.dispatch, store.getState);
}
return dispatcher(arg);
},
getState: () => {
return {
api,
requests: {
[BASE_REQUEST.requestId]: BASE_REQUEST
}
};
}
};
}
describe('redux/requests', () => {
beforeEach(() => {
api = createApi();
dispatcher = sinon.spy();
store = createRedux(dispatcher);
});
it('watches new requests', () => {
store.dispatch(watchRequest(BASE_REQUEST));
expect(dispatcher).to.be.calledWith({
type: 'setRequest',
requestId: BASE_REQUEST.requestId,
requestData: BASE_REQUEST
});
});
it('tracks requests', (done) => {
store.dispatch(trackRequest(BASE_REQUEST.requestId));
setTimeout(() => {
expect(dispatcher).to.be.calledWith({
type: 'setRequest',
requestId: BASE_REQUEST.requestId,
requestData: {
transactionHash: TX_HASH,
show: true
}
});
done();
}, 50);
});
it('hides requests', () => {
store.dispatch(hideRequest(BASE_REQUEST.requestId));
expect(dispatcher).to.be.calledWith({
type: 'setRequest',
requestId: BASE_REQUEST.requestId,
requestData: { show: false }
});
});
});

View File

@@ -0,0 +1,43 @@
// Copyright 2015-2017 Parity Technologies (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 { handleActions } from 'redux-actions';
const initialState = {};
export default handleActions({
setRequest (state, action) {
const { requestId, requestData } = action;
const nextState = {
...state,
[requestId]: {
...(state[requestId] || {}),
...requestData
}
};
return nextState;
},
deleteRequest (state, action) {
const { requestId } = action;
const nextState = { ...state };
delete nextState[requestId];
return nextState;
}
}, initialState);

View File

@@ -121,7 +121,7 @@ export default class Status {
_subscribeBlockNumber = () => {
return this._api
.subscribe('eth_blockNumber', (error, blockNumber) => {
if (error) {
if (error || !blockNumber) {
return;
}

View File

@@ -19,7 +19,7 @@ import { routerReducer } from 'react-router-redux';
import {
apiReducer, balancesReducer, blockchainReducer,
workerReducer, imagesReducer, personalReducer,
workerReducer, imagesReducer, personalReducer, requestsReducer,
signerReducer, statusReducer as nodeStatusReducer,
snackbarReducer, walletReducer
} from './providers';
@@ -45,6 +45,7 @@ export default function () {
nodeStatus: nodeStatusReducer,
personal: personalReducer,
registry: registryReducer,
requests: requestsReducer,
signer: signerReducer,
snackbar: snackbarReducer,
wallet: walletReducer,

View File

@@ -20,6 +20,7 @@ import initMiddleware from './middleware';
import initReducers from './reducers';
import { load as loadWallet } from './providers/walletActions';
import { init as initRequests } from './providers/requestsActions';
import { setupWorker } from './providers/workerWrapper';
import {
@@ -44,6 +45,7 @@ export default function (api, browserHistory, forEmbed = false) {
new SignerProvider(store, api).start();
store.dispatch(loadWallet(api));
store.dispatch(initRequests(api));
setupWorker(store);
return store;