From adf26a73ec552c65999660622fce58cd03c0d5ac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jordi=20G=C3=B3mez?= <jordi.gomez@eclipse-foundation.org>
Date: Mon, 7 Apr 2025 12:09:18 +0200
Subject: [PATCH 01/10] feat: adding email notification when certian
 revalidation threshold is met

---
 pom.xml                                       |  19 +-
 .../git/eca/service/MailerService.java        |  27 ++
 .../service/impl/DefaultMailerService.java    | 104 +++++++
 .../eca/tasks/GithubRevalidationQueue.java    | 267 +++++++++++-------
 .../templates/emails/revalidation_alert.txt   |  10 +
 src/test/resources/application.properties     |  11 +-
 6 files changed, 324 insertions(+), 114 deletions(-)
 create mode 100644 src/main/java/org/eclipsefoundation/git/eca/service/MailerService.java
 create mode 100644 src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultMailerService.java
 create mode 100644 src/main/resources/templates/emails/revalidation_alert.txt

diff --git a/pom.xml b/pom.xml
index 93dcd59b..ec754369 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,8 +1,5 @@
-<?xml version="1.0"?>
-<project
-    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
-    xmlns="http://maven.apache.org/POM/4.0.0"
-    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+<?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">
     <modelVersion>4.0.0</modelVersion>
     <groupId>org.eclipsefoundation</groupId>
     <artifactId>git-eca</artifactId>
@@ -25,8 +22,7 @@
         <sonar.tests>src/test</sonar.tests>
         <sonar.java.coveragePlugin>jacoco</sonar.java.coveragePlugin>
         <sonar.dynamicAnalysis>reuseReports</sonar.dynamicAnalysis>
-        <sonar.coverage.jacoco.xmlReportPaths>
-            ${project.basedir}/target/jacoco-report/jacoco.xml</sonar.coverage.jacoco.xmlReportPaths>
+        <sonar.coverage.jacoco.xmlReportPaths>${project.basedir}/target/jacoco-report/jacoco.xml</sonar.coverage.jacoco.xmlReportPaths>
         <sonar.junit.reportPath>${project.build.directory}/surefire-reports</sonar.junit.reportPath>
         <sonar.host.url>https://sonarcloud.io</sonar.host.url>
         <sonar.organization>eclipse-foundation-it</sonar.organization>
@@ -38,10 +34,8 @@
             <id>eclipsefdn</id>
             <url>https://repo.eclipse.org/content/repositories/eclipsefdn/</url>
             <releases>
-                <enabled>true</enabled>
             </releases>
             <snapshots>
-                <enabled>true</enabled>
             </snapshots>
         </repository>
     </repositories>
@@ -80,6 +74,10 @@
             <groupId>io.quarkus</groupId>
             <artifactId>quarkus-smallrye-jwt</artifactId>
         </dependency>
+        <dependency>
+          <groupId>io.quarkus</groupId>
+          <artifactId>quarkus-mailer</artifactId>
+        </dependency>
 
         <!-- Annotation preprocessors - reduce all of the boiler plate -->
         <dependency>
@@ -179,8 +177,7 @@
                 <configuration>
                     <skipTests>false</skipTests>
                     <systemPropertyVariables>
-                        <java.util.logging.manager>
-                            org.jboss.logmanager.LogManager</java.util.logging.manager>
+                        <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
                         <maven.home>${maven.home}</maven.home>
                     </systemPropertyVariables>
                 </configuration>
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/MailerService.java b/src/main/java/org/eclipsefoundation/git/eca/service/MailerService.java
new file mode 100644
index 00000000..fc3d65f0
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/git/eca/service/MailerService.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 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
+ */
+package org.eclipsefoundation.git.eca.service;
+
+import org.eclipsefoundation.git.eca.dto.GithubWebhookTracking;
+
+/**
+ * A service for sending email notifications related to GitHub webhook events.
+ * This interface defines methods for sending different types of email alerts
+ * to appropriate parties based on webhook tracking data.
+ */
+public interface MailerService {
+  /**
+   * Sends a revalidation alert for a tracked GitHub webhook event.
+   * This method is responsible for notifying relevant parties about the need to revalidate a webhook tracking event.
+   *
+   * @param tracking the GitHub webhook tracking information containing details about the event that needs revalidation
+   */
+  void sendRevalidationAlert(GithubWebhookTracking tracking, Integer threshold);
+}
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultMailerService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultMailerService.java
new file mode 100644
index 00000000..48922653
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultMailerService.java
@@ -0,0 +1,104 @@
+/**
+ * Copyright (c) 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/
+ *
+ * Author: Jordi Gómez <jordi.gomez@eclipse-foundation.org>
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipsefoundation.git.eca.service.impl;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.eclipsefoundation.git.eca.dto.GithubWebhookTracking;
+import org.eclipsefoundation.git.eca.service.MailerService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.quarkus.mailer.Mail;
+import io.quarkus.mailer.Mailer;
+import io.quarkus.qute.Location;
+import io.quarkus.qute.Template;
+import io.smallrye.config.ConfigMapping;
+import jakarta.enterprise.context.ApplicationScoped;
+
+@ApplicationScoped
+public class DefaultMailerService implements MailerService {
+  public static final Logger LOGGER = LoggerFactory.getLogger(DefaultMailerService.class);
+  private final EclipseMailerConfig config;
+  private final Mailer mailer;
+
+  @Location("emails/revalidation-alert")
+  Template revalidationAlertTemplate;
+
+  public DefaultMailerService(EclipseMailerConfig config, Mailer mailer) {
+    this.config = config;
+    this.mailer = mailer;
+  }
+
+  @Override
+  public void sendRevalidationAlert(GithubWebhookTracking tracking, Integer threshold) {
+    var revalidationAlertConfig = config.revalidationAlert();
+
+    if (revalidationAlertConfig.to().isEmpty()) {
+      LOGGER.warn("No recipients configured for revalidation alert. Skipping email notification.");
+      return;
+    }
+
+    String subject = "GitHub Webhook Revalidation Alert";
+
+    Mail messageBuilder = new Mail();
+
+    messageBuilder.setSubject(subject);
+    messageBuilder.setText(revalidationAlertTemplate.data("attempts", threshold)
+        .data("requestId", tracking.getId())
+        .data("repository", tracking.getRepositoryFullName())
+        .data("pullRequest", tracking.getPullRequestNumber())
+        .data("lastState", tracking.getLastKnownState())
+        .data("lastUpdated", tracking.getLastUpdated().toString())
+        .render());
+
+    messageBuilder.addTo(revalidationAlertConfig.to().toArray(String[]::new));
+    revalidationAlertConfig.authorMessage().replyTo()
+        .ifPresent(messageBuilder::addReplyTo);
+    revalidationAlertConfig.authorMessage().bcc()
+        .ifPresent(bcc -> messageBuilder.addBcc(bcc.toArray(String[]::new)));
+
+    mailer.send(messageBuilder);
+    LOGGER.info("Revalidation alert sent to: {}", revalidationAlertConfig.to());
+  }
+
+  /**
+   * Represents configuration for the default mailer service.
+   * 
+   * @author Martin Lowe
+   *
+   */
+  @ConfigMapping(prefix = "eclipse.mailer")
+  public interface EclipseMailerConfig {
+
+    public RevalidationAlert revalidationAlert();
+
+    /**
+     * This interface defines the contract for specifying recipients and message
+     * configuration when sending revalidation alerts within the ECA validation
+     * process.
+     */
+    public interface RevalidationAlert {
+      public List<String> to();
+
+      public MessageConfiguration authorMessage();
+    }
+
+    public interface MessageConfiguration {
+      public Optional<String> replyTo();
+
+      public Optional<List<String>> bcc();
+    }
+  }
+
+}
diff --git a/src/main/java/org/eclipsefoundation/git/eca/tasks/GithubRevalidationQueue.java b/src/main/java/org/eclipsefoundation/git/eca/tasks/GithubRevalidationQueue.java
index bdb9f0c6..fc64cae3 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/tasks/GithubRevalidationQueue.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/tasks/GithubRevalidationQueue.java
@@ -14,11 +14,13 @@ package org.eclipsefoundation.git.eca.tasks;
 import java.net.URI;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 
 import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.eclipsefoundation.git.eca.dto.GithubWebhookTracking;
 import org.eclipsefoundation.git.eca.helper.GithubHelper;
 import org.eclipsefoundation.git.eca.namespace.GitEcaParameterNames;
+import org.eclipsefoundation.git.eca.service.MailerService;
 import org.eclipsefoundation.http.model.FlatRequestWrapper;
 import org.eclipsefoundation.http.model.RequestWrapper;
 import org.eclipsefoundation.http.namespace.DefaultUrlParameterNames;
@@ -40,115 +42,176 @@ import jakarta.ws.rs.core.MultivaluedHashMap;
 import jakarta.ws.rs.core.MultivaluedMap;
 
 /**
- * Scheduled regular task that will interact with the backend persistence to look for requests that are in a
- * failed/unvalidated state after an error while processing that could not be recovered. These requests will be
- * reprocessed using the same logic as the standard validation, updating the timestamp on completion and either setting
- * the revalidation flag to false or incrementing the number of repeated revalidations needed for the request for
+ * Scheduled regular task that will interact with the backend persistence to
+ * look for requests that are in a
+ * failed/unvalidated state after an error while processing that could not be
+ * recovered. These requests will be
+ * reprocessed using the same logic as the standard validation, updating the
+ * timestamp on completion and either setting
+ * the revalidation flag to false or incrementing the number of repeated
+ * revalidations needed for the request for
  * tracking, depending on the succcess of the revalidation.
  */
 @ApplicationScoped
 public class GithubRevalidationQueue {
-    private static final Logger LOGGER = LoggerFactory.getLogger(GithubRevalidationQueue.class);
-
-    // full repo name should be 2 parts, org and actual repo name
-    private static final int FULL_REPO_NAME_PARTS = 2;
-
-    @ConfigProperty(name = "eclipse.git-eca.tasks.gh-revalidation.enabled", defaultValue = "true")
-    Instance<Boolean> isEnabled;
-
-    @Inject
-    PersistenceDao dao;
-    @Inject
-    FilterService filters;
-
-    @Inject
-    GithubHelper validationHelper;
-
-    @PostConstruct
-    void init() {
-        // indicate to log whether enabled to reduce log spam
-        LOGGER.info("Github revalidation queue task is{} enabled.", Boolean.TRUE.equals(isEnabled.get()) ? "" : " not");
+  private static final Logger LOGGER = LoggerFactory.getLogger(GithubRevalidationQueue.class);
+
+  // full repo name should be 2 parts, org and actual repo name
+  private static final int FULL_REPO_NAME_PARTS = 2;
+
+  @ConfigProperty(name = "eclipse.git-eca.tasks.gh-revalidation.enabled", defaultValue = "true")
+  Instance<Boolean> isEnabled;
+
+  @ConfigProperty(name = "eclipse.git-eca.tasks.gh-revalidation.notification-threshold", defaultValue = "3")
+  Instance<Integer> revalidationThreshold;
+
+  @Inject
+  PersistenceDao dao;
+  @Inject
+  FilterService filters;
+  @Inject
+  GithubHelper validationHelper;
+  @Inject
+  MailerService mailerService;
+
+  @PostConstruct
+  void init() {
+    // indicate to log whether enabled to reduce log spam
+    LOGGER.info("Github revalidation queue task is{} enabled.", Boolean.TRUE.equals(isEnabled.get()) ? "" : " not");
+  }
+
+  /**
+   * Every 5s, this method will attempt to load a Github webhook validation
+   * request that has the needs revalidation flag
+   * set to true. This will retrieve the oldest request in queue and will attempt
+   * to revalidate it.
+   */
+  @Scheduled(every = "${eclipse.git-eca.tasks.gh-revalidation.frequency:60s}")
+  @ActivateRequestContext
+  public void revalidate() {
+    // if not enabled, don't process any potentially OOD records
+    if (!Boolean.TRUE.equals(isEnabled.get())) {
+      return;
     }
 
-    /**
-     * Every 5s, this method will attempt to load a Github webhook validation request that has the needs revalidation flag
-     * set to true. This will retrieve the oldest request in queue and will attempt to revalidate it.
-     */
-    @Scheduled(every = "${eclipse.git-eca.tasks.gh-revalidation.frequency:60s}")
-    @ActivateRequestContext
-    public void revalidate() {
-        // if not enabled, don't process any potentially OOD records
-        if (!Boolean.TRUE.equals(isEnabled.get())) {
-            return;
-        }
-
-        // set up params for looking up the top of the revalidation queue
-        MultivaluedMap<String, String> params = new MultivaluedHashMap<>();
-        params.add(GitEcaParameterNames.NEEDS_REVALIDATION_RAW, "true");
-        params.add(PersistenceUrlParameterNames.SORT.getName(), "lastUpdated");
-        params.add(DefaultUrlParameterNames.PAGESIZE.getName(), "1");
-
-        // build the request and query to lookup the longest standing request that needs revalidation
-        RequestWrapper wrap = new FlatRequestWrapper(URI.create("https://api.eclipse.org/git/eca/revalidation-queue"));
-        RDBMSQuery<GithubWebhookTracking> trackingQuery = new RDBMSQuery<>(wrap, filters.get(GithubWebhookTracking.class), params);
-
-        List<GithubWebhookTracking> oldestRevalidation = dao.get(trackingQuery);
-        if (oldestRevalidation.isEmpty()) {
-            LOGGER.debug("No queued revalidation requests found");
-        } else {
-            reprocessRequest(oldestRevalidation.get(0), wrap);
-        }
+    // set up params for looking up the top of the revalidation queue
+    MultivaluedMap<String, String> params = new MultivaluedHashMap<>();
+    params.add(GitEcaParameterNames.NEEDS_REVALIDATION_RAW, "true");
+    params.add(PersistenceUrlParameterNames.SORT.getName(), "lastUpdated");
+    params.add(DefaultUrlParameterNames.PAGESIZE.getName(), "1");
+
+    // build the request and query to lookup the longest standing request that needs
+    // revalidation
+    RequestWrapper wrap = new FlatRequestWrapper(URI.create("https://api.eclipse.org/git/eca/revalidation-queue"));
+    RDBMSQuery<GithubWebhookTracking> trackingQuery = new RDBMSQuery<>(wrap, filters.get(GithubWebhookTracking.class),
+        params);
+
+    List<GithubWebhookTracking> oldestRevalidation = dao.get(trackingQuery);
+    if (oldestRevalidation.isEmpty()) {
+      LOGGER.debug("No queued revalidation requests found");
+    } else {
+      reprocessRequest(oldestRevalidation.get(0), wrap);
+    }
+  }
+
+  /**
+   * Reprocess the given record, attempting to run the ECA validation logic again.
+   * If it passes, the revalidation flag is
+   * set to false and the time code is updated. If the processing fails again, the
+   * failure count gets incremented and the
+   * updated time is set so that another entry can be updated, as to not block on
+   * potentially broken records.
+   * 
+   * @param requestToRevalidate the webhook tracking request to attempt to
+   *                            revalidate
+   * @param wrap                the current stubbed request wrapper used for
+   *                            queries.
+   */
+  private void reprocessRequest(GithubWebhookTracking requestToRevalidate, RequestWrapper wrap) {
+    LOGGER
+        .debug("Attempting revalidation of request w/ ID {}, in repo {}#{}", requestToRevalidate.getId(),
+            requestToRevalidate.getRepositoryFullName(), requestToRevalidate.getPullRequestNumber());
+
+    // update the number of times this status has revalidated (tracking)
+    requestToRevalidate
+        .setManualRevalidationCount(requestToRevalidate.getManualRevalidationCount() == null ? 1
+            : requestToRevalidate.getManualRevalidationCount() + 1);
+
+    // Check if notification threshold is reached and send email if needed
+    checkRevalidationThreshold(requestToRevalidate);
+
+    // wrap in try-catch to avoid errors from stopping the record updates
+    try {
+      // split the full repo name into the org and repo name
+      String[] repoFullNameParts = requestToRevalidate.getRepositoryFullName().split("/");
+      if (repoFullNameParts.length != FULL_REPO_NAME_PARTS) {
+        throw new IllegalStateException("Record with ID '" + Long.toString(requestToRevalidate.getId())
+            + "' is in an invalid state (repository full name is not valid)");
+      }
+
+      // run the validation and then check if it succeeded. Use the forced flag since
+      // we want to try even if there are no
+      // changes
+      validationHelper
+          .validateIncomingRequest(wrap, repoFullNameParts[0], repoFullNameParts[1],
+              requestToRevalidate.getPullRequestNumber(),
+              true);
+      // if we have gotten here, then the validation has completed and can be removed
+      // from queue
+      requestToRevalidate.setNeedsRevalidation(false);
+      LOGGER.debug("Sucessfully revalidated request w/ ID {}", requestToRevalidate.getId());
+    } catch (RuntimeException e) {
+      // log the message so we can see what happened
+      LOGGER.error("Error while revalidating request w/ ID {}", requestToRevalidate.getId(), e);
+    } finally {
+      // whether successful or failed, update the updated time field
+      requestToRevalidate.setLastUpdated(DateTimeHelper.now());
+
+      // if in a closed state, we shouldn't try to revalidate again as it will never
+      // be valid
+      // if a PR is reopened, it should create a new event and validate anyways
+      if ("closed".equalsIgnoreCase(requestToRevalidate.getLastKnownState())) {
+        LOGGER.debug("Tracking request {} set to not revalidate as it was closed", requestToRevalidate.getId());
+        requestToRevalidate.setNeedsRevalidation(false);
+      }
+      // push the update with the potentially updated validation flag or error count
+      dao.add(new RDBMSQuery<>(wrap, filters.get(GithubWebhookTracking.class)), Arrays.asList(requestToRevalidate));
+    }
+  }
+
+  /**
+   * Checks if the manual revalidation count has reached the configured threshold
+   * and sends an email notification if it has.
+   * 
+   * @param tracking the webhook tracking object
+   */
+  private void checkRevalidationThreshold(GithubWebhookTracking tracking) {
+    Integer threshold = revalidationThreshold.get();
+
+    // If manual revalidation count is null or less than threshold, do nothing
+    if (Objects.isNull(tracking.getManualRevalidationCount()) ||
+        tracking.getManualRevalidationCount() < threshold) {
+      return;
     }
 
-    /**
-     * Reprocess the given record, attempting to run the ECA validation logic again. If it passes, the revalidation flag is
-     * set to false and the time code is updated. If the processing fails again, the failure count gets incremented and the
-     * updated time is set so that another entry can be updated, as to not block on potentially broken records.
-     * 
-     * @param requestToRevalidate the webhook tracking request to attempt to revalidate
-     * @param wrap the current stubbed request wrapper used for queries.
-     */
-    private void reprocessRequest(GithubWebhookTracking requestToRevalidate, RequestWrapper wrap) {
-        LOGGER
-                .debug("Attempting revalidation of request w/ ID {}, in repo {}#{}", requestToRevalidate.getId(),
-                        requestToRevalidate.getRepositoryFullName(), requestToRevalidate.getPullRequestNumber());
-
-        // update the number of times this status has revalidated (tracking)
-        requestToRevalidate
-                .setManualRevalidationCount(requestToRevalidate.getManualRevalidationCount() == null ? 1
-                        : requestToRevalidate.getManualRevalidationCount() + 1);
-        // wrap in try-catch to avoid errors from stopping the record updates
-        try {
-            // split the full repo name into the org and repo name
-            String[] repoFullNameParts = requestToRevalidate.getRepositoryFullName().split("/");
-            if (repoFullNameParts.length != FULL_REPO_NAME_PARTS) {
-                throw new IllegalStateException("Record with ID '" + Long.toString(requestToRevalidate.getId())
-                        + "' is in an invalid state (repository full name is not valid)");
-            }
-
-            // run the validation and then check if it succeeded. Use the forced flag since we want to try even if there are no
-            // changes
-            validationHelper
-                    .validateIncomingRequest(wrap, repoFullNameParts[0], repoFullNameParts[1], requestToRevalidate.getPullRequestNumber(),
-                            true);
-            // if we have gotten here, then the validation has completed and can be removed from queue
-            requestToRevalidate.setNeedsRevalidation(false);
-            LOGGER.debug("Sucessfully revalidated request w/ ID {}", requestToRevalidate.getId());
-        } catch (RuntimeException e) {
-            // log the message so we can see what happened
-            LOGGER.error("Error while revalidating request w/ ID {}", requestToRevalidate.getId(), e);
-        } finally {
-            // whether successful or failed, update the updated time field
-            requestToRevalidate.setLastUpdated(DateTimeHelper.now());
-
-            // if in a closed state, we shouldn't try to revalidate again as it will never be valid
-            // if a PR is reopened, it should create a new event and validate anyways
-            if ("closed".equalsIgnoreCase(requestToRevalidate.getLastKnownState())) {
-                LOGGER.debug("Tracking request {} set to not revalidate as it was closed", requestToRevalidate.getId());
-                requestToRevalidate.setNeedsRevalidation(false);
-            }
-            // push the update with the potentially updated validation flag or error count
-            dao.add(new RDBMSQuery<>(wrap, filters.get(GithubWebhookTracking.class)), Arrays.asList(requestToRevalidate));
-        }
+    // Check if count equals threshold (to send notification only once when threshold is reached)
+    if (tracking.getManualRevalidationCount() == threshold) {
+      sendRevalidationAlert(tracking);
+    }
+  }
+
+  /**
+   * Sends an email alert about a webhook that has reached the revalidation
+   * threshold.
+   * 
+   * @param tracking the webhook tracking object
+   */
+  private void sendRevalidationAlert(GithubWebhookTracking tracking) {
+    try {
+      mailerService.sendRevalidationAlert(tracking, revalidationThreshold.get());
+      LOGGER.info("Sent revalidation alert email for request ID {}", tracking.getId());
+    } catch (RuntimeException e) {
+      LOGGER.error("Failed to send revalidation alert email for request ID {}", tracking.getId(), e);
     }
+  }
 }
diff --git a/src/main/resources/templates/emails/revalidation_alert.txt b/src/main/resources/templates/emails/revalidation_alert.txt
new file mode 100644
index 00000000..de4e2fdd
--- /dev/null
+++ b/src/main/resources/templates/emails/revalidation_alert.txt
@@ -0,0 +1,10 @@
+A GitHub webhook has reached the revalidation threshold of {attempts} attempts.
+
+Details:
+- Request ID: {requestId}
+- Repository: {repository}
+- Pull Request: #{pullRequest}
+- Last State: {lastState}
+- Last Updated: {lastUpdated}
+
+Please investigate this issue as it may indicate a persistent problem.
\ No newline at end of file
diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties
index 44c99d18..3c69227e 100644
--- a/src/test/resources/application.properties
+++ b/src/test/resources/application.properties
@@ -23,6 +23,14 @@ quarkus.keycloak.devservices.enabled=false
 quarkus.oidc-client.enabled=false
 smallrye.jwt.sign.key.location=test.pem
 
+# Quarkus Mailer
+quarkus.mailer.auth-methods=DIGEST-MD5 CRAM-SHA256 CRAM-SHA1 CRAM-MD5 PLAIN LOGIN
+quarkus.mailer.from=membership.coordination@eclipse-foundation.org
+# Uses gmail by default, can be overridden
+quarkus.mailer.host=smtp.gmail.com
+quarkus.mailer.port=465
+quarkus.mailer.ssl=true
+
 # hCaptcha test key and secret
 eclipse.hcaptcha.site-key=20000000-ffff-ffff-ffff-000000000002
 eclipse.hcaptcha.secret=0x0000000000000000000000000000000000000000
@@ -35,9 +43,10 @@ eclipse.git-eca.mail.allow-list=noreply@github.com
 eclipse.git-eca.reports.access-key=samplekey
 eclipse.git-eca.tasks.gh-revalidation.enabled=false
 eclipse.git-eca.tasks.gh-installation.enabled=false
+eclipse.git-eca.tasks.gh-revalidation.notification-threshold=100
 
 ## Misc
 eclipse.gitlab.access-token=token_val
 
 ## Disable private project scan in test mode
-eclipse.scheduled.private-project.enabled=false
\ No newline at end of file
+eclipse.scheduled.private-project.enabled=false
-- 
GitLab


From 7f09f19e3dcd0d00d4682598fe11a2e3085570f4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jordi=20G=C3=B3mez?= <jordi.gomez@eclipse-foundation.org>
Date: Tue, 8 Apr 2025 13:25:39 +0200
Subject: [PATCH 02/10] refactor: moving MailerConfig to its standalone class

---
 .../service/impl/DefaultMailerService.java    | 33 -----------------
 .../eca/service/impl/EclipseMailerConfig.java | 35 +++++++++++++++++++
 2 files changed, 35 insertions(+), 33 deletions(-)
 create mode 100644 src/main/java/org/eclipsefoundation/git/eca/service/impl/EclipseMailerConfig.java

diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultMailerService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultMailerService.java
index 48922653..4adde3eb 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultMailerService.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultMailerService.java
@@ -11,9 +11,6 @@
  */
 package org.eclipsefoundation.git.eca.service.impl;
 
-import java.util.List;
-import java.util.Optional;
-
 import org.eclipsefoundation.git.eca.dto.GithubWebhookTracking;
 import org.eclipsefoundation.git.eca.service.MailerService;
 import org.slf4j.Logger;
@@ -23,7 +20,6 @@ import io.quarkus.mailer.Mail;
 import io.quarkus.mailer.Mailer;
 import io.quarkus.qute.Location;
 import io.quarkus.qute.Template;
-import io.smallrye.config.ConfigMapping;
 import jakarta.enterprise.context.ApplicationScoped;
 
 @ApplicationScoped
@@ -72,33 +68,4 @@ public class DefaultMailerService implements MailerService {
     LOGGER.info("Revalidation alert sent to: {}", revalidationAlertConfig.to());
   }
 
-  /**
-   * Represents configuration for the default mailer service.
-   * 
-   * @author Martin Lowe
-   *
-   */
-  @ConfigMapping(prefix = "eclipse.mailer")
-  public interface EclipseMailerConfig {
-
-    public RevalidationAlert revalidationAlert();
-
-    /**
-     * This interface defines the contract for specifying recipients and message
-     * configuration when sending revalidation alerts within the ECA validation
-     * process.
-     */
-    public interface RevalidationAlert {
-      public List<String> to();
-
-      public MessageConfiguration authorMessage();
-    }
-
-    public interface MessageConfiguration {
-      public Optional<String> replyTo();
-
-      public Optional<List<String>> bcc();
-    }
-  }
-
 }
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/EclipseMailerConfig.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/EclipseMailerConfig.java
new file mode 100644
index 00000000..c90ffc87
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/EclipseMailerConfig.java
@@ -0,0 +1,35 @@
+package org.eclipsefoundation.git.eca.service.impl;
+
+import java.util.List;
+import java.util.Optional;
+
+import io.smallrye.config.ConfigMapping;
+
+/**
+ * Represents configuration for the default mailer service.
+ * 
+ * @author Martin Lowe
+ *
+ */
+@ConfigMapping(prefix = "eclipse.mailer")
+public interface EclipseMailerConfig {
+
+  public RevalidationAlert revalidationAlert();
+
+  /**
+   * This interface defines the contract for specifying recipients and message
+   * configuration when sending revalidation alerts within the ECA validation
+   * process.
+   */
+  public interface RevalidationAlert {
+    public List<String> to();
+
+    public MessageConfiguration authorMessage();
+  }
+
+  public interface MessageConfiguration {
+    public Optional<String> replyTo();
+
+    public Optional<List<String>> bcc();
+  }
+}
\ No newline at end of file
-- 
GitLab


From 888f4eac91d83d800ea3cbf54b3090c3fc7dd2ae Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jordi=20G=C3=B3mez?= <jordi.gomez@eclipse-foundation.org>
Date: Tue, 8 Apr 2025 13:26:52 +0200
Subject: [PATCH 03/10] docs: rewording wrongly defined comments

---
 .../git/eca/service/MailerService.java               | 12 +++++-------
 1 file changed, 5 insertions(+), 7 deletions(-)

diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/MailerService.java b/src/main/java/org/eclipsefoundation/git/eca/service/MailerService.java
index fc3d65f0..489ff1a5 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/service/MailerService.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/service/MailerService.java
@@ -12,16 +12,14 @@ package org.eclipsefoundation.git.eca.service;
 import org.eclipsefoundation.git.eca.dto.GithubWebhookTracking;
 
 /**
- * A service for sending email notifications related to GitHub webhook events.
- * This interface defines methods for sending different types of email alerts
- * to appropriate parties based on webhook tracking data.
+ * Service responsible for sending email notifications.
  */
 public interface MailerService {
   /**
-   * Sends a revalidation alert for a tracked GitHub webhook event.
-   * This method is responsible for notifying relevant parties about the need to revalidate a webhook tracking event.
-   *
-   * @param tracking the GitHub webhook tracking information containing details about the event that needs revalidation
+   * Notifies a configured recipient about an stuck revalidation request.
+   * 
+   * @param tracking the GithubWebhookTracking object containing information about the request 
+   * @param threshold the used threshold for the revalidation alert
    */
   void sendRevalidationAlert(GithubWebhookTracking tracking, Integer threshold);
 }
-- 
GitLab


From de863da24e4b20739ece775007556620648eab6f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jordi=20G=C3=B3mez?= <jordi.gomez@eclipse-foundation.org>
Date: Tue, 8 Apr 2025 13:30:26 +0200
Subject: [PATCH 04/10] feat: adding footer indicating is an automated email

---
 src/main/resources/templates/emails/revalidation_alert.txt | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/main/resources/templates/emails/revalidation_alert.txt b/src/main/resources/templates/emails/revalidation_alert.txt
index de4e2fdd..bab58a52 100644
--- a/src/main/resources/templates/emails/revalidation_alert.txt
+++ b/src/main/resources/templates/emails/revalidation_alert.txt
@@ -7,4 +7,7 @@ Details:
 - Last State: {lastState}
 - Last Updated: {lastUpdated}
 
-Please investigate this issue as it may indicate a persistent problem.
\ No newline at end of file
+Please investigate this issue as it may indicate a persistent problem.
+
+-------------------------------------------------------------------------------------------
+This is an automated email from api.eclipse.org/git/eca. Please do not reply to this email.
\ No newline at end of file
-- 
GitLab


From 4c34713555f952492b1b563948bf71c612823a6c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jordi=20G=C3=B3mez?= <jordi.gomez@eclipse-foundation.org>
Date: Tue, 8 Apr 2025 13:31:21 +0200
Subject: [PATCH 05/10] feat: adding option to disable revalidation alert

---
 .../eca/tasks/GithubRevalidationQueue.java    | 101 +++++++++---------
 1 file changed, 52 insertions(+), 49 deletions(-)

diff --git a/src/main/java/org/eclipsefoundation/git/eca/tasks/GithubRevalidationQueue.java b/src/main/java/org/eclipsefoundation/git/eca/tasks/GithubRevalidationQueue.java
index fc64cae3..2c3b3b2a 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/tasks/GithubRevalidationQueue.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/tasks/GithubRevalidationQueue.java
@@ -1,9 +1,8 @@
 /**
  * Copyright (c) 2023 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/
+ * 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/
  *
  * Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
  *
@@ -42,15 +41,12 @@ import jakarta.ws.rs.core.MultivaluedHashMap;
 import jakarta.ws.rs.core.MultivaluedMap;
 
 /**
- * Scheduled regular task that will interact with the backend persistence to
- * look for requests that are in a
- * failed/unvalidated state after an error while processing that could not be
- * recovered. These requests will be
- * reprocessed using the same logic as the standard validation, updating the
- * timestamp on completion and either setting
- * the revalidation flag to false or incrementing the number of repeated
- * revalidations needed for the request for
- * tracking, depending on the succcess of the revalidation.
+ * Scheduled regular task that will interact with the backend persistence to look for requests that
+ * are in a failed/unvalidated state after an error while processing that could not be recovered.
+ * These requests will be reprocessed using the same logic as the standard validation, updating the
+ * timestamp on completion and either setting the revalidation flag to false or incrementing the
+ * number of repeated revalidations needed for the request for tracking, depending on the succcess
+ * of the revalidation.
  */
 @ApplicationScoped
 public class GithubRevalidationQueue {
@@ -62,7 +58,12 @@ public class GithubRevalidationQueue {
   @ConfigProperty(name = "eclipse.git-eca.tasks.gh-revalidation.enabled", defaultValue = "true")
   Instance<Boolean> isEnabled;
 
-  @ConfigProperty(name = "eclipse.git-eca.tasks.gh-revalidation.notification-threshold", defaultValue = "3")
+  @ConfigProperty(name = "eclipse.git-eca.tasks.gh-revalidation.notification-enabled",
+      defaultValue = "true")
+  Instance<Boolean> isRevalidationAlertEnabled;
+
+  @ConfigProperty(name = "eclipse.git-eca.tasks.gh-revalidation.notification-threshold",
+      defaultValue = "100")
   Instance<Integer> revalidationThreshold;
 
   @Inject
@@ -77,14 +78,14 @@ public class GithubRevalidationQueue {
   @PostConstruct
   void init() {
     // indicate to log whether enabled to reduce log spam
-    LOGGER.info("Github revalidation queue task is{} enabled.", Boolean.TRUE.equals(isEnabled.get()) ? "" : " not");
+    LOGGER.info("Github revalidation queue task is{} enabled.",
+        Boolean.TRUE.equals(isEnabled.get()) ? "" : " not");
   }
 
   /**
-   * Every 5s, this method will attempt to load a Github webhook validation
-   * request that has the needs revalidation flag
-   * set to true. This will retrieve the oldest request in queue and will attempt
-   * to revalidate it.
+   * Every 5s, this method will attempt to load a Github webhook validation request that has the
+   * needs revalidation flag set to true. This will retrieve the oldest request in queue and will
+   * attempt to revalidate it.
    */
   @Scheduled(every = "${eclipse.git-eca.tasks.gh-revalidation.frequency:60s}")
   @ActivateRequestContext
@@ -102,9 +103,10 @@ public class GithubRevalidationQueue {
 
     // build the request and query to lookup the longest standing request that needs
     // revalidation
-    RequestWrapper wrap = new FlatRequestWrapper(URI.create("https://api.eclipse.org/git/eca/revalidation-queue"));
-    RDBMSQuery<GithubWebhookTracking> trackingQuery = new RDBMSQuery<>(wrap, filters.get(GithubWebhookTracking.class),
-        params);
+    RequestWrapper wrap =
+        new FlatRequestWrapper(URI.create("https://api.eclipse.org/git/eca/revalidation-queue"));
+    RDBMSQuery<GithubWebhookTracking> trackingQuery =
+        new RDBMSQuery<>(wrap, filters.get(GithubWebhookTracking.class), params);
 
     List<GithubWebhookTracking> oldestRevalidation = dao.get(trackingQuery);
     if (oldestRevalidation.isEmpty()) {
@@ -115,22 +117,18 @@ public class GithubRevalidationQueue {
   }
 
   /**
-   * Reprocess the given record, attempting to run the ECA validation logic again.
-   * If it passes, the revalidation flag is
-   * set to false and the time code is updated. If the processing fails again, the
-   * failure count gets incremented and the
-   * updated time is set so that another entry can be updated, as to not block on
-   * potentially broken records.
+   * Reprocess the given record, attempting to run the ECA validation logic again. If it passes, the
+   * revalidation flag is set to false and the time code is updated. If the processing fails again,
+   * the failure count gets incremented and the updated time is set so that another entry can be
+   * updated, as to not block on potentially broken records.
    * 
-   * @param requestToRevalidate the webhook tracking request to attempt to
-   *                            revalidate
-   * @param wrap                the current stubbed request wrapper used for
-   *                            queries.
+   * @param requestToRevalidate the webhook tracking request to attempt to revalidate
+   * @param wrap the current stubbed request wrapper used for queries.
    */
   private void reprocessRequest(GithubWebhookTracking requestToRevalidate, RequestWrapper wrap) {
-    LOGGER
-        .debug("Attempting revalidation of request w/ ID {}, in repo {}#{}", requestToRevalidate.getId(),
-            requestToRevalidate.getRepositoryFullName(), requestToRevalidate.getPullRequestNumber());
+    LOGGER.debug("Attempting revalidation of request w/ ID {}, in repo {}#{}",
+        requestToRevalidate.getId(), requestToRevalidate.getRepositoryFullName(),
+        requestToRevalidate.getPullRequestNumber());
 
     // update the number of times this status has revalidated (tracking)
     requestToRevalidate
@@ -145,17 +143,16 @@ public class GithubRevalidationQueue {
       // split the full repo name into the org and repo name
       String[] repoFullNameParts = requestToRevalidate.getRepositoryFullName().split("/");
       if (repoFullNameParts.length != FULL_REPO_NAME_PARTS) {
-        throw new IllegalStateException("Record with ID '" + Long.toString(requestToRevalidate.getId())
-            + "' is in an invalid state (repository full name is not valid)");
+        throw new IllegalStateException(
+            "Record with ID '" + Long.toString(requestToRevalidate.getId())
+                + "' is in an invalid state (repository full name is not valid)");
       }
 
       // run the validation and then check if it succeeded. Use the forced flag since
       // we want to try even if there are no
       // changes
-      validationHelper
-          .validateIncomingRequest(wrap, repoFullNameParts[0], repoFullNameParts[1],
-              requestToRevalidate.getPullRequestNumber(),
-              true);
+      validationHelper.validateIncomingRequest(wrap, repoFullNameParts[0], repoFullNameParts[1],
+          requestToRevalidate.getPullRequestNumber(), true);
       // if we have gotten here, then the validation has completed and can be removed
       // from queue
       requestToRevalidate.setNeedsRevalidation(false);
@@ -171,26 +168,32 @@ public class GithubRevalidationQueue {
       // be valid
       // if a PR is reopened, it should create a new event and validate anyways
       if ("closed".equalsIgnoreCase(requestToRevalidate.getLastKnownState())) {
-        LOGGER.debug("Tracking request {} set to not revalidate as it was closed", requestToRevalidate.getId());
+        LOGGER.debug("Tracking request {} set to not revalidate as it was closed",
+            requestToRevalidate.getId());
         requestToRevalidate.setNeedsRevalidation(false);
       }
       // push the update with the potentially updated validation flag or error count
-      dao.add(new RDBMSQuery<>(wrap, filters.get(GithubWebhookTracking.class)), Arrays.asList(requestToRevalidate));
+      dao.add(new RDBMSQuery<>(wrap, filters.get(GithubWebhookTracking.class)),
+          Arrays.asList(requestToRevalidate));
     }
   }
 
   /**
-   * Checks if the manual revalidation count has reached the configured threshold
-   * and sends an email notification if it has.
+   * Checks if the manual revalidation count has reached the configured threshold and sends an email
+   * notification if it has and the notification alert is enabled.
    * 
    * @param tracking the webhook tracking object
    */
   private void checkRevalidationThreshold(GithubWebhookTracking tracking) {
+    // If revalidation alert is not enabled, do nothing
+    if (!Boolean.TRUE.equals(isRevalidationAlertEnabled.get()))
+      return;
+
     Integer threshold = revalidationThreshold.get();
 
     // If manual revalidation count is null or less than threshold, do nothing
-    if (Objects.isNull(tracking.getManualRevalidationCount()) ||
-        tracking.getManualRevalidationCount() < threshold) {
+    if (Objects.isNull(tracking.getManualRevalidationCount())
+        || tracking.getManualRevalidationCount() < threshold) {
       return;
     }
 
@@ -201,8 +204,7 @@ public class GithubRevalidationQueue {
   }
 
   /**
-   * Sends an email alert about a webhook that has reached the revalidation
-   * threshold.
+   * Sends an email alert about a webhook that has reached the revalidation threshold.
    * 
    * @param tracking the webhook tracking object
    */
@@ -211,7 +213,8 @@ public class GithubRevalidationQueue {
       mailerService.sendRevalidationAlert(tracking, revalidationThreshold.get());
       LOGGER.info("Sent revalidation alert email for request ID {}", tracking.getId());
     } catch (RuntimeException e) {
-      LOGGER.error("Failed to send revalidation alert email for request ID {}", tracking.getId(), e);
+      LOGGER.error("Failed to send revalidation alert email for request ID {}", tracking.getId(),
+          e);
     }
   }
 }
-- 
GitLab


From de9b5ff990fe68ded41ef6aba8532fc91dbc226a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jordi=20G=C3=B3mez?= <jordi.gomez@eclipse-foundation.org>
Date: Tue, 8 Apr 2025 13:32:13 +0200
Subject: [PATCH 06/10] chore(formatting): updating code formatting

---
 .../git/eca/service/MailerService.java             |  7 +++----
 .../git/eca/service/impl/DefaultMailerService.java | 14 +++++---------
 .../git/eca/service/impl/EclipseMailerConfig.java  |  7 +++----
 3 files changed, 11 insertions(+), 17 deletions(-)

diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/MailerService.java b/src/main/java/org/eclipsefoundation/git/eca/service/MailerService.java
index 489ff1a5..a2dde623 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/service/MailerService.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/service/MailerService.java
@@ -1,9 +1,8 @@
 /**
  * Copyright (c) 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/
+ * 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
  */
@@ -18,7 +17,7 @@ public interface MailerService {
   /**
    * Notifies a configured recipient about an stuck revalidation request.
    * 
-   * @param tracking the GithubWebhookTracking object containing information about the request 
+   * @param tracking the GithubWebhookTracking object containing information about the request
    * @param threshold the used threshold for the revalidation alert
    */
   void sendRevalidationAlert(GithubWebhookTracking tracking, Integer threshold);
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultMailerService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultMailerService.java
index 4adde3eb..2f1ca9a1 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultMailerService.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultMailerService.java
@@ -1,9 +1,8 @@
 /**
  * Copyright (c) 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/
+ * 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/
  *
  * Author: Jordi Gómez <jordi.gomez@eclipse-foundation.org>
  *
@@ -51,16 +50,13 @@ public class DefaultMailerService implements MailerService {
 
     messageBuilder.setSubject(subject);
     messageBuilder.setText(revalidationAlertTemplate.data("attempts", threshold)
-        .data("requestId", tracking.getId())
-        .data("repository", tracking.getRepositoryFullName())
+        .data("requestId", tracking.getId()).data("repository", tracking.getRepositoryFullName())
         .data("pullRequest", tracking.getPullRequestNumber())
         .data("lastState", tracking.getLastKnownState())
-        .data("lastUpdated", tracking.getLastUpdated().toString())
-        .render());
+        .data("lastUpdated", tracking.getLastUpdated().toString()).render());
 
     messageBuilder.addTo(revalidationAlertConfig.to().toArray(String[]::new));
-    revalidationAlertConfig.authorMessage().replyTo()
-        .ifPresent(messageBuilder::addReplyTo);
+    revalidationAlertConfig.authorMessage().replyTo().ifPresent(messageBuilder::addReplyTo);
     revalidationAlertConfig.authorMessage().bcc()
         .ifPresent(bcc -> messageBuilder.addBcc(bcc.toArray(String[]::new)));
 
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/EclipseMailerConfig.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/EclipseMailerConfig.java
index c90ffc87..2a200eff 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/EclipseMailerConfig.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/EclipseMailerConfig.java
@@ -17,9 +17,8 @@ public interface EclipseMailerConfig {
   public RevalidationAlert revalidationAlert();
 
   /**
-   * This interface defines the contract for specifying recipients and message
-   * configuration when sending revalidation alerts within the ECA validation
-   * process.
+   * This interface defines the contract for specifying recipients and message configuration when
+   * sending revalidation alerts within the ECA validation process.
    */
   public interface RevalidationAlert {
     public List<String> to();
@@ -32,4 +31,4 @@ public interface EclipseMailerConfig {
 
     public Optional<List<String>> bcc();
   }
-}
\ No newline at end of file
+}
-- 
GitLab


From d7d9c4a1f11a71894a0463441bc565bd5bde989f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jordi=20G=C3=B3mez?= <jordi.gomez@eclipse-foundation.org>
Date: Tue, 8 Apr 2025 17:39:09 +0200
Subject: [PATCH 07/10] chore: updating application.properties

---
 .../git/eca/service/impl/DefaultMailerService.java             | 2 +-
 src/test/resources/application.properties                      | 3 +++
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultMailerService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultMailerService.java
index 2f1ca9a1..5e378285 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultMailerService.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultMailerService.java
@@ -27,7 +27,7 @@ public class DefaultMailerService implements MailerService {
   private final EclipseMailerConfig config;
   private final Mailer mailer;
 
-  @Location("emails/revalidation-alert")
+  @Location("emails/revalidation_alert")
   Template revalidationAlertTemplate;
 
   public DefaultMailerService(EclipseMailerConfig config, Mailer mailer) {
diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties
index 3c69227e..205ea9ab 100644
--- a/src/test/resources/application.properties
+++ b/src/test/resources/application.properties
@@ -45,6 +45,9 @@ eclipse.git-eca.tasks.gh-revalidation.enabled=false
 eclipse.git-eca.tasks.gh-installation.enabled=false
 eclipse.git-eca.tasks.gh-revalidation.notification-threshold=100
 
+## Revalidation alert email config 
+eclipse.mailer.revalidation-alert.to=webdev@eclipse-foundation.org
+
 ## Misc
 eclipse.gitlab.access-token=token_val
 
-- 
GitLab


From f45eff79ccc88fc499742227b0cf86e0adf37c86 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jordi=20G=C3=B3mez?= <jordi.gomez@eclipse-foundation.org>
Date: Wed, 9 Apr 2025 10:03:28 +0200
Subject: [PATCH 08/10] refactor: moving MailerConfig to the config folder

---
 .../MailerConfig.java}                             | 14 ++++++++++++--
 .../git/eca/service/impl/DefaultMailerService.java |  5 +++--
 2 files changed, 15 insertions(+), 4 deletions(-)
 rename src/main/java/org/eclipsefoundation/git/eca/{service/impl/EclipseMailerConfig.java => config/MailerConfig.java} (63%)

diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/EclipseMailerConfig.java b/src/main/java/org/eclipsefoundation/git/eca/config/MailerConfig.java
similarity index 63%
rename from src/main/java/org/eclipsefoundation/git/eca/service/impl/EclipseMailerConfig.java
rename to src/main/java/org/eclipsefoundation/git/eca/config/MailerConfig.java
index 2a200eff..4b9d7192 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/EclipseMailerConfig.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/config/MailerConfig.java
@@ -1,4 +1,14 @@
-package org.eclipsefoundation.git.eca.service.impl;
+/**
+ * Copyright (c) 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/
+ *
+ * Author: Jordi Gómez <jordi.gomez@eclipse-foundation.org>
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipsefoundation.git.eca.config;
 
 import java.util.List;
 import java.util.Optional;
@@ -12,7 +22,7 @@ import io.smallrye.config.ConfigMapping;
  *
  */
 @ConfigMapping(prefix = "eclipse.mailer")
-public interface EclipseMailerConfig {
+public interface MailerConfig {
 
   public RevalidationAlert revalidationAlert();
 
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultMailerService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultMailerService.java
index 5e378285..6c2a10d5 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultMailerService.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultMailerService.java
@@ -10,6 +10,7 @@
  */
 package org.eclipsefoundation.git.eca.service.impl;
 
+import org.eclipsefoundation.git.eca.config.MailerConfig;
 import org.eclipsefoundation.git.eca.dto.GithubWebhookTracking;
 import org.eclipsefoundation.git.eca.service.MailerService;
 import org.slf4j.Logger;
@@ -24,13 +25,13 @@ import jakarta.enterprise.context.ApplicationScoped;
 @ApplicationScoped
 public class DefaultMailerService implements MailerService {
   public static final Logger LOGGER = LoggerFactory.getLogger(DefaultMailerService.class);
-  private final EclipseMailerConfig config;
+  private final MailerConfig config;
   private final Mailer mailer;
 
   @Location("emails/revalidation_alert")
   Template revalidationAlertTemplate;
 
-  public DefaultMailerService(EclipseMailerConfig config, Mailer mailer) {
+  public DefaultMailerService(MailerConfig config, Mailer mailer) {
     this.config = config;
     this.mailer = mailer;
   }
-- 
GitLab


From 530e453a9e35440178778ace5499d19e24c7f69e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jordi=20G=C3=B3mez?= <jordi.gomez@eclipse-foundation.org>
Date: Thu, 24 Apr 2025 11:13:55 +0200
Subject: [PATCH 09/10] chore: fix formatting

---
 .../git/eca/config/MailerConfig.java          |  26 +-
 .../git/eca/service/MailerService.java        |  14 +-
 .../service/impl/DefaultMailerService.java    |  65 ++--
 .../eca/tasks/GithubRevalidationQueue.java    | 314 +++++++++---------
 4 files changed, 202 insertions(+), 217 deletions(-)

diff --git a/src/main/java/org/eclipsefoundation/git/eca/config/MailerConfig.java b/src/main/java/org/eclipsefoundation/git/eca/config/MailerConfig.java
index 4b9d7192..abf66ffb 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/config/MailerConfig.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/config/MailerConfig.java
@@ -24,21 +24,21 @@ import io.smallrye.config.ConfigMapping;
 @ConfigMapping(prefix = "eclipse.mailer")
 public interface MailerConfig {
 
-  public RevalidationAlert revalidationAlert();
+    public RevalidationAlert revalidationAlert();
 
-  /**
-   * This interface defines the contract for specifying recipients and message configuration when
-   * sending revalidation alerts within the ECA validation process.
-   */
-  public interface RevalidationAlert {
-    public List<String> to();
+    /**
+     * This interface defines the contract for specifying recipients and message configuration when sending revalidation alerts within the
+     * ECA validation process.
+     */
+    public interface RevalidationAlert {
+        public List<String> to();
 
-    public MessageConfiguration authorMessage();
-  }
+        public MessageConfiguration authorMessage();
+    }
 
-  public interface MessageConfiguration {
-    public Optional<String> replyTo();
+    public interface MessageConfiguration {
+        public Optional<String> replyTo();
 
-    public Optional<List<String>> bcc();
-  }
+        public Optional<List<String>> bcc();
+    }
 }
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/MailerService.java b/src/main/java/org/eclipsefoundation/git/eca/service/MailerService.java
index a2dde623..1e307f69 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/service/MailerService.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/service/MailerService.java
@@ -14,11 +14,11 @@ import org.eclipsefoundation.git.eca.dto.GithubWebhookTracking;
  * Service responsible for sending email notifications.
  */
 public interface MailerService {
-  /**
-   * Notifies a configured recipient about an stuck revalidation request.
-   * 
-   * @param tracking the GithubWebhookTracking object containing information about the request
-   * @param threshold the used threshold for the revalidation alert
-   */
-  void sendRevalidationAlert(GithubWebhookTracking tracking, Integer threshold);
+    /**
+     * Notifies a configured recipient about an stuck revalidation request.
+     * 
+     * @param tracking the GithubWebhookTracking object containing information about the request
+     * @param threshold the used threshold for the revalidation alert
+     */
+    void sendRevalidationAlert(GithubWebhookTracking tracking, Integer threshold);
 }
diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultMailerService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultMailerService.java
index 6c2a10d5..5452c6a0 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultMailerService.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultMailerService.java
@@ -24,45 +24,48 @@ import jakarta.enterprise.context.ApplicationScoped;
 
 @ApplicationScoped
 public class DefaultMailerService implements MailerService {
-  public static final Logger LOGGER = LoggerFactory.getLogger(DefaultMailerService.class);
-  private final MailerConfig config;
-  private final Mailer mailer;
+    public static final Logger LOGGER = LoggerFactory.getLogger(DefaultMailerService.class);
+    private final MailerConfig config;
+    private final Mailer mailer;
 
-  @Location("emails/revalidation_alert")
-  Template revalidationAlertTemplate;
+    @Location("emails/revalidation_alert")
+    Template revalidationAlertTemplate;
 
-  public DefaultMailerService(MailerConfig config, Mailer mailer) {
-    this.config = config;
-    this.mailer = mailer;
-  }
+    public DefaultMailerService(MailerConfig config, Mailer mailer) {
+        this.config = config;
+        this.mailer = mailer;
+    }
 
-  @Override
-  public void sendRevalidationAlert(GithubWebhookTracking tracking, Integer threshold) {
-    var revalidationAlertConfig = config.revalidationAlert();
+    @Override
+    public void sendRevalidationAlert(GithubWebhookTracking tracking, Integer threshold) {
+        var revalidationAlertConfig = config.revalidationAlert();
 
-    if (revalidationAlertConfig.to().isEmpty()) {
-      LOGGER.warn("No recipients configured for revalidation alert. Skipping email notification.");
-      return;
-    }
+        if (revalidationAlertConfig.to().isEmpty()) {
+            LOGGER.warn("No recipients configured for revalidation alert. Skipping email notification.");
+            return;
+        }
 
-    String subject = "GitHub Webhook Revalidation Alert";
+        String subject = "GitHub Webhook Revalidation Alert";
 
-    Mail messageBuilder = new Mail();
+        Mail messageBuilder = new Mail();
 
-    messageBuilder.setSubject(subject);
-    messageBuilder.setText(revalidationAlertTemplate.data("attempts", threshold)
-        .data("requestId", tracking.getId()).data("repository", tracking.getRepositoryFullName())
-        .data("pullRequest", tracking.getPullRequestNumber())
-        .data("lastState", tracking.getLastKnownState())
-        .data("lastUpdated", tracking.getLastUpdated().toString()).render());
+        messageBuilder.setSubject(subject);
+        messageBuilder
+                .setText(revalidationAlertTemplate
+                        .data("attempts", threshold)
+                        .data("requestId", tracking.getId())
+                        .data("repository", tracking.getRepositoryFullName())
+                        .data("pullRequest", tracking.getPullRequestNumber())
+                        .data("lastState", tracking.getLastKnownState())
+                        .data("lastUpdated", tracking.getLastUpdated().toString())
+                        .render());
 
-    messageBuilder.addTo(revalidationAlertConfig.to().toArray(String[]::new));
-    revalidationAlertConfig.authorMessage().replyTo().ifPresent(messageBuilder::addReplyTo);
-    revalidationAlertConfig.authorMessage().bcc()
-        .ifPresent(bcc -> messageBuilder.addBcc(bcc.toArray(String[]::new)));
+        messageBuilder.addTo(revalidationAlertConfig.to().toArray(String[]::new));
+        revalidationAlertConfig.authorMessage().replyTo().ifPresent(messageBuilder::addReplyTo);
+        revalidationAlertConfig.authorMessage().bcc().ifPresent(bcc -> messageBuilder.addBcc(bcc.toArray(String[]::new)));
 
-    mailer.send(messageBuilder);
-    LOGGER.info("Revalidation alert sent to: {}", revalidationAlertConfig.to());
-  }
+        mailer.send(messageBuilder);
+        LOGGER.info("Revalidation alert sent to: {}", revalidationAlertConfig.to());
+    }
 
 }
diff --git a/src/main/java/org/eclipsefoundation/git/eca/tasks/GithubRevalidationQueue.java b/src/main/java/org/eclipsefoundation/git/eca/tasks/GithubRevalidationQueue.java
index 2c3b3b2a..7c638ce0 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/tasks/GithubRevalidationQueue.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/tasks/GithubRevalidationQueue.java
@@ -41,180 +41,162 @@ import jakarta.ws.rs.core.MultivaluedHashMap;
 import jakarta.ws.rs.core.MultivaluedMap;
 
 /**
- * Scheduled regular task that will interact with the backend persistence to look for requests that
- * are in a failed/unvalidated state after an error while processing that could not be recovered.
- * These requests will be reprocessed using the same logic as the standard validation, updating the
- * timestamp on completion and either setting the revalidation flag to false or incrementing the
- * number of repeated revalidations needed for the request for tracking, depending on the succcess
- * of the revalidation.
+ * Scheduled regular task that will interact with the backend persistence to look for requests that are in a failed/unvalidated state after
+ * an error while processing that could not be recovered. These requests will be reprocessed using the same logic as the standard
+ * validation, updating the timestamp on completion and either setting the revalidation flag to false or incrementing the number of repeated
+ * revalidations needed for the request for tracking, depending on the succcess of the revalidation.
  */
 @ApplicationScoped
 public class GithubRevalidationQueue {
-  private static final Logger LOGGER = LoggerFactory.getLogger(GithubRevalidationQueue.class);
-
-  // full repo name should be 2 parts, org and actual repo name
-  private static final int FULL_REPO_NAME_PARTS = 2;
-
-  @ConfigProperty(name = "eclipse.git-eca.tasks.gh-revalidation.enabled", defaultValue = "true")
-  Instance<Boolean> isEnabled;
-
-  @ConfigProperty(name = "eclipse.git-eca.tasks.gh-revalidation.notification-enabled",
-      defaultValue = "true")
-  Instance<Boolean> isRevalidationAlertEnabled;
-
-  @ConfigProperty(name = "eclipse.git-eca.tasks.gh-revalidation.notification-threshold",
-      defaultValue = "100")
-  Instance<Integer> revalidationThreshold;
-
-  @Inject
-  PersistenceDao dao;
-  @Inject
-  FilterService filters;
-  @Inject
-  GithubHelper validationHelper;
-  @Inject
-  MailerService mailerService;
-
-  @PostConstruct
-  void init() {
-    // indicate to log whether enabled to reduce log spam
-    LOGGER.info("Github revalidation queue task is{} enabled.",
-        Boolean.TRUE.equals(isEnabled.get()) ? "" : " not");
-  }
-
-  /**
-   * Every 5s, this method will attempt to load a Github webhook validation request that has the
-   * needs revalidation flag set to true. This will retrieve the oldest request in queue and will
-   * attempt to revalidate it.
-   */
-  @Scheduled(every = "${eclipse.git-eca.tasks.gh-revalidation.frequency:60s}")
-  @ActivateRequestContext
-  public void revalidate() {
-    // if not enabled, don't process any potentially OOD records
-    if (!Boolean.TRUE.equals(isEnabled.get())) {
-      return;
-    }
+    private static final Logger LOGGER = LoggerFactory.getLogger(GithubRevalidationQueue.class);
+
+    // full repo name should be 2 parts, org and actual repo name
+    private static final int FULL_REPO_NAME_PARTS = 2;
+
+    @ConfigProperty(name = "eclipse.git-eca.tasks.gh-revalidation.enabled", defaultValue = "true")
+    Instance<Boolean> isEnabled;
+
+    @ConfigProperty(name = "eclipse.git-eca.tasks.gh-revalidation.notification-enabled", defaultValue = "true")
+    Instance<Boolean> isRevalidationAlertEnabled;
+
+    @ConfigProperty(name = "eclipse.git-eca.tasks.gh-revalidation.notification-threshold", defaultValue = "100")
+    Instance<Integer> revalidationThreshold;
 
-    // set up params for looking up the top of the revalidation queue
-    MultivaluedMap<String, String> params = new MultivaluedHashMap<>();
-    params.add(GitEcaParameterNames.NEEDS_REVALIDATION_RAW, "true");
-    params.add(PersistenceUrlParameterNames.SORT.getName(), "lastUpdated");
-    params.add(DefaultUrlParameterNames.PAGESIZE.getName(), "1");
-
-    // build the request and query to lookup the longest standing request that needs
-    // revalidation
-    RequestWrapper wrap =
-        new FlatRequestWrapper(URI.create("https://api.eclipse.org/git/eca/revalidation-queue"));
-    RDBMSQuery<GithubWebhookTracking> trackingQuery =
-        new RDBMSQuery<>(wrap, filters.get(GithubWebhookTracking.class), params);
-
-    List<GithubWebhookTracking> oldestRevalidation = dao.get(trackingQuery);
-    if (oldestRevalidation.isEmpty()) {
-      LOGGER.debug("No queued revalidation requests found");
-    } else {
-      reprocessRequest(oldestRevalidation.get(0), wrap);
+    @Inject
+    PersistenceDao dao;
+    @Inject
+    FilterService filters;
+    @Inject
+    GithubHelper validationHelper;
+    @Inject
+    MailerService mailerService;
+
+    @PostConstruct
+    void init() {
+        // indicate to log whether enabled to reduce log spam
+        LOGGER.info("Github revalidation queue task is{} enabled.", Boolean.TRUE.equals(isEnabled.get()) ? "" : " not");
     }
-  }
-
-  /**
-   * Reprocess the given record, attempting to run the ECA validation logic again. If it passes, the
-   * revalidation flag is set to false and the time code is updated. If the processing fails again,
-   * the failure count gets incremented and the updated time is set so that another entry can be
-   * updated, as to not block on potentially broken records.
-   * 
-   * @param requestToRevalidate the webhook tracking request to attempt to revalidate
-   * @param wrap the current stubbed request wrapper used for queries.
-   */
-  private void reprocessRequest(GithubWebhookTracking requestToRevalidate, RequestWrapper wrap) {
-    LOGGER.debug("Attempting revalidation of request w/ ID {}, in repo {}#{}",
-        requestToRevalidate.getId(), requestToRevalidate.getRepositoryFullName(),
-        requestToRevalidate.getPullRequestNumber());
-
-    // update the number of times this status has revalidated (tracking)
-    requestToRevalidate
-        .setManualRevalidationCount(requestToRevalidate.getManualRevalidationCount() == null ? 1
-            : requestToRevalidate.getManualRevalidationCount() + 1);
-
-    // Check if notification threshold is reached and send email if needed
-    checkRevalidationThreshold(requestToRevalidate);
-
-    // wrap in try-catch to avoid errors from stopping the record updates
-    try {
-      // split the full repo name into the org and repo name
-      String[] repoFullNameParts = requestToRevalidate.getRepositoryFullName().split("/");
-      if (repoFullNameParts.length != FULL_REPO_NAME_PARTS) {
-        throw new IllegalStateException(
-            "Record with ID '" + Long.toString(requestToRevalidate.getId())
-                + "' is in an invalid state (repository full name is not valid)");
-      }
-
-      // run the validation and then check if it succeeded. Use the forced flag since
-      // we want to try even if there are no
-      // changes
-      validationHelper.validateIncomingRequest(wrap, repoFullNameParts[0], repoFullNameParts[1],
-          requestToRevalidate.getPullRequestNumber(), true);
-      // if we have gotten here, then the validation has completed and can be removed
-      // from queue
-      requestToRevalidate.setNeedsRevalidation(false);
-      LOGGER.debug("Sucessfully revalidated request w/ ID {}", requestToRevalidate.getId());
-    } catch (RuntimeException e) {
-      // log the message so we can see what happened
-      LOGGER.error("Error while revalidating request w/ ID {}", requestToRevalidate.getId(), e);
-    } finally {
-      // whether successful or failed, update the updated time field
-      requestToRevalidate.setLastUpdated(DateTimeHelper.now());
-
-      // if in a closed state, we shouldn't try to revalidate again as it will never
-      // be valid
-      // if a PR is reopened, it should create a new event and validate anyways
-      if ("closed".equalsIgnoreCase(requestToRevalidate.getLastKnownState())) {
-        LOGGER.debug("Tracking request {} set to not revalidate as it was closed",
-            requestToRevalidate.getId());
-        requestToRevalidate.setNeedsRevalidation(false);
-      }
-      // push the update with the potentially updated validation flag or error count
-      dao.add(new RDBMSQuery<>(wrap, filters.get(GithubWebhookTracking.class)),
-          Arrays.asList(requestToRevalidate));
+
+    /**
+     * Every 5s, this method will attempt to load a Github webhook validation request that has the needs revalidation flag set to true. This
+     * will retrieve the oldest request in queue and will attempt to revalidate it.
+     */
+    @Scheduled(every = "${eclipse.git-eca.tasks.gh-revalidation.frequency:60s}")
+    @ActivateRequestContext
+    public void revalidate() {
+        // if not enabled, don't process any potentially OOD records
+        if (!Boolean.TRUE.equals(isEnabled.get())) {
+            return;
+        }
+
+        // set up params for looking up the top of the revalidation queue
+        MultivaluedMap<String, String> params = new MultivaluedHashMap<>();
+        params.add(GitEcaParameterNames.NEEDS_REVALIDATION_RAW, "true");
+        params.add(PersistenceUrlParameterNames.SORT.getName(), "lastUpdated");
+        params.add(DefaultUrlParameterNames.PAGESIZE.getName(), "1");
+
+        // build the request and query to lookup the longest standing request that needs revalidation
+        RequestWrapper wrap = new FlatRequestWrapper(URI.create("https://api.eclipse.org/git/eca/revalidation-queue"));
+        RDBMSQuery<GithubWebhookTracking> trackingQuery = new RDBMSQuery<>(wrap, filters.get(GithubWebhookTracking.class), params);
+
+        List<GithubWebhookTracking> oldestRevalidation = dao.get(trackingQuery);
+        if (oldestRevalidation.isEmpty()) {
+            LOGGER.debug("No queued revalidation requests found");
+        } else {
+            reprocessRequest(oldestRevalidation.get(0), wrap);
+        }
     }
-  }
-
-  /**
-   * Checks if the manual revalidation count has reached the configured threshold and sends an email
-   * notification if it has and the notification alert is enabled.
-   * 
-   * @param tracking the webhook tracking object
-   */
-  private void checkRevalidationThreshold(GithubWebhookTracking tracking) {
-    // If revalidation alert is not enabled, do nothing
-    if (!Boolean.TRUE.equals(isRevalidationAlertEnabled.get()))
-      return;
-
-    Integer threshold = revalidationThreshold.get();
-
-    // If manual revalidation count is null or less than threshold, do nothing
-    if (Objects.isNull(tracking.getManualRevalidationCount())
-        || tracking.getManualRevalidationCount() < threshold) {
-      return;
+
+    /**
+     * Reprocess the given record, attempting to run the ECA validation logic again. If it passes, the revalidation flag is set to false and
+     * the time code is updated. If the processing fails again, the failure count gets incremented and the updated time is set so that
+     * another entry can be updated, as to not block on potentially broken records.
+     * 
+     * @param requestToRevalidate the webhook tracking request to attempt to revalidate
+     * @param wrap the current stubbed request wrapper used for queries.
+     */
+    private void reprocessRequest(GithubWebhookTracking requestToRevalidate, RequestWrapper wrap) {
+        LOGGER
+                .debug("Attempting revalidation of request w/ ID {}, in repo {}#{}", requestToRevalidate.getId(),
+                        requestToRevalidate.getRepositoryFullName(), requestToRevalidate.getPullRequestNumber());
+
+        // update the number of times this status has revalidated (tracking)
+        requestToRevalidate
+                .setManualRevalidationCount(requestToRevalidate.getManualRevalidationCount() == null ? 1
+                        : requestToRevalidate.getManualRevalidationCount() + 1);
+
+        // Check if notification threshold is reached and send email if needed
+        checkRevalidationThreshold(requestToRevalidate);
+
+        // wrap in try-catch to avoid errors from stopping the record updates
+        try {
+            // split the full repo name into the org and repo name
+            String[] repoFullNameParts = requestToRevalidate.getRepositoryFullName().split("/");
+            if (repoFullNameParts.length != FULL_REPO_NAME_PARTS) {
+                throw new IllegalStateException("Record with ID '" + Long.toString(requestToRevalidate.getId())
+                        + "' is in an invalid state (repository full name is not valid)");
+            }
+
+            // run the validation and then check if it succeeded. Use the forced flag since we want to try even if there are no changes
+            validationHelper
+                    .validateIncomingRequest(wrap, repoFullNameParts[0], repoFullNameParts[1], requestToRevalidate.getPullRequestNumber(),
+                            true);
+            // if we have gotten here, then the validation has completed and can be removed from queue
+            requestToRevalidate.setNeedsRevalidation(false);
+            LOGGER.debug("Sucessfully revalidated request w/ ID {}", requestToRevalidate.getId());
+        } catch (RuntimeException e) {
+            // log the message so we can see what happened
+            LOGGER.error("Error while revalidating request w/ ID {}", requestToRevalidate.getId(), e);
+        } finally {
+            // whether successful or failed, update the updated time field
+            requestToRevalidate.setLastUpdated(DateTimeHelper.now());
+
+            // if in a closed state, we shouldn't try to revalidate again as it will never be valid
+            // if a PR is reopened, it should create a new event and validate anyways
+            if ("closed".equalsIgnoreCase(requestToRevalidate.getLastKnownState())) {
+                LOGGER.debug("Tracking request {} set to not revalidate as it was closed", requestToRevalidate.getId());
+                requestToRevalidate.setNeedsRevalidation(false);
+            }
+            // push the update with the potentially updated validation flag or error count
+            dao.add(new RDBMSQuery<>(wrap, filters.get(GithubWebhookTracking.class)), Arrays.asList(requestToRevalidate));
+        }
     }
 
-    // Check if count equals threshold (to send notification only once when threshold is reached)
-    if (tracking.getManualRevalidationCount() == threshold) {
-      sendRevalidationAlert(tracking);
+    /**
+     * Checks if the manual revalidation count has reached the configured threshold and sends an email notification if it has and the
+     * notification alert is enabled.
+     * 
+     * @param tracking the webhook tracking object
+     */
+    private void checkRevalidationThreshold(GithubWebhookTracking tracking) {
+        // If revalidation alert is not enabled, do nothing
+        if (!Boolean.TRUE.equals(isRevalidationAlertEnabled.get()))
+            return;
+
+        Integer threshold = revalidationThreshold.get();
+
+        // If manual revalidation count is null or less than threshold, do nothing
+        if (Objects.isNull(tracking.getManualRevalidationCount()) || tracking.getManualRevalidationCount() < threshold) {
+            return;
+        }
+
+        // Check if count equals threshold (to send notification only once when threshold is reached)
+        if (tracking.getManualRevalidationCount() == threshold) {
+            sendRevalidationAlert(tracking);
+        }
     }
-  }
-
-  /**
-   * Sends an email alert about a webhook that has reached the revalidation threshold.
-   * 
-   * @param tracking the webhook tracking object
-   */
-  private void sendRevalidationAlert(GithubWebhookTracking tracking) {
-    try {
-      mailerService.sendRevalidationAlert(tracking, revalidationThreshold.get());
-      LOGGER.info("Sent revalidation alert email for request ID {}", tracking.getId());
-    } catch (RuntimeException e) {
-      LOGGER.error("Failed to send revalidation alert email for request ID {}", tracking.getId(),
-          e);
+
+    /**
+     * Sends an email alert about a webhook that has reached the revalidation threshold.
+     * 
+     * @param tracking the webhook tracking object
+     */
+    private void sendRevalidationAlert(GithubWebhookTracking tracking) {
+        try {
+            mailerService.sendRevalidationAlert(tracking, revalidationThreshold.get());
+            LOGGER.info("Sent revalidation alert email for request ID {}", tracking.getId());
+        } catch (RuntimeException e) {
+            LOGGER.error("Failed to send revalidation alert email for request ID {}", tracking.getId(), e);
+        }
     }
-  }
 }
-- 
GitLab


From 92d5e711aa4cceedcc0e87f80d4ed43ec78b96c6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jordi=20G=C3=B3mez?= <jordi.gomez@eclipse-foundation.org>
Date: Mon, 28 Apr 2025 11:13:54 +0200
Subject: [PATCH 10/10] chore(config): updating mailer config & secrets sample

---
 config/application/secret.properties.sample |  6 +++++-
 src/main/resources/application.properties   | 12 ++++++++++++
 src/test/resources/application.properties   | 11 +----------
 3 files changed, 18 insertions(+), 11 deletions(-)

diff --git a/config/application/secret.properties.sample b/config/application/secret.properties.sample
index 4de58a1f..99f4d517 100644
--- a/config/application/secret.properties.sample
+++ b/config/application/secret.properties.sample
@@ -12,4 +12,8 @@ quarkus.datasource.password=
 quarkus.datasource.jdbc.url=jdbc:mariadb://mariadb/dev_eclipse_eca
 %dev.quarkus.datasource.jdbc.url=jdbc:mariadb://${eclipse.internal-host}:10101/dev_eclipse_eca
 
-eclipse.gitlab.access-token=
\ No newline at end of file
+eclipse.gitlab.access-token=
+
+## Used to send mail through the EclipseFdn smtp connection
+quarkus.mailer.password=YOURGENERATEDAPPLICATIONPASSWORD
+quarkus.mailer.username=YOUREMAIL@gmail.com
\ No newline at end of file
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 3f208c28..c4402971 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -65,3 +65,15 @@ eclipse.git-eca.reports.allowed-users=mbarbaro,webdev
 
 ## Misc
 eclipse.system-hook.pool-size=5
+
+## Revalidation alert email config 
+eclipse.git-eca.tasks.gh-revalidation.notification-threshold=100
+eclipse.mailer.revalidation-alert.to=webdev@eclipse-foundation.org
+
+# Quarkus Mailer
+quarkus.mailer.auth-methods=DIGEST-MD5 CRAM-SHA256 CRAM-SHA1 CRAM-MD5 PLAIN LOGIN
+quarkus.mailer.from=no-reply@eclipse-foundation.org
+# Uses gmail by default, can be overridden
+quarkus.mailer.host=smtp.gmail.com
+quarkus.mailer.port=465
+quarkus.mailer.ssl=true
diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties
index 205ea9ab..bd6e17d8 100644
--- a/src/test/resources/application.properties
+++ b/src/test/resources/application.properties
@@ -24,12 +24,7 @@ quarkus.oidc-client.enabled=false
 smallrye.jwt.sign.key.location=test.pem
 
 # Quarkus Mailer
-quarkus.mailer.auth-methods=DIGEST-MD5 CRAM-SHA256 CRAM-SHA1 CRAM-MD5 PLAIN LOGIN
-quarkus.mailer.from=membership.coordination@eclipse-foundation.org
-# Uses gmail by default, can be overridden
-quarkus.mailer.host=smtp.gmail.com
-quarkus.mailer.port=465
-quarkus.mailer.ssl=true
+quarkus.mailer.mock=true
 
 # hCaptcha test key and secret
 eclipse.hcaptcha.site-key=20000000-ffff-ffff-ffff-000000000002
@@ -43,10 +38,6 @@ eclipse.git-eca.mail.allow-list=noreply@github.com
 eclipse.git-eca.reports.access-key=samplekey
 eclipse.git-eca.tasks.gh-revalidation.enabled=false
 eclipse.git-eca.tasks.gh-installation.enabled=false
-eclipse.git-eca.tasks.gh-revalidation.notification-threshold=100
-
-## Revalidation alert email config 
-eclipse.mailer.revalidation-alert.to=webdev@eclipse-foundation.org
 
 ## Misc
 eclipse.gitlab.access-token=token_val
-- 
GitLab