Skip to content
Merged
  •  
  •  
  •  
117 changes: 117 additions & 0 deletions config/portal.example.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Portal Configuration Example
# This file demonstrates how to configure a documentation portal that
# composes multiple isolated documentation sets.
#
# Usage:
# docs-builder portal config/portal.example.yml
# docs-builder portal clone config/portal.example.yml
# docs-builder portal build config/portal.example.yml
# docs-builder portal serve

# Site prefix for all portal URLs
# All documentation sets will be served under this prefix
# e.g., /internal-docs, /dev-docs, /docs
site_prefix: /internal-docs

# Title displayed on the portal index page
title: "Elastic's Internal Dev Docs"

# Documentation sets to include in the portal
# Each documentation set is cloned from a git repository and built separately
documentation_sets:
# APM Managed Intake Service documentation
- name: docs-eng-team
branch: main
repo_name: apm-intake
display_name: "APM Managed Intake Service"
icon: apm

# CI Developer Guide
- name: docs-eng-team
branch: main
repo_name: ci-guide
display_name: "CI Developer Guide"
icon: ci

# Content Architecture documentation
- name: docs-eng-team
branch: main
repo_name: content-arch
display_name: "Content Architecture"
icon: architecture

# Benchmarking documentation in observability category
- name: docs-eng-team
branch: main
category: observability
repo_name: benchmarking
display_name: "Benchmarking Elasticsearch"
icon: benchmark

# Migration Guide in tooling category
- name: docs-eng-team
branch: main
category: tooling
repo_name: migration-guide
display_name: "AsciiDoc-to-MDX Migration"
icon: migration

# Artifactory documentation in tooling category
- name: docs-eng-team
branch: main
category: tooling
repo_name: artifactory
display_name: "Artifactory"
icon: artifactory

# QA Team documentation
- name: docs-eng-team
branch: main
repo_name: qa-team
display_name: "QA Team Handbook"
icon: testing

# Consulting documentation
- name: docs-eng-team
branch: main
repo_name: consulting
display_name: "Consulting Team"
icon: consulting

# Configuration options reference:
#
# name (required):
# The identifier for the documentation set. Used as the default for
# origin (elastic/{name}) and repo_name.
#
# origin (optional):
# The git repository to clone from. Defaults to "elastic/{name}".
# Can be a short form like "elastic/my-repo" or a full URL.
#
# branch (required):
# The git branch to checkout.
#
# path (optional):
# The directory within the repository containing documentation.
# Defaults to "docs".
#
# category (optional):
# Group documentation sets under a category.
# Creates URL structure: /{site_prefix}/{category}/{repo_name}/
# If not specified, URL is: /{site_prefix}/{repo_name}/
#
# repo_name (optional):
# Override the name used for checkout directories and URL paths.
# Defaults to the value of "name". This allows including the same
# repository multiple times with different URL paths.
#
# display_name (optional):
# The title shown on the portal landing page card.
# If not specified, uses the documentation set's title from docset.yml.
#
# icon (optional):
# Icon shown on the portal landing page card.
# Available icons: elasticsearch, kibana, observability, security,
# enterprise-search, logstash, beats, apm, fleet, cloud, integrations,
# docs, dev-tools, benchmark, migration, testing, ci, architecture,
# consulting, artifactory
3 changes: 3 additions & 0 deletions docs-builder.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<File Path="config/changelog.example.yml" />
<File Path="config/legacy-url-mappings.yml" />
<File Path="config/navigation.yml" />
<File Path="config/portal.example.yml" />
<File Path="config/products.yml" />
<File Path="config/versions.yml" />
<File Path="config/search.yml" />
Expand All @@ -44,6 +45,7 @@
</Folder>
<Folder Name="/src/">
<File Path="src/Directory.Build.props" />
<Project Path="src/Elastic.Portal/Elastic.Portal.csproj" />
<Project Path="src/Elastic.ApiExplorer/Elastic.ApiExplorer.csproj" />
<Project Path="src/Elastic.Documentation.Configuration/Elastic.Documentation.Configuration.csproj" />
<Project Path="src/Elastic.Documentation.LegacyDocs/Elastic.Documentation.LegacyDocs.csproj" />
Expand All @@ -52,6 +54,7 @@
<Project Path="src/Elastic.Documentation.Navigation/Elastic.Documentation.Navigation.csproj" />
<Project Path="src/Elastic.Documentation.ServiceDefaults/Elastic.Documentation.ServiceDefaults.csproj" />
<Project Path="src/Elastic.Documentation.Site/Elastic.Documentation.Site.csproj" />
<Project Path="src/Elastic.Documentation.Svg/Elastic.Documentation.Svg.csproj" />
<Project Path="src/Elastic.Documentation/Elastic.Documentation.csproj" />
<Project Path="src/Elastic.Markdown/Elastic.Markdown.csproj" />
<Project Path="src/Elastic.Documentation.Mcp/Elastic.Documentation.Mcp.csproj" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// 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 YamlDotNet.Serialization;

namespace Elastic.Documentation.Configuration.Portal;

/// <summary>
/// Configuration for a documentation portal that composes multiple isolated documentation sets.
/// </summary>
public record PortalConfiguration
{
/// <summary>
/// The URL prefix for all portal URLs (e.g., "/internal-docs").
/// All documentation sets will be served under this prefix.
/// </summary>
[YamlMember(Alias = "site_prefix")]
public string SitePrefix { get; set; } = "/docs";

/// <summary>
/// The title displayed on the portal index page.
/// </summary>
[YamlMember(Alias = "title")]
public string Title { get; set; } = "Documentation Portal";

/// <summary>
/// The list of documentation sets to include in the portal.
/// </summary>
[YamlMember(Alias = "documentation_sets")]
public IReadOnlyList<PortalDocumentationSetReference> DocumentationSets { get; set; } = [];

/// <summary>
/// Deserializes a portal configuration from YAML content.
/// </summary>
public static PortalConfiguration Deserialize(string yaml)
{
using var input = new StringReader(yaml);
var config = ConfigurationFileProvider.Deserializer.Deserialize<PortalConfiguration>(input);
return NormalizeConfiguration(config);
}

/// <summary>
/// Loads a portal configuration from a file.
/// </summary>
public static PortalConfiguration Load(IFileInfo file)
{
var yaml = file.OpenText().ReadToEnd();
return Deserialize(yaml);
}

/// <summary>
/// Loads a portal configuration from a file path.
/// </summary>
public static PortalConfiguration Load(IFileSystem fileSystem, string path)
{
var file = fileSystem.FileInfo.New(path);
if (!file.Exists)
throw new FileNotFoundException($"Portal configuration file not found: {path}", path);

return Load(file);
}

private static PortalConfiguration NormalizeConfiguration(PortalConfiguration config)
{
// Normalize site prefix to ensure it starts with / and doesn't end with /
var sitePrefix = config.SitePrefix.Trim();
if (!sitePrefix.StartsWith('/'))
sitePrefix = "/" + sitePrefix;
sitePrefix = sitePrefix.TrimEnd('/');

return config with { SitePrefix = sitePrefix };
}

/// <summary>
/// Gets all unique categories defined in the documentation sets.
/// </summary>
[YamlIgnore]
public IReadOnlyList<string> Categories =>
DocumentationSets
.Where(ds => !string.IsNullOrEmpty(ds.Category))
.Select(ds => ds.Category!)
.Distinct()
.OrderBy(c => c)
.ToList();

/// <summary>
/// Gets documentation sets grouped by category.
/// Documentation sets without a category are grouped under null.
/// </summary>
[YamlIgnore]
public ILookup<string?, PortalDocumentationSetReference> DocumentationSetsByCategory =>
DocumentationSets.ToLookup(ds => ds.Category);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// 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 YamlDotNet.Serialization;

namespace Elastic.Documentation.Configuration.Portal;

/// <summary>
/// Represents a reference to a documentation set in a portal configuration.
/// </summary>
public record PortalDocumentationSetReference
{
/// <summary>
/// The name of the documentation set. This is used in the URL path.
/// </summary>
[YamlMember(Alias = "name")]
public string Name { get; set; } = string.Empty;

/// <summary>
/// The git origin for the repository. Defaults to "elastic/{name}" if not specified.
/// </summary>
[YamlMember(Alias = "origin")]
public string? Origin { get; set; }

/// <summary>
/// The git branch to checkout. Required.
/// </summary>
[YamlMember(Alias = "branch")]
public string Branch { get; set; } = string.Empty;

/// <summary>
/// The path within the repository where documentation lives. Defaults to "docs".
/// </summary>
[YamlMember(Alias = "path")]
public string Path { get; set; } = "docs";

/// <summary>
/// Optional category for grouping documentation sets. If specified, the URL will be
/// /{site-prefix}/{category}/{name}/. If not specified, the URL will be /{site-prefix}/{name}/.
/// </summary>
[YamlMember(Alias = "category")]
public string? Category { get; set; }

/// <summary>
/// Optional override for the repository name used in checkout directories and URL paths.
/// If not specified, defaults to <see cref="Name"/>. This allows including the same
/// repository multiple times with different URL paths.
/// </summary>
[YamlMember(Alias = "repo_name")]
public string? RepoName { get; set; }

/// <summary>
/// Optional display name shown on the portal landing page.
/// If not specified, uses the documentation set's title from docset.yml.
/// </summary>
[YamlMember(Alias = "display_name")]
public string? DisplayName { get; set; }

/// <summary>
/// Optional icon identifier for the documentation set card.
/// Can be a predefined icon name (e.g., "elasticsearch", "kibana", "observability")
/// or a custom SVG path.
/// </summary>
[YamlMember(Alias = "icon")]
public string? Icon { get; set; }

/// <summary>
/// Gets the resolved repository name, defaulting to <see cref="Name"/> if not explicitly set.
/// This is used for checkout directories and URL paths.
/// </summary>
[YamlIgnore]
public string ResolvedRepoName => RepoName ?? Name;

/// <summary>
/// Gets the resolved origin, defaulting to "elastic/{name}" if not explicitly set.
/// </summary>
public string ResolvedOrigin => Origin ?? $"elastic/{Name}";

/// <summary>
/// Gets the full git URL for cloning, handling GitHub token authentication if available.
/// </summary>
public string GetGitUrl()
{
var origin = ResolvedOrigin;

// If origin is already a full URL, return it as-is
if (origin.StartsWith("https://", StringComparison.OrdinalIgnoreCase) ||
origin.StartsWith("git@", StringComparison.OrdinalIgnoreCase))
return origin;

// Otherwise, construct the URL from the short form (e.g., "elastic/repo-name")
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("GITHUB_ACTIONS")))
{
var token = Environment.GetEnvironmentVariable("GITHUB_TOKEN");
return !string.IsNullOrEmpty(token)
? $"https://oauth2:{token}@github.com/{origin}.git"
: $"https://github.com/{origin}.git";
}

return $"git@github.com:{origin}.git";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using Elastic.Documentation.Configuration.Assembler;
using Elastic.Documentation.Configuration.LegacyUrlMappings;
using Elastic.Documentation.Configuration.Portal;
using Elastic.Documentation.Configuration.Products;
using Elastic.Documentation.Configuration.ReleaseNotes;
using Elastic.Documentation.Configuration.Search;
Expand All @@ -14,6 +15,9 @@
namespace Elastic.Documentation.Configuration.Serialization;

[YamlStaticContext]
// Portal configuration
[YamlSerializable(typeof(PortalConfiguration))]
[YamlSerializable(typeof(PortalDocumentationSetReference))]
// Assembly configuration
[YamlSerializable(typeof(AssemblyConfiguration))]
[YamlSerializable(typeof(Repository))]
Expand Down
16 changes: 16 additions & 0 deletions src/Elastic.Documentation.Svg/Elastic.Documentation.Svg.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<InvariantGlobalization>true</InvariantGlobalization>
<PublishAot>true</PublishAot>
</PropertyGroup>

<ItemGroup>
<EmbeddedResource Include="svgs\*.svg" />
<EmbeddedResource Include="svgs\tokens\*.svg" />
</ItemGroup>

</Project>
Loading
Loading