diff options
13 files changed, 409 insertions, 183 deletions
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/QualityGateEventsStep.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/QualityGateEventsStep.java index a19b3c0553c..1d14912da2d 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/QualityGateEventsStep.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/QualityGateEventsStep.java @@ -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); } diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/QualityGateEventsStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/QualityGateEventsStepTest.java index 12251c53a1b..23e7c2d4d5b 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/QualityGateEventsStepTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/QualityGateEventsStepTest.java @@ -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()); diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java index f17fed85c8a..2f43b9b4ad3 100644 --- a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java +++ b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java @@ -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/NewAlerts.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/notification/NewAlerts.java deleted file mode 100644 index 61adbc65d4d..00000000000 --- a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/notification/NewAlerts.java +++ /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/AlertsEmailTemplate.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/notification/QGChangeEmailTemplate.java index 1fa513208a4..284658af2db 100644 --- 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/QGChangeEmailTemplate.java @@ -32,11 +32,11 @@ import org.sonar.plugins.emailnotifications.api.EmailTemplate; * * @since 3.5 */ -public class AlertsEmailTemplate extends EmailTemplate { +public class QGChangeEmailTemplate extends EmailTemplate { private EmailSettings configuration; - public AlertsEmailTemplate(EmailSettings configuration) { + public QGChangeEmailTemplate(EmailSettings configuration) { this.configuration = configuration; } 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 index 00000000000..07ff928af93 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/notification/QGChangeNotification.java @@ -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 index 00000000000..18402cff45e --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/notification/QGChangeNotificationHandler.java @@ -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/NewAlertsTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/notification/NewAlertsTest.java deleted file mode 100644 index 48a7b9f036c..00000000000 --- a/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/notification/NewAlertsTest.java +++ /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/AlertsEmailTemplateTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/notification/QGChangeEmailTemplateTest.java index e8a06c216bb..c6ca1b64d4c 100644 --- 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/QGChangeEmailTemplateTest.java @@ -24,7 +24,6 @@ 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; @@ -32,15 +31,15 @@ import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public class AlertsEmailTemplateTest { +public class QGChangeEmailTemplateTest { - private AlertsEmailTemplate template; + private QGChangeEmailTemplate template; @Before public void setUp() { EmailSettings configuration = mock(EmailSettings.class); when(configuration.getServerBaseURL()).thenReturn("http://nemo.sonarsource.org"); - template = new AlertsEmailTemplate(configuration); + template = new QGChangeEmailTemplate(configuration); } @Test 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 index 00000000000..a9faf4d26db --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/notification/QGChangeNotificationHandlerTest.java @@ -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"; + } + + + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/notification/ws/DispatchersImpl.java b/server/sonar-server/src/main/java/org/sonar/server/notification/ws/DispatchersImpl.java index 5800cca5d34..4b557e8c96b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/notification/ws/DispatchersImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/notification/ws/DispatchersImpl.java @@ -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); diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index bdb47697472..e05feaf20fd 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -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, diff --git a/server/sonar-server/src/test/java/org/sonar/server/notification/ws/DispatchersImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/notification/ws/DispatchersImplTest.java index 8b6c25b88fe..1fcd38247fb 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/notification/ws/DispatchersImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/notification/ws/DispatchersImplTest.java @@ -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); } } |