Skip to content

Commit 306941e

Browse files
committed
Simplify building nodes without intermediary nav items
1 parent 9bfedf7 commit 306941e

File tree

19 files changed

+375
-161
lines changed

19 files changed

+375
-161
lines changed

Directory.Packages.props

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,15 @@
1818
<PackageVersion Include="Amazon.Lambda.S3Events" Version="3.1.0" />
1919
<PackageVersion Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.4.4" />
2020
<PackageVersion Include="Amazon.Lambda.SQSEvents" Version="2.2.0" />
21+
<PackageVersion Include="AngleSharp" Version="1.1.2" />
2122
<PackageVersion Include="Aspire.Hosting" Version="9.4.2" />
2223
<PackageVersion Include="Aspire.Hosting.Testing" Version="9.4.2" />
2324
<PackageVersion Include="AWSSDK.Core" Version="4.0.0.2" />
2425
<PackageVersion Include="AWSSDK.SQS" Version="4.0.0.1" />
2526
<PackageVersion Include="AWSSDK.S3" Version="4.0.0.1" />
2627
<PackageVersion Include="Elastic.OpenTelemetry" Version="1.1.0" />
2728
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0" />
28-
<PackageVersion Include="Generator.Equals" Version="3.2.1" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive"/>
29+
<PackageVersion Include="Generator.Equals" Version="3.2.1" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
2930
<PackageVersion Include="KubernetesClient" Version="17.0.14" />
3031
<PackageVersion Include="Elastic.Aspire.Hosting.Elasticsearch" Version="9.3.0" />
3132
<PackageVersion Include="Elastic.Clients.Elasticsearch" Version="9.1.4" />

config/versions.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ versioning_systems:
2020
eck:
2121
base: 3.0
2222
current: 3.1.0
23+
2324
ess: *all
2425
ecs:
2526
base: 9.0

src/Elastic.ApiExplorer/Landing/LandingNavigationItem.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public class LandingNavigationItem : IApiGroupingNavigationItem<ApiLanding, INav
3636
public bool IsCrossLink => false; // API landing items are never cross-links
3737
public string Url => Index.Url;
3838
public bool Hidden => false;
39+
public Uri Identifier { get; } = new Uri("todo://");
3940

4041
public string NavigationTitle => Index.NavigationTitle;
4142

@@ -50,6 +51,9 @@ public LandingNavigationItem(string url)
5051

5152
/// <inheritdoc />
5253
public bool IsUsingNavigationDropdown => false;
54+
55+
void IAssignableChildrenNavigation.SetNavigationItems(IReadOnlyCollection<INavigationItem> navigationItems) =>
56+
throw new NotSupportedException($"{nameof(IAssignableChildrenNavigation.SetNavigationItems)} is not supported on ${nameof(ClassificationNavigationItem)}");
5357
}
5458

5559
public interface IApiGroupingNavigationItem<out TGroupingModel, out TNavigationItem> : INodeNavigationItem<TGroupingModel, TNavigationItem>
@@ -64,6 +68,7 @@ INodeNavigationItem<INavigationModel, INavigationItem> parent
6468
: IApiGroupingNavigationItem<TGroupingModel, TNavigationItem>
6569
where TGroupingModel : IApiGroupingModel
6670
where TNavigationItem : INavigationItem
71+
6772
{
6873
/// <inheritdoc />
6974
public string Url => NavigationItems.First().Url;
@@ -83,6 +88,8 @@ INodeNavigationItem<INavigationModel, INavigationItem> parent
8388
public int NavigationIndex { get; set; }
8489
public bool IsCrossLink => false; // API grouping items are never cross-links
8590

91+
public Uri Identifier { get; } = new Uri("todo://");
92+
8693
/// <inheritdoc />
8794
public int Depth => 0;
8895

@@ -108,6 +115,9 @@ public class ClassificationNavigationItem(ApiClassification classification, Land
108115

109116
/// <inheritdoc />
110117
public bool IsUsingNavigationDropdown => false;
118+
119+
void IAssignableChildrenNavigation.SetNavigationItems(IReadOnlyCollection<INavigationItem> navigationItems) =>
120+
throw new NotSupportedException($"{nameof(IAssignableChildrenNavigation.SetNavigationItems)} is not supported on ${nameof(ClassificationNavigationItem)}");
111121
}
112122

113123
public class TagNavigationItem(ApiTag tag, IRootNavigationItem<IApiGroupingModel, INavigationItem> rootNavigation, INodeNavigationItem<INavigationModel, INavigationItem> parent)

src/Elastic.Documentation.Configuration/DocSet/SiteNavigationFile.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,24 @@ public static bool ValidatePathPrefixes(IDiagnosticsCollector collector, SiteNav
5252
return valid;
5353
}
5454

55+
public static ImmutableHashSet<Uri> GetAllDeclaredSources(SiteNavigationFile siteNavigation)
56+
{
57+
var set = new HashSet<Uri>();
58+
59+
foreach (var tocRef in siteNavigation.TableOfContents)
60+
CollectSource(tocRef, set);
61+
62+
return set.ToImmutableHashSet();
63+
}
64+
private static void CollectSource(SiteTableOfContentsRef tocRef, HashSet<Uri> set)
65+
{
66+
_ = set.Add(tocRef.Source);
67+
// Recursively collect from children
68+
foreach (var child in tocRef.Children)
69+
CollectSource(child, set);
70+
}
71+
72+
5573
public static ImmutableHashSet<Uri> GetAllPathPrefixes(SiteNavigationFile siteNavigation)
5674
{
5775
var set = new HashSet<Uri>();

src/Elastic.Documentation.Links/CrossLinks/CrossLinkFetcher.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,14 @@ public static RepositoryLinks Deserialize(string json) =>
3939

4040
public abstract Task<FetchedCrossLinks> FetchCrossLinks(Cancel ctx);
4141

42+
private int _logOnce;
4243
public async Task<LinkRegistry> FetchLinkRegistry(Cancel ctx)
4344
{
45+
var result = Interlocked.Increment(ref _logOnce);
4446
if (_linkIndex is not null)
4547
{
46-
Logger.LogTrace("Using cached link index registry (link-index.json)");
48+
if (result == 1)
49+
Logger.LogTrace("Using cached link index registry (link-index.json)");
4750
return _linkIndex;
4851
}
4952

src/Elastic.Documentation.Navigation/Assembler/AssembledNavigation.cs

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
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 System.Collections.Immutable;
56
using System.Diagnostics;
67
using Elastic.Documentation.Configuration.Assembler;
78
using Elastic.Documentation.Configuration.DocSet;
@@ -18,7 +19,8 @@ public class SiteNavigation : IRootNavigationItem<IDocumentationFile, INavigatio
1819
{
1920
private readonly string? _sitePrefix;
2021

21-
public SiteNavigation(SiteNavigationFile siteNavigationFile,
22+
public SiteNavigation(
23+
SiteNavigationFile siteNavigationFile,
2224
IDocumentationContext context,
2325
IReadOnlyCollection<IDocumentationSetNavigation> documentationSetNavigations,
2426
string? sitePrefix
@@ -35,6 +37,9 @@ public SiteNavigation(SiteNavigationFile siteNavigationFile,
3537
Id = ShortId.Create("site");
3638
IsUsingNavigationDropdown = false;
3739
Phantoms = siteNavigationFile.Phantoms;
40+
DeclaredPhantoms = [.. siteNavigationFile.Phantoms.Select(p => new Uri(p.Source))];
41+
DeclaredTableOfContents = SiteNavigationFile.GetAllDeclaredSources(siteNavigationFile);
42+
3843
_nodes = [];
3944
foreach (var setNavigation in documentationSetNavigations)
4045
{
@@ -73,13 +78,21 @@ public SiteNavigation(SiteNavigationFile siteNavigationFile,
7378
Index = this.FindIndex<IDocumentationFile>(new NotFoundModel("/index.md"));
7479
}
7580

76-
private readonly Dictionary<Uri, INodeNavigationItem<IDocumentationFile, INavigationItem>> _nodes;
77-
public IReadOnlyDictionary<Uri, INodeNavigationItem<IDocumentationFile, INavigationItem>> Nodes => _nodes;
81+
public HashSet<Uri> DeclaredPhantoms { get; }
82+
83+
/// <summary> All the table of contents explicitly declared in the navigation</summary>
84+
public ImmutableHashSet<Uri> DeclaredTableOfContents { get; set; }
85+
86+
private readonly Dictionary<Uri, IRootNavigationItem<IDocumentationFile, INavigationItem>> _nodes;
87+
public IReadOnlyDictionary<Uri, IRootNavigationItem<IDocumentationFile, INavigationItem>> Nodes => _nodes;
7888

7989
private HashSet<Uri> UnseenNodes { get; }
8090

8191
public IReadOnlyCollection<PhantomRegistration> Phantoms { get; }
8292

93+
/// <inheritdoc />
94+
public Uri Identifier { get; } = new Uri("site://");
95+
8396
//TODO Obsolete?
8497
public IReadOnlyCollection<INodeNavigationItem<INavigationModel, INavigationItem>> TopLevelItems =>
8598
NavigationItems.OfType<INodeNavigationItem<INavigationModel, INavigationItem>>().ToList();
@@ -120,6 +133,9 @@ public SiteNavigation(SiteNavigationFile siteNavigationFile,
120133
/// <inheritdoc />
121134
public IReadOnlyCollection<INavigationItem> NavigationItems { get; }
122135

136+
void IAssignableChildrenNavigation.SetNavigationItems(IReadOnlyCollection<INavigationItem> navigationItems) =>
137+
throw new NotSupportedException("SetNavigationItems is not supported on SiteNavigation");
138+
123139
/// <summary>
124140
/// Normalizes the site prefix to ensure it has a leading slash and no trailing slash.
125141
/// Returns null for null or empty/whitespace input.
@@ -156,20 +172,20 @@ public SiteNavigation(SiteNavigationFile siteNavigationFile,
156172
// we allow not setting path prefixes for toc references from the narrative repository
157173
if (tocRef.Source.Scheme != NarrativeRepository.RepositoryName)
158174
context.EmitError(context.ConfigurationPath, $"path_prefix is required for TOC reference: {tocRef.Source}");
159-
160-
if (!string.IsNullOrEmpty(tocRef.Source.Host))
161-
pathPrefix += $"/{tocRef.Source.Host}";
162-
if (!string.IsNullOrEmpty(tocRef.Source.AbsolutePath) && tocRef.Source.AbsolutePath != "/")
163-
pathPrefix += $"/{tocRef.Source.AbsolutePath}";
175+
else
176+
{
177+
if (!string.IsNullOrEmpty(tocRef.Source.Host))
178+
pathPrefix += $"/{tocRef.Source.Host}";
179+
if (!string.IsNullOrEmpty(tocRef.Source.AbsolutePath) && tocRef.Source.AbsolutePath != "/")
180+
pathPrefix += $"/{tocRef.Source.AbsolutePath}";
181+
}
164182
}
165183

166184
// Normalize pathPrefix to remove leading/trailing slashes for a consistent combination
167185
pathPrefix = pathPrefix.Trim('/');
168186

169187
// Combine with site prefix if present, otherwise ensure leading slash
170-
pathPrefix = !string.IsNullOrWhiteSpace(_sitePrefix)
171-
? $"{_sitePrefix}/{pathPrefix}"
172-
: "/" + pathPrefix;
188+
pathPrefix = !string.IsNullOrWhiteSpace(_sitePrefix) ? $"{_sitePrefix}/{pathPrefix}" : "/" + pathPrefix;
173189

174190
// Look up the node in the collected nodes
175191
if (!_nodes.TryGetValue(tocRef.Source, out var node))
@@ -183,25 +199,33 @@ public SiteNavigation(SiteNavigationFile siteNavigationFile,
183199
return null;
184200
}
185201

202+
if (node.Identifier == new Uri("docs-content://reference/glossary"))
203+
{
204+
}
205+
206+
root ??= node;
207+
186208
_ = UnseenNodes.Remove(tocRef.Source);
187209
// Set the navigation index
210+
node.Parent = parent;
188211
node.NavigationIndex = index;
189-
homeAccessor.HomeProvider = new NavigationHomeProvider(pathPrefix, root ?? homeAccessor.HomeProvider.NavigationRoot);
212+
homeAccessor.HomeProvider = new NavigationHomeProvider(pathPrefix, root);
190213

191-
var wrapped = new SiteTableOfContentsNavigation<IDocumentationFile>(node, homeAccessor.HomeProvider, parent, root);
192-
parent = wrapped;
193-
root ??= wrapped.NavigationRoot;
214+
//var wrapped = new SiteTableOfContentsNavigation<IDocumentationFile>(node, homeAccessor.HomeProvider, parent, root);
215+
//parent = wrapped;
194216

195217
var children = new List<INavigationItem>();
196218
var nodeChildren = node.NavigationItems;
197219
foreach (var nodeChild in nodeChildren)
198220
{
199-
nodeChild.Parent = parent;
200-
if (nodeChild is IRootNavigationItem<IDocumentationFile, INavigationItem>)
221+
nodeChild.Parent = node;
222+
if (nodeChild is INavigationHomeAccessor childAccessor)
223+
childAccessor.HomeProvider = homeAccessor.HomeProvider;
224+
225+
if (nodeChild is IRootNavigationItem<INavigationModel, INavigationItem>)
201226
continue;
202227
children.Add(nodeChild);
203228
}
204-
// Recursively create child navigation items if children are specified
205229
if (tocRef.Children.Count > 0)
206230
{
207231
var childIndex = 0;
@@ -218,10 +242,27 @@ public SiteNavigation(SiteNavigationFile siteNavigationFile,
218242
children.Add(childItem);
219243
}
220244
}
245+
foreach (var nodeChild in nodeChildren)
246+
{
247+
if (nodeChild is not IRootNavigationItem<INavigationModel, INavigationItem> rootChild)
248+
continue;
249+
if (DeclaredTableOfContents.Contains(rootChild.Identifier) || DeclaredPhantoms.Contains(rootChild.Identifier))
250+
continue;
251+
252+
context.EmitWarning(context.ConfigurationPath, $"Navigation does not explicitly declare: {rootChild.Identifier}");
253+
}
221254

222-
wrapped.NavigationItems = children;
223-
// Always return a wrapper to ensure path_prefix is the URL (not path_prefix + node's URL)
224-
return wrapped;
255+
switch (node)
256+
{
257+
case SiteNavigation:
258+
break;
259+
case IAssignableChildrenNavigation documentationSetNavigation:
260+
documentationSetNavigation.SetNavigationItems(children);
261+
break;
262+
default:
263+
throw new Exception($"node is not a known type: {node.GetType().Name}");
264+
}
265+
return node;
225266
}
226267
}
227268

src/Elastic.Documentation.Navigation/INavigationItem.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,16 @@ public interface INodeNavigationItem<out TIndex, out TChildNavigation> : INaviga
7171
IReadOnlyCollection<TChildNavigation> NavigationItems { get; }
7272
}
7373

74-
public interface IRootNavigationItem<out TIndex, out TChildNavigation> : INodeNavigationItem<TIndex, TChildNavigation>
74+
public interface IAssignableChildrenNavigation
75+
{
76+
void SetNavigationItems(IReadOnlyCollection<INavigationItem> navigationItems);
77+
}
78+
79+
public interface IRootNavigationItem<out TIndex, out TChildNavigation> : INodeNavigationItem<TIndex, TChildNavigation>, IAssignableChildrenNavigation
7580
where TIndex : INavigationModel
7681
where TChildNavigation : INavigationItem
7782
{
7883
bool IsUsingNavigationDropdown { get; }
84+
85+
Uri Identifier { get; }
7986
}

src/Elastic.Documentation.Navigation/Isolated/CrossLinkNavigationLeaf.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,10 @@ public class CrossLinkNavigationLeaf(
1414
string url,
1515
bool hidden,
1616
INodeNavigationItem<INavigationModel, INavigationItem>? parent,
17-
INavigationHomeProvider homeProvider
17+
INavigationHomeAccessor homeAccessor
1818
)
1919
: ILeafNavigationItem<CrossLinkModel>
2020
{
21-
private readonly INavigationHomeProvider _homeProvider = homeProvider;
22-
2321
/// <inheritdoc />
2422
public CrossLinkModel Model { get; init; } = model;
2523

@@ -30,7 +28,7 @@ INavigationHomeProvider homeProvider
3028
public bool Hidden { get; init; } = hidden;
3129

3230
/// <inheritdoc />
33-
public IRootNavigationItem<INavigationModel, INavigationItem> NavigationRoot => _homeProvider.NavigationRoot;
31+
public IRootNavigationItem<INavigationModel, INavigationItem> NavigationRoot => homeAccessor.HomeProvider.NavigationRoot;
3432

3533
/// <inheritdoc />
3634
public INodeNavigationItem<INavigationModel, INavigationItem>? Parent { get; set; } = parent;

0 commit comments

Comments
 (0)