Add search passthrough to the advanced-search work items finder

What does this MR do and why?

ee/lib/ee/search/advanced_finders/work_items_finder.rb hardcoded the Elasticsearch query string to '*', a match-all. It now passes params[:search] through when present and falls back to '*' when not.

QUERY is renamed DEFAULT_QUERY to say what it actually is.

:search becomes an allowed Elasticsearch filter, behind a new feature flag (see below). Without it in the allowed list, the supported-fields check fails and the query falls back to the Postgres finder.

Feature flag

New flag work_items_search_term_es_integration, type gitlab_com_derisk, default_enabled: false, milestone 19.6.

Why it exists, raised by @rkumar555 in review: reaching this finder at all is gated by glql_es_integration, which is an ops flag with default_enabled: true and has been on in production since April 2026. WorkItemsResolver#choose_finder checks glql_es_integration OR work_items_list_es_integration, so that gate is already open. Without a flag of its own, merging this would move every GLQL work-item search with a term from Postgres to Elasticsearch in one step, on GitLab.com and every self-managed instance with advanced search, and the only way back would be disabling glql_es_integration entirely.

The flag gates only the allowed-filters entry. With it off, use_elasticsearch_finder? returns false and the query stays on Postgres exactly as today.

Because a feature flag can't be evaluated when a class-level constant is defined, :search is appended by an allowed_es_filters method rather than living in the frozen ALLOWED_ES_FILTERS array.

Behaviour change

Global Search own ee/lib/ee/search, so the switch itself is theirs to sign off.

How Elasticsearch and Postgres results differ
  1. Fields searched. Postgres covers title and description only (Issuable::SEARCHABLE_FIELDS). Elasticsearch covers iid^50 title^2 description, and WorkItemQueryBuilder::IID_REGEX turns a term like #1234 into an exact iid lookup. So search = "#1234" finds that work item on Elasticsearch and nothing on Postgres.
  2. Query syntax in the term, raised by @rkumar555. Search::Elastic::Queries.by_full_text sends any term matching ADVANCED_QUERY_SYNTAX_REGEX to simple_query_string. That regex is /[*"~#!()|]/ plus /(?:^|\s)[+\-]/, so search = "memory -leak" excludes leak, and *, quotes, | and the rest act as operators. Postgres treats all of them as literal text. Literal matching on Elasticsearch would need the term escaped.
  3. Two Postgres paths. IssuableFinder#by_search picks pg_full_text_search (tsvector, title weight A, description weight B) only when the term matches FULL_TEXT_SEARCH_TERM_REGEX, which covers Latin ranges. Anything else, such as CJK or Cyrillic, falls through to fuzzy_search, i.e. ILIKE. Elasticsearch applies its analyzer uniformly.
  4. Missing search data. pg_full_text_search inner-joins issue_search_data. A work item with no row there, which PgFullTextSearchable notes happens when the tsvector is too long, is excluded from Postgres results entirely while Elasticsearch still finds it.
  5. Ranking. Field boosts and relevance scoring on Elasticsearch versus tsvector A and B weights on Postgres.

Review feedback addressed

Thanks @rkumar555 for the review.

  • Blocking: added the rollout flag, as above.
  • Added #use_elasticsearch_finder? specs covering the routing itself: a term returns true, a term with the flag off returns false, and a term combined with in: returns false because in is not an allowed Elasticsearch filter.
  • Added the query-syntax difference to the behaviour notes above.
  • Moved the stale comment: DEFAULT_QUERY now describes only the match-all fallback, and the keyword behaviour is documented on search_query.
  • Removed an incorrect bullet from this description claiming the MR bumps gitlab_query_language to ~> 0.42.0. Master already pins 0.42.0, bumped independently, and this diff doesn't touch the Gemfile.

How to set up and validate locally

  1. Elasticsearch enabled in the GDK — elasticsearch.enabled: true in gdk.yml, service running, work item index populated.
  2. Enable the flag in a Rails console: Feature.enable(:work_items_search_term_es_integration)
  3. Run the spec: bundle exec rspec ee/spec/lib/ee/search/advanced_finders/work_items_finder_spec.rb
  4. To confirm the gem compiles the filter: bundle exec ruby -e 'require "gitlab_query_language"; puts ::Glql.compile(%q{type = Issue and search = "memory leak"}, { "project" => "gitlab-org/gitlab", "fields" => "id, title" })["output"]' — the generated query should carry search: "memory leak". Note Glql.compile takes and returns a Hash, not JSON.

Testing

New routing specs for use_elasticsearch_finder?, plus the three #execute contexts for the passthrough: term supplied, blank term, no term. RuboCop clean. Verified against the released 0.42.0 gem that search compiles to the search: argument on work items, merge requests and projects.

Related to #631271

🤖 Generated with Claude Code

Edited by Siddharth Dungarwal

Merge request reports

Loading
Loading