diff --git a/docs/testing/req.md b/docs/testing/req.md index 4617b62d4..ce29340ec 100644 --- a/docs/testing/req.md +++ b/docs/testing/req.md @@ -9,4 +9,4 @@ The tutorial assumes that you have no previous knowledge of Elasticsearch or gen - Python development - The [Flask](https://flask.palletsprojects.com/) web framework for Python. -- The command prompt or terminal application in your operating system. \ No newline at end of file +- The command prompt or terminal application in your operating system. diff --git a/src/Elastic.Markdown/Myst/Components/ApplicableToComponent.cshtml b/src/Elastic.Markdown/Myst/Components/ApplicableToComponent.cshtml index 62241dd76..286c705b4 100644 --- a/src/Elastic.Markdown/Myst/Components/ApplicableToComponent.cshtml +++ b/src/Elastic.Markdown/Myst/Components/ApplicableToComponent.cshtml @@ -56,6 +56,26 @@ { @RenderProduct("", appliesTo.Product) } +@if (appliesTo.ProductApplicability is not null) +{ + var pa = appliesTo.ProductApplicability; + if (pa.Ecctl is not null) { @RenderProduct("ECCTL", pa.Ecctl); } + if (pa.Curator is not null) { @RenderProduct("Curator", pa.Curator); } + if (pa.ApmAgentDotnet is not null) { @RenderProduct("APM Agent .NET", pa.ApmAgentDotnet); } + if (pa.ApmAgentGo is not null) { @RenderProduct("APM Agent Go", pa.ApmAgentGo); } + if (pa.ApmAgentJava is not null) { @RenderProduct("APM Agent Java", pa.ApmAgentJava); } + if (pa.ApmAgentNode is not null) { @RenderProduct("APM Agent Node.js", pa.ApmAgentNode); } + if (pa.ApmAgentPython is not null) { @RenderProduct("APM Agent Python", pa.ApmAgentPython); } + if (pa.ApmAgentRuby is not null) { @RenderProduct("APM Agent Ruby", pa.ApmAgentRuby); } + if (pa.ApmAgentRum is not null) { @RenderProduct("APM Agent RUM", pa.ApmAgentRum); } + if (pa.EdotIos is not null) { @RenderProduct("OpenTelemetry iOS", pa.EdotIos); } + if (pa.EdotAndroid is not null) { @RenderProduct("OpenTelemetry Android", pa.EdotAndroid); } + if (pa.EdotDotnet is not null) { @RenderProduct("OpenTelemetry .NET", pa.EdotDotnet); } + if (pa.EdotJava is not null) { @RenderProduct("OpenTelemetry Java", pa.EdotJava); } + if (pa.EdotNode is not null) { @RenderProduct("OpenTelemetry Node.js", pa.EdotNode); } + if (pa.EdotPhp is not null) { @RenderProduct("OpenTelemetry PHP", pa.EdotPhp); } + if (pa.EdotPython is not null) { @RenderProduct("OpenTelemetry Python", pa.EdotPython); } +} @functions { diff --git a/src/Elastic.Markdown/Myst/FrontMatter/ApplicableTo.cs b/src/Elastic.Markdown/Myst/FrontMatter/ApplicableTo.cs index 19a87dfae..d6267d4b1 100644 --- a/src/Elastic.Markdown/Myst/FrontMatter/ApplicableTo.cs +++ b/src/Elastic.Markdown/Myst/FrontMatter/ApplicableTo.cs @@ -50,6 +50,8 @@ public record ApplicableTo [YamlMember(Alias = "product")] public AppliesCollection? Product { get; set; } + public ProductApplicability? ProductApplicability { get; set; } + internal YamlDiagnosticsCollection? Diagnostics { get; set; } public static ApplicableTo All { get; } = new() @@ -113,12 +115,69 @@ public record ServerlessProjectApplicability }; } +[YamlSerializable] +public record ProductApplicability +{ + [YamlMember(Alias = "ecctl")] + public AppliesCollection? Ecctl { get; set; } + + [YamlMember(Alias = "curator")] + public AppliesCollection? Curator { 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_java")] + public AppliesCollection? ApmAgentJava { get; set; } + + [YamlMember(Alias = "apm_agent_node")] + public AppliesCollection? ApmAgentNode { 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")] + public AppliesCollection? ApmAgentRum { 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; } +} + public class ApplicableToConverter : IYamlTypeConverter { private static readonly string[] KnownKeys = - ["stack", "deployment", "serverless", "product", "ece", - "eck", "ess", "self", "elasticsearch", "observability","security" - ]; + [ + "stack", "deployment", "serverless", "product", + "ece", "eck", "ess", "self", + "elasticsearch", "observability", "security", + "ecctl", "curator", + "apm_agent_dotnet", "apm_agent_go", "apm_agent_java", "apm_agent_node", "apm_agent_python", "apm_agent_ruby", "apm_agent_rum", + "edot_ios", "edot_android", "edot_dotnet", "edot_java", "edot_node", "edot_php", "edot_python" + ]; public bool Accepts(Type type) => type == typeof(ApplicableTo); @@ -154,9 +213,7 @@ public class ApplicableToConverter : IYamlTypeConverter if (TryGetApplicabilityOverTime(dictionary, "stack", diagnostics, out var stackAvailability)) applicableTo.Stack = stackAvailability; - if (TryGetApplicabilityOverTime(dictionary, "product", diagnostics, out var productAvailability)) - applicableTo.Product = productAvailability; - + AssignProduct(dictionary, applicableTo, diagnostics); AssignServerless(dictionary, applicableTo, diagnostics); AssignDeploymentType(dictionary, applicableTo, diagnostics); @@ -166,6 +223,9 @@ public class ApplicableToConverter : IYamlTypeConverter if (TryGetProjectApplicability(dictionary, diagnostics, out var serverless)) applicableTo.Serverless = serverless; + if (TryGetProductApplicability(dictionary, diagnostics, out var product)) + applicableTo.ProductApplicability = product; + if (diagnostics.Count > 0) applicableTo.Diagnostics = new YamlDiagnosticsCollection(diagnostics); return applicableTo; @@ -196,6 +256,48 @@ private static void AssignDeploymentType(Dictionary dictionary, } } + private static void AssignProduct(Dictionary dictionary, ApplicableTo applicableTo, List<(Severity, string)> diagnostics) + { + if (!dictionary.TryGetValue("product", out var productValue)) + return; + + // This handles string, null, and empty string cases. + if (productValue is not Dictionary productDictionary) + { + if (TryGetApplicabilityOverTime(dictionary, "product", diagnostics, out var productAvailability)) + applicableTo.Product = productAvailability; + return; + } + + // Handle dictionary case + if (TryGetProductApplicability(productDictionary, diagnostics, out var applicability)) + applicableTo.ProductApplicability = applicability; + } + + private static void AssignServerless(Dictionary dictionary, ApplicableTo applicableTo, List<(Severity, string)> diagnostics) + { + if (!dictionary.TryGetValue("serverless", out var serverless)) + return; + + if (serverless is null || (serverless is string s && string.IsNullOrWhiteSpace(s))) + applicableTo.Serverless = ServerlessProjectApplicability.All; + else if (serverless is string serverlessString) + { + var av = AppliesCollection.TryParse(serverlessString, diagnostics, out var a) ? a : null; + applicableTo.Serverless = new ServerlessProjectApplicability + { + Elasticsearch = av, + Observability = av, + Security = av + }; + } + else if (serverless is Dictionary serverlessDictionary) + { + if (TryGetProjectApplicability(serverlessDictionary, diagnostics, out var applicability)) + applicableTo.Serverless = applicability; + } + } + private static bool TryGetDeployment(Dictionary dictionary, List<(Severity, string)> diagnostics, [NotNullWhen(true)] out DeploymentApplicability? applicability) { @@ -207,6 +309,7 @@ private static bool TryGetDeployment(Dictionary dictionary, Lis d.Ece = ece; assigned = true; } + if (TryGetApplicabilityOverTime(dictionary, "eck", diagnostics, out var eck)) { d.Eck = eck; @@ -234,30 +337,6 @@ private static bool TryGetDeployment(Dictionary dictionary, Lis return false; } - private static void AssignServerless(Dictionary dictionary, ApplicableTo applicableTo, List<(Severity, string)> diagnostics) - { - if (!dictionary.TryGetValue("serverless", out var serverless)) - return; - - if (serverless is null || (serverless is string s && string.IsNullOrWhiteSpace(s))) - applicableTo.Serverless = ServerlessProjectApplicability.All; - else if (serverless is string serverlessString) - { - var av = AppliesCollection.TryParse(serverlessString, diagnostics, out var a) ? a : null; - applicableTo.Serverless = new ServerlessProjectApplicability - { - Elasticsearch = av, - Observability = av, - Security = av - }; - } - else if (serverless is Dictionary serverlessDictionary) - { - if (TryGetProjectApplicability(serverlessDictionary, diagnostics, out var applicability)) - applicableTo.Serverless = applicability; - } - } - private static bool TryGetProjectApplicability(Dictionary dictionary, List<(Severity, string)> diagnostics, [NotNullWhen(true)] out ServerlessProjectApplicability? applicability) @@ -270,6 +349,7 @@ private static bool TryGetProjectApplicability(Dictionary dicti serverlessAvailability.Elasticsearch = elasticsearch; assigned = true; } + if (TryGetApplicabilityOverTime(dictionary, "observability", diagnostics, out var observability)) { serverlessAvailability.Observability = observability; @@ -288,6 +368,115 @@ private static bool TryGetProjectApplicability(Dictionary dicti return true; } + private static bool TryGetProductApplicability(Dictionary dictionary, + List<(Severity, string)> diagnostics, + [NotNullWhen(true)] out ProductApplicability? applicability) + { + applicability = null; + var productAvailability = new ProductApplicability(); + var assigned = false; + if (TryGetApplicabilityOverTime(dictionary, "ecctl", diagnostics, out var ecctl)) + { + productAvailability.Ecctl = ecctl; + assigned = true; + } + + if (TryGetApplicabilityOverTime(dictionary, "curator", diagnostics, out var curator)) + { + productAvailability.Curator = curator; + assigned = true; + } + + if (TryGetApplicabilityOverTime(dictionary, "apm_agent_dotnet", diagnostics, out var apmAgentDotnet)) + { + productAvailability.ApmAgentDotnet = apmAgentDotnet; + assigned = true; + } + + if (TryGetApplicabilityOverTime(dictionary, "apm_agent_go", diagnostics, out var apmAgentGo)) + { + productAvailability.ApmAgentGo = apmAgentGo; + assigned = true; + } + + if (TryGetApplicabilityOverTime(dictionary, "apm_agent_java", diagnostics, out var apmAgentJava)) + { + productAvailability.ApmAgentJava = apmAgentJava; + assigned = true; + } + + if (TryGetApplicabilityOverTime(dictionary, "apm_agent_node", diagnostics, out var apmAgentNode)) + { + productAvailability.ApmAgentNode = apmAgentNode; + assigned = true; + } + + if (TryGetApplicabilityOverTime(dictionary, "apm_agent_python", diagnostics, out var apmAgentPython)) + { + productAvailability.ApmAgentPython = apmAgentPython; + assigned = true; + } + + if (TryGetApplicabilityOverTime(dictionary, "apm_agent_ruby", diagnostics, out var apmAgentRuby)) + { + productAvailability.ApmAgentRuby = apmAgentRuby; + assigned = true; + } + + if (TryGetApplicabilityOverTime(dictionary, "apm_agent_rum", diagnostics, out var apmAgentRum)) + { + productAvailability.ApmAgentRum = apmAgentRum; + assigned = true; + } + + if (TryGetApplicabilityOverTime(dictionary, "edot_ios", diagnostics, out var edotIos)) + { + productAvailability.EdotIos = edotIos; + assigned = true; + } + + if (TryGetApplicabilityOverTime(dictionary, "edot_android", diagnostics, out var edotAndroid)) + { + productAvailability.EdotAndroid = edotAndroid; + assigned = true; + } + + if (TryGetApplicabilityOverTime(dictionary, "edot_dotnet", diagnostics, out var edotDotnet)) + { + productAvailability.EdotDotnet = edotDotnet; + assigned = true; + } + + if (TryGetApplicabilityOverTime(dictionary, "edot_java", diagnostics, out var edotJava)) + { + productAvailability.EdotJava = edotJava; + assigned = true; + } + + if (TryGetApplicabilityOverTime(dictionary, "edot_node", diagnostics, out var edotNode)) + { + productAvailability.EdotNode = edotNode; + assigned = true; + } + + if (TryGetApplicabilityOverTime(dictionary, "edot_php", diagnostics, out var edotPhp)) + { + productAvailability.EdotPhp = edotPhp; + assigned = true; + } + + if (TryGetApplicabilityOverTime(dictionary, "edot_python", diagnostics, out var edotPython)) + { + productAvailability.EdotPython = edotPython; + assigned = true; + } + + if (!assigned) + return false; + applicability = productAvailability; + return true; + } + private static bool TryGetApplicabilityOverTime(Dictionary dictionary, string key, List<(Severity, string)> diagnostics, out AppliesCollection? availability) { diff --git a/tests/authoring/Applicability/AppliesToDirective.fs b/tests/authoring/Applicability/AppliesToDirective.fs index d0e6cbcdf..0bf4bfc8e 100644 --- a/tests/authoring/Applicability/AppliesToDirective.fs +++ b/tests/authoring/Applicability/AppliesToDirective.fs @@ -41,6 +41,8 @@ serverless: security: ga 9.0.0 elasticsearch: beta 9.1.0 observability: discontinued 9.2.0 +apm_agent_dotnet: ga 9.0 +apm_agent_node: ga 10.0 ``` """ @@ -54,6 +56,10 @@ serverless: Security=AppliesCollection.op_Explicit "ga 9.0.0", Elasticsearch=AppliesCollection.op_Explicit "beta 9.1.0", Observability=AppliesCollection.op_Explicit "discontinued 9.2.0" + ), + ProductApplicability=ProductApplicability( + ApmAgentDotnet=AppliesCollection.op_Explicit "ga 9.0", + ApmAgentNode=AppliesCollection.op_Explicit "ga 10.0" ) )) diff --git a/tests/authoring/Applicability/AppliesToFrontMatter.fs b/tests/authoring/Applicability/AppliesToFrontMatter.fs index f178883e8..f6a8f6aab 100644 --- a/tests/authoring/Applicability/AppliesToFrontMatter.fs +++ b/tests/authoring/Applicability/AppliesToFrontMatter.fs @@ -179,6 +179,8 @@ applies_to: elasticsearch: beta 9.1.0 observability: discontinued 9.2.0 product: preview 9.5, discontinued 9.7 + apm_agent_dotnet: ga 9.0 + ecctl: ga 10.0 stack: ga 9.1 """ [] @@ -196,7 +198,11 @@ applies_to: Observability=AppliesCollection.op_Explicit "discontinued 9.2.0" ), Stack=AppliesCollection.op_Explicit "ga 9.1.0", - Product=AppliesCollection.op_Explicit "preview 9.5, discontinued 9.7" + Product=AppliesCollection.op_Explicit "preview 9.5, discontinued 9.7", + ProductApplicability=ProductApplicability( + ApmAgentDotnet=AppliesCollection.op_Explicit "ga 9.0", + Ecctl=AppliesCollection.op_Explicit "ga 10.0" + ) )) type ``parses empty applies_to as null`` () =