From 74a28057ff511635579f874037313ead461803d5 Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Fri, 17 Oct 2025 21:01:10 -0300 Subject: [PATCH 1/5] Improve versioning system inference --- src/Elastic.Markdown/HtmlWriter.cs | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/Elastic.Markdown/HtmlWriter.cs b/src/Elastic.Markdown/HtmlWriter.cs index 804d12e01..a72e17031 100644 --- a/src/Elastic.Markdown/HtmlWriter.cs +++ b/src/Elastic.Markdown/HtmlWriter.cs @@ -7,6 +7,7 @@ using Elastic.Documentation; using Elastic.Documentation.Configuration.LegacyUrlMappings; using Elastic.Documentation.Configuration.Products; +using Elastic.Documentation.Configuration.TableOfContents; using Elastic.Documentation.Configuration.Versions; using Elastic.Documentation.Site.FileProviders; using Elastic.Documentation.Site.Navigation; @@ -102,9 +103,27 @@ private async Task RenderLayout(MarkdownFile markdown, MarkdownDoc fullNavigationRenderResult ); - var currentBaseVersion = legacyPages is { Count: > 0 } - ? $"{legacyPages.ElementAt(0).Product.VersioningSystem?.Base.Major}.{legacyPages.ElementAt(0).Product.VersioningSystem?.Base.Minor}+" - : $"{DocumentationSet.Context.VersionsConfiguration.VersioningSystems[VersioningSystemId.Stack].Base.Major}.{DocumentationSet.Context.VersionsConfiguration.VersioningSystems[VersioningSystemId.Stack].Base.Minor}+"; + // Acquiring the versioning system for the current page: + // 1. If the page has a legacy page mapping, use the versioning system of the legacy page + // 2. If the page's docset has a name with a direct product maatch, use the versioning system of the product + // 3. If the page's docset has a table of contents entry with a direct product match, use the versioning system of the product + // 4. Fallback to the stack versioning system + VersioningSystem pageVersioning = null!; + if (legacyPages is not null && legacyPages.Count > 0) + pageVersioning = legacyPages.ElementAt(0).Product.VersioningSystem!; + else if (DocumentationSet.Context.ProductsConfiguration.Products.TryGetValue(DocumentationSet.Name, out var belonging)) + pageVersioning = belonging.VersioningSystem!; + else if (DocumentationSet.Configuration.TableOfContents.FirstOrDefault(t => t is TocReference tRef && !tRef.Source.LocalPath.Equals("/")) is TocReference tocRef) + { + var productFound = DocumentationSet.Context.ProductsConfiguration.Products.TryGetValue(tocRef.Source.LocalPath.Trim('/'), out var tocProduct); + if (productFound && tocProduct is not null) + pageVersioning = tocProduct.VersioningSystem!; + } + else + pageVersioning = DocumentationSet.Context.VersionsConfiguration.VersioningSystems[VersioningSystemId.Stack]; + + var currentBaseVersion = $"{pageVersioning.Base.Major}.{pageVersioning.Base.Minor}+"; + //TODO should we even distinctby var breadcrumbs = parents.Reverse().DistinctBy(p => p.Url).ToArray(); var breadcrumbsList = CreateStructuredBreadcrumbsData(markdown, breadcrumbs); From d379363ad069e7a82021bec12441a1b9c28fedd7 Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Wed, 22 Oct 2025 14:07:28 -0300 Subject: [PATCH 2/5] Move inference logic to a service and use it --- config/products.yml | 7 +++ .../Products/Product.cs | 1 + .../Products/ProductExtensions.cs | 4 +- .../Versions/VersionInference.cs | 43 +++++++++++++++++++ .../DocumentationGenerator.cs | 3 +- src/Elastic.Markdown/HtmlWriter.cs | 25 +++-------- 6 files changed, 61 insertions(+), 22 deletions(-) create mode 100644 src/Elastic.Documentation.Configuration/Versions/VersionInference.cs diff --git a/config/products.yml b/config/products.yml index 0a35f5dd9..7c735a5da 100644 --- a/config/products.yml +++ b/config/products.yml @@ -91,18 +91,25 @@ products: versioning: 'stack' edot-ios: display: 'Elastic Distribution of OpenTelemetry iOS' + repository: 'apm-agent-ios' edot-android: display: 'Elastic Distribution of OpenTelemetry Android' + repository: 'apm-agent-android' edot-dotnet: display: 'Elastic Distribution of OpenTelemetry .NET' + repository: 'elastic-otel-dotnet' edot-java: display: 'Elastic Distribution of OpenTelemetry Java' + repository: 'elastic-otel-java' edot-node: display: 'Elastic Distribution of OpenTelemetry Node' + repository: 'elastic-otel-node' edot-php: display: 'Elastic Distribution of OpenTelemetry PHP' + repository: 'elastic-otel-php' edot-python: display: 'Elastic Distribution of OpenTelemetry Python' + repository: 'elastic-otel-python' edot-cf-aws: display: 'EDOT Cloud Forwarder for AWS' edot-cf-azure: diff --git a/src/Elastic.Documentation.Configuration/Products/Product.cs b/src/Elastic.Documentation.Configuration/Products/Product.cs index 9dabeb1eb..74a1afd96 100644 --- a/src/Elastic.Documentation.Configuration/Products/Product.cs +++ b/src/Elastic.Documentation.Configuration/Products/Product.cs @@ -19,5 +19,6 @@ public record Product public required string Id { get; init; } public required string DisplayName { get; init; } public VersioningSystem? VersioningSystem { get; init; } + public string? Repository { get; init; } } diff --git a/src/Elastic.Documentation.Configuration/Products/ProductExtensions.cs b/src/Elastic.Documentation.Configuration/Products/ProductExtensions.cs index eab68d0bb..5c90d0bbd 100644 --- a/src/Elastic.Documentation.Configuration/Products/ProductExtensions.cs +++ b/src/Elastic.Documentation.Configuration/Products/ProductExtensions.cs @@ -21,7 +21,8 @@ public static ProductsConfiguration CreateProducts(this ConfigurationFileProvide { Id = kvp.Key, DisplayName = kvp.Value.Display, - VersioningSystem = versionsConfiguration.GetVersioningSystem(VersionsConfigurationExtensions.ToVersioningSystemId(kvp.Value.Versioning ?? kvp.Key)) + VersioningSystem = versionsConfiguration.GetVersioningSystem(VersionsConfigurationExtensions.ToVersioningSystemId(kvp.Value.Versioning ?? kvp.Key)), + Repository = kvp.Value.Repository }); return new ProductsConfiguration @@ -41,4 +42,5 @@ internal sealed record ProductDto { public string Display { get; set; } = string.Empty; public string? Versioning { get; set; } + public string? Repository { get; set; } } diff --git a/src/Elastic.Documentation.Configuration/Versions/VersionInference.cs b/src/Elastic.Documentation.Configuration/Versions/VersionInference.cs new file mode 100644 index 000000000..6cd4bdafb --- /dev/null +++ b/src/Elastic.Documentation.Configuration/Versions/VersionInference.cs @@ -0,0 +1,43 @@ +// 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 + +using Elastic.Documentation.Configuration.LegacyUrlMappings; +using Elastic.Documentation.Configuration.Products; + +namespace Elastic.Documentation.Configuration.Versions; + +public interface IVersionInferrerService +{ + VersioningSystem InferVersion(IReadOnlyCollection? legacyPages); +} + +public class ProductVersionInferrerService(ProductsConfiguration productsConfiguration, VersionsConfiguration versionsConfiguration, string repositoryName) : IVersionInferrerService +{ + private ProductsConfiguration ProductsConfiguration { get; } = productsConfiguration; + private VersionsConfiguration VersionsConfiguration { get; } = versionsConfiguration; + private string RepositoryName { get; } = repositoryName; + public VersioningSystem InferVersion(IReadOnlyCollection? legacyPages) + { + var versioning = legacyPages is not null && legacyPages.Count > 0 + ? legacyPages.ElementAt(0).Product.VersioningSystem! // If the page has a legacy page mapping, use the versioning system of the legacy page + : ProductsConfiguration.Products.TryGetValue(RepositoryName, out var belonging) + ? belonging.VersioningSystem! //If the page's docset has a name with a direct product match, use the versioning system of the product + : ProductsConfiguration.Products.Values.SingleOrDefault(p => + p.Repository is not null && p.Repository.Equals(RepositoryName, StringComparison.OrdinalIgnoreCase)) is { } repositoryMatch + ? repositoryMatch.VersioningSystem! // Verify if the page belongs to a repository linked to a product, and if so, use the versioning system of the product + : VersionsConfiguration.VersioningSystems[VersioningSystemId.Stack]; // Fallback to the stack versioning system + + return versioning; + } +} + +public class NoopVersionInferrer : IVersionInferrerService +{ + public VersioningSystem InferVersion(IReadOnlyCollection? legacyPages) => new() + { + Id = VersioningSystemId.Stack, + Base = new SemVersion(0, 0, 0), + Current = new SemVersion(0, 0, 0) + }; +} diff --git a/src/Elastic.Markdown/DocumentationGenerator.cs b/src/Elastic.Markdown/DocumentationGenerator.cs index 34ece3528..314ec199a 100644 --- a/src/Elastic.Markdown/DocumentationGenerator.cs +++ b/src/Elastic.Markdown/DocumentationGenerator.cs @@ -7,6 +7,7 @@ using Elastic.Documentation; using Elastic.Documentation.Configuration; using Elastic.Documentation.Configuration.LegacyUrlMappings; +using Elastic.Documentation.Configuration.Versions; using Elastic.Documentation.Links; using Elastic.Documentation.Links.CrossLinks; using Elastic.Documentation.Serialization; @@ -72,7 +73,7 @@ public DocumentationGenerator( Context = docSet.Context; CrossLinkResolver = docSet.CrossLinkResolver; HtmlWriter = new HtmlWriter(DocumentationSet, _writeFileSystem, new DescriptionGenerator(), navigationHtmlWriter, legacyUrlMapper, - positionalNavigation); + positionalNavigation, new ProductVersionInferrerService(DocumentationSet.Context.ProductsConfiguration, DocumentationSet.Context.VersionsConfiguration, DocumentationSet.Context.Git.RepositoryName)); _documentationFileExporter = docSet.Context.AvailableExporters.Contains(Exporter.Html) ? docSet.EnabledExtensions.FirstOrDefault(e => e.FileExporter != null)?.FileExporter diff --git a/src/Elastic.Markdown/HtmlWriter.cs b/src/Elastic.Markdown/HtmlWriter.cs index a72e17031..bdc9fda00 100644 --- a/src/Elastic.Markdown/HtmlWriter.cs +++ b/src/Elastic.Markdown/HtmlWriter.cs @@ -7,7 +7,6 @@ using Elastic.Documentation; using Elastic.Documentation.Configuration.LegacyUrlMappings; using Elastic.Documentation.Configuration.Products; -using Elastic.Documentation.Configuration.TableOfContents; using Elastic.Documentation.Configuration.Versions; using Elastic.Documentation.Site.FileProviders; using Elastic.Documentation.Site.Navigation; @@ -26,7 +25,8 @@ public class HtmlWriter( IDescriptionGenerator descriptionGenerator, INavigationHtmlWriter? navigationHtmlWriter = null, ILegacyUrlMapper? legacyUrlMapper = null, - IPositionalNavigation? positionalNavigation = null + IPositionalNavigation? positionalNavigation = null, + IVersionInferrerService? versionInferrerService = null ) : IMarkdownStringRenderer { @@ -39,6 +39,8 @@ public class HtmlWriter( private ILegacyUrlMapper LegacyUrlMapper { get; } = legacyUrlMapper ?? new NoopLegacyUrlMapper(); private IPositionalNavigation PositionalNavigation { get; } = positionalNavigation ?? documentationSet; + private IVersionInferrerService VersionInferrerService { get; } = versionInferrerService ?? new NoopVersionInferrer(); + /// public string Render(string markdown, IFileInfo? source) { @@ -103,24 +105,7 @@ private async Task RenderLayout(MarkdownFile markdown, MarkdownDoc fullNavigationRenderResult ); - // Acquiring the versioning system for the current page: - // 1. If the page has a legacy page mapping, use the versioning system of the legacy page - // 2. If the page's docset has a name with a direct product maatch, use the versioning system of the product - // 3. If the page's docset has a table of contents entry with a direct product match, use the versioning system of the product - // 4. Fallback to the stack versioning system - VersioningSystem pageVersioning = null!; - if (legacyPages is not null && legacyPages.Count > 0) - pageVersioning = legacyPages.ElementAt(0).Product.VersioningSystem!; - else if (DocumentationSet.Context.ProductsConfiguration.Products.TryGetValue(DocumentationSet.Name, out var belonging)) - pageVersioning = belonging.VersioningSystem!; - else if (DocumentationSet.Configuration.TableOfContents.FirstOrDefault(t => t is TocReference tRef && !tRef.Source.LocalPath.Equals("/")) is TocReference tocRef) - { - var productFound = DocumentationSet.Context.ProductsConfiguration.Products.TryGetValue(tocRef.Source.LocalPath.Trim('/'), out var tocProduct); - if (productFound && tocProduct is not null) - pageVersioning = tocProduct.VersioningSystem!; - } - else - pageVersioning = DocumentationSet.Context.VersionsConfiguration.VersioningSystems[VersioningSystemId.Stack]; + var pageVersioning = VersionInferrerService.InferVersion(legacyPages); var currentBaseVersion = $"{pageVersioning.Base.Major}.{pageVersioning.Base.Minor}+"; From 2a405682411578ea190ea40f122aa93cdb82c148 Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Wed, 22 Oct 2025 14:10:13 -0300 Subject: [PATCH 3/5] Document parameter --- docs/configure/site/products.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/configure/site/products.md b/docs/configure/site/products.md index 9a5ca5164..e8d228c68 100644 --- a/docs/configure/site/products.md +++ b/docs/configure/site/products.md @@ -10,6 +10,7 @@ products: edot-collector: display: 'Elastic Distribution of OpenTelemetry Collector' versioning: 'stack' + repository: 'elastic-edot-collector' #... ``` @@ -19,6 +20,7 @@ products: : A YAML mapping where each key is an Elastic product. * `display`: A friendly name for the product. * `versioning`: The versioning system used by the project. The value for this field must match one of the versioning systems defined in [`versions.yml`](https://github.com/elastic/docs-builder/blob/main/config/versions.yml) +* `repository`: The repository name for the product. It's optional and primarily intended for handling edge cases where there is a mismatch between the repository name and the product identifier. From f2905a64bd2d9d061266b6a263d7ed5e84fbe8b9 Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Wed, 22 Oct 2025 14:11:34 -0300 Subject: [PATCH 4/5] Give a fallback value for Repository --- .../Products/ProductExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Elastic.Documentation.Configuration/Products/ProductExtensions.cs b/src/Elastic.Documentation.Configuration/Products/ProductExtensions.cs index 5c90d0bbd..b4920d65b 100644 --- a/src/Elastic.Documentation.Configuration/Products/ProductExtensions.cs +++ b/src/Elastic.Documentation.Configuration/Products/ProductExtensions.cs @@ -22,7 +22,7 @@ public static ProductsConfiguration CreateProducts(this ConfigurationFileProvide Id = kvp.Key, DisplayName = kvp.Value.Display, VersioningSystem = versionsConfiguration.GetVersioningSystem(VersionsConfigurationExtensions.ToVersioningSystemId(kvp.Value.Versioning ?? kvp.Key)), - Repository = kvp.Value.Repository + Repository = kvp.Value.Repository ?? kvp.Key }); return new ProductsConfiguration From a5123a1f9216b74dbc4e869a8c7cff7b49716463 Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Wed, 22 Oct 2025 14:35:52 -0300 Subject: [PATCH 5/5] Take the repository name in InferVersion --- .../Versions/VersionInference.cs | 13 ++++++------- src/Elastic.Markdown/DocumentationGenerator.cs | 3 ++- src/Elastic.Markdown/HtmlWriter.cs | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Elastic.Documentation.Configuration/Versions/VersionInference.cs b/src/Elastic.Documentation.Configuration/Versions/VersionInference.cs index 6cd4bdafb..fde4230cb 100644 --- a/src/Elastic.Documentation.Configuration/Versions/VersionInference.cs +++ b/src/Elastic.Documentation.Configuration/Versions/VersionInference.cs @@ -9,22 +9,21 @@ namespace Elastic.Documentation.Configuration.Versions; public interface IVersionInferrerService { - VersioningSystem InferVersion(IReadOnlyCollection? legacyPages); + VersioningSystem InferVersion(string repositoryName, IReadOnlyCollection? legacyPages); } -public class ProductVersionInferrerService(ProductsConfiguration productsConfiguration, VersionsConfiguration versionsConfiguration, string repositoryName) : IVersionInferrerService +public class ProductVersionInferrerService(ProductsConfiguration productsConfiguration, VersionsConfiguration versionsConfiguration) : IVersionInferrerService { private ProductsConfiguration ProductsConfiguration { get; } = productsConfiguration; private VersionsConfiguration VersionsConfiguration { get; } = versionsConfiguration; - private string RepositoryName { get; } = repositoryName; - public VersioningSystem InferVersion(IReadOnlyCollection? legacyPages) + public VersioningSystem InferVersion(string repositoryName, IReadOnlyCollection? legacyPages) { var versioning = legacyPages is not null && legacyPages.Count > 0 ? legacyPages.ElementAt(0).Product.VersioningSystem! // If the page has a legacy page mapping, use the versioning system of the legacy page - : ProductsConfiguration.Products.TryGetValue(RepositoryName, out var belonging) + : ProductsConfiguration.Products.TryGetValue(repositoryName, out var belonging) ? belonging.VersioningSystem! //If the page's docset has a name with a direct product match, use the versioning system of the product : ProductsConfiguration.Products.Values.SingleOrDefault(p => - p.Repository is not null && p.Repository.Equals(RepositoryName, StringComparison.OrdinalIgnoreCase)) is { } repositoryMatch + p.Repository is not null && p.Repository.Equals(repositoryName, StringComparison.OrdinalIgnoreCase)) is { } repositoryMatch ? repositoryMatch.VersioningSystem! // Verify if the page belongs to a repository linked to a product, and if so, use the versioning system of the product : VersionsConfiguration.VersioningSystems[VersioningSystemId.Stack]; // Fallback to the stack versioning system @@ -34,7 +33,7 @@ public VersioningSystem InferVersion(IReadOnlyCollection? leg public class NoopVersionInferrer : IVersionInferrerService { - public VersioningSystem InferVersion(IReadOnlyCollection? legacyPages) => new() + public VersioningSystem InferVersion(string repositoryName, IReadOnlyCollection? legacyPages) => new() { Id = VersioningSystemId.Stack, Base = new SemVersion(0, 0, 0), diff --git a/src/Elastic.Markdown/DocumentationGenerator.cs b/src/Elastic.Markdown/DocumentationGenerator.cs index 314ec199a..5aeb99861 100644 --- a/src/Elastic.Markdown/DocumentationGenerator.cs +++ b/src/Elastic.Markdown/DocumentationGenerator.cs @@ -72,8 +72,9 @@ public DocumentationGenerator( DocumentationSet = docSet; Context = docSet.Context; CrossLinkResolver = docSet.CrossLinkResolver; + var productVersionInferrer = new ProductVersionInferrerService(DocumentationSet.Context.ProductsConfiguration, DocumentationSet.Context.VersionsConfiguration); HtmlWriter = new HtmlWriter(DocumentationSet, _writeFileSystem, new DescriptionGenerator(), navigationHtmlWriter, legacyUrlMapper, - positionalNavigation, new ProductVersionInferrerService(DocumentationSet.Context.ProductsConfiguration, DocumentationSet.Context.VersionsConfiguration, DocumentationSet.Context.Git.RepositoryName)); + positionalNavigation, productVersionInferrer); _documentationFileExporter = docSet.Context.AvailableExporters.Contains(Exporter.Html) ? docSet.EnabledExtensions.FirstOrDefault(e => e.FileExporter != null)?.FileExporter diff --git a/src/Elastic.Markdown/HtmlWriter.cs b/src/Elastic.Markdown/HtmlWriter.cs index bdc9fda00..761e8b1ad 100644 --- a/src/Elastic.Markdown/HtmlWriter.cs +++ b/src/Elastic.Markdown/HtmlWriter.cs @@ -105,7 +105,7 @@ private async Task RenderLayout(MarkdownFile markdown, MarkdownDoc fullNavigationRenderResult ); - var pageVersioning = VersionInferrerService.InferVersion(legacyPages); + var pageVersioning = VersionInferrerService.InferVersion(DocumentationSet.Context.Git.RepositoryName, legacyPages); var currentBaseVersion = $"{pageVersioning.Base.Major}.{pageVersioning.Base.Minor}+";