Skip to content

Commit 5c7380b

Browse files
theletterfclaude
andcommitted
Wire up page: cross-link resolution in V2 nav
Add PageCrossLinkLeaf — resolves a page: URI to a canonical URL using sitePrefix + URI host + path (minus .md extension). Renders as a real clickable link via the existing ILeafNavigationItem branch in the Razor partial, distinct from PlaceholderNavigationLeaf (disabled). Also wires page: candidates into the "Ingest or migrate" section of navigation-v2.yml, replacing title: placeholders where content exists. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 99670dd commit 5c7380b

File tree

3 files changed

+104
-36
lines changed

3 files changed

+104
-36
lines changed

config/navigation-v2.yml

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -164,42 +164,56 @@ nav:
164164
children:
165165
- group: "Ingest or migrate: bring your data into Elasticsearch"
166166
children:
167-
- title: Choose/Plan your ingest method
168-
- title: Ingest architectures
167+
- page: docs-content://manage-data/ingest.md
168+
title: Choose/Plan your ingest method
169+
- page: docs-content://manage-data/ingest/ingest-reference-architectures.md
170+
title: Ingest architectures
169171
- group: Ingest by solution (New - Crosslinks)
170172
children:
171-
- title: Ingesting data for search use cases
173+
- page: docs-content://solutions/search/ingest-for-search.md
174+
title: Ingesting data for search use cases
172175
- title: Ingesting data for observability (New)
173-
- title: Ingesting data for security (Move from Solutions)
174-
- title: Ingesting time series data
176+
- page: docs-content://solutions/security/get-started/ingest-data-to-elastic-security.md
177+
title: Ingesting data for security (Move from Solutions)
178+
- page: docs-content://manage-data/ingest/ingesting-timeseries-data.md
179+
title: Ingesting time series data
175180
- title: Logs (Solutions / Obs)
176181
- group: Ingest data from applications (New)
177182
children:
178-
- title: Collect application data (Solutions / Obs)
183+
- page: docs-content://manage-data/ingest/ingesting-data-from-applications.md
184+
title: Collect application data (Solutions / Obs)
179185
- title: APM agents
180-
- title: Ingest data with Node.js
181-
- title: Ingest data with Python
182-
- title: Ingest data from Beats with Logstash as a proxy
183-
- title: Ingest data from a relational database
184-
- title: Ingest logs from a Python application using Filebeat
185-
- title: Ingest logs from a Node.js web application using Filebeat
186+
- page: docs-content://manage-data/ingest/ingesting-data-from-applications/ingest-data-with-nodejs-on-elasticsearch-service.md
187+
title: Ingest data with Node.js
188+
- page: docs-content://manage-data/ingest/ingesting-data-from-applications/ingest-data-with-python-on-elasticsearch-service.md
189+
title: Ingest data with Python
190+
- page: docs-content://manage-data/ingest/ingesting-data-from-applications/ingest-data-from-beats-to-elasticsearch-service-with-logstash-as-proxy.md
191+
title: Ingest data from Beats with Logstash as a proxy
192+
- page: docs-content://manage-data/ingest/ingesting-data-from-applications/ingest-data-from-relational-database-into-elasticsearch-service.md
193+
title: Ingest data from a relational database
194+
- page: docs-content://manage-data/ingest/ingesting-data-from-applications/ingest-logs-from-python-application-using-filebeat.md
195+
title: Ingest logs from a Python application using Filebeat
196+
- page: docs-content://manage-data/ingest/ingesting-data-from-applications/ingest-logs-from-nodejs-web-application-using-filebeat.md
197+
title: Ingest logs from a Node.js web application using Filebeat
186198
- title: Ingest data using the API (New)
187-
- title: Upload data files
188-
- title: Sample data
199+
- page: docs-content://manage-data/ingest/upload-data-files.md
200+
title: Upload data files
201+
- page: docs-content://manage-data/ingest/sample-data.md
202+
title: Sample data
189203
- title: Migrating your Elasticsearch data
190204
- group: Ingest tools (from Reference)
191205
children:
192-
- title: Fleet and Elastic Agent
193-
- title: Elastic Distributions of OpenTelemetry (EDOT)
194-
- title: Elastic Integrations
195-
- title: Content connectors
196-
- title: Logstash
197-
- title: APM
198-
- title: Beats
206+
- toc: docs-content://reference/fleet
207+
- toc: opentelemetry://reference
208+
- toc: integration-docs://reference
209+
- toc: elasticsearch://reference/search-connectors
210+
- toc: logstash://reference
211+
- toc: docs-content://reference/apm
212+
- toc: beats://reference
199213
- group: Other ingest tools
200214
children:
201-
- title: Elasticsearch for Apache Hadoop
202-
- title: Elastic Serverless Forwarder for AWS
215+
- toc: elasticsearch-hadoop://reference
216+
- toc: elastic-serverless-forwarder://reference
203217
- group: Data storage and lifecycle
204218
children:
205219
- group: The Elasticsearch data store
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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+
namespace Elastic.Documentation.Navigation.V2;
6+
7+
/// <summary>
8+
/// A real cross-link leaf in the V2 sidebar — has a URL derived from the source
9+
/// <c>page:</c> URI and the site prefix. Renders as a normal clickable link.
10+
/// </summary>
11+
public class PageCrossLinkLeaf(
12+
Uri page,
13+
string title,
14+
string sitePrefix,
15+
INodeNavigationItem<INavigationModel, INavigationItem>? parent
16+
) : ILeafNavigationItem<INavigationModel>, INavigationModel
17+
{
18+
/// <inheritdoc />
19+
public INavigationModel Model => this;
20+
21+
/// <inheritdoc />
22+
public string Url { get; } = ResolveUrl(page, sitePrefix);
23+
24+
/// <inheritdoc />
25+
public string NavigationTitle { get; } = title;
26+
27+
/// <inheritdoc />
28+
public IRootNavigationItem<INavigationModel, INavigationItem> NavigationRoot { get; } = parent?.NavigationRoot!;
29+
30+
/// <inheritdoc />
31+
public INodeNavigationItem<INavigationModel, INavigationItem>? Parent { get; set; } = parent;
32+
33+
/// <inheritdoc />
34+
public bool Hidden => false;
35+
36+
/// <inheritdoc />
37+
public int NavigationIndex { get; set; }
38+
39+
private static string ResolveUrl(Uri page, string sitePrefix)
40+
{
41+
// URI host carries the first path segment (e.g. docs-content://manage-data/ingest.md
42+
// → host="manage-data", absolutePath="/ingest.md"), so combine both.
43+
var path = (page.Host + page.AbsolutePath).Trim('/');
44+
if (path.EndsWith(".md", StringComparison.OrdinalIgnoreCase))
45+
path = path[..^3];
46+
if (path.EndsWith("/index", StringComparison.OrdinalIgnoreCase))
47+
path = path[..^6];
48+
var prefix = "/" + sitePrefix.Trim('/');
49+
return string.IsNullOrEmpty(path) ? prefix : $"{prefix}/{path}";
50+
}
51+
}

src/Elastic.Documentation.Navigation/V2/SiteNavigationV2.cs

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,24 @@ public SiteNavigationV2(
2525
IReadOnlyCollection<IDocumentationSetNavigation> documentationSetNavigations,
2626
string? sitePrefix
2727
) : base(originalFile, context, documentationSetNavigations, sitePrefix)
28-
=> V2NavigationItems = BuildV2Items(v2File.Nav, Nodes, this, depth: 0);
28+
=> V2NavigationItems = BuildV2Items(v2File.Nav, Nodes, this, depth: 0, sitePrefix: sitePrefix ?? string.Empty);
2929

3030
/// <summary>
3131
/// Label-structured navigation items for V2 sidebar rendering.
3232
/// Contains <see cref="LabelNavigationNode"/>, <see cref="PlaceholderNavigationLeaf"/>,
33-
/// and existing <see cref="IRootNavigationItem{TIndex,TChildNavigation}"/> nodes.
33+
/// <see cref="PageCrossLinkLeaf"/>, and existing <see cref="IRootNavigationItem{TIndex,TChildNavigation}"/> nodes.
3434
/// </summary>
3535
public IReadOnlyList<INavigationItem> V2NavigationItems { get; }
3636

3737
private static IReadOnlyList<INavigationItem> BuildV2Items(
3838
IReadOnlyList<INavV2Item> v2Items,
3939
IReadOnlyDictionary<Uri, IRootNavigationItem<IDocumentationFile, INavigationItem>> nodes,
4040
INodeNavigationItem<INavigationModel, INavigationItem> parent,
41-
int depth
41+
int depth,
42+
string sitePrefix
4243
) =>
4344
v2Items
44-
.Select(item => CreateV2NavigationItem(item, nodes, parent, depth))
45+
.Select(item => CreateV2NavigationItem(item, nodes, parent, depth, sitePrefix))
4546
.Where(navItem => navItem is not null)
4647
.Cast<INavigationItem>()
4748
.ToList();
@@ -50,40 +51,42 @@ int depth
5051
INavV2Item item,
5152
IReadOnlyDictionary<Uri, IRootNavigationItem<IDocumentationFile, INavigationItem>> nodes,
5253
INodeNavigationItem<INavigationModel, INavigationItem> parent,
53-
int depth
54+
int depth,
55+
string sitePrefix
5456
) =>
5557
item switch
5658
{
57-
LabelNavV2Item label => CreateLabel(label, nodes, parent, depth),
58-
GroupNavV2Item group => CreateGroup(group, nodes, parent, depth),
59+
LabelNavV2Item label => CreateLabel(label, nodes, parent, depth, sitePrefix),
60+
GroupNavV2Item group => CreateGroup(group, nodes, parent, depth, sitePrefix),
5961
TocNavV2Item toc => nodes.TryGetValue(toc.Source, out var node) ? node : null,
6062
PageNavV2Item { Page: null, Title: var title } => new PlaceholderNavigationLeaf(title ?? "Untitled", parent),
61-
PageNavV2Item { Title: var title } page => new PlaceholderNavigationLeaf(title ?? page.Page?.ToString() ?? "Unknown", parent),
63+
PageNavV2Item { Page: var page, Title: var title } => new PageCrossLinkLeaf(page!, title ?? page!.ToString(), sitePrefix, parent),
6264
_ => null
6365
};
6466

6567
private static LabelNavigationNode CreateLabel(
6668
LabelNavV2Item label,
6769
IReadOnlyDictionary<Uri, IRootNavigationItem<IDocumentationFile, INavigationItem>> nodes,
6870
INodeNavigationItem<INavigationModel, INavigationItem> parent,
69-
int depth
71+
int depth,
72+
string sitePrefix
7073
)
7174
{
72-
// Build a temporary label so it can be used as parent for its children
7375
var placeholder = new LabelNavigationNode(label.Label, label.Expanded, [], parent);
74-
var children = BuildV2Items(label.Children, nodes, placeholder, depth + 1);
76+
var children = BuildV2Items(label.Children, nodes, placeholder, depth + 1, sitePrefix);
7577
return new LabelNavigationNode(label.Label, label.Expanded, children, parent);
7678
}
7779

7880
private static PlaceholderNavigationNode CreateGroup(
7981
GroupNavV2Item group,
8082
IReadOnlyDictionary<Uri, IRootNavigationItem<IDocumentationFile, INavigationItem>> nodes,
8183
INodeNavigationItem<INavigationModel, INavigationItem> parent,
82-
int depth
84+
int depth,
85+
string sitePrefix
8386
)
8487
{
8588
var placeholder = new PlaceholderNavigationNode(group.Title, [], parent);
86-
var children = BuildV2Items(group.Children, nodes, placeholder, depth + 1);
89+
var children = BuildV2Items(group.Children, nodes, placeholder, depth + 1, sitePrefix);
8790
return new PlaceholderNavigationNode(group.Title, children, parent);
8891
}
8992
}

0 commit comments

Comments
 (0)