Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor and redesign confirm transaction views #4691

Merged
merged 19 commits into from
Jul 12, 2018
Merged

Conversation

alextsg
Copy link
Contributor

@alextsg alextsg commented Jun 29, 2018

No description provided.

@alextsg alextsg force-pushed the i4404-confirm-refactor branch from ce7adb1 to 42ada41 Compare June 29, 2018 04:50
@danjm
Copy link
Contributor

danjm commented Jun 30, 2018

@alextsg What are your thoughts on adding directory specific .selectors files for most of the components/pages/confirm-** directories, to be used to replace any selection logic within the mapStateToProps functions?

My preference is for no selection logic at all in mapStateToProps, and instead contain all of that in imported selector functions. Also, if there are selectors that will only be used in one container component, my preference is that they be in a file in the same directory as that component.

Thoughts?

t: PropTypes.func,
}

static propTypes = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps alphabetize this list and put spaces between the sections (by type of source) of props.

} = {},
} = this.props

const INSUFFICIENT_FUNDS_ERROR = t('insufficientFunds')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might be better to store errors as their translation keys, as opposed to their translations. And then translate them at the point of render.

Reasons:
(1) Data stored will not be a 'view' value, just data that represents the current state of errors
(2) Debugging will be easier if the users state logs are all in english, as opposed to some parts of state that representing error handling being in a different laguage

Also, perhaps these errors should be moved to a constants file

return
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are your thoughts on moving some or all of the logic in getError and validateEditGas to either reducers or utils?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think keeping errors in the redux state makes sense when there's an app-wide display for errors (like how GitHub has it under the nav bar) but when the error is localized to a component then I don't think it's necessary, since it's basically up to the component how the error should be handled (displayed somewhere, disable buttons, etc.).

As for utils, I get that by doing so it keeps the component file smaller, but in my opinion the purpose of a util is re-usability. I want to avoid passing in a bunch of arguments into a util function as that just makes the util very specific and hard to use. In this case, I think isBalanceSufficient is great as a util, but getError would be too specific to the component. validateEditGas could probably be moved to the container file, but we should figure out how to deal with the translation of error messages.

if (onEditGas) {
onEditGas()
} else {
showCustomizeGasModal({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe put all of showCustomizeGasModal in the container?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... maybe not

@@ -399,19 +399,17 @@ class TransactionStateManager extends EventEmitter {
_setTxStatus (txId, status) {
const txMeta = this.getTx(txId)
txMeta.status = status
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this change needed?

@@ -705,11 +705,10 @@ function signTypedMsg (msgData) {

function signTx (txData) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you remove showLoadingIndication and hideLoadingIndication here, but not in the other methods edited below?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a weird case where the callback passed to ethQuery.sendTransaction gets called when the tx gets approved or rejected, and not when we'd expect (called after the tx is added to the list of unapproved tx'es). The loading spinner was being hidden unintentionally during approving/rejecting on the confirm step.

})
.then(() => updateMetamaskStateFromBackground())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related to this PR, but we should probably rename updateMetamaskStateFromBackground to something like getUpdatedMetamaskStateFromBackground. The current name makes it sound like the method does the updating of react state itself.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving a TODO here for myself to do this after I finish reviwing:

  • create github issue to rename updateMetamaskStateFromBackground

@@ -0,0 +1,65 @@
import { createSelector } from 'reselect'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you prefer to have these here in a general selectors/ directory, as opposed to in one of the confirm-transaction directories? (As is done, for example, with send)


renderTabs () {
// const { children } = this.props
const numberOfTabs = React.Children.count(this.props.children)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity why is React.Children (and not this.props.children) needed here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • remove commented out code

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

React.Children is a set of utils that can be used on this.props.children https://reactjs.org/docs/react-api.html#reactchildren

const { detailsComponent, dataComponent } = this.props

return (
<Tabs>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be nice if the tabs component had an API that allowed:

<Tabs
  tabs={
    'Details': detailsComponent,
    'Data': dataComponent,
  }
/>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was actually trying to avoid this so that the elements would be more HTML semantic:

<Tabs>
  <Tab />
  <Tab />
</Tabs>

{ this.renderContent() }
{
errorMessage && (
<div className="confirm-page-container-content__error-container">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we put the <div className="confirm-page-container-content__error-container"> inside the ConfirmPageContainerError component?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not here, because the confirm-page-container-content__error-container class adds padding around the ConfirmPageContainerError component. This makes the ConfirmPageContainerError more reusable and contained since the parent would be responsible for the spacing of the child. I might not want the same spacing/padding used elsewhere.

</div>
</div>
{
hideSubtitle || <div className="confirm-page-container-summary__subtitle">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be slight improvement on code readability if this was showSubtitle && . I say this because the other conditionally rendered elements are rendered if a value is truthy with &&. Reading the file and then encountering || is a little confusing. I say this lightly though, if switching to showSubtitle && makes things less readable or intuitive elsewhere, then my suggestion here should be ignored.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did it this way because the default of the summary component is to show the subtitle, and hiding it is the exception. If I used showSubtitle as the boolean prop then I think it's a less convenient API since I'd have to add that in more places than not.

@@ -267,7 +269,31 @@ const MODALS = {

CUSTOMIZE_GAS: {
contents: [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we also need to switch over the modal used on the send screen at some point (but not as part of this PR).

  • Create github issue for using new customize gas modal component on send screen.

import ConfirmTransactionSwitch from './confirm-transaction-switch.component'

const mapStateToProps = state => {
const { confirmTransaction } = state
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should map the specific properties within confirmTransaction to props here. It will simplify the logic within the component.

@@ -0,0 +1,4 @@
export function isConfirmDeployContract (txData = {}) {
const { txParams = {} } = txData
return !txParams.to
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is consistent with current code, but I wonder if there is a more specific check. It seems likely that we will be handling other transactions in the future that don't have a recipient address. Perhaps we can parse something from data?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tools I have for parsing data (abi-decoder and eth-method-registry) don't return anything from the deploy contract data, so I don't think there's a quick fix at our disposal. I asked kumavis about this as well and deploying a contract should be the only case when txParams.to is not defined.


if (paramsTransactionId) {
// Check to make sure params ID is valid
const tx = R.find(({ id }) => id + '' === paramsTransactionId)(unconfirmedTransactions)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with using ramda, but the code might become a little more accessible if we use the native find method.

}
}

setTransactionToConfirm () {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this method is another case where the logic could be moved to the .container component and then to selectors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept this method in the component file since it works with react-router props. I kinda see methods in the container file as having to do with modifying the data the component uses and not anything to do with the view, so I've been keeping react-router/view changes in the component and not the container.

@@ -45,9 +45,9 @@ function calcGasTotal (gasLimit, gasPrice) {

function isBalanceSufficient ({
amount = '0x0',
amountConversionRate = 0,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder why amountConversionRate was defaulted to 0 before? We should confirm that changing the default to 1 does not break any desired behaviour.

@@ -28,6 +28,8 @@ const BN = ethUtil.BN
const R = require('ramda')
const { stripHexPrefix } = require('ethereumjs-util')

global.BigNumber = BigNumber
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why add BigNumber to global?

@@ -0,0 +1,116 @@
import currencyFormatter from 'currency-formatter'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I asked a similar question elsewhere. Your thoughts on including this in a general ui/app/helpers/ directory as opposed to within a root directory for confirm-transaction components?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just found myself reusing these in various components and the .duck, and it got annoying having to move these functions/files around based on which component was using a function during debugging/refactoring (up one level when used by a parent, down one level when not used).

@alextsg
Copy link
Contributor Author

alextsg commented Jul 2, 2018

@danjm I'm indifferent to using selectors as it has pros and cons, unless it's for more complicated retrieval or using a combination of selectors with reselect that can benefit from memoization. While it can make updating state easier since you just need to update the selector, realistically that's kinda rare and you'd likely have to update all the selector function names anyways. Your mapStateToProps functions in the send containers look neat, but it makes it so that your container files provide very little context as to what properties are being pulled since all you see are function names (especially if props nested differently have the same names). I also find it annoying to have to navigate between a bunch of files when debugging.

As for the folder structure, I think I placed selectors I did use in a top level selectors folder, since I used them in both a duck file and container files if I remember correctly.

@alextsg alextsg force-pushed the i4404-confirm-refactor branch 2 times, most recently from 7380c90 to c123419 Compare July 6, 2018 19:06
@alextsg alextsg force-pushed the i4404-confirm-refactor branch from c123419 to a2d9c43 Compare July 6, 2018 23:27
danjm
danjm previously approved these changes Jul 11, 2018
@metamaskbot
Copy link
Collaborator

Builds ready [6701771]: mascara, chrome, firefox, edge, opera

@alextsg alextsg merged commit 0d4dbbe into develop Jul 12, 2018
@alextsg alextsg deleted the i4404-confirm-refactor branch July 12, 2018 01:31
@alextsg
Copy link
Contributor Author

alextsg commented Jul 12, 2018

This closes #4298 closes #4635 closes #3414 closes #4404 closes #4481 closes #4429 closes #3824 closes #4430

@1blockologist
Copy link

1blockologist commented Aug 2, 2018

What is the process of getting this up into master/production? Didn't see a place to chat with the team (maybe add chat server badge to readme?)

@bdresser
Copy link
Contributor

bdresser commented Aug 2, 2018

@1blockologist this will bei n 4.9.0, should be out early next week! thanks for your patience.

@abitrolly
Copy link
Contributor

Where are screenshots of before and after? Common, it is just Ctrl+Shift+PrtSc and Ctrl+V on GNOME. ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants