Skip to content
Open
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
2 changes: 1 addition & 1 deletion docs/cli/release/changelog-add.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ docs-builder changelog add [options...] [-h|--help]
: If specified, `--title` can be derived from the PR.
: If mappings are configured, `--areas` and `--type` can also be derived from the PR.
: Creates one changelog file per PR.
: If `add_blockers` are configured in the changelog configuration file and a PR has a blocking label for any product in `--products`, that PR is skipped and no changelog file is created for it.
: If there are `block ... create` definitions in the changelog configuration file and a PR has a blocking label for any product in `--products`, that PR is skipped and no changelog file is created for it.

`--repo <string?>`
: Optional: GitHub repository name (used when `--pr` is just a number).
Expand Down
4 changes: 2 additions & 2 deletions docs/cli/release/changelog-render.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ docs-builder changelog render [options...] [-h|--help]
`--config <string?>`
: Optional: Path to the changelog.yml configuration file.
: Defaults to `docs/changelog.yml`.
: This configuration file is where the command looks for `render_blockers` details.
: This configuration file is where the command looks `block ... publish` definitions.

`--hide-features <string[]?>`
: Optional: Filter by feature IDs (comma-separated), or a path to a newline-delimited file containing feature IDs. Can be specified multiple times.
Expand Down Expand Up @@ -54,7 +54,7 @@ docs-builder changelog render [options...] [-h|--help]
: Defaults to the version in the first bundle.
: If the string contains spaces, they are replaced with dashes when used in directory names and anchors.

You can configure `render_blockers` in your `changelog.yml` configuration file to automatically block changelog entries from being rendered based on their products, areas, and/or types.
You can configure `block` definitions in your `changelog.yml` configuration file to automatically comment out changelog entries based on their products, areas, and/or types.
For more information, refer to [](/contribute/changelog.md#example-block-label).

## Output formats
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,15 @@ public override void Render(IReadOnlyCollection<ChangelogEntry> entries, Changel

foreach (var group in groupedEntries)
{
// Check if all entries in this group are hidden
var allEntriesHidden = group.All(entry =>
ChangelogRenderUtilities.ShouldHideEntry(entry, context.FeatureIdsToHide, context));

if (context.Subsections && !string.IsNullOrWhiteSpace(group.Key))
{
var header = ChangelogTextUtilities.FormatSubtypeHeader(group.Key);
_ = sb.AppendLine(CultureInfo.InvariantCulture, $"**{header}**");
var headerLine = allEntriesHidden ? $"// **{header}**" : $"**{header}**";
_ = sb.AppendLine(headerLine);
_ = sb.AppendLine();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,15 @@ public override void Render(IReadOnlyCollection<ChangelogEntry> entries, Changel

foreach (var areaGroup in groupedByArea)
{
// Check if all entries in this area group are hidden
var allEntriesHidden = areaGroup.All(entry =>
ChangelogRenderUtilities.ShouldHideEntry(entry, context.FeatureIdsToHide, context));

var componentName = !string.IsNullOrWhiteSpace(areaGroup.Key) ? areaGroup.Key : "General";
var formattedComponent = ChangelogTextUtilities.FormatAreaHeader(componentName);

_ = sb.AppendLine(CultureInfo.InvariantCulture, $"{formattedComponent}::");
var headerLine = allEntriesHidden ? $"// {formattedComponent}::" : $"{formattedComponent}::";
_ = sb.AppendLine(headerLine);
_ = sb.AppendLine();

foreach (var entry in areaGroup)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,15 @@ public override void Render(IReadOnlyCollection<ChangelogEntry> entries, Changel

foreach (var areaGroup in groupedByArea)
{
// Check if all entries in this area group are hidden
var allEntriesHidden = areaGroup.All(entry =>
ChangelogRenderUtilities.ShouldHideEntry(entry, context.FeatureIdsToHide, context));

var componentName = !string.IsNullOrWhiteSpace(areaGroup.Key) ? areaGroup.Key : "General";
var formattedComponent = ChangelogTextUtilities.FormatAreaHeader(componentName);

_ = sb.AppendLine(CultureInfo.InvariantCulture, $"{formattedComponent}::");
var headerLine = allEntriesHidden ? $"// {formattedComponent}::" : $"{formattedComponent}::";
_ = sb.AppendLine(headerLine);
_ = sb.AppendLine();

foreach (var entry in areaGroup)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,15 @@ public override void Render(IReadOnlyCollection<ChangelogEntry> entries, Changel

foreach (var areaGroup in groupedByArea)
{
// Check if all entries in this area group are hidden
var allEntriesHidden = areaGroup.All(entry =>
ChangelogRenderUtilities.ShouldHideEntry(entry, context.FeatureIdsToHide, context));

var componentName = !string.IsNullOrWhiteSpace(areaGroup.Key) ? areaGroup.Key : "General";
var formattedComponent = ChangelogTextUtilities.FormatAreaHeader(componentName);

_ = sb.AppendLine(CultureInfo.InvariantCulture, $"{formattedComponent}::");
var headerLine = allEntriesHidden ? $"// {formattedComponent}::" : $"{formattedComponent}::";
_ = sb.AppendLine(headerLine);
_ = sb.AppendLine();

foreach (var entry in areaGroup)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using System.ComponentModel.DataAnnotations;
using System.IO.Abstractions;
using System.Linq;
using System.Text.Json.Serialization;
using Elastic.Changelog.Configuration;
using Elastic.Documentation;
Expand Down Expand Up @@ -122,13 +123,16 @@ Cancel ctx
// Emit warnings for hidden entries
EmitHiddenEntryWarnings(collector, resolvedResult.Entries, featureHidingResult.FeatureIdsToHide);

// Build render context (needed for block checking)
var context = BuildRenderContext(input, outputSetup, resolvedResult, featureHidingResult.FeatureIdsToHide, config);

// Emit warnings for blocked entries
EmitBlockedEntryWarnings(collector, resolvedResult.Entries, context);

// Validate entry types
if (!ValidateEntryTypes(collector, resolvedResult.Entries, config.Types))
return false;

// Build render context
var context = BuildRenderContext(input, outputSetup, resolvedResult, featureHidingResult.FeatureIdsToHide, config);

// Render output
var renderer = new ChangelogRenderer(_fileSystem, _logger);
await renderer.RenderAsync(input.FileType, context, ctx);
Expand Down Expand Up @@ -194,6 +198,90 @@ private static void EmitHiddenEntryWarnings(
}
}

private static void EmitBlockedEntryWarnings(
IDiagnosticsCollector collector,
IReadOnlyList<ResolvedEntry> entries,
ChangelogRenderContext context)
{
if (context.Configuration?.Block == null)
return;

var visibleEntries = entries.Where(resolved =>
string.IsNullOrWhiteSpace(resolved.Entry.FeatureId) ||
!context.FeatureIdsToHide.Contains(resolved.Entry.FeatureId));

foreach (var resolved in visibleEntries)
{
// Get product IDs for this entry
var productIds = context.EntryToBundleProducts.GetValueOrDefault(resolved.Entry, new HashSet<string>(StringComparer.OrdinalIgnoreCase));
if (productIds.Count == 0)
continue;

// Check each product's block configuration
foreach (var productId in productIds)
{
var blocker = GetPublishBlockerForProduct(context.Configuration.Block, productId);
if (blocker != null && blocker.ShouldBlock(resolved.Entry))
{
var reasons = GetBlockReasons(resolved.Entry, blocker);
var productInfo = productIds.Count > 1 ? $" for product '{productId}'" : "";
var entryIdentifier = GetEntryIdentifier(resolved.Entry, context);
collector.EmitWarning(string.Empty, $"Changelog entry {entryIdentifier} will be commented out{productInfo} because it matches block configuration: {reasons}");
}
}
}
}

private static string GetEntryIdentifier(ChangelogEntry entry, ChangelogRenderContext context)
{
// Try to extract PR number if available
if (!string.IsNullOrWhiteSpace(entry.Pr))
{
var repo = context.EntryToRepo.GetValueOrDefault(entry, context.Repo);
var prNumber = ChangelogTextUtilities.ExtractPrNumber(entry.Pr, "elastic", repo);
if (prNumber.HasValue)
return $"for PR {prNumber.Value}";
}

// Fall back to title if no PR is available
return $"'{entry.Title}'";
}

private static string GetBlockReasons(ChangelogEntry entry, PublishBlocker blocker)
{
var reasons = new List<string>();

// Check if blocked by type
if (blocker.Types?.Count > 0)
{
var entryTypeName = entry.Type.ToStringFast(true);
if (blocker.Types.Any(t => t.Equals(entryTypeName, StringComparison.OrdinalIgnoreCase)))
reasons.Add($"type '{entryTypeName}'");
}

// Check if blocked by area
if (blocker.Areas?.Count > 0 && entry.Areas?.Count > 0)
{
var blockedAreas = entry.Areas
.Where(area => blocker.Areas.Any(blocked => blocked.Equals(area, StringComparison.OrdinalIgnoreCase)))
.ToList();
if (blockedAreas.Count > 0)
reasons.Add($"area{(blockedAreas.Count > 1 ? "s" : "")} '{string.Join("', '", blockedAreas)}'");
}

return string.Join(" and ", reasons);
}

private static PublishBlocker? GetPublishBlockerForProduct(BlockConfiguration blockConfig, string productId)
{
// Check product-specific override first
if (blockConfig.ByProduct?.TryGetValue(productId, out var productBlockers) == true)
return productBlockers.Publish;

// Fall back to global publish blocker
return blockConfig.Publish;
}

private static bool ValidateEntryTypes(
IDiagnosticsCollector collector,
IReadOnlyList<ResolvedEntry> entries,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ public override async Task RenderAsync(ChangelogRenderContext context, Cancel ct
var sb = new StringBuilder();
_ = sb.AppendLine(InvariantCulture, $"## {context.Title} [{context.Repo}-{context.TitleSlug}-breaking-changes]");

// Check if all entries are hidden
var allEntriesHidden = breakingChanges.Count > 0 && breakingChanges.All(entry =>
ChangelogRenderUtilities.ShouldHideEntry(entry, context.FeatureIdsToHide, context));

if (breakingChanges.Count > 0)
{
// Group by subtype if subsections are enabled, otherwise group by area
Expand All @@ -35,11 +39,19 @@ public override async Task RenderAsync(ChangelogRenderContext context, Cancel ct

foreach (var group in groupedEntries)
{
// Check if all entries in this group are hidden
var allGroupEntriesHidden = group.All(entry =>
ChangelogRenderUtilities.ShouldHideEntry(entry, context.FeatureIdsToHide, context));

if (context.Subsections && !string.IsNullOrWhiteSpace(group.Key))
{
var header = ChangelogTextUtilities.FormatSubtypeHeader(group.Key);
_ = sb.AppendLine();
if (allGroupEntriesHidden)
_ = sb.AppendLine("<!--");
_ = sb.AppendLine(InvariantCulture, $"**{header}**");
if (allGroupEntriesHidden)
_ = sb.AppendLine("-->");
}

foreach (var entry in group)
Expand Down Expand Up @@ -70,9 +82,18 @@ public override async Task RenderAsync(ChangelogRenderContext context, Cancel ct
_ = sb.AppendLine("-->");
}
}

// Add message if all entries are hidden
if (allEntriesHidden)
{
_ = sb.AppendLine();
_ = sb.AppendLine("_There are no breaking changes associated with this release._");
}
}
else
_ = sb.AppendLine("_No breaking changes._");
{
_ = sb.AppendLine("_There are no breaking changes associated with this release._");
}

await WriteOutputFileAsync(context.OutputDir, context.TitleSlug, sb.ToString(), ctx);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,30 @@ public override async Task RenderAsync(ChangelogRenderContext context, Cancel ct
var sb = new StringBuilder();
_ = sb.AppendLine(InvariantCulture, $"## {context.Title} [{context.Repo}-{context.TitleSlug}-deprecations]");

// Check if all entries are hidden
var allEntriesHidden = deprecations.Count > 0 && deprecations.All(entry =>
ChangelogRenderUtilities.ShouldHideEntry(entry, context.FeatureIdsToHide, context));

if (deprecations.Count > 0)
{
var groupedByArea = context.Subsections
? deprecations.GroupBy(ChangelogRenderUtilities.GetComponent).OrderBy(g => g.Key).ToList()
: deprecations.GroupBy(ChangelogRenderUtilities.GetComponent).ToList();
foreach (var areaGroup in groupedByArea)
{
// Check if all entries in this area group are hidden
var allGroupEntriesHidden = areaGroup.All(entry =>
ChangelogRenderUtilities.ShouldHideEntry(entry, context.FeatureIdsToHide, context));

if (context.Subsections && !string.IsNullOrWhiteSpace(areaGroup.Key))
{
var header = ChangelogTextUtilities.FormatAreaHeader(areaGroup.Key);
_ = sb.AppendLine();
if (allGroupEntriesHidden)
_ = sb.AppendLine("<!--");
_ = sb.AppendLine(InvariantCulture, $"**{header}**");
if (allGroupEntriesHidden)
_ = sb.AppendLine("-->");
}

foreach (var entry in areaGroup)
Expand Down Expand Up @@ -68,9 +80,18 @@ public override async Task RenderAsync(ChangelogRenderContext context, Cancel ct
_ = sb.AppendLine("-->");
}
}

// Add message if all entries are hidden
if (allEntriesHidden)
{
_ = sb.AppendLine();
_ = sb.AppendLine("_There are no deprecations associated with this release._");
}
}
else
_ = sb.AppendLine("_No deprecations._");
{
_ = sb.AppendLine("_There are no deprecations associated with this release._");
}

await WriteOutputFileAsync(context.OutputDir, context.TitleSlug, sb.ToString(), ctx);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,36 @@ public override async Task RenderAsync(ChangelogRenderContext context, Cancel ct

var hasAnyEntries = features.Count > 0 || enhancements.Count > 0 || security.Count > 0 || bugFixes.Count > 0 || docs.Count > 0 || regressions.Count > 0 || other.Count > 0;

// Helper to check if all entries in a collection are hidden
bool AllEntriesHidden(IReadOnlyCollection<ChangelogEntry> entries) =>
entries.Count > 0 && entries.All(entry =>
ChangelogRenderUtilities.ShouldHideEntry(entry, context.FeatureIdsToHide, context));

// Check if each category has visible entries
var hasVisibleFeatures = (features.Count > 0 || enhancements.Count > 0) &&
!(AllEntriesHidden(features) && AllEntriesHidden(enhancements));
var hasVisibleFixes = (security.Count > 0 || bugFixes.Count > 0) &&
!(AllEntriesHidden(security) && AllEntriesHidden(bugFixes));
var hasVisibleDocs = docs.Count > 0 && !AllEntriesHidden(docs);
var hasVisibleRegressions = regressions.Count > 0 && !AllEntriesHidden(regressions);
var hasVisibleOther = other.Count > 0 && !AllEntriesHidden(other);

var hasAnyVisibleEntries = hasVisibleFeatures || hasVisibleFixes || hasVisibleDocs || hasVisibleRegressions || hasVisibleOther;

if (hasAnyEntries)
{
if (features.Count > 0 || enhancements.Count > 0)
{
_ = sb.AppendLine(InvariantCulture, $"### Features and enhancements [{context.Repo}-{context.TitleSlug}-features-enhancements]");
var combined = features.Concat(enhancements).ToList();
_ = sb.AppendLine(InvariantCulture, $"### Features and enhancements [{context.Repo}-{context.TitleSlug}-features-enhancements]");
RenderEntriesByArea(sb, combined, context);
}

if (security.Count > 0 || bugFixes.Count > 0)
{
var combined = security.Concat(bugFixes).ToList();
_ = sb.AppendLine();
_ = sb.AppendLine(InvariantCulture, $"### Fixes [{context.Repo}-{context.TitleSlug}-fixes]");
var combined = security.Concat(bugFixes).ToList();
RenderEntriesByArea(sb, combined, context);
}

Expand All @@ -91,9 +107,18 @@ public override async Task RenderAsync(ChangelogRenderContext context, Cancel ct
_ = sb.AppendLine(InvariantCulture, $"### Other changes [{context.Repo}-{context.TitleSlug}-other]");
RenderEntriesByArea(sb, other, context);
}

// Add message if all entries are hidden
if (!hasAnyVisibleEntries)
{
_ = sb.AppendLine();
_ = sb.AppendLine("_There are no new features, enhancements, or fixes associated with this release._");
}
}
else
_ = sb.AppendLine("_No new features, enhancements, or fixes._");
{
_ = sb.AppendLine("_There are no new features, enhancements, or fixes associated with this release._");
}

await WriteOutputFileAsync(context.OutputDir, context.TitleSlug, sb.ToString(), ctx);
}
Expand All @@ -108,10 +133,16 @@ private static void RenderEntriesByArea(
: entries.GroupBy(ChangelogRenderUtilities.GetComponent).ToList();
foreach (var areaGroup in groupedByArea)
{
// Check if all entries in this area group are hidden
var allEntriesHidden = areaGroup.All(entry =>
ChangelogRenderUtilities.ShouldHideEntry(entry, context.FeatureIdsToHide, context));

if (context.Subsections && !string.IsNullOrWhiteSpace(areaGroup.Key))
{
var header = ChangelogTextUtilities.FormatAreaHeader(areaGroup.Key);
_ = sb.AppendLine();
if (allEntriesHidden)
_ = sb.Append("% ");
_ = sb.AppendLine(InvariantCulture, $"**{header}**");
}

Expand Down
Loading
Loading