-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Conversation
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? |
There was a problem hiding this 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; |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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 ) { |
There was a problem hiding this comment.
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 ] ) { |
There was a problem hiding this comment.
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 );
There was a problem hiding this comment.
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> | ||
); |
There was a problem hiding this comment.
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 */ | |||
|
|||
/** |
There was a problem hiding this comment.
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?
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 ) } |
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
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
There was a problem hiding this 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
@jameskoster Feedback on the search component itself can go here :) There's also more info about the component itself in the description here.
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?
Sure - like usual I basically threw words in hoping for feedback 🙂
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). |
c47a28f
to
8e3db38
Compare
<span> | ||
<span>{ nameWithPrice }</span> | ||
<Button compact onClick={ this.toggleCustomizeForm }> | ||
{ translate( 'Select variations' ) } |
There was a problem hiding this comment.
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).
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. # #
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. |
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.
hmm that's unusual. was this product created in wp-admin, or calypso?
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). |
…h also renders a variation form when a variable product is selected
Adds a class to the variations wrapper Puts the variations notice inside a `Notice`
…st of the selected variations
f48c55d
to
d488f4b
Compare
value={ this.state[ attribute.name ] } | ||
> | ||
<option key={ DEFAULT_ATTR } value={ DEFAULT_ATTR }> | ||
{ translate( 'Select one' ) } |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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 = { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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> ) } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
No, it was introduced with the change in d488f4b & had to do with setting this.state without |
value={ this.state[ attribute.name ] } | ||
> | ||
<option key={ `${ attribute.name }-${ DEFAULT_ATTR }` } value={ DEFAULT_ATTR }> | ||
{ translate( 'Select one' ) } |
There was a problem hiding this comment.
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).
@coderkevin This PR is getting pretty long, are you OK with me merging it? The things still on my to-address list:
|
There was a problem hiding this 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!
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? |
@kellychoffman I've updated my "to-address list" with the design feedback, so I'll address that post-merge. |
… 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
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.
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.
This component also supports enforcing a single selection. This uses radio buttons, instead of checkboxes.
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.
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.❓ 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
/store/order/:site
order-create/index.js
and add thesingular
prop to see the single-select viewAs usual, if anyone has better copy I'm happy to update anything here 🙂