diff --git a/src/Elastic.Markdown/Exporters/LlmMarkdownExporter.cs b/src/Elastic.Markdown/Exporters/LlmMarkdownExporter.cs index 1ed4b7207..aae0f3ac7 100644 --- a/src/Elastic.Markdown/Exporters/LlmMarkdownExporter.cs +++ b/src/Elastic.Markdown/Exporters/LlmMarkdownExporter.cs @@ -6,6 +6,7 @@ using System.IO.Compression; using System.Text; using Elastic.Documentation.Configuration; +using Elastic.Documentation.Configuration.Products; using Elastic.Markdown.Helpers; using Markdig.Syntax; @@ -113,21 +114,11 @@ private string CreateLlmContentWithMetadata(MarkdownExportFileContext context, s if (!string.IsNullOrEmpty(sourceFile.Url)) _ = metadata.AppendLine($"url: {context.BuildContext.CanonicalBaseUrl?.Scheme}://{context.BuildContext.CanonicalBaseUrl?.Host}{sourceFile.Url}"); - var configProducts = context.BuildContext.ProductsConfiguration.Products.Select(p => - { - if (context.BuildContext.ProductsConfiguration.Products.TryGetValue(p.Value.Id, out var product)) - return product; - throw new ArgumentException($"Invalid product id: {p.Value.Id}"); - }); - var frontMatterProducts = sourceFile.YamlFrontMatter?.Products ?? []; - var allProducts = frontMatterProducts - .Union(configProducts) - .Distinct() - .ToList(); - if (allProducts.Count > 0) + var pageProducts = GetPageProducts(sourceFile.YamlFrontMatter?.Products); + if (pageProducts.Count > 0) { _ = metadata.AppendLine("products:"); - foreach (var item in allProducts.Select(p => p.DisplayName).Order()) + foreach (var item in pageProducts.Select(p => p.DisplayName).Order()) _ = metadata.AppendLine($" - {item}"); } @@ -138,6 +129,9 @@ private string CreateLlmContentWithMetadata(MarkdownExportFileContext context, s return metadata.ToString(); } + + private static List GetPageProducts(IReadOnlyCollection? frontMatterProducts) => + frontMatterProducts?.ToList() ?? []; } public static class LlmMarkdownExporterExtensions diff --git a/src/Elastic.Markdown/HtmlWriter.cs b/src/Elastic.Markdown/HtmlWriter.cs index 5c68c29ee..804d12e01 100644 --- a/src/Elastic.Markdown/HtmlWriter.cs +++ b/src/Elastic.Markdown/HtmlWriter.cs @@ -6,6 +6,7 @@ using System.Text.Json; using Elastic.Documentation; using Elastic.Documentation.Configuration.LegacyUrlMappings; +using Elastic.Documentation.Configuration.Products; using Elastic.Documentation.Configuration.Versions; using Elastic.Documentation.Site.FileProviders; using Elastic.Documentation.Site.Navigation; @@ -86,19 +87,7 @@ private async Task RenderLayout(MarkdownFile markdown, MarkdownDoc var siteName = DocumentationSet.Tree.Index.Title ?? "Elastic Documentation"; var legacyPages = LegacyUrlMapper.MapLegacyUrl(markdown.YamlFrontMatter?.MappedPages); - var configProducts = DocumentationSet.Context.ProductsConfiguration.Products.Select(p => - { - if (DocumentationSet.Context.ProductsConfiguration.Products.TryGetValue(p.Value.Id, out var product)) - return product; - throw new ArgumentException($"Invalid product id: {p.Value.Id}"); - }); - - var frontMatterProducts = markdown.YamlFrontMatter?.Products ?? []; - - var allProducts = frontMatterProducts - .Union(configProducts) - .Distinct() - .ToHashSet(); + var pageProducts = GetPageProducts(markdown.YamlFrontMatter?.Products); string? allVersionsUrl = null; @@ -153,7 +142,7 @@ private async Task RenderLayout(MarkdownFile markdown, MarkdownDoc AllVersionsUrl = allVersionsUrl, LegacyPages = legacyPages?.ToArray(), VersionDropdownItems = VersionDropDownItemViewModel.FromLegacyPageMappings(legacyPages?.ToArray()), - Products = allProducts, + Products = pageProducts, VersionsConfig = DocumentationSet.Context.VersionsConfiguration, StructuredBreadcrumbsJson = structuredBreadcrumbsJsonString }); @@ -229,6 +218,9 @@ public async Task WriteAsync(IDirectoryInfo outBaseDir, IFileI return document; } + private static HashSet GetPageProducts(IReadOnlyCollection? frontMatterProducts) => + frontMatterProducts?.ToHashSet() ?? []; + } public record RenderResult diff --git a/src/infra/docs-lambda-index-publisher/lambda.DockerFile b/src/infra/docs-lambda-index-publisher/lambda.DockerFile index 44d1f2b6d..0066279df 100644 --- a/src/infra/docs-lambda-index-publisher/lambda.DockerFile +++ b/src/infra/docs-lambda-index-publisher/lambda.DockerFile @@ -32,4 +32,3 @@ RUN arch=$TARGETARCH \ && echo $TARGETOS-$arch > /tmp/rid RUN dotnet publish src/infra/docs-lambda-index-publisher -r linux-x64 -c Release - diff --git a/tests/authoring/FrontMatter/ProductsFrontMatter.fs b/tests/authoring/FrontMatter/ProductsFrontMatter.fs new file mode 100644 index 000000000..f349c39bd --- /dev/null +++ b/tests/authoring/FrontMatter/ProductsFrontMatter.fs @@ -0,0 +1,87 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +module ``AuthoringTests``.``frontmatter``.``products frontmatter`` + +open Swensen.Unquote +open Xunit +open authoring +open JetBrains.Annotations + +let frontMatter ([]m: string) = + Setup.Document $"""--- +{m} +--- +# Test Page + +This is a test page with products frontmatter. +""" + +type ``products frontmatter in HTML`` () = + static let markdownWithProducts = frontMatter """ +products: + - id: elasticsearch + - id: ecctl +""" + + [] + let ``includes products meta tags when products are specified`` () = + markdownWithProducts |> converts "index.md" |> containsHtml """ + + +""" + + [] + let ``does not include products meta tags when no products are specified`` () = + let markdownWithoutProducts = Setup.Document """ +# Test Page + +This is a test page without products frontmatter. +""" + // When there are no products, no product-related meta tags should be rendered at all + let results = markdownWithoutProducts.Value + let defaultFile = results.MarkdownResults |> Seq.find (fun r -> r.File.RelativePath = "index.md") + let html = defaultFile.Html + + // Verify that product meta tags are NOT present in the HTML + test <@ not (html.Contains("product_name")) @> + test <@ not (html.Contains("DC.subject")) @> + +type ``products frontmatter in LLM Markdown`` () = + static let markdownWithProducts = frontMatter """ +products: + - id: elasticsearch + - id: ecctl +""" + + static let markdownWithoutProducts = Setup.Document """ +# Test Page + +This is a test page without products frontmatter. +""" + + [] + let ``includes products in frontmatter when products are specified`` () = + // Test that the products frontmatter is correctly processed by checking the file + let results = markdownWithProducts.Value + let defaultFile = results.MarkdownResults |> Seq.find (fun r -> r.File.RelativePath = "index.md") + + // Test that the file has the correct products + test <@ defaultFile.File.YamlFrontMatter <> null @> + test <@ defaultFile.File.YamlFrontMatter.Products <> null @> + test <@ defaultFile.File.YamlFrontMatter.Products.Count = 2 @> + + // Test that the products are correctly identified + let productIds = defaultFile.File.YamlFrontMatter.Products |> Seq.map (fun p -> p.Id) |> Set.ofSeq + test <@ productIds.Contains("elasticsearch") @> + test <@ productIds.Contains("ecctl") @> + + [] + let ``does not include products in frontmatter when no products are specified`` () = + // Test that pages without products frontmatter don't have products + let results = markdownWithoutProducts.Value + let defaultFile = results.MarkdownResults |> Seq.find (fun r -> r.File.RelativePath = "index.md") + + // Test that the file has no products + test <@ defaultFile.File.YamlFrontMatter = null || defaultFile.File.YamlFrontMatter.Products = null || defaultFile.File.YamlFrontMatter.Products.Count = 0 @> diff --git a/tests/authoring/authoring.fsproj b/tests/authoring/authoring.fsproj index 661be140d..dedc4f06f 100644 --- a/tests/authoring/authoring.fsproj +++ b/tests/authoring/authoring.fsproj @@ -55,5 +55,6 @@ +