Fix updating products failure when too many parameters are sent#14293
Conversation
…s and enhance form submission handling for changed fields
rioug
left a comment
There was a problem hiding this comment.
Looks good, thanks.
I am not super thrilled about the added complexity. I wonder, given we already have a frontend check for changed item, if we could keep a list of updated products/variants when they are changed, and use that list to create the data to send.
But that probably comes with another set of complexity, so who knows 🤷
Makes me wonder if we should have gone with one request per changed product/variants instead of using product sets...
| elements.forEach((element) => { | ||
| if (element.type !== "submit") { | ||
| element.disabled = true; | ||
| } | ||
| }); |
There was a problem hiding this comment.
Shouldn't we be doing this after we checked if any elements have been changed ? No point disabling element if we are not going to submit anything
Sorry I misunderstood what we are doing here, I see we are disabling element so that they don't get submitted. I am assuming element will get re enabled after the form reload.
There was a problem hiding this comment.
Yes, Gaetan. This will only get triggered upon form submission and after that we are reloading the entire table that would reset the all elements state to the original one.
| variant_ids = Array(variants_attributes).filter_map { |variant| variant[:id].presence } | ||
| return if variant_ids.empty? | ||
|
|
||
| Spree::Variant.where(id: variant_ids).pick(:product_id) |
There was a problem hiding this comment.
Is this to cover an edge case ? or is it likely we'll get lots of product is missing ? I fear this could generate a lots of queries and possibly result in performance issue.
There was a problem hiding this comment.
In our case it shouldn't happen because I've made sure that we always include product and variant id in the payload, so the payload ids will be used rather than db query. However, just in case if the payload doesn't include product ids then we could use a db fallback.
Let me just double check this as well.
There was a problem hiding this comment.
I double checked this, and yes, we are making sure that we send the product and variant id from the frontend in the request.
There was a problem hiding this comment.
If we are making sure that we always send the product ID with the Javascript, then maybe we don't need infer_product_id_from_variants anymore. Can it be removed?
dacook
left a comment
There was a problem hiding this comment.
Thanks Ahmed, I tried it out and it works beautifully. I updated 27 products and the payload was 1.6kb 👍
I did notice that the disabled fields have a grey border around them temporarily. I think the experience would be smoother if these didn't appear, can you hide them?
Also for the second part, I have an alternative idea, can you please check it out and if it looks good, update the code and specs?
| variant_ids = Array(variants_attributes).filter_map { |variant| variant[:id].presence } | ||
| return if variant_ids.empty? | ||
|
|
||
| Spree::Variant.where(id: variant_ids).pick(:product_id) |
There was a problem hiding this comment.
If we are making sure that we always send the product ID with the Javascript, then maybe we don't need infer_product_id_from_variants anymore. Can it be removed?
| relatedContainer | ||
| .querySelectorAll('input[type="hidden"], input[type="checkbox"], select, textarea') | ||
| .forEach((relatedElement) => this.#enableElement(relatedElement)); | ||
| } |
There was a problem hiding this comment.
I'm guessing this was required to ensure unit value updates work.
It's quite specific and I worry that it might break in the future. Do you know if this specific case is covered by a system spec?
It makes me wonder if it would have been ok to take a simpler approach and enable the entire variant row if any fields are changed. But this implementation is much more efficient in the case where you update one field on lots of products at once.
Which elements exactly is this for? if there's a Stimulus controller for these elements, maybe it could be marking them as "changed", then you wouldn't need this extra logic here.
| variant_unit: variant.variant_unit, | ||
| unit_scale: variant.variant_unit_scale, | ||
| unit_value: variant.unit_value, | ||
| } |
There was a problem hiding this comment.
I think Rails does this for you: https://api.rubyonrails.org/classes/ActiveModel/Dirty.html
For example variant.variant_unit_was
| variant&.errors.blank? | ||
| end | ||
|
|
||
| def normalize_unit_value_for_scale_change( |
There was a problem hiding this comment.
I actually think it might be better to handle this on the browser side. It seems this code is intended to copy the behaviour on the browser, which automatically adjusts the unit value and displays it to the user.
The problem is that the hidden field unit_value changes, but doesn't get marked as changed because it's a hidden field.
We can't actually track changes on a hidden field but could work around by changing it to a text field with display:none
(see
openfoodnetwork/app/webpacker/controllers/bulk_form_controller.js
Lines 176 to 178 in 63255e6
In that case, I think this problem would be solved. I just tried and it looks like it will work.
I added a commit to share, can you please check if this solves the cases you are aware of?
What? Why?
This PR fixes bulk product updates failing with:
The root cause was that the bulk update form submitted all product fields on the page, including unchanged records and nested variant attributes. When many products were displayed, the generated payload exceeded Rails/Rack's parameter limit and resulted in a
ActionController::BadRequesterror.Solution
This change reduces the size of submitted bulk update payloads by submitting only changed fields and the minimum identity fields required for updates:
id) so updates can still be applied correctly.product_idfrom submitted variant IDs when a product ID is omitted (for variant-only changes).Additionally, this PR fixes an edge case in variant unit scale updates where changing the scale (e.g.
g → kg) without modifying the visible quantity could lead to incorrect persisted values. The update flow now normalizesunit_valueto preserve the visible quantity while still respecting explicitly submitted values.Tests were added to cover:
What should we test?
/admin/products).ActionController::BadRequest.g → kg) without modifying the quantity and confirm the visible quantity remains consistent after save.Release notes
Changelog Category (reviewers may add a label for the release notes):