diff --git a/docs/_docset.yml b/docs/_docset.yml index 16559027b..a31228d0b 100644 --- a/docs/_docset.yml +++ b/docs/_docset.yml @@ -13,9 +13,6 @@ subs: serverless-short: Serverless ece: "Elastic Cloud Enterprise" eck: "Elastic Cloud on Kubernetes" - -products: - - elasticsearch features: primary-nav: false diff --git a/docs/syntax/code.md b/docs/syntax/code.md index 5e0633b16..15524985a 100644 --- a/docs/syntax/code.md +++ b/docs/syntax/code.md @@ -1,8 +1,3 @@ ---- -products: - - apm ---- - # Code Code blocks can be used to display multiple lines of code. They preserve formatting and provide syntax highlighting when possible. diff --git a/docs/syntax/frontmatter.md b/docs/syntax/frontmatter.md index 4e30c4d7e..5a176807c 100644 --- a/docs/syntax/frontmatter.md +++ b/docs/syntax/frontmatter.md @@ -13,8 +13,8 @@ description: This is a description of the page <2> applies_to: <3> serverless: all products: <4> - - apm-java-agent - - edot-java + - id: apm-java-agent + - id: edot-java --- ``` @@ -45,7 +45,7 @@ See [](./applies.md) The products frontmatter is a list of products that the page relates to. This is used for the "Products" filter in the Search UI. -The products frontmatter is a list of strings, each string is the id of a product. +The products frontmatter is a list of objects, each object has an `id` field. | Product ID | Product Name | |---------------------------------------------|-----------------------------------------------| diff --git a/src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs b/src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs index 8287237a7..b5bbb2a9e 100644 --- a/src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs +++ b/src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs @@ -8,6 +8,7 @@ using Elastic.Documentation.Configuration.TableOfContents; using Elastic.Documentation.Links; using Elastic.Documentation.Navigation; +using YamlDotNet.RepresentationModel; namespace Elastic.Documentation.Configuration.Builder; @@ -108,17 +109,33 @@ public ConfigurationFile(IDocumentationContext context) // read this later break; case "products": - var productIds = YamlStreamReader.ReadStringArray(entry.Entry); - foreach (var productId in productIds) + if (entry.Entry.Value is not YamlSequenceNode sequence) { - if (!Builder.Products.AllById.ContainsKey(productId)) + reader.EmitError("products must be a sequence", entry.Entry.Value); + break; + } + + foreach (var node in sequence.Children.OfType()) + { + YamlScalarNode? productId = null; + foreach (var child in node.Children) { - var message = - $"Product \"{productId}\" not found in the product list. {new Suggestion(Builder.Products.All.Select(p => p.Id).ToHashSet(), productId).GetSuggestionQuestion()}"; - reader.EmitError(message, entry.Entry.Value); + if (child.Key is YamlScalarNode { Value: "id" } && child.Value is YamlScalarNode scalarNode) + { + productId = scalarNode; + break; + } } + if (productId?.Value is null) + { + reader.EmitError("products must contain an id", node); + break; + } + + if (!Builder.Products.AllById.ContainsKey(productId.Value)) + reader.EmitError($"Product \"{productId.Value}\" not found in the product list. {new Suggestion(Builder.Products.All.Select(p => p.Id).ToHashSet(), productId.Value).GetSuggestionQuestion()}", node); else - _ = Products.Add(productId); + _ = Products.Add(productId.Value); } break; case "features": diff --git a/src/Elastic.Markdown/Myst/FrontMatter/Products.cs b/src/Elastic.Markdown/Myst/FrontMatter/Products.cs index 88c3e2b32..40335b5e4 100644 --- a/src/Elastic.Markdown/Myst/FrontMatter/Products.cs +++ b/src/Elastic.Markdown/Myst/FrontMatter/Products.cs @@ -17,14 +17,33 @@ public class ProductConverter : IYamlTypeConverter public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) { - var value = parser.Consume(); - if (string.IsNullOrWhiteSpace(value.Value)) - throw new InvalidProductException(""); - - if (Products.AllById.TryGetValue(value.Value, out var product)) + if (parser.Current is Scalar) + { + var value = parser.Consume().Value; + throw new InvalidProductException($"Invalid YAML format. Products must be specified as a mapping with an 'id' field. Found scalar value: '{value}'. Example format:\nproducts:\n - id: apm"); + } + + _ = parser.Consume(); + string? productId = null; + + while (parser.Current is not MappingEnd) + { + var key = parser.Consume().Value; + if (key == "id") + productId = parser.Consume().Value; + else + parser.SkipThisAndNestedEvents(); + } + + _ = parser.Consume(); + + if (string.IsNullOrWhiteSpace(productId)) + throw new InvalidProductException("Product 'id' field is required. Example format:\nproducts:\n - id: apm"); + + if (Products.AllById.TryGetValue(productId, out var product)) return product; - throw new InvalidProductException(value.Value); + throw new InvalidProductException(productId); } public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) => serializer.Invoke(value, type); diff --git a/tests/Elastic.Markdown.Tests/FrontMatter/YamlFrontMatterTests.cs b/tests/Elastic.Markdown.Tests/FrontMatter/YamlFrontMatterTests.cs index 415bd7f1f..3d3d8e3c9 100644 --- a/tests/Elastic.Markdown.Tests/FrontMatter/YamlFrontMatterTests.cs +++ b/tests/Elastic.Markdown.Tests/FrontMatter/YamlFrontMatterTests.cs @@ -69,7 +69,7 @@ public class ProductsSingle(ITestOutputHelper output) : DirectiveTest(output, """ --- products: - - "apm" + - id: "apm" --- # APM @@ -90,8 +90,8 @@ public class ProductsMultiple(ITestOutputHelper output) : DirectiveTest(output, """ --- products: - - "apm" - - "elasticsearch" + - id: "apm" + - id: "elasticsearch" --- # APM @@ -113,7 +113,7 @@ public class ProductsSuggestionWhenMispelled(ITestOutputHelper output) : Directi """ --- products: - - aapm + - id: aapm --- # APM @@ -132,7 +132,7 @@ public class ProductsSuggestionWhenMispelled2(ITestOutputHelper output) : Direct """ --- products: - - apm-javaagent + - id: apm-javaagent --- # APM @@ -151,7 +151,7 @@ public class ProductsSuggestionWhenCasingError(ITestOutputHelper output) : Direc """ --- products: - - Apm + - id: Apm --- # APM @@ -170,7 +170,7 @@ public class ProductsSuggestionWhenEmpty(ITestOutputHelper output) : DirectiveTe """ --- products: - - "" + - id: "" --- # APM @@ -181,6 +181,6 @@ public class ProductsSuggestionWhenEmpty(ITestOutputHelper output) : DirectiveTe public void HasErrors() { Collector.Diagnostics.Should().HaveCount(1); - Collector.Diagnostics.Should().Contain(d => d.Message.Contains("Invalid products frontmatter value: \"\".\nYou can find the full list at https://docs-v3-preview.elastic.dev/elastic/docs-builder/tree/main/syntax/frontmatter#products.")); + Collector.Diagnostics.Should().Contain(d => d.Message.Contains("Invalid products frontmatter value: \"Product 'id' field is required.")); } }