diff --git a/docs/cli/release/changelog-add.md b/docs/cli/release/changelog-add.md index 45d3e31ab..d17b613f3 100644 --- a/docs/cli/release/changelog-add.md +++ b/docs/cli/release/changelog-add.md @@ -38,6 +38,10 @@ docs-builder changelog add [options...] [-h|--help] `--output ` : Optional: Output directory for the changelog fragment. Defaults to current directory. +`--use-pr-number` +: Optional: Use the PR number as the filename instead of generating it from a unique ID and title. +: When using this option, you must also provide the `--pr` option. + `--owner ` : Optional: GitHub repository owner (used when `--pr` is just a number). diff --git a/docs/contribute/changelog.md b/docs/contribute/changelog.md index 693217424..31024aa2a 100644 --- a/docs/contribute/changelog.md +++ b/docs/contribute/changelog.md @@ -41,6 +41,7 @@ Options: --highlight Optional: Include in release highlights [Default: null] --output Optional: Output directory for the changelog fragment. Defaults to current directory [Default: null] --config Optional: Path to the changelog.yml configuration file. Defaults to 'docs/changelog.yml' [Default: null] + --use-pr-number Optional: Use the PR number as the filename instead of generating it from a unique ID and title ``` ### Product format @@ -87,6 +88,28 @@ Refer to [changelog.yml.example](https://github.com/elastic/docs-builder/blob/ma ## Examples +### Filenames + +By default, the command generates filenames using a timestamp and a sanitized version of the title: +`{timestamp}-{sanitized-title}.yaml` + +For example: `1735689600-fixes-enrich-and-lookup-join-resolution.yaml` + +If you want to use the PR number as the filename instead, use the `--use-pr-number` option: + +```sh +docs-builder changelog add \ + --pr https://github.com/elastic/elasticsearch/pull/137431 \ + --products "elasticsearch 9.2.3" \ + --use-pr-number +``` + +This creates a file named `137431.yaml` instead of the default timestamp-based filename. + +:::{important} +When using `--use-pr-number`, you must also provide the `--pr` option. The PR number is extracted from the PR URL or number you provide. +::: + ### Multiple products The following command creates a changelog for a bug fix that applies to two products: diff --git a/src/services/Elastic.Documentation.Services/Changelog/ChangelogInput.cs b/src/services/Elastic.Documentation.Services/Changelog/ChangelogInput.cs index 86d4dce98..59f109a37 100644 --- a/src/services/Elastic.Documentation.Services/Changelog/ChangelogInput.cs +++ b/src/services/Elastic.Documentation.Services/Changelog/ChangelogInput.cs @@ -25,5 +25,6 @@ public class ChangelogInput public bool? Highlight { get; set; } public string? Output { get; set; } public string? Config { get; set; } + public bool UsePrNumber { get; set; } } diff --git a/src/services/Elastic.Documentation.Services/ChangelogService.cs b/src/services/Elastic.Documentation.Services/ChangelogService.cs index f2c96226b..67d071994 100644 --- a/src/services/Elastic.Documentation.Services/ChangelogService.cs +++ b/src/services/Elastic.Documentation.Services/ChangelogService.cs @@ -49,6 +49,13 @@ Cancel ctx return false; } + // Validate that if --use-pr-number is set, PR must be provided + if (input.UsePrNumber && string.IsNullOrWhiteSpace(input.Pr)) + { + collector.EmitError(string.Empty, "When --use-pr-number is specified, --pr must also be provided"); + return false; + } + // If PR is specified, try to fetch PR information and derive title/type if (!string.IsNullOrWhiteSpace(input.Pr)) { @@ -192,10 +199,25 @@ Cancel ctx _ = _fileSystem.Directory.CreateDirectory(outputDir); } - // Generate filename (timestamp-slug.yaml) - var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - var slug = SanitizeFilename(input.Title); - var filename = $"{timestamp}-{slug}.yaml"; + // Generate filename + string filename; + if (input.UsePrNumber) + { + var prNumber = ExtractPrNumber(input.Pr!, input.Owner, input.Repo); + if (prNumber == null) + { + collector.EmitError(string.Empty, $"Unable to extract PR number from '{input.Pr}'. Cannot use --use-pr-number option."); + return false; + } + filename = $"{prNumber}.yaml"; + } + else + { + // Generate filename (timestamp-slug.yaml) + var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + var slug = SanitizeFilename(input.Title); + filename = $"{timestamp}-{slug}.yaml"; + } var filePath = _fileSystem.Path.Combine(outputDir, filename); // Write file @@ -451,6 +473,44 @@ private static string SanitizeFilename(string input) return sanitized; } + private static int? ExtractPrNumber(string prUrl, string? defaultOwner = null, string? defaultRepo = null) + { + // Handle full URL: https://github.com/owner/repo/pull/123 + if (prUrl.StartsWith("https://github.com/", StringComparison.OrdinalIgnoreCase) || + prUrl.StartsWith("http://github.com/", StringComparison.OrdinalIgnoreCase)) + { + var uri = new Uri(prUrl); + var segments = uri.Segments; + // segments[0] is "/", segments[1] is "owner/", segments[2] is "repo/", segments[3] is "pull/", segments[4] is "123" + if (segments.Length >= 5 && + segments[3].Equals("pull/", StringComparison.OrdinalIgnoreCase) && + int.TryParse(segments[4], out var prNum)) + { + return prNum; + } + } + + // Handle short format: owner/repo#123 + var hashIndex = prUrl.LastIndexOf('#'); + if (hashIndex > 0 && hashIndex < prUrl.Length - 1) + { + var prPart = prUrl[(hashIndex + 1)..]; + if (int.TryParse(prPart, out var prNum)) + { + return prNum; + } + } + + // Handle just a PR number when owner/repo are provided + if (int.TryParse(prUrl, out var prNumber) && + !string.IsNullOrWhiteSpace(defaultOwner) && !string.IsNullOrWhiteSpace(defaultRepo)) + { + return prNumber; + } + + return null; + } + private async Task TryFetchPrInfoAsync(string? prUrl, string? owner, string? repo, Cancel ctx) { if (string.IsNullOrWhiteSpace(prUrl) || _githubPrService == null) diff --git a/src/tooling/docs-builder/Commands/ChangelogCommand.cs b/src/tooling/docs-builder/Commands/ChangelogCommand.cs index 7c108441d..335ccf0c0 100644 --- a/src/tooling/docs-builder/Commands/ChangelogCommand.cs +++ b/src/tooling/docs-builder/Commands/ChangelogCommand.cs @@ -47,6 +47,7 @@ public Task Default() /// Optional: Include in release highlights /// Optional: Output directory for the changelog fragment. Defaults to current directory /// Optional: Path to the changelog.yml configuration file. Defaults to 'docs/changelog.yml' + /// Optional: Use the PR number as the filename instead of generating it from a unique ID and title /// [Command("add")] public async Task Create( @@ -66,6 +67,7 @@ public async Task Create( bool? highlight = null, string? output = null, string? config = null, + bool usePrNumber = false, Cancel ctx = default ) { @@ -91,7 +93,8 @@ public async Task Create( FeatureId = featureId, Highlight = highlight, Output = output, - Config = config + Config = config, + UsePrNumber = usePrNumber }; serviceInvoker.AddCommand(service, input,