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

Store: Update ProductSearch component for better variation support, multiple selections #19531

Merged
merged 28 commits into from
Nov 9, 2017

Conversation

ryelle
Copy link
Member

@ryelle ryelle commented Nov 6, 2017

See #15681. We need a reusable product search component for orders and promotions, so I've pulled together the code from what's currently in the promotions section and added variation support.

We now have a component that renders out all the products in a store, which can be filtered by searching. Products with variations are listed with a Count component (mostly to take up the space where the checkbox should be), and a Customize button, which opens the variations form.

list

As products are selected in the form, they become selected rows in the list. Unchecking them deletes them from the list. They're also displayed with the correct variation info - price and image.

list-selected

This component also supports enforcing a single selection. This uses radio buttons, instead of checkboxes.

single

It only allows for one variation to be selected at a time, and will replace the currently-selected variation the new one if it's changed.

single-selected

You can also check out the new README for examples/expected props.

ℹ️ Note: This PR also updates the per_page for the variations API request. It was set to 10, so my Mug example with 12 variations (3 sizes, 4 colors) was not fully loading here or on the product edit screen.

⚠️ Known issue: currently when searching, selected simple products stay in the list even if they don't match (a holdover from the appliesTo component). This isn't true for variations right now, but I wanted to finally get this PR up before it took even longer.

❓ Question: Currently the text says that the user can add the "base product" without picking variations, but I haven't added that functionality yet. Is that something we should support?

To test

This isn't in use anywhere real yet, but you can still test it by checking out the order create screen

  • View order create page by visiting /store/order/:site
  • You can find the search under "Add products to the order"
  • Make sure all your products load in (they come in either 50 or 100 at a time, so if you have a lot of products they might not all be there immediately)
  • Try selecting some products
  • Click around the variations, select and unselect them
  • You can edit order-create/index.js and add the singular prop to see the single-select view
  • Additional testing of the Products edit screen to make sure all variations load in the variations table would also help 👍

As usual, if anyone has better copy I'm happy to update anything here 🙂

@ryelle ryelle added Store [Status] Needs Design Review Add this when you'd like to get a review / feedback from the Design team on your PR [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. labels Nov 6, 2017
@ryelle ryelle self-assigned this Nov 6, 2017
@matticbot
Copy link
Contributor

@coderkevin
Copy link
Contributor

coderkevin commented Nov 6, 2017

❓ Question: Currently the text says that the user can add the "base product" without picking variations, but I haven't added that functionality yet. Is that something we should support?

We'll need this for coupon-based promotions, as it's possible to select entire variable products or individual variations of that product for a coupon. However, for product sales, it's not possible to select entire variable products. So maybe make it optional?

Copy link
Contributor

@coderkevin coderkevin left a comment

Choose a reason for hiding this comment

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

Thanks for all the code on this, @ryelle! I've got just a couple of comments.

}

const { oldSiteId, oldProductId } = this.props;
const { newSiteId, newProductId } = newProps;
Copy link
Contributor

@coderkevin coderkevin Nov 6, 2017

Choose a reason for hiding this comment

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

You're naming props here as oldSiteId, oldProductId, newSiteId, and newProductId? Wouldn't the incoming props be just siteId and productId?

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed this, thanks for catching it

if ( matchingVariations.length === 1 ) {
// We found a match.
const variation = head( matchingVariations );
if ( this.props.singular ) {
Copy link
Contributor

@coderkevin coderkevin Nov 6, 2017

Choose a reason for hiding this comment

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

couldn't this be simplified just by creating

const variations = ( this.props.singular ? [ head( matchingVariations ) ] : matchingVariations );

or something like that? And then removing the singular-specific code?

areAnySelected = () => {
const { product, singular } = this.props;
const { variations } = this.state;
if ( singular && variations[ 0 ] ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here, maybe

const variations = ( singular ? [ this.state.variations[ 0 ] ] : this.state.variations );

Copy link
Member Author

Choose a reason for hiding this comment

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

No to both, really, because of the line after both of these; behavior following is different for singular vs multiple selections. If you want to take a stab at updating this so that it's not the case once it's merged, feel free :)

{ this.renderInputImage( product ) }
{ this.renderInputName( product ) }
</FormLabel>
);
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 how clean this all stacks up!

@@ -0,0 +1,48 @@
/** @format */

/**
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe some unit tests for these functions in a future PR?

@allendav
Copy link
Contributor

allendav commented Nov 7, 2017

Possible future PR - if you don't have any products, maybe a disabled placeholder that says as much instead of the search box?

/>
<ProductSearchResults search={ this.state.query } onSelect={ this.handleSelect } />
{ this.renderSearch( this.state.searchFilter ) }
{ this.renderList( this.props.singular ) }
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not just destructure singular inside renderList instead of passing it? Same question for renderSearch too.

reduce,
uniqBy,
values,
} from 'lodash';
Copy link
Contributor

Choose a reason for hiding this comment

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

10 lodash imports - you get your next one free :P

Copy link
Contributor

@allendav allendav left a comment

Choose a reason for hiding this comment

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

Tests very well. Just one comment on the code and one suggestion regarding zero-product stores. Pre-approving.

coderkevin
coderkevin previously approved these changes Nov 7, 2017
Copy link
Contributor

@coderkevin coderkevin left a comment

Choose a reason for hiding this comment

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

LGTM

@ryelle
Copy link
Member Author

ryelle commented Nov 8, 2017

@jameskoster Feedback on the search component itself can go here :) There's also more info about the component itself in the description here.

It's a shame there's no way to add more than one variation at once, but I can't really see a solution for that.

If you're talking about the bug in your gif, that's definitely a bug. You can see in the screenshots above that you can select more than one variation. Does this only happen on products with two variations?

"Customize" doesn't feel like the right word for the variation button. How about "Select variations"?

Sure - like usual I basically threw words in hoping for feedback 🙂

One other quirk. If I check a product, then search for another product for which there's only one result and check that, it looks like only one product will be added. Perhaps checked products should be permanently visible?

That shouldn't be the case, but i'll test again. I did note in the description here that only selected simple products are shown when searching, and that I know that's wonky from a UX perspective but that I'll fix it in a follow up PR (I wanted this one and the add products to get merged first, so we would have the base to work off of).

@ryelle ryelle force-pushed the add/store-new-product-search branch from c47a28f to 8e3db38 Compare November 8, 2017 15:42
<span>
<span>{ nameWithPrice }</span>
<Button compact onClick={ this.toggleCustomizeForm }>
{ translate( 'Select variations' ) }
Copy link

Choose a reason for hiding this comment

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

Hi! I've found a possible matching string that has already been translated 17 times:
translate( 'Multiple variations' ) ES Score: 8

Help me improve these suggestions: react with 👎 if the suggestion doesn't make any sense, or with 👍 if it's a particularly good one (even if not implemented).

@jameskoster
Copy link
Contributor

Does this only happen on products with two variations?

OK this is odd, I just tried a product with 2 attributes + 6 variations, but when I try to add it I can only choose one attribute and the component say's there are only 2 variations. # #

I did note in the description here that only selected simple products are shown when searching

Oh the searching works fine, I was just saying it's an odd experience to have checked a product, then search and see one result, check that then click add and see 2 products added. It just feels odd cause when you click the button you can only see a single product. Like I said, permanently displaying checked products would fix this.

@ryelle
Copy link
Member Author

ryelle commented Nov 8, 2017

Managed to reproduce the issue @jameskoster mentioned, it looks like if you select an already-selected variation it wipes out the rest of the selected variations? Not sure why, but I cleaned up some of the variation state logic and it's working much better now.

I just tried a product with 2 attributes + 6 variations, but when I try to add it I can only choose one attribute and the component say's there are only 2 variations.

hmm that's unusual. was this product created in wp-admin, or calypso?

the component say's there are only 2 variations.

The 2 is part of your product's name - the notice-label doesn't say anything about the count of variations (though it could, if you think that's useful).

@ryelle ryelle force-pushed the add/store-new-product-search branch from f48c55d to d488f4b Compare November 8, 2017 18:29
value={ this.state[ attribute.name ] }
>
<option key={ DEFAULT_ATTR } value={ DEFAULT_ATTR }>
{ translate( 'Select one' ) }
Copy link

Choose a reason for hiding this comment

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

Hi! I've found a possible matching string that has already been translated 15 times:
translate( 'Select one...' ) ES Score: 11

Help me improve these suggestions: react with 👎 if the suggestion doesn't make any sense, or with 👍 if it's a particularly good one (even if not implemented).

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, who forgot to use the … character?


const selectedIds = intersection( newProps.value, newProps.product.variations );
const selectedVariations = selectedIds.map( id => find( newProps.variations, { id } ) );
this.state = {
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 use setState instead. It's causing a console warning.

Copy link
Member Author

Choose a reason for hiding this comment

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

hah, bet that's new with the React upgrade, hadn't seen that the rest of the time I was working on this

<option key={ DEFAULT_ATTR } value={ DEFAULT_ATTR }>
{ translate( 'Select one' ) }
</option>
{ attribute.options.map( ( opt, i ) => <option key={ i }>{ opt }</option> ) }
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 that i as a key isn't specific enough when you have more than one attribute. I'm getting a console warning on the same key 0 when I have two attributes on a variable product.

Copy link
Member Author

Choose a reason for hiding this comment

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

Strange, I'm not. But I'll update it regardless, seems like a good idea.

@coderkevin coderkevin dismissed their stale review November 8, 2017 19:04

There are a couple of issues still.

@coderkevin
Copy link
Contributor

On a variable product with two attributes, the first selector never displays the selected attribute value, although it still appears to be working. Maybe it's something to do with the React same key, 0 warning I mentioned in the code?
multiple-attribute-selection-problem

@ryelle
Copy link
Member Author

ryelle commented Nov 8, 2017

Maybe it's something to do with the React same key, 0 warning I mentioned in the code?

No, it was introduced with the change in d488f4b & had to do with setting this.state without setState. All the points you raised should be fixed now.

value={ this.state[ attribute.name ] }
>
<option key={ `${ attribute.name }-${ DEFAULT_ATTR }` } value={ DEFAULT_ATTR }>
{ translate( 'Select one' ) }
Copy link

Choose a reason for hiding this comment

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

Hi! I've found a possible matching string that has already been translated 15 times:
translate( 'Select one...' ) ES Score: 11

Help me improve these suggestions: react with 👎 if the suggestion doesn't make any sense, or with 👍 if it's a particularly good one (even if not implemented).

@ryelle
Copy link
Member Author

ryelle commented Nov 8, 2017

@coderkevin This PR is getting pretty long, are you OK with me merging it? The things still on my to-address list:

  • A placeholder view for a store with no products
  • Always show all selected products (including variations)
  • "Select variations" button should hide the form when clicked again
  • Whatever the issue with Jay's product is, though I can't reproduce it
  • Indent variations below products

Copy link
Contributor

@coderkevin coderkevin left a comment

Choose a reason for hiding this comment

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

Latest changes LGTM and test well. 🎉 Great job!
:shipit:

@kellychoffman
Copy link
Member

One design request: Re: https://user-images.githubusercontent.com/541093/32455495-77ad6bdc-c2f0-11e7-8e92-a1d645d2106a.png

Could the variations listed below the "main product" be indented so it looks like they are part of it?

@ryelle
Copy link
Member Author

ryelle commented Nov 9, 2017

@kellychoffman I've updated my "to-address list" with the design feedback, so I'll address that post-merge.

@ryelle ryelle added [Status] Ready to Merge and removed [Status] Needs Design Review Add this when you'd like to get a review / feedback from the Design team on your PR [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. labels Nov 9, 2017
@ryelle ryelle merged commit 805d743 into master Nov 9, 2017
@ryelle ryelle deleted the add/store-new-product-search branch November 13, 2017 15:27
rclations pushed a commit to rclations/wp-calypso that referenced this pull request Nov 15, 2017
… multiple selections (Automattic#19531)

* Remove existing `ProductSearch` component & style, copy over `AppliesToFilteredList`

* Split each product row into a new component, `ProductSearchRow`, which also renders a variation form when a variable product is selected

* Pull in product variations

* Style the variations form

* Clean up component, bring in the price display

* Update row display when a variation is selected

* Increase number of variations we can load

* Clean up component

* Move general functions into a helper utils file

* Add support for passing arrays to add/remove product IDs

* Pull row-rendering into a separate function, so we can use the same thing for products as variations. Move isSelected into this component so that we can check if variations are selected

* Handle selecting variations when the parent is unselected, delete unselected options

* Split up row-rendering function into smaller functions

* Add button to “Customize” variable products

* Retain selected variations even if the components are hidden by search

* Add attribute support into search by collapsing all attributes into a searchable string

* Fix singular display/interactions

* Fix “Customize” button behavior

* Update README

* Fix props destructuring, use the correct name but map to the new/old variable names

* Style the variation selector

Adds a class to the variations wrapper
Puts the variations notice inside a `Notice`

* Bring back FormLabel for accessibility, update variation form button text

* Address PR feedback

* Fix destructuring of new/old props

* Fix issue where selecting an already-selected variation clears the rest of the selected variations

* Update label text & the default select text.

* Reset the form back to initial state after selecting a variation

* Address PR feedback: don’t assign to this.state directly, update option keys
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants