Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Elastic.ApiExplorer/Export/ApiSearchWords.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,6 @@ .. path
.Select(word => word.ToLowerInvariant())
.Distinct(StringComparer.Ordinal)
];

internal static string[] OfOperationId(string operationId) => Of(operationId, string.Empty);
}
5 changes: 4 additions & 1 deletion src/Elastic.ApiExplorer/Export/OpenApiDocumentExporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,10 @@ public IEnumerable<DocumentationDocument> ConvertToDocuments(OpenApiDocument ope
Method = method,
Path = path.Key,
OperationId = operationId,
Deprecated = operation.Value.Deprecated
Deprecated = operation.Value.Deprecated,
Words = ApiSearchWords.Of(operationId, path.Key),
Group = groupParent?.Title ?? "",
IdWords = ApiSearchWords.OfOperationId(operationId).Length
};

yield return new DocumentationDocument
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,28 @@ public record ApiOperationMetadata
[JsonPropertyName("operation_id")]
public required string OperationId { get; init; }

/// <summary>
/// Plain lowercase words of the operation id and its literal path segments, e.g. <c>cluster health</c>.
/// A typed <c>cluster</c> or <c>esql</c> names an operation by these words, which otherwise live only
/// concatenated into <c>search_title</c>.
/// </summary>
[Text]
[JsonPropertyName("words")]
public string[] Words { get; init; } = [];

/// <summary>Title of the operation's group as the explorer shows it, e.g. <c>ES|QL</c>. Empty without a group.</summary>
[Text]
[JsonPropertyName("group")]
public string Group { get; init; } = "";

/// <summary>
/// Number of words in the operation id alone, e.g. 2 for <c>cluster.health</c>. Mapped as
/// <c>rank_feature</c> with negative score impact. Fewer id words is a more specific operation,
/// so ties break toward <c>esql-query</c> over <c>esql-async-query-stop</c>.
/// </summary>
[JsonPropertyName("id_words")]
public int IdWords { get; init; }

/// <summary>Whether the spec marks the operation deprecated.</summary>
[Boolean]
[JsonPropertyName("deprecated")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,24 @@ public static MappingsBuilder<DocumentationDocument> AddDocumentationMappings(th
.AddProperty("applies_to.sub_type", f => f.Keyword().Normalizer(SharedMappingConfig.KeywordNormalizer))
.AddProperty("applies_to.lifecycle", f => f.Keyword().Normalizer(SharedMappingConfig.KeywordNormalizer))
.AddProperty("applies_to.version", f => f.Version())
.AddProperty("api.properties.path", f => f.Keyword().MultiField("match", mf => mf.Text()));
.AddProperty("api.properties.path", f => f.Keyword().MultiField("match", mf => mf.Text()))
.AddProperty(
"api.words",
f => f
.Text()
.Analyzer(SharedMappingConfig.SynonymsFixedAnalyzer)
.SearchAnalyzer(SharedMappingConfig.SynonymsAnalyzer)
.MultiField(
"completion",
mf => mf
.SearchAsYouType()
.Analyzer(SharedMappingConfig.SynonymsFixedAnalyzer)
.SearchAnalyzer(SharedMappingConfig.SynonymsAnalyzer)
)
)
.AddProperty(
"api.group",
f => f.Text().Analyzer(SharedMappingConfig.SynonymsFixedAnalyzer).SearchAnalyzer(SharedMappingConfig.SynonymsAnalyzer)
)
.AddProperty("api.id_words", f => f.RankFeature().PositiveScoreImpact(false));
}
7 changes: 7 additions & 0 deletions tests/Elastic.ApiExplorer.Tests/ApiSearchWordsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,11 @@ public class ApiSearchWordsTests
[Arguments("_bulk", "/_bulk", "bulk")]
public void Of_OperationIdAndPath_ReturnsLowercaseDistinctWords(string operationId, string path, string words) =>
string.Join(' ', ApiSearchWords.Of(operationId, path)).Should().Be(words);

[Test]
[Arguments("cluster.health", "cluster health")]
[Arguments("esql-async-query-stop", "esql async query stop")]
[Arguments("_bulk", "bulk")]
public void OfOperationId_OperationId_ReturnsIdWordsOnly(string operationId, string words) =>
string.Join(' ', ApiSearchWords.OfOperationId(operationId)).Should().Be(words);
}
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,15 @@ public async Task ConvertToDocuments_TaggedOperations_IndexGroupAndWordsInSearch
o => o.WithStrictOrdering()
);
flush.Api!.Deprecated.Should().BeFalse();
flush.Api.Words.Should().Equal("ml", "flush", "job", "anomaly", "detectors");
flush.Api.Group.Should().Be("Machine learning anomaly detection");
flush.Api.IdWords.Should().Be(3);
docs["ml-close-job"].Api!.Deprecated.Should().BeTrue();
var bulk = docs["_bulk"];
bulk.SearchTitle.Should().Be("Bulk index or delete documents - Elasticsearch API - _bulk - PUT /_bulk - bulk");
bulk.Api!.Words.Should().Equal("bulk");
bulk.Api.Group.Should().Be("");
bulk.Api.IdWords.Should().Be(1);
bulk
.Parents
.Should()
Expand Down
50 changes: 43 additions & 7 deletions tests/Elastic.Documentation.Api.Tests/ApiPropertySearchTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@
.Should()
.Be("knn", "the endpoint is required, so the children lift is the reading's only should");
readings.EnumerateArray().Should().AllSatisfy(r => r.GetProperty("bool").TryGetProperty("_name", out _).Should().BeTrue());
var names = System

Check warning on line 196 in tests/Elastic.Documentation.Api.Tests/ApiPropertySearchTests.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Use 'GeneratedRegexAttribute' to generate the regular expression implementation at compile-time. (https://learn.microsoft.com/dotnet/fundamentals/syslib-diagnostics/syslib1045)

Check warning on line 196 in tests/Elastic.Documentation.Api.Tests/ApiPropertySearchTests.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Use 'GeneratedRegexAttribute' to generate the regular expression implementation at compile-time. (https://learn.microsoft.com/dotnet/fundamentals/syslib-diagnostics/syslib1045)

Check warning on line 196 in tests/Elastic.Documentation.Api.Tests/ApiPropertySearchTests.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest)

Use 'GeneratedRegexAttribute' to generate the regular expression implementation at compile-time. (https://learn.microsoft.com/dotnet/fundamentals/syslib-diagnostics/syslib1045)

Check warning on line 196 in tests/Elastic.Documentation.Api.Tests/ApiPropertySearchTests.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest)

Use 'GeneratedRegexAttribute' to generate the regular expression implementation at compile-time. (https://learn.microsoft.com/dotnet/fundamentals/syslib-diagnostics/syslib1045)

Check warning on line 196 in tests/Elastic.Documentation.Api.Tests/ApiPropertySearchTests.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

Use 'GeneratedRegexAttribute' to generate the regular expression implementation at compile-time. (https://learn.microsoft.com/dotnet/fundamentals/syslib-diagnostics/syslib1045)

Check warning on line 196 in tests/Elastic.Documentation.Api.Tests/ApiPropertySearchTests.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

Use 'GeneratedRegexAttribute' to generate the regular expression implementation at compile-time. (https://learn.microsoft.com/dotnet/fundamentals/syslib-diagnostics/syslib1045)
.Text
.RegularExpressions
.Regex
Expand Down Expand Up @@ -303,17 +303,44 @@
[Test]
[Arguments(false)]
[Arguments(true)]
public void Mapping_ApiDeprecated_IsBooleanWithoutGroup(bool semantic)
public void Mapping_ApiDeprecated_IsBoolean(bool semantic)
{
var context = semantic
? DocumentationMappingContext.DocumentationDocumentSemantic.CreateContext(type: "assembler", env: "test")
: DocumentationMappingContext.DocumentationDocument.CreateContext(type: "assembler", env: "test");
using var json = JsonDocument.Parse(context.GetMappingsJson());
var api = json.RootElement.GetProperty("properties").GetProperty("api").GetProperty("properties");
api.TryGetProperty("group", out _).Should().BeFalse();
var api = ApiMapping(semantic);
api.GetProperty("deprecated").GetProperty("type").GetString().Should().Be("boolean");
}

[Test]
[Arguments(false)]
[Arguments(true)]
public void Mapping_ApiWordsAndGroup_AreSynonymText(bool semantic)
{
var api = ApiMapping(semantic);
var words = api.GetProperty("words");
words.GetProperty("type").GetString().Should().Be("text");
words.GetProperty("analyzer").GetString().Should().Be("synonyms_fixed_analyzer");
words.GetProperty("search_analyzer").GetString().Should().Be("synonyms_analyzer");
var completion = words.GetProperty("fields").GetProperty("completion");
completion.GetProperty("type").GetString().Should().Be("search_as_you_type");
completion.GetProperty("analyzer").GetString().Should().Be("synonyms_fixed_analyzer");
completion.GetProperty("search_analyzer").GetString().Should().Be("synonyms_analyzer");
completion.TryGetProperty("index_options", out _).Should().BeFalse();

var group = api.GetProperty("group");
group.GetProperty("type").GetString().Should().Be("text");
group.GetProperty("analyzer").GetString().Should().Be("synonyms_fixed_analyzer");
group.GetProperty("search_analyzer").GetString().Should().Be("synonyms_analyzer");
}

[Test]
[Arguments(false)]
[Arguments(true)]
public void Mapping_ApiIdWords_IsRankFeatureWithNegativeImpact(bool semantic)
{
var idWords = ApiMapping(semantic).GetProperty("id_words");
idWords.GetProperty("type").GetString().Should().Be("rank_feature");
idWords.GetProperty("positive_score_impact").GetBoolean().Should().BeFalse();
}

[Test]
[Arguments(false)]
[Arguments(true)]
Expand Down Expand Up @@ -768,4 +795,13 @@

result.Properties.Should().ContainSingle().Which.Anchor.Should().Be("req-track_total_hits");
}

private static JsonElement ApiMapping(bool semantic)
{
var context = semantic
? DocumentationMappingContext.DocumentationDocumentSemantic.CreateContext(type: "assembler", env: "test")
: DocumentationMappingContext.DocumentationDocument.CreateContext(type: "assembler", env: "test");
using var json = JsonDocument.Parse(context.GetMappingsJson());
return json.RootElement.GetProperty("properties").GetProperty("api").GetProperty("properties").Clone();
}
}
Loading