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

Add to Wishlist from Cart #3130

Merged
merged 33 commits into from
May 14, 2021
Merged

Add to Wishlist from Cart #3130

merged 33 commits into from
May 14, 2021

Conversation

revanth0212
Copy link
Contributor

Description

This PR adds a new feature that lets the user move an item from the cart to the wishlist. This PR only targets single wishlist use cases, for instance, CE backends or EE backends with multiple wishlists turned off.

Related Issue

Closes PWA-1495

Verification Stakeholders

@schensley
@tkacheva
@dpatil-magento

Verification Steps

  1. Go to the deployed version of the app.
  2. Login and add an item to the cart.
  3. Go to the cart page and click on the kabab menu.
  4. Click the Save for later button.
  5. The item should be removed from the cart and should be available in the favorites list.

Screenshots / Screen Captures (if appropriate)

Checklist

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

@revanth0212 revanth0212 added the version: Minor This changeset includes functionality added in a backwards compatible manner. label Apr 16, 2021
@PWAStudioBot
Copy link
Contributor

PWAStudioBot commented Apr 16, 2021

Messages
📖

Associated JIRA tickets: PWA-1495.

📖 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).
📖

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

Generated by 🚫 dangerJS against ab8c672

@revanth0212
Copy link
Contributor Author

UX and PM review passed.

timeout: 5000
});

await removeItemFromCart({
Copy link
Contributor Author

@revanth0212 revanth0212 Apr 20, 2021

Choose a reason for hiding this comment

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

We have discussed this in the Dev time and decided that we will not be rolling back wishlist addition if remove from the cart fails.

@@ -42,6 +42,7 @@ export const ProductListingFragment = gql`
... on ConfigurableCartItem {
configurable_options {
id
configurable_product_option_value_uid
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
configurable_product_option_value_uid

I guess this is a change I forgot to revert after reverting the rollback code.

Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like you are still using configurable_product_option_value_uid throughout the code. Not sure what you meant by your comment but...do something? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, my bad. Wrong call.

@revanth0212 revanth0212 marked this pull request as ready for review April 20, 2021 16:01
@sirugh sirugh force-pushed the revanth/add-cart-to-wishlist branch from 4786865 to cf19090 Compare April 22, 2021 17:28
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.

Nice work :) I was a little confused why we have a .ee file in here when you're just implementing the single wishlist, but I suppose this is going to help be a jumping off point for the next ticket (add to multiple).

Comment on lines +90 to +91
const [displayError, setDisplayError] = useState(false);
const [showLoginToast, setShowLoginToast] = useState(0);
Copy link
Contributor

Choose a reason for hiding this comment

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

Would prefer a boolean here, but I understand why you use an integer (so the memo fires, and returns a new object, triggering the toast again). I wonder if we have any other instances of a repeated toast in the app, or if this is a new pattern.

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 picked this logic from Tommy's #3105

@@ -91,10 +101,16 @@ const Product = props => {
})
: '';

useEffect(() => {
if (loginToastProps) {
addToast({ ...loginToastProps, icon: InfoIcon });
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this the new style so that the props are customizable via talon export?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I kind of like this idea of sending toast props from the talon and let the component only worry about when to show the toast itself.

@@ -376,10 +378,10 @@
"validation.isRequired": "Is required.",
"validation.mustBeChecked": "Must be checked.",
"validation.validatePassword": "A password must contain at least 3 of the following: lowercase, uppercase, digits, special characters.",
"wishlist.emptyListText": "There are currently no items in this list",
"wishlistButton.addedText": "Added to Favorites",
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 nitpicky, sorry, but addedText should come after addText as add comes before added.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Alphabetically they are correct arent they?
image

Copy link
Contributor

@sirugh sirugh Apr 23, 2021

Choose a reason for hiding this comment

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

I don't think so :P If we sorted purely by string value, then you'd be correct, but afaict we use dictionary order here, and you'd find add before added in the dictionary.

Edit: I was wrong - string value should work here:

Copy link
Contributor

Choose a reason for hiding this comment

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

Honestly we should just set up a lint rule to auto sort this file for us :D

Copy link
Contributor

Choose a reason for hiding this comment

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

Prefer standard string-value sorting, please, as it's the least arbitrary.

addError
addText
addedText

Copy link
Contributor Author

@revanth0212 revanth0212 Apr 23, 2021

Choose a reason for hiding this comment

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

Yeah, the plugin I used is not considering the length of the keys, it is only considering based on the character in the position. Since addEr comes after added it sorted it that way.

fragment WishlistFragment on Wishlist {
id
name
items: items_v2 {
Copy link
Contributor

Choose a reason for hiding this comment

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

Generally I would avoid renaming or aliasing responses from gql as it is just another layer of abstraction that could make it more difficult to debug. If something went wrong with items in the response we would have to know that it was aliased from items_v2. Obviously this is a pretty minor thing, and I won't ask you to change it, but I think it's just good to be aware.

In this case I might actually leave it as items_v2 is...rough.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah dude you are right. I am not a fan of renaming usually but I felt items_v2 is not readable in the code.

Copy link
Contributor

Choose a reason for hiding this comment

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

After discussion in the other PR I think we should move the alias to the destructure so we don't run into a conflict with the existing items schema value.

import configuredVariant from '@magento/peregrine/lib/util/configuredVariant';

import { useWishlist } from './useWishlist';
Copy link
Contributor

Choose a reason for hiding this comment

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

I feel like having this hook live as a sibling to the talon within the talons directory could be confusing. I know we spoke about this regarding @tjwiebell's use case. Did we end up with a decision on where to place these utility hooks?

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 quite completely though. I am trying to see if I can generalize wishlist logic into a talon of its own so all the components/talons that use it can just import. But unfortunately, that can be tricky because the use cases can be different and Jimmy suggested moving over it if it's getting complex to generalize.

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 agree. At a minimum, throw the file into a helpers directory.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If I am able to generalize this, yes I will move it into a helper's folder but if it is bound to stay like this, it's not a helper anymore so we can leave it as it is. I'll update soon about the generalization work and let's make the call then.

@revanth0212
Copy link
Contributor Author

Hey @supernova-at, thanks for doing the QA on this. I have handled the simple product's scenario. Unfortunately, I could not understand what the error scenario was. Can you expand on that?

@dpatil-magento
Copy link
Contributor

dpatil-magento commented May 11, 2021

@dpatil-magento I'm curious if any of the mftf failures are due to the recent category filters/sort work. Failures 3 and 4 seem like it but I thought MFTF passed on the #3034 PR...

@sirugh Looks like this PR branch is not updated with latest develop that why those are failing.

@supernova-at
Copy link
Contributor

supernova-at commented May 11, 2021

Unfortunately, I could not understand what the error scenario was. Can you expand on that?

@revanth0212 Sure, it's this one (in the JIRA ticket):

Verify error flow. One way to simulate this scenario is to have 2 tabs open for same customer > Move or remove product in tab 1 and try to move the product on second tab.

@supernova-at
Copy link
Contributor

@revanth0212 I updated the QA comment after merging in develop. There is still one MFTF test failure.

@dpatil-magento
Copy link
Contributor

dpatil-magento commented May 11, 2021

@revanth0212 I updated the QA comment after merging in develop. There is still one MFTF test failure.

@supernova-at Those are valid failures as display position in selectors has been changed. You can try running using this branch https://github.com/magento-commerce/pwa-tests/pull/15

BTW to avoid this kind of things we will have data-ids for cypress :)

@revanth0212
Copy link
Contributor Author

revanth0212 commented May 12, 2021

Verify error flow. One way to simulate this scenario is to have 2 tabs open for same customer > Move or remove product in tab 1 and try to move the product on second tab.

Unfortunately, this is a problem with the GQL mutation. Since there is no single mutation to move an item from cart to wishlist, we are using 2 mutations in tandem.

  1. First we add the item to the wishlist, which in this case is working fine.
    image

  2. Second we remove the item from the cart, which in this case is failing because the item is not part of the cart anymore.
    image

I am not sure if we need to solve for this case, because firstly, its an edge case and secondly this wont happen if GQL can provide us with a unified GQL mutation for moving an item from cart to wishlist.

Also, this wont be a problem if the user refreshes the page because the cart will update and the item wont be visible anymore.

Alternatively, if we want to solve this, the ideal solution would be to refresh the cart before performing this action but that would slow the process down by adding a new query into the mix.

@supernova-at
Copy link
Contributor

MFTF tests are failing but not because of this PR. Running using a PR in that repo, they all pass.

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.

Not sure how to evaluate the recent push - could you help me understand how to repro the error state?

const handleWishlistUpdateError = useCallback(() => {
setIsCartUpdating(true);

fetchCartDetails({ variables: { cartId } });
Copy link
Contributor

Choose a reason for hiding this comment

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

Threading this prop all the way down from the cart page feels odd. Could you tell me why this is necessary in the first place and how to repro or get this function to be called? If we do have to go this route, perhaps we can go with a callback like onError that is passed down.

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 react state update error I showed earlier today was present even before this change. That said, you can reproduce an error that can call this function by opening the cart in 2 tabs, move an item in 1 cart to the wishlist and do the same on the other tab. Removing items from the cart will fail because the item is not in the cart anymore.

Copy link
Contributor

@sirugh sirugh May 13, 2021

Choose a reason for hiding this comment

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

Ok thanks for providing repro.

I do think the handling here is a bit off still - the item greys out, then the success toast pops up, then you see an error on the item for a brief moment and then the item is deleted from the cart page UI.

Image from Gyazo

Edit:

If this is the desired approach (refetching the cart data on error) then I think a callback such as onError defined by the cart page and passed to any children who care, is preferable to a fetchCartDetails prop.

I also think we can remove the setDisplayError(true) call for the product causing the error since it's going to be deleted anyways.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, it is doggy for sure, but that's the best at the moment till GQL comes up with a unified query. And this is a very very edge case. UX approves this as a valid solution given the circumstances.

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 also think we can remove the setDisplayError(true) call for the product causing the error since it's going to be deleted anyways.

Wouldn't that hide legitimate errors as well?

Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't that hide legitimate errors as well?

Would it? Normally you would call setDisplayError(true) in the catch block related to the mutation that fails. Is this what we use this function for?

Perhaps it would hide legitimate errors but I still think the UX is weird. Look at the gif!

Copy link
Contributor

@sirugh sirugh May 14, 2021

Choose a reason for hiding this comment

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

I think I understand my confusion about this. In these files we have called setDisplayError in the catch block of the related item call, such as here. In fact, I believe that is the code that is causing the error (failure to remove) to display, not what you added here on line 118.

So please disregard my suggestion to remove the call to setDisplayError. We do need it, in the event that the wishlist mutation throws an error.

I still would like to see my other concern addressed :)

Copy link
Contributor

Choose a reason for hiding this comment

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

@revanth0212 had reasoning for not making my suggested change, I wanted to post it here so it was captured:

I would prefer it called fetchCartDetails because form the cart page POV we are not using that function for all errors. We are only using that when the wishlist move errors out.

This makes sense to me.

@sirugh
Copy link
Contributor

sirugh commented May 13, 2021

Maybe the state error, at least the one from product listing, is coming from here: https://github.com/magento/pwa-studio/blob/revanth/add-cart-to-wishlist/packages/peregrine/lib/talons/CartPage/ProductListing/useQuantity.js#L94

The backend I'm using got so slow I can't debug this right now, but I was wondering if the problem existed before your PR, such as when you remove the last item from your cart in the cart page (just by deleting, or setting quantity to 0). If it doesn't manifest on develop then there must be some effect being triggered after your "add to wishlist" mutation that is not completing by the time the "remove from cart" mutation completes.

@supernova-at
Copy link
Contributor

supernova-at commented May 13, 2021

I still think the UX is weird. Look at the gif!

It is definitely weird but since we're getting a mutation that moves to wishlist from cart all at once, this is an interim weirdness that we made sure to get UX and QA's approval on.

@sirugh
Copy link
Contributor

sirugh commented May 14, 2021

@supernova-at thanks, I didn't have that context! I also realized that Revanth's use of setDisplayError was not actually the cause of the weirdness in the gif - as you said it is due to the dual mutations.

When are we getting a single mutation?

@tjwiebell
Copy link
Contributor

@revanth0212 - you mentioned during Office Hour that you were using a single operation for this, and just wanted to clarify what I meant in case it can help with future use cases. No need to re-factor if you've worked through the issues of these mutations being done is separate transactions, but there is one example of this with Configurable Product edits (which happen to be an add + removal).

@revanth0212
Copy link
Contributor Author

No need to re-factor if you've worked through the issues of these mutations being done is separate transactions, but there is one example of this with Configurable Product edits (which happen to be an add + removal).

This is a wonderful idea @tjwiebell but unfortunately, it did not also solve the problem and also this will be a huge change because the cart fragment resides in venia-ui and the talon is in peregrine. We can move the cart fragments over but I would prefer that to be done in a different PR.

@revanth0212
Copy link
Contributor Author

@sirugh can you do a code review on the recent changes and @supernova-at can you please do a QA re-run on this?

@supernova-at
Copy link
Contributor

When are we getting a single mutation?

Likely not until 2.4.3 in August. I created PWA-1736 to track.

@sirugh
Copy link
Contributor

sirugh commented May 14, 2021

@revanth0212 did you see my previous comment? It may have gotten lost in the discussion about the other issue.

If this is the desired approach (refetching the cart data on error) then I think a callback such as onError defined by the cart page and passed to any children who care, is preferable to a fetchCartDetails prop.

@supernova-at
Copy link
Contributor

supernova-at commented May 14, 2021

QA

⏳ In Progress ⏳

  • MFTF
  • JIRA ticket test plan
  • Run Verification Steps
  • Chrome
  • Firefox
  • Safari. I have accidentally upgraded to 14.1 and the whole app fails to load. Also fails on develop and demo instance, so not specific to this PR. May be related to https://bugs.webkit.org/show_bug.cgi?id=225344.
  • Mobile device

@supernova-at supernova-at merged commit cee1896 into develop May 14, 2021
supernova-at added a commit that referenced this pull request May 14, 2021
* Updated handler function to add item to wishlist and remove from cart.

* Minor.

* Roll back functionality added.

* Created CE and EE versions.

* Minor.

* Added login check.

* Added toasts.

* Sorting lang files.

* Removed the rolling back logic.

* Minor.

* Added loading state.

* Added french dictionary items.

* chore: log error from mutation (#3140)

* Addressed PR comments.

* Minor test updates.

* Common useWishlist helper hook. (#3141)

* Using useWishlist as a helper.

* CE wishlist refactor.

* EE wishlist refactor.

* onWishlistUpdateError -> setDisplayError

* Minor.

* Minor.

* Added initial test.

* Added useWishlist.ce tests.

* Added useWishlist.ee tests.

* Wishlist hook files refactor.

* Revert "Wishlist hook files refactor."

This reverts commit 34a771c.

* Revert "Common useWishlist helper hook. (#3141)"

This reverts commit 063144e.

* Updated failing tests.

* Added more tests.

* Removed unecesaary gql.

* Added cart page tests.

* More tests.

* Removed isFavorite

* Fix for simple products.

* Minor.

* Refreshing cart on error.

* Minor prop rename.

Co-authored-by: Stephen <sirugh@users.noreply.github.com>
Co-authored-by: Andy Terranova <13182778+supernova-at@users.noreply.github.com>
@michaelyu0123 michaelyu0123 deleted the revanth/add-cart-to-wishlist branch February 1, 2022 20:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pkg:extensions pkg:peregrine pkg:venia-ui Progress: done version: Minor This changeset includes functionality added in a backwards compatible manner.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants