Skip to content

[TASK] Content Drive: add a Title / All Content scope selector to the search box #37479

Description

@zJaaal

Description

The Content Drive search box has one behavior: every term runs a global, all-content search. There is no way to say "I only care about the title". Authors who know the name of what they are looking for get back everything whose body, blocks or metadata happens to contain the word, and the row they wanted is buried.

Add a scope selector to the right of the search input, inside the same rounded box, with two options:

Option Behavior
Title Match against the contentlet title (and folder/file names) only
All Content Current behavior: title plus every indexed field

The control is a small dropdown anchored to the search box, showing the active scope as its label and a check next to the selected option:

┌──────────────────────────────────────────────┬───────────┐
│  🔍  Search by title                         │  Title  ▲ │
└──────────────────────────────────────────────┴───────────┘
                                        ┌──────────────────┐
                                        │  Title        ✓  │
                                        │  All Content     │
                                        └──────────────────┘

The placeholder follows the scope ("Search by title" vs "Search all content"), so the box says what it will do before the user types.

This is scoped to Content Drive search (POST /api/v1/drive/search). It is not a rewrite of the search strategy, it is a switch that selects between the existing all-content query and a narrower title-only one.

Why this also matters for performance

The all-content query is the expensive one, and today every keystroke pays for it. GlobalSearchAttributeStrategy builds a mandatory gate of:

+(catchall:<value>*^10 OR title_dotraw:*<value>*^2)

Two costs sit in that single line:

  • catchall aggregates every field of the document (body, Story Block content, metadata). A common word matches a large slice of the index, so the term is cheap to look up and enormously expensive in what it returns.
  • title_dotraw:*<value>* is a leading wildcard, which forces a scan across every distinct raw title in the index rather than a prefix seek.

In the drive path the ES hit set is not the end of the work. BrowserAPIImpl feeds those matches through DB hydration and per-chunk permission filtering (BROWSER_CONTENT_CHUNK_SIZE, default 900) before a page can be returned, so a broad match multiplies DB round trips and permission checks, not just ES time. A title-only query that drops the catchall clause and gates on a title prefix (+title:<value>*, keeping title_dotraw as a boost or fallback rather than as the gate) shrinks the candidate set at the source, and everything downstream gets cheaper with it.

So the scope selector is worth building twice over: it is the result quality users are asking for, and it gives them a fast path that avoids the most expensive clause in the query.

Where to point

Frontend

File What changes
core-web/libs/ui/src/lib/components/dot-search-input/dot-search-input.component.ts / .html Shared presentational box. Add the scope dropdown as an optional input (plus a scopeChange output) so the AssetPicker toolbar, which reuses this component, is untouched unless it opts in.
core-web/libs/portlets/dot-content-drive/portlet/src/lib/components/dot-content-drive-toolbar/components/dot-content-drive-search-input/dot-content-drive-search-input.component.ts Store adapter. Opts the dropdown in, binds the current scope, forwards changes.
.../store/dot-content-drive.store.tssetGlobalSearch (~line 228) Carry the scope alongside the title filter; resetting pagination the same way.
.../store/dot-content-drive.store.ts$request computed (~line 119) Send the scope in the request payload next to filters.text.
.../lib/utils/functions.ts Filter encode/decode, so the scope survives a URL restore and Back/Forward like every other filter.
core-web/libs/dotcms-models/src/lib/dot-content-drive.model.ts DotContentDriveSearchRequest / filters model.

Backend

File What changes
dotCMS/src/main/java/com/dotcms/rest/api/v1/drive/AbstractQueryFilters.java Natural home for the new field, next to text() and filterFolders(). Default it to the current behavior so existing callers are unaffected.
.../v1/drive/ContentDriveHelper.java (~lines 179–183) Where filters.text() is wired into BrowserQuery.withFilter() and useElasticsearchFiltering(true) is flipped on. The scope rides along here.
dotCMS/src/main/java/com/dotcms/browser/BrowserQuery.java Carry the scope through to the API.
dotCMS/src/main/java/com/dotcms/browser/BrowserAPIImpl.javabuildBaseESQuery (~line 1193) The branch point: today it unconditionally calls GlobalSearchAttributeStrategy. Title mode selects the narrower query instead. Note buildPureESQuery (~line 663) still holds an older title:'*x*'^5 OR catchall:*x*^3 OR fileName:*x*^2 shape — worth confirming whether the drive reaches it.
.../v1/content/search/strategies/GlobalSearchAttributeStrategy.java The current all-content query. Leave it as the All Content path; add a title-scoped sibling rather than branching inside it.

Related history: #36688 replaced the old broad catchall:*kw* leading wildcard with the current strategy for exactly these reasons, and #36814 tracks search performance at scale.

Acceptance Criteria

  • The Content Drive search box shows a scope dropdown with Title and All Content, the active scope as its label, and a check mark on the selected option
  • Selecting a scope re-runs the current search immediately, with pagination reset to page 1
  • The placeholder reflects the active scope
  • Title mode returns only rows whose title (or folder/file name) matches; a term that appears only in a body or Story Block does not match
  • All Content mode returns exactly what the search returns today (no regression)
  • The Title-mode query contains no catchall clause and no leading-wildcard term as its mandatory gate
  • The scope is encoded in the URL filters and survives reload and browser Back/Forward
  • The backend field defaults to the current all-content behavior, so a request that omits it is unchanged
  • The AssetPicker's search box is unaffected unless it explicitly opts in
  • Postman coverage for POST /api/v1/drive/search in both scopes, including the omitted-field default
  • Jest coverage for the dropdown, the store wiring and the URL round-trip
  • A before/after latency comparison on a large dataset is recorded on the issue

Open decisions

  1. Default scope. The mock shows Title selected with a "Search by title" placeholder. Defaulting to Title gives everyone the fast path, but silently changes behavior for existing users. Defaulting to All Content is the no-regression choice. Needs a call before implementation.
  2. File names in Title mode. For file assets, the file name is usually what the user means by "title". Should Title mode also cover fileName / metadata.name, or stay strictly on title?
  3. Stickiness. Is the scope per-search (resets on reload beyond the URL), or remembered per user?
  4. Sorting. Results are currently sorted by score descending whenever a term is present. Confirm that holds in Title mode.

Additional Context

Behavior surfaced from the attached mock. Frontend and backend both change; the frontend cannot narrow the query on its own because the Lucene string is built server-side in BrowserAPIImpl.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions