From 1f7b8f38fb08f1d02ba655ec209f5e0712faf357 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 6 Jun 2024 11:59:36 +0100 Subject: [PATCH] Improve how a repository is displayed in the calendar - 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 --- .../calendar/github/GitHubProperties.java | 56 +++++- .../github/GitHubReleaseScheduleSource.java | 163 ++++++++++++------ .../calendar/release/ProjectNameAliaser.java | 28 --- .../release/ReleaseConfiguration.java | 37 ---- .../calendar/release/ReleaseProperties.java | 41 ----- .../calendar/release/ReleaseUpdater.java | 11 +- .../release/StandardProjectNameAliaser.java | 39 ----- src/main/resources/application.yml | 23 +-- .../GitHubReleaseScheduleSourceTests.java | 5 +- 9 files changed, 179 insertions(+), 224 deletions(-) delete mode 100644 src/main/java/io/spring/calendar/release/ProjectNameAliaser.java delete mode 100644 src/main/java/io/spring/calendar/release/ReleaseConfiguration.java delete mode 100644 src/main/java/io/spring/calendar/release/ReleaseProperties.java delete mode 100644 src/main/java/io/spring/calendar/release/StandardProjectNameAliaser.java diff --git a/src/main/java/io/spring/calendar/github/GitHubProperties.java b/src/main/java/io/spring/calendar/github/GitHubProperties.java index 501e451..bf7850d 100644 --- a/src/main/java/io/spring/calendar/github/GitHubProperties.java +++ b/src/main/java/io/spring/calendar/github/GitHubProperties.java @@ -16,6 +16,7 @@ package io.spring.calendar.github; +import java.util.Collections; import java.util.List; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -30,9 +31,9 @@ class GitHubProperties { private final String token; - private final List organizations; + private final List organizations; - GitHubProperties(String token, List organizations) { + GitHubProperties(String token, List organizations) { this.token = token; this.organizations = organizations; } @@ -41,8 +42,57 @@ String getToken() { return this.token; } - List getOrganizations() { + List getOrganizations() { return this.organizations; } + static class Organization { + + private final String name; + + private final List transforms; + + Organization(String name, List transforms) { + this.name = name; + this.transforms = (transforms != null) ? transforms : Collections.emptyList(); + } + + String getName() { + return this.name; + } + + List 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; + } + + } + } diff --git a/src/main/java/io/spring/calendar/github/GitHubReleaseScheduleSource.java b/src/main/java/io/spring/calendar/github/GitHubReleaseScheduleSource.java index 26de8a8..27b6634 100644 --- a/src/main/java/io/spring/calendar/github/GitHubReleaseScheduleSource.java +++ b/src/main/java/io/spring/calendar/github/GitHubReleaseScheduleSource.java @@ -27,6 +27,8 @@ 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; @@ -34,6 +36,8 @@ 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. * @@ -41,17 +45,15 @@ */ class GitHubReleaseScheduleSource implements ReleaseScheduleSource { - private static final String COMMERCIAL_REPOSITORY_NAME_SUFFIX = "-commercial"; - private final Map> earlierMilestones = new HashMap<>(); private final Map> earlierRepositories = new HashMap<>(); - private final List organizations; + private final List organizations; private final GitHubOperations gitHub; - GitHubReleaseScheduleSource(GitHubOperations gitHub, List organizations) { + GitHubReleaseScheduleSource(GitHubOperations gitHub, List organizations) { this.gitHub = gitHub; this.organizations = organizations; } @@ -60,56 +62,45 @@ class GitHubReleaseScheduleSource implements ReleaseScheduleSource { public List get() { return this.organizations.stream() .flatMap(this::getRepositories) - .filter(this::include) + .filter(Project::include) .map(this::createReleaseSchedule) .toList(); } - private Stream getRepositories(String organization) { - Page 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 page = this.gitHub.getMilestones(repository, - this.earlierMilestones.get(repository.getFullName())); - this.earlierMilestones.put(repository.getFullName(), page); - String projectName = getProjectName(repository); - List releases = getReleases(projectName, repository, page); - return new ReleaseSchedule(projectName, releases); + private Stream getRepositories(Organization organization) { + String organizationName = organization.getName(); + Map 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 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 page = this.gitHub.getMilestones(project.getRepository(), + this.earlierMilestones.get(project.getRepository().getFullName())); + this.earlierMilestones.put(project.getRepository().getFullName(), page); + List releases = getReleases(project, page); + return new ReleaseSchedule(project.getName(), releases); } - private List getReleases(String projectName, Repository repository, Page page) { + private List getReleases(Project project, Page page) { return collectContent(page).stream() .filter(this::hasReleaseDate) - .map((Milestone milestone) -> createRelease(projectName, repository, milestone)) + .map((Milestone milestone) -> createRelease(project, milestone)) .toList(); } @@ -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); + } + } + } } diff --git a/src/main/java/io/spring/calendar/release/ProjectNameAliaser.java b/src/main/java/io/spring/calendar/release/ProjectNameAliaser.java deleted file mode 100644 index 6e5157b..0000000 --- a/src/main/java/io/spring/calendar/release/ProjectNameAliaser.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2016-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.spring.calendar.release; - -import java.util.function.Function; - -/** - * Provides an alias for the name of a project. - * - * @author Andy Wilkinson - */ -public interface ProjectNameAliaser extends Function { - -} diff --git a/src/main/java/io/spring/calendar/release/ReleaseConfiguration.java b/src/main/java/io/spring/calendar/release/ReleaseConfiguration.java deleted file mode 100644 index 1c51cbb..0000000 --- a/src/main/java/io/spring/calendar/release/ReleaseConfiguration.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2016-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.spring.calendar.release; - -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * Configuration for release-related functionality. - * - * @author Andy Wilkinson - */ -@Configuration -@EnableConfigurationProperties(ReleaseProperties.class) -class ReleaseConfiguration { - - @Bean - StandardProjectNameAliaser projectNameAliaser(ReleaseProperties releaseProperties) { - return new StandardProjectNameAliaser(releaseProperties.getProjectAliases()); - } - -} diff --git a/src/main/java/io/spring/calendar/release/ReleaseProperties.java b/src/main/java/io/spring/calendar/release/ReleaseProperties.java deleted file mode 100644 index 8544f4c..0000000 --- a/src/main/java/io/spring/calendar/release/ReleaseProperties.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2016-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.spring.calendar.release; - -import java.util.Map; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Release-related configuration properties. - * - * @author Andy Wilkinson - */ -@ConfigurationProperties(prefix = "calendar.release") -class ReleaseProperties { - - private final Map projectAliases; - - ReleaseProperties(Map projectAliases) { - this.projectAliases = projectAliases; - } - - Map getProjectAliases() { - return this.projectAliases; - } - -} diff --git a/src/main/java/io/spring/calendar/release/ReleaseUpdater.java b/src/main/java/io/spring/calendar/release/ReleaseUpdater.java index 81162ba..d54dc22 100644 --- a/src/main/java/io/spring/calendar/release/ReleaseUpdater.java +++ b/src/main/java/io/spring/calendar/release/ReleaseUpdater.java @@ -40,13 +40,9 @@ class ReleaseUpdater { private final ReleaseRepository releaseRepository; - private final ProjectNameAliaser projectNameAliaser; - - ReleaseUpdater(List releaseScheduleSources, ReleaseRepository releaseRepository, - ProjectNameAliaser projectNameAliaser) { + ReleaseUpdater(List releaseScheduleSources, ReleaseRepository releaseRepository) { this.releaseScheduleSources = releaseScheduleSources; this.releaseRepository = releaseRepository; - this.projectNameAliaser = projectNameAliaser; } @Scheduled(fixedRate = 5 * 60 * 1000) @@ -55,7 +51,6 @@ void updateReleases() { List releases = getReleaseSchedulesByProject().values() .stream() .flatMap((releaseSchedule) -> releaseSchedule.getReleases().stream()) - .map(this::applyNameAlias) .toList(); updateReleases(releases); log.info("Releases updated"); @@ -77,10 +72,6 @@ private void collect(Map schedulesByProject, ReleaseSch } } - private Release applyNameAlias(Release release) { - return release.withProject(this.projectNameAliaser.apply(release.getProject())); - } - private void updateReleases(List releases) { this.releaseRepository.set(releases); } diff --git a/src/main/java/io/spring/calendar/release/StandardProjectNameAliaser.java b/src/main/java/io/spring/calendar/release/StandardProjectNameAliaser.java deleted file mode 100644 index 27eacc3..0000000 --- a/src/main/java/io/spring/calendar/release/StandardProjectNameAliaser.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2016-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.spring.calendar.release; - -import java.util.Map; - -/** - * Standard implementation of {@code ProjectNameAliaser}. - * - * @author Andy Wilkinson - */ -class StandardProjectNameAliaser implements ProjectNameAliaser { - - private final Map aliases; - - StandardProjectNameAliaser(Map aliases) { - this.aliases = aliases; - } - - @Override - public String apply(String name) { - return this.aliases.getOrDefault(name, name); - } - -} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 4826a6d..87ea217 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -6,13 +6,16 @@ calendar: github: token: ${calendar-github-token} organizations: - - micrometer-metrics - - r2dbc - - reactor - - spring-cloud - - spring-gradle-plugins - - spring-io - - spring-projects - release: - project_aliases: - 'Toolsuite Distribution': STS + - name: micrometer-metrics + transforms: + - repository: tracing + display-name: Micrometer Tracing + - repository: tracing-commercial + display-name: Micrometer Tracing + commercial-project-id: micrometer-tracing + - name: r2dbc + - name: reactor + - name: spring-cloud + - name: spring-gradle-plugins + - name: spring-io + - name: spring-projects diff --git a/src/test/java/io/spring/calendar/github/GitHubReleaseScheduleSourceTests.java b/src/test/java/io/spring/calendar/github/GitHubReleaseScheduleSourceTests.java index 44e3ed5..50d79cd 100644 --- a/src/test/java/io/spring/calendar/github/GitHubReleaseScheduleSourceTests.java +++ b/src/test/java/io/spring/calendar/github/GitHubReleaseScheduleSourceTests.java @@ -17,8 +17,10 @@ package io.spring.calendar.github; import java.time.ZonedDateTime; +import java.util.Collections; import java.util.List; +import io.spring.calendar.github.GitHubProperties.Organization; import io.spring.calendar.github.Milestone.State; import io.spring.calendar.github.Repository.Visibility; import io.spring.calendar.release.ReleaseSchedule; @@ -39,7 +41,8 @@ class GitHubReleaseScheduleSourceTests { private final GitHubOperations gitHub = mock(GitHubOperations.class); private final GitHubReleaseScheduleSource source = new GitHubReleaseScheduleSource(this.gitHub, - List.of("spring-projects", "spring-cloud")); + List.of(new Organization("spring-projects", Collections.emptyList()), + new Organization("spring-cloud", Collections.emptyList()))); @Test void whenThereAreNoRepositoriesThenReleaseSchedulesIsEmpty() {