Skip to content
This repository has been archived by the owner on Jul 9, 2021. It is now read-only.

[instant] Asset buyer errors #1142

Merged
merged 18 commits into from
Oct 18, 2018

Conversation

steveklebanoff
Copy link
Contributor

@steveklebanoff steveklebanoff commented Oct 17, 2018

Description

Shows error messages for AssetBuyer getBuyQuoteAsync errors

Also:

  • adds quoteState to keep track of the status of the current quote

  • shows "--" if we get an error when fetching in top right

  • renamed selectedAssetBuyState to buyOrderState

  • handling of additional AssetBuyer errors

  • tests for bestNameForAsset

  • polish

Testing instructions

Types of changes

  • New feature

Checklist:

  • Prefix PR title with [WIP] if necessary.
  • Prefix PR title with bracketed package name(s) corresponding to the changed package(s). For example: [sol-cov] Fixed bug.
  • Add tests to cover changes as needed.
  • Update documentation as needed.
  • Add new entries to the relevant CHANGELOG.jsons.

@@ -0,0 +1,27 @@
import { Dispatch } from 'redux';
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wasn't sure the right place for this file, open to suggestions


export interface LatestErrorComponentProps {
assetData?: string;
latestError?: any;
Copy link
Contributor Author

@steveklebanoff steveklebanoff Oct 17, 2018

Choose a reason for hiding this comment

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

Per convo w/ @fragosti , we explored two options:

  1. When error is "cleared" (after timeout), set currentError to be undefined. This would have been nice because it's the most straightforward representation of the current state, but animating the un-mounting of the component would either be pretty hairy or involve introducing a new dependency.

  2. Keep the error around in latestError but set latestErrorDismissed to true, and then render the sliding down error message. I don't love keeping the last error around but treating it as "dismissed", but it makes the animation logic simple.

After discussing, I went with option 2 for the drawbacks listed in option 1. Open to feedback though.

@@ -3,4 +3,5 @@ import { createStore, Store as ReduxStore } from 'redux';

import { reducer, State } from './reducer';

export const store: ReduxStore<State> = createStore(reducer);
const reduxDevTools = (window as any).__REDUX_DEVTOOLS_EXTENSION__;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

For using redux dev tools in chrome. I couldn't find a pattern for this in the website code, so went with this (window as any) hack

Copy link
Contributor

Choose a reason for hiding this comment

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

In the website its setup so that it's only being connected in development https://github.com/zalmoxisus/redux-devtools-extension, however I think this is fine?

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 like using the package and having it be dev only, fixed in 3f0d7c70bd156edc4039d08737e74e99c0b3a04e

@@ -3,4 +3,5 @@ import { createStore, Store as ReduxStore } from 'redux';

import { reducer, State } from './reducer';

export const store: ReduxStore<State> = createStore(reducer);
const reduxDevTools = (window as any).__REDUX_DEVTOOLS_EXTENSION__;
Copy link
Contributor

Choose a reason for hiding this comment

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

In the website its setup so that it's only being connected in development https://github.com/zalmoxisus/redux-devtools-extension, however I think this is fine?

selectedAssetData?: string;
selectedAssetAmount?: BigNumber;
selectedAssetBuyState: AsyncProcessState;
ethUsdPrice?: BigNumber;
latestBuyQuote?: BuyQuote;
}
interface StateWithError extends BaseState {
latestError: any;
latestErrorDismissed: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

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

needs to follow boolean naming convention (ie. "was", "is", "should" etc...)

packages/instant/src/util/asset_data.ts Outdated Show resolved Hide resolved

// TODO: tests for this
export const bestNameForAsset = (assetData: string | undefined, defaultString: string) => {
if (assetData === undefined) {
Copy link
Contributor

Choose a reason for hiding this comment

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

we use _.isUndefined pretty consistently for this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ah I noticed that, but I try to avoid importing a library function if it's simple, easy and readable to perfrom the same thing without it, a la https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore

Copy link
Contributor

Choose a reason for hiding this comment

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

Yea I kind of agree? Don't feel too strongly. However, as mentioned previously, I am a big fan of consistency and reducing decisions. It's nice to not have to think about "how am I going to check for undefined". And it's also nice for the entire codebase to do it the same way. Idk, I think since we already have lodash in the bundle it's worth doing that, but don't feel strongly about it.

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 since we already have lodash in the bundle it's worth doing that

If we are able to get our bundle optimized, the bundle should only include the lodash functions that we use, and it'd be nice to be pulling in one less function if we consistently used this approach for checking for undefined

That being said, I hear you RE: consistency and reducing decisions. For the sake of those two points, I'm down to use the lodash function for now. We can revisit our approach to lodash in general as a team later, and perhaps establish a new consistent pattern moving forward at that point.

@@ -12,6 +13,9 @@ export interface ZeroExInstantContainerProps {}

export const ZeroExInstantContainer: React.StatelessComponent<ZeroExInstantContainerProps> = props => (
<Container width="350px">
<Container zIndex={1} position="relative">
<LatestError />
</Container>
Copy link
Contributor

Choose a reason for hiding this comment

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

🔥

const slidingDirection = props.latestErrorDismissed ? 'down' : 'up';
const { icon, message } = errorDescription(props.latestError, props.assetData);
return <SlidingError direction={slidingDirection} icon={icon} message={message} />;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

A convention I'm trying to follow is to not have any Component code in these container files, they simply connect a pure presentational component to the redux store and do any state -> prop mapping. Seems like you don't need this component to me?

  • The sliding direction logjc can just be done in mapStateToProps.
  • icon is a pure function of state.
  • message is a pure function of state.

The only thing is not rendering when latestError is not defined, but do we need that? The error hides behind container.

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 like the idea of getting rid of this component, but I think we need the custom "not render when latestError is undefined" logic. Some thoughts:

  • If there is no latestError, we wouldn't have anything to send in to SlidingError's icon and message props. I like keeping these props as required instead of optional.
  • Even if we rendered SlidingError without icon and message props, in the current implementation, rendering it in the "Slide Down" phase would have it pop up at the top and slide down, which is not what we want. We could modify SlidingError to return a blank div if icon and message are empty, but that doesn't feel quite right to me.

I may be missing something though, let me know if there is an alternative approach in which we could get rid of this component.

If you agree we need to keep the component, I understand the convention and pattern of separating presentational and container components, but in this case it seems overboard IMHO: this component is extremely simple and it's highly unlikely it'd be used anywhere else.

Copy link
Contributor

Choose a reason for hiding this comment

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

You could change it so that latestError sticks around and you simply toggle it up and down in the flasher / redux. This kind of seems better to me because then it becomes impossible to make the error just disappear?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Per slack convo, decided to leave as-is and address later if needed

selectedAssetData?: string;
selectedAssetAmount?: BigNumber;
selectedAssetBuyState: AsyncProcessState;
ethUsdPrice?: BigNumber;
latestBuyQuote?: BuyQuote;
}
interface StateWithError extends BaseState {
latestError: any;
latestErrorDismissed: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

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

what if we call this latestErrorState and it can be either Present or Hidden or something. And lets not allow it to be undefined.

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 like this -- feels more clear than undefined or a boolean 💯. Will update!

icon: '😢',
message: bestMessage || 'Something went wrong...',
};
};
Copy link
Contributor

Choose a reason for hiding this comment

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

I would perhaps create one util/error.ts file that has one error or errorUtil object export and also provides the flashNewError utility?

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'll try this and see how it feels once implemented. ErrorFlasher is kinda big though, not sure if I love it being grouped together with other things

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in d45ffb33513057789a2e1cf03a9f01ee05846e17

import { assetMetaData } from '../data/asset_meta_data';

// TODO: tests for this
export const bestNameForAsset = (assetData: string | undefined, defaultString: string) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

I like the way this is structured but I'd like to follow our somewhat soft convention of exporting one thing from file.
So the api would be assetDataUtils.bestName()

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 wonder if this convention could be bad for optimizing builds. Not sure. Cool w/ keeping convention for now though

@coveralls
Copy link

coveralls commented Oct 17, 2018

Coverage Status

Coverage increased (+0.009%) to 84.65% when pulling 6ea386a on feature/instant/asset-buyer-errors into 5ec4b27 on development.

@coveralls
Copy link

Coverage Status

Coverage remained the same at 84.688% when pulling 241846e on feature/instant/asset-buyer-errors into 8ba6534 on feature/instant/move-features-over-from-zrx-buyer.

latestError: undefined;
latestErrorDismissed: undefined;
}
export type State = StateWithError | StateWithoutError;
Copy link
Contributor

@BMillman19 BMillman19 Oct 17, 2018

Choose a reason for hiding this comment

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

What if we had two types, BaseState that looks like above and ErrorState that looks like this:

interface ErrorState {
    latestError?: any;
    latestErrorDismissed?: boolean;
}

the final type is:

type State = BaseState & ErrorState

BaseState could even be broken down further

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 like this idea -- but I think with the change to Present | Hidden type which is always present might not be as important

Copy link
Contributor

@fragosti fragosti left a comment

Choose a reason for hiding this comment

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

Looking good overall. Feel free to address issues how you see fit :)

const slidingDirection = props.latestErrorDismissed ? 'down' : 'up';
const { icon, message } = errorDescription(props.latestError, props.assetData);
return <SlidingError direction={slidingDirection} icon={icon} message={message} />;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

You could change it so that latestError sticks around and you simply toggle it up and down in the flasher / redux. This kind of seems better to me because then it becomes impossible to make the error just disappear?

@steveklebanoff steveklebanoff force-pushed the feature/instant/asset-buyer-errors branch from 2d7eb9a to 7b43cd1 Compare October 17, 2018 21:45
@steveklebanoff steveklebanoff changed the base branch from feature/instant/move-features-over-from-zrx-buyer to development October 17, 2018 21:45
@steveklebanoff steveklebanoff changed the title [WIP][instant] Asset buyer errors [instant] Asset buyer errors Oct 17, 2018
@fabioberger fabioberger merged commit d0df074 into development Oct 18, 2018
@fabioberger fabioberger deleted the feature/instant/asset-buyer-errors branch October 18, 2018 10:12
@@ -12,20 +13,45 @@ export interface InstantHeadingProps {
selectedAssetAmount?: BigNumber;
totalEthBaseAmount?: BigNumber;
ethUsdPrice?: BigNumber;
quoteState: AsyncProcessState;
Copy link
Contributor

Choose a reason for hiding this comment

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

I still think this should be called quoteRequestState. quote is data, and it doesn't make sense to me for it to have state.

@@ -47,11 +73,11 @@ export const InstantHeading: React.StatelessComponent<InstantHeadingProps> = pro
<Flex direction="column" justify="space-between">
<Container marginBottom="5px">
<Text fontSize="16px" fontColor={ColorOption.white} fontWeight={500}>
{displaytotalEthBaseAmount(props)}
{loadingOrAmount(props.quoteState, displaytotalEthBaseAmount(props))}
Copy link
Contributor

Choose a reason for hiding this comment

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

Feels kind of weird to have two nested Text components. Can't displayTotalEthUsdAmount take care of all of this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call -- yes, will fix


if (!_.isUndefined(value) && !_.isUndefined(assetData)) {
// even if it's debounced, give them the illusion it's loading
dispatch(actions.updateBuyQuoteStatePending());
Copy link
Contributor

Choose a reason for hiding this comment

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

ah good idea

}

export const actions = {
updateEthUsdPrice: (price?: BigNumber) => createAction(ActionTypes.UPDATE_ETH_USD_PRICE, price),
updateSelectedAssetAmount: (amount?: BigNumber) => createAction(ActionTypes.UPDATE_SELECTED_ASSET_AMOUNT, amount),
updateSelectedAssetBuyState: (buyState: AsyncProcessState) =>
updatebuyOrderState: (buyState: AsyncProcessState) =>
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: this is not camelCase

@@ -25,12 +25,22 @@ export enum ActionTypes {
UPDATE_SELECTED_ASSET_AMOUNT = 'UPDATE_SELECTED_ASSET_AMOUNT',
UPDATE_SELECTED_ASSET_BUY_STATE = 'UPDATE_SELECTED_ASSET_BUY_STATE',
UPDATE_LATEST_BUY_QUOTE = 'UPDATE_LATEST_BUY_QUOTE',
UPDATE_BUY_QUOTE_STATE_PENDING = 'UPDATE_BUY_QUOTE_STATE_PENDING',
UPDATE_BUY_QUOTE_STATE_FAILURE = 'UPDATE_BUY_QUOTE_STATE_FAILURE',
Copy link
Contributor

Choose a reason for hiding this comment

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

SET_BUY_QUOTE_STATE_PENDING & SET_BUY_QUOTE_STATE_FAILURE ? Update means it takes an argument.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, wasn't hip to this convention. Will change.

@@ -7,21 +7,31 @@ import { AsyncProcessState } from '../types';

import { Action, ActionTypes } from './actions';

export enum LatestErrorDisplay {
Copy link
Contributor

Choose a reason for hiding this comment

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

This should go in types.ts and can probably be more general.


export const errorUtil = {
errorFlasher: new ErrorFlasher(),
errorDescription: (error?: any, assetData?: string): { icon: string; message: string } => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for doing this!

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

Successfully merging this pull request may close these issues.

5 participants