[AGILE-432] Keep an added filter row visible - #25519
Conversation
|
Caution The provided work package version does not match the core version Details:
Please make sure that:
|
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
One or more issues must be addressed before approval.
Review effort: Lite
Findings: None
What changed in this PR
Keeps newly added, empty filter rows visible during Turbo morphs without adding draft values to requests or URLs.
Changes:
- Tracks pending filter rows and preserves their visibility/disabled state.
- Clears pending state when filters receive values or are removed.
- Adds coverage for morphing, values, removal, and unrelated rows.
| File | Description |
|---|---|
| frontend/src/stimulus/controllers/dynamic/filter/filters-form.controller.ts | Updated as part of this pull request. |
| frontend/src/stimulus/controllers/dynamic/filter/filters-form-pending-rows.controller.spec.ts | Updated as part of this pull request. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
617f371 to
3813e15
Compare
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Cached pending rows retain draft control values, allowing discarded input to be reapplied after restoration.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 1
Open (2)
Resolved since last review (1)
Since the AGILE-432 fix a row added without a value is left out of the request, so a form that the response morphs (Meetings) renders it hidden again. The controller now marks such rows and stops the morph from hiding them, instead of sending an empty value the server rejects as an invalid filter. A restored page drops the marked rows again, as Turbo snapshots them before the controller disconnects. An empty date range counts as no value, so a fresh "Dates interval" row is kept the same way. Restores the Meeting status example in meetings_index_spec. https://community.openproject.org/wp/AGILE-432
3813e15 to
bf6dc0f
Compare
Replaces the stored bound handlers for the clear button and the morph guard with one AbortController that disconnect aborts. Drops the second getElementById lookup and the initialize-time bindings those needed.
|
Warning Flaky specs
🤖 Ask Copilot to investigateCopy the prompt below into a new comment on this PR to delegate the investigation to GitHub Copilot. It will look into the flakiness and open a separate pull request with you as reviewer. |
There was a problem hiding this comment.
This feels quite well tested.
The controller itself feels quite complex and not well factored. I fell like very many things are happening all in the same module, and that also makes the tests harder to set up and follow. But since this was the case before, and seems to happen often with stimulus, changing it here would be far out of scope.
I left some comments but they are smaller things or more open ended questions.
Since you asked for considered opinions, I do like using factory functions instead of the shared variables that get set up in before. I feel that factory functions make dependencies explicit. Unfortunately they can also make the test arrangement more verbose. Still, I like the explicitness and the natural magnetism such a function has for collecting the variants of a test suite. (An early version, pre-ts, can be found on the bitcrowd dev blog 🙂 )
This comment was marked as resolved.
This comment was marked as resolved.
The helper only touches the row it is given and never reads controller state, so it lives as a module function next to the pending marker it serves rather than as a private method.
A row that now carries a value is the server's to render from here on, so its marker is dropped before the request goes out. The method name says so; the inline block in sendForm did not.
Dropping an unsent row on a restored page resets its control values, but primer-multi-input keeps the active picker in hidden and disabled attributes that a value reset does not touch. Adding the filter again then showed the range picker under the default operator. The row now re-applies the operator's visibility after the reset.
The operator change submits behind a 300 ms debounce while the idle wait returned after 50 ms of quiet, so the list and URL assertions ran before any request could have gone out and passed either way.
@lwassermann I agree. There may be an opportunity for refactoring as part of AGILE-375 or an eventual follow-up. |
Guarding only the row's hidden attribute and marker let a response that does not know the row put its operator, draft value and active picker back to server defaults. A user who had picked "does not contain" would then submit "contains". A pending row is now skipped by the morph as a whole; the add-filter option keeps its own guard.
@lwassermann Good find. It would deal with half of the problem. Since 94f141b the morph skips a pending row entirely, which is exactly what However it wouldn't cover the rest. |
|
Warning Flaky specs
🤖 Ask Copilot to investigateCopy the prompt below into a new comment on this PR to delegate the investigation to GitHub Copilot. It will look into the flakiness and open a separate pull request with you as reviewer. |
|
That was my thoughts as well. But I thought I'd at least raise it in case I didn't have the full picture 🙂 Thank you for checking 🙇 |


Ticket
https://community.openproject.org/wp/AGILE-432 (regression)
Alternative to #25512, same symptom.
What are you trying to accomplish?
modules/meeting/spec/features/meetings_index_spec.rb:203("Meeting status" filter) fails ondevand on every PR based on it. Adding a list filter unhides its row on the client and then asks the server for a fresh render; the server renders a row hidden unless the query holds that filter (base_filter_form.rb:84). Sincefd8536fb2de(AGILE-432) an autocomplete row without a selection is left out of the request, so the response hides the row again. The Meetings index is the one consumer whose response morphs the filter form (meetings_controller.rb:69-72); Projects and Portfolios only replace the filter button, Backlogs keeps the form outside its frame.The meetings status filter (d53cc9b, OP-17199) landed on
devafter the AGILE-432 branch was cut fromrelease/17.9, so the two only met in the release merge, which raises no PR checks.What approach did you choose and why?
An added row without a value is marked
data-filter-pending. The controller cancelsturbo:before-morph-elementfor marked rows, so a re-render leaves them alone, operator, draft value and active picker included, and cancelsturbo:before-morph-attributefordisabledon their add-filter option. The marker goes once the row is submitted with a value or removed. Onconnect()a marker already in the DOM is stale (Turbo snapshots the page before the old controller disconnects): the row is hidden again and its controls reset to their server defaults, since the snapshot also keeps whatever the user had typed. So Back/Forward neither shows nor later submits an unsent draft. The request and the URL are untouched.#25512 serializes the row as
status_id = ""instead. The Meetings query rejects the blank value and returns nothing until a value is picked, an unsent draft lands in URL and history (which the AGILE-375 design rules out), and it adds a second filter memory next tosentFiltersthat OP-20194 and AGILE-375 both rewrite.Empty date ranges (
"-"as rendered by the picker, or"") now parse as no value as well, so a fresh "Dates interval" row is kept the same way. One-sided ranges are submitted as before.AI involvement
Collaborative – AI generated a substantial part of the code; I reviewed and understand every line.
Merge checklist