Skip to content

Commit dc8f8c5

Browse files
authored
Add docs-builder changelog render --hide-features option (#2412)
1 parent 236a819 commit dc8f8c5

File tree

6 files changed

+657
-11
lines changed

6 files changed

+657
-11
lines changed

docs/cli/release/changelog-render.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ docs-builder changelog render [options...] [-h|--help]
3535
: Defaults to false.
3636

3737
`--hide-private-links`
38-
: Optional: Hide private links by commenting them out in markdown output.
38+
: Optional: Hide private links by commenting them out in the markdown output.
3939
: This option is useful when rendering changelog bundles in private repositories.
4040
: Defaults to false.
41+
42+
`--hide-features <string[]?>`
43+
: Optional: Filter by feature IDs (comma-separated), or a path to a newline-delimited file containing feature IDs. Can be specified multiple times.
44+
: Each occurrence can be either comma-separated feature IDs (e.g., `--hide-features "feature:new-search-api,feature:enhanced-analytics"`) or a file path (e.g., `--hide-features /path/to/file.txt`).
45+
: When specifying feature IDs directly, provide comma-separated values.
46+
: When specifying a file path, provide a single value that points to a newline-delimited file. The file should contain one feature ID per line.
47+
: Entries with matching `feature-id` values will be commented out in the markdown output and a warning will be emitted.

docs/contribute/changelog.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,8 @@ Options:
259259
--output <string?> Optional: Output directory for rendered markdown files. Defaults to current directory [Default: null]
260260
--title <string?> Optional: Title to use for section headers in output markdown files. Defaults to version from first bundle [Default: null]
261261
--subsections Optional: Group entries by area/component in subsections. Defaults to false
262-
--hide-private-links Optional: Hide private links by commenting them out in markdown output. Defaults to false
262+
--hide-private-links Optional: Hide private links by commenting them out in the markdown output. Defaults to false
263+
--hide-features <string[]?> Filter by feature IDs (comma-separated), or a path to a newline-delimited file containing feature IDs. Can be specified multiple times. Entries with matching feature-id values will be commented out in the markdown output. [Default: null]
263264
```
264265
265266
Before you can use this command you must create changelog files and collect them into bundles.
@@ -314,3 +315,6 @@ For example, the `index.md` output file contains information derived from the ch
314315
```
315316
316317
To comment out the pull request and issue links, for example if they relate to a private repository, use the `--hide-private-links` option.
318+
319+
If you have changelogs with `feature-id` values and you want them to be omitted from the output, use the `--hide-features` option.
320+
For more information, refer to [](/cli/release/changelog-render.md).

src/services/Elastic.Documentation.Services/Changelog/ChangelogRenderInput.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ public class ChangelogRenderInput
1414
public string? Title { get; set; }
1515
public bool Subsections { get; set; }
1616
public bool HidePrivateLinks { get; set; }
17+
public string[]? HideFeatures { get; set; }
1718
}
1819

src/services/Elastic.Documentation.Services/ChangelogService.cs

Lines changed: 171 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1452,17 +1452,131 @@ Cancel ctx
14521452
// Convert title to slug format for folder names and anchors (lowercase, dashes instead of spaces)
14531453
var titleSlug = TitleToSlug(title);
14541454

1455+
// Load feature IDs to hide - check if --hide-features contains a file path or a list of feature IDs
1456+
var featureIdsToHide = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
1457+
if (input.HideFeatures is { Length: > 0 })
1458+
{
1459+
// If there's exactly one value, check if it's a file path
1460+
if (input.HideFeatures.Length == 1)
1461+
{
1462+
var singleValue = input.HideFeatures[0];
1463+
1464+
if (_fileSystem.File.Exists(singleValue))
1465+
{
1466+
// File exists, read feature IDs from it
1467+
var featureIdsFileContent = await _fileSystem.File.ReadAllTextAsync(singleValue, ctx);
1468+
var featureIdsFromFile = featureIdsFileContent
1469+
.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
1470+
.Where(f => !string.IsNullOrWhiteSpace(f))
1471+
.ToArray();
1472+
1473+
foreach (var featureId in featureIdsFromFile)
1474+
{
1475+
_ = featureIdsToHide.Add(featureId);
1476+
}
1477+
}
1478+
else
1479+
{
1480+
// Check if it looks like a file path
1481+
var looksLikeFilePath = singleValue.Contains(_fileSystem.Path.DirectorySeparatorChar) ||
1482+
singleValue.Contains(_fileSystem.Path.AltDirectorySeparatorChar) ||
1483+
_fileSystem.Path.HasExtension(singleValue);
1484+
1485+
if (looksLikeFilePath)
1486+
{
1487+
// File path doesn't exist
1488+
collector.EmitError(singleValue, $"File does not exist: {singleValue}");
1489+
return false;
1490+
}
1491+
else
1492+
{
1493+
// Doesn't look like a file path, treat as feature ID
1494+
_ = featureIdsToHide.Add(singleValue);
1495+
}
1496+
}
1497+
}
1498+
else
1499+
{
1500+
// Multiple values - process all values first, then check for errors
1501+
var nonExistentFiles = new List<string>();
1502+
foreach (var value in input.HideFeatures)
1503+
{
1504+
if (_fileSystem.File.Exists(value))
1505+
{
1506+
// File exists, read feature IDs from it
1507+
var featureIdsFileContent = await _fileSystem.File.ReadAllTextAsync(value, ctx);
1508+
var featureIdsFromFile = featureIdsFileContent
1509+
.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
1510+
.Where(f => !string.IsNullOrWhiteSpace(f))
1511+
.ToArray();
1512+
1513+
foreach (var featureId in featureIdsFromFile)
1514+
{
1515+
_ = featureIdsToHide.Add(featureId);
1516+
}
1517+
}
1518+
else
1519+
{
1520+
// Check if it looks like a file path
1521+
var looksLikeFilePath = value.Contains(_fileSystem.Path.DirectorySeparatorChar) ||
1522+
value.Contains(_fileSystem.Path.AltDirectorySeparatorChar) ||
1523+
_fileSystem.Path.HasExtension(value);
1524+
1525+
if (looksLikeFilePath)
1526+
{
1527+
// Track non-existent files to check later
1528+
nonExistentFiles.Add(value);
1529+
}
1530+
else
1531+
{
1532+
// Doesn't look like a file path, treat as feature ID
1533+
_ = featureIdsToHide.Add(value);
1534+
}
1535+
}
1536+
}
1537+
1538+
// Report errors for non-existent files
1539+
if (nonExistentFiles.Count > 0)
1540+
{
1541+
foreach (var filePath in nonExistentFiles)
1542+
{
1543+
collector.EmitError(filePath, $"File does not exist: {filePath}");
1544+
}
1545+
return false;
1546+
}
1547+
}
1548+
}
1549+
1550+
// Track hidden entries for warnings
1551+
var hiddenEntries = new List<(string title, string featureId)>();
1552+
foreach (var (entry, _) in allResolvedEntries)
1553+
{
1554+
if (!string.IsNullOrWhiteSpace(entry.FeatureId) && featureIdsToHide.Contains(entry.FeatureId))
1555+
{
1556+
hiddenEntries.Add((entry.Title ?? "Unknown", entry.FeatureId));
1557+
}
1558+
}
1559+
1560+
// Emit warnings for hidden entries
1561+
if (hiddenEntries.Count > 0)
1562+
{
1563+
foreach (var (entryTitle, featureId) in hiddenEntries)
1564+
{
1565+
collector.EmitWarning(string.Empty, $"Changelog entry '{entryTitle}' with feature-id '{featureId}' will be commented out in markdown output");
1566+
}
1567+
}
1568+
14551569
// Render markdown files (use first repo found, or default)
14561570
var repoForRendering = allResolvedEntries.Count > 0 ? allResolvedEntries[0].repo : defaultRepo;
14571571

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

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

14641578
// Render deprecations.md
1465-
await RenderDeprecationsMarkdown(collector, outputDir, title, titleSlug, repoForRendering, allResolvedEntries.Select(e => e.entry).ToList(), entriesByType, input.Subsections, input.HidePrivateLinks, ctx);
1579+
await RenderDeprecationsMarkdown(collector, outputDir, title, titleSlug, repoForRendering, allResolvedEntries.Select(e => e.entry).ToList(), entriesByType, input.Subsections, input.HidePrivateLinks, featureIdsToHide, ctx);
14661580

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

@@ -1501,6 +1615,7 @@ private async Task RenderIndexMarkdown(
15011615
Dictionary<string, List<ChangelogData>> entriesByType,
15021616
bool subsections,
15031617
bool hidePrivateLinks,
1618+
HashSet<string> featureIdsToHide,
15041619
Cancel ctx
15051620
)
15061621
{
@@ -1548,15 +1663,15 @@ Cancel ctx
15481663
{
15491664
sb.AppendLine(CultureInfo.InvariantCulture, $"### Features and enhancements [{repo}-{titleSlug}-features-enhancements]");
15501665
var combined = features.Concat(enhancements).ToList();
1551-
RenderEntriesByArea(sb, combined, repo, subsections, hidePrivateLinks);
1666+
RenderEntriesByArea(sb, combined, repo, subsections, hidePrivateLinks, featureIdsToHide);
15521667
}
15531668

15541669
if (security.Count > 0 || bugFixes.Count > 0)
15551670
{
15561671
sb.AppendLine();
15571672
sb.AppendLine(CultureInfo.InvariantCulture, $"### Fixes [{repo}-{titleSlug}-fixes]");
15581673
var combined = security.Concat(bugFixes).ToList();
1559-
RenderEntriesByArea(sb, combined, repo, subsections, hidePrivateLinks);
1674+
RenderEntriesByArea(sb, combined, repo, subsections, hidePrivateLinks, featureIdsToHide);
15601675
}
15611676
}
15621677
else
@@ -1586,6 +1701,7 @@ private async Task RenderBreakingChangesMarkdown(
15861701
Dictionary<string, List<ChangelogData>> entriesByType,
15871702
bool subsections,
15881703
bool hidePrivateLinks,
1704+
HashSet<string> featureIdsToHide,
15891705
Cancel ctx
15901706
)
15911707
{
@@ -1608,7 +1724,13 @@ Cancel ctx
16081724

16091725
foreach (var entry in areaGroup)
16101726
{
1727+
var shouldHide = !string.IsNullOrWhiteSpace(entry.FeatureId) && featureIdsToHide.Contains(entry.FeatureId);
1728+
16111729
sb.AppendLine();
1730+
if (shouldHide)
1731+
{
1732+
sb.AppendLine("<!--");
1733+
}
16121734
sb.AppendLine(CultureInfo.InvariantCulture, $"::::{{dropdown}} {Beautify(entry.Title)}");
16131735
sb.AppendLine(entry.Description ?? "% Describe the functionality that changed");
16141736
sb.AppendLine();
@@ -1668,6 +1790,10 @@ Cancel ctx
16681790
}
16691791

16701792
sb.AppendLine("::::");
1793+
if (shouldHide)
1794+
{
1795+
sb.AppendLine("-->");
1796+
}
16711797
}
16721798
}
16731799
}
@@ -1698,6 +1824,7 @@ private async Task RenderDeprecationsMarkdown(
16981824
Dictionary<string, List<ChangelogData>> entriesByType,
16991825
bool subsections,
17001826
bool hidePrivateLinks,
1827+
HashSet<string> featureIdsToHide,
17011828
Cancel ctx
17021829
)
17031830
{
@@ -1720,7 +1847,13 @@ Cancel ctx
17201847

17211848
foreach (var entry in areaGroup)
17221849
{
1850+
var shouldHide = !string.IsNullOrWhiteSpace(entry.FeatureId) && featureIdsToHide.Contains(entry.FeatureId);
1851+
17231852
sb.AppendLine();
1853+
if (shouldHide)
1854+
{
1855+
sb.AppendLine("<!--");
1856+
}
17241857
sb.AppendLine(CultureInfo.InvariantCulture, $"::::{{dropdown}} {Beautify(entry.Title)}");
17251858
sb.AppendLine(entry.Description ?? "% Describe the functionality that was deprecated");
17261859
sb.AppendLine();
@@ -1780,6 +1913,10 @@ Cancel ctx
17801913
}
17811914

17821915
sb.AppendLine("::::");
1916+
if (shouldHide)
1917+
{
1918+
sb.AppendLine("-->");
1919+
}
17831920
}
17841921
}
17851922
}
@@ -1799,7 +1936,7 @@ Cancel ctx
17991936
}
18001937

18011938
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0058:Expression value is never used", Justification = "StringBuilder methods return builder for chaining")]
1802-
private void RenderEntriesByArea(StringBuilder sb, List<ChangelogData> entries, string repo, bool subsections, bool hidePrivateLinks)
1939+
private void RenderEntriesByArea(StringBuilder sb, List<ChangelogData> entries, string repo, bool subsections, bool hidePrivateLinks, HashSet<string> featureIdsToHide)
18031940
{
18041941
var groupedByArea = entries.GroupBy(e => GetComponent(e)).ToList();
18051942
foreach (var areaGroup in groupedByArea)
@@ -1813,6 +1950,12 @@ private void RenderEntriesByArea(StringBuilder sb, List<ChangelogData> entries,
18131950

18141951
foreach (var entry in areaGroup)
18151952
{
1953+
var shouldHide = !string.IsNullOrWhiteSpace(entry.FeatureId) && featureIdsToHide.Contains(entry.FeatureId);
1954+
1955+
if (shouldHide)
1956+
{
1957+
sb.Append("% ");
1958+
}
18161959
sb.Append("* ");
18171960
sb.Append(Beautify(entry.Title));
18181961

@@ -1823,6 +1966,10 @@ private void RenderEntriesByArea(StringBuilder sb, List<ChangelogData> entries,
18231966
if (!string.IsNullOrWhiteSpace(entry.Pr))
18241967
{
18251968
sb.AppendLine();
1969+
if (shouldHide)
1970+
{
1971+
sb.Append("% ");
1972+
}
18261973
sb.Append(" ");
18271974
sb.Append(FormatPrLink(entry.Pr, repo, hidePrivateLinks));
18281975
hasCommentedLinks = true;
@@ -1833,6 +1980,10 @@ private void RenderEntriesByArea(StringBuilder sb, List<ChangelogData> entries,
18331980
foreach (var issue in entry.Issues)
18341981
{
18351982
sb.AppendLine();
1983+
if (shouldHide)
1984+
{
1985+
sb.Append("% ");
1986+
}
18361987
sb.Append(" ");
18371988
sb.Append(FormatIssueLink(issue, repo, hidePrivateLinks));
18381989
hasCommentedLinks = true;
@@ -1877,7 +2028,20 @@ private void RenderEntriesByArea(StringBuilder sb, List<ChangelogData> entries,
18772028
sb.AppendLine();
18782029
}
18792030
var indented = Indent(entry.Description);
1880-
sb.AppendLine(indented);
2031+
if (shouldHide)
2032+
{
2033+
// Comment out each line of the description
2034+
var indentedLines = indented.Split('\n');
2035+
foreach (var line in indentedLines)
2036+
{
2037+
sb.Append("% ");
2038+
sb.AppendLine(line);
2039+
}
2040+
}
2041+
else
2042+
{
2043+
sb.AppendLine(indented);
2044+
}
18812045
}
18822046
else
18832047
{

src/tooling/docs-builder/Commands/ChangelogCommand.cs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,8 @@ async static (s, collector, state, ctx) => await s.BundleChangelogs(collector, s
181181
/// <param name="output">Optional: Output directory for rendered markdown files. Defaults to current directory</param>
182182
/// <param name="title">Optional: Title to use for section headers in output markdown files. Defaults to version from first bundle</param>
183183
/// <param name="subsections">Optional: Group entries by area/component in subsections. Defaults to false</param>
184-
/// <param name="hidePrivateLinks">Optional: Hide private links by commenting them out in markdown output. Defaults to false</param>
184+
/// <param name="hidePrivateLinks">Optional: Hide private links by commenting them out in the markdown output. Defaults to false</param>
185+
/// <param name="hideFeatures">Filter by feature IDs (comma-separated), or a path to a newline-delimited file containing feature IDs. Can be specified multiple times. Entries with matching feature-id values will be commented out in the markdown output.</param>
185186
/// <param name="ctx"></param>
186187
[Command("render")]
187188
public async Task<int> Render(
@@ -190,20 +191,47 @@ public async Task<int> Render(
190191
string? title = null,
191192
bool subsections = false,
192193
bool hidePrivateLinks = false,
194+
string[]? hideFeatures = null,
193195
Cancel ctx = default
194196
)
195197
{
196198
await using var serviceInvoker = new ServiceInvoker(collector);
197199

198200
var service = new ChangelogService(logFactory, configurationContext, null);
199201

202+
// Process each --hide-features occurrence: each can be comma-separated feature IDs or a file path
203+
var allFeatureIds = new List<string>();
204+
if (hideFeatures is { Length: > 0 })
205+
{
206+
foreach (var hideFeaturesValue in hideFeatures)
207+
{
208+
if (string.IsNullOrWhiteSpace(hideFeaturesValue))
209+
continue;
210+
211+
// Check if it contains commas - if so, split and add each as a feature ID
212+
if (hideFeaturesValue.Contains(','))
213+
{
214+
var commaSeparatedFeatureIds = hideFeaturesValue
215+
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
216+
.Where(f => !string.IsNullOrWhiteSpace(f));
217+
allFeatureIds.AddRange(commaSeparatedFeatureIds);
218+
}
219+
else
220+
{
221+
// Single value - pass as-is (will be handled by service layer as file path or feature ID)
222+
allFeatureIds.Add(hideFeaturesValue);
223+
}
224+
}
225+
}
226+
200227
var renderInput = new ChangelogRenderInput
201228
{
202229
Bundles = input ?? [],
203230
Output = output,
204231
Title = title,
205232
Subsections = subsections,
206-
HidePrivateLinks = hidePrivateLinks
233+
HidePrivateLinks = hidePrivateLinks,
234+
HideFeatures = allFeatureIds.Count > 0 ? allFeatureIds.ToArray() : null
207235
};
208236

209237
serviceInvoker.AddCommand(service, renderInput,

0 commit comments

Comments
 (0)