Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions docs/contribute/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ Options:
--output <string?> Optional: Output directory for rendered markdown files. Defaults to current directory [Default: null]
--title <string?> Optional: Title to use for section headers in output markdown files. Defaults to version from first bundle [Default: null]
--subsections Optional: Group entries by area/component in subsections. Defaults to false
--hide-private-links Optional: Hide private links by commenting them out in markdown output. Defaults to false
```

Before you can use this command you must create changelog files and collect them into bundles.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ public class ChangelogRenderInput
public string? Output { get; set; }
public string? Title { get; set; }
public bool Subsections { get; set; }
public bool HidePrivateLinks { get; set; }
}

174 changes: 137 additions & 37 deletions src/services/Elastic.Documentation.Services/ChangelogService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1354,13 +1354,13 @@ Cancel ctx
var repoForRendering = allResolvedEntries.Count > 0 ? allResolvedEntries[0].repo : defaultRepo;

// Render index.md (features, enhancements, bug fixes, security)
await RenderIndexMarkdown(collector, outputDir, title, repoForRendering, allResolvedEntries.Select(e => e.entry).ToList(), entriesByType, input.Subsections, ctx);
await RenderIndexMarkdown(collector, outputDir, title, repoForRendering, allResolvedEntries.Select(e => e.entry).ToList(), entriesByType, input.Subsections, input.HidePrivateLinks, ctx);

// Render breaking-changes.md
await RenderBreakingChangesMarkdown(collector, outputDir, title, repoForRendering, allResolvedEntries.Select(e => e.entry).ToList(), entriesByType, input.Subsections, ctx);
await RenderBreakingChangesMarkdown(collector, outputDir, title, repoForRendering, allResolvedEntries.Select(e => e.entry).ToList(), entriesByType, input.Subsections, input.HidePrivateLinks, ctx);

// Render deprecations.md
await RenderDeprecationsMarkdown(collector, outputDir, title, repoForRendering, allResolvedEntries.Select(e => e.entry).ToList(), entriesByType, input.Subsections, ctx);
await RenderDeprecationsMarkdown(collector, outputDir, title, repoForRendering, allResolvedEntries.Select(e => e.entry).ToList(), entriesByType, input.Subsections, input.HidePrivateLinks, ctx);

_logger.LogInformation("Rendered changelog markdown files to {OutputDir}", outputDir);

Expand Down Expand Up @@ -1397,6 +1397,7 @@ private async Task RenderIndexMarkdown(
List<ChangelogData> entries,
Dictionary<string, List<ChangelogData>> entriesByType,
bool subsections,
bool hidePrivateLinks,
Cancel ctx
)
{
Expand Down Expand Up @@ -1444,15 +1445,15 @@ Cancel ctx
{
sb.AppendLine(CultureInfo.InvariantCulture, $"### Features and enhancements [{repo}-{title}-features-enhancements]");
var combined = features.Concat(enhancements).ToList();
RenderEntriesByArea(sb, combined, repo, subsections);
RenderEntriesByArea(sb, combined, repo, subsections, hidePrivateLinks);
}

if (security.Count > 0 || bugFixes.Count > 0)
{
sb.AppendLine();
sb.AppendLine(CultureInfo.InvariantCulture, $"### Fixes [{repo}-{title}-fixes]");
var combined = security.Concat(bugFixes).ToList();
RenderEntriesByArea(sb, combined, repo, subsections);
RenderEntriesByArea(sb, combined, repo, subsections, hidePrivateLinks);
}
}
else
Expand Down Expand Up @@ -1480,6 +1481,7 @@ private async Task RenderBreakingChangesMarkdown(
List<ChangelogData> entries,
Dictionary<string, List<ChangelogData>> entriesByType,
bool subsections,
bool hidePrivateLinks,
Cancel ctx
)
{
Expand All @@ -1506,20 +1508,39 @@ Cancel ctx
sb.AppendLine(CultureInfo.InvariantCulture, $"::::{{dropdown}} {Beautify(entry.Title)}");
sb.AppendLine(entry.Description ?? "% Describe the functionality that changed");
sb.AppendLine();
sb.Append("For more information, check ");
if (!string.IsNullOrWhiteSpace(entry.Pr))
if (hidePrivateLinks)
{
sb.Append(FormatPrLink(entry.Pr, repo));
// When hiding private links, put them on separate lines as comments
if (!string.IsNullOrWhiteSpace(entry.Pr))
{
sb.AppendLine(FormatPrLink(entry.Pr, repo, hidePrivateLinks));
}
if (entry.Issues != null && entry.Issues.Count > 0)
{
foreach (var issue in entry.Issues)
{
sb.AppendLine(FormatIssueLink(issue, repo, hidePrivateLinks));
}
}
sb.AppendLine("For more information, check the pull request or issue above.");
}
if (entry.Issues != null && entry.Issues.Count > 0)
else
{
foreach (var issue in entry.Issues)
sb.Append("For more information, check ");
if (!string.IsNullOrWhiteSpace(entry.Pr))
{
sb.Append(' ');
sb.Append(FormatIssueLink(issue, repo));
sb.Append(FormatPrLink(entry.Pr, repo, hidePrivateLinks));
}
if (entry.Issues != null && entry.Issues.Count > 0)
{
foreach (var issue in entry.Issues)
{
sb.Append(' ');
sb.Append(FormatIssueLink(issue, repo, hidePrivateLinks));
}
}
sb.AppendLine(".");
}
sb.AppendLine(".");
sb.AppendLine();

if (!string.IsNullOrWhiteSpace(entry.Impact))
Expand Down Expand Up @@ -1571,6 +1592,7 @@ private async Task RenderDeprecationsMarkdown(
List<ChangelogData> entries,
Dictionary<string, List<ChangelogData>> entriesByType,
bool subsections,
bool hidePrivateLinks,
Cancel ctx
)
{
Expand All @@ -1597,20 +1619,39 @@ Cancel ctx
sb.AppendLine(CultureInfo.InvariantCulture, $"::::{{dropdown}} {Beautify(entry.Title)}");
sb.AppendLine(entry.Description ?? "% Describe the functionality that was deprecated");
sb.AppendLine();
sb.Append("For more information, check ");
if (!string.IsNullOrWhiteSpace(entry.Pr))
if (hidePrivateLinks)
{
sb.Append(FormatPrLink(entry.Pr, repo));
// When hiding private links, put them on separate lines as comments
if (!string.IsNullOrWhiteSpace(entry.Pr))
{
sb.AppendLine(FormatPrLink(entry.Pr, repo, hidePrivateLinks));
}
if (entry.Issues != null && entry.Issues.Count > 0)
{
foreach (var issue in entry.Issues)
{
sb.AppendLine(FormatIssueLink(issue, repo, hidePrivateLinks));
}
}
sb.AppendLine("For more information, check the pull request or issue above.");
}
if (entry.Issues != null && entry.Issues.Count > 0)
else
{
foreach (var issue in entry.Issues)
sb.Append("For more information, check ");
if (!string.IsNullOrWhiteSpace(entry.Pr))
{
sb.Append(' ');
sb.Append(FormatIssueLink(issue, repo));
sb.Append(FormatPrLink(entry.Pr, repo, hidePrivateLinks));
}
if (entry.Issues != null && entry.Issues.Count > 0)
{
foreach (var issue in entry.Issues)
{
sb.Append(' ');
sb.Append(FormatIssueLink(issue, repo, hidePrivateLinks));
}
}
sb.AppendLine(".");
}
sb.AppendLine(".");
sb.AppendLine();

if (!string.IsNullOrWhiteSpace(entry.Impact))
Expand Down Expand Up @@ -1653,7 +1694,7 @@ Cancel ctx
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0058:Expression value is never used", Justification = "StringBuilder methods return builder for chaining")]
private void RenderEntriesByArea(StringBuilder sb, List<ChangelogData> entries, string repo, bool subsections)
private void RenderEntriesByArea(StringBuilder sb, List<ChangelogData> entries, string repo, bool subsections, bool hidePrivateLinks)
{
var groupedByArea = entries.GroupBy(e => GetComponent(e)).ToList();
foreach (var areaGroup in groupedByArea)
Expand All @@ -1669,26 +1710,67 @@ private void RenderEntriesByArea(StringBuilder sb, List<ChangelogData> entries,
{
sb.Append("* ");
sb.Append(Beautify(entry.Title));
sb.Append(' ');

if (!string.IsNullOrWhiteSpace(entry.Pr))
var hasCommentedLinks = false;
if (hidePrivateLinks)
{
sb.Append(FormatPrLink(entry.Pr, repo));
sb.Append(' ');
}
// When hiding private links, put them on separate lines as comments with proper indentation
if (!string.IsNullOrWhiteSpace(entry.Pr))
{
sb.AppendLine();
sb.Append(" ");
sb.Append(FormatPrLink(entry.Pr, repo, hidePrivateLinks));
hasCommentedLinks = true;
}

if (entry.Issues != null && entry.Issues.Count > 0)
{
foreach (var issue in entry.Issues)
{
sb.AppendLine();
sb.Append(" ");
sb.Append(FormatIssueLink(issue, repo, hidePrivateLinks));
hasCommentedLinks = true;
}
}

if (entry.Issues != null && entry.Issues.Count > 0)
// Add newline after the last link if there are commented links
if (hasCommentedLinks)
{
sb.AppendLine();
}
}
else
{
foreach (var issue in entry.Issues)
sb.Append(' ');
if (!string.IsNullOrWhiteSpace(entry.Pr))
{
sb.Append(FormatIssueLink(issue, repo));
sb.Append(FormatPrLink(entry.Pr, repo, hidePrivateLinks));
sb.Append(' ');
}

if (entry.Issues != null && entry.Issues.Count > 0)
{
foreach (var issue in entry.Issues)
{
sb.Append(FormatIssueLink(issue, repo, hidePrivateLinks));
sb.Append(' ');
}
}
}

if (!string.IsNullOrWhiteSpace(entry.Description))
{
sb.AppendLine();
// Add blank line before description
// When hidePrivateLinks is true and links exist, add an indented blank line
if (hidePrivateLinks && hasCommentedLinks)
{
sb.AppendLine(" ");
}
else
{
sb.AppendLine();
}
var indented = Indent(entry.Description);
sb.AppendLine(indented);
}
Expand Down Expand Up @@ -1747,40 +1829,58 @@ private static string Indent(string text)
[GeneratedRegex(@"\d+$", RegexOptions.None)]
private static partial Regex IssueNumberRegex();

private static string FormatPrLink(string pr, string repo)
private static string FormatPrLink(string pr, string repo, bool hidePrivateLinks)
{
// Extract PR number
var match = PrNumberRegex().Match(pr);
var prNumber = match.Success ? match.Value : pr;

// Format as markdown link
string link;
if (pr.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
return $"[#{prNumber}]({pr})";
link = $"[#{prNumber}]({pr})";
}
else
{
var url = $"https://github.com/elastic/{repo}/pull/{prNumber}";
return $"[#{prNumber}]({url})";
link = $"[#{prNumber}]({url})";
}

// Comment out link if hiding private links
if (hidePrivateLinks)
{
return $"% {link}";
}

return link;
}

private static string FormatIssueLink(string issue, string repo)
private static string FormatIssueLink(string issue, string repo, bool hidePrivateLinks)
{
// Extract issue number
var match = IssueNumberRegex().Match(issue);
var issueNumber = match.Success ? match.Value : issue;

// Format as markdown link
string link;
if (issue.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
return $"[#{issueNumber}]({issue})";
link = $"[#{issueNumber}]({issue})";
}
else
{
var url = $"https://github.com/elastic/{repo}/issues/{issueNumber}";
return $"[#{issueNumber}]({url})";
link = $"[#{issueNumber}]({url})";
}

// Comment out link if hiding private links
if (hidePrivateLinks)
{
return $"% {link}";
}

return link;
}
}

5 changes: 4 additions & 1 deletion src/tooling/docs-builder/Commands/ChangelogCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,13 +162,15 @@ async static (s, collector, state, ctx) => await s.BundleChangelogs(collector, s
/// <param name="output">Optional: Output directory for rendered markdown files. Defaults to current directory</param>
/// <param name="title">Optional: Title to use for section headers in output markdown files. Defaults to version from first bundle</param>
/// <param name="subsections">Optional: Group entries by area/component in subsections. Defaults to false</param>
/// <param name="hidePrivateLinks">Optional: Hide private links by commenting them out in markdown output. Defaults to false</param>
/// <param name="ctx"></param>
[Command("render")]
public async Task<int> Render(
[BundleInputParser] List<BundleInput> input,
string? output = null,
string? title = null,
bool subsections = false,
bool hidePrivateLinks = false,
Cancel ctx = default
)
{
Expand All @@ -181,7 +183,8 @@ public async Task<int> Render(
Bundles = input ?? [],
Output = output,
Title = title,
Subsections = subsections
Subsections = subsections,
HidePrivateLinks = hidePrivateLinks
};

serviceInvoker.AddCommand(service, renderInput,
Expand Down
Loading