Skip to content

Avoid obsolete filter syncing [2 days]#7135

Closed
mstahv wants to merge 5 commits into
vaadin:mainfrom
mstahv:bugix/avoid-many-obsolete-xhrs
Closed

Avoid obsolete filter syncing [2 days]#7135
mstahv wants to merge 5 commits into
vaadin:mainfrom
mstahv:bugix/avoid-many-obsolete-xhrs

Conversation

@mstahv

@mstahv mstahv commented Feb 21, 2025

Copy link
Copy Markdown
Member

That appears to be obsolete as the filter is anyways sent to the @ClientCallable method when the filtering should actually be done. After this change there is a lot less XHRs/server-visits happening aka reduces "chattiness" (although there still is some that I don't really know what they are related to (opened changed and some "confirm" requests).

Opening as draft as somebody involved in the current implementation really should look into this (and couldn't run the tests locally).

See also #6863 and #7133

@sonarqubecloud

Copy link
Copy Markdown

@mstahv

mstahv commented Feb 21, 2025

Copy link
Copy Markdown
Member Author

One failed test. Which is kind of more a timing issue. In a mode when the popup is configured NOT to be automatically openend, the previous test triggered the filtering more eagerly. Don't know what would be the "correct" behaviour if #6863 gets fixex.

Could be fixed either by adding a wait/timeout to the IT or to keep syncing the filter also via event, but make that lazy. Or changing client side so taht by making open event also contain the filter or so that connector calls some method instead (like it does with regular filtering).

@yuriy-fix

Copy link
Copy Markdown
Contributor

Let's consider combining this PR with #7133 so that the context is available in one place. We should also proceed with adding tests.

@mstahv, would you be able to add tests, or would you like us to handle them? Also, please specify your compilation issues so we can help resolve them.

It might be potentially a small breaking change, since addPropertyChangeListener for filter will stop working after removing @Synchronize

@mstahv

mstahv commented Mar 4, 2025

Copy link
Copy Markdown
Member Author

I'd keep these separatly. This is more like a bugfix or performance improveiment and the other is technically "a feature", so it will probably be require a minor version (and now that 24.7 was just "closed", it will take a looong time).

Would be great if you can tackle the possible new tests. As I don't work with selenium daily, I don't have good ideas to how to automatically assert extra requests are made.

@CLAassistant

CLAassistant commented Oct 28, 2025

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

That appears to be obsolete as the filter is anyways sent to the @ClientCallable method when the filtering
should actually be done.
@vursen
vursen force-pushed the bugix/avoid-many-obsolete-xhrs branch from a125906 to a055bc0 Compare January 12, 2026 16:40
That appears to be obsolete as the filter is anyways sent to the @ClientCallable method when the filtering
should actually be done.
@vursen
vursen force-pushed the bugix/avoid-many-obsolete-xhrs branch from a055bc0 to 29d5749 Compare January 12, 2026 16:52

@vursen vursen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

There are failing integration tests with this change. Also, the actual filter logic seems to be more complex than what this PR assumes e.g. the filter value may need to be reset in certain scenarios, see ComboBoxDataController.

@mstahv

mstahv commented Jan 13, 2026

Copy link
Copy Markdown
Member Author

@vursen Are you going to work on this further?

@vursen

vursen commented Jan 13, 2026

Copy link
Copy Markdown
Contributor

Are you going to work on this further?

No, I'm tied up with other tasks right now.

Works locally, but maybe now debouncing causes issue in CI
…ow-components into bugix/avoid-many-obsolete-xhrs
@mstahv

mstahv commented Jan 13, 2026

Copy link
Copy Markdown
Member Author

Checked in the project now builds on my local workstataion and tests pass there. Pushed a blind wait that might happen now as the filtering is debounced. Let's see if that helps.

Please explain your concern about ComboBoxDataController. That looks very complex class indeed, but didn't easily see how this could affect that.

@sonarqubecloud

Copy link
Copy Markdown

@mstahv
mstahv marked this pull request as ready for review January 13, 2026 13:16
@vursen

vursen commented Jan 13, 2026

Copy link
Copy Markdown
Contributor

Please explain your concern about ComboBoxDataController. That looks very complex class indeed, but didn't easily see how this could affect that.

For example, consider this scenario:

  1. comboBox.setItems(yourDataProvider)
  2. comboBox.getLazyDataView().getItems() returns all items (expected)
  3. Open, filter, then close the combo box
  4. comboBox.setItems(yourDataProvider)
  5. comboBox.getLazyDataView().getItems() returns filtered items because the filter wasn't cleared (regression)

It would help if ComboBoxDataController was the only place handling filters.

@mstahv

mstahv commented Jan 15, 2026

Copy link
Copy Markdown
Member Author

Do you have that available somewhere as regression test? I can't see any failing. Or the original thing that the developer/user is trying to do in that scenario? To me it sounds bit odd the filter is ever taken into account with the dataview (although I haven't really understood what is the purpose of the dataview in general either).

The fix is probably easy, but maybe there is some more fundamental isseu in the current implementation 🤷‍♂️

@yuriy-fix yuriy-fix changed the title Avoid obsolete filter syncing Avoid obsolete filter syncing [2 days] Feb 3, 2026
@vursen

vursen commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

/code-review

@vaadin-review-bot vaadin-review-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed the changes — left 3 comments and collected 2 findings below that could not be attached to the diff.

Finding
⚠️ First open of a non-auto-open lazy combo box fetches with a stale/null filter because getFilter() no longer reflects the typed text.
⚠️ setDataProvider seeds the initial filter from getFilter(), which is stale in client-side filtering and after the popup closes.
🧹 Test hand-builds a WebDriverWait instead of the inherited waitUntil helper used throughout this module.
👀 lastKnownFilter is a hand-maintained shadow of the filter that must be poked at every client entry point, and already misses paths on day one.
🧹 setFilter() is left half-finished: a now-dead filter property write, stale javadoc, and a dated speculative comment.

⚠️ First open of a non-auto-open lazy combo box fetches with a stale/null filter because getFilter() no longer reflects the typed text.
A lazy ComboBox with setAutoOpen(false) (the reviewed LazyComboBoxFilterPage): the user types "1", then opens the popup. The opened change runs executeRegistration, which calls setViewportRange(0, pageSize, comboBox.getFilter()). With @Synchronize removed, getFilter() returns lastKnownFilter, which is still null here — the client only sends the filter through the debounced setViewportRange RPC, which has not fired yet. So the first server query runs with the wrong filter ("Filter: <undefined> Count: 99") and only a later round-trip corrects it to "Filter: 1 Count: 10".

This is a behavior regression: before the change the first query already carried the filter. It is exactly why this PR had to weaken the IT from an immediate Assert.assertTrue to a 2-second wait.until. It also adds a wrong initial fetch rather than reducing round-trips. The fix should feed the current client filter into this path (e.g. keep syncing it, or defer the fetch until the client's filtered request arrives).
ComboBoxDataController.java:609 · correctness · confirmed

⚠️ setDataProvider seeds the initial filter from getFilter(), which is stale in client-side filtering and after the popup closes.
lastKnownFilter is only written by the deprecated setFilter() and by the setViewportRange RPC, and it is never cleared. In client-side filtering mode (item count fits one page, _clientSideFilter true) the connector filters locally and returns without calling setViewportRange (comboBoxConnector.js:70-85), so lastKnownFilter never tracks the typed text. Closing the popup also resets the controller's lastFilter but leaves lastKnownFilter holding the old value.

If the app then calls setItems(...)/setDataProvider(...), line 547 seeds the new DataCommunicator with convertOrNull.apply(comboBox.getFilter()) — a filter that no longer matches what the user sees, so the initial page is wrong until the next client request corrects it. Before the change the synced filter property always held the current value. Confirming this needs a run with a swapped data provider while a client-side filter is active.
ComboBoxDataController.java:547 · correctness · plausible

Assert.assertTrue(query.getText().contains("Filter: 1"));
Assert.assertTrue(query.getText().contains("Count: 10"));

WebDriverWait wait = new WebDriverWait(driver,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🧹 Test hand-builds a WebDriverWait instead of the inherited waitUntil helper used throughout this module.

AbstractComponentIT (via com.vaadin.flow.testutil.AbstractComponentIT) already provides waitUntil(ExpectedCondition), and it is the established pattern across this IT module (ComboBoxClearIT, DataProviderIT, ClientSideFilterIT, and others). The new code instead constructs new WebDriverWait(driver, Duration.of(2, ChronoUnit.SECONDS)) and pulls in three extra imports (Duration, ChronoUnit, WebDriverWait).

The cost is inconsistent timeout/polling behavior and boilerplate the shared helper exists to avoid. Replace with waitUntil(ExpectedConditions.textToBePresentInElement(query, "Filter: 1 Count: 10")).

LazyComboBoxFilterIT.java:43 · reuse · confirmed

HasValidationProperties, HasValidator<TValue>, HasPlaceholder {
private static final int DEFAULT_FILTER_TIMEOUT = 500;

private String lastKnownFilter;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

👀 lastKnownFilter is a hand-maintained shadow of the filter that must be poked at every client entry point, and already misses paths on day one.

The removed @Synchronize was a single source of truth kept current by Flow on every filter-changed event. The replacement is a field updated in only two sites (setFilter() and the setViewportRange RPC) and never cleared — so it already fails to track the client-side-filter path and the popup-close path (see the two correctness findings). ComboBoxDataController also already tracks the requested filter in its own lastFilter field.

The cost is fragility: any future client path that changes the filter, or any new server reader of getFilter(), must remember to update this private field, with nothing enforcing it. A deeper fix would adjust when the client sends the filter (dropping the redundant per-keystroke sync) rather than adding a parallel field that drifts out of sync.

ComboBoxBase.java:102 · altitude · plausible

* the String value to set
*/
@Deprecated
protected void setFilter(String filter) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🧹 setFilter() is left half-finished: a now-dead filter property write, stale javadoc, and a dated speculative comment.

With @Synchronize removed and getFilter() reading lastKnownFilter, nothing reads the filter element property anymore (no getProperty("filter") remains), so getElement().setProperty("filter", ...) on line 368 is an inert DOM write. The javadoc still says Setter is only required to allow using @Synchronize — the very annotation this PR deletes — and the body carries // MT 20250-02-21 I assume client side never uses this, probably the whole method is obsolete (note the typo year 20250).

The cost is that a future maintainer cannot tell what the method is for or whether it is safe to delete: the javadoc, the inline comment, and the @Deprecated (which has no @deprecated tag or replacement) give three conflicting signals. Resolve it here — confirm the client ignores the property and remove the method or the dead write, and replace the scratch comment with a real deprecation note.

ComboBoxBase.java:364 · simplification · confirmed

@yuriy-fix

Copy link
Copy Markdown
Contributor

We spent some time investigating the proposed change and identified several cases that are not currently covered. Addressing them would require significant additional work to complete the solution.

For now, we've decided not to proceed with the change and will close the PR. Please feel free to reopen it if you would like to address the review comments.

@yuriy-fix yuriy-fix closed this Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants