Skip to content

Commit 9e1cb8f

Browse files
authored
Fix/versioning dropdown (#2110)
* Improve product version inference * Don't display version dropdowns when the page refers to a versionless product * Readjust priorities and introduce test cases
1 parent d381e55 commit 9e1cb8f

File tree

9 files changed

+657
-129
lines changed

9 files changed

+657
-129
lines changed

src/Elastic.Documentation.Configuration/Versions/VersionInference.cs

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,34 @@
22
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
33
// See the LICENSE file in the project root for more information
44

5+
using Elastic.Documentation.AppliesTo;
56
using Elastic.Documentation.Configuration.LegacyUrlMappings;
67
using Elastic.Documentation.Configuration.Products;
78

89
namespace Elastic.Documentation.Configuration.Versions;
910

1011
public interface IVersionInferrerService
1112
{
12-
VersioningSystem InferVersion(string repositoryName, IReadOnlyCollection<LegacyPageMapping>? legacyPages);
13+
VersioningSystem InferVersion(string repositoryName, IReadOnlyCollection<LegacyPageMapping>? legacyPages, IReadOnlyCollection<Product>? products, ApplicableTo? applicableTo);
1314
}
1415

1516
public class ProductVersionInferrerService(ProductsConfiguration productsConfiguration, VersionsConfiguration versionsConfiguration) : IVersionInferrerService
1617
{
1718
private ProductsConfiguration ProductsConfiguration { get; } = productsConfiguration;
1819
private VersionsConfiguration VersionsConfiguration { get; } = versionsConfiguration;
19-
public VersioningSystem InferVersion(string repositoryName, IReadOnlyCollection<LegacyPageMapping>? legacyPages)
20+
public VersioningSystem InferVersion(string repositoryName, IReadOnlyCollection<LegacyPageMapping>? legacyPages, IReadOnlyCollection<Product>? products, ApplicableTo? applicableTo)
2021
{
21-
var versioning = legacyPages is not null && legacyPages.Count > 0
22-
? legacyPages.ElementAt(0).Product.VersioningSystem! // If the page has a legacy page mapping, use the versioning system of the legacy page
23-
: ProductsConfiguration.Products.TryGetValue(repositoryName, out var belonging)
22+
if (legacyPages is { Count: > 0 })
23+
return legacyPages.ElementAt(0).Product.VersioningSystem!; // If the page has legacy mappings, use the versioning system of the first mapping's product
24+
25+
if (applicableTo is not null)
26+
{
27+
var versioningFromApplicability = VersioningFromApplicability(applicableTo); // Try to infer the versioning system from the applicability metadata
28+
if (versioningFromApplicability is not null)
29+
return versioningFromApplicability;
30+
}
31+
32+
var versioning = ProductsConfiguration.Products.TryGetValue(repositoryName, out var belonging)
2433
? belonging.VersioningSystem! //If the page's docset has a name with a direct product match, use the versioning system of the product
2534
: ProductsConfiguration.Products.Values.SingleOrDefault(p =>
2635
p.Repository is not null && p.Repository.Equals(repositoryName, StringComparison.OrdinalIgnoreCase)) is { } repositoryMatch
@@ -29,11 +38,62 @@ public VersioningSystem InferVersion(string repositoryName, IReadOnlyCollection<
2938

3039
return versioning;
3140
}
41+
42+
private VersioningSystem? VersioningFromApplicability(ApplicableTo applicableTo)
43+
{
44+
// Priority 1: Product applicability
45+
var product = ProductFromApplicability(applicableTo.ProductApplicability);
46+
if (product?.VersioningSystem is not null)
47+
return product.VersioningSystem;
48+
49+
// Priority 2: Stack applicability
50+
if (applicableTo.Stack is not null)
51+
return VersionsConfiguration.VersioningSystems[VersioningSystemId.Stack];
52+
// Priority 3: Deployment applicability
53+
if (applicableTo.Deployment is not null)
54+
{
55+
var versioning = applicableTo.Deployment switch
56+
{
57+
{ Ece: not null } => VersionsConfiguration.VersioningSystems[VersioningSystemId.Ece],
58+
{ Eck: not null } => VersionsConfiguration.VersioningSystems[VersioningSystemId.Eck],
59+
{ Ess: not null } => VersionsConfiguration.VersioningSystems[VersioningSystemId.Ess],
60+
{ Self: not null } => VersionsConfiguration.VersioningSystems[VersioningSystemId.Self],
61+
_ => null
62+
};
63+
if (versioning is not null)
64+
return versioning;
65+
}
66+
67+
// Priority 4: Serverless applicability
68+
if (applicableTo.Serverless is not null)
69+
{
70+
var versioning = applicableTo.Serverless switch
71+
{
72+
{ Elasticsearch: not null } => VersionsConfiguration.VersioningSystems[VersioningSystemId.ElasticsearchProject],
73+
{ Observability: not null } => VersionsConfiguration.VersioningSystems[VersioningSystemId.ObservabilityProject],
74+
{ Security: not null } => VersionsConfiguration.VersioningSystems[VersioningSystemId.SecurityProject],
75+
_ => null
76+
};
77+
if (versioning is not null)
78+
return versioning;
79+
}
80+
81+
return null;
82+
}
83+
private Product? ProductFromApplicability(ProductApplicability? productApplicability)
84+
{
85+
if (productApplicability is null)
86+
return null;
87+
88+
var productId = ProductApplicabilityConversion.ProductApplicabilityToProductId(productApplicability);
89+
90+
return productId is null ? null : ProductsConfiguration.Products.GetValueOrDefault(productId);
91+
}
3292
}
3393

3494
public class NoopVersionInferrer : IVersionInferrerService
3595
{
36-
public VersioningSystem InferVersion(string repositoryName, IReadOnlyCollection<LegacyPageMapping>? legacyPages) => new()
96+
public VersioningSystem InferVersion(string repositoryName, IReadOnlyCollection<LegacyPageMapping>? legacyPages, IReadOnlyCollection<Product>? products, ApplicableTo? applicableTo) => new()
3797
{
3898
Id = VersioningSystemId.Stack,
3999
Base = new SemVersion(0, 0, 0),

src/Elastic.Documentation/AppliesTo/ApplicableTo.cs

Lines changed: 0 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -236,119 +236,3 @@ public override string ToString()
236236
return sb.ToString();
237237
}
238238
}
239-
240-
[YamlSerializable]
241-
public record ProductApplicability
242-
{
243-
[YamlMember(Alias = "ecctl")]
244-
public AppliesCollection? Ecctl { get; set; }
245-
246-
[YamlMember(Alias = "curator")]
247-
public AppliesCollection? Curator { get; set; }
248-
249-
[YamlMember(Alias = "apm-agent-android")]
250-
public AppliesCollection? ApmAgentAndroid { get; set; }
251-
252-
[YamlMember(Alias = "apm-agent-dotnet")]
253-
public AppliesCollection? ApmAgentDotnet { get; set; }
254-
255-
[YamlMember(Alias = "apm-agent-go")]
256-
public AppliesCollection? ApmAgentGo { get; set; }
257-
258-
[YamlMember(Alias = "apm-agent-ios")]
259-
public AppliesCollection? ApmAgentIos { get; set; }
260-
261-
[YamlMember(Alias = "apm-agent-java")]
262-
public AppliesCollection? ApmAgentJava { get; set; }
263-
264-
[YamlMember(Alias = "apm-agent-node")]
265-
public AppliesCollection? ApmAgentNode { get; set; }
266-
267-
[YamlMember(Alias = "apm-agent-php")]
268-
public AppliesCollection? ApmAgentPhp { get; set; }
269-
270-
[YamlMember(Alias = "apm-agent-python")]
271-
public AppliesCollection? ApmAgentPython { get; set; }
272-
273-
[YamlMember(Alias = "apm-agent-ruby")]
274-
public AppliesCollection? ApmAgentRuby { get; set; }
275-
276-
[YamlMember(Alias = "apm-agent-rum-js")]
277-
public AppliesCollection? ApmAgentRumJs { get; set; }
278-
279-
[YamlMember(Alias = "edot-ios")]
280-
public AppliesCollection? EdotIos { get; set; }
281-
282-
[YamlMember(Alias = "edot-android")]
283-
public AppliesCollection? EdotAndroid { get; set; }
284-
285-
[YamlMember(Alias = "edot-dotnet")]
286-
public AppliesCollection? EdotDotnet { get; set; }
287-
288-
[YamlMember(Alias = "edot-java")]
289-
public AppliesCollection? EdotJava { get; set; }
290-
291-
[YamlMember(Alias = "edot-node")]
292-
public AppliesCollection? EdotNode { get; set; }
293-
294-
[YamlMember(Alias = "edot-php")]
295-
public AppliesCollection? EdotPhp { get; set; }
296-
297-
[YamlMember(Alias = "edot-python")]
298-
public AppliesCollection? EdotPython { get; set; }
299-
300-
[YamlMember(Alias = "edot-cf-aws")]
301-
public AppliesCollection? EdotCfAws { get; set; }
302-
303-
[YamlMember(Alias = "edot-cf-azure")]
304-
public AppliesCollection? EdotCfAzure { get; set; }
305-
306-
[YamlMember(Alias = "edot-cf-gcp")]
307-
public AppliesCollection? EdotCfGcp { get; set; }
308-
309-
[YamlMember(Alias = "edot-collector")]
310-
public AppliesCollection? EdotCollector { get; set; }
311-
312-
/// <inheritdoc />
313-
public override string ToString()
314-
{
315-
var sb = new StringBuilder();
316-
var hasContent = false;
317-
318-
void AppendProduct(string name, AppliesCollection? value)
319-
{
320-
if (value is null)
321-
return;
322-
if (hasContent)
323-
_ = sb.Append(", ");
324-
_ = sb.Append(name).Append('=').Append(value);
325-
hasContent = true;
326-
}
327-
328-
AppendProduct("ecctl", Ecctl);
329-
AppendProduct("curator", Curator);
330-
AppendProduct("apm-agent-android", ApmAgentAndroid);
331-
AppendProduct("apm-agent-dotnet", ApmAgentDotnet);
332-
AppendProduct("apm-agent-go", ApmAgentGo);
333-
AppendProduct("apm-agent-ios", ApmAgentIos);
334-
AppendProduct("apm-agent-java", ApmAgentJava);
335-
AppendProduct("apm-agent-node", ApmAgentNode);
336-
AppendProduct("apm-agent-php", ApmAgentPhp);
337-
AppendProduct("apm-agent-python", ApmAgentPython);
338-
AppendProduct("apm-agent-ruby", ApmAgentRuby);
339-
AppendProduct("apm-agent-rum-js", ApmAgentRumJs);
340-
AppendProduct("edot-ios", EdotIos);
341-
AppendProduct("edot-android", EdotAndroid);
342-
AppendProduct("edot-dotnet", EdotDotnet);
343-
AppendProduct("edot-java", EdotJava);
344-
AppendProduct("edot-node", EdotNode);
345-
AppendProduct("edot-php", EdotPhp);
346-
AppendProduct("edot-python", EdotPython);
347-
AppendProduct("edot-cf-aws", EdotCfAws);
348-
AppendProduct("edot-cf-azure", EdotCfAzure);
349-
AppendProduct("edot-cf-gcp", EdotCfGcp);
350-
AppendProduct("edot-collector", EdotCollector);
351-
352-
return sb.ToString();
353-
}
354-
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
using System.Text;
6+
using YamlDotNet.Serialization;
7+
8+
namespace Elastic.Documentation.AppliesTo;
9+
10+
[YamlSerializable]
11+
public record ProductApplicability
12+
{
13+
[YamlMember(Alias = "ecctl")]
14+
public AppliesCollection? Ecctl { get; set; }
15+
16+
[YamlMember(Alias = "curator")]
17+
public AppliesCollection? Curator { get; set; }
18+
19+
[YamlMember(Alias = "apm-agent-android")]
20+
public AppliesCollection? ApmAgentAndroid { get; set; }
21+
22+
[YamlMember(Alias = "apm-agent-dotnet")]
23+
public AppliesCollection? ApmAgentDotnet { get; set; }
24+
25+
[YamlMember(Alias = "apm-agent-go")]
26+
public AppliesCollection? ApmAgentGo { get; set; }
27+
28+
[YamlMember(Alias = "apm-agent-ios")]
29+
public AppliesCollection? ApmAgentIos { get; set; }
30+
31+
[YamlMember(Alias = "apm-agent-java")]
32+
public AppliesCollection? ApmAgentJava { get; set; }
33+
34+
[YamlMember(Alias = "apm-agent-node")]
35+
public AppliesCollection? ApmAgentNode { get; set; }
36+
37+
[YamlMember(Alias = "apm-agent-php")]
38+
public AppliesCollection? ApmAgentPhp { get; set; }
39+
40+
[YamlMember(Alias = "apm-agent-python")]
41+
public AppliesCollection? ApmAgentPython { get; set; }
42+
43+
[YamlMember(Alias = "apm-agent-ruby")]
44+
public AppliesCollection? ApmAgentRuby { get; set; }
45+
46+
[YamlMember(Alias = "apm-agent-rum-js")]
47+
public AppliesCollection? ApmAgentRumJs { get; set; }
48+
49+
[YamlMember(Alias = "edot-ios")]
50+
public AppliesCollection? EdotIos { get; set; }
51+
52+
[YamlMember(Alias = "edot-android")]
53+
public AppliesCollection? EdotAndroid { get; set; }
54+
55+
[YamlMember(Alias = "edot-dotnet")]
56+
public AppliesCollection? EdotDotnet { get; set; }
57+
58+
[YamlMember(Alias = "edot-java")]
59+
public AppliesCollection? EdotJava { get; set; }
60+
61+
[YamlMember(Alias = "edot-node")]
62+
public AppliesCollection? EdotNode { get; set; }
63+
64+
[YamlMember(Alias = "edot-php")]
65+
public AppliesCollection? EdotPhp { get; set; }
66+
67+
[YamlMember(Alias = "edot-python")]
68+
public AppliesCollection? EdotPython { get; set; }
69+
70+
[YamlMember(Alias = "edot-cf-aws")]
71+
public AppliesCollection? EdotCfAws { get; set; }
72+
73+
[YamlMember(Alias = "edot-cf-azure")]
74+
public AppliesCollection? EdotCfAzure { get; set; }
75+
76+
[YamlMember(Alias = "edot-cf-gcp")]
77+
public AppliesCollection? EdotCfGcp { get; set; }
78+
79+
[YamlMember(Alias = "edot-collector")]
80+
public AppliesCollection? EdotCollector { get; set; }
81+
82+
/// <inheritdoc />
83+
public override string ToString()
84+
{
85+
var sb = new StringBuilder();
86+
var hasContent = false;
87+
88+
void AppendProduct(string name, AppliesCollection? value)
89+
{
90+
if (value is null)
91+
return;
92+
if (hasContent)
93+
_ = sb.Append(": not null } => ");
94+
_ = sb.Append(name).Append('=').Append(value);
95+
hasContent = true;
96+
}
97+
98+
AppendProduct("ecctl", Ecctl);
99+
AppendProduct("curator", Curator);
100+
AppendProduct("apm-agent-android", ApmAgentAndroid);
101+
AppendProduct("apm-agent-dotnet", ApmAgentDotnet);
102+
AppendProduct("apm-agent-go", ApmAgentGo);
103+
AppendProduct("apm-agent-ios", ApmAgentIos);
104+
AppendProduct("apm-agent-java", ApmAgentJava);
105+
AppendProduct("apm-agent-node", ApmAgentNode);
106+
AppendProduct("apm-agent-php", ApmAgentPhp);
107+
AppendProduct("apm-agent-python", ApmAgentPython);
108+
AppendProduct("apm-agent-ruby", ApmAgentRuby);
109+
AppendProduct("apm-agent-rum-js", ApmAgentRumJs);
110+
AppendProduct("edot-ios", EdotIos);
111+
AppendProduct("edot-android", EdotAndroid);
112+
AppendProduct("edot-dotnet", EdotDotnet);
113+
AppendProduct("edot-java", EdotJava);
114+
AppendProduct("edot-node", EdotNode);
115+
AppendProduct("edot-php", EdotPhp);
116+
AppendProduct("edot-python", EdotPython);
117+
AppendProduct("edot-cf-aws", EdotCfAws);
118+
AppendProduct("edot-cf-azure", EdotCfAzure);
119+
AppendProduct("edot-cf-gcp", EdotCfGcp);
120+
AppendProduct("edot-collector", EdotCollector);
121+
122+
return sb.ToString();
123+
}
124+
}
125+
126+
public static class ProductApplicabilityConversion
127+
{
128+
public static string? ProductApplicabilityToProductId(ProductApplicability p) => p switch
129+
{
130+
{ Ecctl: not null } => "cloud-control-ecctl",
131+
{ Curator: not null } => "curator",
132+
{ ApmAgentAndroid: not null } => "edot-android",
133+
{ ApmAgentDotnet: not null } => "apm-agent-dotnet",
134+
{ ApmAgentGo: not null } => "apm-agent-go",
135+
{ ApmAgentIos: not null } => "edot-ios",
136+
{ ApmAgentJava: not null } => "apm-agent-java",
137+
{ ApmAgentNode: not null } => "apm-agent-node",
138+
{ ApmAgentPhp: not null } => "apm-agent-php",
139+
{ ApmAgentPython: not null } => "apm-agent-python",
140+
{ ApmAgentRuby: not null } => "apm-agent-ruby",
141+
{ ApmAgentRumJs: not null } => "apm-agent-rum-js",
142+
{ EdotIos: not null } => "edot-ios",
143+
{ EdotAndroid: not null } => "edot-android",
144+
{ EdotDotnet: not null } => "edot-dotnet",
145+
{ EdotJava: not null } => "edot-java",
146+
{ EdotNode: not null } => "edot-node",
147+
{ EdotPhp: not null } => "edot-php",
148+
{ EdotPython: not null } => "edot-python",
149+
{ EdotCfAws: not null } => "edot-cf-aws",
150+
{ EdotCfAzure: not null } => "edot-cf-azure",
151+
{ EdotCfGcp: not null } => "edot-cf-gcp",
152+
{ EdotCollector: not null } => "edot-collector",
153+
_ => null
154+
};
155+
}

src/Elastic.Markdown/HtmlWriter.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ private async Task<RenderResult> RenderLayout(MarkdownFile markdown, MarkdownDoc
112112

113113
}
114114

115-
var pageVersioning = VersionInferrerService.InferVersion(DocumentationSet.Context.Git.RepositoryName, legacyPages);
115+
var pageVersioning = VersionInferrerService.InferVersion(DocumentationSet.Context.Git.RepositoryName, legacyPages, markdown.YamlFrontMatter?.Products, markdown.YamlFrontMatter?.AppliesTo);
116116

117117
var currentBaseVersion = $"{pageVersioning.Base.Major}.{pageVersioning.Base.Minor}+";
118118

@@ -154,6 +154,7 @@ private async Task<RenderResult> RenderLayout(MarkdownFile markdown, MarkdownDoc
154154
LegacyPages = legacyPages?.ToArray(),
155155
VersionDropdownItems = VersionDropDownItemViewModel.FromLegacyPageMappings(legacyPages?.ToArray()),
156156
Products = pageProducts,
157+
VersioningSystem = pageVersioning,
157158
VersionsConfig = DocumentationSet.Context.VersionsConfiguration,
158159
StructuredBreadcrumbsJson = structuredBreadcrumbsJsonString
159160
});

0 commit comments

Comments
 (0)