]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11753 move "QP change" notification to email specific algo
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Tue, 26 Mar 2019 17:07:51 +0000 (18:07 +0100)
committersonartech <sonartech@sonarsource.com>
Tue, 23 Apr 2019 08:37:54 +0000 (10:37 +0200)
15 files changed:
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/QualityGateEventsStep.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/QualityGateEventsStepTest.java
server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/notification/AlertsEmailTemplate.java [deleted file]
server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/notification/NewAlerts.java [deleted file]
server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/notification/QGChangeEmailTemplate.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/notification/QGChangeNotification.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/notification/QGChangeNotificationHandler.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/notification/AlertsEmailTemplateTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/notification/NewAlertsTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/notification/QGChangeEmailTemplateTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/notification/QGChangeNotificationHandlerTest.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/notification/ws/DispatchersImpl.java
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
server/sonar-server/src/test/java/org/sonar/server/notification/ws/DispatchersImplTest.java

index a19b3c0553c9c77a818c4b997fed3c0ac6e4eaec..1d14912da2d755e5bfd27cea1d50e2fb9f17bbd7 100644 (file)
@@ -22,7 +22,6 @@ package org.sonar.ce.task.projectanalysis.step;
 import java.util.Optional;
 import javax.annotation.Nullable;
 import org.sonar.api.measures.CoreMetrics;
-import org.sonar.api.notifications.Notification;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
 import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
@@ -42,6 +41,9 @@ import org.sonar.ce.task.projectanalysis.metric.Metric;
 import org.sonar.ce.task.projectanalysis.metric.MetricRepository;
 import org.sonar.ce.task.step.ComputationStep;
 import org.sonar.server.notification.NotificationService;
+import org.sonar.server.qualitygate.notification.QGChangeNotification;
+
+import static java.util.Collections.singleton;
 
 /**
  * This step must be executed after computation of quality gate measure {@link QualityGateMeasuresStep}
@@ -128,7 +130,8 @@ public class QualityGateEventsStep implements ComputationStep {
    * @param rawStatus OK or ERROR + optional text
    */
   private void notifyUsers(Component project, String label, QualityGateStatus rawStatus, boolean isNewAlert) {
-    Notification notification = new Notification("alerts")
+    QGChangeNotification notification = new QGChangeNotification();
+    notification
       .setDefaultMessage(String.format("Alert on %s: %s", project.getName(), label))
       .setFieldValue("projectName", project.getName())
       .setFieldValue("projectKey", project.getKey())
@@ -141,6 +144,9 @@ public class QualityGateEventsStep implements ComputationStep {
     if (!branch.isMain()) {
       notification.setFieldValue("branch", branch.getName());
     }
+    notificationService.deliverEmails(singleton(notification));
+
+    // compatibility with old API
     notificationService.deliver(notification);
   }
 
index 12251c53a1b00e8d8c1b1cbda17f24dac2f8260b..23e7c2d4d5b6acfbc123497d0afba899a75c93b6 100644 (file)
@@ -19,8 +19,8 @@
  */
 package org.sonar.ce.task.projectanalysis.step;
 
+import java.util.Collection;
 import java.util.Optional;
-import java.util.Random;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -44,6 +44,7 @@ import org.sonar.ce.task.step.TestComputationStepContext;
 import org.sonar.db.component.BranchType;
 import org.sonar.server.notification.NotificationService;
 import org.sonar.server.project.Project;
+import org.sonar.server.qualitygate.notification.QGChangeNotification;
 
 import static java.util.Collections.emptyList;
 import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
@@ -177,8 +178,13 @@ public class QualityGateEventsStepTest {
     assertThat(event.getDescription()).isEqualTo(ALERT_TEXT);
     assertThat(event.getData()).isNull();
 
+    ArgumentCaptor<Collection> collectionCaptor = ArgumentCaptor.forClass(Collection.class);
+    verify(notificationService).deliverEmails(collectionCaptor.capture());
     verify(notificationService).deliver(notificationArgumentCaptor.capture());
     Notification notification = notificationArgumentCaptor.getValue();
+    assertThat(collectionCaptor.getValue()).hasSize(1);
+    assertThat(collectionCaptor.getValue().iterator().next()).isSameAs(notification);
+    assertThat(notification).isInstanceOf(QGChangeNotification.class);
     assertThat(notification.getType()).isEqualTo("alerts");
     assertThat(notification.getFieldValue("projectKey")).isEqualTo(PROJECT_COMPONENT.getKey());
     assertThat(notification.getFieldValue("projectName")).isEqualTo(PROJECT_COMPONENT.getName());
index f17fed85c8aa55c0d593312c093bb4164b6c52da..2f43b9b4ad347e5843c60be77aca445e9cc1290d 100644 (file)
@@ -91,7 +91,6 @@ import org.sonar.server.component.index.ComponentIndexer;
 import org.sonar.server.config.ConfigurationProvider;
 import org.sonar.server.es.EsModule;
 import org.sonar.server.es.ProjectIndexersImpl;
-import org.sonar.server.qualitygate.notification.NewAlerts;
 import org.sonar.server.extension.CoreExtensionBootstraper;
 import org.sonar.server.extension.CoreExtensionStopper;
 import org.sonar.server.favorite.FavoriteUpdater;
@@ -115,7 +114,6 @@ import org.sonar.server.metric.CoreCustomMetrics;
 import org.sonar.server.metric.DefaultMetricFinder;
 import org.sonar.server.notification.DefaultNotificationManager;
 import org.sonar.server.notification.NotificationService;
-import org.sonar.server.qualitygate.notification.AlertsEmailTemplate;
 import org.sonar.server.notification.email.EmailNotificationChannel;
 import org.sonar.server.organization.BillingValidationsProxyImpl;
 import org.sonar.server.organization.DefaultOrganizationProviderImpl;
@@ -140,6 +138,8 @@ import org.sonar.server.plugins.ServerExtensionInstaller;
 import org.sonar.server.property.InternalPropertiesImpl;
 import org.sonar.server.qualitygate.QualityGateEvaluatorImpl;
 import org.sonar.server.qualitygate.QualityGateFinder;
+import org.sonar.server.qualitygate.notification.QGChangeEmailTemplate;
+import org.sonar.server.qualitygate.notification.QGChangeNotificationHandler;
 import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
 import org.sonar.server.rule.DefaultRuleFinder;
 import org.sonar.server.rule.index.RuleIndex;
@@ -384,8 +384,8 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
       // components,
       FavoriteUpdater.class,
       ProjectIndexersImpl.class,
-      NewAlerts.class,
-      NewAlerts.newMetadata(),
+      QGChangeNotificationHandler.class,
+      QGChangeNotificationHandler.newMetadata(),
       ProjectMeasuresIndexer.class,
       ComponentIndexer.class,
 
@@ -413,7 +413,7 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
       DoNotFixNotificationHandler.newMetadata(),
 
       // Notifications
-      AlertsEmailTemplate.class,
+      QGChangeEmailTemplate.class,
       EmailSettings.class,
       NotificationService.class,
       DefaultNotificationManager.class,
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/notification/AlertsEmailTemplate.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/notification/AlertsEmailTemplate.java
deleted file mode 100644 (file)
index 1fa5132..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.qualitygate.notification;
-
-import javax.annotation.Nullable;
-import org.apache.commons.lang.StringUtils;
-import org.sonar.api.config.EmailSettings;
-import org.sonar.api.measures.Metric;
-import org.sonar.api.notifications.Notification;
-import org.sonar.plugins.emailnotifications.api.EmailMessage;
-import org.sonar.plugins.emailnotifications.api.EmailTemplate;
-
-/**
- * Creates email message for notification "alerts".
- *
- * @since 3.5
- */
-public class AlertsEmailTemplate extends EmailTemplate {
-
-  private EmailSettings configuration;
-
-  public AlertsEmailTemplate(EmailSettings configuration) {
-    this.configuration = configuration;
-  }
-
-  @Override
-  public EmailMessage format(Notification notification) {
-    if (!"alerts".equals(notification.getType())) {
-      return null;
-    }
-
-    // Retrieve useful values
-    String projectId = notification.getFieldValue("projectId");
-    String projectKey = notification.getFieldValue("projectKey");
-    String projectName = notification.getFieldValue("projectName");
-    String projectVersion = notification.getFieldValue("projectVersion");
-    String branchName = notification.getFieldValue("branch");
-    String alertName = notification.getFieldValue("alertName");
-    String alertText = notification.getFieldValue("alertText");
-    String alertLevel = notification.getFieldValue("alertLevel");
-    boolean isNewAlert = Boolean.parseBoolean(notification.getFieldValue("isNewAlert"));
-    String fullProjectName = computeFullProjectName(projectName, branchName);
-
-    // Generate text
-    String subject = generateSubject(fullProjectName, alertLevel, isNewAlert);
-    String messageBody = generateMessageBody(projectName, projectKey, projectVersion, branchName, alertName, alertText, isNewAlert);
-
-    // And finally return the email that will be sent
-    return new EmailMessage()
-      .setMessageId("alerts/" + projectId)
-      .setSubject(subject)
-      .setMessage(messageBody);
-  }
-
-  private static String computeFullProjectName(String projectName, @Nullable String branchName) {
-    if (branchName == null || branchName.isEmpty()) {
-      return projectName;
-    }
-    return String.format("%s (%s)", projectName, branchName);
-  }
-
-  private static String generateSubject(String fullProjectName, String alertLevel, boolean isNewAlert) {
-    StringBuilder subjectBuilder = new StringBuilder();
-    if (Metric.Level.OK.toString().equals(alertLevel)) {
-      subjectBuilder.append("\"").append(fullProjectName).append("\" is back to green");
-    } else if (isNewAlert) {
-      subjectBuilder.append("New quality gate threshold reached on \"").append(fullProjectName).append("\"");
-    } else {
-      subjectBuilder.append("Quality gate status changed on \"").append(fullProjectName).append("\"");
-    }
-    return subjectBuilder.toString();
-  }
-
-  private String generateMessageBody(String projectName, String projectKey,
-    @Nullable String projectVersion, @Nullable String branchName,
-    String alertName, String alertText, boolean isNewAlert) {
-    StringBuilder messageBody = new StringBuilder();
-    messageBody.append("Project: ").append(projectName).append("\n");
-    if (branchName != null) {
-      messageBody.append("Branch: ").append(branchName).append("\n");
-    }
-    if (projectVersion != null) {
-      messageBody.append("Version: ").append(projectVersion).append("\n");
-    }
-    messageBody.append("Quality gate status: ").append(alertName).append("\n\n");
-
-    String[] alerts = StringUtils.split(alertText, ",");
-    if (alerts.length > 0) {
-      if (isNewAlert) {
-        messageBody.append("New quality gate threshold");
-      } else {
-        messageBody.append("Quality gate threshold");
-      }
-      if (alerts.length == 1) {
-        messageBody.append(": ").append(alerts[0].trim()).append("\n");
-      } else {
-        messageBody.append("s:\n");
-        for (String alert : alerts) {
-          messageBody.append("  - ").append(alert.trim()).append("\n");
-        }
-      }
-    }
-
-    messageBody.append("\n").append("More details at: ").append(configuration.getServerBaseURL()).append("/dashboard?id=").append(projectKey);
-    if (branchName != null) {
-      messageBody.append("&branch=").append(branchName);
-    }
-
-    return messageBody.toString();
-  }
-
-}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/notification/NewAlerts.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/notification/NewAlerts.java
deleted file mode 100644 (file)
index 61adbc6..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.qualitygate.notification;
-
-import com.google.common.collect.Multimap;
-import java.util.Collection;
-import java.util.Map;
-import org.sonar.api.notifications.Notification;
-import org.sonar.api.notifications.NotificationChannel;
-import org.sonar.server.notification.NotificationDispatcher;
-import org.sonar.server.notification.NotificationDispatcherMetadata;
-import org.sonar.server.notification.NotificationManager;
-
-import static org.sonar.server.notification.NotificationManager.SubscriberPermissionsOnProject.ALL_MUST_HAVE_ROLE_USER;
-
-/**
- * This dispatcher means: "notify me each new alert event".
- *
- * @since 3.5
- */
-public class NewAlerts extends NotificationDispatcher {
-
-  public static final String KEY = "NewAlerts";
-  private final NotificationManager notifications;
-
-  public NewAlerts(NotificationManager notifications) {
-    super("alerts");
-    this.notifications = notifications;
-  }
-
-  @Override
-  public String getKey() {
-    return KEY;
-  }
-
-  public static NotificationDispatcherMetadata newMetadata() {
-    return NotificationDispatcherMetadata.create(KEY)
-      .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
-      .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
-  }
-
-  @Override
-  public void dispatch(Notification notification, Context context) {
-    String projectKey = notification.getFieldValue("projectKey");
-    if (projectKey != null) {
-      Multimap<String, NotificationChannel> subscribedRecipients = notifications
-          .findSubscribedRecipientsForDispatcher(this, projectKey, ALL_MUST_HAVE_ROLE_USER);
-
-      for (Map.Entry<String, Collection<NotificationChannel>> channelsByRecipients : subscribedRecipients.asMap().entrySet()) {
-        String userLogin = channelsByRecipients.getKey();
-        for (NotificationChannel channel : channelsByRecipients.getValue()) {
-          context.addUser(userLogin, channel);
-        }
-      }
-    }
-  }
-}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/notification/QGChangeEmailTemplate.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/notification/QGChangeEmailTemplate.java
new file mode 100644 (file)
index 0000000..284658a
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.qualitygate.notification;
+
+import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.config.EmailSettings;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.notifications.Notification;
+import org.sonar.plugins.emailnotifications.api.EmailMessage;
+import org.sonar.plugins.emailnotifications.api.EmailTemplate;
+
+/**
+ * Creates email message for notification "alerts".
+ *
+ * @since 3.5
+ */
+public class QGChangeEmailTemplate extends EmailTemplate {
+
+  private EmailSettings configuration;
+
+  public QGChangeEmailTemplate(EmailSettings configuration) {
+    this.configuration = configuration;
+  }
+
+  @Override
+  public EmailMessage format(Notification notification) {
+    if (!"alerts".equals(notification.getType())) {
+      return null;
+    }
+
+    // Retrieve useful values
+    String projectId = notification.getFieldValue("projectId");
+    String projectKey = notification.getFieldValue("projectKey");
+    String projectName = notification.getFieldValue("projectName");
+    String projectVersion = notification.getFieldValue("projectVersion");
+    String branchName = notification.getFieldValue("branch");
+    String alertName = notification.getFieldValue("alertName");
+    String alertText = notification.getFieldValue("alertText");
+    String alertLevel = notification.getFieldValue("alertLevel");
+    boolean isNewAlert = Boolean.parseBoolean(notification.getFieldValue("isNewAlert"));
+    String fullProjectName = computeFullProjectName(projectName, branchName);
+
+    // Generate text
+    String subject = generateSubject(fullProjectName, alertLevel, isNewAlert);
+    String messageBody = generateMessageBody(projectName, projectKey, projectVersion, branchName, alertName, alertText, isNewAlert);
+
+    // And finally return the email that will be sent
+    return new EmailMessage()
+      .setMessageId("alerts/" + projectId)
+      .setSubject(subject)
+      .setMessage(messageBody);
+  }
+
+  private static String computeFullProjectName(String projectName, @Nullable String branchName) {
+    if (branchName == null || branchName.isEmpty()) {
+      return projectName;
+    }
+    return String.format("%s (%s)", projectName, branchName);
+  }
+
+  private static String generateSubject(String fullProjectName, String alertLevel, boolean isNewAlert) {
+    StringBuilder subjectBuilder = new StringBuilder();
+    if (Metric.Level.OK.toString().equals(alertLevel)) {
+      subjectBuilder.append("\"").append(fullProjectName).append("\" is back to green");
+    } else if (isNewAlert) {
+      subjectBuilder.append("New quality gate threshold reached on \"").append(fullProjectName).append("\"");
+    } else {
+      subjectBuilder.append("Quality gate status changed on \"").append(fullProjectName).append("\"");
+    }
+    return subjectBuilder.toString();
+  }
+
+  private String generateMessageBody(String projectName, String projectKey,
+    @Nullable String projectVersion, @Nullable String branchName,
+    String alertName, String alertText, boolean isNewAlert) {
+    StringBuilder messageBody = new StringBuilder();
+    messageBody.append("Project: ").append(projectName).append("\n");
+    if (branchName != null) {
+      messageBody.append("Branch: ").append(branchName).append("\n");
+    }
+    if (projectVersion != null) {
+      messageBody.append("Version: ").append(projectVersion).append("\n");
+    }
+    messageBody.append("Quality gate status: ").append(alertName).append("\n\n");
+
+    String[] alerts = StringUtils.split(alertText, ",");
+    if (alerts.length > 0) {
+      if (isNewAlert) {
+        messageBody.append("New quality gate threshold");
+      } else {
+        messageBody.append("Quality gate threshold");
+      }
+      if (alerts.length == 1) {
+        messageBody.append(": ").append(alerts[0].trim()).append("\n");
+      } else {
+        messageBody.append("s:\n");
+        for (String alert : alerts) {
+          messageBody.append("  - ").append(alert.trim()).append("\n");
+        }
+      }
+    }
+
+    messageBody.append("\n").append("More details at: ").append(configuration.getServerBaseURL()).append("/dashboard?id=").append(projectKey);
+    if (branchName != null) {
+      messageBody.append("&branch=").append(branchName);
+    }
+
+    return messageBody.toString();
+  }
+
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/notification/QGChangeNotification.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/notification/QGChangeNotification.java
new file mode 100644 (file)
index 0000000..07ff928
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.qualitygate.notification;
+
+import javax.annotation.CheckForNull;
+import org.sonar.api.notifications.Notification;
+
+public class QGChangeNotification extends Notification {
+  public QGChangeNotification() {
+    super("alerts");
+  }
+
+  @CheckForNull
+  public String getProjectKey() {
+    return getFieldValue("projectKey");
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/notification/QGChangeNotificationHandler.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/notification/QGChangeNotificationHandler.java
new file mode 100644 (file)
index 0000000..18402cf
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.qualitygate.notification;
+
+import com.google.common.collect.Multimap;
+import java.util.Collection;
+import java.util.Set;
+import java.util.stream.Stream;
+import org.sonar.server.notification.NotificationDispatcherMetadata;
+import org.sonar.server.notification.NotificationHandler;
+import org.sonar.server.notification.NotificationManager;
+import org.sonar.server.notification.email.EmailNotificationChannel;
+import org.sonar.server.notification.email.EmailNotificationChannel.EmailDeliveryRequest;
+
+import static org.sonar.core.util.stream.MoreCollectors.index;
+import static org.sonar.core.util.stream.MoreCollectors.toSet;
+import static org.sonar.server.notification.NotificationManager.SubscriberPermissionsOnProject.ALL_MUST_HAVE_ROLE_USER;
+
+public class QGChangeNotificationHandler implements NotificationHandler<QGChangeNotification> {
+
+  public static final String KEY = "NewAlerts";
+
+  private final NotificationManager notificationManager;
+  private final EmailNotificationChannel emailNotificationChannel;
+
+  public QGChangeNotificationHandler(NotificationManager notificationManager, EmailNotificationChannel emailNotificationChannel) {
+    this.notificationManager = notificationManager;
+    this.emailNotificationChannel = emailNotificationChannel;
+  }
+
+  @Override
+  public Class<QGChangeNotification> getNotificationClass() {
+    return QGChangeNotification.class;
+  }
+
+  public static NotificationDispatcherMetadata newMetadata() {
+    return NotificationDispatcherMetadata.create(KEY)
+      .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
+      .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
+  }
+
+  @Override
+  public int deliver(Collection<QGChangeNotification> notifications) {
+    if (notifications.isEmpty() || !emailNotificationChannel.isActivated()) {
+      return 0;
+    }
+
+    Multimap<String, QGChangeNotification> notificationsByProjectKey = notifications.stream()
+      .filter(t -> t.getProjectKey() != null)
+      .collect(index(QGChangeNotification::getProjectKey));
+    if (notificationsByProjectKey.isEmpty()) {
+      return 0;
+    }
+
+    Set<EmailDeliveryRequest> deliveryRequests = notificationsByProjectKey.asMap().entrySet()
+      .stream()
+      .flatMap(e -> toEmailDeliveryRequests(e.getKey(), e.getValue()))
+      .collect(toSet(notifications.size()));
+    if (deliveryRequests.isEmpty()) {
+      return 0;
+    }
+    return emailNotificationChannel.deliver(deliveryRequests);
+  }
+
+  private Stream<? extends EmailDeliveryRequest> toEmailDeliveryRequests(String projectKey, Collection<QGChangeNotification> notifications) {
+    return notificationManager.findSubscribedEmailRecipients(KEY, projectKey, ALL_MUST_HAVE_ROLE_USER)
+      .stream()
+      .flatMap(emailRecipient -> notifications.stream()
+        .map(notification -> new EmailDeliveryRequest(emailRecipient.getEmail(), notification)));
+  }
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/notification/AlertsEmailTemplateTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/notification/AlertsEmailTemplateTest.java
deleted file mode 100644 (file)
index e8a06c2..0000000
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.qualitygate.notification;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.api.config.EmailSettings;
-import org.sonar.api.notifications.Notification;
-import org.sonar.plugins.emailnotifications.api.EmailMessage;
-import org.sonar.server.qualitygate.notification.AlertsEmailTemplate;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.nullValue;
-import static org.junit.Assert.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class AlertsEmailTemplateTest {
-
-  private AlertsEmailTemplate template;
-
-  @Before
-  public void setUp() {
-    EmailSettings configuration = mock(EmailSettings.class);
-    when(configuration.getServerBaseURL()).thenReturn("http://nemo.sonarsource.org");
-    template = new AlertsEmailTemplate(configuration);
-  }
-
-  @Test
-  public void shouldNotFormatIfNotCorrectNotification() {
-    Notification notification = new Notification("other-notif");
-    EmailMessage message = template.format(notification);
-    assertThat(message, nullValue());
-  }
-
-  @Test
-  public void shouldFormatAlertWithSeveralMessages() {
-    Notification notification = createNotification("Red (was Green)", "violations > 4, coverage < 75%", "ERROR", "false");
-
-    EmailMessage message = template.format(notification);
-    assertThat(message.getMessageId(), is("alerts/45"));
-    assertThat(message.getSubject(), is("Quality gate status changed on \"Foo\""));
-    assertThat(message.getMessage(), is("" +
-      "Project: Foo\n" +
-      "Version: V1-SNAP\n" +
-      "Quality gate status: Red (was Green)\n" +
-      "\n" +
-      "Quality gate thresholds:\n" +
-      "  - violations > 4\n" +
-      "  - coverage < 75%\n" +
-      "\n" +
-      "More details at: http://nemo.sonarsource.org/dashboard?id=org.sonar.foo:foo"));
-  }
-
-  @Test
-  public void shouldFormatAlertWithSeveralMessagesOnBranch() {
-    Notification notification = createNotification("Red (was Green)", "violations > 4, coverage < 75%", "ERROR", "false")
-        .setFieldValue("branch", "feature");
-
-    EmailMessage message = template.format(notification);
-    assertThat(message.getMessageId(), is("alerts/45"));
-    assertThat(message.getSubject(), is("Quality gate status changed on \"Foo (feature)\""));
-    assertThat(message.getMessage(), is("" +
-      "Project: Foo\n" +
-      "Branch: feature\n" +
-      "Version: V1-SNAP\n" +
-      "Quality gate status: Red (was Green)\n" +
-      "\n" +
-      "Quality gate thresholds:\n" +
-      "  - violations > 4\n" +
-      "  - coverage < 75%\n" +
-      "\n" +
-      "More details at: http://nemo.sonarsource.org/dashboard?id=org.sonar.foo:foo&branch=feature"));
-  }
-
-  @Test
-  public void shouldFormatNewAlertWithSeveralMessages() {
-    Notification notification = createNotification("Red (was Green)", "violations > 4, coverage < 75%", "ERROR", "true");
-
-    EmailMessage message = template.format(notification);
-    assertThat(message.getMessageId(), is("alerts/45"));
-    assertThat(message.getSubject(), is("New quality gate threshold reached on \"Foo\""));
-    assertThat(message.getMessage(), is("" +
-      "Project: Foo\n" +
-      "Version: V1-SNAP\n" +
-      "Quality gate status: Red (was Green)\n" +
-      "\n" +
-      "New quality gate thresholds:\n" +
-      "  - violations > 4\n" +
-      "  - coverage < 75%\n" +
-      "\n" +
-      "More details at: http://nemo.sonarsource.org/dashboard?id=org.sonar.foo:foo"));
-  }
-
-  @Test
-  public void shouldFormatNewAlertWithOneMessage() {
-    Notification notification = createNotification("Red (was Green)", "violations > 4", "ERROR", "true");
-
-    EmailMessage message = template.format(notification);
-    assertThat(message.getMessageId(), is("alerts/45"));
-    assertThat(message.getSubject(), is("New quality gate threshold reached on \"Foo\""));
-    assertThat(message.getMessage(), is("" +
-      "Project: Foo\n" +
-      "Version: V1-SNAP\n" +
-      "Quality gate status: Red (was Green)\n" +
-      "\n" +
-      "New quality gate threshold: violations > 4\n" +
-      "\n" +
-      "More details at: http://nemo.sonarsource.org/dashboard?id=org.sonar.foo:foo"));
-  }
-
-  @Test
-  public void shouldFormatNewAlertWithoutVersion() {
-    Notification notification = createNotification("Red (was Green)", "violations > 4", "ERROR", "true")
-        .setFieldValue("projectVersion", null);
-
-    EmailMessage message = template.format(notification);
-    assertThat(message.getMessageId(), is("alerts/45"));
-    assertThat(message.getSubject(), is("New quality gate threshold reached on \"Foo\""));
-    assertThat(message.getMessage(), is("" +
-      "Project: Foo\n" +
-      "Quality gate status: Red (was Green)\n" +
-      "\n" +
-      "New quality gate threshold: violations > 4\n" +
-      "\n" +
-      "More details at: http://nemo.sonarsource.org/dashboard?id=org.sonar.foo:foo"));
-  }
-
-  @Test
-  public void shouldFormatNewAlertWithOneMessageOnBranch() {
-    Notification notification = createNotification("Red (was Green)", "violations > 4", "ERROR", "true")
-      .setFieldValue("branch", "feature");
-
-    EmailMessage message = template.format(notification);
-    assertThat(message.getMessageId(), is("alerts/45"));
-    assertThat(message.getSubject(), is("New quality gate threshold reached on \"Foo (feature)\""));
-    assertThat(message.getMessage(), is("" +
-      "Project: Foo\n" +
-      "Branch: feature\n" +
-      "Version: V1-SNAP\n" +
-      "Quality gate status: Red (was Green)\n" +
-      "\n" +
-      "New quality gate threshold: violations > 4\n" +
-      "\n" +
-      "More details at: http://nemo.sonarsource.org/dashboard?id=org.sonar.foo:foo&branch=feature"));
-  }
-
-  @Test
-  public void shouldFormatBackToGreenMessage() {
-    Notification notification = createNotification("Green (was Red)", "", "OK", "false");
-
-    EmailMessage message = template.format(notification);
-    assertThat(message.getMessageId(), is("alerts/45"));
-    assertThat(message.getSubject(), is("\"Foo\" is back to green"));
-    assertThat(message.getMessage(), is("" +
-      "Project: Foo\n" +
-      "Version: V1-SNAP\n" +
-      "Quality gate status: Green (was Red)\n" +
-      "\n" +
-      "\n" +
-      "More details at: http://nemo.sonarsource.org/dashboard?id=org.sonar.foo:foo"));
-  }
-
-  @Test
-  public void shouldFormatBackToGreenMessageOnBranch() {
-    Notification notification = createNotification("Green (was Red)", "", "OK", "false")
-        .setFieldValue("branch", "feature");
-
-    EmailMessage message = template.format(notification);
-    assertThat(message.getMessageId(), is("alerts/45"));
-    assertThat(message.getSubject(), is("\"Foo (feature)\" is back to green"));
-    assertThat(message.getMessage(), is("" +
-      "Project: Foo\n" +
-      "Branch: feature\n" +
-      "Version: V1-SNAP\n" +
-      "Quality gate status: Green (was Red)\n" +
-      "\n" +
-      "\n" +
-      "More details at: http://nemo.sonarsource.org/dashboard?id=org.sonar.foo:foo&branch=feature"));
-  }
-
-  private Notification createNotification(String alertName, String alertText, String alertLevel, String isNewAlert) {
-    Notification notification = new Notification("alerts")
-        .setFieldValue("projectName", "Foo")
-        .setFieldValue("projectKey", "org.sonar.foo:foo")
-        .setFieldValue("projectId", "45")
-        .setFieldValue("projectVersion", "V1-SNAP")
-        .setFieldValue("alertName", alertName)
-        .setFieldValue("alertText", alertText)
-        .setFieldValue("alertLevel", alertLevel)
-        .setFieldValue("isNewAlert", isNewAlert);
-    return notification;
-  }
-
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/notification/NewAlertsTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/notification/NewAlertsTest.java
deleted file mode 100644 (file)
index 48a7b9f..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.qualitygate.notification;
-
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.Multimap;
-import org.junit.Test;
-import org.sonar.api.notifications.Notification;
-import org.sonar.api.notifications.NotificationChannel;
-import org.sonar.api.web.UserRole;
-import org.sonar.server.notification.NotificationDispatcher;
-import org.sonar.server.notification.NotificationManager;
-
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-public class NewAlertsTest {
-
-  private NotificationManager notificationManager = mock(NotificationManager.class);
-  private NotificationDispatcher.Context context = mock(NotificationDispatcher.Context.class);
-  private NotificationChannel emailChannel = mock(NotificationChannel.class);
-  private NotificationChannel twitterChannel = mock(NotificationChannel.class);
-  private NewAlerts dispatcher = new NewAlerts(notificationManager);
-
-  @Test
-  public void should_not_dispatch_if_not_alerts_notification() {
-    Notification notification = new Notification("other-notif");
-    dispatcher.performDispatch(notification, context);
-
-    verify(context, never()).addUser(any(String.class), any(NotificationChannel.class));
-  }
-
-  @Test
-  public void should_dispatch_to_users_who_have_subscribed() {
-    Multimap<String, NotificationChannel> recipients = HashMultimap.create();
-    recipients.put("user1", emailChannel);
-    recipients.put("user2", twitterChannel);
-    when(notificationManager.findSubscribedRecipientsForDispatcher(dispatcher, "key_34", new NotificationManager.SubscriberPermissionsOnProject(UserRole.USER)))
-      .thenReturn(recipients);
-
-    Notification notification = new Notification("alerts")
-      .setFieldValue("projectKey", "key_34");
-    dispatcher.performDispatch(notification, context);
-
-    verify(context).addUser("user1", emailChannel);
-    verify(context).addUser("user2", twitterChannel);
-    verifyNoMoreInteractions(context);
-  }
-
-  @Test
-  public void should_not_dispatch_if_missing_project_key() {
-    Multimap<String, NotificationChannel> recipients = HashMultimap.create();
-    recipients.put("user1", emailChannel);
-    recipients.put("user2", twitterChannel);
-    when(notificationManager.findSubscribedRecipientsForDispatcher(dispatcher, "key_34", new NotificationManager.SubscriberPermissionsOnProject(UserRole.USER)))
-      .thenReturn(recipients);
-
-    Notification notification = new Notification("alerts");
-    dispatcher.performDispatch(notification, context);
-
-    verifyNoMoreInteractions(context);
-  }
-
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/notification/QGChangeEmailTemplateTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/notification/QGChangeEmailTemplateTest.java
new file mode 100644 (file)
index 0000000..c6ca1b6
--- /dev/null
@@ -0,0 +1,211 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.qualitygate.notification;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.config.EmailSettings;
+import org.sonar.api.notifications.Notification;
+import org.sonar.plugins.emailnotifications.api.EmailMessage;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class QGChangeEmailTemplateTest {
+
+  private QGChangeEmailTemplate template;
+
+  @Before
+  public void setUp() {
+    EmailSettings configuration = mock(EmailSettings.class);
+    when(configuration.getServerBaseURL()).thenReturn("http://nemo.sonarsource.org");
+    template = new QGChangeEmailTemplate(configuration);
+  }
+
+  @Test
+  public void shouldNotFormatIfNotCorrectNotification() {
+    Notification notification = new Notification("other-notif");
+    EmailMessage message = template.format(notification);
+    assertThat(message, nullValue());
+  }
+
+  @Test
+  public void shouldFormatAlertWithSeveralMessages() {
+    Notification notification = createNotification("Red (was Green)", "violations > 4, coverage < 75%", "ERROR", "false");
+
+    EmailMessage message = template.format(notification);
+    assertThat(message.getMessageId(), is("alerts/45"));
+    assertThat(message.getSubject(), is("Quality gate status changed on \"Foo\""));
+    assertThat(message.getMessage(), is("" +
+      "Project: Foo\n" +
+      "Version: V1-SNAP\n" +
+      "Quality gate status: Red (was Green)\n" +
+      "\n" +
+      "Quality gate thresholds:\n" +
+      "  - violations > 4\n" +
+      "  - coverage < 75%\n" +
+      "\n" +
+      "More details at: http://nemo.sonarsource.org/dashboard?id=org.sonar.foo:foo"));
+  }
+
+  @Test
+  public void shouldFormatAlertWithSeveralMessagesOnBranch() {
+    Notification notification = createNotification("Red (was Green)", "violations > 4, coverage < 75%", "ERROR", "false")
+        .setFieldValue("branch", "feature");
+
+    EmailMessage message = template.format(notification);
+    assertThat(message.getMessageId(), is("alerts/45"));
+    assertThat(message.getSubject(), is("Quality gate status changed on \"Foo (feature)\""));
+    assertThat(message.getMessage(), is("" +
+      "Project: Foo\n" +
+      "Branch: feature\n" +
+      "Version: V1-SNAP\n" +
+      "Quality gate status: Red (was Green)\n" +
+      "\n" +
+      "Quality gate thresholds:\n" +
+      "  - violations > 4\n" +
+      "  - coverage < 75%\n" +
+      "\n" +
+      "More details at: http://nemo.sonarsource.org/dashboard?id=org.sonar.foo:foo&branch=feature"));
+  }
+
+  @Test
+  public void shouldFormatNewAlertWithSeveralMessages() {
+    Notification notification = createNotification("Red (was Green)", "violations > 4, coverage < 75%", "ERROR", "true");
+
+    EmailMessage message = template.format(notification);
+    assertThat(message.getMessageId(), is("alerts/45"));
+    assertThat(message.getSubject(), is("New quality gate threshold reached on \"Foo\""));
+    assertThat(message.getMessage(), is("" +
+      "Project: Foo\n" +
+      "Version: V1-SNAP\n" +
+      "Quality gate status: Red (was Green)\n" +
+      "\n" +
+      "New quality gate thresholds:\n" +
+      "  - violations > 4\n" +
+      "  - coverage < 75%\n" +
+      "\n" +
+      "More details at: http://nemo.sonarsource.org/dashboard?id=org.sonar.foo:foo"));
+  }
+
+  @Test
+  public void shouldFormatNewAlertWithOneMessage() {
+    Notification notification = createNotification("Red (was Green)", "violations > 4", "ERROR", "true");
+
+    EmailMessage message = template.format(notification);
+    assertThat(message.getMessageId(), is("alerts/45"));
+    assertThat(message.getSubject(), is("New quality gate threshold reached on \"Foo\""));
+    assertThat(message.getMessage(), is("" +
+      "Project: Foo\n" +
+      "Version: V1-SNAP\n" +
+      "Quality gate status: Red (was Green)\n" +
+      "\n" +
+      "New quality gate threshold: violations > 4\n" +
+      "\n" +
+      "More details at: http://nemo.sonarsource.org/dashboard?id=org.sonar.foo:foo"));
+  }
+
+  @Test
+  public void shouldFormatNewAlertWithoutVersion() {
+    Notification notification = createNotification("Red (was Green)", "violations > 4", "ERROR", "true")
+        .setFieldValue("projectVersion", null);
+
+    EmailMessage message = template.format(notification);
+    assertThat(message.getMessageId(), is("alerts/45"));
+    assertThat(message.getSubject(), is("New quality gate threshold reached on \"Foo\""));
+    assertThat(message.getMessage(), is("" +
+      "Project: Foo\n" +
+      "Quality gate status: Red (was Green)\n" +
+      "\n" +
+      "New quality gate threshold: violations > 4\n" +
+      "\n" +
+      "More details at: http://nemo.sonarsource.org/dashboard?id=org.sonar.foo:foo"));
+  }
+
+  @Test
+  public void shouldFormatNewAlertWithOneMessageOnBranch() {
+    Notification notification = createNotification("Red (was Green)", "violations > 4", "ERROR", "true")
+      .setFieldValue("branch", "feature");
+
+    EmailMessage message = template.format(notification);
+    assertThat(message.getMessageId(), is("alerts/45"));
+    assertThat(message.getSubject(), is("New quality gate threshold reached on \"Foo (feature)\""));
+    assertThat(message.getMessage(), is("" +
+      "Project: Foo\n" +
+      "Branch: feature\n" +
+      "Version: V1-SNAP\n" +
+      "Quality gate status: Red (was Green)\n" +
+      "\n" +
+      "New quality gate threshold: violations > 4\n" +
+      "\n" +
+      "More details at: http://nemo.sonarsource.org/dashboard?id=org.sonar.foo:foo&branch=feature"));
+  }
+
+  @Test
+  public void shouldFormatBackToGreenMessage() {
+    Notification notification = createNotification("Green (was Red)", "", "OK", "false");
+
+    EmailMessage message = template.format(notification);
+    assertThat(message.getMessageId(), is("alerts/45"));
+    assertThat(message.getSubject(), is("\"Foo\" is back to green"));
+    assertThat(message.getMessage(), is("" +
+      "Project: Foo\n" +
+      "Version: V1-SNAP\n" +
+      "Quality gate status: Green (was Red)\n" +
+      "\n" +
+      "\n" +
+      "More details at: http://nemo.sonarsource.org/dashboard?id=org.sonar.foo:foo"));
+  }
+
+  @Test
+  public void shouldFormatBackToGreenMessageOnBranch() {
+    Notification notification = createNotification("Green (was Red)", "", "OK", "false")
+        .setFieldValue("branch", "feature");
+
+    EmailMessage message = template.format(notification);
+    assertThat(message.getMessageId(), is("alerts/45"));
+    assertThat(message.getSubject(), is("\"Foo (feature)\" is back to green"));
+    assertThat(message.getMessage(), is("" +
+      "Project: Foo\n" +
+      "Branch: feature\n" +
+      "Version: V1-SNAP\n" +
+      "Quality gate status: Green (was Red)\n" +
+      "\n" +
+      "\n" +
+      "More details at: http://nemo.sonarsource.org/dashboard?id=org.sonar.foo:foo&branch=feature"));
+  }
+
+  private Notification createNotification(String alertName, String alertText, String alertLevel, String isNewAlert) {
+    Notification notification = new Notification("alerts")
+        .setFieldValue("projectName", "Foo")
+        .setFieldValue("projectKey", "org.sonar.foo:foo")
+        .setFieldValue("projectId", "45")
+        .setFieldValue("projectVersion", "V1-SNAP")
+        .setFieldValue("alertName", alertName)
+        .setFieldValue("alertText", alertText)
+        .setFieldValue("alertLevel", alertLevel)
+        .setFieldValue("isNewAlert", isNewAlert);
+    return notification;
+  }
+
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/notification/QGChangeNotificationHandlerTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/notification/QGChangeNotificationHandlerTest.java
new file mode 100644 (file)
index 0000000..a9faf4d
--- /dev/null
@@ -0,0 +1,252 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.qualitygate.notification;
+
+import java.util.Collections;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import javax.annotation.Nullable;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.sonar.server.notification.NotificationDispatcherMetadata;
+import org.sonar.server.notification.NotificationManager;
+import org.sonar.server.notification.email.EmailNotificationChannel;
+
+import static java.util.Collections.emptySet;
+import static java.util.stream.Collectors.toSet;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import static org.sonar.server.notification.NotificationDispatcherMetadata.GLOBAL_NOTIFICATION;
+import static org.sonar.server.notification.NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION;
+import static org.sonar.server.notification.NotificationManager.SubscriberPermissionsOnProject.ALL_MUST_HAVE_ROLE_USER;
+
+public class QGChangeNotificationHandlerTest {
+  private static final String QG_CHANGE_DISPATCHER_KEY = "NewAlerts";
+  private NotificationManager notificationManager = mock(NotificationManager.class);
+  private EmailNotificationChannel emailNotificationChannel = mock(EmailNotificationChannel.class);
+  private QGChangeNotificationHandler underTest = new QGChangeNotificationHandler(notificationManager, emailNotificationChannel);
+
+  @Test
+  public void verify_qgChange_notification_dispatcher_key() {
+    NotificationDispatcherMetadata metadata = QGChangeNotificationHandler.newMetadata();
+
+    assertThat(metadata.getDispatcherKey()).isEqualTo(QG_CHANGE_DISPATCHER_KEY);
+  }
+
+  @Test
+  public void qgChange_notification_is_enable_at_global_level() {
+    NotificationDispatcherMetadata metadata = QGChangeNotificationHandler.newMetadata();
+
+    assertThat(metadata.getProperty(GLOBAL_NOTIFICATION)).isEqualTo("true");
+  }
+
+  @Test
+  public void qgChange_notification_is_enable_at_project_level() {
+    NotificationDispatcherMetadata metadata = QGChangeNotificationHandler.newMetadata();
+
+    assertThat(metadata.getProperty(PER_PROJECT_NOTIFICATION)).isEqualTo("true");
+  }
+
+  @Test
+  public void getNotificationClass_is_QGChangeNotification() {
+    assertThat(underTest.getNotificationClass()).isEqualTo(QGChangeNotification.class);
+  }
+
+  @Test
+  public void deliver_has_no_effect_if_notifications_is_empty() {
+    when(emailNotificationChannel.isActivated()).thenReturn(true);
+    int deliver = underTest.deliver(Collections.emptyList());
+
+    assertThat(deliver).isZero();
+    verifyZeroInteractions(notificationManager, emailNotificationChannel);
+  }
+
+  @Test
+  public void deliver_has_no_effect_if_emailNotificationChannel_is_disabled() {
+    when(emailNotificationChannel.isActivated()).thenReturn(false);
+    Set<QGChangeNotification> notifications = IntStream.range(0, 1 + new Random().nextInt(10))
+      .mapToObj(i -> mock(QGChangeNotification.class))
+      .collect(toSet());
+
+    int deliver = underTest.deliver(notifications);
+
+    assertThat(deliver).isZero();
+    verifyZeroInteractions(notificationManager);
+    verify(emailNotificationChannel).isActivated();
+    verifyNoMoreInteractions(emailNotificationChannel);
+    notifications.forEach(Mockito::verifyZeroInteractions);
+  }
+
+  @Test
+  public void deliver_has_no_effect_if_no_notification_has_projectKey() {
+    when(emailNotificationChannel.isActivated()).thenReturn(true);
+    Set<QGChangeNotification> notifications = IntStream.range(0, 1 + new Random().nextInt(10))
+      .mapToObj(i -> newNotification(null))
+      .collect(toSet());
+
+    int deliver = underTest.deliver(notifications);
+
+    assertThat(deliver).isZero();
+    verifyZeroInteractions(notificationManager);
+    verify(emailNotificationChannel).isActivated();
+    verifyNoMoreInteractions(emailNotificationChannel);
+    notifications.forEach(notification -> {
+      verify(notification).getProjectKey();
+      verifyNoMoreInteractions(notification);
+    });
+  }
+
+  @Test
+  public void deliver_has_no_effect_if_no_notification_has_subscribed_recipients_to_QGChange_notifications() {
+    String projectKey = randomAlphabetic(12);
+    QGChangeNotification notification = newNotification(projectKey);
+    when(emailNotificationChannel.isActivated()).thenReturn(true);
+    when(notificationManager.findSubscribedEmailRecipients(QG_CHANGE_DISPATCHER_KEY, projectKey, ALL_MUST_HAVE_ROLE_USER))
+      .thenReturn(emptySet());
+
+    int deliver = underTest.deliver(Collections.singleton(notification));
+
+    assertThat(deliver).isZero();
+    verify(notificationManager).findSubscribedEmailRecipients(QG_CHANGE_DISPATCHER_KEY, projectKey, ALL_MUST_HAVE_ROLE_USER);
+    verifyNoMoreInteractions(notificationManager);
+    verify(emailNotificationChannel).isActivated();
+    verifyNoMoreInteractions(emailNotificationChannel);
+  }
+
+  @Test
+  public void deliver_ignores_notification_without_projectKey() {
+    String projectKey = randomAlphabetic(10);
+    Set<QGChangeNotification> withProjectKey = IntStream.range(0, 1 + new Random().nextInt(5))
+      .mapToObj(i -> newNotification(projectKey))
+      .collect(toSet());
+    Set<QGChangeNotification> noProjectKey = IntStream.range(0, 1 + new Random().nextInt(5))
+      .mapToObj(i -> newNotification(null))
+      .collect(toSet());
+    Set<NotificationManager.EmailRecipient> emailRecipients = IntStream.range(0, 1 + new Random().nextInt(10))
+      .mapToObj(i -> "user_" + i)
+      .map(login -> new NotificationManager.EmailRecipient(login, emailOf(login)))
+      .collect(toSet());
+    Set<EmailNotificationChannel.EmailDeliveryRequest> expectedRequests = emailRecipients.stream()
+      .flatMap(emailRecipient -> withProjectKey.stream().map(notif -> new EmailNotificationChannel.EmailDeliveryRequest(emailRecipient.getEmail(), notif)))
+      .collect(toSet());
+    when(emailNotificationChannel.isActivated()).thenReturn(true);
+    when(notificationManager.findSubscribedEmailRecipients(QG_CHANGE_DISPATCHER_KEY, projectKey, ALL_MUST_HAVE_ROLE_USER))
+      .thenReturn(emailRecipients);
+
+    Set<QGChangeNotification> notifications = Stream.of(withProjectKey.stream(), noProjectKey.stream())
+      .flatMap(t -> t)
+      .collect(toSet());
+    int deliver = underTest.deliver(notifications);
+
+    assertThat(deliver).isZero();
+    verify(notificationManager).findSubscribedEmailRecipients(QG_CHANGE_DISPATCHER_KEY, projectKey, ALL_MUST_HAVE_ROLE_USER);
+    verifyNoMoreInteractions(notificationManager);
+    verify(emailNotificationChannel).isActivated();
+    verify(emailNotificationChannel).deliver(expectedRequests);
+    verifyNoMoreInteractions(emailNotificationChannel);
+  }
+
+  @Test
+  public void deliver_checks_by_projectKey_if_notifications_have_subscribed_assignee_to_QGChange_notifications() {
+    String projectKey1 = randomAlphabetic(10);
+    String projectKey2 = randomAlphabetic(11);
+    Set<QGChangeNotification> notifications1 = randomSetOfNotifications(projectKey1);
+    Set<QGChangeNotification> notifications2 = randomSetOfNotifications(projectKey2);
+    when(emailNotificationChannel.isActivated()).thenReturn(true);
+
+    Set<NotificationManager.EmailRecipient> emailRecipients1 = IntStream.range(0, 1 + new Random().nextInt(10))
+      .mapToObj(i -> "user1_" + i)
+      .map(login -> new NotificationManager.EmailRecipient(login, emailOf(login)))
+      .collect(toSet());
+    Set<NotificationManager.EmailRecipient> emailRecipients2 = IntStream.range(0, 1 + new Random().nextInt(10))
+      .mapToObj(i -> "user2_" + i)
+      .map(login -> new NotificationManager.EmailRecipient(login, emailOf(login)))
+      .collect(toSet());
+    when(notificationManager.findSubscribedEmailRecipients(QG_CHANGE_DISPATCHER_KEY, projectKey1, ALL_MUST_HAVE_ROLE_USER))
+      .thenReturn(emailRecipients1);
+    when(notificationManager.findSubscribedEmailRecipients(QG_CHANGE_DISPATCHER_KEY, projectKey2, ALL_MUST_HAVE_ROLE_USER))
+      .thenReturn(emailRecipients2);
+    Set<EmailNotificationChannel.EmailDeliveryRequest> expectedRequests = Stream.concat(
+      emailRecipients1.stream()
+        .flatMap(emailRecipient -> notifications1.stream().map(notif -> new EmailNotificationChannel.EmailDeliveryRequest(emailRecipient.getEmail(), notif))),
+      emailRecipients2.stream()
+        .flatMap(emailRecipient -> notifications2.stream().map(notif -> new EmailNotificationChannel.EmailDeliveryRequest(emailRecipient.getEmail(), notif))))
+      .collect(toSet());
+
+    int deliver = underTest.deliver(Stream.concat(notifications1.stream(), notifications2.stream()).collect(toSet()));
+
+    assertThat(deliver).isZero();
+    verify(notificationManager).findSubscribedEmailRecipients(QG_CHANGE_DISPATCHER_KEY, projectKey1, ALL_MUST_HAVE_ROLE_USER);
+    verify(notificationManager).findSubscribedEmailRecipients(QG_CHANGE_DISPATCHER_KEY, projectKey2, ALL_MUST_HAVE_ROLE_USER);
+    verifyNoMoreInteractions(notificationManager);
+    verify(emailNotificationChannel).isActivated();
+    verify(emailNotificationChannel).deliver(expectedRequests);
+    verifyNoMoreInteractions(emailNotificationChannel);
+  }
+
+  @Test
+  public void deliver_send_notifications_to_all_subscribers_of_all_projects() {
+    String projectKey1 = randomAlphabetic(10);
+    String projectKey2 = randomAlphabetic(11);
+    Set<QGChangeNotification> notifications1 = randomSetOfNotifications(projectKey1);
+    Set<QGChangeNotification> notifications2 = randomSetOfNotifications(projectKey2);
+    when(emailNotificationChannel.isActivated()).thenReturn(true);
+    when(notificationManager.findSubscribedEmailRecipients(QG_CHANGE_DISPATCHER_KEY, projectKey1, ALL_MUST_HAVE_ROLE_USER))
+      .thenReturn(emptySet());
+    when(notificationManager.findSubscribedEmailRecipients(QG_CHANGE_DISPATCHER_KEY, projectKey2, ALL_MUST_HAVE_ROLE_USER))
+      .thenReturn(emptySet());
+
+    int deliver = underTest.deliver(Stream.concat(notifications1.stream(), notifications2.stream()).collect(toSet()));
+
+    assertThat(deliver).isZero();
+    verify(notificationManager).findSubscribedEmailRecipients(QG_CHANGE_DISPATCHER_KEY, projectKey1, ALL_MUST_HAVE_ROLE_USER);
+    verify(notificationManager).findSubscribedEmailRecipients(QG_CHANGE_DISPATCHER_KEY, projectKey2, ALL_MUST_HAVE_ROLE_USER);
+    verifyNoMoreInteractions(notificationManager);
+    verify(emailNotificationChannel).isActivated();
+    verifyNoMoreInteractions(emailNotificationChannel);
+  }
+
+  private static Set<QGChangeNotification> randomSetOfNotifications(@Nullable String projectKey) {
+    return IntStream.range(0, 1 + new Random().nextInt(5))
+      .mapToObj(i -> newNotification(projectKey))
+      .collect(Collectors.toSet());
+  }
+
+  private static QGChangeNotification newNotification(@Nullable String projectKey) {
+    QGChangeNotification notification = mock(QGChangeNotification.class);
+    when(notification.getProjectKey()).thenReturn(projectKey);
+    return notification;
+  }
+
+  private static String emailOf(String assignee1) {
+    return assignee1 + "@giraffe";
+  }
+
+
+
+}
index 5800cca5d349558323cafc092207e7506a6d0a33..4b557e8c96b0b745ed90a43565a1fdb1b3c268e4 100644 (file)
@@ -26,10 +26,10 @@ import java.util.function.Predicate;
 import org.sonar.api.Startable;
 import org.sonar.api.config.Configuration;
 import org.sonar.process.ProcessProperties;
-import org.sonar.server.qualitygate.notification.NewAlerts;
 import org.sonar.server.issue.notification.DoNotFixNotificationHandler;
 import org.sonar.server.issue.notification.NewIssuesNotificationHandler;
 import org.sonar.server.notification.NotificationCenter;
+import org.sonar.server.qualitygate.notification.QGChangeNotificationHandler;
 
 import static org.sonar.core.util.stream.MoreCollectors.toList;
 import static org.sonar.server.notification.NotificationDispatcherMetadata.GLOBAL_NOTIFICATION;
@@ -38,7 +38,7 @@ import static org.sonar.server.notification.NotificationDispatcherMetadata.PER_P
 public class DispatchersImpl implements Dispatchers, Startable {
 
   private static final Set<String> GLOBAL_DISPATCHERS_TO_IGNORE_ON_SONAR_CLOUD = ImmutableSet.of(
-    NewAlerts.KEY,
+    QGChangeNotificationHandler.KEY,
     DoNotFixNotificationHandler.KEY,
     NewIssuesNotificationHandler.KEY);
 
index bdb47697472196da283cffad69a626867da938c5..e05feaf20fd8569f7e09f2fdc0dc9a7ec844aaa9 100644 (file)
@@ -64,7 +64,6 @@ import org.sonar.server.es.RecoveryIndexer;
 import org.sonar.server.es.metadata.EsDbCompatibilityImpl;
 import org.sonar.server.es.metadata.MetadataIndex;
 import org.sonar.server.es.metadata.MetadataIndexDefinition;
-import org.sonar.server.qualitygate.notification.NewAlerts;
 import org.sonar.server.extension.CoreExtensionBootstraper;
 import org.sonar.server.extension.CoreExtensionStopper;
 import org.sonar.server.favorite.FavoriteModule;
@@ -151,6 +150,7 @@ import org.sonar.server.projecttag.ws.ProjectTagsWsModule;
 import org.sonar.server.property.InternalPropertiesImpl;
 import org.sonar.server.property.ws.PropertiesWs;
 import org.sonar.server.qualitygate.QualityGateModule;
+import org.sonar.server.qualitygate.notification.QGChangeNotificationHandler;
 import org.sonar.server.qualityprofile.BuiltInQProfileDefinitionsBridge;
 import org.sonar.server.qualityprofile.BuiltInQProfileRepositoryImpl;
 import org.sonar.server.qualityprofile.BuiltInQualityProfilesNotificationDispatcher;
@@ -385,8 +385,8 @@ public class PlatformLevel4 extends PlatformLevel {
       ComponentService.class,
       ComponentUpdater.class,
       ComponentFinder.class,
-      NewAlerts.class,
-      NewAlerts.newMetadata(),
+      QGChangeNotificationHandler.class,
+      QGChangeNotificationHandler.newMetadata(),
       ComponentCleanerService.class,
       ComponentIndexDefinition.class,
       ComponentIndex.class,
index 8b6c25b88fe63a1295d86fde1d2272c5cbb27412..1fcd38247fb3ff3ca808b4fe0a7adef80c686511 100644 (file)
@@ -22,12 +22,12 @@ package org.sonar.server.notification.ws;
 import org.junit.Test;
 import org.sonar.api.config.internal.MapSettings;
 import org.sonar.api.notifications.NotificationChannel;
-import org.sonar.server.qualitygate.notification.NewAlerts;
 import org.sonar.server.issue.notification.DoNotFixNotificationHandler;
 import org.sonar.server.issue.notification.MyNewIssuesNotificationHandler;
 import org.sonar.server.issue.notification.NewIssuesNotificationHandler;
 import org.sonar.server.notification.NotificationCenter;
 import org.sonar.server.notification.NotificationDispatcherMetadata;
+import org.sonar.server.qualitygate.notification.QGChangeNotificationHandler;
 
 import static org.assertj.core.api.Java6Assertions.assertThat;
 import static org.sonar.server.notification.NotificationDispatcherMetadata.GLOBAL_NOTIFICATION;
@@ -42,7 +42,7 @@ public class DispatchersImplTest {
         .setProperty(PER_PROJECT_NOTIFICATION, "true"),
       NotificationDispatcherMetadata.create(NewIssuesNotificationHandler.KEY)
         .setProperty(GLOBAL_NOTIFICATION, "true"),
-      NotificationDispatcherMetadata.create(NewAlerts.KEY)
+      NotificationDispatcherMetadata.create(QGChangeNotificationHandler.KEY)
         .setProperty(GLOBAL_NOTIFICATION, "true")
         .setProperty(PER_PROJECT_NOTIFICATION, "true"),
       NotificationDispatcherMetadata.create(DoNotFixNotificationHandler.KEY)
@@ -60,7 +60,7 @@ public class DispatchersImplTest {
     underTest.start();
 
     assertThat(underTest.getGlobalDispatchers()).containsExactly(
-      NewAlerts.KEY, DoNotFixNotificationHandler.KEY, NewIssuesNotificationHandler.KEY, MyNewIssuesNotificationHandler.KEY);
+      QGChangeNotificationHandler.KEY, DoNotFixNotificationHandler.KEY, NewIssuesNotificationHandler.KEY, MyNewIssuesNotificationHandler.KEY);
   }
 
   @Test
@@ -77,7 +77,7 @@ public class DispatchersImplTest {
     underTest.start();
 
     assertThat(underTest.getProjectDispatchers()).containsExactly(
-      NewAlerts.KEY, DoNotFixNotificationHandler.KEY, MyNewIssuesNotificationHandler.KEY);
+      QGChangeNotificationHandler.KEY, DoNotFixNotificationHandler.KEY, MyNewIssuesNotificationHandler.KEY);
   }
 
   @Test
@@ -87,6 +87,6 @@ public class DispatchersImplTest {
     underTest.start();
 
     assertThat(underTest.getProjectDispatchers()).containsOnly(
-      MyNewIssuesNotificationHandler.KEY, NewAlerts.KEY, DoNotFixNotificationHandler.KEY);
+      MyNewIssuesNotificationHandler.KEY, QGChangeNotificationHandler.KEY, DoNotFixNotificationHandler.KEY);
   }
 }