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:
committed by
Jaco Greeff
parent
e28c477075
commit
a99721004b
@@ -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';
|
||||
|
||||
171
js/src/redux/providers/requestsActions.js
Normal file
171
js/src/redux/providers/requestsActions.js
Normal 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
|
||||
};
|
||||
};
|
||||
111
js/src/redux/providers/requestsActions.spec.js
Normal file
111
js/src/redux/providers/requestsActions.spec.js
Normal 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 }
|
||||
});
|
||||
});
|
||||
});
|
||||
43
js/src/redux/providers/requestsReducer.js
Normal file
43
js/src/redux/providers/requestsReducer.js
Normal 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);
|
||||
@@ -121,7 +121,7 @@ export default class Status {
|
||||
_subscribeBlockNumber = () => {
|
||||
return this._api
|
||||
.subscribe('eth_blockNumber', (error, blockNumber) => {
|
||||
if (error) {
|
||||
if (error || !blockNumber) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user