diff --git a/README.adoc b/README.adoc index 49f195e..206a501 100644 --- a/README.adoc +++ b/README.adoc @@ -73,6 +73,17 @@ changelog: title: "Contributors" ---- +You can add external links such as release notes for quick access using: + +[source,yaml] +---- +changelog: + external_links: + - name: "Release Notes" + location: "https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.3-Release-Notes" +---- + + ==== Showing Issues in Multiple Sections diff --git a/src/main/java/io/spring/githubchangeloggenerator/ApplicationProperties.java b/src/main/java/io/spring/githubchangeloggenerator/ApplicationProperties.java index 8eb25ab..2886022 100644 --- a/src/main/java/io/spring/githubchangeloggenerator/ApplicationProperties.java +++ b/src/main/java/io/spring/githubchangeloggenerator/ApplicationProperties.java @@ -33,6 +33,7 @@ * * @author Madhura Bhave * @author Phillip Webb + * @author Mahendra Bishnoi */ @ConfigurationProperties(prefix = "changelog") @ConstructorBinding @@ -63,14 +64,20 @@ public class ApplicationProperties { */ private final Contributors contributors; + /** + * Settings specific to external links. + */ + private final List externalLinks; + public ApplicationProperties(Repository repository, @DefaultValue("title") MilestoneReference milestoneReference, - List
sections, Issues issues, Contributors contributors) { + List
sections, Issues issues, Contributors contributors, List externalLinks) { Assert.notNull(repository, "Repository must not be null"); this.repository = repository; this.milestoneReference = milestoneReference; this.sections = (sections != null) ? sections : Collections.emptyList(); this.issues = (issues != null) ? issues : new Issues(null, null, null); this.contributors = (contributors != null) ? contributors : new Contributors(null, null); + this.externalLinks = (externalLinks != null) ? externalLinks : Collections.emptyList(); } public Repository getRepository() { @@ -93,6 +100,10 @@ public Contributors getContributors() { return this.contributors; } + public List getExternalLinks() { + return this.externalLinks; + } + /** * Properties for a single changelog section. */ @@ -285,6 +296,36 @@ public Set getNames() { } + /** + * Properties for a single external link. + */ + public static class ExternalLink { + + /** + * Name to be shown for an external link. + */ + private final String name; + + /** + * URL for an external link. + */ + private final String location; + + public ExternalLink(String name, String location) { + this.name = name; + this.location = location; + } + + public String getName() { + return this.name; + } + + public String getLocation() { + return this.location; + } + + } + public enum IssueSort { /** diff --git a/src/main/java/io/spring/githubchangeloggenerator/ChangelogGenerator.java b/src/main/java/io/spring/githubchangeloggenerator/ChangelogGenerator.java index 5c071c2..63d26fa 100644 --- a/src/main/java/io/spring/githubchangeloggenerator/ChangelogGenerator.java +++ b/src/main/java/io/spring/githubchangeloggenerator/ChangelogGenerator.java @@ -29,6 +29,7 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; +import io.spring.githubchangeloggenerator.ApplicationProperties.ExternalLink; import io.spring.githubchangeloggenerator.ApplicationProperties.IssueSort; import io.spring.githubchangeloggenerator.ApplicationProperties.PortedIssue; import io.spring.githubchangeloggenerator.github.payload.Issue; @@ -46,6 +47,7 @@ * * @author Madhura Bhave * @author Phillip Webb + * @author Mahendra Bishnoi */ @Component public class ChangelogGenerator { @@ -73,6 +75,8 @@ public class ChangelogGenerator { private final ChangelogSections sections; + private final List externalLinks; + public ChangelogGenerator(GitHubService service, ApplicationProperties properties) { this.service = service; this.repository = properties.getRepository(); @@ -83,6 +87,7 @@ public ChangelogGenerator(GitHubService service, ApplicationProperties propertie this.contributorsTitle = properties.getContributors().getTitle(); this.sections = new ChangelogSections(properties); this.portedIssues = properties.getIssues().getPorts(); + this.externalLinks = properties.getExternalLinks(); } /** @@ -131,6 +136,9 @@ private String generateContent(List issues) { if (!contributors.isEmpty()) { addContributorsContent(content, contributors); } + if (!this.externalLinks.isEmpty()) { + addExternalLinksContent(content, this.externalLinks); + } return content.toString(); } @@ -201,6 +209,16 @@ private String formatContributors(User c) { return String.format("- [@%s](%s)%n", c.getName(), c.getUrl()); } + private void addExternalLinksContent(StringBuilder content, List externalLinks) { + content.append(String.format("## ")); + content.append(String.format("External Links%n%n")); + externalLinks.stream().map(this::formatExternalLinks).forEach(content::append); + } + + private String formatExternalLinks(ExternalLink externalLink) { + return String.format("- [%s](%s)%n", externalLink.getName(), externalLink.getLocation()); + } + private void writeContentToFile(String content, String path) throws IOException { FileCopyUtils.copy(content, new FileWriter(new File(path))); } diff --git a/src/test/java/io/spring/githubchangeloggenerator/ApplicationPropertiesTests.java b/src/test/java/io/spring/githubchangeloggenerator/ApplicationPropertiesTests.java index fc2351f..d854daf 100644 --- a/src/test/java/io/spring/githubchangeloggenerator/ApplicationPropertiesTests.java +++ b/src/test/java/io/spring/githubchangeloggenerator/ApplicationPropertiesTests.java @@ -62,6 +62,10 @@ void loadYaml() throws Exception { assertThat(properties.getIssues().getSort()).isEqualTo(IssueSort.TITLE); assertThat(properties.getContributors().getTitle()).isEqualTo("Nice one!"); assertThat(properties.getContributors().getExclude().getNames()).containsExactly("philwebb"); + assertThat(properties.getExternalLinks().get(0).getName()).isEqualTo("Release Notes 1"); + assertThat(properties.getExternalLinks().get(0).getLocation()).isEqualTo("url1"); + assertThat(properties.getExternalLinks().get(1).getName()).isEqualTo("Release Notes 2"); + assertThat(properties.getExternalLinks().get(1).getLocation()).isEqualTo("url2"); } } diff --git a/src/test/java/io/spring/githubchangeloggenerator/ChangelogGeneratorTests.java b/src/test/java/io/spring/githubchangeloggenerator/ChangelogGeneratorTests.java index ba80369..c097499 100644 --- a/src/test/java/io/spring/githubchangeloggenerator/ChangelogGeneratorTests.java +++ b/src/test/java/io/spring/githubchangeloggenerator/ChangelogGeneratorTests.java @@ -31,6 +31,7 @@ import io.spring.githubchangeloggenerator.ApplicationProperties.Contributors; import io.spring.githubchangeloggenerator.ApplicationProperties.ContributorsExclude; +import io.spring.githubchangeloggenerator.ApplicationProperties.ExternalLink; import io.spring.githubchangeloggenerator.ApplicationProperties.IssueSort; import io.spring.githubchangeloggenerator.ApplicationProperties.Issues; import io.spring.githubchangeloggenerator.ApplicationProperties.IssuesExclude; @@ -58,6 +59,7 @@ * * @author Madhura Bhave * @author Phillip Webb + * @author Mahendra Bishnoi */ class ChangelogGeneratorTests { @@ -139,7 +141,7 @@ void generateWhenHasExcludedContributors() throws Exception { issues.add(newPullRequest("Enhancement 2", "2", Type.ENHANCEMENT, "enhancement-2-url", contributor2)); given(this.service.getIssuesForMilestone(23, REPO)).willReturn(issues); ApplicationProperties properties = new ApplicationProperties(REPO, MilestoneReference.ID, null, null, - new Contributors(null, new ContributorsExclude(Collections.singleton("contributor1")))); + new Contributors(null, new ContributorsExclude(Collections.singleton("contributor1"))), null); this.generator = new ChangelogGenerator(this.service, properties); assertChangelog("23").hasContent(from("output-with-excluded-contributors")); } @@ -153,7 +155,7 @@ void generateWhenHasAllContributorsExcluded() throws Exception { issues.add(newPullRequest("Enhancement 2", "2", Type.ENHANCEMENT, "enhancement-2-url", contributor2)); given(this.service.getIssuesForMilestone(23, REPO)).willReturn(issues); ApplicationProperties properties = new ApplicationProperties(REPO, MilestoneReference.ID, null, null, - new Contributors(null, new ContributorsExclude(Collections.singleton("*")))); + new Contributors(null, new ContributorsExclude(Collections.singleton("*"))), null); this.generator = new ChangelogGenerator(this.service, properties); assertChangelog("23").hasContent(from("output-with-all-contributors-excluded")); } @@ -223,7 +225,7 @@ void generateWhenSectionSortedByTitle() throws Exception { Set labels = Collections.singleton("type: enhancement"); sections.add(new Section("Enhancements", null, IssueSort.TITLE, labels)); ApplicationProperties properties = new ApplicationProperties(REPO, MilestoneReference.ID, sections, - new Issues(null, null, null), null); + new Issues(null, null, null), null, null); this.generator = new ChangelogGenerator(this.service, properties); List issues = new ArrayList<>(); issues.add(newIssue("Enhancement c", "1", "enhancement-1-url", Type.ENHANCEMENT)); @@ -239,7 +241,7 @@ void generateWhenAllIssuesSortedByTitle() throws Exception { Set labels = Collections.singleton("type: enhancement"); sections.add(new Section("Enhancements", null, null, labels)); ApplicationProperties properties = new ApplicationProperties(REPO, MilestoneReference.ID, sections, - new Issues(IssueSort.TITLE, null, null), null); + new Issues(IssueSort.TITLE, null, null), null, null); this.generator = new ChangelogGenerator(this.service, properties); List issues = new ArrayList<>(); issues.add(newIssue("Enhancement c", "1", "enhancement-1-url", Type.ENHANCEMENT)); @@ -256,18 +258,40 @@ void generateWhenHasCustomContributorsTitle() throws Exception { issues.add(newPullRequest("Bug 1", "1", Type.BUG, "bug-1-url", contributor1)); given(this.service.getIssuesForMilestone(23, REPO)).willReturn(issues); ApplicationProperties properties = new ApplicationProperties(REPO, MilestoneReference.ID, null, null, - new Contributors(":heart: Teamwork", null)); + new Contributors(":heart: Teamwork", null), null); this.generator = new ChangelogGenerator(this.service, properties); assertChangelog("23").hasContent(from("output-with-custom-contributors-title")); } + @Test + void generateWhenOneExternalLink() throws Exception { + List externalLinks = new ArrayList<>(); + externalLinks.add(new ExternalLink("Release Notes Link 1", "url1")); + ApplicationProperties properties = new ApplicationProperties(REPO, MilestoneReference.ID, null, null, null, + externalLinks); + this.generator = new ChangelogGenerator(this.service, properties); + assertChangelog("23").hasContent(from("output-with-one-external-link")); + } + + @Test + void generateWhenMultipleExternalLink() throws Exception { + List externalLinks = new ArrayList<>(); + externalLinks.add(new ExternalLink("Release Notes Link 1", "url1")); + externalLinks.add(new ExternalLink("Release Notes Link 2", "url2")); + externalLinks.add(new ExternalLink("Release Notes Link 3", "url3")); + ApplicationProperties properties = new ApplicationProperties(REPO, MilestoneReference.ID, null, null, null, + externalLinks); + this.generator = new ChangelogGenerator(this.service, properties); + assertChangelog("23").hasContent(from("output-with-multiple-external-link")); + } + private void setupGenerator(MilestoneReference id) { Set labels = new HashSet<>(Arrays.asList("duplicate", "wontfix")); PortedIssue forwardPort = new PortedIssue("status: forward-port", "Forward port of issue #(\\d+)"); PortedIssue cherryPick = new PortedIssue("status: back-port", "Back port of issue #(\\d+)"); Set portedIssues = new HashSet<>(Arrays.asList(forwardPort, cherryPick)); ApplicationProperties properties = new ApplicationProperties(REPO, id, null, - new Issues(null, new IssuesExclude(labels), portedIssues), null); + new Issues(null, new IssuesExclude(labels), portedIssues), null, null); this.generator = new ChangelogGenerator(this.service, properties); } diff --git a/src/test/java/io/spring/githubchangeloggenerator/ChangelogSectionsTests.java b/src/test/java/io/spring/githubchangeloggenerator/ChangelogSectionsTests.java index d5cfe41..8465042 100644 --- a/src/test/java/io/spring/githubchangeloggenerator/ChangelogSectionsTests.java +++ b/src/test/java/io/spring/githubchangeloggenerator/ChangelogSectionsTests.java @@ -46,7 +46,8 @@ void collateWhenNoCustomSectionsUsesDefaultSections() { Issue bug = createIssue("2", "bug"); Issue documentation = createIssue("3", "documentation"); Issue dependencyUpgrade = createIssue("4", "dependency-upgrade"); - ApplicationProperties properties = new ApplicationProperties(REPO, MilestoneReference.TITLE, null, null, null); + ApplicationProperties properties = new ApplicationProperties(REPO, MilestoneReference.TITLE, null, null, null, + null); ChangelogSections sections = new ChangelogSections(properties); Map> collated = sections .collate(Arrays.asList(enhancement, bug, documentation, dependencyUpgrade)); @@ -67,7 +68,7 @@ void collateWhenHasCustomSectionsUsesDefinedSections() { Collections.singleton("bug")); List customSections = Arrays.asList(breaksPassivitySection, bugsSection); ApplicationProperties properties = new ApplicationProperties(REPO, MilestoneReference.TITLE, customSections, - null, null); + null, null, null); ChangelogSections sections = new ChangelogSections(properties); Issue bug = createIssue("1", "bug"); Issue nonPassive = createIssue("1", "breaks-passivity"); @@ -79,7 +80,8 @@ void collateWhenHasCustomSectionsUsesDefinedSections() { @Test void collateWhenNoIssuesInSectionExcludesSection() { Issue bug = createIssue("1", "bug"); - ApplicationProperties properties = new ApplicationProperties(REPO, MilestoneReference.TITLE, null, null, null); + ApplicationProperties properties = new ApplicationProperties(REPO, MilestoneReference.TITLE, null, null, null, + null); ChangelogSections sections = new ChangelogSections(properties); Map> collated = sections.collate(Collections.singletonList(bug)); Map> bySection = getBySection(collated); @@ -90,7 +92,8 @@ void collateWhenNoIssuesInSectionExcludesSection() { void collateWhenIssueDoesNotMatchAnySectionLabelThenExcludesIssue() { Issue bug = createIssue("1", "bug"); Issue nonPassive = createIssue("2", "non-passive"); - ApplicationProperties properties = new ApplicationProperties(REPO, MilestoneReference.TITLE, null, null, null); + ApplicationProperties properties = new ApplicationProperties(REPO, MilestoneReference.TITLE, null, null, null, + null); ChangelogSections sections = new ChangelogSections(properties); Map> collated = sections.collate(Arrays.asList(bug, nonPassive)); Map> bySection = getBySection(collated); @@ -109,7 +112,7 @@ void collateWithDefaultsDoesNotAddIssueToMultipleSections() { Collections.singleton("highlight")); List customSections = Arrays.asList(bugs, highlights); ApplicationProperties properties = new ApplicationProperties(REPO, MilestoneReference.TITLE, customSections, - null, null); + null, null, null); ChangelogSections sections = new ChangelogSections(properties); Map> collated = sections.collate(Arrays.asList(bug, highlight, bugAndHighlight)); Map> bySection = getBySection(collated); @@ -129,7 +132,7 @@ void collateWithGroupsAddsIssuePerGroup() { Collections.singleton("highlight")); List customSections = Arrays.asList(bugs, highlights); ApplicationProperties properties = new ApplicationProperties(REPO, MilestoneReference.TITLE, customSections, - null, null); + null, null, null); ChangelogSections sections = new ChangelogSections(properties); Map> collated = sections.collate(Arrays.asList(bug, highlight, bugAndHighlight)); Map> bySection = getBySection(collated); diff --git a/src/test/resources/io/spring/githubchangeloggenerator/output-with-multiple-external-link b/src/test/resources/io/spring/githubchangeloggenerator/output-with-multiple-external-link new file mode 100644 index 0000000..95b5df6 --- /dev/null +++ b/src/test/resources/io/spring/githubchangeloggenerator/output-with-multiple-external-link @@ -0,0 +1,5 @@ +## External Links + +- [Release Notes Link 1](url1) +- [Release Notes Link 2](url2) +- [Release Notes Link 3](url3) diff --git a/src/test/resources/io/spring/githubchangeloggenerator/output-with-one-external-link b/src/test/resources/io/spring/githubchangeloggenerator/output-with-one-external-link new file mode 100644 index 0000000..3c9e0b9 --- /dev/null +++ b/src/test/resources/io/spring/githubchangeloggenerator/output-with-one-external-link @@ -0,0 +1,3 @@ +## External Links + +- [Release Notes Link 1](url1) diff --git a/src/test/resources/io/spring/githubchangeloggenerator/test-application.yml b/src/test/resources/io/spring/githubchangeloggenerator/test-application.yml index 63bf275..b099ad7 100644 --- a/src/test/resources/io/spring/githubchangeloggenerator/test-application.yml +++ b/src/test/resources/io/spring/githubchangeloggenerator/test-application.yml @@ -15,3 +15,8 @@ changelog: title: "Nice one!" exclude: names: ["philwebb"] + external_links: + - name: "Release Notes 1" + location: "url1" + - name: "Release Notes 2" + location: "url2" \ No newline at end of file