Skip to content
Snippets Groups Projects
Verified Commit a1019488 authored by Jordi Gómez's avatar Jordi Gómez
Browse files

feat: using Qute to create a more flexible message template

parent 930fc063
No related branches found
No related tags found
No related merge requests found
Pipeline #71401 passed
......@@ -17,6 +17,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
......@@ -38,6 +40,7 @@ import org.eclipsefoundation.git.eca.dto.CommitValidationStatus;
import org.eclipsefoundation.git.eca.dto.GithubApplicationInstallation;
import org.eclipsefoundation.git.eca.dto.GithubWebhookTracking;
import org.eclipsefoundation.git.eca.model.Commit;
import org.eclipsefoundation.git.eca.model.CommitStatus;
import org.eclipsefoundation.git.eca.model.GitUser;
import org.eclipsefoundation.git.eca.model.ValidationRequest;
import org.eclipsefoundation.git.eca.model.ValidationResponse;
......@@ -56,7 +59,8 @@ import org.eclipsefoundation.utils.helper.DateTimeHelper;
import org.eclipsefoundation.utils.helper.TransformationHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.quarkus.qute.Location;
import io.quarkus.qute.Template;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.BadRequestException;
......@@ -104,6 +108,9 @@ public class GithubHelper {
@RestClient
GithubAPI ghApi;
@Location("github/validation_failure.md")
Template failureMessage;
/**
* Using the wrapper and the passed unique information about a Github validation instance,
* lookup or create the tracking request and validate the data.
......@@ -261,32 +268,6 @@ public class GithubHelper {
.setContext(webhooksConfig.github().context()).build());
}
/**
* Posts a comment on a GitHub pull request when validation errors are found.
* If the error set is empty, no comment will be posted.
*
* @param request The GitHub webhook request containing pull request and repository information
* @param errors A set of error messages found during validation
*/
private void commentOnFailure(GithubWebhookRequest request, Set<String> errors) {
if (errors.isEmpty()) {
return;
}
StringBuilder sb = new StringBuilder();
sb.append("The following errors were found in the validation of this pull request:\n");
for (String error : errors) {
sb.append("- ").append(error).append('\n');
}
sb.append("Please check the ECA validation status for more information.");
ghApi.addComment(jwtHelper.getGhBearerString(request.getInstallation().getId()), apiVersion,
request.getRepository().getOwner().getLogin(), request.getRepository().getName(),
request.getPullRequest().getNumber(),
GithubPullRequestCommentRequest.builder().setBody(sb.toString())
.setCommitId(request.getPullRequest().getHead().getSha()).setPath(".")
.build());
}
/**
* Process the current request and update the checks state to pending then success or failure.
* Contains verbose TRACE logging for more info on the states of the validation for more
......@@ -325,12 +306,30 @@ public class GithubHelper {
pr.getNumber(), GithubCommitStatuses.FAILURE);
updateCommitStatus(request, GithubCommitStatuses.FAILURE);
commentOnFailure(request,
r.getCommits().entrySet().stream()
.filter(e -> !e.getValue().getErrors().isEmpty()).map(e -> e.getKey())
.map(hash -> findCommitByHash(vr, hash)).filter(Optional::isPresent)
.map(Optional::get).map(c -> c.getAuthor().getName())
.collect(Collectors.toSet()),
r.getCommits().values().stream().flatMap(c -> c.getErrors().stream())
.map(e -> e.getMessage()).collect(Collectors.toSet()));
}
return r;
}
/**
* Searches for a commit in the validation request based on its hash value.
*
* @param vr The validation request containing the list of commits to search
* @param hash The hash value to search for
* @return An Optional containing the matching Commit if found, or empty if not found
*/
private Optional<Commit> findCommitByHash(ValidationRequest vr, String hash) {
return vr.getCommits().stream().filter((commit) -> hash.equals(commit.getHash()))
.findFirst();
}
/**
* Shortcut method that will retrieve existing GH tracking info, create new entries if missing,
* and will update the state of existing requests as well.
......@@ -516,6 +515,48 @@ public class GithubHelper {
.setContext(webhooksConfig.github().context()).build());
}
/**
* This method posts a comment to the pull request detailing the validation errors and
* mentioning the usernames that need to take action.
*
* @param request The request containing repository and pull request information
* @param usernames Set of GitHub usernames that need to take action
* @param errors Set of error messages describing the validation failures
*
* @throws IllegalArgumentException if the request parameter is null
*/
private void commentOnFailure(GithubWebhookRequest request, Set<String> usernames,
Set<String> errors) {
// This should never happen given the logic behind the getPassed, but adding this just in
// case that logic changes
if (errors.isEmpty()) {
return;
}
String ghBearerString = jwtHelper.getGhBearerString(request.getInstallation().getId());
String login = request.getRepository().getOwner().getLogin();
String repositoryName = request.getRepository().getName();
Integer pullRequestNumber = request.getPullRequest().getNumber();
GithubPullRequestCommentRequest comment = GithubPullRequestCommentRequest.builder()
.setBody(failureMessage.data("reasons", errors).data("usernames", usernames)
.render())
.setCommitId(Objects.requireNonNull(request, "Request cannot be null")
.getPullRequest().getHead().getSha())
.setPath(".").build();
LOGGER.trace("Generated access token for installation {}: {}",
request.getInstallation().getId(),
jwtHelper.getGithubAccessToken(request.getInstallation().getId()).getToken());
LOGGER.trace("Adding comment to PR {} in repo {}/{}: {}", pullRequestNumber,
TransformationHelper.formatLog(login),
TransformationHelper.formatLog(repositoryName), comment.getBody());
ghApi.addComment(ghBearerString, apiVersion, login, repositoryName, pullRequestNumber,
comment);
}
/**
* Wraps a nullable value fetch to handle errors and will return null if the value can't be
* retrieved.
......
Hi{#for username in usernames} @{username}{/for} — thank you for your contribution!
The [Eclipse Contributor Agreement (ECA)](https://www.eclipse.org/legal/eca/) check has failed for this pull request due to one of the following reasons:
{#for reason in reasons}
- {reason}
{/for}
To resolve this, please:
1. Sign in or create an Eclipse Foundation account: https://accounts.eclipse.org/user/eca
1. Ensure your GitHub username is linked to your Eclipse account
1. Complete and submit the ECA form
Once done, push a new commit (or rebase) to re-trigger the ECA validation.
If you believe you've already completed these steps, please double-check your account settings or report an issue to [Eclipse Foundation Helpdesk](https://gitlab.eclipse.org/eclipsefdn/helpdesk).
Thanks again for your contribution!
\ No newline at end of file
......@@ -16,6 +16,7 @@ import java.net.URI;
import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.microprofile.rest.client.inject.RestClient;
......@@ -29,6 +30,7 @@ import org.eclipsefoundation.git.eca.model.ValidationRequest;
import org.eclipsefoundation.git.eca.model.ValidationResponse;
import org.eclipsefoundation.git.eca.namespace.APIStatusCode;
import org.eclipsefoundation.git.eca.namespace.ProviderType;
import org.eclipsefoundation.git.eca.namespace.GithubCommitStatuses;
import org.eclipsefoundation.git.eca.service.ValidationService;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
......@@ -52,22 +54,23 @@ class GithubHelperTest {
@Test
void testHandleGithubWebhookValidation_WithErrors() {
String commitHash = "abc123";
String orgName = "test-org";
String repoName = "test-repo";
int prNumber = 42;
Commit testCommit1 = createTestCommit("abc123", "testUser", "test@example.com");
Commit testCommit2 = createTestCommit("def456", "testUser2", "test2@example.com");
GithubWebhookRequest request =
createTestWebhookRequest("12345", repoName, orgName, prNumber, commitHash);
Commit testCommit = createTestCommit(commitHash, "Test User", "test@example.com");
createTestWebhookRequest("12345", repoName, orgName, prNumber, "abc123");
List<Commit> commits = List.of(testCommit1, testCommit2);
ValidationRequest vr = ValidationRequest.builder()
.setRepoUrl(URI.create("https://github.com/" + orgName + "/" + repoName))
.setProvider(ProviderType.GITHUB).setCommits(Collections.singletonList(testCommit))
.build();
.setProvider(ProviderType.GITHUB).setCommits(commits).build();
ValidationResponse mockResponse =
createErrorValidationResponse(commitHash, "Missing ECA for user");
ValidationResponse mockResponse = createErrorValidationResponse(
Map.of(testCommit1, List.of("Missing ECA for user"), testCommit2, List.of("Missing ECA for user", "Another error")));
when(validationService.validateIncomingRequest(Mockito.any(), Mockito.any()))
.thenReturn(mockResponse);
......@@ -75,15 +78,84 @@ class GithubHelperTest {
helper.handleGithubWebhookValidation(request, vr, null);
var expectedRequestBody = GithubPullRequestCommentRequest.builder()
.setBody("The following errors were found in the validation of this pull request:\n"
+ "- Missing ECA for user\n"
+ "Please check the ECA validation status for more information.")
.setBody(
"""
Hi @testUser @testUser2 — thank you for your contribution!
The [Eclipse Contributor Agreement (ECA)](https://www.eclipse.org/legal/eca/) check has failed for this pull request due to one of the following reasons:
- Missing ECA for user
- Another error
To resolve this, please:
1. Sign in or create an Eclipse Foundation account: https://accounts.eclipse.org/user/eca
1. Ensure your GitHub username is linked to your Eclipse account
1. Complete and submit the ECA form
Once done, push a new commit (or rebase) to re-trigger the ECA validation.
If you believe you've already completed these steps, please double-check your account settings or report an issue to [Eclipse Foundation Helpdesk](https://gitlab.eclipse.org/eclipsefdn/helpdesk).
Thanks again for your contribution!""")
.setCommitId(request.getPullRequest().getHead().getSha()).setPath(".").build();
verify(ghApi).addComment(Mockito.anyString(), Mockito.anyString(), Mockito.eq(orgName),
Mockito.eq(repoName), Mockito.eq(prNumber), Mockito.eq(expectedRequestBody));
}
@Test
void testHandleGithubWebhookValidation_Success() {
String orgName = "test-org";
String repoName = "test-repo";
int prNumber = 42;
Commit testCommit = createTestCommit("abc123", "testUser", "test@example.com");
GithubWebhookRequest request =
createTestWebhookRequest("12345", repoName, orgName, prNumber, "abc123");
List<Commit> commits = List.of(testCommit);
ValidationRequest vr = ValidationRequest.builder()
.setRepoUrl(URI.create("https://github.com/" + orgName + "/" + repoName))
.setProvider(ProviderType.GITHUB)
.setCommits(commits)
.build();
// Create a successful validation response with no errors
ValidationResponse mockResponse = ValidationResponse.builder()
.setTime(ZonedDateTime.now())
.setCommits(Map.of(testCommit.getHash(), CommitStatus.builder().build()))
.setTrackedProject(true)
.setStrictMode(true)
.build();
when(validationService.validateIncomingRequest(Mockito.any(), Mockito.any()))
.thenReturn(mockResponse);
helper.handleGithubWebhookValidation(request, vr, null);
// Verify status was updated to success
verify(ghApi).updateStatus(
Mockito.anyString(),
Mockito.anyString(),
Mockito.eq(orgName),
Mockito.eq(repoName),
Mockito.eq(request.getPullRequest().getHead().getSha()),
Mockito.argThat(status -> status.getState().equals(GithubCommitStatuses.SUCCESS.toString()))
);
// Verify no comment was posted
verify(ghApi, Mockito.never()).addComment(
Mockito.anyString(),
Mockito.anyString(),
Mockito.anyString(),
Mockito.anyString(),
Mockito.anyInt(),
Mockito.any()
);
}
/**
* Creates a GitHub webhook request for testing purposes.
*
......@@ -135,12 +207,13 @@ class GithubHelperTest {
* @param errorMessage The error message
* @return ValidationResponse instance
*/
private ValidationResponse createErrorValidationResponse(String commitHash,
String errorMessage) {
Map<String, CommitStatus> commits = new HashMap<>();
CommitStatus status = CommitStatus.builder().build();
status.addError(errorMessage, APIStatusCode.ERROR_AUTHOR);
commits.put(commitHash, status);
private ValidationResponse createErrorValidationResponse(Map<Commit, List<String>> commitErrors) {
Map<String, CommitStatus> commits =
commitErrors.entrySet().stream().collect(HashMap::new, (map, entry) -> {
CommitStatus status = CommitStatus.builder().build();
entry.getValue().forEach(error -> status.addError(error, APIStatusCode.ERROR_AUTHOR));
map.put(entry.getKey().getHash(), status);
}, HashMap::putAll);
return ValidationResponse.builder().setTime(ZonedDateTime.now()).setCommits(commits)
.setTrackedProject(true).setStrictMode(true).build();
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment