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

[PWA-336] Cart/Checkout Input Errors UI cases #2495

Merged
merged 10 commits into from
Jun 25, 2020

Conversation

tjwiebell
Copy link
Contributor

@tjwiebell tjwiebell commented Jun 17, 2020

Description

After Cart/Checkout is done we should make a general pass over the error states to make sure we represent them correctly according to UX desires and the style guide.

Cases to consider:

  • User errors
  • System errors

h5. Open Questions:

  • How should we handle form fields that are out of view of the shopper when the error occurs?
  • Are we going to have both form and field level reporting?
  • Do we have the most common error scenarios / use-cases documented?

Summary of GraphQL Operations that may have error state: https://wiki.corp.magento.com/pages/viewpage.action?pageId=144314396

Related Issue

  • [PWA-336] Cart/Checkout Input Errors UI cases

Acceptance

Verification Stakeholders

Specification

Verification Steps

This guy is going to be a little difficult to test, it's quite tricky to simulate errors. You might be able to go offline and use network errors (we've discovered this actually doesn't work in most places), but modifying code to break the queries or mutations is what I did. I'll outline the components touched, and the verification steps are all the same; simulate an error somehow and verify output. I have also included screenshots in the HLD for UX, to make visual review easier.

  • CartPage > Product Edit > Loading Options (now renders error)
  • CartPage > Remove Item (throws Toast)
  • CartPage > Product Edit > Update (renders quantity and option errors now and doesn't close form)
  • CartPage > Remove Coupon (now renders error)
  • CartPage > Estimate Shipping > Set Address (now renders error)
  • CheckoutPage > Select Shipping Method (now renders error and doesn't close if in modal)
  • CheckoutPage > Address Book > Apply Address (throws Toast, doesn't close)
  • CheckoutPage > Auth/Guest Address > Add/Update Address (renders error, auto scrolls to form top, doesn't close anymore)

Screenshots / Screen Captures (if appropriate)

See HLD for tons of screenshots.

Checklist

  • I have added tests to cover my changes, if necessary.
  • I have updated the documentation accordingly, if necessary.

@m2-community-project m2-community-project bot added this to Ready for Review in Pull Request Progress Jun 17, 2020
@PWAStudioBot
Copy link
Contributor

PWAStudioBot commented Jun 17, 2020

Messages
📖

Access a deployed version of this PR here. Make sure to wait for the "pwa-pull-request-deploy" job to complete.

📖 DangerCI Failures related to missing labels/description/linked issues/etc will persist until the next push or next nightly build run (assuming they are fixed).
📖

Associated JIRA tickets: PWA-336.

Generated by 🚫 dangerJS against 49cc995

@devops-pwa-codebuild
Copy link
Collaborator

devops-pwa-codebuild commented Jun 17, 2020

Performance Test Results

The following fails have been reported by WebpageTest. These numbers indicates a possible performance issue with the PR which requires further manual testing to validate.

https://pr-2495.pwa-venia.com : LH Performance Expected 0.85 Actual 0.57, LH Best Practices Expected 1 Actual 0.92, WPT Cache Expected 90 Actual 88
https://pr-2495.pwa-venia.com/venia-tops.html : LH Performance Expected 0.75 Actual 0.34, LH Best Practices Expected 1 Actual 0.92
https://pr-2495.pwa-venia.com/valeria-two-layer-tank.html : LH Performance Expected 0.8 Actual 0.49, LH Accessibility Expected 0.9 Actual 0.89, LH Best Practices Expected 1 Actual 0.92

@tjwiebell tjwiebell added the version: Major This changeset includes incompatible API changes and its release necessitates a Major version bump. label Jun 18, 2020
@tjwiebell tjwiebell changed the base branch from develop to release/7.0 June 22, 2020 16:46
{ called: setShippingAddressCalled, loading: isSetShippingLoading }
{
called: setShippingAddressCalled,
error: setShippingAddressError,
Copy link
Contributor

Choose a reason for hiding this comment

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

It's a bit counterintuitive to see setShippingAddressCalled and setShippingAddressError used as object names, since it's our most common format for naming functions.

Consider calledSetShippingAddress or errorSettingShippingAddress, I suppose?

Copy link
Contributor

Choose a reason for hiding this comment

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

This pattern is used all over the place. If we want to switch it up let's make sure everyone is aware.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Renamed in 57ab1a7.

@@ -27,6 +27,10 @@
padding: 0.75rem 1rem;
}

.error {
color: rgb(var(--venia-error));
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 not sure this is a property anymore.

  • See tokens.css for new tokens
  • If you replace this one, make sure to replace all of the others as well
Suggested change
color: rgb(var(--venia-error));
color: rgb(var(--venia-global-color-error));

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jimbo - that token doesn't seem to exist. Would you like me to add it, or pick one of the --venia-global-color-red-xxx values?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Strike that, we cut the release branch before design tokens landed, so the new tokens are not available. Going to leave this as-is, and we'll resolve when we merge release back into develop.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, my mistake. I assumed this was targeting develop but it's actually targeting the release branch. Carry on. 👍

handleOnSubmit,
handleZipChange,
isSetShippingLoading
} = talonProps;

const classes = mergeClasses(defaultClasses, props.classes);

const errorElement = errorMessage ? (
<span className={classes.error}>{errorMessage}</span>
Copy link
Contributor

Choose a reason for hiding this comment

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

I might have liked to see a FormError component for form-level errors, since I imagine the presentation will be nearly identical for all of them. Such a component could render null when props.message was empty, too, cutting down on a lot of the boilerplate.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added FormError component in 57ab1a7, and changed all usages in this PR to use it (where it didn't dramatically alter the UX).

I thought we had discussed deferring a dedicated component to a followup Story, but I've gone ahead and implemented one in this scope.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks. I apologize if we backtracked on an agreement. It's not the duplication of code that bothered me; I just saw that we were adding new classnames to a lot of form components as a result of the new error elements, and thought it'd be nice to avoid that churn. Glad to have a component now.

@@ -39,6 +47,30 @@ export const useCustomerForm = props => {
}
}, [getCustomerError]);

useEffect(() => {
if (createCustomerAddressError || updateCustomerAddressError) {
errorRef.current.scrollIntoView({
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 scrollIntoView() is the right choice here, but I don't think it belongs in the talon.

The only other place we use this, PaymentsFormItems, has a talon of its own as well, but the component is the one to call scrollIntoView because this kind of effect is really just presentational. There's very little logic we put in the component rather than the talon, but I think this qualifies.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved this logic to FormError and put scroll and ref logic in component instead of talon (57ab1a7).

# Conflicts:
#	packages/peregrine/lib/talons/CartPage/ProductListing/EditModal/__tests__/useProductForm.spec.js
#	packages/peregrine/lib/talons/CartPage/ProductListing/EditModal/useProductForm.js
sirugh
sirugh previously approved these changes Jun 24, 2020
Copy link
Contributor

@sirugh sirugh left a comment

Choose a reason for hiding this comment

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

This looks good - I don't see anything worth holding up the PR. Would be nice to have a utility function, and perhaps move the error scrolling to the component but otherwise 👍

Comment on lines 44 to 50
if (setShippingAddressError.graphQLErrors) {
derivedError = setShippingAddressError.graphQLErrors
.map(({ message }) => message)
.join(', ');
} else {
derivedError = setShippingAddressError.message;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I smell a utility function!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We are probably not going to render Apollo errors going forward, since they can't be trusted to be human readable. I have moved this logic to a new FormError component which should reduce the boiler plate though, but you still gotta handroll this logic if you don't like the presentation of that component.

] = useMutation(updateConfigurableOptionsMutation);

const isSaving =
(updateQuantityCalled && updateQuantityLoading) ||
(updateConfigurableCalled && updateConfigurableLoading);

useEffect(() => {
if (formApi) {
formApi.setValue('quantity', cartItem.quantity);
Copy link
Contributor

Choose a reason for hiding this comment

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

What was this doing and why is it gone now?

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 intention was to make sure quantity was updated between renders, but two things made it obsolete:

  1. I started un-mounting between renders, so this wasn't needed
  2. This was fixed in the Quantity component in the same way, so this was redundant anyway (even if we didn't unmount between renders)

jimbo
jimbo previously approved these changes Jun 24, 2020
@@ -40,6 +42,7 @@ const ShippingForm = props => {
return (
<Fragment>
<h3 className={classes.formTitle}>Destination</h3>
<FormError errors={formErrors} />
Copy link
Contributor

Choose a reason for hiding this comment

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

You don't have to do this, but consider just rendering the FormError element as the first child of the Form element, rather than wrapping these forms in fragments. Might help if the form wants to have the form error participate in its layout.

.map(({ message }) => message)
.join(', ');
} else {
// A non-GraphQL error occurred.
applyErrorMessage - applyError.message;
derivedErrorMessage = errorTarget.message;
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to handle network errors? They'd be on the target as errorTarget.networkError.

Copy link
Contributor

Choose a reason for hiding this comment

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

Discussed offline - I am for moving this sort of logic into a utility that is used by useFormError and by any other components that need to turn apollo errors into a 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 was going to suggest that too, 💯 agree

@@ -23,7 +23,7 @@ jest.mock('../shippingForm', () => 'ShippingForm');
jest.mock('../shippingRadios', () => 'ShippingRadios');

test('renders description and confirm link w/o shipping address set', () => {
useLazyQuery.mockReturnValue([
useQuery.mockReturnValue([
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 surprised this is working - useQuery just returns an object, not an array like useLazyQuery does.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Very odd. I haven't full covered this with tests yet, so I'll be sure to investigate this and update the mock to accurately reflect the implementation of useQuery.


if (applyError) {
if (applyError.graphQLErrors) {
if (errorTarget.graphQLErrors) {
Copy link
Contributor

Choose a reason for hiding this comment

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

OOPS - as we just discovered, graphQLErrors is an array. All these checks will need to be updated to length

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great catch! Added this check to FormError, and we'll address the other areas once we create a utility.

supernova-at
supernova-at previously approved these changes Jun 25, 2020
Copy link
Contributor

@supernova-at supernova-at left a comment

Choose a reason for hiding this comment

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

Looks awesome! Great organization and consistency.

I had one question about a unit test mock that I'm surprised doesn't break things.

@tjwiebell tjwiebell dismissed stale reviews from supernova-at and jimbo via 49cc995 June 25, 2020 15:24
Copy link
Contributor

@sirugh sirugh left a comment

Choose a reason for hiding this comment

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

Good - we will push the fixing of the other places missing the length check to a follow up ticket.

@dpatil-magento
Copy link
Contributor

QA Approved.

@dpatil-magento dpatil-magento merged commit 7f67037 into release/7.0 Jun 25, 2020
@m2-community-project m2-community-project bot moved this from Review in Progress to Done in Pull Request Progress Jun 25, 2020
@tjwiebell tjwiebell mentioned this pull request Jun 26, 2020
1 task
@dpatil-magento dpatil-magento mentioned this pull request Jul 14, 2020
1 task
@tjwiebell tjwiebell deleted the tommy/error-handling branch October 2, 2020 18:42
@sirugh sirugh restored the tommy/error-handling branch April 26, 2021 16:29
@sirugh sirugh deleted the tommy/error-handling branch April 26, 2021 16:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pkg:peregrine pkg:venia-ui version: Major This changeset includes incompatible API changes and its release necessitates a Major version bump.
Development

Successfully merging this pull request may close these issues.

None yet

7 participants