diff --git a/pom.xml b/pom.xml index ec754369e5983b43b31117963d17f85ad035badf..b712261f63fcbd37bf876df5313fe140284ec448 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,18 @@ <?xml version="1.0" encoding="UTF-8"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> +<!-- + Copyright (c) 2020 + 2025 Eclipse Foundation + + This program and the accompanying materials are made + available under the terms of the Eclipse Public License 2.0 + which is available at https://www.eclipse.org/legal/epl-2.0/ + + SPDX-License-Identifier: EPL-2.0 +--> + +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.eclipsefoundation</groupId> <artifactId>git-eca</artifactId> @@ -15,7 +28,7 @@ <quarkus.platform.version>3.15.4</quarkus.platform.version> <surefire-plugin.version>3.3.1</surefire-plugin.version> <maven.compiler.parameters>true</maven.compiler.parameters> - <eclipse-api-version>1.2.3</eclipse-api-version> + <eclipse-api-version>1.2.5</eclipse-api-version> <auto-value.version>1.10.4</auto-value.version> <org.mapstruct.version>1.5.5.Final</org.mapstruct.version> <sonar.sources>src/main</sonar.sources> @@ -75,8 +88,8 @@ <artifactId>quarkus-smallrye-jwt</artifactId> </dependency> <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-mailer</artifactId> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-mailer</artifactId> </dependency> <!-- Annotation preprocessors - reduce all of the boiler plate --> @@ -97,6 +110,12 @@ <version>${org.mapstruct.version}</version> <scope>provided</scope> </dependency> + <dependency> + <groupId>io.soabase.record-builder</groupId> + <artifactId>record-builder-processor</artifactId> + <version>45</version> + <scope>provided</scope> + </dependency> <!-- Test requirements --> <dependency> @@ -168,6 +187,11 @@ <artifactId>mapstruct-processor</artifactId> <version>${org.mapstruct.version}</version> </path> + <path> + <groupId>io.soabase.record-builder</groupId> + <artifactId>record-builder-processor</artifactId> + <version>45</version> + </path> </annotationProcessorPaths> </configuration> </plugin> @@ -184,4 +208,4 @@ </plugin> </plugins> </build> -</project> +</project> \ No newline at end of file diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubIssueComment.java b/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubIssueComment.java index a2c00ffe790fa549b7b2e9624b4797c874a64f69..91a798a7d5334e243b284e19e8c45aa4647d58b9 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubIssueComment.java +++ b/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubIssueComment.java @@ -10,53 +10,24 @@ package org.eclipsefoundation.git.eca.api.models; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; -import com.google.auto.value.AutoValue; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; + +import io.soabase.recordbuilder.core.RecordBuilder; /** * Model response for repos/{org}/{repo}/issues/{issueNumber}/comments */ -@AutoValue -@JsonDeserialize(builder = AutoValue_GithubIssueComment.Builder.class) -public abstract class GithubIssueComment { - public abstract String getBody(); - - public abstract Long getId(); - - public abstract GithubUser getUser(); - - @AutoValue - @JsonDeserialize(builder = AutoValue_GithubIssueComment_GithubUser.Builder.class) - public abstract static class GithubUser { - public abstract Long getId(); - - public static Builder builder() { - return new AutoValue_GithubIssueComment_GithubUser.Builder(); - } - - @AutoValue.Builder - @JsonPOJOBuilder(withPrefix = "set") - public abstract static class Builder { - public abstract Builder setId(Long id); - - public abstract GithubUser build(); - } - } - - public static Builder builder() { - return new AutoValue_GithubIssueComment.Builder(); - } - - @AutoValue.Builder - @JsonPOJOBuilder(withPrefix = "set") - public abstract static class Builder { - public abstract Builder setBody(String body); - - public abstract Builder setId(Long id); - - public abstract Builder setUser(GithubUser user); - - public abstract GithubIssueComment build(); - } +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@RecordBuilder +public record GithubIssueComment( + String body, + Long id, + GithubUser user +) { + @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) + @RecordBuilder + public static record GithubUser( + Long id + ) {} } diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubIssueCommentRequest.java b/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubIssueCommentRequest.java index 35b2adff4fdfff1e145f676a6dcdd986ce9b6389..b6ec978dd2a6016e5bcaba8e38eb609ce293a051 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubIssueCommentRequest.java +++ b/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubIssueCommentRequest.java @@ -10,51 +10,13 @@ package org.eclipsefoundation.git.eca.api.models; import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonNaming; -import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; -import com.google.auto.value.AutoValue; +import io.soabase.recordbuilder.core.RecordBuilder; /** * Model for creating a comment on a GitHub issue or pull request. * Uses the issues API endpoint which works for both issues and PRs. */ -@AutoValue -@JsonDeserialize(builder = AutoValue_GithubIssueCommentRequest.Builder.class) @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) -public abstract class GithubIssueCommentRequest { - /** - * Gets the comment body text. - * - * @return the comment body text - */ - public abstract String getBody(); - - /** - * Creates a new builder for this class. - * - * @return a new builder instance - */ - public static Builder builder() { - return new AutoValue_GithubIssueCommentRequest.Builder(); - } - - @AutoValue.Builder - @JsonPOJOBuilder(withPrefix = "set") - public abstract static class Builder { - /** - * Sets the comment body text. - * - * @param body the comment text to set - * @return this builder instance - */ - public abstract Builder setBody(String body); - - /** - * Builds a new GithubIssueCommentRequest instance. - * - * @return the built instance - */ - public abstract GithubIssueCommentRequest build(); - } -} \ No newline at end of file +@RecordBuilder +public record GithubIssueCommentRequest(String body) {} \ No newline at end of file diff --git a/src/main/java/org/eclipsefoundation/git/eca/config/GithubBotConfig.java b/src/main/java/org/eclipsefoundation/git/eca/config/GithubBotConfig.java index 65a3a3d34feb644cd47463ad752fd48ad650ea93..8d26a3e156d84bde21e4c9c6af21acf8807e69c2 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/config/GithubBotConfig.java +++ b/src/main/java/org/eclipsefoundation/git/eca/config/GithubBotConfig.java @@ -9,29 +9,14 @@ */ package org.eclipsefoundation.git.eca.config; +import io.smallrye.config.ConfigMapping; import java.util.Optional; -import org.eclipse.microprofile.config.inject.ConfigProperty; - -import jakarta.enterprise.context.ApplicationScoped; - /** * The intention of this class is to provide an static bot ID, given that this app only supports one installation, this way we can avoid * having to retrieve this information from the API on every start, or storing it in the DB. */ -@ApplicationScoped -public class GithubBotConfig { - - @ConfigProperty(name = "eclipse.github.bot.id") - Optional<Integer> botId; - - - /** - * Gets the GitHub bot ID. - * - * @return the bot ID as an Optional Integer - */ - public Optional<Integer> getBotId() { - return botId; - } +@ConfigMapping(prefix = "eclipse.github.bot") +public interface GithubBotConfig { + Optional<Long> id(); } diff --git a/src/main/java/org/eclipsefoundation/git/eca/helper/GithubHelper.java b/src/main/java/org/eclipsefoundation/git/eca/helper/GithubHelper.java index 2d5efc789a1e2e2f63dcf1a8192074c7bcf67fb1..0f5e15a22ccc97236a72948008345a24ae8164b1 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/helper/GithubHelper.java +++ b/src/main/java/org/eclipsefoundation/git/eca/helper/GithubHelper.java @@ -35,6 +35,7 @@ import org.eclipsefoundation.git.eca.api.models.GithubCommit.ParentCommit; import org.eclipsefoundation.git.eca.api.models.GithubCommitStatusRequest; import org.eclipsefoundation.git.eca.api.models.GithubIssueComment; import org.eclipsefoundation.git.eca.api.models.GithubIssueCommentRequest; +import org.eclipsefoundation.git.eca.api.models.GithubIssueCommentRequestBuilder; import org.eclipsefoundation.git.eca.api.models.GithubWebhookRequest; import org.eclipsefoundation.git.eca.api.models.GithubWebhookRequest.PullRequest; import org.eclipsefoundation.git.eca.config.GithubBotConfig; @@ -153,7 +154,7 @@ public class GithubHelper { } LOGGER.debug("Found {} commits for '{}#{}'", commitShas.size(), fullRepoName, prNo); - // retrieve the webhook tracking info, or generate an entry to track this PR + // retrieve the webhook tracking info, or generate an entry to track this PR if it's missing. GithubWebhookTracking updatedTracking = retrieveAndUpdateTrackingInformation(wrapper, installationId, org, repoName, prResponse.get()); if (updatedTracking == null) { @@ -472,9 +473,12 @@ public class GithubHelper { throw new IllegalStateException("Pull request should not be null when handling validation"); } - LOGGER - .trace("Generated access token for installation {}: {}", request.getInstallation().getId(), - jwtHelper.getGithubAccessToken(request.getInstallation().getId()).getToken()); + if (LOGGER.isTraceEnabled()) { + LOGGER + .trace("Generated access token for installation {}: {}", request.getInstallation().getId(), + jwtHelper.getGithubAccessToken(request.getInstallation().getId()).getToken()); + } + ghApi .updateStatus(jwtHelper.getGhBearerString(request.getInstallation().getId()), apiVersion, request.getRepository().getOwner().getLogin(), request.getRepository().getName(), pr.getHead().getSha(), @@ -499,24 +503,23 @@ public class GithubHelper { * @return List of all comments from the pull request */ private List<GithubIssueComment> getAllPullRequestCommentsByUserId(String bearer, String apiVersion, String org, String repo, - int prNumber, int userId) { + int prNumber, Long userId) { int perPage = 100; // GitHub's maximum items per page int page = 1; // Start from the first page List<GithubIssueComment> allComments = new ArrayList<>(); // Given that there's no pagination in the response, we need to query until we get an empty response, that would mean that we've // reached the end - while (true) { - RestResponse<List<GithubIssueComment>> response = ghApi - .getComments(bearer, apiVersion, org, repo, prNumber, perPage, page); + while (page < 50) { + RestResponse<List<GithubIssueComment>> response = ghApi.getComments(bearer, apiVersion, org, repo, prNumber, perPage, page); List<GithubIssueComment> comments = response.getEntity(); - if (Objects.isNull(comments) || comments.isEmpty()) { + if (comments == null || comments.isEmpty()) { break; } // We only want the comments made by the bot user - allComments.addAll(comments.stream().filter(comment -> comment.getUser().getId() == userId).collect(Collectors.toList())); + allComments.addAll(comments.stream().filter(comment -> comment.user().id() == userId).collect(Collectors.toList())); page++; } @@ -546,17 +549,17 @@ public class GithubHelper { Set<String> nonMentionedUsers = Set.copyOf(usernames); - if (botConfig.getBotId().isPresent()) { + if (botConfig.id().isPresent()) { // Get existing comments using pagination List<GithubIssueComment> comments = getAllPullRequestCommentsByUserId(ghBearerString, apiVersion, login, repositoryName, - pullRequestNumber, botConfig.getBotId().get()); + pullRequestNumber, botConfig.id().get()); nonMentionedUsers = usernames .stream() .filter(username -> comments .stream() .noneMatch( - comment -> Objects.requireNonNullElse(comment.getBody(), "").contains(String.format("@%s", username)))) + comment -> Objects.requireNonNullElse(comment.body(), "").contains(String.format("@%s", username)))) .collect(Collectors.toSet()); } @@ -566,18 +569,20 @@ public class GithubHelper { return; } - GithubIssueCommentRequest comment = GithubIssueCommentRequest + GithubIssueCommentRequest comment = GithubIssueCommentRequestBuilder .builder() - .setBody(failureMessage.data("reasons", errors).data("usernames", nonMentionedUsers).render()) + .body(failureMessage.data("reasons", errors).data("usernames", nonMentionedUsers).render()) .build(); - LOGGER - .trace("Generated access token for installation {}: {}", request.getInstallation().getId(), - jwtHelper.getGithubAccessToken(request.getInstallation().getId()).getToken()); + if (LOGGER.isTraceEnabled()) { + LOGGER + .trace("Generated access token for installation {}: {}", request.getInstallation().getId(), + jwtHelper.getGithubAccessToken(request.getInstallation().getId()).getToken()); + } LOGGER .trace("Adding new comment to PR {} in repo {}/{}: {}", pullRequestNumber, TransformationHelper.formatLog(login), - TransformationHelper.formatLog(repositoryName), comment.getBody()); + TransformationHelper.formatLog(repositoryName), comment.body()); ghApi.addComment(ghBearerString, apiVersion, login, repositoryName, pullRequestNumber, comment); } diff --git a/src/test/java/org/eclipsefoundation/git/eca/helper/GithubHelperTest.java b/src/test/java/org/eclipsefoundation/git/eca/helper/GithubHelperTest.java index 91e08bd918da6cd47b8e0443ff09065883cbf84c..7a8d3dd9aeeba8ac52a7782e455086876064f4e3 100644 --- a/src/test/java/org/eclipsefoundation/git/eca/helper/GithubHelperTest.java +++ b/src/test/java/org/eclipsefoundation/git/eca/helper/GithubHelperTest.java @@ -301,7 +301,7 @@ class GithubHelperTest { // Get the comments and verify content List<GithubIssueComment> comments = ghApi.getComments("", "", orgName, repoName, prNumber, 30, 1).getEntity(); - assertEquals(1, comments.stream().filter(comment -> comment.getBody().equals(expectedBody)).toList().size(), + assertEquals(1, comments.stream().filter(comment -> comment.body().equals(expectedBody)).toList().size(), "Expected one comment with the expected body"); } diff --git a/src/test/java/org/eclipsefoundation/git/eca/test/api/MockGithubAPI.java b/src/test/java/org/eclipsefoundation/git/eca/test/api/MockGithubAPI.java index db5b52ef38a6abe20de260996a126ed6fb962046..011d87550a63414aec5f3173016c85471e733826 100644 --- a/src/test/java/org/eclipsefoundation/git/eca/test/api/MockGithubAPI.java +++ b/src/test/java/org/eclipsefoundation/git/eca/test/api/MockGithubAPI.java @@ -30,6 +30,8 @@ import org.eclipsefoundation.git.eca.api.models.GithubCommit.GitCommitUser; import org.eclipsefoundation.git.eca.api.models.GithubCommit.GithubCommitUser; import org.eclipsefoundation.git.eca.api.models.GithubCommitStatusRequest; import org.eclipsefoundation.git.eca.api.models.GithubIssueComment; +import org.eclipsefoundation.git.eca.api.models.GithubIssueCommentBuilder; +import org.eclipsefoundation.git.eca.api.models.GithubIssueCommentGithubUserBuilder; import org.eclipsefoundation.git.eca.api.models.GithubIssueCommentRequest; import org.eclipsefoundation.git.eca.api.models.GithubWebhookRequest.PullRequest; import org.eclipsefoundation.git.eca.config.GithubBotConfig; @@ -139,11 +141,10 @@ public class MockGithubAPI implements GithubAPI { public Response addComment(String bearer, String apiVersion, String organization, String repo, int issueNumber, GithubIssueCommentRequest commentRequest) { String repoFullName = organization + '/' + repo; - GithubIssueComment comment = GithubIssueComment - .builder() - .setId(Long.valueOf(0)) - .setBody(commentRequest.getBody()) - .setUser(GithubIssueComment.GithubUser.builder().setId(Long.valueOf(githubBotConfig.getBotId().orElse(0))).build()) + GithubIssueComment comment = GithubIssueCommentBuilder.builder() + .id(0L) + .body(commentRequest.body()) + .user(GithubIssueCommentGithubUserBuilder.builder().id(githubBotConfig.id().orElse(0L)).build()) .build(); comments.computeIfAbsent(repoFullName, k -> new HashMap<>()).computeIfAbsent(issueNumber, k -> new ArrayList<>()).add(comment);