mirror of
https://github.com/elastic/elasticsearch.git
synced 2025-04-25 23:57:20 -04:00
Generate a release notes page per patch version (#85279)
Closes #85250. The approach of generating a single asciidoc page for all releases in a minor series makes it harder to preserve any manual edits that we have to make. Instead, generate a page per-version. It should make little difference to the final documentation that users see.
This commit is contained in:
parent
fa1053e972
commit
4ec75537cd
9 changed files with 75 additions and 159 deletions
|
@ -82,7 +82,9 @@ public class GenerateReleaseNotesTask extends DefaultTask {
|
|||
|
||||
@TaskAction
|
||||
public void executeTask() throws IOException {
|
||||
if (needsGitTags(VersionProperties.getElasticsearch())) {
|
||||
final String currentVersion = VersionProperties.getElasticsearch();
|
||||
|
||||
if (needsGitTags(currentVersion)) {
|
||||
findAndUpdateUpstreamRemote(gitWrapper);
|
||||
}
|
||||
|
||||
|
@ -90,7 +92,7 @@ public class GenerateReleaseNotesTask extends DefaultTask {
|
|||
|
||||
final Map<QualifiedVersion, Set<File>> filesByVersion = partitionFilesByVersion(
|
||||
gitWrapper,
|
||||
VersionProperties.getElasticsearch(),
|
||||
currentVersion,
|
||||
this.changelogs.getFiles()
|
||||
);
|
||||
|
||||
|
@ -103,7 +105,7 @@ public class GenerateReleaseNotesTask extends DefaultTask {
|
|||
changelogsByVersion.put(version, entriesForVersion);
|
||||
});
|
||||
|
||||
final Set<QualifiedVersion> versions = getVersions(gitWrapper, VersionProperties.getElasticsearch());
|
||||
final Set<QualifiedVersion> versions = getVersions(gitWrapper, currentVersion);
|
||||
|
||||
LOGGER.info("Updating release notes index...");
|
||||
ReleaseNotesIndexGenerator.update(
|
||||
|
@ -113,10 +115,12 @@ public class GenerateReleaseNotesTask extends DefaultTask {
|
|||
);
|
||||
|
||||
LOGGER.info("Generating release notes...");
|
||||
final QualifiedVersion qualifiedVersion = QualifiedVersion.of(currentVersion);
|
||||
ReleaseNotesGenerator.update(
|
||||
this.releaseNotesTemplate.get().getAsFile(),
|
||||
this.releaseNotesFile.get().getAsFile(),
|
||||
changelogsByVersion
|
||||
qualifiedVersion,
|
||||
changelogsByVersion.getOrDefault(qualifiedVersion, Set.of())
|
||||
);
|
||||
|
||||
LOGGER.info("Generating release highlights...");
|
||||
|
|
|
@ -11,6 +11,7 @@ package org.elasticsearch.gradle.internal.release;
|
|||
import org.elasticsearch.gradle.Version;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
@ -21,12 +22,7 @@ import java.util.regex.Pattern;
|
|||
* with how {@link Version} is used in the build. It also retains any qualifier (prerelease) information, and uses
|
||||
* that information when comparing instances.
|
||||
*/
|
||||
public record QualifiedVersion(
|
||||
int major,
|
||||
int minor,
|
||||
int revision,
|
||||
org.elasticsearch.gradle.internal.release.QualifiedVersion.Qualifier qualifier
|
||||
) implements Comparable<QualifiedVersion> {
|
||||
public record QualifiedVersion(int major, int minor, int revision, Qualifier qualifier) implements Comparable<QualifiedVersion> {
|
||||
|
||||
private static final Pattern pattern = Pattern.compile(
|
||||
"^v? (\\d+) \\. (\\d+) \\. (\\d+) (?: - (alpha\\d+ | beta\\d+ | rc\\d+ | SNAPSHOT ) )? $",
|
||||
|
@ -56,7 +52,7 @@ public record QualifiedVersion(
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "%d.%d.%d%s".formatted(major, minor, revision, qualifier == null ? "" : "-" + qualifier);
|
||||
return String.format(Locale.ROOT, "%d.%d.%d%s", major, minor, revision, qualifier == null ? "" : "-" + qualifier);
|
||||
}
|
||||
|
||||
public boolean hasQualifier() {
|
||||
|
|
|
@ -14,7 +14,6 @@ import java.io.File;
|
|||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -31,7 +30,7 @@ import static java.util.stream.Collectors.toList;
|
|||
*/
|
||||
public class ReleaseNotesGenerator {
|
||||
/**
|
||||
* These mappings translate change types into the headings as they should appears in the release notes.
|
||||
* These mappings translate change types into the headings as they should appear in the release notes.
|
||||
*/
|
||||
private static final Map<String, String> TYPE_LABELS = new HashMap<>();
|
||||
|
||||
|
@ -47,33 +46,27 @@ public class ReleaseNotesGenerator {
|
|||
TYPE_LABELS.put("upgrade", "Upgrades");
|
||||
}
|
||||
|
||||
static void update(File templateFile, File outputFile, Map<QualifiedVersion, Set<ChangelogEntry>> changelogs) throws IOException {
|
||||
static void update(File templateFile, File outputFile, QualifiedVersion version, Set<ChangelogEntry> changelogs) throws IOException {
|
||||
final String templateString = Files.readString(templateFile.toPath());
|
||||
|
||||
try (FileWriter output = new FileWriter(outputFile)) {
|
||||
output.write(generateFile(templateString, changelogs));
|
||||
output.write(generateFile(templateString, version, changelogs));
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static String generateFile(String template, Map<QualifiedVersion, Set<ChangelogEntry>> changelogs) throws IOException {
|
||||
final var changelogsByVersionByTypeByArea = buildChangelogBreakdown(changelogs);
|
||||
static String generateFile(String template, QualifiedVersion version, Set<ChangelogEntry> changelogs) throws IOException {
|
||||
final var changelogsByTypeByArea = buildChangelogBreakdown(changelogs);
|
||||
|
||||
final Map<String, Object> bindings = new HashMap<>();
|
||||
bindings.put("changelogsByVersionByTypeByArea", changelogsByVersionByTypeByArea);
|
||||
bindings.put("version", version);
|
||||
bindings.put("changelogsByTypeByArea", changelogsByTypeByArea);
|
||||
bindings.put("TYPE_LABELS", TYPE_LABELS);
|
||||
|
||||
return TemplateUtils.render(template, bindings);
|
||||
}
|
||||
|
||||
private static Map<QualifiedVersion, Map<String, Map<String, List<ChangelogEntry>>>> buildChangelogBreakdown(
|
||||
Map<QualifiedVersion, Set<ChangelogEntry>> changelogsByVersion
|
||||
) {
|
||||
Map<QualifiedVersion, Map<String, Map<String, List<ChangelogEntry>>>> changelogsByVersionByTypeByArea = new TreeMap<>(
|
||||
Comparator.reverseOrder()
|
||||
);
|
||||
|
||||
changelogsByVersion.forEach((version, changelogs) -> {
|
||||
private static Map<String, Map<String, List<ChangelogEntry>>> buildChangelogBreakdown(Set<ChangelogEntry> changelogs) {
|
||||
Map<String, Map<String, List<ChangelogEntry>>> changelogsByTypeByArea = changelogs.stream()
|
||||
.collect(
|
||||
groupingBy(
|
||||
|
@ -83,27 +76,18 @@ public class ReleaseNotesGenerator {
|
|||
// Group changelogs for each type by their team area
|
||||
groupingBy(
|
||||
// `security` and `known-issue` areas don't need to supply an area
|
||||
entry -> entry.getType().equals("known-issue") || entry.getType().equals("security")
|
||||
? "_all_"
|
||||
: entry.getArea(),
|
||||
entry -> entry.getType().equals("known-issue") || entry.getType().equals("security") ? "_all_" : entry.getArea(),
|
||||
TreeMap::new,
|
||||
toList()
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
changelogsByVersionByTypeByArea.put(version, changelogsByTypeByArea);
|
||||
});
|
||||
|
||||
// Sort per-area changelogs by their summary text. Assumes that the underlying list is sortable
|
||||
changelogsByVersionByTypeByArea.forEach(
|
||||
(_version, byVersion) -> byVersion.forEach(
|
||||
(_type, byTeam) -> byTeam.forEach(
|
||||
(_team, changelogsForTeam) -> changelogsForTeam.sort(comparing(ChangelogEntry::getSummary))
|
||||
)
|
||||
)
|
||||
changelogsByTypeByArea.forEach(
|
||||
(_type, byTeam) -> byTeam.forEach((_team, changelogsForTeam) -> changelogsForTeam.sort(comparing(ChangelogEntry::getSummary)))
|
||||
);
|
||||
|
||||
return changelogsByVersionByTypeByArea;
|
||||
return changelogsByTypeByArea;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,12 @@ public class ReleaseNotesIndexGenerator {
|
|||
versionsSet.stream().map(v -> v.isSnapshot() ? v.withoutQualifier() : v).forEach(versions::add);
|
||||
|
||||
final List<String> includeVersions = versions.stream()
|
||||
.map(v -> v.hasQualifier() ? v.toString() : v.major() + "." + v.minor())
|
||||
.map(
|
||||
// We didn't split up the notes for 8.0
|
||||
version -> version.isBefore(QualifiedVersion.of("8.1.0")) && version.hasQualifier() == false
|
||||
? version.major() + "." + version.minor()
|
||||
: version.toString()
|
||||
)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
|
|
|
@ -77,7 +77,14 @@ public class ReleaseToolsPlugin implements Plugin<Project> {
|
|||
|
||||
task.setReleaseNotesTemplate(projectDirectory.file(RESOURCES + "templates/release-notes.asciidoc"));
|
||||
task.setReleaseNotesFile(
|
||||
projectDirectory.file(String.format("docs/reference/release-notes/%d.%d.asciidoc", version.getMajor(), version.getMinor()))
|
||||
projectDirectory.file(
|
||||
String.format(
|
||||
"docs/reference/release-notes/%d.%d.%d.asciidoc",
|
||||
version.getMajor(),
|
||||
version.getMinor(),
|
||||
version.getRevision()
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
task.setReleaseHighlightsTemplate(projectDirectory.file(RESOURCES + "templates/release-highlights.asciidoc"));
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<% for (version in changelogsByVersionByTypeByArea.keySet()) {
|
||||
<%
|
||||
def unqualifiedVersion = version.withoutQualifier()
|
||||
%>[[release-notes-$unqualifiedVersion]]
|
||||
== {es} version ${unqualifiedVersion}
|
||||
|
@ -6,32 +6,32 @@ def unqualifiedVersion = version.withoutQualifier()
|
|||
coming[$unqualifiedVersion]
|
||||
<% } %>
|
||||
Also see <<breaking-changes-${ version.major }.${ version.minor },Breaking changes in ${ version.major }.${ version.minor }>>.
|
||||
<% if (changelogsByVersionByTypeByArea[version]["security"] != null) { %>
|
||||
<% if (changelogsByTypeByArea["security"] != null) { %>
|
||||
[discrete]
|
||||
[[security-updates-${unqualifiedVersion}]]
|
||||
=== Security updates
|
||||
|
||||
<% for (change in changelogsByVersionByTypeByArea[version].remove("security").remove("_all_")) {
|
||||
<% for (change in changelogsByTypeByArea.remove("security").remove("_all_")) {
|
||||
print "* ${change.summary}\n"
|
||||
}
|
||||
}
|
||||
if (changelogsByVersionByTypeByArea[version]["known-issue"] != null) { %>
|
||||
if (changelogsByTypeByArea["known-issue"] != null) { %>
|
||||
[discrete]
|
||||
[[known-issues-${unqualifiedVersion}]]
|
||||
=== Known issues
|
||||
|
||||
<% for (change in changelogsByVersionByTypeByArea[version].remove("known-issue").remove("_all_")) {
|
||||
<% for (change in changelogsByTypeByArea.remove("known-issue").remove("_all_")) {
|
||||
print "* ${change.summary}\n"
|
||||
}
|
||||
}
|
||||
for (changeType in changelogsByVersionByTypeByArea[version].keySet()) { %>
|
||||
for (changeType in changelogsByTypeByArea.keySet()) { %>
|
||||
[[${ changeType }-${ unqualifiedVersion }]]
|
||||
[float]
|
||||
=== ${ TYPE_LABELS.getOrDefault(changeType, 'No mapping for TYPE_LABELS[' + changeType + ']') }
|
||||
<% for (team in changelogsByVersionByTypeByArea[version][changeType].keySet()) {
|
||||
<% for (team in changelogsByTypeByArea[changeType].keySet()) {
|
||||
print "\n${team}::\n";
|
||||
|
||||
for (change in changelogsByVersionByTypeByArea[version][changeType][team]) {
|
||||
for (change in changelogsByTypeByArea[changeType][team]) {
|
||||
print "* ${change.summary} {es-pull}${change.pr}[#${change.pr}]"
|
||||
if (change.issues != null && change.issues.empty == false) {
|
||||
print change.issues.size() == 1 ? " (issue: " : " (issues: "
|
||||
|
@ -43,5 +43,3 @@ for (changeType in changelogsByVersionByTypeByArea[version].keySet()) { %>
|
|||
}
|
||||
}
|
||||
print "\n\n"
|
||||
}
|
||||
%>
|
||||
|
|
|
@ -14,10 +14,8 @@ import java.nio.charset.StandardCharsets;
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -37,52 +35,36 @@ public class ReleaseNotesGeneratorTest {
|
|||
"/org/elasticsearch/gradle/internal/release/ReleaseNotesGeneratorTest.generateFile.asciidoc"
|
||||
);
|
||||
|
||||
final Map<QualifiedVersion, Set<ChangelogEntry>> entries = getEntries();
|
||||
final Set<ChangelogEntry> entries = getEntries();
|
||||
|
||||
// when:
|
||||
final String actualOutput = ReleaseNotesGenerator.generateFile(template, entries);
|
||||
final String actualOutput = ReleaseNotesGenerator.generateFile(template, QualifiedVersion.of("8.2.0-SNAPSHOT"), entries);
|
||||
|
||||
// then:
|
||||
assertThat(actualOutput, equalTo(expectedOutput));
|
||||
}
|
||||
|
||||
private Map<QualifiedVersion, Set<ChangelogEntry>> getEntries() {
|
||||
final Set<ChangelogEntry> entries_8_2_0 = new HashSet<>();
|
||||
entries_8_2_0.addAll(buildEntries(1, 2));
|
||||
entries_8_2_0.addAll(buildEntries(2, 2));
|
||||
entries_8_2_0.addAll(buildEntries(3, 2));
|
||||
|
||||
final Set<ChangelogEntry> entries_8_1_0 = new HashSet<>();
|
||||
entries_8_1_0.addAll(buildEntries(4, 2));
|
||||
entries_8_1_0.addAll(buildEntries(5, 2));
|
||||
entries_8_1_0.addAll(buildEntries(6, 2));
|
||||
|
||||
final Set<ChangelogEntry> entries_8_0_0 = new HashSet<>();
|
||||
entries_8_0_0.addAll(buildEntries(7, 2));
|
||||
entries_8_0_0.addAll(buildEntries(8, 2));
|
||||
entries_8_0_0.addAll(buildEntries(9, 2));
|
||||
private Set<ChangelogEntry> getEntries() {
|
||||
final Set<ChangelogEntry> entries = new HashSet<>();
|
||||
entries.addAll(buildEntries(1, 2));
|
||||
entries.addAll(buildEntries(2, 2));
|
||||
entries.addAll(buildEntries(3, 2));
|
||||
|
||||
// Security issues are presented first in the notes
|
||||
final ChangelogEntry securityEntry = new ChangelogEntry();
|
||||
securityEntry.setArea("Security");
|
||||
securityEntry.setType("security");
|
||||
securityEntry.setSummary("Test security issue");
|
||||
entries_8_2_0.add(securityEntry);
|
||||
entries.add(securityEntry);
|
||||
|
||||
// known issues are presented after security issues
|
||||
final ChangelogEntry knownIssue = new ChangelogEntry();
|
||||
knownIssue.setArea("Search");
|
||||
knownIssue.setType("known-issue");
|
||||
knownIssue.setSummary("Test known issue");
|
||||
entries_8_1_0.add(knownIssue);
|
||||
entries.add(knownIssue);
|
||||
|
||||
final Map<QualifiedVersion, Set<ChangelogEntry>> result = new HashMap<>();
|
||||
|
||||
result.put(QualifiedVersion.of("8.2.0-SNAPSHOT"), entries_8_2_0);
|
||||
result.put(QualifiedVersion.of("8.1.0"), entries_8_1_0);
|
||||
result.put(QualifiedVersion.of("8.0.0"), entries_8_0_0);
|
||||
|
||||
return result;
|
||||
return entries;
|
||||
}
|
||||
|
||||
private List<ChangelogEntry> buildEntries(int seed, int count) {
|
||||
|
|
|
@ -11,6 +11,12 @@ Also see <<breaking-changes-8.2,Breaking changes in 8.2>>.
|
|||
|
||||
* Test security issue
|
||||
|
||||
[discrete]
|
||||
[[known-issues-8.2.0]]
|
||||
=== Known issues
|
||||
|
||||
* Test known issue
|
||||
|
||||
[[deprecation-8.2.0]]
|
||||
[float]
|
||||
=== Deprecations
|
||||
|
@ -36,70 +42,3 @@ Mappings::
|
|||
* Test changelog entry 3_1 {es-pull}3002[#3002] (issues: {es-issue}3003[#3003], {es-issue}3004[#3004])
|
||||
|
||||
|
||||
[[release-notes-8.1.0]]
|
||||
== {es} version 8.1.0
|
||||
|
||||
Also see <<breaking-changes-8.1,Breaking changes in 8.1>>.
|
||||
|
||||
[discrete]
|
||||
[[known-issues-8.1.0]]
|
||||
=== Known issues
|
||||
|
||||
* Test known issue
|
||||
|
||||
[[new-aggregation-8.1.0]]
|
||||
[float]
|
||||
=== New aggregation
|
||||
|
||||
Search::
|
||||
* Test changelog entry 4_0 {es-pull}4000[#4000] (issue: {es-issue}4001[#4001])
|
||||
* Test changelog entry 4_1 {es-pull}4002[#4002] (issues: {es-issue}4003[#4003], {es-issue}4004[#4004])
|
||||
|
||||
[[regression-8.1.0]]
|
||||
[float]
|
||||
=== Regressions
|
||||
|
||||
Security::
|
||||
* Test changelog entry 5_0 {es-pull}5000[#5000] (issue: {es-issue}5001[#5001])
|
||||
* Test changelog entry 5_1 {es-pull}5002[#5002] (issues: {es-issue}5003[#5003], {es-issue}5004[#5004])
|
||||
|
||||
[[upgrade-8.1.0]]
|
||||
[float]
|
||||
=== Upgrades
|
||||
|
||||
Aggregation::
|
||||
* Test changelog entry 6_0 {es-pull}6000[#6000] (issue: {es-issue}6001[#6001])
|
||||
* Test changelog entry 6_1 {es-pull}6002[#6002] (issues: {es-issue}6003[#6003], {es-issue}6004[#6004])
|
||||
|
||||
|
||||
[[release-notes-8.0.0]]
|
||||
== {es} version 8.0.0
|
||||
|
||||
Also see <<breaking-changes-8.0,Breaking changes in 8.0>>.
|
||||
|
||||
[[bug-8.0.0]]
|
||||
[float]
|
||||
=== Bug fixes
|
||||
|
||||
Cluster::
|
||||
* Test changelog entry 7_0 {es-pull}7000[#7000] (issue: {es-issue}7001[#7001])
|
||||
* Test changelog entry 7_1 {es-pull}7002[#7002] (issues: {es-issue}7003[#7003], {es-issue}7004[#7004])
|
||||
|
||||
[[deprecation-8.0.0]]
|
||||
[float]
|
||||
=== Deprecations
|
||||
|
||||
Indices::
|
||||
* Test changelog entry 8_0 {es-pull}8000[#8000] (issue: {es-issue}8001[#8001])
|
||||
* Test changelog entry 8_1 {es-pull}8002[#8002] (issues: {es-issue}8003[#8003], {es-issue}8004[#8004])
|
||||
|
||||
[[enhancement-8.0.0]]
|
||||
[float]
|
||||
=== Enhancements
|
||||
|
||||
Mappings::
|
||||
* Test changelog entry 9_0 {es-pull}9000[#9000] (issue: {es-issue}9001[#9001])
|
||||
* Test changelog entry 9_1 {es-pull}9002[#9002] (issues: {es-issue}9003[#9003], {es-issue}9004[#9004])
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -18,8 +18,9 @@ This section summarizes the changes in each release.
|
|||
|
||||
--
|
||||
|
||||
include::release-notes/8.2.asciidoc[]
|
||||
include::release-notes/8.1.asciidoc[]
|
||||
include::release-notes/8.2.0.asciidoc[]
|
||||
include::release-notes/8.1.1.asciidoc[]
|
||||
include::release-notes/8.1.0.asciidoc[]
|
||||
include::release-notes/8.0.asciidoc[]
|
||||
include::release-notes/8.0.0-rc3.asciidoc[]
|
||||
include::release-notes/8.0.0-beta2.asciidoc[]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue