Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/bootstrap/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,6 @@ runs:
- uses: actions/setup-node@v4
with:
cache: npm
cache-dependency-path: src/Elastic.Markdown/package-lock.json
cache-dependency-path: src/Elastic.Documentation.Site/package-lock.json
node-version-file: .nvmrc

4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ jobs:
runs-on: ubuntu-latest
defaults:
run:
working-directory: src/Elastic.Markdown
working-directory: src/Elastic.Documentation.Site
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
cache: npm
cache-dependency-path: src/Elastic.Markdown/package-lock.json
cache-dependency-path: src/Elastic.Documentation.Site/package-lock.json
node-version-file: .nvmrc

- name: Install dependencies
Expand Down
15 changes: 10 additions & 5 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
<PackageVersion Include="AWSSDK.S3" Version="4.0.0.1" />
<PackageVersion Include="FakeItEasy" Version="8.3.0" />
<PackageVersion Include="Elastic.Ingest.Elasticsearch" Version="0.11.3" />
<PackageVersion Include="Microsoft.OpenApi" Version="2.0.0-preview9" />
<PackageVersion Include="System.Text.Json" Version="9.0.5" />
</ItemGroup>
<!-- Build -->
<ItemGroup>
Expand All @@ -40,7 +42,7 @@
<PackageVersion Include="Markdig" Version="0.41.1" />
<PackageVersion Include="NetEscapades.EnumGenerators" Version="1.0.0-beta12" PrivateAssets="all" ExcludeAssets="runtime" />
<PackageVersion Include="Proc" Version="0.9.1" />
<PackageVersion Include="RazorSlices" Version="0.9.1" />
<PackageVersion Include="RazorSlices" Version="0.9.0" />
<PackageVersion Include="Samboy063.Tomlet" Version="6.0.0" />
<PackageVersion Include="Slugify.Core" Version="4.0.1" />
<PackageVersion Include="SoftCircuits.IniFileParser" Version="2.7.0" />
Expand All @@ -55,14 +57,17 @@
<ItemGroup>
<PackageVersion Include="AngleSharp.Diffing" Version="1.0.0" />
<PackageVersion Include="DiffPlex" Version="1.7.2" />
<PackageVersion Include="FluentAssertions" Version="6.12.1" />
<PackageVersion Include="FluentAssertions" Version="7.2.0" />
<PackageVersion Include="FsUnit.xUnit" Version="7.0.1" />
<PackageVersion Include="GitHubActionsTestLogger" Version="2.4.1" />
<PackageVersion Include="JetBrains.Annotations" Version="2024.3.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="22.0.14" />
<PackageVersion Include="Unquote" Version="7.0.1" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.2" />
<PackageVersion Include="xunit.v3" Version="1.1.0" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageVersion>
<PackageVersion Include="xunit.v3" Version="2.0.2" />
</ItemGroup>
</Project>
21 changes: 21 additions & 0 deletions docs-builder.sln
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assembler-config-validate",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elastic.Documentation.LinkIndex", "src\Elastic.Documentation.LinkIndex\Elastic.Documentation.LinkIndex.csproj", "{FD1AC230-798B-4AB9-8CE6-A06264885DBC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elastic.ApiExplorer", "src\Elastic.ApiExplorer\Elastic.ApiExplorer.csproj", "{C883AC18-7C6A-482E-A9D7-C44DF8633425}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elastic.ApiExplorer.Tests", "tests\Elastic.ApiExplorer.Tests\Elastic.ApiExplorer.Tests.csproj", "{0331559E-4ED1-4A56-9C35-3EAD4D7E696D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elastic.Documentation.Site", "src\Elastic.Documentation.Site\Elastic.Documentation.Site.csproj", "{89B83007-71E6-4B57-BA78-2544BFA476DB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -166,6 +172,18 @@ Global
{FD1AC230-798B-4AB9-8CE6-A06264885DBC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FD1AC230-798B-4AB9-8CE6-A06264885DBC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FD1AC230-798B-4AB9-8CE6-A06264885DBC}.Release|Any CPU.Build.0 = Release|Any CPU
{C883AC18-7C6A-482E-A9D7-C44DF8633425}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C883AC18-7C6A-482E-A9D7-C44DF8633425}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C883AC18-7C6A-482E-A9D7-C44DF8633425}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C883AC18-7C6A-482E-A9D7-C44DF8633425}.Release|Any CPU.Build.0 = Release|Any CPU
{0331559E-4ED1-4A56-9C35-3EAD4D7E696D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0331559E-4ED1-4A56-9C35-3EAD4D7E696D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0331559E-4ED1-4A56-9C35-3EAD4D7E696D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0331559E-4ED1-4A56-9C35-3EAD4D7E696D}.Release|Any CPU.Build.0 = Release|Any CPU
{89B83007-71E6-4B57-BA78-2544BFA476DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{89B83007-71E6-4B57-BA78-2544BFA476DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{89B83007-71E6-4B57-BA78-2544BFA476DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{89B83007-71E6-4B57-BA78-2544BFA476DB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{4D198E25-C211-41DC-9E84-B15E89BD7048} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A}
Expand All @@ -191,5 +209,8 @@ Global
{FB1C1954-D8E2-4745-BA62-04DD82FB4792} = {245023D2-D3CA-47B9-831D-DAB91A2FFDC7}
{E20FEEF9-1D1A-4CDA-A546-7FDC573BE399} = {245023D2-D3CA-47B9-831D-DAB91A2FFDC7}
{FD1AC230-798B-4AB9-8CE6-A06264885DBC} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A}
{C883AC18-7C6A-482E-A9D7-C44DF8633425} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A}
{0331559E-4ED1-4A56-9C35-3EAD4D7E696D} = {67B576EE-02FA-4F9B-94BC-3630BC09ECE5}
{89B83007-71E6-4B57-BA78-2544BFA476DB} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A}
EndGlobalSection
EndGlobal
139 changes: 139 additions & 0 deletions src/Elastic.ApiExplorer/Class1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Licensed to Elasticsearch B.V under one or more agreements.
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information

using System.IO.Abstractions;
using Elastic.ApiExplorer.Endpoints;
using Elastic.ApiExplorer.Landing;
using Elastic.ApiExplorer.Operations;
using Elastic.Documentation.Configuration;
using Elastic.Documentation.Site.FileProviders;
using Elastic.Documentation.Site.Navigation;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Reader;

namespace Elastic.ApiExplorer;

public static class OpenApiReader
{
public static async Task<OpenApiDocument> Create()
{
var settings = new OpenApiReaderSettings
{
LeaveStreamOpen = false
};
await using var fs = File.Open("/Users/mpdreamz/Projects/docs-builder/src/Elastic.ApiExplorer/elasticsearch-openapi.json", FileMode.Open);
var openApiDocument = await OpenApiDocument.LoadAsync(fs, settings: settings);
return openApiDocument.Document;
}
}

public interface IPageRenderer<in T>
{
Task RenderAsync(FileSystemStream stream, T context, CancellationToken ctx = default);
}

public interface IRenderContext<out T>
{
BuildContext BuildContext { get; }
T Model { get; }
}

public record RenderContext<T>(BuildContext BuildContext, T Model) : IRenderContext<T>;

public abstract class ApiViewModel
{
public required string NavigationHtml { get; init; }
public required StaticFileContentHashProvider StaticFileContentHashProvider { get; init; }
}


public record ApiRenderContext(
BuildContext BuildContext,
OpenApiDocument Model,
StaticFileContentHashProvider StaticFileContentHashProvider
)
: RenderContext<OpenApiDocument>(BuildContext, Model)
{
public required string NavigationHtml { get; init; }
}

public class OpenApiGenerator(BuildContext context, ILoggerFactory logger)
{
private readonly ILogger _logger = logger.CreateLogger<OpenApiGenerator>();
private readonly IFileSystem _writeFileSystem = context.WriteFileSystem;
private readonly StaticFileContentHashProvider _contentHashProvider = new(new EmbeddedOrPhysicalFileProvider(context));

public static LandingNavigationItem CreateNavigation(OpenApiDocument openApiDocument)
{
var url = "/api";
var rootNavigation = new LandingNavigationItem(url);
var rootItems = new List<EndpointNavigationItem>();

foreach (var path in openApiDocument.Paths)
{
var endpointUrl = $"{url}/{path.Key.Trim('/').Replace('/', '-').Replace("{", "").Replace("}", "")}";
var apiEndpoint = new ApiEndpoint(endpointUrl, path.Key, path.Value, rootNavigation);
var endpointNavigationItem = new EndpointNavigationItem(1, apiEndpoint, rootNavigation, rootNavigation);
var endpointNavigationItems = new List<OperationNavigationItem>();
foreach (var operation in path.Value.Operations)
{
var operationUrl = $"{endpointUrl}/{operation.Key.ToString().ToLowerInvariant()}";
var apiOperation = new ApiOperation(operationUrl, operation.Key, operation.Value, rootNavigation);
var navigation = new OperationNavigationItem(2, apiOperation, endpointNavigationItem, rootNavigation);
endpointNavigationItems.Add(navigation);
}

endpointNavigationItem.NavigationItems = endpointNavigationItems;
rootItems.Add(endpointNavigationItem);
}

rootNavigation.NavigationItems = rootItems;
return rootNavigation;
}

public async Task Generate(Cancel ctx = default)
{
var openApiDocument = await OpenApiReader.Create();
var navigation = CreateNavigation(openApiDocument);
_logger.LogInformation("Generating OpenApiDocument {Title}", openApiDocument.Info.Title);

var navigationRenderer = new IsolatedBuildNavigationHtmlWriter(context, navigation);
var navigationHtml = await navigationRenderer.RenderNavigation(navigation, new Uri("http://ignored.example"), ctx);

var renderContext = new ApiRenderContext(context, openApiDocument, _contentHashProvider)
{
NavigationHtml = navigationHtml
};
_ = await Render(navigation.Landing, renderContext, ctx);
foreach (var endpoint in navigation.NavigationItems.OfType<EndpointNavigationItem>())
{
_ = await Render(endpoint.Endpoint, renderContext, ctx);
foreach (var operation in endpoint.NavigationItems.OfType<OperationNavigationItem>())
_ = await Render(operation.Operation, renderContext, ctx);
}
}

private async Task<IFileInfo> Render<T>(T page, ApiRenderContext renderContext, Cancel ctx)
where T : IPageInformation, IPageRenderer<ApiRenderContext>
{
var outputFile = OutputFile(page);
if (!outputFile.Directory!.Exists)
outputFile.Directory.Create();

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

IFileInfo OutputFile(IPageInformation pageInformation)
{
const string indexHtml = "index.html";
var fileName = pageInformation.Url + "/" + indexHtml;
var fileInfo = _writeFileSystem.FileInfo.New(Path.Combine(context.DocumentationOutputDirectory.FullName, fileName.Trim('/')));
return fileInfo;
}
}
}


21 changes: 21 additions & 0 deletions src/Elastic.ApiExplorer/Elastic.ApiExplorer.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.OpenApi"/>
<PackageReference Include="RazorSlices"/>
<PackageReference Include="System.Text.Json"/>
<PackageReference Include="Utf8StreamReader"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Elastic.Documentation.Site\Elastic.Documentation.Site.csproj"/>
<ProjectReference Include="..\Elastic.Documentation.Configuration\Elastic.Documentation.Configuration.csproj"/>
</ItemGroup>

</Project>
70 changes: 70 additions & 0 deletions src/Elastic.ApiExplorer/Endpoints/ApiEndpoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Licensed to Elasticsearch B.V under one or more agreements.
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information

using System.IO.Abstractions;
using Elastic.ApiExplorer.Landing;
using Elastic.Documentation.Site.Navigation;
using Microsoft.OpenApi.Models.Interfaces;
using RazorSlices;

namespace Elastic.ApiExplorer.Endpoints;

public record ApiEndpoint : IPageInformation, IPageRenderer<ApiRenderContext>
{
public ApiEndpoint(string url, string route, IOpenApiPathItem pathValue, IGroupNavigationItem navigationRoot)
{
Route = route;
PathValue = pathValue;
NavigationRoot = navigationRoot;

//TODO
NavigationTitle = pathValue.Summary;
CrossLink = pathValue.Summary;
Url = url;
}

public string NavigationTitle { get; }
public string CrossLink { get; }
public string Url { get; }
public string Route { get; }
public IOpenApiPathItem PathValue { get; }
public IGroupNavigationItem NavigationRoot { get; }

public async Task RenderAsync(FileSystemStream stream, ApiRenderContext context, Cancel ctx = default)
{
var viewModel = new IndexViewModel
{
ApiEndpoint = this,
StaticFileContentHashProvider = context.StaticFileContentHashProvider,
NavigationHtml = context.NavigationHtml
};
var slice = Endpoints.EndpointView.Create(viewModel);

Check failure on line 42 in src/Elastic.ApiExplorer/Endpoints/ApiEndpoint.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

Name can be simplified

Check failure on line 42 in src/Elastic.ApiExplorer/Endpoints/ApiEndpoint.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Name can be simplified

Check failure on line 42 in src/Elastic.ApiExplorer/Endpoints/ApiEndpoint.cs

View workflow job for this annotation

GitHub Actions / build (macos-latest)

Name can be simplified
await slice.RenderAsync(stream, cancellationToken: ctx);
}
}

public class EndpointNavigationItem : IGroupNavigationItem
{
public EndpointNavigationItem(int depth, ApiEndpoint apiEndpoint, IGroupNavigationItem? parent, LandingNavigationItem root)
{
Parent = parent;
Depth = depth;
//Current = group.Current;
NavigationRoot = root;
Id = NavigationRoot.Id;

Index = apiEndpoint;
Current = apiEndpoint;
Endpoint = apiEndpoint;
}

public IGroupNavigationItem NavigationRoot { get; }
public string Id { get; }
public IGroupNavigationItem? Parent { get; set; }
public int Depth { get; }
public IPageInformation? Current { get; }
public IPageInformation? Index { get; }
public ApiEndpoint Endpoint { get; }
public IReadOnlyCollection<INavigationItem> NavigationItems { get; set; } = [];
}
33 changes: 33 additions & 0 deletions src/Elastic.ApiExplorer/Endpoints/EndpointView.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
@using Elastic.Documentation.Configuration.Assembler
@using Elastic.Documentation.Configuration.Builder
@inherits RazorSliceHttpResult<IndexViewModel>
@implements IUsesLayout<Elastic.ApiExplorer._Layout, GlobalLayoutViewModel>
@functions {
public GlobalLayoutViewModel LayoutModel => new()
{
DocSetName = "Api Explorer",
Description = "",
Layout = null,
PageTocItems = [],
CurrentDocument = Model.ApiEndpoint,
Previous = null,
Next = null,
NavigationHtml = Model.NavigationHtml,
LegacyPage = null,
UrlPathPrefix = null,
GithubEditUrl = null,
ReportIssueUrl = null,
AllowIndexing = false,
CanonicalBaseUrl = null,
GoogleTagManager = new GoogleTagManagerConfiguration(),
Features = new FeatureFlags([]),
Parents =
[
],
Products = null,
StaticFileContentHashProvider = Model.StaticFileContentHashProvider
};
}
<section id="elastic-docs-v3">
<h1>@Model.ApiEndpoint.Url</h1>
</section>
11 changes: 11 additions & 0 deletions src/Elastic.ApiExplorer/Endpoints/IndexViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Licensed to Elasticsearch B.V under one or more agreements.
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information

namespace Elastic.ApiExplorer.Endpoints;

public class IndexViewModel : ApiViewModel
{
public required ApiEndpoint ApiEndpoint { get; init; }

}
Loading
Loading