diff --git a/docs/input/docs/configuration/default-configuration.md b/docs/input/docs/configuration/default-configuration.md
index 79b90b78..9dbe9a86 100644
--- a/docs/input/docs/configuration/default-configuration.md
+++ b/docs/input/docs/configuration/default-configuration.md
@@ -148,6 +148,14 @@ control the look and feel of the generated release notes.
no issues are found to be associated with a milestone. The contents of the
empty release can be controlled via the associated Scriban template.
**NOTE:** This configuration option was added in version 0.20.0 of GitReleaseManager.
+- **sort-issues-by**
+ - A string value which indicates the name of the field used to sort the issues. The
+ possible values are: Title, Id.
+ **NOTE:** This configuration option was added in version 0.21.0 of GitReleaseManager.
+- **sort-issues-direction**
+ - A string value which indicates how the issues are sorted. The posible values are:
+ Ascending, Descending.
+ **NOTE:** This configuration option was added in version 0.21.0 of GitReleaseManager.
See the [example create configuration section](create-configuration) to see an
example of how a footer can be configured.
diff --git a/src/GitReleaseManager.Core/Configuration/Config.cs b/src/GitReleaseManager.Core/Configuration/Config.cs
index 51da2a99..e25aa88f 100644
--- a/src/GitReleaseManager.Core/Configuration/Config.cs
+++ b/src/GitReleaseManager.Core/Configuration/Config.cs
@@ -29,6 +29,8 @@ public Config()
AllowUpdateToPublishedRelease = false,
AllowMilestonesWithoutIssues = false,
IncludeContributors = false,
+ SortIssuesBy = Model.SortIssuesBy.Id,
+ SortIssuesDirection = Model.SortDirection.Ascending,
};
Export = new ExportConfig
diff --git a/src/GitReleaseManager.Core/Configuration/CreateConfig.cs b/src/GitReleaseManager.Core/Configuration/CreateConfig.cs
index 06b27389..65d355f0 100644
--- a/src/GitReleaseManager.Core/Configuration/CreateConfig.cs
+++ b/src/GitReleaseManager.Core/Configuration/CreateConfig.cs
@@ -1,5 +1,6 @@
using System.ComponentModel;
using GitReleaseManager.Core.Attributes;
+using GitReleaseManager.Core.Model;
using YamlDotNet.Serialization;
namespace GitReleaseManager.Core.Configuration
@@ -40,5 +41,11 @@ public class CreateConfig
[YamlMember(Alias = "include-contributors")]
public bool IncludeContributors { get; set; }
+
+ [YamlMember(Alias = "sort-issues-by")]
+ public SortIssuesBy SortIssuesBy { get; set; }
+
+ [YamlMember(Alias = "sort-issues-direction")]
+ public SortDirection SortIssuesDirection { get; set; }
}
}
\ No newline at end of file
diff --git a/src/GitReleaseManager.Core/Model/SortDirection.cs b/src/GitReleaseManager.Core/Model/SortDirection.cs
new file mode 100644
index 00000000..5a498eb0
--- /dev/null
+++ b/src/GitReleaseManager.Core/Model/SortDirection.cs
@@ -0,0 +1,11 @@
+namespace GitReleaseManager.Core.Model
+{
+ public enum SortDirection
+ {
+ /// Ascending.
+ Ascending = 0,
+
+ /// Descending.
+ Descending = 1,
+ }
+}
\ No newline at end of file
diff --git a/src/GitReleaseManager.Core/Model/SortIssuesBy.cs b/src/GitReleaseManager.Core/Model/SortIssuesBy.cs
new file mode 100644
index 00000000..02e4b1aa
--- /dev/null
+++ b/src/GitReleaseManager.Core/Model/SortIssuesBy.cs
@@ -0,0 +1,15 @@
+namespace GitReleaseManager.Core.Model
+{
+ public enum SortIssuesBy
+ {
+ ///
+ /// Sort by title of the issue
+ ///
+ Title,
+
+ ///
+ /// Sort by the Id of the issue
+ ///
+ Id,
+ }
+}
\ No newline at end of file
diff --git a/src/GitReleaseManager.Core/ReleaseNotes/ReleaseNotesBuilder.cs b/src/GitReleaseManager.Core/ReleaseNotes/ReleaseNotesBuilder.cs
index b8fbb815..20a7cf76 100644
--- a/src/GitReleaseManager.Core/ReleaseNotes/ReleaseNotesBuilder.cs
+++ b/src/GitReleaseManager.Core/ReleaseNotes/ReleaseNotesBuilder.cs
@@ -147,13 +147,34 @@ private Dictionary> GetIssuesDict(List issues)
var issueLabels = _configuration.IssueLabelsInclude;
var excludedIssueLabels = _configuration.IssueLabelsExclude;
+ Func getSortPropertyName = (sortBy) =>
+ {
+ return sortBy switch
+ {
+ SortIssuesBy.Title => "Title",
+ SortIssuesBy.Id => "PublicNumber",
+ _ => throw new NotSupportedException($"'{sortBy}' is an unknown sort property."),
+ };
+ };
+
+ Func keySelector = (i) => typeof(Issue).GetProperty(getSortPropertyName(_configuration.Create.SortIssuesBy)).GetValue(i, null);
+
var issuesByLabel = issues
.Where(o => !o.Labels.Any(l => excludedIssueLabels.Any(eil => string.Equals(eil, l.Name, StringComparison.OrdinalIgnoreCase))))
.SelectMany(o => o.Labels, (issue, label) => new { Label = label.Name, Issue = issue })
.Where(o => issueLabels.Any(il => string.Equals(il, o.Label, StringComparison.OrdinalIgnoreCase)))
.GroupBy(o => o.Label, o => o.Issue)
- .OrderBy(o => o.Key)
- .ToDictionary(o => GetValidLabel(o.Key, o.Count()), o => o.OrderBy(issue => issue.PublicNumber).ToList());
+ .OrderBy(o => o.Key) // Sort the labels alphabetically
+ .ToDictionary(o => GetValidLabel(o.Key, o.Count()), o =>
+ {
+ // Sort the issues within each group based on configuration
+ return _configuration.Create.SortIssuesDirection switch
+ {
+ SortDirection.Ascending => o.OrderBy(i => keySelector(i)).ToList(),
+ SortDirection.Descending => o.OrderByDescending(i => keySelector(i)).ToList(),
+ _ => throw new NotSupportedException($"'{_configuration.Create.SortIssuesDirection}' is an unknown sort direction."),
+ };
+ });
return issuesByLabel;
}
diff --git a/src/GitReleaseManager.IntegrationTests/ReleaseNotesBuilderIntegrationTests.cs b/src/GitReleaseManager.IntegrationTests/ReleaseNotesBuilderIntegrationTests.cs
index 2415e5c6..04c85cb3 100644
--- a/src/GitReleaseManager.IntegrationTests/ReleaseNotesBuilderIntegrationTests.cs
+++ b/src/GitReleaseManager.IntegrationTests/ReleaseNotesBuilderIntegrationTests.cs
@@ -77,6 +77,10 @@ public async Task SingleMilestone()
// Indicate that we want to include the 'Contributors' section in the release notes
configuration.Create.IncludeContributors = true;
+ // Configure sorting
+ configuration.Create.SortIssuesBy = Core.Model.SortIssuesBy.Title;
+ configuration.Create.SortIssuesDirection = Core.Model.SortDirection.Descending;
+
var vcsProvider = new GitHubProvider(_gitHubClient, _mapper, _graphQlClient);
var releaseNotesBuilder = new ReleaseNotesBuilder(vcsProvider, _logger, fileSystem, configuration, new TemplateFactory(fileSystem, configuration, TemplateKind.Create));
var result = await releaseNotesBuilder.BuildReleaseNotesAsync("GitTools", "GitReleaseManager", "0.12.0", string.Empty).ConfigureAwait(false); // 0.12.0 contains a mix of issues and PRs
diff --git a/src/GitReleaseManager.Tests/ConfigurationTests.cs b/src/GitReleaseManager.Tests/ConfigurationTests.cs
index 4ebfc358..e21499a3 100644
--- a/src/GitReleaseManager.Tests/ConfigurationTests.cs
+++ b/src/GitReleaseManager.Tests/ConfigurationTests.cs
@@ -2,6 +2,7 @@
using System.IO;
using System.Text;
using GitReleaseManager.Core.Configuration;
+using GitReleaseManager.Core.Model;
using NUnit.Framework;
namespace GitReleaseManager.Tests
@@ -127,5 +128,45 @@ public void Should_WriteSample_Multiline_String_Values()
Environment.NewLine);
Assert.That(text, Contains.Substring(expectedText));
}
+
+ [Test]
+ [TestCase(SortIssuesBy.Id, "Id")]
+ [TestCase(SortIssuesBy.Title, "Title")]
+ public void Should_Write_SortIssuesBy_Values(SortIssuesBy sortBy, string expected)
+ {
+ // Given
+ var config = new Config();
+ config.Create.SortIssuesBy = sortBy;
+
+ // When
+ var builder = new StringBuilder();
+ using (var writer = new StringWriter(builder))
+ {
+ ConfigSerializer.Write(config, writer);
+ }
+
+ // Then
+ Assert.That(builder.ToString(), Contains.Substring($"sort-issues-by: {expected}"));
+ }
+
+ [Test]
+ [TestCase(SortDirection.Ascending, "Ascending")]
+ [TestCase(SortDirection.Descending, "Descending")]
+ public void Should_Write_SortIssuesDirection_Values(SortDirection sortDirection, string expected)
+ {
+ // Given
+ var config = new Config();
+ config.Create.SortIssuesDirection = sortDirection;
+
+ // When
+ var builder = new StringBuilder();
+ using (var writer = new StringWriter(builder))
+ {
+ ConfigSerializer.Write(config, writer);
+ }
+
+ // Then
+ Assert.That(builder.ToString(), Contains.Substring($"sort-issues-direction: {expected}"));
+ }
}
}
\ No newline at end of file