From 89bac02831f6913944acd7d650c7b9e94473cbba Mon Sep 17 00:00:00 2001 From: lcawl Date: Fri, 16 Jan 2026 09:31:24 -0800 Subject: [PATCH 1/5] Rebase --- .../ChangelogService.cs | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/src/services/Elastic.Documentation.Services/ChangelogService.cs b/src/services/Elastic.Documentation.Services/ChangelogService.cs index 350020325..f1c65d2be 100644 --- a/src/services/Elastic.Documentation.Services/ChangelogService.cs +++ b/src/services/Elastic.Documentation.Services/ChangelogService.cs @@ -393,12 +393,36 @@ Cancel ctx _ = _fileSystem.Directory.CreateDirectory(outputDir); } - // Generate filename (timestamp-slug.yaml) - var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - var slug = string.IsNullOrWhiteSpace(input.Title) - ? (prUrl != null ? $"pr-{prUrl.Replace("/", "-").Replace(":", "-")}" : "changelog") - : SanitizeFilename(input.Title); - var filename = $"{timestamp}-{slug}.yaml"; + // Generate filename + string filename; + if (input.UsePrNumber && !string.IsNullOrWhiteSpace(prUrl)) + { + // Use PR number as filename when --use-pr-number is specified + var prNumber = ExtractPrNumber(prUrl, input.Owner, input.Repo); + if (prNumber.HasValue) + { + filename = $"{prNumber.Value}.yaml"; + } + else + { + // Fall back to timestamp-slug format if PR number extraction fails + collector.EmitWarning(string.Empty, $"Failed to extract PR number from '{prUrl}'. Falling back to timestamp-based filename."); + var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + var slug = string.IsNullOrWhiteSpace(input.Title) + ? (prUrl != null ? $"pr-{prUrl.Replace("/", "-").Replace(":", "-")}" : "changelog") + : SanitizeFilename(input.Title); + filename = $"{timestamp}-{slug}.yaml"; + } + } + else + { + // Default: timestamp-slug.yaml + var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + var slug = string.IsNullOrWhiteSpace(input.Title) + ? (prUrl != null ? $"pr-{prUrl.Replace("/", "-").Replace(":", "-")}" : "changelog") + : SanitizeFilename(input.Title); + filename = $"{timestamp}-{slug}.yaml"; + } var filePath = _fileSystem.Path.Combine(outputDir, filename); // Write file From 994eee879620484d0567729c1ffad80c71358cfa Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Thu, 15 Jan 2026 15:25:32 -0800 Subject: [PATCH 2/5] Potential fix for pull request finding 'Constant condition' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --- .../Elastic.Documentation.Services/ChangelogService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/Elastic.Documentation.Services/ChangelogService.cs b/src/services/Elastic.Documentation.Services/ChangelogService.cs index f1c65d2be..dd5332ac6 100644 --- a/src/services/Elastic.Documentation.Services/ChangelogService.cs +++ b/src/services/Elastic.Documentation.Services/ChangelogService.cs @@ -409,7 +409,7 @@ Cancel ctx collector.EmitWarning(string.Empty, $"Failed to extract PR number from '{prUrl}'. Falling back to timestamp-based filename."); var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); var slug = string.IsNullOrWhiteSpace(input.Title) - ? (prUrl != null ? $"pr-{prUrl.Replace("/", "-").Replace(":", "-")}" : "changelog") + ? $"pr-{prUrl.Replace("/", "-").Replace(":", "-")}" : SanitizeFilename(input.Title); filename = $"{timestamp}-{slug}.yaml"; } @@ -419,7 +419,7 @@ Cancel ctx // Default: timestamp-slug.yaml var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); var slug = string.IsNullOrWhiteSpace(input.Title) - ? (prUrl != null ? $"pr-{prUrl.Replace("/", "-").Replace(":", "-")}" : "changelog") + ? $"pr-{prUrl.Replace("/", "-").Replace(":", "-")}" : SanitizeFilename(input.Title); filename = $"{timestamp}-{slug}.yaml"; } From 6d9fef10b09d77fd56b1191cb70d934059c621b7 Mon Sep 17 00:00:00 2001 From: lcawl Date: Thu, 15 Jan 2026 15:25:45 -0800 Subject: [PATCH 3/5] Add test --- .../ChangelogServiceTests.cs | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/tests/Elastic.Documentation.Services.Tests/ChangelogServiceTests.cs b/tests/Elastic.Documentation.Services.Tests/ChangelogServiceTests.cs index dedb25d47..c8e80e705 100644 --- a/tests/Elastic.Documentation.Services.Tests/ChangelogServiceTests.cs +++ b/tests/Elastic.Documentation.Services.Tests/ChangelogServiceTests.cs @@ -232,6 +232,78 @@ public async Task CreateChangelog_WithPrOption_FetchesPrInfoAndDerivesTitle() yamlContent.Should().Contain("pr: https://github.com/elastic/elasticsearch/pull/12345"); } + [Fact] + public async Task CreateChangelog_WithUsePrNumber_CreatesFileWithPrNumberAsFilename() + { + // Arrange + var mockGitHubService = A.Fake(); + var prInfo = new GitHubPrInfo + { + Title = "Fix memory leak in search", + Labels = ["type:bug"] + }; + + A.CallTo(() => mockGitHubService.FetchPrInfoAsync( + "https://github.com/elastic/elasticsearch/pull/140034", + null, + null, + A._)) + .Returns(prInfo); + + // Create a config file with label mappings + // Note: ChangelogService uses real FileSystem, so we need to use the real file system + var fileSystem = new FileSystem(); + var configDir = fileSystem.Path.Combine(fileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + fileSystem.Directory.CreateDirectory(configDir); + var configPath = fileSystem.Path.Combine(configDir, "changelog.yml"); + var configContent = """ + available_types: + - feature + - bug-fix + available_subtypes: [] + available_lifecycles: + - preview + - beta + - ga + label_to_type: + "type:bug": bug-fix + """; + await fileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); + + var service = new ChangelogService(_loggerFactory, _configurationContext, mockGitHubService); + + var input = new ChangelogInput + { + Prs = ["https://github.com/elastic/elasticsearch/pull/140034"], + Products = [new ProductInfo { Product = "elasticsearch", Target = "9.2.0", Lifecycle = "ga" }], + Config = configPath, + Output = fileSystem.Path.Combine(fileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()), + UsePrNumber = true + }; + + // Act + var result = await service.CreateChangelog(_collector, input, TestContext.Current.CancellationToken); + + // Assert + result.Should().BeTrue(); + _collector.Errors.Should().Be(0); + + // Note: ChangelogService uses real FileSystem, so we need to check the actual file system + var outputDir = input.Output ?? Directory.GetCurrentDirectory(); + if (!Directory.Exists(outputDir)) + Directory.CreateDirectory(outputDir); + var files = Directory.GetFiles(outputDir, "*.yaml"); + files.Should().HaveCount(1); + + // Verify the filename is the PR number, not a timestamp-based name + var fileName = Path.GetFileName(files[0]); + fileName.Should().Be("140034.yaml", "the filename should be the PR number when UsePrNumber is true"); + + var yamlContent = await File.ReadAllTextAsync(files[0], TestContext.Current.CancellationToken); + yamlContent.Should().Contain("type: bug-fix"); + yamlContent.Should().Contain("pr: https://github.com/elastic/elasticsearch/pull/140034"); + } + [Fact] public async Task CreateChangelog_WithPrOptionAndLabelMapping_MapsLabelsToType() { From 089cd7b6b3399d40e0ca1b0c54f47e3014da9c29 Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Thu, 15 Jan 2026 15:41:31 -0800 Subject: [PATCH 4/5] Potential fix for pull request finding 'Dereferenced variable may be null' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --- .../Elastic.Documentation.Services/ChangelogService.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/services/Elastic.Documentation.Services/ChangelogService.cs b/src/services/Elastic.Documentation.Services/ChangelogService.cs index dd5332ac6..b6623e6da 100644 --- a/src/services/Elastic.Documentation.Services/ChangelogService.cs +++ b/src/services/Elastic.Documentation.Services/ChangelogService.cs @@ -419,7 +419,9 @@ Cancel ctx // Default: timestamp-slug.yaml var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); var slug = string.IsNullOrWhiteSpace(input.Title) - ? $"pr-{prUrl.Replace("/", "-").Replace(":", "-")}" + ? (string.IsNullOrWhiteSpace(prUrl) + ? "changelog" + : $"pr-{prUrl.Replace("/", "-").Replace(":", "-")}") : SanitizeFilename(input.Title); filename = $"{timestamp}-{slug}.yaml"; } From 0396939836e5b7b75229471b06a6c9a927794252 Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Mon, 19 Jan 2026 07:49:57 -0300 Subject: [PATCH 5/5] Minor cleanups --- .../Elastic.Documentation.Services/ChangelogService.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/services/Elastic.Documentation.Services/ChangelogService.cs b/src/services/Elastic.Documentation.Services/ChangelogService.cs index b6623e6da..fa864c764 100644 --- a/src/services/Elastic.Documentation.Services/ChangelogService.cs +++ b/src/services/Elastic.Documentation.Services/ChangelogService.cs @@ -419,9 +419,9 @@ Cancel ctx // Default: timestamp-slug.yaml var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); var slug = string.IsNullOrWhiteSpace(input.Title) - ? (string.IsNullOrWhiteSpace(prUrl) + ? string.IsNullOrWhiteSpace(prUrl) ? "changelog" - : $"pr-{prUrl.Replace("/", "-").Replace(":", "-")}") + : $"pr-{prUrl.Replace("/", "-").Replace(":", "-")}" : SanitizeFilename(input.Title); filename = $"{timestamp}-{slug}.yaml"; } @@ -724,7 +724,7 @@ private string GenerateYaml(ChangelogData data, ChangelogConfiguration config, b # An optional string for new features or enhancements that have a specific availability. # It can be one of: {lifecyclesList} - + ##### Optional fields ##### # action: @@ -816,7 +816,7 @@ private static string StripSquareBracketPrefix(string title) // 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)) + int.TryParse(segments[4].TrimEnd('/'), out var prNum)) { return prNum; }