Skip to content

Commit a522f70

Browse files
lcawlcotti
andauthored
Add description-visibility option to changelog directive (#3224)
Co-authored-by: Felipe Cotti <felipe.cotti@elastic.co>
1 parent 25c5de5 commit a522f70

6 files changed

Lines changed: 428 additions & 29 deletions

File tree

docs/syntax/changelog.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ The directive supports the following options:
2525
| `:type: value` | Filter entries by type | Excludes separated types |
2626
| `:subsections:` | Group entries by area/component | false |
2727
| `:link-visibility: value` | Visibility of pull request (PR) and issue links | `auto` |
28+
| `:description-visibility: value` | Visibility of changelog **record** descriptions (YAML `description` on each entry) | `auto` |
2829
| `:config: path` | Path to `changelog.yml` configuration | auto-discover |
2930

3031
### Example with options
@@ -34,6 +35,7 @@ The directive supports the following options:
3435
:type: all
3536
:subsections:
3637
:link-visibility: keep-links
38+
:description-visibility: keep-descriptions
3739
:::
3840
```
3941

@@ -114,6 +116,18 @@ Bundles whose repo is listed as private in `assembler.yml` hide links by default
114116

115117
This aligns with the `changelog render` command's link visibility controls.
116118

119+
#### `:description-visibility:`
120+
121+
Controls whether the **`description`** text on each **changelog record** appears in output (bullet body text under each item, and the first paragraph inside breaking-change, deprecation, known-issue, and highlight dropdowns). This is **different** from the optional **bundle** `description` field (release intro prose after `_Released:_`), which is always shown when present. See [Rendered output](#rendered-output).
122+
123+
| Value | Behavior |
124+
|-------|----------|
125+
| `auto` | When **every** constituent repository in the bundle’s resolved repo identity is **public** (same private-repo detection as `:link-visibility:` from `assembler.yml`, including `repo1+repo2` merged bundles), **omit** record `description` bodies. When **any** constituent is marked **private**, **show** those bodies. In standalone builds without `assembler.yml`, every repo is treated as public ⇒ record descriptions are omitted under `auto`. |
126+
| `keep-descriptions` | Always render record descriptions when present in the bundle source. Use this on pages such as deprecations or breaking changes when you still want full release-note prose alongside public repos. |
127+
| `hide-descriptions` | Always omit record `description` bodies (titles, PR/issue links, Impact and Action sections, and bundle-level intros are unaffected). |
128+
129+
**Contrast with `:link-visibility:`:** `:link-visibility: auto` hides **links** when a repo is **private**. `:description-visibility: auto` **shows** richer record **description** prose when **any** source repo is **private**, and hides that prose for bundles that resolve to **only public** repositories.
130+
117131
#### `:subsections:`
118132

119133
When enabled, entries are grouped by "area" within each section.
@@ -254,6 +268,8 @@ When present, the `release-date` field is rendered immediately after the version
254268

255269
Bundle descriptions are rendered when present in the bundle YAML file. The description appears after the release date (if any) but before any entry sections. Descriptions support Markdown formatting including links, lists, and multiple paragraphs.
256270

271+
**Record descriptions:** Each changelog entry may have its own `description` field in YAML (shown as body text under list items or as the introductory paragraph inside dropdowns). Visibility of **these** descriptions is controlled with `:description-visibility:` (defaults to `auto`; see Option details section). Do not confuse bundle `description` (intro prose) with per-record `description` (entry bodies).
272+
257273
### Section types
258274

259275
| Section | Entry type | Rendering |

src/Elastic.Markdown/Myst/Directives/Changelog/ChangelogBlock.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,11 @@ public class ChangelogBlock(DirectiveBlockParser parser, ParserContext context)
160160
/// </summary>
161161
public ChangelogLinkVisibility LinkVisibility { get; private set; }
162162

163+
/// <summary>
164+
/// Visibility of changelog record <c>description</c> body text (see :description-visibility: option).
165+
/// </summary>
166+
public ChangelogDescriptionVisibility DescriptionVisibility { get; private set; }
167+
163168
/// <summary>
164169
/// Returns all anchors that will be generated by this directive during rendering.
165170
/// </summary>
@@ -183,6 +188,7 @@ public override void FinalizeAndValidate(ParserContext context)
183188
LoadConfiguration();
184189
LoadPrivateRepositories();
185190
LinkVisibility = ParseLinkVisibility();
191+
DescriptionVisibility = ParseDescriptionVisibility();
186192
if (Found)
187193
LoadAndCacheBundles();
188194
}
@@ -209,6 +215,28 @@ private ChangelogLinkVisibility EmitInvalidLinkVisibilityWarning(string value)
209215
return ChangelogLinkVisibility.Auto;
210216
}
211217

218+
private ChangelogDescriptionVisibility ParseDescriptionVisibility()
219+
{
220+
var value = Prop("description-visibility");
221+
if (string.IsNullOrWhiteSpace(value))
222+
return ChangelogDescriptionVisibility.Auto;
223+
224+
return value.ToLowerInvariant() switch
225+
{
226+
"auto" => ChangelogDescriptionVisibility.Auto,
227+
"keep-descriptions" => ChangelogDescriptionVisibility.KeepDescriptions,
228+
"hide-descriptions" => ChangelogDescriptionVisibility.HideDescriptions,
229+
_ => EmitInvalidDescriptionVisibilityWarning(value)
230+
};
231+
}
232+
233+
private ChangelogDescriptionVisibility EmitInvalidDescriptionVisibilityWarning(string value)
234+
{
235+
this.EmitWarning(
236+
$"Invalid :description-visibility: value '{value}'. Valid values are: auto, keep-descriptions, hide-descriptions. Using auto.");
237+
return ChangelogDescriptionVisibility.Auto;
238+
}
239+
212240
/// <summary>
213241
/// Parses and validates the :type: option.
214242
/// Valid values: all, breaking-change, deprecation, known-issue, highlight.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
namespace Elastic.Markdown.Myst.Directives.Changelog;
6+
7+
/// <summary>
8+
/// Controls changelog entry description (body text) rendering for the {changelog} directive.
9+
/// Mirrors the structure of <see cref="ChangelogLinkVisibility"/> while using opposite privacy defaults for <see cref="Auto"/>.
10+
/// </summary>
11+
public enum ChangelogDescriptionVisibility
12+
{
13+
/// <summary>
14+
/// Hide record descriptions when the bundle has only public constituent repos (per assembler.yml);
15+
/// show when any constituent is private. With no private repos configured, hides descriptions everywhere.
16+
/// </summary>
17+
Auto,
18+
19+
/// <summary>
20+
/// Always render record descriptions when present in source YAML.
21+
/// </summary>
22+
KeepDescriptions,
23+
24+
/// <summary>
25+
/// Never render record descriptions (including dropdown authoring placeholders).
26+
/// </summary>
27+
HideDescriptions
28+
}

src/Elastic.Markdown/Myst/Directives/Changelog/ChangelogInlineRenderer.cs

Lines changed: 78 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ public static class ChangelogInlineRenderer
3737
block.PrivateRepositories,
3838
block.HideFeatures,
3939
typeFilter,
40-
block.LinkVisibility);
40+
block.LinkVisibility,
41+
block.DescriptionVisibility);
4142
_ = sb.Append(bundleMarkdown);
4243

4344
isFirst = false;
@@ -53,7 +54,8 @@ private static string RenderSingleBundle(
5354
HashSet<string> privateRepositories,
5455
HashSet<string> hideFeatures,
5556
ChangelogTypeFilter typeFilter,
56-
ChangelogLinkVisibility linkVisibility)
57+
ChangelogLinkVisibility linkVisibility,
58+
ChangelogDescriptionVisibility descriptionVisibility)
5759
{
5860
var titleSlug = ChangelogTextUtilities.TitleToSlug(bundle.Version);
5961

@@ -78,8 +80,22 @@ private static string RenderSingleBundle(
7880
_ => ShouldHideLinksForRepo(bundle.Repo, privateRepositories)
7981
};
8082

83+
var hideEntryDescriptions = ShouldHideEntryDescriptionsForRepo(bundle.Repo, privateRepositories, descriptionVisibility);
84+
8185
var displayVersion = VersionOrDate.FormatDisplayVersion(bundle.Version);
82-
return GenerateMarkdown(displayVersion, titleSlug, bundle.Repo, bundle.Owner, entriesByType, subsections, hideLinks, typeFilter, publishBlocker, bundle.Data?.Description, bundle.Data?.ReleaseDate);
86+
return GenerateMarkdown(
87+
displayVersion,
88+
titleSlug,
89+
bundle.Repo,
90+
bundle.Owner,
91+
entriesByType,
92+
subsections,
93+
hideLinks,
94+
hideEntryDescriptions,
95+
typeFilter,
96+
publishBlocker,
97+
bundle.Data?.Description,
98+
bundle.Data?.ReleaseDate);
8399
}
84100

85101
/// <summary>
@@ -123,10 +139,35 @@ public static bool ShouldHideLinksForRepo(string bundleRepo, HashSet<string> pri
123139
if (privateRepositories.Count == 0)
124140
return false;
125141

126-
// Split on '+' to handle merged bundles (e.g., "elasticsearch+kibana+private-repo")
142+
return HasAnyPrivateRepoConstituent(bundleRepo, privateRepositories);
143+
}
144+
145+
/// <summary>
146+
/// When true, changelog entry YAML <c>description</c> bodies (bullet text and dropdown intro) must not be rendered.
147+
/// </summary>
148+
public static bool ShouldHideEntryDescriptionsForRepo(
149+
string bundleRepo,
150+
HashSet<string> privateRepositories,
151+
ChangelogDescriptionVisibility visibility) =>
152+
visibility switch
153+
{
154+
ChangelogDescriptionVisibility.HideDescriptions => true,
155+
ChangelogDescriptionVisibility.KeepDescriptions => false,
156+
ChangelogDescriptionVisibility.Auto => !HasAnyPrivateRepoConstituent(bundleRepo, privateRepositories),
157+
_ => !HasAnyPrivateRepoConstituent(bundleRepo, privateRepositories)
158+
};
159+
160+
/// <summary>
161+
/// True when merged <paramref name="bundleRepo"/> (<c>elasticsearch+kibana</c>-style) has at least one
162+
/// component listed as private for the build.
163+
/// </summary>
164+
public static bool HasAnyPrivateRepoConstituent(string bundleRepo, HashSet<string> privateRepositories)
165+
{
166+
if (privateRepositories.Count == 0)
167+
return false;
168+
127169
var repos = bundleRepo.Split('+', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
128170

129-
// Hide links if ANY component repo is private
130171
return repos.Any(privateRepositories.Contains);
131172
}
132173

@@ -151,6 +192,7 @@ private static string GenerateMarkdown(
151192
Dictionary<ChangelogEntryType, List<ChangelogEntry>> entriesByType,
152193
bool subsections,
153194
bool hideLinks,
195+
bool hideEntryDescriptions,
154196
ChangelogTypeFilter typeFilter,
155197
PublishBlocker? publishBlocker,
156198
string? description = null,
@@ -207,79 +249,79 @@ private static string GenerateMarkdown(
207249
if (typeFilter == ChangelogTypeFilter.Highlight)
208250
{
209251
if (highlights.Count > 0)
210-
RenderDetailedEntries(sb, highlights, repo, owner, groupBySubtype: false, hideLinks, publishBlocker);
252+
RenderDetailedEntries(sb, highlights, repo, owner, groupBySubtype: false, hideLinks, hideEntryDescriptions, publishBlocker);
211253
return sb.ToString();
212254
}
213255

214256
if (breakingChanges.Count > 0)
215257
{
216258
_ = sb.AppendLine();
217259
_ = sb.AppendLine(CultureInfo.InvariantCulture, $"### Breaking changes [{repo}-{titleSlug}-breaking-changes]");
218-
RenderDetailedEntries(sb, breakingChanges, repo, owner, groupBySubtype: true, hideLinks, publishBlocker);
260+
RenderDetailedEntries(sb, breakingChanges, repo, owner, groupBySubtype: true, hideLinks, hideEntryDescriptions, publishBlocker);
219261
}
220262

221263
if (highlights.Count > 0 && typeFilter == ChangelogTypeFilter.All)
222264
{
223265
_ = sb.AppendLine();
224266
_ = sb.AppendLine(CultureInfo.InvariantCulture, $"### Highlights [{repo}-{titleSlug}-highlights]");
225-
RenderDetailedEntries(sb, highlights, repo, owner, groupBySubtype: false, hideLinks, publishBlocker);
267+
RenderDetailedEntries(sb, highlights, repo, owner, groupBySubtype: false, hideLinks, hideEntryDescriptions, publishBlocker);
226268
}
227269

228270
if (security.Count > 0)
229271
{
230272
_ = sb.AppendLine();
231273
_ = sb.AppendLine(CultureInfo.InvariantCulture, $"### Security [{repo}-{titleSlug}-security]");
232-
RenderEntriesByArea(sb, security, repo, owner, subsections, hideLinks, publishBlocker);
274+
RenderEntriesByArea(sb, security, repo, owner, subsections, hideLinks, hideEntryDescriptions, publishBlocker);
233275
}
234276

235277
if (knownIssues.Count > 0)
236278
{
237279
_ = sb.AppendLine();
238280
_ = sb.AppendLine(CultureInfo.InvariantCulture, $"### Known issues [{repo}-{titleSlug}-known-issues]");
239-
RenderDetailedEntries(sb, knownIssues, repo, owner, groupBySubtype: false, hideLinks, publishBlocker);
281+
RenderDetailedEntries(sb, knownIssues, repo, owner, groupBySubtype: false, hideLinks, hideEntryDescriptions, publishBlocker);
240282
}
241283

242284
if (deprecations.Count > 0)
243285
{
244286
_ = sb.AppendLine();
245287
_ = sb.AppendLine(CultureInfo.InvariantCulture, $"### Deprecations [{repo}-{titleSlug}-deprecations]");
246-
RenderDetailedEntries(sb, deprecations, repo, owner, groupBySubtype: false, hideLinks, publishBlocker);
288+
RenderDetailedEntries(sb, deprecations, repo, owner, groupBySubtype: false, hideLinks, hideEntryDescriptions, publishBlocker);
247289
}
248290

249291
if (features.Count > 0 || enhancements.Count > 0)
250292
{
251293
_ = sb.AppendLine();
252294
_ = sb.AppendLine(CultureInfo.InvariantCulture, $"### Features and enhancements [{repo}-{titleSlug}-features-enhancements]");
253295
var combined = features.Concat(enhancements).ToList();
254-
RenderEntriesByArea(sb, combined, repo, owner, subsections, hideLinks, publishBlocker);
296+
RenderEntriesByArea(sb, combined, repo, owner, subsections, hideLinks, hideEntryDescriptions, publishBlocker);
255297
}
256298

257299
if (bugFixes.Count > 0)
258300
{
259301
_ = sb.AppendLine();
260302
_ = sb.AppendLine(CultureInfo.InvariantCulture, $"### Fixes [{repo}-{titleSlug}-fixes]");
261-
RenderEntriesByArea(sb, bugFixes, repo, owner, subsections, hideLinks, publishBlocker);
303+
RenderEntriesByArea(sb, bugFixes, repo, owner, subsections, hideLinks, hideEntryDescriptions, publishBlocker);
262304
}
263305

264306
if (docs.Count > 0)
265307
{
266308
_ = sb.AppendLine();
267309
_ = sb.AppendLine(CultureInfo.InvariantCulture, $"### Documentation [{repo}-{titleSlug}-docs]");
268-
RenderEntriesByArea(sb, docs, repo, owner, subsections, hideLinks, publishBlocker);
310+
RenderEntriesByArea(sb, docs, repo, owner, subsections, hideLinks, hideEntryDescriptions, publishBlocker);
269311
}
270312

271313
if (regressions.Count > 0)
272314
{
273315
_ = sb.AppendLine();
274316
_ = sb.AppendLine(CultureInfo.InvariantCulture, $"### Regressions [{repo}-{titleSlug}-regressions]");
275-
RenderEntriesByArea(sb, regressions, repo, owner, subsections, hideLinks, publishBlocker);
317+
RenderEntriesByArea(sb, regressions, repo, owner, subsections, hideLinks, hideEntryDescriptions, publishBlocker);
276318
}
277319

278320
if (other.Count > 0)
279321
{
280322
_ = sb.AppendLine();
281323
_ = sb.AppendLine(CultureInfo.InvariantCulture, $"### Other changes [{repo}-{titleSlug}-other]");
282-
RenderEntriesByArea(sb, other, repo, owner, subsections, hideLinks, publishBlocker);
324+
RenderEntriesByArea(sb, other, repo, owner, subsections, hideLinks, hideEntryDescriptions, publishBlocker);
283325
}
284326

285327
return sb.ToString();
@@ -292,6 +334,7 @@ private static void RenderEntriesByArea(
292334
string owner,
293335
bool subsections,
294336
bool hideLinks,
337+
bool hideEntryDescriptions,
295338
PublishBlocker? publishBlocker)
296339
{
297340
if (subsections)
@@ -309,29 +352,29 @@ private static void RenderEntriesByArea(
309352
}
310353

311354
foreach (var entry in areaGroup)
312-
RenderSingleEntry(sb, entry, repo, owner, hideLinks);
355+
RenderSingleEntry(sb, entry, repo, owner, hideLinks, hideEntryDescriptions);
313356
}
314357
}
315358
else
316359
{
317360
foreach (var entry in entries)
318-
RenderSingleEntry(sb, entry, repo, owner, hideLinks);
361+
RenderSingleEntry(sb, entry, repo, owner, hideLinks, hideEntryDescriptions);
319362
}
320363
}
321364

322-
private static void RenderSingleEntry(StringBuilder sb, ChangelogEntry entry, string repo, string owner, bool hideLinks)
365+
private static void RenderSingleEntry(StringBuilder sb, ChangelogEntry entry, string repo, string owner, bool hideLinks, bool hideEntryDescriptions)
323366
{
324367
_ = sb.Append("* ");
325368
_ = sb.Append(ChangelogTextUtilities.Beautify(entry.Title));
326369

327370
RenderEntryLinks(sb, entry, repo, owner, hideLinks);
328371

329-
if (!string.IsNullOrWhiteSpace(entry.Description))
330-
{
331-
_ = sb.AppendLine();
332-
var indented = ChangelogTextUtilities.Indent(entry.Description);
333-
_ = sb.AppendLine(indented);
334-
}
372+
if (hideEntryDescriptions || string.IsNullOrWhiteSpace(entry.Description))
373+
return;
374+
375+
_ = sb.AppendLine();
376+
var indented = ChangelogTextUtilities.Indent(entry.Description);
377+
_ = sb.AppendLine(indented);
335378
}
336379

337380
private static void RenderEntryLinks(StringBuilder sb, ChangelogEntry entry, string repo, string owner, bool hideLinks)
@@ -373,6 +416,7 @@ private static void RenderDetailedEntries(
373416
string owner,
374417
bool groupBySubtype,
375418
bool hideLinks,
419+
bool hideEntryDescriptions,
376420
PublishBlocker? publishBlocker)
377421
{
378422
var grouped = groupBySubtype
@@ -391,16 +435,21 @@ private static void RenderDetailedEntries(
391435
}
392436

393437
foreach (var entry in group)
394-
RenderDetailedEntry(sb, entry, repo, owner, hideLinks);
438+
RenderDetailedEntry(sb, entry, repo, owner, hideLinks, hideEntryDescriptions);
395439
}
396440
}
397441

398-
private static void RenderDetailedEntry(StringBuilder sb, ChangelogEntry entry, string repo, string owner, bool hideLinks)
442+
private static void RenderDetailedEntry(StringBuilder sb, ChangelogEntry entry, string repo, string owner, bool hideLinks, bool hideEntryDescriptions)
399443
{
400444
_ = sb.AppendLine();
401445
_ = sb.AppendLine(CultureInfo.InvariantCulture, $"::::{{dropdown}} {ChangelogTextUtilities.Beautify(entry.Title)}");
402-
_ = sb.AppendLine(entry.Description ?? "% Describe the change");
403-
_ = sb.AppendLine();
446+
if (!hideEntryDescriptions)
447+
{
448+
_ = sb.AppendLine(entry.Description ?? "% Describe the change");
449+
_ = sb.AppendLine();
450+
}
451+
else
452+
_ = sb.AppendLine();
404453

405454
RenderDetailedEntryLinks(sb, entry, repo, owner, hideLinks);
406455

tests/Elastic.Markdown.Tests/Directives/ChangelogBasicTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,7 @@ public ChangelogTitleDescriptionSpacingTests(ITestOutputHelper output) : base(ou
582582
// language=markdown
583583
"""
584584
:::{changelog}
585+
:description-visibility: keep-descriptions
585586
:::
586587
""") => FileSystem.AddFile("docs/changelog/bundles/9.3.0.yaml", new MockFileData(
587588
// language=yaml

0 commit comments

Comments
 (0)