Skip to content

Commit

Permalink
Improve how a repository is displayed in the calendar
Browse files Browse the repository at this point in the history
- Allow configuration properties to be used to override how a
  repository's name is transformed into the name that's displayed on
  the calendar
- Allow configuration properties to be used to override how a
  repository's name is used to link to its project page on
  https://enterprise.spring.io
  • Loading branch information
wilkinsona committed Jun 6, 2024
1 parent 2559c16 commit 1f7b8f3
Show file tree
Hide file tree
Showing 9 changed files with 179 additions and 224 deletions.
56 changes: 53 additions & 3 deletions src/main/java/io/spring/calendar/github/GitHubProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package io.spring.calendar.github;

import java.util.Collections;
import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;
Expand All @@ -30,9 +31,9 @@ class GitHubProperties {

private final String token;

private final List<String> organizations;
private final List<Organization> organizations;

GitHubProperties(String token, List<String> organizations) {
GitHubProperties(String token, List<Organization> organizations) {
this.token = token;
this.organizations = organizations;
}
Expand All @@ -41,8 +42,57 @@ String getToken() {
return this.token;
}

List<String> getOrganizations() {
List<Organization> getOrganizations() {
return this.organizations;
}

static class Organization {

private final String name;

private final List<Transform> transforms;

Organization(String name, List<Transform> transforms) {
this.name = name;
this.transforms = (transforms != null) ? transforms : Collections.emptyList();
}

String getName() {
return this.name;
}

List<Transform> getTransforms() {
return this.transforms;
}

}

static class Transform {

private final String repository;

private final String displayName;

private final String commercialProjectId;

Transform(String repository, String displayName, String commercialProjectId) {
this.repository = repository;
this.displayName = displayName;
this.commercialProjectId = commercialProjectId;
}

String getRepository() {
return this.repository;
}

String getDisplayName() {
return this.displayName;
}

String getCommercialProjectId() {
return this.commercialProjectId;
}

}

}
163 changes: 108 additions & 55 deletions src/main/java/io/spring/calendar/github/GitHubReleaseScheduleSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,31 +27,33 @@
import java.util.Map;
import java.util.stream.Stream;

import io.spring.calendar.github.GitHubProperties.Organization;
import io.spring.calendar.github.GitHubProperties.Transform;
import io.spring.calendar.github.Milestone.State;
import io.spring.calendar.github.Repository.Visibility;
import io.spring.calendar.release.Release;
import io.spring.calendar.release.Release.Status;
import io.spring.calendar.release.ReleaseSchedule;
import io.spring.calendar.release.ReleaseScheduleSource;

import org.springframework.util.Assert;

/**
* A {@link ReleaseScheduleSource} for projects managed on GitHub.
*
* @author Andy Wilkinson
*/
class GitHubReleaseScheduleSource implements ReleaseScheduleSource {

private static final String COMMERCIAL_REPOSITORY_NAME_SUFFIX = "-commercial";

private final Map<String, Page<Milestone>> earlierMilestones = new HashMap<>();

private final Map<String, Page<Repository>> earlierRepositories = new HashMap<>();

private final List<String> organizations;
private final List<Organization> organizations;

private final GitHubOperations gitHub;

GitHubReleaseScheduleSource(GitHubOperations gitHub, List<String> organizations) {
GitHubReleaseScheduleSource(GitHubOperations gitHub, List<Organization> organizations) {
this.gitHub = gitHub;
this.organizations = organizations;
}
Expand All @@ -60,56 +62,45 @@ class GitHubReleaseScheduleSource implements ReleaseScheduleSource {
public List<ReleaseSchedule> get() {
return this.organizations.stream()
.flatMap(this::getRepositories)
.filter(this::include)
.filter(Project::include)
.map(this::createReleaseSchedule)
.toList();
}

private Stream<Repository> getRepositories(String organization) {
Page<Repository> page = this.gitHub.getRepositories(organization, this.earlierRepositories.get(organization));
this.earlierRepositories.put(organization, this.earlierRepositories.get(organization));
return collectContent(page).stream();
}

private boolean include(Repository repository) {
return (repository.getVisibility() == Visibility.PUBLIC) || repository.getName().endsWith("-commercial");
}

private ReleaseSchedule createReleaseSchedule(Repository repository) {
Page<Milestone> page = this.gitHub.getMilestones(repository,
this.earlierMilestones.get(repository.getFullName()));
this.earlierMilestones.put(repository.getFullName(), page);
String projectName = getProjectName(repository);
List<Release> releases = getReleases(projectName, repository, page);
return new ReleaseSchedule(projectName, releases);
private Stream<Project> getRepositories(Organization organization) {
String organizationName = organization.getName();
Map<String, Transform> transforms = new HashMap<>();
for (Transform transform : organization.getTransforms()) {
Transform existing = transforms.put(transform.getRepository(), transform);
Assert.isNull(existing, () -> "Found duplicate transform for %s/%s".formatted(organization.getName(),
transform.getRepository()));
}
Page<Repository> page = this.gitHub.getRepositories(organizationName,
this.earlierRepositories.get(organizationName));
this.earlierRepositories.put(organizationName, this.earlierRepositories.get(organizationName));
return collectContent(page).stream()
.map((repository) -> asProject(repository, transforms.get(repository.getName())));
}

private String getProjectName(Repository repository) {
String name = repository.getName();
if (name.endsWith(GitHubReleaseScheduleSource.COMMERCIAL_REPOSITORY_NAME_SUFFIX)) {
name = name.substring(0,
name.length() - GitHubReleaseScheduleSource.COMMERCIAL_REPOSITORY_NAME_SUFFIX.length());
private Project asProject(Repository repository, Transform transform) {
if (transform == null) {
return new Project(repository);
}
return capitalize(name.replace('-', ' '));
return new Project(repository, transform);
}

private static String capitalize(String input) {
StringWriter output = new StringWriter();
for (int i = 0; i < input.length(); i++) {
if (i == 0 || i > 0 && input.charAt(i - 1) == ' ') {
output.append(Character.toUpperCase(input.charAt(i)));
}
else {
output.append(input.charAt(i));
}
}
return output.toString();
private ReleaseSchedule createReleaseSchedule(Project project) {
Page<Milestone> page = this.gitHub.getMilestones(project.getRepository(),
this.earlierMilestones.get(project.getRepository().getFullName()));
this.earlierMilestones.put(project.getRepository().getFullName(), page);
List<Release> releases = getReleases(project, page);
return new ReleaseSchedule(project.getName(), releases);
}

private List<Release> getReleases(String projectName, Repository repository, Page<Milestone> page) {
private List<Release> getReleases(Project project, Page<Milestone> page) {
return collectContent(page).stream()
.filter(this::hasReleaseDate)
.map((Milestone milestone) -> createRelease(projectName, repository, milestone))
.map((Milestone milestone) -> createRelease(project, milestone))
.toList();
}

Expand All @@ -126,33 +117,95 @@ private boolean hasReleaseDate(Milestone milestone) {
return milestone.getDueOn() != null;
}

private Release createRelease(String projectName, Repository repository, Milestone milestone) {
return new Release(projectName, milestone.getTitle(),
private Release createRelease(Project project, Milestone milestone) {
return new Release(project.getName(), milestone.getTitle(),
milestone.getDueOn()
.withZoneSameInstant(ZoneId.of("Europe/London"))
.format(DateTimeFormatter.ISO_LOCAL_DATE),
getStatus(milestone), getUrl(repository, milestone),
repository.getName().endsWith(GitHubReleaseScheduleSource.COMMERCIAL_REPOSITORY_NAME_SUFFIX));
getStatus(milestone), project.urlFor(milestone), project.isCommercial());
}

private Status getStatus(Milestone milestone) {
return (milestone.getState() == State.OPEN) ? Status.OPEN : Status.CLOSED;
}

private URL getUrl(Repository repository, Milestone milestone) {
try {
if (repository.getName().endsWith(COMMERCIAL_REPOSITORY_NAME_SUFFIX)) {
String url = "https://enterprise.spring.io/projects/" + repository.getName();
if (milestone.getState() == State.CLOSED) {
url = url + "/changelog/" + milestone.getTitle();
private static class Project {

private static final String COMMERCIAL_REPOSITORY_NAME_SUFFIX = "-commercial";

private final Repository repository;

private final String name;

private final String commercialProjectId;

Project(Repository repository) {
this(repository, getName(repository), repository.getName());
}

Project(Repository repository, Transform transform) {
this(repository, transform.getDisplayName(), transform.getCommercialProjectId());
}

Project(Repository repository, String name, String commercialProjectId) {
this.repository = repository;
this.name = name;
this.commercialProjectId = commercialProjectId;
}

private Repository getRepository() {
return this.repository;
}

private boolean include() {
return (this.repository.getVisibility() == Visibility.PUBLIC) || this.isCommercial();
}

private String getName() {
return this.name;
}

private static String getName(Repository repository) {
String name = repository.getName();
if (name.endsWith(COMMERCIAL_REPOSITORY_NAME_SUFFIX)) {
name = name.substring(0, name.length() - COMMERCIAL_REPOSITORY_NAME_SUFFIX.length());
}
return capitalize(name.replace('-', ' '));
}

private static String capitalize(String input) {
StringWriter output = new StringWriter();
for (int i = 0; i < input.length(); i++) {
if (i == 0 || i > 0 && input.charAt(i - 1) == ' ') {
output.append(Character.toUpperCase(input.charAt(i)));
}
else {
output.append(input.charAt(i));
}
return new URL(url);
}
return new URL(repository.getHtmlUrl().toString() + "/milestone/" + milestone.getNumber());
return output.toString();
}
catch (MalformedURLException ex) {
throw new RuntimeException(ex);

private boolean isCommercial() {
return this.repository.getName().endsWith(COMMERCIAL_REPOSITORY_NAME_SUFFIX);
}

private URL urlFor(Milestone milestone) {
try {
if (this.repository.getName().endsWith(COMMERCIAL_REPOSITORY_NAME_SUFFIX)) {
String url = "https://enterprise.spring.io/projects/" + this.commercialProjectId;
if (milestone.getState() == State.CLOSED) {
url = url + "/changelog/" + milestone.getTitle();
}
return new URL(url);
}
return new URL(this.repository.getHtmlUrl().toString() + "/milestone/" + milestone.getNumber());
}
catch (MalformedURLException ex) {
throw new RuntimeException(ex);
}
}

}

}
28 changes: 0 additions & 28 deletions src/main/java/io/spring/calendar/release/ProjectNameAliaser.java

This file was deleted.

37 changes: 0 additions & 37 deletions src/main/java/io/spring/calendar/release/ReleaseConfiguration.java

This file was deleted.

Loading

0 comments on commit 1f7b8f3

Please sign in to comment.