Skip to content

Commit 2126466

Browse files
committed
better grouping
1 parent 8687979 commit 2126466

File tree

6 files changed

+139
-80
lines changed

6 files changed

+139
-80
lines changed

docs/_docset.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ subs:
1717
features:
1818
primary-nav: false
1919

20-
api: kibana-openapi.json
21-
#api: elasticsearch-openapi.json
20+
#api: kibana-openapi.json
21+
api: elasticsearch-openapi.json
2222

2323
toc:
2424
- file: index.md

src/Elastic.ApiExplorer/Landing/LandingNavigationItem.cs

Lines changed: 2 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ public class EndpointNavigationItem(ApiEndpoint endpoint, IRootNavigationItem<IA
131131
public string Url => NavigationItems.First().Url;
132132

133133
/// <inheritdoc />
134-
public string NavigationTitle { get; } = endpoint.Route;
134+
public string NavigationTitle { get; } = endpoint.Operations.First().ApiName;
135135

136136
/// <inheritdoc />
137137
public IRootNavigationItem<INavigationModel, INavigationItem> NavigationRoot { get; } = rootNavigation;
@@ -149,45 +149,11 @@ public class EndpointNavigationItem(ApiEndpoint endpoint, IRootNavigationItem<IA
149149
public int Depth => 0;
150150

151151
/// <inheritdoc />
152-
public string Id { get; } = ShortId.Create(endpoint.Route);
152+
public string Id { get; } = ShortId.Create(endpoint.Operations.First().ApiName + endpoint.Operations.First().Route);
153153

154154
/// <inheritdoc />
155155
public ApiEndpoint Index { get; } = endpoint;
156156

157157
/// <inheritdoc />
158158
public IReadOnlyCollection<OperationNavigationItem> NavigationItems { get; set; } = [];
159159
}
160-
public class OperationNavigationItem : ILeafNavigationItem<ApiOperation>, IEndpointOrOperationNavigationItem
161-
{
162-
#pragma warning disable IDE0290
163-
public OperationNavigationItem(
164-
#pragma warning restore IDE0290
165-
ApiOperation apiOperation,
166-
IRootNavigationItem<IApiGroupingModel, INavigationItem> root,
167-
IApiGroupingNavigationItem<IApiGroupingModel, INavigationItem> parent
168-
)
169-
{
170-
NavigationRoot = root;
171-
Id = ShortId.Create(apiOperation.Operation.OperationId ?? apiOperation.OperationType.ToString());
172-
Model = apiOperation;
173-
NavigationTitle = apiOperation.Operation.Summary ?? $"{apiOperation.OperationType.ToString().ToLowerInvariant()} {apiOperation.Operation.OperationId}";
174-
Parent = parent;
175-
var moniker = apiOperation.Operation.OperationId ?? apiOperation.Route.Replace("}", "").Replace("{", "").Replace('/', '-');
176-
Url = $"/api/endpoints/{moniker}";
177-
}
178-
179-
public IRootNavigationItem<INavigationModel, INavigationItem> NavigationRoot { get; }
180-
//TODO enum to string
181-
public string Id { get; }
182-
public int Depth { get; } = 1;
183-
public ApiOperation Model { get; }
184-
public string Url { get; }
185-
public bool Hidden => false;
186-
187-
public string NavigationTitle { get; }
188-
189-
public INodeNavigationItem<INavigationModel, INavigationItem>? Parent { get; set; }
190-
191-
public int NavigationIndex { get; set; }
192-
193-
}

src/Elastic.ApiExplorer/OpenApiGenerator.cs

Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public record ApiTag(string Name, string Description, IReadOnlyCollection<ApiEnd
3131
public Task RenderAsync(FileSystemStream stream, ApiRenderContext context, CancellationToken ctx = default) => Task.CompletedTask;
3232
}
3333

34-
public record ApiEndpoint(string Route, IOpenApiPathItem OpenApiPath, List<ApiOperation> Operations, string? Name) : IApiGroupingModel
34+
public record ApiEndpoint(List<ApiOperation> Operations, string? Name) : IApiGroupingModel
3535
{
3636
/// <inheritdoc />
3737
public Task RenderAsync(FileSystemStream stream, ApiRenderContext context, CancellationToken ctx = default) => Task.CompletedTask;
@@ -48,6 +48,54 @@ public static LandingNavigationItem CreateNavigation(OpenApiDocument openApiDocu
4848
var url = "/api";
4949
var rootNavigation = new LandingNavigationItem(url);
5050

51+
var ops = openApiDocument.Paths
52+
.SelectMany(p => p.Value.Operations.Select(op => (Path: p, Operation: op)))
53+
.Select(pair =>
54+
{
55+
var op = pair.Operation;
56+
var extensions = op.Value.Extensions;
57+
var ns = (extensions?.TryGetValue("x-namespace", out var n) ?? false) && n is OpenApiAny anyNs
58+
? anyNs.Node.GetValue<string>()
59+
: null;
60+
var api = (extensions?.TryGetValue("x-api-name", out var a) ?? false) && a is OpenApiAny anyApi
61+
? anyApi.Node.GetValue<string>()
62+
: null;
63+
var tag = op.Value.Tags?.FirstOrDefault()?.Reference.Id;
64+
var classification = openApiDocument.Info.Title == "Elasticsearch Request & Response Specification"
65+
? ClassifyElasticsearchTag(tag ?? "unknown")
66+
: "unknown";
67+
68+
var apiString = ns is null
69+
? api ?? op.Value.Summary ?? Guid.NewGuid().ToString("N") : $"{ns}.{api}";
70+
return new
71+
{
72+
Classification = classification,
73+
Api = apiString,
74+
Tag = tag,
75+
pair.Path,
76+
pair.Operation
77+
};
78+
})
79+
.ToArray();
80+
81+
var nestedGrouping =
82+
(
83+
from op in ops
84+
group op by op.Classification
85+
into classificationGroup
86+
from tagGroup in
87+
from op in classificationGroup
88+
group op by op.Tag
89+
into apiGroups
90+
from apiGroup in
91+
from op in apiGroups
92+
group op by op.Api
93+
group apiGroup by apiGroups.Key
94+
group tagGroup by classificationGroup.Key
95+
).ToArray();
96+
97+
98+
/*
5199
var grouped = openApiDocument.Paths
52100
.Select(p =>
53101
{
@@ -60,48 +108,48 @@ public static LandingNavigationItem CreateNavigation(OpenApiDocument openApiDocu
60108
? anyApi.Node.GetValue<string>()
61109
: null;
62110
var tag = op.Value.Tags?.FirstOrDefault()?.Reference.Id;
63-
if (tag is not null)
64-
{
65-
}
66-
var classification = openApiDocument.Info.Title == "Elasticsearch Request & Response Specification" ? ClassifyElasticsearchTag(tag ?? "unknown") : "unknown";
111+
var classification = openApiDocument.Info.Title == "Elasticsearch Request & Response Specification"
112+
? ClassifyElasticsearchTag(tag ?? "unknown")
113+
: "unknown";
67114
115+
var apiString = ns is null ? api ?? Guid.NewGuid().ToString("N") : $"{ns}.{api}";
68116
return new
69117
{
70118
Classification = classification,
71-
Namespace = ns,
72-
Api = api,
119+
Api = apiString,
73120
Tag = tag,
74121
Path = p
75122
};
76123
})
77124
.GroupBy(g => g.Classification)
78125
.ToArray();
126+
*/
79127

80128
// intermediate grouping of models to create the navigation tree
81129
// this is two-phased because we need to know if an endpoint has one or more operations
82130
var classifications = new List<ApiClassification>();
83-
foreach (var group in grouped)
131+
foreach (var classificationGroup in nestedGrouping)
84132
{
85133
var tags = new List<ApiTag>();
86-
foreach (var tagGroup in group.GroupBy(g => g.Tag))
134+
foreach (var tagGroup in classificationGroup)
87135
{
88-
var endpoints = new List<ApiEndpoint>();
89-
foreach (var endpoint in tagGroup)
136+
var apis = new List<ApiEndpoint>();
137+
foreach (var apiGroup in tagGroup)
90138
{
91-
var api = endpoint.Namespace is null ? endpoint.Api ?? null : $"{endpoint.Namespace}.{endpoint.Api}";
92139
var operations = new List<ApiOperation>();
93-
foreach (var operation in endpoint.Path.Value.Operations)
140+
foreach (var api in apiGroup)
94141
{
95-
var apiOperation = new ApiOperation(operation.Key, operation.Value, endpoint.Path.Key, api);
142+
var operation = api.Operation;
143+
var apiOperation = new ApiOperation(operation.Key, operation.Value, api.Path.Key, api.Path.Value, apiGroup.Key);
96144
operations.Add(apiOperation);
97145
}
98-
var apiEndpoint = new ApiEndpoint(endpoint.Path.Key, endpoint.Path.Value, operations, api);
99-
endpoints.Add(apiEndpoint);
146+
var apiEndpoint = new ApiEndpoint(operations, apiGroup.Key);
147+
apis.Add(apiEndpoint);
100148
}
101-
var tag = new ApiTag(tagGroup.Key ?? "unknown", "", endpoints);
149+
var tag = new ApiTag(tagGroup.Key ?? "unknown", "", apis);
102150
tags.Add(tag);
103151
}
104-
var classification = new ApiClassification(group.Key, "", tags);
152+
var classification = new ApiClassification(classificationGroup.Key, "", tags);
105153
classifications.Add(classification);
106154
}
107155

@@ -171,7 +219,10 @@ List<IEndpointOrOperationNavigationItem> endpointNavigationItems
171219
var operationNavigationItems = new List<OperationNavigationItem>();
172220
foreach (var operation in endpoint.Operations)
173221
{
174-
var operationNavigationItem = new OperationNavigationItem(operation, rootNavigation, endpointNavigationItem);
222+
var operationNavigationItem = new OperationNavigationItem(operation, rootNavigation, endpointNavigationItem)
223+
{
224+
Hidden = true
225+
};
175226
operationNavigationItems.Add(operationNavigationItem);
176227
}
177228
endpointNavigationItem.NavigationItems = operationNavigationItems;
@@ -219,12 +270,12 @@ async Task RenderNavigationItems(INavigationItem currentNavigation)
219270
await RenderNavigationItems(child);
220271
}
221272

273+
#pragma warning disable IDE0045
222274
else if (currentNavigation is ILeafNavigationItem<IApiModel> leaf)
275+
#pragma warning restore IDE0045
223276
_ = await Render(leaf, leaf.Model, renderContext, navigationRenderer, ctx);
224277
else
225-
{
226-
227-
}
278+
throw new Exception($"Unknown navigation item type {currentNavigation.GetType()}");
228279
}
229280
}
230281

src/Elastic.ApiExplorer/Operations/OperationNavigationItem.cs

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

55
using System.IO.Abstractions;
6-
using Elastic.ApiExplorer.Endpoints;
76
using Elastic.ApiExplorer.Landing;
7+
using Elastic.Documentation.Extensions;
88
using Elastic.Documentation.Site.Navigation;
99
using Microsoft.OpenApi.Models;
10+
using Microsoft.OpenApi.Models.Interfaces;
1011
using RazorSlices;
1112

1213
namespace Elastic.ApiExplorer.Operations;
1314

14-
public record ApiOperation(OperationType OperationType, OpenApiOperation Operation, string Route, string? ApiName) : IApiModel
15+
public record ApiOperation(OperationType OperationType, OpenApiOperation Operation, string Route, IOpenApiPathItem Path, string ApiName) : IApiModel
1516
{
1617
public async Task RenderAsync(FileSystemStream stream, ApiRenderContext context, Cancel ctx = default)
1718
{
@@ -26,3 +27,36 @@ public async Task RenderAsync(FileSystemStream stream, ApiRenderContext context,
2627
await slice.RenderAsync(stream, cancellationToken: ctx);
2728
}
2829
}
30+
31+
public class OperationNavigationItem : ILeafNavigationItem<ApiOperation>, IEndpointOrOperationNavigationItem
32+
{
33+
public OperationNavigationItem(
34+
ApiOperation apiOperation,
35+
IRootNavigationItem<IApiGroupingModel, INavigationItem> root,
36+
IApiGroupingNavigationItem<IApiGroupingModel, INavigationItem> parent
37+
)
38+
{
39+
NavigationRoot = root;
40+
Model = apiOperation;
41+
NavigationTitle = apiOperation.ApiName;
42+
Parent = parent;
43+
var moniker = apiOperation.Operation.OperationId ?? apiOperation.Route.Replace("}", "").Replace("{", "").Replace('/', '-');
44+
Url = $"/api/endpoints/{moniker}";
45+
Id = ShortId.Create(Url);
46+
}
47+
48+
public IRootNavigationItem<INavigationModel, INavigationItem> NavigationRoot { get; }
49+
//TODO enum to string
50+
public string Id { get; }
51+
public int Depth { get; } = 1;
52+
public ApiOperation Model { get; }
53+
public string Url { get; }
54+
public bool Hidden { get; set; }
55+
56+
public string NavigationTitle { get; }
57+
58+
public INodeNavigationItem<INavigationModel, INavigationItem>? Parent { get; set; }
59+
60+
public int NavigationIndex { get; set; }
61+
62+
}

src/Elastic.ApiExplorer/Operations/OperationView.cshtml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
StaticFileContentHashProvider = Model.StaticFileContentHashProvider
2121
};
2222
}
23+
24+
@if (Model.CurrentNavigationItem.)
25+
26+
2327
<section id="elastic-docs-v3">
2428
<h1>Id: @Model.Operation.Operation.OperationId</h1>
2529
<h2>Name: @Model.Operation.ApiName</h2>

src/Elastic.Documentation.Site/Navigation/_TocTreeNav.cshtml

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
else if (item is INodeNavigationItem<INavigationModel, INavigationItem> folder)
2525
{
2626
var g = folder;
27+
var allHidden = folder.NavigationItems.All(n => n.Hidden);
2728
<li class="flex flex-wrap group-navigation @(isTopLevel ? "mt-6" : "mt-4")">
2829
<div class="peer grid grid-cols-[1fr_auto] w-full">
2930
<a
@@ -32,24 +33,27 @@
3233
class="sidebar-link pr-2 content-center @(isTopLevel ? "font-semibold" : "")">
3334
@(g.NavigationTitle)
3435
</a>
35-
<label for="@folder.Id" class="group/label flex mr-2 items-start">
36-
<div class="hover:bg-grey-20 rounded-sm p-1 cursor-pointer">
37-
<svg
38-
xmlns="http://www.w3.org/2000/svg"
39-
fill="none"
40-
viewBox="0 0 24 24"
41-
stroke-width="1.5"
42-
stroke="currentColor"
43-
class="shrink-0 -rotate-90 group-has-checked/label:rotate-0 w-3.5">
44-
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"/>
45-
</svg>
46-
</div>
47-
<input
48-
id="@folder.Id"
49-
type="checkbox"
50-
class="hidden"
51-
aria-hidden="true">
52-
</label>
36+
@if (!allHidden)
37+
{
38+
<label for="@folder.Id" class="group/label flex mr-2 items-start">
39+
<div class="hover:bg-grey-20 rounded-sm p-1 cursor-pointer">
40+
<svg
41+
xmlns="http://www.w3.org/2000/svg"
42+
fill="none"
43+
viewBox="0 0 24 24"
44+
stroke-width="1.5"
45+
stroke="currentColor"
46+
class="shrink-0 -rotate-90 group-has-checked/label:rotate-0 w-3.5">
47+
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"/>
48+
</svg>
49+
</div>
50+
<input
51+
id="@folder.Id"
52+
type="checkbox"
53+
class="hidden"
54+
aria-hidden="true">
55+
</label>
56+
}
5357
</div>
5458
@if (g.NavigationItems.Count > 0)
5559
{

0 commit comments

Comments
 (0)