Repository navigation
Add API property search with direct links to matching fields - #4574
Conversation
Co-Authored-By: GPT-6 <noreply@anthropic.com>
Docs preview (local build)Handbook preview: https://docs-v3-preview.elastic.dev/elastic/docs-builder/pull/4574/ Sample changed pages: |
Main replaced Jest with Vitest in [#4588](#4588) while this branch was open, so `tsc` no longer knows the `jest` global and the `npm` CI job failed. The tests now use `vi.fn`, and the file-level `htmx.org` mock goes away because `setupTests.ts` already mocks it for every file. The property navigation test also looks for the "Explore APIs" placeholder that [#4565](#4565) introduced. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
"Jump to API" no longer says where Enter leads once a result box holds an operation and its matching fields. An operation row now says "Go to operation", a property row says "Go to property", and a docs page keeps "Jump to". The dropdown footer shows the label of the selected row, so the hint changes as the arrow keys move between an operation and its properties. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A footer label that changes with the selected row draws the eye on every arrow press. The footer now always says "Go to page", and a docs page row uses the same words so one action has one name. Operation and property rows keep "Go to operation" and "Go to property". Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The operation row insets its "Go to operation" badge 24px from the box edge while the property row insets "Go to property" 16px, so the two badges did not line up. The property row now uses the same right padding as the operation row. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
These tests hold the contract steady for a refactor. The result processor keeps the three best-scoring properties with distinct anchors in score order. The navigation service links a property to its anchor on the operation page. The document hash changes when a property is added or its path changes, through a pure `DocumentHash` split out of `AssignDocumentMetadata`. A property row shows its location only when the same path appears in two locations, and the mouse selects a property row unless the keyboard is driving. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Results are grouped once, in `groupSearchResults`, which numbers the operation and its properties in keyboard order and decides where a location chip is needed. The list only renders those groups, the dropdown loses a one-caller wrapper, both row kinds share one click telemetry path, and the Enter badge owns its own show-on-select rule. The match tint lives in one `--search-highlight-tint` token that the two result lists and the property flash all read. The anchor telemetry attribute joins its siblings in `semconv.ts`, and the modal search drops a `properties` field it never read. On the server, the query builder parses the input once and reads the nested field names from `QueryFieldNames`, the property walk is a single pre-order `SelectMany`, and plain-text descriptions are cached per spec so shared schemas are converted once per indexing run instead of once per operation. Verified against the same cluster: all 18 captured navigation-search responses are byte-identical before and after, and the headless run on localhost:4001 passes every functional check. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…sear-a6c56a6b # Conflicts: # src/Elastic.Documentation.Indexing/Exporters/Elasticsearch/ElasticsearchMarkdownExporter.Export.cs
| apiOperation, | ||
| new SchemaAnalyzer(openApiDocument), | ||
| new ParameterRowSource(builder, null, OperationEndpoint.Build(apiOperation, operation.Value.Description, [])) | ||
| ); |
There was a problem hiding this comment.
Severity: P3 (Low)
Pass the operation's supplemental documentation into property indexing instead of always supplying null. Rendering applies parameter and request-body description overrides, but these new search rows always retain the original spec descriptions, or remain blank when only the supplemental file documents the field. Editing an override also leaves the indexed property metadata unchanged, so search continues showing text that no longer matches the destination row.
Generated by Libra. React with 👍 or 👎 to give feedback on this comment.
There was a problem hiding this comment.
This is substantial enough to track separately. Created #4662 as a follow-up. Not blocking this PR.
A query such as `_search` matched an operation's path, but the result only showed the match buried in the body snippet after "Method: POST Path:". The index now stores each operation's routes with their methods, taken from the same endpoint rows the operation page renders, and the navigation search returns them. When a query word names a path, the result replaces the description with the primary route: the method the page leads with and the path with the match highlighted. An alternate route takes its place only when the primary one does not contain the word, so the hit is always explained without listing every variant. The autocomplete source filter now returns every required member of the api object, since a hit that carries any part of it must deserialize, and a test pins that rule. The mapping update sends the whole api object so the existing semantic index gains the field. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
| Routes = endpoint | ||
| .Rows | ||
| .Select( |
There was a problem hiding this comment.
Severity: P2 (Medium)
Populate Routes from the merged page endpoint rather than the individual spec operation. This endpoint is built with an empty sibling list, but navigation can collapse interchangeable operations onto one page. For a GET/POST /_search and /{index}/_search family without a description-level route listing, each indexed hit therefore carries only its own route and method, while the destination page leads with POST /{index}/_search and lists both routes. Search can consequently show a different primary route or omit a matching alternate route entirely.
Generated by Libra. React with 👍 or 👎 to give feedback on this comment.
There was a problem hiding this comment.
This is substantial enough to track separately. Created #4663 as a follow-up. Not blocking this PR.
Typing `/_search` showed `POST /{index}/_search`, the page's primary
route, which left the reader checking whether that was the route they
meant. A route whose whole path is a query word now wins, so
`/_search` and `_search` show `POST /_search`, while `search` or
`index` still show the primary route that contains them.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A combined query took its last word as the field and the rest as the endpoint, so `knn /_search` looked for a field named `/_search` and returned the operation without its `knn` rows, while `knn search` surfaced an unrelated operation whose fields contain "search". The field is now the last word that cannot be part of an endpoint, one that neither starts with a slash nor is an HTTP method, and the endpoint is everything else in order. `knn /_search`, `/_search knn` and `knn GET /_search` read the same. A query made only of endpoint words skips the field branch instead of searching for a field named after the path. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Without a slash or a method word nothing marks the endpoint in a two-word query, so `knn search` took `search` as the field and surfaced an unrelated operation. Such a query is now read both ways, and each reading requires its endpoint half to match the stored endpoint keys: `search` becomes `_search` and holds, `knn` becomes `_knn` and matches nothing, so only the reading that holds returns rows. `knn search`, `search knn`, `_search knn` and `knn _search` now all return the search operation with its `knn` rows. Each reading carries its own inner-hits name, because Elasticsearch rejects two nested queries that share one, and the result processor merges the groups. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A property row highlighted only the last query word, a leftover of the rule that read the last word as the field, so `knn search` marked nothing on the `knn` rows that the query now returns. The row now highlights every query word that can name a field, which excludes paths and HTTP method words, so `knn search` and `search knn` both mark `knn`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A property row's click reported the keyboard row index, which counts the property rows above it, so a page listed after an operation with three fields was reported at position 4. The position is now the result's rank among the results for the operation and for each of its properties, and the anchor keeps identifying the property row. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A query word was matched against the raw spec path, so `index` or `id`
matched `{index}` and `{id}` and a query such as `delete index` replaced
most descriptions with a route line even though it named no path. A
placeholder now stands as `*` when a path is compared, as the indexed
endpoint keys already do, so only a literal segment can match.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A method word was never allowed to be the field, so `options` found no property and `_bulk delete` looked for a field named `_bulk`, although both are real schema fields. A method word now marks the endpoint only when the next word starts with a slash, as in `GET /_search`, on both the query builder and the result row's highlighting. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The nested field query also ran in the unfiltered docs search for any single word, matching field names by prefix, so `s` or `index` lifted hundreds of API operations above the pages about them and inflated the type counts. The docs-wide search now tries only words that look like identifiers, containing `_` or `.`, and matches a field by its exact name; the prefix clause stays in the API-filtered lookup where the bare-word endpoint boost already lives for the same reason. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The endpoint half of a combined query tried the underscore alias first and, since `_deployments` always builds a query, never tried the literal `deployments`. With the endpoint required in a two-word query, `deployments status` could not reach the fields of `/deployments`. The endpoint half now accepts the path as typed or its alias. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Elasticsearch computes a nested query's inner hits for every returned document from that nested query alone, so a two-word query's rejected reading still attached its field rows: for `knn search` the reading that paired field `search` with endpoint `_knn` returned `search_*` rows on the search operation, which could take two of the three slots. Each reading is now a named query carrying its inner-hits name, and the processor keeps a group only when the hit matched that reading. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The exporter rendered every property description as an empty string, so two listings that differ only in their variant descriptions hashed alike and the second was folded into the first. The page renders the descriptions and keeps both, so its anchors had no index entries. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The guard looked at the whole query, so `elasticsearch index_name settings` passed it on the underscore and then tried the plain word `settings` as the field, lifting every operation with a `settings` field into the docs-wide results. The check now applies to the field word of each reading, which is what the docs describe. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Elasticsearch leaves matched_queries out of a hit when no named query matched it, so a hit the lexical branch alone matched looked the same as a hit from a query that named nothing, and kept the inner hits of every reading. Every reading is now named, so the processor can require the name and drop the rows of a reading the hit did not match. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The page and the index build the same property tree with different markdown renderers, and listing identity hashed the rendered HTML, so the two could disagree on which listings to share: `*Code it*` and `_Code it_` are one listing on the page and two in the index, leaving index entries whose anchors the page never renders. Identity now keys on the markdown both builders have, and the index renders nothing. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…dings Whether a reading must match its endpoint was taken from how many readings were left, so once the docs-wide filter dropped the plain reading of `bulk track_total_hits`, the one that remained had its endpoint demoted to a lift and admitted every operation with the field. The parse now records that two plain words require their endpoints, and the filter keeps that. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
API search finds parameter and schema field names and links directly to the matching row on an operation page.
Closes https://github.com/elastic/docs-eng-team/issues/967
Affects: Search, API reference, Site UI
Prompt summary: Make API fields discoverable through search while keeping operation and endpoint matches prominent. Show compact property rows with their JSON paths, clear page grouping, and yellow highlights that match the destination property.
Why
Readers who know a field name must find its operation and then locate the field manually. Operation text does not reliably identify nested schema fields or provide a link to their rendered anchors.
What
Searchable property metadata
Index rendered parameter, request, and response fields with their names, paths, types, descriptions, locations, and anchors. Indexing shares the page's property-tree construction so search links match rendered rows, including dictionaries and union variants, and two listings that differ only in their descriptions stay separate as they do on the page. Existing parameter text stays in the searchable operation body.
Operation and property matching
Match a field's own name or prefix in the API-filtered lookup, with qualified paths available for narrower searches. The docs-wide search tries only a word that looks like an identifier, containing
_or., and matches a field by its exact name, so plain words such asindexkeep the pages about them on top. Ancestor paths and descriptions provide context without admitting unrelated child rows. Direct operation and endpoint matches retain priority, and combined searches such as/_search knn,knn /_searchorknn searchshow the matching fields beneath their operations: a word with a slash, or an HTTP method word right before one, is the endpoint, and two plain words are tried in both orders with the endpoint required, so the index keeps the reading that holds. The endpoint half matches the path as typed or its underscore alias, and each reading is a named query, so a result carries only the field rows of the reading it matched.Routes for a path hit
When a query word names a path, such as
_searchor/_search, the result shows one route in place of the description: the method the page leads with and the path with the match highlighted. The route whose whole path is the query word wins, so/_searchshows/_search; otherwise the operation's primary route shows when it contains the word, and an alternate route only when the primary does not. A placeholder such as{index}never matches a query word. The index stores each operation's routes with their methods, taken from the same endpoint rows the operation page renders.Compact results and direct navigation
Use a consistent bordered box for each result, with compact property rows beneath its operation header. Arrow keys traverse operations and properties together, and Enter opens the selected destination. The Enter badge on a row names where it leads: "Go to operation", "Go to property", or "Go to page" for a docs page, and the dropdown footer keeps a static "Go to page" hint. Field navigation reveals collapsed content and flashes the destination name using the same yellow tint as search matches. A click reports the result's rank as its telemetry position, with the property anchor as a separate attribute.
Verify
The full C# unit suite, 779 frontend tests, type checking, and lint pass. Local browser checks verify 32px property rows, 12px text, yellow matches, consistent boxes, and destination flashes.
Known limitation: A combined query of three or more plain words, with no slash and no HTTP method word, takes its last word as the field. Two plain words are tried in both orders with the endpoint required, so
knn searchandsearch knnread the same.Risk: Indexing adds the nested
api.propertiesmapping to existing semantic indices. A code revert leaves this additive mapping in place. Existing documents receive property metadata during the next indexing run.Check on the preview
search,_search, and/_search knn. Check operation priority, compact grouped fields, yellow matches, and keyboard navigation to a field. For_search, each result should show one route line,POST /_searchon the search operation, in place of the description;searchshows the primaryPOST /{index}/_searchinstead.bulkoperation lookup.Property search checks require a search API and indexed metadata with this branch's property support.