ENhanced blog post generation; fixes gh-94
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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<ProjectVersion, String> 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<String, String> openMilestones() {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("state", "open");
|
||||
params.put("sort", "due_on");
|
||||
params.put("direction", "desc");
|
||||
return params;
|
||||
}
|
||||
|
||||
private Map<String, String> closedMilestones() {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("state", "closed");
|
||||
params.put("sort", "due_on");
|
||||
params.put("direction", "desc");
|
||||
return params;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<BlogTuple> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Notes> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<String, Object> map = ImmutableMap.<String, Object>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<Notes> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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("<version>Dalston.RELEASE</version>")
|
||||
.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("<version>Dalston.RELEASE</version>")
|
||||
.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("<version>Dalston.SR1</version>")
|
||||
.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("<version>Dalston.SR1</version>")
|
||||
.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("<id>spring-milestones</id>")
|
||||
.contains("url 'http://repo.spring.io/milestone'")
|
||||
.contains("<version>Dalston.M1</version>")
|
||||
@@ -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("<id>spring-milestones</id>")
|
||||
.contains("url 'http://repo.spring.io/milestone'")
|
||||
.contains("<version>Dalston.M1</version>")
|
||||
@@ -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("<id>spring-milestones</id>")
|
||||
.contains("url 'http://repo.spring.io/milestone'")
|
||||
.contains("<version>Dalston.RC1</version>")
|
||||
@@ -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("<id>spring-milestones</id>")
|
||||
.contains("url 'http://repo.spring.io/milestone'")
|
||||
.contains("<version>Dalston.RC1</version>")
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user