diff --git a/src/services/Elastic.Documentation.Services/ChangelogService.cs b/src/services/Elastic.Documentation.Services/ChangelogService.cs index 350020325..b6623e6da 100644 --- a/src/services/Elastic.Documentation.Services/ChangelogService.cs +++ b/src/services/Elastic.Documentation.Services/ChangelogService.cs @@ -393,12 +393,38 @@ 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) + ? $"pr-{prUrl.Replace("/", "-").Replace(":", "-")}" + : SanitizeFilename(input.Title); + filename = $"{timestamp}-{slug}.yaml"; + } + } + else + { + // Default: timestamp-slug.yaml + var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + var slug = string.IsNullOrWhiteSpace(input.Title) + ? (string.IsNullOrWhiteSpace(prUrl) + ? "changelog" + : $"pr-{prUrl.Replace("/", "-").Replace(":", "-")}") + : SanitizeFilename(input.Title); + filename = $"{timestamp}-{slug}.yaml"; + } var filePath = _fileSystem.Path.Combine(outputDir, filename); // Write file 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() {