From 5317ebf32dc7906af57f378dc7afcf0458f737ec Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Mon, 25 Mar 2019 10:24:56 +0100 Subject: [PATCH] SONAR-11753 move "New Issues" notification to email specific algo --- .../step/SendIssueNotificationsStep.java | 12 +- .../step/SendIssueNotificationsStepTest.java | 3 +- .../container/ComputeEngineContainerImpl.java | 6 +- .../notification/MyNewIssuesNotification.java | 5 - .../notification/NewIssuesNotification.java | 6 + .../NewIssuesNotificationDispatcher.java | 71 ----- .../NewIssuesNotificationHandler.java | 89 +++++++ .../MyNewIssuesNotificationTest.java | 16 -- .../NewIssuesNotificationDispatcherTest.java | 70 ----- .../NewIssuesNotificationHandlerTest.java | 252 ++++++++++++++++++ .../NewIssuesNotificationTest.java | 25 ++ .../notification/ws/DispatchersImpl.java | 4 +- .../platformlevel/PlatformLevel4.java | 6 +- .../notification/ws/DispatchersImplTest.java | 6 +- 14 files changed, 393 insertions(+), 178 deletions(-) delete mode 100644 server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/NewIssuesNotificationDispatcher.java create mode 100644 server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/NewIssuesNotificationHandler.java delete mode 100644 server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationDispatcherTest.java create mode 100644 server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationHandlerTest.java diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/SendIssueNotificationsStep.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/SendIssueNotificationsStep.java index dee1f1bb373..02c2e2a7f9e 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/SendIssueNotificationsStep.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/SendIssueNotificationsStep.java @@ -57,6 +57,7 @@ import org.sonar.server.issue.notification.NewIssuesNotificationFactory; import org.sonar.server.issue.notification.NewIssuesStatistics; import org.sonar.server.notification.NotificationService; +import static java.util.Collections.singleton; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toMap; import static java.util.stream.StreamSupport.stream; @@ -101,9 +102,9 @@ public class SendIssueNotificationsStep implements ComputationStep { Component project = treeRootHolder.getRoot(); NotificationStatistics notificationStatistics = new NotificationStatistics(); // FIXME do we still need this fail fast? -// if (service.hasProjectSubscribersForTypes(project.getUuid(), NOTIF_TYPES)) { - doExecute(notificationStatistics, project); -// } + // if (service.hasProjectSubscribersForTypes(project.getUuid(), NOTIF_TYPES)) { + doExecute(notificationStatistics, project); + // } notificationStatistics.dumpTo(context); } @@ -171,8 +172,11 @@ public class SendIssueNotificationsStep implements ComputationStep { .setAnalysisDate(new Date(analysisDate)) .setStatistics(project.getName(), globalStatistics) .setDebt(Duration.create(globalStatistics.effort().getOnLeak())); - notificationStatistics.newIssuesDeliveries += service.deliver(notification); + notificationStatistics.newIssuesDeliveries += service.deliverEmails(singleton(notification)); notificationStatistics.newIssues++; + + // compatibility with old API + notificationStatistics.newIssuesDeliveries += service.deliver(notification); } private void sendMyNewIssuesNotification(NewIssuesStatistics statistics, Component project, long analysisDate, NotificationStatistics notificationStatistics) { diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/SendIssueNotificationsStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/SendIssueNotificationsStepTest.java index 37caed25832..f17efdeb442 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/SendIssueNotificationsStepTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/SendIssueNotificationsStepTest.java @@ -286,9 +286,10 @@ public class SendIssueNotificationsStepTest extends BaseStepTest { TestComputationStepContext context = new TestComputationStepContext(); underTest.execute(context); - verify(notificationService).deliver(newIssuesNotificationMock); + verify(notificationService).deliverEmails(ImmutableSet.of(newIssuesNotificationMock)); verify(notificationService).deliverEmails(ImmutableSet.of(myNewIssuesNotificationMock)); // old API compatibility call + verify(notificationService).deliver(newIssuesNotificationMock); verify(notificationService).deliver(myNewIssuesNotificationMock); verify(myNewIssuesNotificationMock).setAssignee(any(UserDto.class)); verify(myNewIssuesNotificationMock).setProject(PROJECT.getKey(), PROJECT.getName(), null, null); 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 f0d236a35fc..a09aaff37b5 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 @@ -105,8 +105,8 @@ import org.sonar.server.issue.notification.IssueChangesEmailTemplate; import org.sonar.server.issue.notification.MyNewIssuesEmailTemplate; import org.sonar.server.issue.notification.MyNewIssuesNotificationHandler; import org.sonar.server.issue.notification.NewIssuesEmailTemplate; -import org.sonar.server.issue.notification.NewIssuesNotificationDispatcher; import org.sonar.server.issue.notification.NewIssuesNotificationFactory; +import org.sonar.server.issue.notification.NewIssuesNotificationHandler; import org.sonar.server.issue.workflow.FunctionExecutor; import org.sonar.server.issue.workflow.IssueWorkflow; import org.sonar.server.l18n.ServerI18n; @@ -406,8 +406,8 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer { IssueChangesEmailTemplate.class, ChangesOnMyIssueNotificationDispatcher.class, ChangesOnMyIssueNotificationDispatcher.newMetadata(), - NewIssuesNotificationDispatcher.class, - NewIssuesNotificationDispatcher.newMetadata(), + NewIssuesNotificationHandler.class, + NewIssuesNotificationHandler.newMetadata(), MyNewIssuesNotificationHandler.class, MyNewIssuesNotificationHandler.newMetadata(), DoNotFixNotificationDispatcher.class, diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/MyNewIssuesNotification.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/MyNewIssuesNotification.java index a1e11bafa11..54d0d1aeb25 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/MyNewIssuesNotification.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/MyNewIssuesNotification.java @@ -44,11 +44,6 @@ public class MyNewIssuesNotification extends NewIssuesNotification { return this; } - @CheckForNull - public String getProjectKey() { - return getFieldValue("projectKey"); - } - @CheckForNull public String getAssignee() { return getFieldValue(FIELD_ASSIGNEE); diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/NewIssuesNotification.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/NewIssuesNotification.java index b50ca9ca2a1..28eb3f673b3 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/NewIssuesNotification.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/NewIssuesNotification.java @@ -28,6 +28,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.ToIntFunction; +import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.sonar.api.notifications.Notification; import org.sonar.api.rule.RuleKey; @@ -92,6 +93,11 @@ public class NewIssuesNotification extends Notification { return this; } + @CheckForNull + public String getProjectKey() { + return getFieldValue(FIELD_PROJECT_KEY); + } + public NewIssuesNotification setProjectVersion(@Nullable String version) { if (version != null) { setFieldValue(FIELD_PROJECT_VERSION, version); diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/NewIssuesNotificationDispatcher.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/NewIssuesNotificationDispatcher.java deleted file mode 100644 index d71ce535b8f..00000000000 --- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/NewIssuesNotificationDispatcher.java +++ /dev/null @@ -1,71 +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.issue.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 when new issues are introduced during project analysis" - */ -public class NewIssuesNotificationDispatcher extends NotificationDispatcher { - - public static final String KEY = "NewIssues"; - private final NotificationManager manager; - - public NewIssuesNotificationDispatcher(NotificationManager manager) { - super(NewIssuesNotification.TYPE); - this.manager = manager; - } - - @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"); - Multimap subscribedRecipients = manager - .findSubscribedRecipientsForDispatcher(this, projectKey, ALL_MUST_HAVE_ROLE_USER); - - for (Map.Entry> 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/issue/notification/NewIssuesNotificationHandler.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/NewIssuesNotificationHandler.java new file mode 100644 index 00000000000..0dd5ee1e096 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/NewIssuesNotificationHandler.java @@ -0,0 +1,89 @@ +/* + * 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.issue.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 NewIssuesNotificationHandler implements NotificationHandler { + + public static final String KEY = "NewIssues"; + + private final NotificationManager notificationManager; + private final EmailNotificationChannel emailNotificationChannel; + + public NewIssuesNotificationHandler(NotificationManager notificationManager, EmailNotificationChannel emailNotificationChannel) { + this.notificationManager = notificationManager; + this.emailNotificationChannel = emailNotificationChannel; + } + + 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 Class getNotificationClass() { + return NewIssuesNotification.class; + } + + @Override + public int deliver(Collection notifications) { + if (notifications.isEmpty() || !emailNotificationChannel.isActivated()) { + return 0; + } + + Multimap notificationsByProjectKey = notifications.stream() + .filter(t -> t.getProjectKey() != null) + .collect(index(NewIssuesNotification::getProjectKey)); + if (notificationsByProjectKey.isEmpty()) { + return 0; + } + + Set 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 toEmailDeliveryRequests(String projectKey, Collection 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/issue/notification/MyNewIssuesNotificationTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/MyNewIssuesNotificationTest.java index 7669b6805e6..de3eb27e3af 100644 --- a/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/MyNewIssuesNotificationTest.java +++ b/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/MyNewIssuesNotificationTest.java @@ -25,7 +25,6 @@ import org.sonar.db.DbClient; import org.sonar.db.user.UserDto; import org.sonar.db.user.UserTesting; -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.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_ASSIGNEE; @@ -50,19 +49,4 @@ public class MyNewIssuesNotificationTest { assertThat(underTest.getType()).isEqualTo(MyNewIssuesNotification.MY_NEW_ISSUES_NOTIF_TYPE); } - @Test - public void getProjectKey_returns_null_if_setProject_has_no_been_called() { - assertThat(underTest.getProjectKey()).isNull(); - } - - @Test - public void getProjectKey_returns_projectKey_if_setProject_has_been_called() { - String projectKey = randomAlphabetic(5); - String projectName = randomAlphabetic(6); - String branchName = randomAlphabetic(7); - String pullRequest = randomAlphabetic(8); - underTest.setProject(projectKey, projectName, branchName, pullRequest); - - assertThat(underTest.getProjectKey()).isEqualTo(projectKey); - } } diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationDispatcherTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationDispatcherTest.java deleted file mode 100644 index 182d5933493..00000000000 --- a/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationDispatcherTest.java +++ /dev/null @@ -1,70 +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.issue.notification; - -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; -import org.junit.Before; -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.*; - -public class NewIssuesNotificationDispatcherTest { - - private NotificationManager notifications = mock(NotificationManager.class); - private NotificationDispatcher.Context context = mock(NotificationDispatcher.Context.class); - private NotificationChannel emailChannel = mock(NotificationChannel.class); - private NotificationChannel twitterChannel = mock(NotificationChannel.class); - private NewIssuesNotificationDispatcher dispatcher = mock(NewIssuesNotificationDispatcher.class); - - @Before - public void setUp() { - dispatcher = new NewIssuesNotificationDispatcher(notifications); - } - - @Test - public void shouldNotDispatchIfNotNewViolationsNotification() { - Notification notification = new Notification("other-notif"); - dispatcher.performDispatch(notification, context); - - verify(context, never()).addUser(any(String.class), any(NotificationChannel.class)); - } - - @Test - public void shouldDispatchToUsersWhoHaveSubscribedAndFlaggedProjectAsFavourite() { - Multimap recipients = HashMultimap.create(); - recipients.put("user1", emailChannel); - recipients.put("user2", twitterChannel); - when(notifications.findSubscribedRecipientsForDispatcher(dispatcher, "struts", new NotificationManager.SubscriberPermissionsOnProject(UserRole.USER))).thenReturn(recipients); - - Notification notification = new Notification(NewIssuesNotification.TYPE) - .setFieldValue("projectKey", "struts"); - dispatcher.performDispatch(notification, context); - - verify(context).addUser("user1", emailChannel); - verify(context).addUser("user2", twitterChannel); - verifyNoMoreInteractions(context); - } -} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationHandlerTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationHandlerTest.java new file mode 100644 index 00000000000..c6eb8954195 --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationHandlerTest.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.issue.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.NotificationManager.EmailRecipient; +import org.sonar.server.notification.email.EmailNotificationChannel; +import org.sonar.server.notification.email.EmailNotificationChannel.EmailDeliveryRequest; + +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 NewIssuesNotificationHandlerTest { + private static final String NEW_ISSUES_DISPATCHER_KEY = "NewIssues"; + private NotificationManager notificationManager = mock(NotificationManager.class); + private EmailNotificationChannel emailNotificationChannel = mock(EmailNotificationChannel.class); + private NewIssuesNotificationHandler underTest = new NewIssuesNotificationHandler(notificationManager, emailNotificationChannel); + + @Test + public void verify_myNewIssues_notification_dispatcher_key() { + NotificationDispatcherMetadata metadata = NewIssuesNotificationHandler.newMetadata(); + + assertThat(metadata.getDispatcherKey()).isEqualTo(NEW_ISSUES_DISPATCHER_KEY); + } + + @Test + public void myNewIssues_notification_is_enable_at_global_level() { + NotificationDispatcherMetadata metadata = NewIssuesNotificationHandler.newMetadata(); + + assertThat(metadata.getProperty(GLOBAL_NOTIFICATION)).isEqualTo("true"); + } + + @Test + public void myNewIssues_notification_is_enable_at_project_level() { + NotificationDispatcherMetadata metadata = NewIssuesNotificationHandler.newMetadata(); + + assertThat(metadata.getProperty(PER_PROJECT_NOTIFICATION)).isEqualTo("true"); + } + + @Test + public void getNotificationClass_is_NewIssuesNotification() { + assertThat(underTest.getNotificationClass()).isEqualTo(NewIssuesNotification.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 notifications = IntStream.range(0, 1 + new Random().nextInt(10)) + .mapToObj(i -> mock(NewIssuesNotification.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 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_NewIssue_notifications() { + String projectKey = randomAlphabetic(12); + NewIssuesNotification notification = newNotification(projectKey); + when(emailNotificationChannel.isActivated()).thenReturn(true); + when(notificationManager.findSubscribedEmailRecipients(NEW_ISSUES_DISPATCHER_KEY, projectKey, ALL_MUST_HAVE_ROLE_USER)) + .thenReturn(emptySet()); + + int deliver = underTest.deliver(Collections.singleton(notification)); + + assertThat(deliver).isZero(); + verify(notificationManager).findSubscribedEmailRecipients(NEW_ISSUES_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 withProjectKey = IntStream.range(0, 1 + new Random().nextInt(5)) + .mapToObj(i -> newNotification(projectKey)) + .collect(toSet()); + Set noProjectKey = IntStream.range(0, 1 + new Random().nextInt(5)) + .mapToObj(i -> newNotification(null)) + .collect(toSet()); + Set emailRecipients = IntStream.range(0, 1 + new Random().nextInt(10)) + .mapToObj(i -> "user_" + i) + .map(login -> new EmailRecipient(login, emailOf(login))) + .collect(toSet()); + Set expectedRequests = emailRecipients.stream() + .flatMap(emailRecipient -> withProjectKey.stream().map(notif -> new EmailDeliveryRequest(emailRecipient.getEmail(), notif))) + .collect(toSet()); + when(emailNotificationChannel.isActivated()).thenReturn(true); + when(notificationManager.findSubscribedEmailRecipients(NEW_ISSUES_DISPATCHER_KEY, projectKey, ALL_MUST_HAVE_ROLE_USER)) + .thenReturn(emailRecipients); + + Set notifications = Stream.of(withProjectKey.stream(), noProjectKey.stream()) + .flatMap(t -> t) + .collect(toSet()); + int deliver = underTest.deliver(notifications); + + assertThat(deliver).isZero(); + verify(notificationManager).findSubscribedEmailRecipients(NEW_ISSUES_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_MyNewIssue_notifications() { + String projectKey1 = randomAlphabetic(10); + String projectKey2 = randomAlphabetic(11); + Set notifications1 = randomSetOfNotifications(projectKey1); + Set notifications2 = randomSetOfNotifications(projectKey2); + when(emailNotificationChannel.isActivated()).thenReturn(true); + + Set emailRecipients1 = IntStream.range(0, 1 + new Random().nextInt(10)) + .mapToObj(i -> "user1_" + i) + .map(login -> new EmailRecipient(login, emailOf(login))) + .collect(toSet()); + Set emailRecipients2 = IntStream.range(0, 1 + new Random().nextInt(10)) + .mapToObj(i -> "user2_" + i) + .map(login -> new EmailRecipient(login, emailOf(login))) + .collect(toSet()); + when(notificationManager.findSubscribedEmailRecipients(NEW_ISSUES_DISPATCHER_KEY, projectKey1, ALL_MUST_HAVE_ROLE_USER)) + .thenReturn(emailRecipients1); + when(notificationManager.findSubscribedEmailRecipients(NEW_ISSUES_DISPATCHER_KEY, projectKey2, ALL_MUST_HAVE_ROLE_USER)) + .thenReturn(emailRecipients2); + Set expectedRequests = Stream.concat( + emailRecipients1.stream() + .flatMap(emailRecipient -> notifications1.stream().map(notif -> new EmailDeliveryRequest(emailRecipient.getEmail(), notif))), + emailRecipients2.stream() + .flatMap(emailRecipient -> notifications2.stream().map(notif -> new EmailDeliveryRequest(emailRecipient.getEmail(), notif)))) + .collect(toSet()); + + int deliver = underTest.deliver(Stream.concat(notifications1.stream(), notifications2.stream()).collect(toSet())); + + assertThat(deliver).isZero(); + verify(notificationManager).findSubscribedEmailRecipients(NEW_ISSUES_DISPATCHER_KEY, projectKey1, ALL_MUST_HAVE_ROLE_USER); + verify(notificationManager).findSubscribedEmailRecipients(NEW_ISSUES_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 notifications1 = randomSetOfNotifications(projectKey1); + Set notifications2 = randomSetOfNotifications(projectKey2); + when(emailNotificationChannel.isActivated()).thenReturn(true); + when(notificationManager.findSubscribedEmailRecipients(NEW_ISSUES_DISPATCHER_KEY, projectKey1, ALL_MUST_HAVE_ROLE_USER)) + .thenReturn(emptySet()); + when(notificationManager.findSubscribedEmailRecipients(NEW_ISSUES_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(NEW_ISSUES_DISPATCHER_KEY, projectKey1, ALL_MUST_HAVE_ROLE_USER); + verify(notificationManager).findSubscribedEmailRecipients(NEW_ISSUES_DISPATCHER_KEY, projectKey2, ALL_MUST_HAVE_ROLE_USER); + verifyNoMoreInteractions(notificationManager); + verify(emailNotificationChannel).isActivated(); + verifyNoMoreInteractions(emailNotificationChannel); + } + + private static Set randomSetOfNotifications(@Nullable String projectKey) { + return IntStream.range(0, 1 + new Random().nextInt(5)) + .mapToObj(i -> newNotification(projectKey)) + .collect(Collectors.toSet()); + } + + private static NewIssuesNotification newNotification(@Nullable String projectKey) { + NewIssuesNotification notification = mock(NewIssuesNotification.class); + when(notification.getProjectKey()).thenReturn(projectKey); + return notification; + } + + private static String emailOf(String assignee1) { + return assignee1 + "@donut"; + } + +} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationTest.java index c7f54833a88..e3d7b02dbb0 100644 --- a/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationTest.java +++ b/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationTest.java @@ -34,6 +34,7 @@ import org.sonar.db.user.UserDto; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; +import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; import static org.assertj.core.api.Assertions.assertThat; import static org.sonar.api.rules.RuleType.BUG; @@ -97,6 +98,30 @@ public class NewIssuesNotificationTest { assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_VERSION)).isNull(); } + @Test + public void getProjectKey_returns_null_if_setProject_has_no_been_called() { + assertThat(underTest.getProjectKey()).isNull(); + } + + @Test + public void getProjectKey_returns_projectKey_if_setProject_has_been_called() { + String projectKey = randomAlphabetic(5); + String projectName = randomAlphabetic(6); + String branchName = randomAlphabetic(7); + String pullRequest = randomAlphabetic(8); + underTest.setProject(projectKey, projectName, branchName, pullRequest); + + assertThat(underTest.getProjectKey()).isEqualTo(projectKey); + } + + @Test + public void getProjectKey_returns_value_of_field_projectKey() { + String projectKey = randomAlphabetic(5); + underTest.setFieldValue("projectKey", projectKey); + + assertThat(underTest.getProjectKey()).isEqualTo(projectKey); + } + @Test public void set_date() { Date date = new Date(); 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 605aa2583cf..758c6aff096 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 @@ -28,7 +28,7 @@ import org.sonar.api.config.Configuration; import org.sonar.process.ProcessProperties; import org.sonar.server.event.NewAlerts; import org.sonar.server.issue.notification.DoNotFixNotificationDispatcher; -import org.sonar.server.issue.notification.NewIssuesNotificationDispatcher; +import org.sonar.server.issue.notification.NewIssuesNotificationHandler; import org.sonar.server.notification.NotificationCenter; import static org.sonar.core.util.stream.MoreCollectors.toList; @@ -40,7 +40,7 @@ public class DispatchersImpl implements Dispatchers, Startable { private static final Set GLOBAL_DISPATCHERS_TO_IGNORE_ON_SONAR_CLOUD = ImmutableSet.of( NewAlerts.KEY, DoNotFixNotificationDispatcher.KEY, - NewIssuesNotificationDispatcher.KEY); + NewIssuesNotificationHandler.KEY); private final NotificationCenter notificationCenter; private final Configuration configuration; 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 b44de2ea773..d1960a8d73e 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 @@ -86,8 +86,8 @@ import org.sonar.server.issue.notification.IssueChangesEmailTemplate; import org.sonar.server.issue.notification.MyNewIssuesEmailTemplate; import org.sonar.server.issue.notification.MyNewIssuesNotificationHandler; import org.sonar.server.issue.notification.NewIssuesEmailTemplate; -import org.sonar.server.issue.notification.NewIssuesNotificationDispatcher; import org.sonar.server.issue.notification.NewIssuesNotificationFactory; +import org.sonar.server.issue.notification.NewIssuesNotificationHandler; import org.sonar.server.issue.ws.IssueWsModule; import org.sonar.server.language.ws.LanguageWs; import org.sonar.server.log.ServerLogging; @@ -412,8 +412,8 @@ public class PlatformLevel4 extends PlatformLevel { IssueChangesEmailTemplate.class, ChangesOnMyIssueNotificationDispatcher.class, ChangesOnMyIssueNotificationDispatcher.newMetadata(), - NewIssuesNotificationDispatcher.class, - NewIssuesNotificationDispatcher.newMetadata(), + NewIssuesNotificationHandler.class, + NewIssuesNotificationHandler.newMetadata(), MyNewIssuesNotificationHandler.class, MyNewIssuesNotificationHandler.newMetadata(), DoNotFixNotificationDispatcher.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 7fd2cdc1eee..b7b00908b34 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 @@ -25,7 +25,7 @@ import org.sonar.api.notifications.NotificationChannel; import org.sonar.server.event.NewAlerts; import org.sonar.server.issue.notification.DoNotFixNotificationDispatcher; import org.sonar.server.issue.notification.MyNewIssuesNotificationHandler; -import org.sonar.server.issue.notification.NewIssuesNotificationDispatcher; +import org.sonar.server.issue.notification.NewIssuesNotificationHandler; import org.sonar.server.notification.NotificationCenter; import org.sonar.server.notification.NotificationDispatcherMetadata; @@ -40,7 +40,7 @@ public class DispatchersImplTest { NotificationDispatcherMetadata.create(MyNewIssuesNotificationHandler.KEY) .setProperty(GLOBAL_NOTIFICATION, "true") .setProperty(PER_PROJECT_NOTIFICATION, "true"), - NotificationDispatcherMetadata.create(NewIssuesNotificationDispatcher.KEY) + NotificationDispatcherMetadata.create(NewIssuesNotificationHandler.KEY) .setProperty(GLOBAL_NOTIFICATION, "true"), NotificationDispatcherMetadata.create(NewAlerts.KEY) .setProperty(GLOBAL_NOTIFICATION, "true") @@ -60,7 +60,7 @@ public class DispatchersImplTest { underTest.start(); assertThat(underTest.getGlobalDispatchers()).containsExactly( - NewAlerts.KEY, DoNotFixNotificationDispatcher.KEY, NewIssuesNotificationDispatcher.KEY, MyNewIssuesNotificationHandler.KEY); + NewAlerts.KEY, DoNotFixNotificationDispatcher.KEY, NewIssuesNotificationHandler.KEY, MyNewIssuesNotificationHandler.KEY); } @Test -- 2.39.5