Skip to content

Commit 66e27aa

Browse files
authored
Simplify navigation interfaces and make them partially generic (#1347)
* simpify navigation model by removing wrapper navigation items * seperate navigation interfaces out more clearly * start adding generics to navigation * make root generic * Undo making root generic * remove crosslink from navigation interfaces now that pageinformation is generic * Some renaming of properties * Url and NavigationTile now powered by navigation items * more navigation interface refactorings * current nav should be nullable * small comment typo * only emit an error if file configured in toc is not markdown and is not snippetfile * Include AI generated and touched up xml docs * Address code problems flagged by rider, mostly due to improved non nullabilty of navigation items * replace temp MarkdownIndexFile property with Index which is now the generic MarkdownFile
1 parent bd6a346 commit 66e27aa

File tree

96 files changed

+321
-455
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

96 files changed

+321
-455
lines changed

src/Elastic.ApiExplorer/ApiRenderContext.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ StaticFileContentHashProvider StaticFileContentHashProvider
1717
: RenderContext<OpenApiDocument>(BuildContext, Model)
1818
{
1919
public required string NavigationHtml { get; init; }
20+
public required INavigationItem CurrentNavigation { get; init; }
2021
}

src/Elastic.ApiExplorer/ApiViewModel.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
// See the LICENSE file in the project root for more information
44

55
using Elastic.Documentation.Site.FileProviders;
6+
using Elastic.Documentation.Site.Navigation;
67

78
namespace Elastic.ApiExplorer;
89

910
public abstract class ApiViewModel
1011
{
1112
public required string NavigationHtml { get; init; }
1213
public required StaticFileContentHashProvider StaticFileContentHashProvider { get; init; }
14+
public required INavigationItem CurrentNavigationItem { get; init; }
1315
}

src/Elastic.ApiExplorer/Endpoints/ApiEndpoint.cs

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,67 +4,64 @@
44

55
using System.IO.Abstractions;
66
using Elastic.ApiExplorer.Landing;
7+
using Elastic.ApiExplorer.Operations;
78
using Elastic.Documentation.Site.Navigation;
89
using Microsoft.OpenApi.Models.Interfaces;
910
using RazorSlices;
1011

1112
namespace Elastic.ApiExplorer.Endpoints;
1213

13-
public record ApiEndpoint : IPageInformation, IPageRenderer<ApiRenderContext>
14+
public record ApiEndpoint : INavigationModel, IPageRenderer<ApiRenderContext>
1415
{
15-
public ApiEndpoint(string url, string route, IOpenApiPathItem pathValue, IGroupNavigationItem navigationRoot)
16+
public ApiEndpoint(string route, IOpenApiPathItem openApiPath)
1617
{
1718
Route = route;
18-
PathValue = pathValue;
19-
NavigationRoot = navigationRoot;
19+
OpenApiPath = openApiPath;
2020

21-
//TODO
22-
NavigationTitle = pathValue.Summary;
23-
CrossLink = pathValue.Summary;
24-
Url = url;
2521
}
2622

27-
public string NavigationTitle { get; }
28-
public string CrossLink { get; }
29-
public string Url { get; }
3023
public string Route { get; }
31-
public IOpenApiPathItem PathValue { get; }
32-
public IGroupNavigationItem NavigationRoot { get; }
24+
public IOpenApiPathItem OpenApiPath { get; }
3325

3426
public async Task RenderAsync(FileSystemStream stream, ApiRenderContext context, Cancel ctx = default)
3527
{
3628
var viewModel = new IndexViewModel
3729
{
3830
ApiEndpoint = this,
3931
StaticFileContentHashProvider = context.StaticFileContentHashProvider,
40-
NavigationHtml = context.NavigationHtml
32+
NavigationHtml = context.NavigationHtml,
33+
CurrentNavigationItem = context.CurrentNavigation
34+
4135
};
4236
var slice = EndpointView.Create(viewModel);
4337
await slice.RenderAsync(stream, cancellationToken: ctx);
4438
}
4539
}
4640

47-
public class EndpointNavigationItem : IGroupNavigationItem
41+
public class EndpointNavigationItem : INodeNavigationItem<ApiEndpoint, OperationNavigationItem>
4842
{
49-
public EndpointNavigationItem(int depth, ApiEndpoint apiEndpoint, IGroupNavigationItem? parent, LandingNavigationItem root)
43+
public EndpointNavigationItem(int depth, string url, ApiEndpoint apiEndpoint, LandingNavigationItem parent, LandingNavigationItem root)
5044
{
5145
Parent = parent;
5246
Depth = depth;
53-
//Current = group.Current;
5447
NavigationRoot = root;
5548
Id = NavigationRoot.Id;
5649

5750
Index = apiEndpoint;
58-
Current = apiEndpoint;
59-
Endpoint = apiEndpoint;
51+
Url = url;
52+
//TODO
53+
NavigationTitle = apiEndpoint.OpenApiPath.Summary;
6054
}
6155

62-
public IGroupNavigationItem NavigationRoot { get; }
6356
public string Id { get; }
64-
public IGroupNavigationItem? Parent { get; set; }
6557
public int Depth { get; }
66-
public IPageInformation? Current { get; }
67-
public IPageInformation? Index { get; }
68-
public ApiEndpoint Endpoint { get; }
69-
public IReadOnlyCollection<INavigationItem> NavigationItems { get; set; } = [];
58+
public ApiEndpoint Index { get; }
59+
public string Url { get; }
60+
public string NavigationTitle { get; }
61+
62+
public IReadOnlyCollection<OperationNavigationItem> NavigationItems { get; set; } = [];
63+
64+
public INodeNavigationItem<INavigationModel, INavigationItem> NavigationRoot { get; }
65+
66+
public INodeNavigationItem<INavigationModel, INavigationItem>? Parent { get; set; }
7067
}

src/Elastic.ApiExplorer/Endpoints/EndpointView.cshtml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
Description = "",
1010
Layout = null,
1111
PageTocItems = [],
12-
CurrentDocument = Model.ApiEndpoint,
12+
CurrentNavigationItem = Model.CurrentNavigationItem,
1313
Previous = null,
1414
Next = null,
1515
NavigationHtml = Model.NavigationHtml,
@@ -29,5 +29,5 @@
2929
};
3030
}
3131
<section id="elastic-docs-v3">
32-
<h1>@Model.ApiEndpoint.Url</h1>
32+
<h1>@Model.CurrentNavigationItem.Url</h1>
3333
</section>

src/Elastic.ApiExplorer/Landing/LandingNavigationItem.cs

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,15 @@
33
// See the LICENSE file in the project root for more information
44

55
using System.IO.Abstractions;
6+
using Elastic.ApiExplorer.Endpoints;
67
using Elastic.Documentation.Extensions;
78
using Elastic.Documentation.Site.Navigation;
89
using RazorSlices;
910

1011
namespace Elastic.ApiExplorer.Landing;
1112

12-
public class ApiLanding(IGroupNavigationItem root, string url) : IPageInformation, IPageRenderer<ApiRenderContext>
13+
public class ApiLanding : INavigationModel, IPageRenderer<ApiRenderContext>
1314
{
14-
public IGroupNavigationItem NavigationRoot { get; } = root;
15-
public string Url { get; } = url;
16-
17-
//TODO
18-
public string NavigationTitle { get; } = "API Documentation";
19-
public string CrossLink { get; } = string.Empty;
20-
2115
public async Task RenderAsync(FileSystemStream stream, ApiRenderContext context, Cancel ctx = default)
2216
{
2317
var viewModel = new LandingViewModel
@@ -26,34 +20,36 @@ public async Task RenderAsync(FileSystemStream stream, ApiRenderContext context,
2620
StaticFileContentHashProvider = context.StaticFileContentHashProvider,
2721
NavigationHtml = context.NavigationHtml,
2822
ApiInfo = context.Model.Info,
23+
CurrentNavigationItem = context.CurrentNavigation
2924
};
3025
var slice = LandingView.Create(viewModel);
3126
await slice.RenderAsync(stream, cancellationToken: ctx);
3227
}
3328
}
3429

35-
public class LandingNavigationItem : IGroupNavigationItem
30+
public class LandingNavigationItem : INodeNavigationItem<ApiLanding, EndpointNavigationItem>
3631
{
37-
public IGroupNavigationItem NavigationRoot { get; }
32+
public INodeNavigationItem<INavigationModel, INavigationItem> NavigationRoot { get; }
3833
public string Id { get; }
39-
public IGroupNavigationItem? Parent { get; set; }
4034
public int Depth { get; }
41-
public IPageInformation Current { get; set; }
42-
public IPageInformation Index { get; set; }
43-
public ApiLanding Landing { get; set; }
44-
public IReadOnlyCollection<INavigationItem> NavigationItems { get; set; } = [];
35+
public ApiLanding Index { get; }
36+
public INodeNavigationItem<INavigationModel, INavigationItem>? Parent { get; set; }
37+
public IReadOnlyCollection<EndpointNavigationItem> NavigationItems { get; set; } = [];
38+
public string Url { get; }
39+
40+
//TODO
41+
public string NavigationTitle { get; } = "API Documentation";
42+
4543

4644
public LandingNavigationItem(string url)
4745
{
48-
Parent = null;
4946
Depth = 0;
5047
NavigationRoot = this;
5148
Id = ShortId.Create("root");
5249

53-
var landing = new ApiLanding(this, url);
50+
var landing = new ApiLanding();
51+
Url = url;
5452

5553
Index = landing;
56-
Current = landing;
57-
Landing = landing;
5854
}
5955
}

src/Elastic.ApiExplorer/Landing/LandingView.cshtml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
Description = "",
1010
Layout = null,
1111
PageTocItems = [],
12-
CurrentDocument = Model.Landing,
12+
CurrentNavigationItem = Model.CurrentNavigationItem,
1313
Previous = null,
1414
Next = null,
1515
NavigationHtml = Model.NavigationHtml,

src/Elastic.ApiExplorer/OpenApiGenerator.cs

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@ public static LandingNavigationItem CreateNavigation(OpenApiDocument openApiDocu
2929
foreach (var path in openApiDocument.Paths)
3030
{
3131
var endpointUrl = $"{url}/{path.Key.Trim('/').Replace('/', '-').Replace("{", "").Replace("}", "")}";
32-
var apiEndpoint = new ApiEndpoint(endpointUrl, path.Key, path.Value, rootNavigation);
33-
var endpointNavigationItem = new EndpointNavigationItem(1, apiEndpoint, rootNavigation, rootNavigation);
32+
var apiEndpoint = new ApiEndpoint(path.Key, path.Value);
33+
var endpointNavigationItem = new EndpointNavigationItem(1, endpointUrl, apiEndpoint, rootNavigation, rootNavigation);
3434
var endpointNavigationItems = new List<OperationNavigationItem>();
3535
foreach (var operation in path.Value.Operations)
3636
{
3737
var operationUrl = $"{endpointUrl}/{operation.Key.ToString().ToLowerInvariant()}";
38-
var apiOperation = new ApiOperation(operationUrl, operation.Key, operation.Value, rootNavigation);
39-
var navigation = new OperationNavigationItem(2, apiOperation, endpointNavigationItem, rootNavigation);
38+
var apiOperation = new ApiOperation(operation.Key, operation.Value);
39+
var navigation = new OperationNavigationItem(2, operationUrl, apiOperation, endpointNavigationItem, rootNavigation);
4040
endpointNavigationItems.Add(navigation);
4141
}
4242

@@ -65,32 +65,33 @@ public async Task Generate(Cancel ctx = default)
6565

6666
var renderContext = new ApiRenderContext(context, openApiDocument, _contentHashProvider)
6767
{
68-
NavigationHtml = navigationHtml
68+
NavigationHtml = navigationHtml,
69+
CurrentNavigation = navigation,
6970
};
70-
_ = await Render(navigation.Landing, renderContext, ctx);
71-
foreach (var endpoint in navigation.NavigationItems.OfType<EndpointNavigationItem>())
71+
_ = await Render(navigation.Index, renderContext, ctx);
72+
foreach (var endpoint in navigation.NavigationItems)
7273
{
73-
_ = await Render(endpoint.Endpoint, renderContext, ctx);
74-
foreach (var operation in endpoint.NavigationItems.OfType<OperationNavigationItem>())
75-
_ = await Render(operation.Operation, renderContext, ctx);
74+
_ = await Render(endpoint.Index, renderContext, ctx);
75+
foreach (var operation in endpoint.NavigationItems)
76+
_ = await Render(operation.Model, renderContext, ctx);
7677
}
7778
}
7879

7980
private async Task<IFileInfo> Render<T>(T page, ApiRenderContext renderContext, Cancel ctx)
80-
where T : IPageInformation, IPageRenderer<ApiRenderContext>
81+
where T : INavigationModel, IPageRenderer<ApiRenderContext>
8182
{
82-
var outputFile = OutputFile(page);
83+
var outputFile = OutputFile(renderContext.CurrentNavigation);
8384
if (!outputFile.Directory!.Exists)
8485
outputFile.Directory.Create();
8586

8687
await using var stream = _writeFileSystem.FileStream.New(outputFile.FullName, FileMode.OpenOrCreate);
8788
await page.RenderAsync(stream, renderContext, ctx);
8889
return outputFile;
8990

90-
IFileInfo OutputFile(IPageInformation pageInformation)
91+
IFileInfo OutputFile(INavigationItem currentNavigation)
9192
{
9293
const string indexHtml = "index.html";
93-
var fileName = pageInformation.Url + "/" + indexHtml;
94+
var fileName = currentNavigation.Url + "/" + indexHtml;
9495
var fileInfo = _writeFileSystem.FileInfo.New(Path.Combine(context.DocumentationOutputDirectory.FullName, fileName.Trim('/')));
9596
return fileInfo;
9697
}

src/Elastic.ApiExplorer/Operations/OperationNavigationItem.cs

Lines changed: 18 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,69 +3,52 @@
33
// See the LICENSE file in the project root for more information
44

55
using System.IO.Abstractions;
6+
using Elastic.ApiExplorer.Endpoints;
67
using Elastic.ApiExplorer.Landing;
78
using Elastic.Documentation.Site.Navigation;
89
using Microsoft.OpenApi.Models;
910
using RazorSlices;
1011

1112
namespace Elastic.ApiExplorer.Operations;
1213

13-
public record ApiOperation : IPageInformation, IPageRenderer<ApiRenderContext>
14+
public record ApiOperation(OperationType OperationType, OpenApiOperation Operation) : INavigationModel, IPageRenderer<ApiRenderContext>
1415
{
15-
public ApiOperation(string url, OperationType operationType, OpenApiOperation operation, IGroupNavigationItem navigationRoot)
16-
{
17-
OperationType = operationType;
18-
Operation = operation;
19-
NavigationRoot = navigationRoot;
20-
21-
//TODO
22-
NavigationTitle = $"{operationType.ToString().ToLowerInvariant()} {operation.OperationId}";
23-
CrossLink = "";
24-
Url = url;
25-
}
26-
27-
public string NavigationTitle { get; }
28-
public string CrossLink { get; }
29-
public string Url { get; }
30-
31-
public OperationType OperationType { get; }
32-
public OpenApiOperation Operation { get; }
33-
public IGroupNavigationItem NavigationRoot { get; }
34-
35-
public async Task RenderAsync(FileSystemStream stream, ApiRenderContext context, CancellationToken ctx = default)
16+
public async Task RenderAsync(FileSystemStream stream, ApiRenderContext context, Cancel ctx = default)
3617
{
3718
var viewModel = new OperationViewModel
3819
{
3920
Operation = this,
4021
StaticFileContentHashProvider = context.StaticFileContentHashProvider,
41-
NavigationHtml = context.NavigationHtml
22+
NavigationHtml = context.NavigationHtml,
23+
CurrentNavigationItem = context.CurrentNavigation
4224
};
4325
var slice = OperationView.Create(viewModel);
4426
await slice.RenderAsync(stream, cancellationToken: ctx);
4527
}
4628
}
4729

48-
public class OperationNavigationItem : IGroupNavigationItem
30+
public class OperationNavigationItem : ILeafNavigationItem<ApiOperation>
4931
{
50-
public OperationNavigationItem(int depth, ApiOperation apiOperation, IGroupNavigationItem? parent, LandingNavigationItem root)
32+
public OperationNavigationItem(int depth, string url, ApiOperation apiOperation, EndpointNavigationItem parent, LandingNavigationItem root)
5133
{
5234
Parent = parent;
5335
Depth = depth;
5436
//Current = group.Current;
5537
NavigationRoot = root;
5638
Id = NavigationRoot.Id;
57-
58-
Index = apiOperation;
59-
Current = apiOperation;
60-
Operation = apiOperation;
39+
Model = apiOperation;
40+
Url = url;
41+
//TODO
42+
NavigationTitle = $"{apiOperation.OperationType.ToString().ToLowerInvariant()} {apiOperation.Operation.OperationId}";
6143
}
6244

63-
public IGroupNavigationItem NavigationRoot { get; }
45+
public INodeNavigationItem<INavigationModel, INavigationItem> NavigationRoot { get; }
6446
public string Id { get; }
65-
public IGroupNavigationItem? Parent { get; set; }
6647
public int Depth { get; }
67-
public IPageInformation Current { get; }
68-
public IPageInformation Index { get; }
69-
public IReadOnlyCollection<INavigationItem> NavigationItems { get; set; } = [];
70-
public ApiOperation Operation { get; set; }
48+
public ApiOperation Model { get; }
49+
public string Url { get; }
50+
51+
public string NavigationTitle { get; }
52+
53+
public INodeNavigationItem<INavigationModel, INavigationItem>? Parent { get; set; }
7154
}

src/Elastic.ApiExplorer/Operations/OperationView.cshtml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
Description = "",
1010
Layout = null,
1111
PageTocItems = [],
12-
CurrentDocument = Model.Operation,
12+
CurrentNavigationItem = Model.CurrentNavigationItem,
1313
Previous = null,
1414
Next = null,
1515
NavigationHtml = Model.NavigationHtml,

src/Elastic.Documentation.Configuration/Assembler/AssemblyConfiguration.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
// See the LICENSE file in the project root for more information
44

55
using System.Text.RegularExpressions;
6-
using Elastic.Documentation.Serialization;
76
using YamlDotNet.Serialization;
87
using YamlStaticContext = Elastic.Documentation.Configuration.Serialization.YamlStaticContext;
98

src/Elastic.Documentation.LinkIndex/LinkIndexReader.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
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.Net;
65
using Amazon.Runtime;
76
using Amazon.S3;
87
using Amazon.S3.Model;
@@ -15,7 +14,7 @@ public class Aws3LinkIndexReader(IAmazonS3 s3Client, string bucketName = "elasti
1514

1615
// <summary>
1716
// Using <see cref="AnonymousAWSCredentials"/> to access the link index
18-
// allows to read from the link index without the need to provide AWS credentials.
17+
// allows reading from the link index without the need to provide AWS credentials.
1918
// </summary>
2019
public static Aws3LinkIndexReader CreateAnonymous()
2120
{

src/Elastic.Documentation.LinkIndex/LinkIndexReaderWriter.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
// See the LICENSE file in the project root for more information
44

55
using System.Net;
6-
using Amazon.Runtime;
76
using Amazon.S3;
87
using Amazon.S3.Model;
98
using Elastic.Documentation.Links;

0 commit comments

Comments
 (0)