From dacba0314e8933c91b263896e11a82c82b725f73 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 29 Oct 2018 10:46:28 +0100 Subject: [PATCH] ENhanced blog post generation; fixes gh-94 --- .../release/internal/ReleaserProperties.java | 2 +- .../internal/git/GithubMilestones.java | 18 ++++- .../template/BlogTemplateGenerator.java | 53 ++----------- .../internal/template/NotesGenerator.java | 77 +++++++++++++++++++ .../ReleaseNotesTemplateGenerator.java | 62 +-------------- .../internal/template/TemplateGenerator.java | 2 +- .../main/resources/templates/cloud/blog.hbs | 6 +- .../internal/git/GithubMilestonesTests.java | 10 +++ .../template/TemplateGeneratorTests.java | 23 +++--- .../release/internal/ReleaserApplication.java | 15 ++-- 10 files changed, 140 insertions(+), 128 deletions(-) create mode 100644 spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/template/NotesGenerator.java diff --git a/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/ReleaserProperties.java b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/ReleaserProperties.java index a8ceae4c..5993f8d1 100644 --- a/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/ReleaserProperties.java +++ b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/ReleaserProperties.java @@ -210,7 +210,7 @@ public class ReleaserProperties implements Serializable { * In order not to iterate endlessly over milestones we introduce a threshold of milestones * that we will go through to find the matching milestone */ - private Integer numberOfCheckedMilestones = 10; + private Integer numberOfCheckedMilestones = 50; /** * If {@code false}, will not update the documentation repository. diff --git a/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/git/GithubMilestones.java b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/git/GithubMilestones.java index 1db5c8cd..617a4b26 100644 --- a/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/git/GithubMilestones.java +++ b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/git/GithubMilestones.java @@ -12,12 +12,14 @@ import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.release.internal.ReleaserProperties; import org.springframework.cloud.release.internal.pom.ProjectVersion; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * @author Marcin Grzejszczak @@ -28,6 +30,7 @@ class GithubMilestones { private final Github github; private final ReleaserProperties properties; + static final Map CACHE = new ConcurrentHashMap<>(); GithubMilestones(ReleaserProperties properties) { this.github = new RtGithub(new RtGithub( @@ -88,15 +91,20 @@ class GithubMilestones { } String milestoneUrl(ProjectVersion version) { + String cachedUrl = CACHE.get(version); + if (StringUtils.hasText(cachedUrl)) { + return cachedUrl; + } Assert.hasText(this.properties.getGit().getOauthToken(), "You have to pass Github OAuth token for milestone closing to be operational"); String tagVersion = version.version; Milestone.Smart foundMilestone = matchingMilestone(tagVersion, closedMilestones(version)); + String foundUrl = ""; if (foundMilestone != null) { try { URL url = foundMilestoneUrl(foundMilestone); log.info("Found a matching milestone with issues URL [{}]", url); - return url.toString() + foundUrl = url.toString() .replace("https://api.github.com/repos", "https://github.com") .replace("milestones", "milestone")+ "?closed=1"; } @@ -105,10 +113,10 @@ class GithubMilestones { } catch (Exception e) { log.error("Exception occurred while trying to find milestone", e); - return ""; } } - return ""; + CACHE.put(version, foundUrl); + return foundUrl; } private String numericVersion(String version) { @@ -151,12 +159,16 @@ class GithubMilestones { private Map openMilestones() { Map params = new HashMap<>(); params.put("state", "open"); + params.put("sort", "due_on"); + params.put("direction", "desc"); return params; } private Map closedMilestones() { Map params = new HashMap<>(); params.put("state", "closed"); + params.put("sort", "due_on"); + params.put("direction", "desc"); return params; } } diff --git a/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/template/BlogTemplateGenerator.java b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/template/BlogTemplateGenerator.java index 1a1057b3..c2d19a26 100644 --- a/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/template/BlogTemplateGenerator.java +++ b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/template/BlogTemplateGenerator.java @@ -11,6 +11,8 @@ import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + +import org.springframework.cloud.release.internal.git.ProjectGitHandler; import org.springframework.cloud.release.internal.pom.Projects; import org.springframework.util.StringUtils; @@ -32,13 +34,15 @@ class BlogTemplateGenerator { private final String releaseVersion; private final File blogOutput; private final Projects projects; + private final NotesGenerator notesGenerator; BlogTemplateGenerator(Template template, String releaseVersion, File blogOutput, - Projects projects) { + Projects projects, ProjectGitHandler handler) { this.template = template; this.releaseVersion = releaseVersion; this.blogOutput = blogOutput; this.projects = projects; + this.notesGenerator = new NotesGenerator(handler); } File blog() { @@ -59,7 +63,7 @@ class BlogTemplateGenerator { .put("releaseName", releaseName) .put("releaseLink", releaseLink) .put("releaseVersion", this.releaseVersion) - .put("projects", fromProjects()) + .put("projects", this.notesGenerator.fromProjects(this.projects)) .put("nonRelease", nonRelease) .build(); String blog = this.template.apply(map); @@ -72,16 +76,6 @@ class BlogTemplateGenerator { } } - private Set fromProjects() { - return this.projects.stream().map(projectVersion -> { - String name = projectVersion.projectName; - String version = projectVersion.version; - String convertedName = Arrays.stream(name.split("-")).map( - StringUtils::capitalize).collect(Collectors.joining(" ")); - return new BlogTuple(convertedName, version); - }).collect(Collectors.toSet()); - } - private String parsedReleaseName(String version) { return version.substring(0, version.indexOf(".")); } @@ -121,38 +115,3 @@ class BlogTemplateGenerator { return "[Maven Central](http://repo1.maven.org/maven2/org/springframework/cloud/spring-cloud-dependencies/" + this.releaseVersion + "/)"; } } - -class BlogTuple { - private final String name; - private final String version; - - BlogTuple(String name, String version) { - this.name = name; - this.version = version; - } - - public String getName() { - return name; - } - - public String getVersion() { - return version; - } - - @Override public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - BlogTuple blogTuple = (BlogTuple) o; - if (name != null ? !name.equals(blogTuple.name) : blogTuple.name != null) - return false; - return version != null ? version.equals(blogTuple.version) : blogTuple.version == null; - } - - @Override public int hashCode() { - int result = name != null ? name.hashCode() : 0; - result = 31 * result + (version != null ? version.hashCode() : 0); - return result; - } -} diff --git a/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/template/NotesGenerator.java b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/template/NotesGenerator.java new file mode 100644 index 00000000..ab1268ca --- /dev/null +++ b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/template/NotesGenerator.java @@ -0,0 +1,77 @@ +package org.springframework.cloud.release.internal.template; + +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +import org.springframework.cloud.release.internal.git.ProjectGitHandler; +import org.springframework.cloud.release.internal.pom.Projects; +import org.springframework.util.StringUtils; + +/** + * @author Marcin Grzejszczak + */ +class NotesGenerator { + + private final ProjectGitHandler handler; + + NotesGenerator(ProjectGitHandler handler) { + this.handler = handler; + } + + Set fromProjects(Projects projects) { + return projects.stream().filter(projectVersion -> + !projectVersion.projectName.toLowerCase().contains("boot") + ).map(projectVersion -> { + String name = projectVersion.projectName; + String version = projectVersion.version; + String closedMilestoneUrl = this.handler.milestoneUrl(projectVersion); + String convertedName = Arrays.stream(name.split("-")) + .map(StringUtils::capitalize).collect(Collectors.joining(" ")); + return new Notes(convertedName, version, closedMilestoneUrl); + }).collect(Collectors.toSet()); + } +} + +class Notes { + private final String name; + private final String version; + private final String closedMilestoneUrl; + + Notes(String name, String version, String closedMilestoneUrl) { + this.name = name; + this.version = version; + this.closedMilestoneUrl = closedMilestoneUrl; + } + + public String getName() { + return name; + } + + public String getVersion() { + return version; + } + + public String getClosedMilestoneUrl() { + return closedMilestoneUrl; + } + + @Override public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Notes notes = (Notes) o; + if (name != null ? !name.equals(notes.name) : notes.name != null) + return false; + return version != null ? + version.equals(notes.version) : + notes.version == null; + } + + @Override public int hashCode() { + int result = name != null ? name.hashCode() : 0; + result = 31 * result + (version != null ? version.hashCode() : 0); + return result; + } +} diff --git a/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/template/ReleaseNotesTemplateGenerator.java b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/template/ReleaseNotesTemplateGenerator.java index 2bd12360..0d037551 100644 --- a/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/template/ReleaseNotesTemplateGenerator.java +++ b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/template/ReleaseNotesTemplateGenerator.java @@ -30,7 +30,7 @@ class ReleaseNotesTemplateGenerator { private final String releaseVersion; private final File blogOutput; private final Projects projects; - private final ProjectGitHandler handler; + private final NotesGenerator notesGenerator; ReleaseNotesTemplateGenerator(Template template, String releaseVersion, File blogOutput, Projects projects, ProjectGitHandler handler) { @@ -38,7 +38,7 @@ class ReleaseNotesTemplateGenerator { this.releaseVersion = releaseVersion; this.blogOutput = blogOutput; this.projects = projects; - this.handler = handler; + this.notesGenerator = new NotesGenerator(handler); } File releseNotes() { @@ -46,7 +46,7 @@ class ReleaseNotesTemplateGenerator { Map map = ImmutableMap.builder() .put("date", LocalDate.now().format(DateTimeFormatter.ISO_DATE)) .put("releaseVersion", this.releaseVersion) - .put("projects", fromProjects()) + .put("projects", this.notesGenerator.fromProjects(this.projects)) .build(); String blog = this.template.apply(map); Files.write(this.blogOutput.toPath(), blog.getBytes()); @@ -57,60 +57,4 @@ class ReleaseNotesTemplateGenerator { return null; } } - - private Set fromProjects() { - return this.projects.stream().filter(projectVersion -> - !projectVersion.projectName.toLowerCase().contains("boot") - ).map(projectVersion -> { - String name = projectVersion.projectName; - String version = projectVersion.version; - String closedMilestoneUrl = this.handler.milestoneUrl(projectVersion); - String convertedName = Arrays.stream(name.split("-")) - .map(StringUtils::capitalize).collect(Collectors.joining(" ")); - return new Notes(convertedName, version, closedMilestoneUrl); - }).collect(Collectors.toSet()); - } -} - -class Notes { - private final String name; - private final String version; - private final String closedMilestoneUrl; - - Notes(String name, String version, String closedMilestoneUrl) { - this.name = name; - this.version = version; - this.closedMilestoneUrl = closedMilestoneUrl; - } - - public String getName() { - return name; - } - - public String getVersion() { - return version; - } - - public String getClosedMilestoneUrl() { - return closedMilestoneUrl; - } - - @Override public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - Notes notes = (Notes) o; - if (name != null ? !name.equals(notes.name) : notes.name != null) - return false; - return version != null ? - version.equals(notes.version) : - notes.version == null; - } - - @Override public int hashCode() { - int result = name != null ? name.hashCode() : 0; - result = 31 * result + (version != null ? version.hashCode() : 0); - return result; - } } diff --git a/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/template/TemplateGenerator.java b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/template/TemplateGenerator.java index ac3e4ea3..f0f29a7e 100644 --- a/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/template/TemplateGenerator.java +++ b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/template/TemplateGenerator.java @@ -72,7 +72,7 @@ public class TemplateGenerator implements ReleaserPropertiesAware { File blogOutput = file(this.blogOutput); String releaseVersion = parsedVersion(projects); Template template = template(BLOG_TEMPLATE); - return new BlogTemplateGenerator(template, releaseVersion, blogOutput, projects).blog(); + return new BlogTemplateGenerator(template, releaseVersion, blogOutput, projects, handler).blog(); } public File tweet(Projects projects) { diff --git a/spring-cloud-release-tools-core/src/main/resources/templates/cloud/blog.hbs b/spring-cloud-release-tools-core/src/main/resources/templates/cloud/blog.hbs index 96395997..7a9c8a0b 100644 --- a/spring-cloud-release-tools-core/src/main/resources/templates/cloud/blog.hbs +++ b/spring-cloud-release-tools-core/src/main/resources/templates/cloud/blog.hbs @@ -9,9 +9,9 @@ Some text related to project The following modules were updated as part of {{ releaseVersion }}: -| Module | Version | -|--- |--- | -{{#each projects}}| {{name}} | {{version}} | +| Module | Version | Issues +|--- |--- |--- |--- +{{#each projects}}| {{name}} | {{version}} | {{#if closedMilestoneUrl}}([issues]({{{ closedMilestoneUrl }}})){{/if}} {{/each}} As always, we welcome feedback on [GitHub](https://github.com/spring-cloud/), on [Gitter](https://gitter.im/spring-cloud/spring-cloud), on [Stack Overflow](http://stackoverflow.com/questions/tagged/spring-cloud), or on [Twitter](https://twitter.com/SpringCloud). diff --git a/spring-cloud-release-tools-core/src/test/java/org/springframework/cloud/release/internal/git/GithubMilestonesTests.java b/spring-cloud-release-tools-core/src/test/java/org/springframework/cloud/release/internal/git/GithubMilestonesTests.java index 410cec72..bf3a8912 100644 --- a/spring-cloud-release-tools-core/src/test/java/org/springframework/cloud/release/internal/git/GithubMilestonesTests.java +++ b/spring-cloud-release-tools-core/src/test/java/org/springframework/cloud/release/internal/git/GithubMilestonesTests.java @@ -121,6 +121,16 @@ public class GithubMilestonesTests { then(url).isEqualTo("https://github.com/spring-cloud/spring-cloud-sleuth/milestone/33?closed=1"); } + @Test + public void should_fetch_url_of_a_closed_matching_milestone_from_cache() throws IOException { + GithubMilestones milestones = new GithubMilestones(this.github, withToken()); + GithubMilestones.CACHE.put(gaSleuthProject(), "https://github.com/spring-cloud/spring-cloud-sleuth/milestone/33?closed=1"); + + String url = milestones.milestoneUrl(gaSleuthProject()); + + then(url).isEqualTo("https://github.com/spring-cloud/spring-cloud-sleuth/milestone/33?closed=1"); + } + @Test public void should_return_null_if_no_matching_milestone_was_found() throws IOException { GithubMilestones milestones = new GithubMilestones(this.github, withToken()) { diff --git a/spring-cloud-release-tools-core/src/test/java/org/springframework/cloud/release/internal/template/TemplateGeneratorTests.java b/spring-cloud-release-tools-core/src/test/java/org/springframework/cloud/release/internal/template/TemplateGeneratorTests.java index a38cdc7b..900272ee 100644 --- a/spring-cloud-release-tools-core/src/test/java/org/springframework/cloud/release/internal/template/TemplateGeneratorTests.java +++ b/spring-cloud-release-tools-core/src/test/java/org/springframework/cloud/release/internal/template/TemplateGeneratorTests.java @@ -19,7 +19,12 @@ import static org.assertj.core.api.BDDAssertions.then; public class TemplateGeneratorTests { ReleaserProperties props = new ReleaserProperties(); - ProjectGitHandler handler = new ProjectGitHandler(this.props); + ProjectGitHandler handler = new ProjectGitHandler(this.props) { + @Override + public String milestoneUrl(ProjectVersion releaseVersion) { + return "http://foo.bar.com"; + } + }; @Test public void should_generate_email_from_template_for_tag_with_v_prefix() { @@ -84,7 +89,7 @@ public class TemplateGeneratorTests { .contains("General Availability (RELEASE) of the [Spring Cloud Dalston]") .contains("The release can be found in [Maven Central]") .contains("### Spring Cloud Sleuth") - .contains("| Spring Cloud Sleuth \t| 1.0.0.RELEASE \t|") + .contains("| Spring Cloud Sleuth | 1.0.0.RELEASE | ([issues](http://foo.bar.com))") .contains("Dalston.RELEASE") .contains("mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Dalston.RELEASE'"); } @@ -106,7 +111,7 @@ public class TemplateGeneratorTests { .contains("General Availability (RELEASE) of the [Spring Cloud Dalston]") .contains("The release can be found in [Maven Central]") .contains("### Spring Cloud Sleuth") - .contains("| Spring Cloud Sleuth \t| 1.0.0.RELEASE \t|") + .contains("| Spring Cloud Sleuth\t| 1.0.0.RELEASE\t| ([issues](http://foo.bar.com))") .contains("Dalston.RELEASE") .contains("mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Dalston.RELEASE'"); } @@ -128,7 +133,7 @@ public class TemplateGeneratorTests { .contains("Service Release 1 (SR1) of the [Spring Cloud Dalston]") .contains("The release can be found in [Maven Central]") .contains("### Spring Cloud Sleuth") - .contains("| Spring Cloud Sleuth \t| 1.0.0.RELEASE \t|") + .contains("| Spring Cloud Sleuth | 1.0.0.RELEASE | ([issues](http://foo.bar.com))") .contains("Dalston.SR1") .contains("mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Dalston.SR1'"); } @@ -150,7 +155,7 @@ public class TemplateGeneratorTests { .contains("Service Release 1 (SR1) of the [Spring Cloud Dalston]") .contains("The release can be found in [Maven Central]") .contains("### Spring Cloud Sleuth") - .contains("| Spring Cloud Sleuth \t| 1.0.0.RELEASE \t|") + .contains("| Spring Cloud Sleuth | 1.0.0.RELEASE | ([issues](http://foo.bar.com))") .contains("Dalston.SR1") .contains("mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Dalston.SR1'"); } @@ -172,7 +177,7 @@ public class TemplateGeneratorTests { .contains("Milestone 1 (M1) of the [Spring Cloud Dalston]") .contains("The release can be found in [Spring Milestone]") .contains("### Spring Cloud Sleuth") - .contains("| Spring Cloud Sleuth \t| 1.0.0.M1 \t|") + .contains("| Spring Cloud Sleuth\t| 1.0.0.M1\t| ([issues](http://foo.bar.com))") .contains("spring-milestones") .contains("url 'http://repo.spring.io/milestone'") .contains("Dalston.M1") @@ -196,7 +201,7 @@ public class TemplateGeneratorTests { .contains("Milestone 1 (M1) of the [Spring Cloud Dalston]") .contains("The release can be found in [Spring Milestone]") .contains("### Spring Cloud Sleuth") - .contains("| Spring Cloud Sleuth \t| 1.0.0.M1 \t|") + .contains("| Spring Cloud Sleuth\t| 1.0.0.M1\t| ([issues](http://foo.bar.com))") .contains("spring-milestones") .contains("url 'http://repo.spring.io/milestone'") .contains("Dalston.M1") @@ -220,7 +225,7 @@ public class TemplateGeneratorTests { .contains("Release Candidate 1 (RC1) of the [Spring Cloud Dalston]") .contains("The release can be found in [Spring Milestone]") .contains("### Spring Cloud Sleuth") - .contains("| Spring Cloud Sleuth \t| 1.0.0.RC1 \t|") + .contains("| Spring Cloud Sleuth\t| 1.0.0.RC1\t| ([issues](http://foo.bar.com))") .contains("spring-milestones") .contains("url 'http://repo.spring.io/milestone'") .contains("Dalston.RC1") @@ -244,7 +249,7 @@ public class TemplateGeneratorTests { .contains("Release Candidate 1 (RC1) of the [Spring Cloud Dalston]") .contains("The release can be found in [Spring Milestone]") .contains("### Spring Cloud Sleuth") - .contains("| Spring Cloud Sleuth \t| 1.0.0.RC1 \t|") + .contains("| Spring Cloud Sleuth\t| 1.0.0.RC1\t| ([issues](http://foo.bar.com))") .contains("spring-milestones") .contains("url 'http://repo.spring.io/milestone'") .contains("Dalston.RC1") diff --git a/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/ReleaserApplication.java b/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/ReleaserApplication.java index f564386b..07facd2f 100644 --- a/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/ReleaserApplication.java +++ b/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/ReleaserApplication.java @@ -32,9 +32,14 @@ public class ReleaserApplication implements CommandLineRunner { private static final Logger log = LoggerFactory.getLogger(ReleaserApplication.class); public static void main(String[] args) { - SpringApplication application = new SpringApplication(ReleaserApplication.class); - application.setWebApplicationType(WebApplicationType.NONE); - application.run(args); + try { + SpringApplication application = new SpringApplication(ReleaserApplication.class); + application.setWebApplicationType(WebApplicationType.NONE); + application.run(args); + } catch (Throwable e) { + log.error("Exception occurred for the releaser", e); + throw e; + } } @Autowired SpringReleaser releaser; @@ -44,8 +49,8 @@ public class ReleaserApplication implements CommandLineRunner { Options options = this.parser.parse(strings); try { this.releaser.release(options); - } catch (Exception e) { - log.error("Exception occurred for the releaser. Picked options were [" + options + "]", e); + } catch (Throwable e) { + log.error("Exception occurred for the releaser. Picked options were [" + options + "]"); throw e; } System.exit(0);