fix(search-modal): count NoResults per settled query, not per keystroke - #13114
Merged
mekarpeles merged 1 commit intoJul 3, 2026
Merged
Conversation
The NoResults analytics event fired on every empty autocomplete response, including the transient empties a query passes through while being typed and every filter re-toggle, with no per-session dedupe. That inflated the count far above the number of searches that actually ended with nothing. Defer the event behind a short idle window (superseded by the next keystroke's fetch) so only a query the patron stops on is counted, and dedupe repeat settles of the same (query+filters) key per modal open.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #
fix β Corrects the
NoResultsanalytics event on the new search modal, which was massively over-counting.What's the problem, in plain terms?
We recently added tracking to the search modal to learn how people use it. One of the events,
NoResults, is meant to answer: "How often does someone search and find nothing?" Early numbers showed it firing far more than expected β roughly as often as the modal is even opened.That number is misleading, because of when the event fired. The modal shows results as you type. The old code sent a
NoResultsevent every single time the results area happened to be empty β and there are lots of harmless, temporary moments where that's true but the person is perfectly happy:chamber of secrets, you briefly pass throughcham,chamber o,chamber of secβ¦ and one of those in-between fragments might momentarily match nothing before your full query matches plenty. The old code counted each of those blips as a "no results" β even though your search ultimately succeeded.NoResultsevents in a single sitting.So
NoResultswasn't measuring "searches that ended with nothing" β it was measuring "empty split-seconds," which is a much bigger, much noisier number. It can't be meaningfully compared against the other events (likeOpen, which fires once per session).How is it fixed?
The event now only counts a search the person has actually settled on:
The result:
NoResultsnow reflects genuine dead-ends β a person searched, stopped, and truly saw nothing β which is the question we wanted answered. The event's label (which filters were active) and all the other events are unchanged, so no dashboards break; theNoResultscount simply becomes trustworthy (and will drop substantially).Technical
Client-side only β
openlibrary/plugins/openlibrary/js/search-modal/SearchModal.js. No server, template, or API changes.The empty-results branch of
_fetchResults()no longer calls_track('NoResults', β¦)synchronously. It now calls a new_scheduleNoResultsTrack(fetchKey), which:setTimeout(~1.2s). Because every keystroke starts a new fetch and reassigns_activeFetchKey, the deferred callback re-checksthis._activeFetchKey === fetchKeyand bails if the query has moved on. Net effect: only the final settled query in a typing burst is counted, not each partial string.Set(_noResultsTracked) keyed onfetchKeyβ which is the existing_buildSearchJsonUrl(trimmed), so it already encodes query + availability + language. Repeat settles of the same (query+filters) fire once. The Set is cleared in_openModal, so each fresh open re-counts.The pending timer is cleared everywhere a search is abandoned so no phantom event fires:
_resetResults()(covers backspacing below the min length, the clear button, and the fetch-error path),_onDialogClosed(), anddisconnectedCallback().No false positives were possible before (the
_results.length === 0check is honest), so this is purely a de-noising/de-duplication change to the event cadence β the label taxonomy (unfiltered/availability/language/availability+language) is unchanged.Testing
The event is only observable via Matomo, so verify in the browser dev console / network tab (filter for the analytics
trackEventcall, categorySearchModal, actionNoResults):asdkjfhaskjdfh), typing steadily. Before: severalNoResultsevents fire (one per pause). After: a singleNoResultsfires ~1.2s after you stop typing.NoResultseven though the final query has hits. After: noNoResultsfires β the successful query supersedes the pending event.NoResultsfires each toggle. After: at most one per distinct (query+filters) combination.NoResultsfires for the abandoned search.Open,ResultClick, andSeeAllResultsstill fire exactly as before.Screenshot
N/A β no visible UI change; this only alters when an analytics event is sent.
Stakeholders