diff --git a/src/Elastic.Documentation.Configuration/Versions/VersionInference.cs b/src/Elastic.Documentation.Configuration/Versions/VersionInference.cs index fde4230cb..102dca32f 100644 --- a/src/Elastic.Documentation.Configuration/Versions/VersionInference.cs +++ b/src/Elastic.Documentation.Configuration/Versions/VersionInference.cs @@ -2,6 +2,7 @@ // 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.AppliesTo; using Elastic.Documentation.Configuration.LegacyUrlMappings; using Elastic.Documentation.Configuration.Products; @@ -9,18 +10,26 @@ namespace Elastic.Documentation.Configuration.Versions; public interface IVersionInferrerService { - VersioningSystem InferVersion(string repositoryName, IReadOnlyCollection? legacyPages); + VersioningSystem InferVersion(string repositoryName, IReadOnlyCollection? legacyPages, IReadOnlyCollection? products, ApplicableTo? applicableTo); } public class ProductVersionInferrerService(ProductsConfiguration productsConfiguration, VersionsConfiguration versionsConfiguration) : IVersionInferrerService { private ProductsConfiguration ProductsConfiguration { get; } = productsConfiguration; private VersionsConfiguration VersionsConfiguration { get; } = versionsConfiguration; - public VersioningSystem InferVersion(string repositoryName, IReadOnlyCollection? legacyPages) + public VersioningSystem InferVersion(string repositoryName, IReadOnlyCollection? legacyPages, IReadOnlyCollection? products, ApplicableTo? applicableTo) { - 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) + if (legacyPages is { Count: > 0 }) + return legacyPages.ElementAt(0).Product.VersioningSystem!; // If the page has legacy mappings, use the versioning system of the first mapping's product + + if (applicableTo is not null) + { + var versioningFromApplicability = VersioningFromApplicability(applicableTo); // Try to infer the versioning system from the applicability metadata + if (versioningFromApplicability is not null) + return versioningFromApplicability; + } + + var versioning = 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 @@ -29,11 +38,62 @@ public VersioningSystem InferVersion(string repositoryName, IReadOnlyCollection< return versioning; } + + private VersioningSystem? VersioningFromApplicability(ApplicableTo applicableTo) + { + // Priority 1: Product applicability + var product = ProductFromApplicability(applicableTo.ProductApplicability); + if (product?.VersioningSystem is not null) + return product.VersioningSystem; + + // Priority 2: Stack applicability + if (applicableTo.Stack is not null) + return VersionsConfiguration.VersioningSystems[VersioningSystemId.Stack]; + // Priority 3: Deployment applicability + if (applicableTo.Deployment is not null) + { + var versioning = applicableTo.Deployment switch + { + { Ece: not null } => VersionsConfiguration.VersioningSystems[VersioningSystemId.Ece], + { Eck: not null } => VersionsConfiguration.VersioningSystems[VersioningSystemId.Eck], + { Ess: not null } => VersionsConfiguration.VersioningSystems[VersioningSystemId.Ess], + { Self: not null } => VersionsConfiguration.VersioningSystems[VersioningSystemId.Self], + _ => null + }; + if (versioning is not null) + return versioning; + } + + // Priority 4: Serverless applicability + if (applicableTo.Serverless is not null) + { + var versioning = applicableTo.Serverless switch + { + { Elasticsearch: not null } => VersionsConfiguration.VersioningSystems[VersioningSystemId.ElasticsearchProject], + { Observability: not null } => VersionsConfiguration.VersioningSystems[VersioningSystemId.ObservabilityProject], + { Security: not null } => VersionsConfiguration.VersioningSystems[VersioningSystemId.SecurityProject], + _ => null + }; + if (versioning is not null) + return versioning; + } + + return null; + } + private Product? ProductFromApplicability(ProductApplicability? productApplicability) + { + if (productApplicability is null) + return null; + + var productId = ProductApplicabilityConversion.ProductApplicabilityToProductId(productApplicability); + + return productId is null ? null : ProductsConfiguration.Products.GetValueOrDefault(productId); + } } public class NoopVersionInferrer : IVersionInferrerService { - public VersioningSystem InferVersion(string repositoryName, IReadOnlyCollection? legacyPages) => new() + public VersioningSystem InferVersion(string repositoryName, IReadOnlyCollection? legacyPages, IReadOnlyCollection? products, ApplicableTo? applicableTo) => new() { Id = VersioningSystemId.Stack, Base = new SemVersion(0, 0, 0), diff --git a/src/Elastic.Documentation/AppliesTo/ApplicableTo.cs b/src/Elastic.Documentation/AppliesTo/ApplicableTo.cs index 5889a9964..6bfa16b4d 100644 --- a/src/Elastic.Documentation/AppliesTo/ApplicableTo.cs +++ b/src/Elastic.Documentation/AppliesTo/ApplicableTo.cs @@ -236,119 +236,3 @@ public override string ToString() return sb.ToString(); } } - -[YamlSerializable] -public record ProductApplicability -{ - [YamlMember(Alias = "ecctl")] - public AppliesCollection? Ecctl { get; set; } - - [YamlMember(Alias = "curator")] - public AppliesCollection? Curator { get; set; } - - [YamlMember(Alias = "apm-agent-android")] - public AppliesCollection? ApmAgentAndroid { get; set; } - - [YamlMember(Alias = "apm-agent-dotnet")] - public AppliesCollection? ApmAgentDotnet { get; set; } - - [YamlMember(Alias = "apm-agent-go")] - public AppliesCollection? ApmAgentGo { get; set; } - - [YamlMember(Alias = "apm-agent-ios")] - public AppliesCollection? ApmAgentIos { get; set; } - - [YamlMember(Alias = "apm-agent-java")] - public AppliesCollection? ApmAgentJava { get; set; } - - [YamlMember(Alias = "apm-agent-node")] - public AppliesCollection? ApmAgentNode { get; set; } - - [YamlMember(Alias = "apm-agent-php")] - public AppliesCollection? ApmAgentPhp { get; set; } - - [YamlMember(Alias = "apm-agent-python")] - public AppliesCollection? ApmAgentPython { get; set; } - - [YamlMember(Alias = "apm-agent-ruby")] - public AppliesCollection? ApmAgentRuby { get; set; } - - [YamlMember(Alias = "apm-agent-rum-js")] - public AppliesCollection? ApmAgentRumJs { get; set; } - - [YamlMember(Alias = "edot-ios")] - public AppliesCollection? EdotIos { get; set; } - - [YamlMember(Alias = "edot-android")] - public AppliesCollection? EdotAndroid { get; set; } - - [YamlMember(Alias = "edot-dotnet")] - public AppliesCollection? EdotDotnet { get; set; } - - [YamlMember(Alias = "edot-java")] - public AppliesCollection? EdotJava { get; set; } - - [YamlMember(Alias = "edot-node")] - public AppliesCollection? EdotNode { get; set; } - - [YamlMember(Alias = "edot-php")] - public AppliesCollection? EdotPhp { get; set; } - - [YamlMember(Alias = "edot-python")] - public AppliesCollection? EdotPython { get; set; } - - [YamlMember(Alias = "edot-cf-aws")] - public AppliesCollection? EdotCfAws { get; set; } - - [YamlMember(Alias = "edot-cf-azure")] - public AppliesCollection? EdotCfAzure { get; set; } - - [YamlMember(Alias = "edot-cf-gcp")] - public AppliesCollection? EdotCfGcp { get; set; } - - [YamlMember(Alias = "edot-collector")] - public AppliesCollection? EdotCollector { get; set; } - - /// - public override string ToString() - { - var sb = new StringBuilder(); - var hasContent = false; - - void AppendProduct(string name, AppliesCollection? value) - { - if (value is null) - return; - if (hasContent) - _ = sb.Append(", "); - _ = sb.Append(name).Append('=').Append(value); - hasContent = true; - } - - AppendProduct("ecctl", Ecctl); - AppendProduct("curator", Curator); - AppendProduct("apm-agent-android", ApmAgentAndroid); - AppendProduct("apm-agent-dotnet", ApmAgentDotnet); - AppendProduct("apm-agent-go", ApmAgentGo); - AppendProduct("apm-agent-ios", ApmAgentIos); - AppendProduct("apm-agent-java", ApmAgentJava); - AppendProduct("apm-agent-node", ApmAgentNode); - AppendProduct("apm-agent-php", ApmAgentPhp); - AppendProduct("apm-agent-python", ApmAgentPython); - AppendProduct("apm-agent-ruby", ApmAgentRuby); - AppendProduct("apm-agent-rum-js", ApmAgentRumJs); - AppendProduct("edot-ios", EdotIos); - AppendProduct("edot-android", EdotAndroid); - AppendProduct("edot-dotnet", EdotDotnet); - AppendProduct("edot-java", EdotJava); - AppendProduct("edot-node", EdotNode); - AppendProduct("edot-php", EdotPhp); - AppendProduct("edot-python", EdotPython); - AppendProduct("edot-cf-aws", EdotCfAws); - AppendProduct("edot-cf-azure", EdotCfAzure); - AppendProduct("edot-cf-gcp", EdotCfGcp); - AppendProduct("edot-collector", EdotCollector); - - return sb.ToString(); - } -} diff --git a/src/Elastic.Documentation/AppliesTo/ProductApplicability.cs b/src/Elastic.Documentation/AppliesTo/ProductApplicability.cs new file mode 100644 index 000000000..886f6a62e --- /dev/null +++ b/src/Elastic.Documentation/AppliesTo/ProductApplicability.cs @@ -0,0 +1,155 @@ +// 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 System.Text; +using YamlDotNet.Serialization; + +namespace Elastic.Documentation.AppliesTo; + +[YamlSerializable] +public record ProductApplicability +{ + [YamlMember(Alias = "ecctl")] + public AppliesCollection? Ecctl { get; set; } + + [YamlMember(Alias = "curator")] + public AppliesCollection? Curator { get; set; } + + [YamlMember(Alias = "apm-agent-android")] + public AppliesCollection? ApmAgentAndroid { get; set; } + + [YamlMember(Alias = "apm-agent-dotnet")] + public AppliesCollection? ApmAgentDotnet { get; set; } + + [YamlMember(Alias = "apm-agent-go")] + public AppliesCollection? ApmAgentGo { get; set; } + + [YamlMember(Alias = "apm-agent-ios")] + public AppliesCollection? ApmAgentIos { get; set; } + + [YamlMember(Alias = "apm-agent-java")] + public AppliesCollection? ApmAgentJava { get; set; } + + [YamlMember(Alias = "apm-agent-node")] + public AppliesCollection? ApmAgentNode { get; set; } + + [YamlMember(Alias = "apm-agent-php")] + public AppliesCollection? ApmAgentPhp { get; set; } + + [YamlMember(Alias = "apm-agent-python")] + public AppliesCollection? ApmAgentPython { get; set; } + + [YamlMember(Alias = "apm-agent-ruby")] + public AppliesCollection? ApmAgentRuby { get; set; } + + [YamlMember(Alias = "apm-agent-rum-js")] + public AppliesCollection? ApmAgentRumJs { get; set; } + + [YamlMember(Alias = "edot-ios")] + public AppliesCollection? EdotIos { get; set; } + + [YamlMember(Alias = "edot-android")] + public AppliesCollection? EdotAndroid { get; set; } + + [YamlMember(Alias = "edot-dotnet")] + public AppliesCollection? EdotDotnet { get; set; } + + [YamlMember(Alias = "edot-java")] + public AppliesCollection? EdotJava { get; set; } + + [YamlMember(Alias = "edot-node")] + public AppliesCollection? EdotNode { get; set; } + + [YamlMember(Alias = "edot-php")] + public AppliesCollection? EdotPhp { get; set; } + + [YamlMember(Alias = "edot-python")] + public AppliesCollection? EdotPython { get; set; } + + [YamlMember(Alias = "edot-cf-aws")] + public AppliesCollection? EdotCfAws { get; set; } + + [YamlMember(Alias = "edot-cf-azure")] + public AppliesCollection? EdotCfAzure { get; set; } + + [YamlMember(Alias = "edot-cf-gcp")] + public AppliesCollection? EdotCfGcp { get; set; } + + [YamlMember(Alias = "edot-collector")] + public AppliesCollection? EdotCollector { get; set; } + + /// + public override string ToString() + { + var sb = new StringBuilder(); + var hasContent = false; + + void AppendProduct(string name, AppliesCollection? value) + { + if (value is null) + return; + if (hasContent) + _ = sb.Append(": not null } => "); + _ = sb.Append(name).Append('=').Append(value); + hasContent = true; + } + + AppendProduct("ecctl", Ecctl); + AppendProduct("curator", Curator); + AppendProduct("apm-agent-android", ApmAgentAndroid); + AppendProduct("apm-agent-dotnet", ApmAgentDotnet); + AppendProduct("apm-agent-go", ApmAgentGo); + AppendProduct("apm-agent-ios", ApmAgentIos); + AppendProduct("apm-agent-java", ApmAgentJava); + AppendProduct("apm-agent-node", ApmAgentNode); + AppendProduct("apm-agent-php", ApmAgentPhp); + AppendProduct("apm-agent-python", ApmAgentPython); + AppendProduct("apm-agent-ruby", ApmAgentRuby); + AppendProduct("apm-agent-rum-js", ApmAgentRumJs); + AppendProduct("edot-ios", EdotIos); + AppendProduct("edot-android", EdotAndroid); + AppendProduct("edot-dotnet", EdotDotnet); + AppendProduct("edot-java", EdotJava); + AppendProduct("edot-node", EdotNode); + AppendProduct("edot-php", EdotPhp); + AppendProduct("edot-python", EdotPython); + AppendProduct("edot-cf-aws", EdotCfAws); + AppendProduct("edot-cf-azure", EdotCfAzure); + AppendProduct("edot-cf-gcp", EdotCfGcp); + AppendProduct("edot-collector", EdotCollector); + + return sb.ToString(); + } +} + +public static class ProductApplicabilityConversion +{ + public static string? ProductApplicabilityToProductId(ProductApplicability p) => p switch + { + { Ecctl: not null } => "cloud-control-ecctl", + { Curator: not null } => "curator", + { ApmAgentAndroid: not null } => "edot-android", + { ApmAgentDotnet: not null } => "apm-agent-dotnet", + { ApmAgentGo: not null } => "apm-agent-go", + { ApmAgentIos: not null } => "edot-ios", + { ApmAgentJava: not null } => "apm-agent-java", + { ApmAgentNode: not null } => "apm-agent-node", + { ApmAgentPhp: not null } => "apm-agent-php", + { ApmAgentPython: not null } => "apm-agent-python", + { ApmAgentRuby: not null } => "apm-agent-ruby", + { ApmAgentRumJs: not null } => "apm-agent-rum-js", + { EdotIos: not null } => "edot-ios", + { EdotAndroid: not null } => "edot-android", + { EdotDotnet: not null } => "edot-dotnet", + { EdotJava: not null } => "edot-java", + { EdotNode: not null } => "edot-node", + { EdotPhp: not null } => "edot-php", + { EdotPython: not null } => "edot-python", + { EdotCfAws: not null } => "edot-cf-aws", + { EdotCfAzure: not null } => "edot-cf-azure", + { EdotCfGcp: not null } => "edot-cf-gcp", + { EdotCollector: not null } => "edot-collector", + _ => null + }; +} diff --git a/src/Elastic.Markdown/HtmlWriter.cs b/src/Elastic.Markdown/HtmlWriter.cs index 6a886f92e..aabfa19cc 100644 --- a/src/Elastic.Markdown/HtmlWriter.cs +++ b/src/Elastic.Markdown/HtmlWriter.cs @@ -112,7 +112,7 @@ private async Task RenderLayout(MarkdownFile markdown, MarkdownDoc } - var pageVersioning = VersionInferrerService.InferVersion(DocumentationSet.Context.Git.RepositoryName, legacyPages); + var pageVersioning = VersionInferrerService.InferVersion(DocumentationSet.Context.Git.RepositoryName, legacyPages, markdown.YamlFrontMatter?.Products, markdown.YamlFrontMatter?.AppliesTo); var currentBaseVersion = $"{pageVersioning.Base.Major}.{pageVersioning.Base.Minor}+"; @@ -154,6 +154,7 @@ private async Task RenderLayout(MarkdownFile markdown, MarkdownDoc LegacyPages = legacyPages?.ToArray(), VersionDropdownItems = VersionDropDownItemViewModel.FromLegacyPageMappings(legacyPages?.ToArray()), Products = pageProducts, + VersioningSystem = pageVersioning, VersionsConfig = DocumentationSet.Context.VersionsConfiguration, StructuredBreadcrumbsJson = structuredBreadcrumbsJsonString }); diff --git a/src/Elastic.Markdown/Layout/_TableOfContents.cshtml b/src/Elastic.Markdown/Layout/_TableOfContents.cshtml index 86b49cedc..82edc931d 100644 --- a/src/Elastic.Markdown/Layout/_TableOfContents.cshtml +++ b/src/Elastic.Markdown/Layout/_TableOfContents.cshtml @@ -1,11 +1,16 @@ +@using Elastic.Documentation.Configuration.Versions @inherits RazorSlice