package org.sonar.ce.task.projectanalysis.notification;
import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
+import org.sonar.api.notifications.Notification;
-import static java.util.Objects.requireNonNull;
+public class ReportAnalysisFailureNotification extends Notification {
-public class ReportAnalysisFailureNotification {
public static final String TYPE = "ce-report-task-failure";
- private final Project project;
- private final Task task;
- private final String errorMessage;
-
- public ReportAnalysisFailureNotification(Project project, Task task, @Nullable String errorMessage) {
- this.project = requireNonNull(project, "project can't be null");
- this.task = requireNonNull(task, "task can't be null");
- this.errorMessage = errorMessage;
- }
-
- public Project getProject() {
- return project;
- }
-
- public Task getTask() {
- return task;
+ public ReportAnalysisFailureNotification() {
+ super(TYPE);
}
@CheckForNull
- public String getErrorMessage() {
- return errorMessage;
- }
-
- public static final class Project {
- private final String uuid;
- private final String key;
- private final String name;
- private final String branchName;
-
- public Project(String uuid, String key, String name, @Nullable String branchName) {
- this.uuid = requireNonNull(uuid, "uuid can't be null");
- this.key = requireNonNull(key, "key can't be null");
- this.name = requireNonNull(name, "name can't be null");
- this.branchName = branchName;
- }
-
- public String getUuid() {
- return uuid;
- }
-
- public String getKey() {
- return key;
- }
-
- public String getName() {
- return name;
- }
-
- @CheckForNull
- public String getBranchName() {
- return branchName;
- }
- }
-
- public static final class Task {
- private final String uuid;
- private final long createdAt;
- private final long failedAt;
-
- public Task(String uuid, long createdAt, long failedAt) {
- this.uuid = requireNonNull(uuid, "uuid can't be null");
- this.createdAt = createdAt;
- this.failedAt = failedAt;
- }
-
- public String getUuid() {
- return uuid;
- }
-
- public long getCreatedAt() {
- return createdAt;
- }
-
- public long getFailedAt() {
- return failedAt;
- }
+ public String getProjectKey() {
+ return getFieldValue("project.key");
}
}
--- /dev/null
+/*
+ * 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.ce.task.projectanalysis.notification;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import static java.util.Objects.requireNonNull;
+
+public class ReportAnalysisFailureNotificationBuilder {
+ private final Project project;
+ private final Task task;
+ private final String errorMessage;
+
+ public ReportAnalysisFailureNotificationBuilder(Project project, Task task, @Nullable String errorMessage) {
+ this.project = requireNonNull(project, "project can't be null");
+ this.task = requireNonNull(task, "task can't be null");
+ this.errorMessage = errorMessage;
+ }
+
+ public Project getProject() {
+ return project;
+ }
+
+ public Task getTask() {
+ return task;
+ }
+
+ @CheckForNull
+ public String getErrorMessage() {
+ return errorMessage;
+ }
+
+ public static final class Project {
+ private final String uuid;
+ private final String key;
+ private final String name;
+ private final String branchName;
+
+ public Project(String uuid, String key, String name, @Nullable String branchName) {
+ this.uuid = requireNonNull(uuid, "uuid can't be null");
+ this.key = requireNonNull(key, "key can't be null");
+ this.name = requireNonNull(name, "name can't be null");
+ this.branchName = branchName;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @CheckForNull
+ public String getBranchName() {
+ return branchName;
+ }
+ }
+
+ public static final class Task {
+ private final String uuid;
+ private final long createdAt;
+ private final long failedAt;
+
+ public Task(String uuid, long createdAt, long failedAt) {
+ this.uuid = requireNonNull(uuid, "uuid can't be null");
+ this.createdAt = createdAt;
+ this.failedAt = failedAt;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public long getCreatedAt() {
+ return createdAt;
+ }
+
+ public long getFailedAt() {
+ return failedAt;
+ }
+ }
+}
+++ /dev/null
-/*
- * 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.ce.task.projectanalysis.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.api.web.UserRole;
-import org.sonar.server.notification.NotificationDispatcher;
-import org.sonar.server.notification.NotificationDispatcherMetadata;
-import org.sonar.server.notification.NotificationManager;
-import org.sonar.server.notification.NotificationManager.SubscriberPermissionsOnProject;
-
-public class ReportAnalysisFailureNotificationDispatcher extends NotificationDispatcher {
-
- public static final String KEY = "CeReportTaskFailure";
- private static final SubscriberPermissionsOnProject REQUIRED_SUBSCRIBER_PERMISSIONS = new SubscriberPermissionsOnProject(UserRole.ADMIN, UserRole.USER);
-
- private final NotificationManager manager;
-
- public ReportAnalysisFailureNotificationDispatcher(NotificationManager manager) {
- super(ReportAnalysisFailureNotification.TYPE);
- this.manager = manager;
- }
-
- 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 String getKey() {
- return KEY;
- }
-
- @Override
- public void dispatch(Notification notification, Context context) {
- String projectKey = notification.getFieldValue("project.key");
- Multimap<String, NotificationChannel> subscribedRecipients = manager
- .findSubscribedRecipientsForDispatcher(this, projectKey, REQUIRED_SUBSCRIBER_PERMISSIONS);
-
- for (Map.Entry<String, Collection<NotificationChannel>> channelsByRecipients : subscribedRecipients.asMap().entrySet()) {
- String userLogin = channelsByRecipients.getKey();
- for (NotificationChannel channel : channelsByRecipients.getValue()) {
- context.addUser(userLogin, channel);
- }
- }
- }
-}
@Override
public EmailMessage format(Notification notification) {
- if (!ReportAnalysisFailureNotification.TYPE.equals(notification.getType())) {
+ if (!(notification instanceof ReportAnalysisFailureNotification)) {
return null;
}
- ReportAnalysisFailureNotification taskFailureNotification = serializer.fromNotification(notification);
+ ReportAnalysisFailureNotificationBuilder taskFailureNotification = serializer.fromNotification((ReportAnalysisFailureNotification) notification);
String projectUuid = taskFailureNotification.getProject().getUuid();
String projectFullName = computeProjectFullName(taskFailureNotification.getProject());
.setMessage(message(projectFullName, taskFailureNotification));
}
- private static String computeProjectFullName(ReportAnalysisFailureNotification.Project project) {
+ private static String computeProjectFullName(ReportAnalysisFailureNotificationBuilder.Project project) {
String branchName = project.getBranchName();
if (branchName != null) {
return String.format("%s (%s)", project.getName(), branchName);
return String.format("%s: Background task in failure", projectFullName);
}
- private String message(String projectFullName, ReportAnalysisFailureNotification taskFailureNotification) {
- ReportAnalysisFailureNotification.Project project = taskFailureNotification.getProject();
- ReportAnalysisFailureNotification.Task task = taskFailureNotification.getTask();
+ private String message(String projectFullName, ReportAnalysisFailureNotificationBuilder taskFailureNotification) {
+ ReportAnalysisFailureNotificationBuilder.Project project = taskFailureNotification.getProject();
+ ReportAnalysisFailureNotificationBuilder.Task task = taskFailureNotification.getTask();
StringBuilder res = new StringBuilder();
res.append("Project:").append(TAB).append(projectFullName).append(LINE_RETURN);
--- /dev/null
+/*
+ * 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.ce.task.projectanalysis.notification;
+
+import com.google.common.collect.Multimap;
+import java.util.Collection;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Stream;
+import org.sonar.api.web.UserRole;
+import org.sonar.server.notification.NotificationDispatcherMetadata;
+import org.sonar.server.notification.NotificationHandler;
+import org.sonar.server.notification.NotificationManager;
+import org.sonar.server.notification.NotificationManager.SubscriberPermissionsOnProject;
+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;
+
+public class ReportAnalysisFailureNotificationHandler implements NotificationHandler<ReportAnalysisFailureNotification> {
+ private static final String KEY = "CeReportTaskFailure";
+ private static final NotificationDispatcherMetadata METADATA = NotificationDispatcherMetadata.create(KEY)
+ .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
+ .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
+ private static final SubscriberPermissionsOnProject REQUIRED_SUBSCRIBER_PERMISSIONS = new SubscriberPermissionsOnProject(UserRole.ADMIN, UserRole.USER);
+
+ private final NotificationManager notificationManager;
+ private final EmailNotificationChannel emailNotificationChannel;
+
+ public ReportAnalysisFailureNotificationHandler(NotificationManager notificationManager, EmailNotificationChannel emailNotificationChannel) {
+ this.notificationManager = notificationManager;
+ this.emailNotificationChannel = emailNotificationChannel;
+ }
+
+ @Override
+ public Optional<NotificationDispatcherMetadata> getMetadata() {
+ return Optional.of(METADATA);
+ }
+
+ public static NotificationDispatcherMetadata newMetadata() {
+ return METADATA;
+ }
+
+ @Override
+ public Class<ReportAnalysisFailureNotification> getNotificationClass() {
+ return ReportAnalysisFailureNotification.class;
+ }
+
+ @Override
+ public int deliver(Collection<ReportAnalysisFailureNotification> notifications) {
+ if (notifications.isEmpty() || !emailNotificationChannel.isActivated()) {
+ return 0;
+ }
+
+ Multimap<String, ReportAnalysisFailureNotification> notificationsByProjectKey = notifications.stream()
+ .filter(t -> t.getProjectKey() != null)
+ .collect(index(ReportAnalysisFailureNotification::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<ReportAnalysisFailureNotification> notifications) {
+ return notificationManager.findSubscribedEmailRecipients(KEY, projectKey, REQUIRED_SUBSCRIBER_PERMISSIONS)
+ .stream()
+ .flatMap(emailRecipient -> notifications.stream()
+ .map(notification -> new EmailDeliveryRequest(emailRecipient.getEmail(), notification)));
+ }
+}
@Override
protected void configureModule() {
add(
- ReportAnalysisFailureNotificationDispatcher.class,
- ReportAnalysisFailureNotificationDispatcher.newMetadata(),
+ ReportAnalysisFailureNotificationHandler.class,
+ ReportAnalysisFailureNotificationHandler.newMetadata(),
ReportAnalysisFailureNotificationSerializerImpl.class,
ReportAnalysisFailureNotificationEmailTemplate.class);
}
*/
package org.sonar.ce.task.projectanalysis.notification;
-import org.sonar.api.notifications.Notification;
-
public interface ReportAnalysisFailureNotificationSerializer {
- Notification toNotification(ReportAnalysisFailureNotification reportAnalysisFailureNotification);
+ ReportAnalysisFailureNotification toNotification(ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder);
- ReportAnalysisFailureNotification fromNotification(Notification notification);
+ ReportAnalysisFailureNotificationBuilder fromNotification(ReportAnalysisFailureNotification notification);
}
*/
package org.sonar.ce.task.projectanalysis.notification;
-import org.sonar.api.notifications.Notification;
-
import static java.lang.String.valueOf;
public class ReportAnalysisFailureNotificationSerializerImpl implements ReportAnalysisFailureNotificationSerializer {
private static final String FIELD_ERROR_MESSAGE = "error.message";
@Override
- public Notification toNotification(ReportAnalysisFailureNotification reportAnalysisFailureNotification) {
- return new Notification(ReportAnalysisFailureNotification.TYPE)
- .setFieldValue(FIELD_PROJECT_UUID, reportAnalysisFailureNotification.getProject().getUuid())
- .setFieldValue(FIELD_PROJECT_KEY, reportAnalysisFailureNotification.getProject().getKey())
- .setFieldValue(FIELD_PROJECT_NAME, reportAnalysisFailureNotification.getProject().getName())
- .setFieldValue(FIELD_PROJECT_BRANCH, reportAnalysisFailureNotification.getProject().getBranchName())
- .setFieldValue(FIELD_TASK_UUID, reportAnalysisFailureNotification.getTask().getUuid())
- .setFieldValue(FIELD_TASK_CREATED_AT, valueOf(reportAnalysisFailureNotification.getTask().getCreatedAt()))
- .setFieldValue(FIELD_TASK_FAILED_AT, valueOf(reportAnalysisFailureNotification.getTask().getFailedAt()))
- .setFieldValue(FIELD_ERROR_MESSAGE, reportAnalysisFailureNotification.getErrorMessage());
+ public ReportAnalysisFailureNotification toNotification(ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder) {
+ ReportAnalysisFailureNotification notification = new ReportAnalysisFailureNotification();
+ notification
+ .setFieldValue(FIELD_PROJECT_UUID, reportAnalysisFailureNotificationBuilder.getProject().getUuid())
+ .setFieldValue(FIELD_PROJECT_KEY, reportAnalysisFailureNotificationBuilder.getProject().getKey())
+ .setFieldValue(FIELD_PROJECT_NAME, reportAnalysisFailureNotificationBuilder.getProject().getName())
+ .setFieldValue(FIELD_PROJECT_BRANCH, reportAnalysisFailureNotificationBuilder.getProject().getBranchName())
+ .setFieldValue(FIELD_TASK_UUID, reportAnalysisFailureNotificationBuilder.getTask().getUuid())
+ .setFieldValue(FIELD_TASK_CREATED_AT, valueOf(reportAnalysisFailureNotificationBuilder.getTask().getCreatedAt()))
+ .setFieldValue(FIELD_TASK_FAILED_AT, valueOf(reportAnalysisFailureNotificationBuilder.getTask().getFailedAt()))
+ .setFieldValue(FIELD_ERROR_MESSAGE, reportAnalysisFailureNotificationBuilder.getErrorMessage());
+ return notification;
}
@Override
- public ReportAnalysisFailureNotification fromNotification(Notification notification) {
- return new ReportAnalysisFailureNotification(
- new ReportAnalysisFailureNotification.Project(
+ public ReportAnalysisFailureNotificationBuilder fromNotification(ReportAnalysisFailureNotification notification) {
+ return new ReportAnalysisFailureNotificationBuilder(
+ new ReportAnalysisFailureNotificationBuilder.Project(
notification.getFieldValue(FIELD_PROJECT_UUID),
notification.getFieldValue(FIELD_PROJECT_KEY),
notification.getFieldValue(FIELD_PROJECT_NAME),
notification.getFieldValue(FIELD_PROJECT_BRANCH)),
- new ReportAnalysisFailureNotification.Task(
+ new ReportAnalysisFailureNotificationBuilder.Task(
notification.getFieldValue(FIELD_TASK_UUID),
Long.valueOf(notification.getFieldValue(FIELD_TASK_CREATED_AT)),
Long.valueOf(notification.getFieldValue(FIELD_TASK_FAILED_AT))),
import java.util.function.Predicate;
import javax.annotation.CheckForNull;
import org.sonar.api.issue.Issue;
+import org.sonar.api.notifications.Notification;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.Duration;
import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
/**
* Types of the notifications sent by this step
*/
- static final Set<String> NOTIF_TYPES = ImmutableSet.of(IssueChangeNotification.TYPE, NewIssuesNotification.TYPE, MyNewIssuesNotification.MY_NEW_ISSUES_NOTIF_TYPE);
+ static final Set<Class<? extends Notification>> NOTIF_TYPES = ImmutableSet.of(NewIssuesNotification.class, MyNewIssuesNotification.class, IssueChangeNotification.class);
private final IssueCache issueCache;
private final RuleRepository rules;
public void execute(ComputationStep.Context context) {
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);
}
--- /dev/null
+/*
+ * 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.ce.task.projectanalysis.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.api.web.UserRole;
+import org.sonar.server.notification.NotificationDispatcherMetadata;
+import org.sonar.server.notification.NotificationManager;
+import org.sonar.server.notification.NotificationManager.EmailRecipient;
+import org.sonar.server.notification.NotificationManager.SubscriberPermissionsOnProject;
+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;
+
+public class ReportAnalysisFailureNotificationHandlerTest {
+ private static final String REPORT_FAILURE_DISPATCHER_KEY = "CeReportTaskFailure";
+ private static final SubscriberPermissionsOnProject REQUIRED_SUBSCRIBER_PERMISSIONS = new SubscriberPermissionsOnProject(UserRole.ADMIN, UserRole.USER);
+ private NotificationManager notificationManager = mock(NotificationManager.class);
+ private EmailNotificationChannel emailNotificationChannel = mock(EmailNotificationChannel.class);
+ private ReportAnalysisFailureNotificationHandler underTest = new ReportAnalysisFailureNotificationHandler(notificationManager, emailNotificationChannel);
+
+ @Test
+ public void getMetadata_returns_same_instance_as_static_method() {
+ assertThat(underTest.getMetadata().get()).isSameAs(ReportAnalysisFailureNotificationHandler.newMetadata());
+ }
+
+ @Test
+ public void verify_reportFailures_notification_dispatcher_key() {
+ NotificationDispatcherMetadata metadata = ReportAnalysisFailureNotificationHandler.newMetadata();
+
+ assertThat(metadata.getDispatcherKey()).isEqualTo(REPORT_FAILURE_DISPATCHER_KEY);
+ }
+
+ @Test
+ public void reportFailures_notification_is_enable_at_global_level() {
+ NotificationDispatcherMetadata metadata = ReportAnalysisFailureNotificationHandler.newMetadata();
+
+ assertThat(metadata.getProperty(GLOBAL_NOTIFICATION)).isEqualTo("true");
+ }
+
+ @Test
+ public void reportFailures_notification_is_enable_at_project_level() {
+ NotificationDispatcherMetadata metadata = ReportAnalysisFailureNotificationHandler.newMetadata();
+
+ assertThat(metadata.getProperty(PER_PROJECT_NOTIFICATION)).isEqualTo("true");
+ }
+
+ @Test
+ public void getNotificationClass_is_ReportAnalysisFailureNotification() {
+ assertThat(underTest.getNotificationClass()).isEqualTo(ReportAnalysisFailureNotification.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<ReportAnalysisFailureNotification> notifications = IntStream.range(0, 1 + new Random().nextInt(10))
+ .mapToObj(i -> mock(ReportAnalysisFailureNotification.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<ReportAnalysisFailureNotification> 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_ReportFailure_notifications() {
+ String projectKey = randomAlphabetic(12);
+ ReportAnalysisFailureNotification notification = newNotification(projectKey);
+ when(emailNotificationChannel.isActivated()).thenReturn(true);
+ when(notificationManager.findSubscribedEmailRecipients(REPORT_FAILURE_DISPATCHER_KEY, projectKey, REQUIRED_SUBSCRIBER_PERMISSIONS))
+ .thenReturn(emptySet());
+
+ int deliver = underTest.deliver(Collections.singleton(notification));
+
+ assertThat(deliver).isZero();
+ verify(notificationManager).findSubscribedEmailRecipients(REPORT_FAILURE_DISPATCHER_KEY, projectKey, REQUIRED_SUBSCRIBER_PERMISSIONS);
+ verifyNoMoreInteractions(notificationManager);
+ verify(emailNotificationChannel).isActivated();
+ verifyNoMoreInteractions(emailNotificationChannel);
+ }
+
+ @Test
+ public void deliver_ignores_notification_without_projectKey() {
+ String projectKey = randomAlphabetic(10);
+ Set<ReportAnalysisFailureNotification> withProjectKey = IntStream.range(0, 1 + new Random().nextInt(5))
+ .mapToObj(i -> newNotification(projectKey))
+ .collect(toSet());
+ Set<ReportAnalysisFailureNotification> noProjectKey = IntStream.range(0, 1 + new Random().nextInt(5))
+ .mapToObj(i -> newNotification(null))
+ .collect(toSet());
+ Set<EmailRecipient> emailRecipients = IntStream.range(0, 1 + new Random().nextInt(10))
+ .mapToObj(i -> "user_" + i)
+ .map(login -> new EmailRecipient(login, emailOf(login)))
+ .collect(toSet());
+ Set<EmailDeliveryRequest> expectedRequests = emailRecipients.stream()
+ .flatMap(emailRecipient -> withProjectKey.stream().map(notif -> new EmailDeliveryRequest(emailRecipient.getEmail(), notif)))
+ .collect(toSet());
+ when(emailNotificationChannel.isActivated()).thenReturn(true);
+ when(notificationManager.findSubscribedEmailRecipients(REPORT_FAILURE_DISPATCHER_KEY, projectKey, REQUIRED_SUBSCRIBER_PERMISSIONS))
+ .thenReturn(emailRecipients);
+
+ Set<ReportAnalysisFailureNotification> notifications = Stream.of(withProjectKey.stream(), noProjectKey.stream())
+ .flatMap(t -> t)
+ .collect(toSet());
+ int deliver = underTest.deliver(notifications);
+
+ assertThat(deliver).isZero();
+ verify(notificationManager).findSubscribedEmailRecipients(REPORT_FAILURE_DISPATCHER_KEY, projectKey, REQUIRED_SUBSCRIBER_PERMISSIONS);
+ verifyNoMoreInteractions(notificationManager);
+ verify(emailNotificationChannel).isActivated();
+ verify(emailNotificationChannel).deliver(expectedRequests);
+ verifyNoMoreInteractions(emailNotificationChannel);
+ }
+
+ @Test
+ public void deliver_checks_by_projectKey_if_notifications_have_subscribed_assignee_to_ReportFailure_notifications() {
+ String projectKey1 = randomAlphabetic(10);
+ String projectKey2 = randomAlphabetic(11);
+ Set<ReportAnalysisFailureNotification> notifications1 = randomSetOfNotifications(projectKey1);
+ Set<ReportAnalysisFailureNotification> notifications2 = randomSetOfNotifications(projectKey2);
+ when(emailNotificationChannel.isActivated()).thenReturn(true);
+
+ Set<EmailRecipient> emailRecipients1 = IntStream.range(0, 1 + new Random().nextInt(10))
+ .mapToObj(i -> "user1_" + i)
+ .map(login -> new EmailRecipient(login, emailOf(login)))
+ .collect(toSet());
+ Set<EmailRecipient> 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(REPORT_FAILURE_DISPATCHER_KEY, projectKey1, REQUIRED_SUBSCRIBER_PERMISSIONS))
+ .thenReturn(emailRecipients1);
+ when(notificationManager.findSubscribedEmailRecipients(REPORT_FAILURE_DISPATCHER_KEY, projectKey2, REQUIRED_SUBSCRIBER_PERMISSIONS))
+ .thenReturn(emailRecipients2);
+ Set<EmailDeliveryRequest> 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(REPORT_FAILURE_DISPATCHER_KEY, projectKey1, REQUIRED_SUBSCRIBER_PERMISSIONS);
+ verify(notificationManager).findSubscribedEmailRecipients(REPORT_FAILURE_DISPATCHER_KEY, projectKey2, REQUIRED_SUBSCRIBER_PERMISSIONS);
+ 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<ReportAnalysisFailureNotification> notifications1 = randomSetOfNotifications(projectKey1);
+ Set<ReportAnalysisFailureNotification> notifications2 = randomSetOfNotifications(projectKey2);
+ when(emailNotificationChannel.isActivated()).thenReturn(true);
+ when(notificationManager.findSubscribedEmailRecipients(REPORT_FAILURE_DISPATCHER_KEY, projectKey1, REQUIRED_SUBSCRIBER_PERMISSIONS))
+ .thenReturn(emptySet());
+ when(notificationManager.findSubscribedEmailRecipients(REPORT_FAILURE_DISPATCHER_KEY, projectKey2, REQUIRED_SUBSCRIBER_PERMISSIONS))
+ .thenReturn(emptySet());
+
+ int deliver = underTest.deliver(Stream.concat(notifications1.stream(), notifications2.stream()).collect(toSet()));
+
+ assertThat(deliver).isZero();
+ verify(notificationManager).findSubscribedEmailRecipients(REPORT_FAILURE_DISPATCHER_KEY, projectKey1, REQUIRED_SUBSCRIBER_PERMISSIONS);
+ verify(notificationManager).findSubscribedEmailRecipients(REPORT_FAILURE_DISPATCHER_KEY, projectKey2, REQUIRED_SUBSCRIBER_PERMISSIONS);
+ verifyNoMoreInteractions(notificationManager);
+ verify(emailNotificationChannel).isActivated();
+ verifyNoMoreInteractions(emailNotificationChannel);
+ }
+
+ private static Set<ReportAnalysisFailureNotification> randomSetOfNotifications(@Nullable String projectKey) {
+ return IntStream.range(0, 1 + new Random().nextInt(5))
+ .mapToObj(i -> newNotification(projectKey))
+ .collect(Collectors.toSet());
+ }
+
+ private static ReportAnalysisFailureNotification newNotification(@Nullable String projectKey) {
+ ReportAnalysisFailureNotification notification = mock(ReportAnalysisFailureNotification.class);
+ when(notification.getProjectKey()).thenReturn(projectKey);
+ return notification;
+ }
+ private static String emailOf(String assignee1) {
+ return assignee1 + "@house";
+ }
+
+}
import org.sonar.ce.task.CeTask;
import org.sonar.ce.task.CeTaskResult;
import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotification;
+import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotificationBuilder;
import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotificationSerializer;
import org.sonar.ce.taskprocessor.CeWorker;
import org.sonar.db.DbClient;
return;
}
- if (notificationService.hasProjectSubscribersForTypes(projectUuid, singleton(ReportAnalysisFailureNotification.TYPE))) {
+ if (notificationService.hasProjectSubscribersForTypes(projectUuid, singleton(ReportAnalysisFailureNotification.class))) {
try (DbSession dbSession = dbClient.openSession(false)) {
ComponentDto projectDto = dbClient.componentDao().selectOrFailByUuid(dbSession, projectUuid);
checkScopeAndQualifier(projectDto);
CeActivityDto ceActivityDto = dbClient.ceActivityDao().selectByUuid(dbSession, ceTask.getUuid())
.orElseThrow(() -> new RowNotFoundException(format("CeActivity with uuid '%s' not found", ceTask.getUuid())));
- ReportAnalysisFailureNotification taskFailureNotification = buildNotification(ceActivityDto, projectDto, error);
- notificationService.deliver(taskFailureNotificationSerializer.toNotification(taskFailureNotification));
+ ReportAnalysisFailureNotificationBuilder taskFailureNotification = buildNotification(ceActivityDto, projectDto, error);
+ ReportAnalysisFailureNotification notification = taskFailureNotificationSerializer.toNotification(taskFailureNotification);
+ notificationService.deliverEmails(singleton(notification));
+
+ // compatibility with old API
+ notificationService.deliver(notification);
}
}
}
"Component %s must be a project (scope=%s, qualifier=%s)", projectDto.uuid(), scope, qualifier);
}
- private ReportAnalysisFailureNotification buildNotification(CeActivityDto ceActivityDto, ComponentDto projectDto, @Nullable Throwable error) {
+ private ReportAnalysisFailureNotificationBuilder buildNotification(CeActivityDto ceActivityDto, ComponentDto projectDto, @Nullable Throwable error) {
Long executedAt = ceActivityDto.getExecutedAt();
- return new ReportAnalysisFailureNotification(
- new ReportAnalysisFailureNotification.Project(
+ return new ReportAnalysisFailureNotificationBuilder(
+ new ReportAnalysisFailureNotificationBuilder.Project(
projectDto.uuid(),
projectDto.getKey(),
projectDto.name(),
projectDto.getBranch()),
- new ReportAnalysisFailureNotification.Task(
+ new ReportAnalysisFailureNotificationBuilder.Task(
ceActivityDto.getUuid(),
ceActivityDto.getSubmittedAt(),
executedAt == null ? system2.now() : executedAt),
import org.sonar.ce.task.CeTask;
import org.sonar.ce.task.CeTaskResult;
import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotification;
+import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotificationBuilder;
import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotificationSerializer;
import org.sonar.db.DbClient;
import org.sonar.db.DbTester;
String componentUuid = randomAlphanumeric(6);
when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
- when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.TYPE)))
+ when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.class)))
.thenReturn(false);
fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, throwableMock);
String componentUuid = randomAlphanumeric(6);
when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
- when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.TYPE)))
+ when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.class)))
.thenReturn(true);
expectedException.expect(RowNotFoundException.class);
.forEach(component -> {
try {
when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(component.uuid(), null, null)));
- when(notificationService.hasProjectSubscribersForTypes(component.uuid(), singleton(ReportAnalysisFailureNotification.TYPE)))
+ when(notificationService.hasProjectSubscribersForTypes(component.uuid(), singleton(ReportAnalysisFailureNotification.class)))
.thenReturn(true);
underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, throwableMock);
when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
when(ceTaskMock.getUuid()).thenReturn(taskUuid);
when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
- when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.TYPE)))
+ when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.class)))
.thenReturn(true);
dbTester.components().insertPrivateProject(s -> s.setUuid(componentUuid));
underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, throwableMock);
- ArgumentCaptor<ReportAnalysisFailureNotification> notificationCaptor = verifyAndCaptureSerializedNotification();
+ ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
verify(notificationService).deliver(same(notificationMock));
- ReportAnalysisFailureNotification reportAnalysisFailureNotification = notificationCaptor.getValue();
+ ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder = notificationCaptor.getValue();
- ReportAnalysisFailureNotification.Project notificationProject = reportAnalysisFailureNotification.getProject();
+ ReportAnalysisFailureNotificationBuilder.Project notificationProject = reportAnalysisFailureNotificationBuilder.getProject();
assertThat(notificationProject.getName()).isEqualTo(project.name());
assertThat(notificationProject.getKey()).isEqualTo(project.getKey());
assertThat(notificationProject.getUuid()).isEqualTo(project.uuid());
assertThat(notificationProject.getBranchName()).isEqualTo(project.getBranch());
- ReportAnalysisFailureNotification.Task notificationTask = reportAnalysisFailureNotification.getTask();
+ ReportAnalysisFailureNotificationBuilder.Task notificationTask = reportAnalysisFailureNotificationBuilder.getTask();
assertThat(notificationTask.getUuid()).isEqualTo(taskUuid);
assertThat(notificationTask.getCreatedAt()).isEqualTo(createdAt);
assertThat(notificationTask.getFailedAt()).isEqualTo(executedAt);
underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, throwableMock);
- ArgumentCaptor<ReportAnalysisFailureNotification> notificationCaptor = verifyAndCaptureSerializedNotification();
+ ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
- ReportAnalysisFailureNotification reportAnalysisFailureNotification = notificationCaptor.getValue();
- assertThat(reportAnalysisFailureNotification.getErrorMessage()).isEqualTo(message);
+ ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder = notificationCaptor.getValue();
+ assertThat(reportAnalysisFailureNotificationBuilder.getErrorMessage()).isEqualTo(message);
}
@Test
underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, null);
verify(notificationService).deliver(same(notificationMock));
- ArgumentCaptor<ReportAnalysisFailureNotification> notificationCaptor = verifyAndCaptureSerializedNotification();
+ ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
- ReportAnalysisFailureNotification reportAnalysisFailureNotification = notificationCaptor.getValue();
- assertThat(reportAnalysisFailureNotification.getErrorMessage()).isNull();
+ ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder = notificationCaptor.getValue();
+ assertThat(reportAnalysisFailureNotificationBuilder.getErrorMessage()).isNull();
}
@Test
underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, null);
verify(notificationService).deliver(same(notificationMock));
- ArgumentCaptor<ReportAnalysisFailureNotification> notificationCaptor = verifyAndCaptureSerializedNotification();
+ ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
assertThat(notificationCaptor.getValue().getTask().getFailedAt()).isEqualTo(now);
}
- private Notification mockSerializer() {
- Notification notificationMock = mock(Notification.class);
- when(serializer.toNotification(any(ReportAnalysisFailureNotification.class))).thenReturn(notificationMock);
+ private ReportAnalysisFailureNotification mockSerializer() {
+ ReportAnalysisFailureNotification notificationMock = mock(ReportAnalysisFailureNotification.class);
+ when(serializer.toNotification(any(ReportAnalysisFailureNotificationBuilder.class))).thenReturn(notificationMock);
return notificationMock;
}
when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(project.uuid(), null, null)));
when(ceTaskMock.getUuid()).thenReturn(taskUuid);
- when(notificationService.hasProjectSubscribersForTypes(project.uuid(), singleton(ReportAnalysisFailureNotification.TYPE)))
+ when(notificationService.hasProjectSubscribersForTypes(project.uuid(), singleton(ReportAnalysisFailureNotification.class)))
.thenReturn(true);
insertActivityDto(taskUuid, createdAt, executedAt, project);
return project;
dbTester.getSession().commit();
}
- private ArgumentCaptor<ReportAnalysisFailureNotification> verifyAndCaptureSerializedNotification() {
- ArgumentCaptor<ReportAnalysisFailureNotification> notificationCaptor = ArgumentCaptor.forClass(ReportAnalysisFailureNotification.class);
+ private ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> verifyAndCaptureSerializedNotification() {
+ ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = ArgumentCaptor.forClass(ReportAnalysisFailureNotificationBuilder.class);
verify(serializer).toNotification(notificationCaptor.capture());
return notificationCaptor;
}
}
public boolean hasProjectNotificationSubscribersForDispatchers(String projectUuid, Collection<String> dispatcherKeys) {
+ if (dispatcherKeys.isEmpty()) {
+ return false;
+ }
+
try (DbSession session = mybatis.openSession(false);
Connection connection = session.getConnection();
PreparedStatement pstmt = createStatement(projectUuid, dispatcherKeys, connection);
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import javax.annotation.CheckForNull;
public class ChangesOnMyIssueNotificationHandler implements NotificationHandler<IssueChangeNotification> {
private static final String KEY = "ChangesOnMyIssue";
+ private static final NotificationDispatcherMetadata METADATA = NotificationDispatcherMetadata.create(KEY)
+ .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
+ .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
private final NotificationManager notificationManager;
private final EmailNotificationChannel emailNotificationChannel;
this.emailNotificationChannel = emailNotificationChannel;
}
+ @Override
+ public Optional<NotificationDispatcherMetadata> getMetadata() {
+ return Optional.of(METADATA);
+ }
+
public static NotificationDispatcherMetadata newMetadata() {
- return NotificationDispatcherMetadata.create(KEY)
- .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
- .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
+ return METADATA;
}
@Override
import com.google.common.collect.Multimap;
import java.util.Collection;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import org.sonar.api.issue.Issue;
import org.sonar.server.notification.email.EmailNotificationChannel;
import org.sonar.server.notification.email.EmailNotificationChannel.EmailDeliveryRequest;
+import static java.util.Optional.of;
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 DoNotFixNotificationHandler implements NotificationHandler<IssueChangeNotification> {
public static final String KEY = "NewFalsePositiveIssue";
+ private static final NotificationDispatcherMetadata METADATA = NotificationDispatcherMetadata.create(KEY)
+ .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
+ .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
private static final Set<String> SUPPORTED_NEW_RESOLUTIONS = ImmutableSet.of(Issue.RESOLUTION_FALSE_POSITIVE, Issue.RESOLUTION_WONT_FIX);
this.emailNotificationChannel = emailNotificationChannel;
}
+ @Override
+ public Optional<NotificationDispatcherMetadata> getMetadata() {
+ return of(METADATA);
+ }
+
public static NotificationDispatcherMetadata newMetadata() {
- return NotificationDispatcherMetadata.create(KEY)
- .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
- .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
+ return METADATA;
}
@Override
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import javax.annotation.CheckForNull;
public class MyNewIssuesNotificationHandler implements NotificationHandler<MyNewIssuesNotification> {
public static final String KEY = "SQ-MyNewIssues";
+ private static final NotificationDispatcherMetadata METADATA = NotificationDispatcherMetadata.create(KEY)
+ .setProperty(GLOBAL_NOTIFICATION, String.valueOf(true))
+ .setProperty(PER_PROJECT_NOTIFICATION, String.valueOf(true));
private final NotificationManager notificationManager;
private final EmailNotificationChannel emailNotificationChannel;
this.emailNotificationChannel = emailNotificationChannel;
}
+ @Override
+ public Optional<NotificationDispatcherMetadata> getMetadata() {
+ return Optional.of(METADATA);
+ }
+
public static NotificationDispatcherMetadata newMetadata() {
- return NotificationDispatcherMetadata.create(KEY)
- .setProperty(GLOBAL_NOTIFICATION, String.valueOf(true))
- .setProperty(PER_PROJECT_NOTIFICATION, String.valueOf(true));
+ return METADATA;
}
@Override
import com.google.common.collect.Multimap;
import java.util.Collection;
+import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import org.sonar.server.notification.NotificationDispatcherMetadata;
public class NewIssuesNotificationHandler implements NotificationHandler<NewIssuesNotification> {
public static final String KEY = "NewIssues";
+ private static final NotificationDispatcherMetadata METADATA = NotificationDispatcherMetadata.create(KEY)
+ .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
+ .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
private final NotificationManager notificationManager;
private final EmailNotificationChannel emailNotificationChannel;
this.emailNotificationChannel = emailNotificationChannel;
}
+ @Override
+ public Optional<NotificationDispatcherMetadata> getMetadata() {
+ return Optional.of(METADATA);
+ }
+
public static NotificationDispatcherMetadata newMetadata() {
- return NotificationDispatcherMetadata.create(KEY)
- .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
- .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
+ return METADATA;
}
@Override
package org.sonar.server.notification;
import java.util.Collection;
+import java.util.Optional;
import org.sonar.api.ce.ComputeEngineSide;
import org.sonar.api.notifications.Notification;
import org.sonar.api.server.ServerSide;
@ServerSide
@ComputeEngineSide
public interface NotificationHandler<T extends Notification> {
+ Optional<NotificationDispatcherMetadata> getMetadata();
+
Class<T> getNotificationClass();
int deliver(Collection<T> notifications);
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimap;
import com.google.common.collect.SetMultimap;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.server.ServerSide;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
+import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.DbClient;
import static com.google.common.base.Preconditions.checkArgument;
private static final Logger LOG = Loggers.get(NotificationService.class);
private final List<NotificationDispatcher> dispatchers;
- private final List<NotificationHandler> handlers;
+ private final List<NotificationHandler<? extends Notification>> handlers;
private final DbClient dbClient;
- public NotificationService(DbClient dbClient, NotificationDispatcher[] dispatchers, NotificationHandler[] handlers) {
+ public NotificationService(DbClient dbClient, NotificationDispatcher[] dispatchers, NotificationHandler<? extends Notification>[] handlers) {
this.dbClient = dbClient;
this.dispatchers = ImmutableList.copyOf(dispatchers);
this.handlers = ImmutableList.copyOf(handlers);
* Returns true if at least one user is subscribed to at least one notification with given types.
* Subscription can be global or on the specific project.
*/
- public boolean hasProjectSubscribersForTypes(String projectUuid, Set<String> notificationTypes) {
- Collection<String> dispatcherKeys = new ArrayList<>();
- for (NotificationDispatcher dispatcher : dispatchers) {
- if (notificationTypes.contains(dispatcher.getType())) {
- dispatcherKeys.add(dispatcher.getKey());
- }
- }
+ public boolean hasProjectSubscribersForTypes(String projectUuid, Set<Class<? extends Notification>> notificationTypes) {
+ Set<String> dispatcherKeys = handlers.stream()
+ .filter(handler -> notificationTypes.stream().anyMatch(notificationType -> handler.getNotificationClass() == notificationType))
+ .map(NotificationHandler::getMetadata)
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .map(NotificationDispatcherMetadata::getDispatcherKey)
+ .collect(MoreCollectors.toSet(notificationTypes.size()));
return dbClient.propertiesDao().hasProjectNotificationSubscribersForDispatchers(projectUuid, dispatcherKeys);
}
import com.google.common.collect.Multimap;
import java.util.Collection;
+import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import org.sonar.server.notification.NotificationDispatcherMetadata;
public class QGChangeNotificationHandler implements NotificationHandler<QGChangeNotification> {
public static final String KEY = "NewAlerts";
+ private static final NotificationDispatcherMetadata METADATA = NotificationDispatcherMetadata.create(KEY)
+ .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
+ .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
private final NotificationManager notificationManager;
private final EmailNotificationChannel emailNotificationChannel;
return QGChangeNotification.class;
}
+ @Override
+ public Optional<NotificationDispatcherMetadata> getMetadata() {
+ return Optional.of(METADATA);
+ }
+
public static NotificationDispatcherMetadata newMetadata() {
- return NotificationDispatcherMetadata.create(KEY)
- .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
- .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
+ return METADATA;
}
@Override
private EmailNotificationChannel emailNotificationChannel = mock(EmailNotificationChannel.class);
private ChangesOnMyIssueNotificationHandler underTest = new ChangesOnMyIssueNotificationHandler(notificationManager, emailNotificationChannel);
+ @Test
+ public void getMetadata_returns_same_instance_as_static_method() {
+ assertThat(underTest.getMetadata().get()).isSameAs(ChangesOnMyIssueNotificationHandler.newMetadata());
+ }
+
@Test
public void verify_changeOnMyIssues_notification_dispatcher_key() {
NotificationDispatcherMetadata metadata = ChangesOnMyIssueNotificationHandler.newMetadata();
private EmailNotificationChannel emailNotificationChannel = mock(EmailNotificationChannel.class);
private DoNotFixNotificationHandler underTest = new DoNotFixNotificationHandler(notificationManager, emailNotificationChannel);
+ @Test
+ public void getMetadata_returns_same_instance_as_static_method() {
+ assertThat(underTest.getMetadata().get()).isSameAs(DoNotFixNotificationHandler.newMetadata());
+ }
+
@Test
public void verify_changeOnMyIssues_notification_dispatcher_key() {
NotificationDispatcherMetadata metadata = DoNotFixNotificationHandler.newMetadata();
private MyNewIssuesNotificationHandler underTest = new MyNewIssuesNotificationHandler(notificationManager, emailNotificationChannel);
+ @Test
+ public void getMetadata_returns_same_instance_as_static_method() {
+ assertThat(underTest.getMetadata().get()).isSameAs(MyNewIssuesNotificationHandler.newMetadata());
+ }
+
@Test
public void verify_myNewIssues_notification_dispatcher_key() {
NotificationDispatcherMetadata metadata = MyNewIssuesNotificationHandler.newMetadata();
private EmailNotificationChannel emailNotificationChannel = mock(EmailNotificationChannel.class);
private NewIssuesNotificationHandler underTest = new NewIssuesNotificationHandler(notificationManager, emailNotificationChannel);
+ @Test
+ public void getMetadata_returns_same_instance_as_static_method() {
+ assertThat(underTest.getMetadata().get()).isSameAs(NewIssuesNotificationHandler.newMetadata());
+ }
+
@Test
public void verify_myNewIssues_notification_dispatcher_key() {
NotificationDispatcherMetadata metadata = NewIssuesNotificationHandler.newMetadata();
}
@Test
- public void deliver_checks_by_projectKey_if_notifications_have_subscribed_assignee_to_MyNewIssue_notifications() {
+ public void deliver_checks_by_projectKey_if_notifications_have_subscribed_assignee_to_NewIssue_notifications() {
String projectKey1 = randomAlphabetic(10);
String projectKey2 = randomAlphabetic(11);
Set<NewIssuesNotification> notifications1 = randomSetOfNotifications(projectKey1);
*/
package org.sonar.server.notification;
+import com.google.common.collect.ImmutableSet;
import java.util.Collections;
import java.util.List;
+import java.util.Optional;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.notifications.Notification;
import org.sonar.db.DbClient;
+import org.sonar.db.property.PropertiesDao;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyCollection;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
public ExpectedException expectedException = ExpectedException.none();
private final DbClient dbClient = mock(DbClient.class);
+ private final PropertiesDao propertiesDao = mock(PropertiesDao.class);
+
+ @Before
+ public void wire_mocks() {
+ when(dbClient.propertiesDao()).thenReturn(propertiesDao);
+ }
@Test
public void deliverEmails_fails_with_IAE_if_type_of_collection_is_Notification() {
verify(handler1B, times(0)).deliver(anyCollection());
}
+ @Test
+ public void hasProjectSubscribersForType_returns_false_if_there_are_no_handler() {
+ String projectUuid = randomAlphabetic(7);
+ NotificationService underTest = new NotificationService(dbClient);
+
+ assertThat(underTest.hasProjectSubscribersForTypes(projectUuid, ImmutableSet.of(Notification1.class))).isFalse();
+ assertThat(underTest.hasProjectSubscribersForTypes(projectUuid, ImmutableSet.of(Notification2.class))).isFalse();
+ }
+
+ @Test
+ public void hasProjectSubscribersForType_checks_property_for_each_dispatcher_key_supporting_Notification_type() {
+ String dispatcherKey1A = randomAlphabetic(5);
+ String dispatcherKey1B = randomAlphabetic(6);
+ String projectUuid = randomAlphabetic(7);
+ NotificationHandler1A handler1A = mock(NotificationHandler1A.class);
+ when(handler1A.getMetadata()).thenReturn(Optional.of(NotificationDispatcherMetadata.create(dispatcherKey1A)));
+ NotificationHandler1B handler1B = mock(NotificationHandler1B.class);
+ when(handler1B.getMetadata()).thenReturn(Optional.of(NotificationDispatcherMetadata.create(dispatcherKey1B)));
+ NotificationHandler2 handler2 = mock(NotificationHandler2.class);
+ when(handler2.getMetadata()).thenReturn(Optional.empty());
+ boolean expected = new Random().nextBoolean();
+ when(propertiesDao.hasProjectNotificationSubscribersForDispatchers(projectUuid, ImmutableSet.of(dispatcherKey1A, dispatcherKey1B)))
+ .thenReturn(expected);
+ NotificationService underTest = new NotificationService(dbClient, new NotificationHandler[] {handler1A, handler1B, handler2});
+
+ boolean flag = underTest.hasProjectSubscribersForTypes(projectUuid, ImmutableSet.of(Notification1.class));
+
+ verify(propertiesDao).hasProjectNotificationSubscribersForDispatchers(projectUuid, ImmutableSet.of(dispatcherKey1A, dispatcherKey1B));
+ verifyNoMoreInteractions(propertiesDao);
+ assertThat(flag).isEqualTo(expected);
+
+ flag = underTest.hasProjectSubscribersForTypes(projectUuid, ImmutableSet.of(Notification1.class, Notification2.class));
+
+ verify(propertiesDao, times(2)).hasProjectNotificationSubscribersForDispatchers(projectUuid, ImmutableSet.of(dispatcherKey1A, dispatcherKey1B));
+ verifyNoMoreInteractions(propertiesDao);
+ assertThat(flag).isEqualTo(expected);
+ }
+
+ @Test
+ public void hasProjectSubscribersForType_checks_property_for_each_dispatcher_key_supporting_Notification_types() {
+ String dispatcherKey1A = randomAlphabetic(5);
+ String dispatcherKey1B = randomAlphabetic(6);
+ String dispatcherKey2 = randomAlphabetic(7);
+ String projectUuid = randomAlphabetic(8);
+ NotificationHandler1A handler1A = mock(NotificationHandler1A.class);
+ when(handler1A.getMetadata()).thenReturn(Optional.of(NotificationDispatcherMetadata.create(dispatcherKey1A)));
+ NotificationHandler1B handler1B = mock(NotificationHandler1B.class);
+ when(handler1B.getMetadata()).thenReturn(Optional.of(NotificationDispatcherMetadata.create(dispatcherKey1B)));
+ NotificationHandler2 handler2 = mock(NotificationHandler2.class);
+ when(handler2.getMetadata()).thenReturn(Optional.of(NotificationDispatcherMetadata.create(dispatcherKey2)));
+ boolean expected1 = new Random().nextBoolean();
+ when(propertiesDao.hasProjectNotificationSubscribersForDispatchers(projectUuid, ImmutableSet.of(dispatcherKey1A, dispatcherKey1B, dispatcherKey2)))
+ .thenReturn(expected1);
+ boolean expected2 = new Random().nextBoolean();
+ when(propertiesDao.hasProjectNotificationSubscribersForDispatchers(projectUuid, ImmutableSet.of(dispatcherKey2)))
+ .thenReturn(expected2);
+ NotificationService underTest = new NotificationService(dbClient, new NotificationHandler[] {handler1A, handler1B, handler2});
+
+ boolean flag = underTest.hasProjectSubscribersForTypes(projectUuid, ImmutableSet.of(Notification1.class, Notification2.class));
+
+ verify(propertiesDao).hasProjectNotificationSubscribersForDispatchers(projectUuid, ImmutableSet.of(dispatcherKey1A, dispatcherKey1B, dispatcherKey2));
+ verifyNoMoreInteractions(propertiesDao);
+ assertThat(flag).isEqualTo(expected1);
+
+ flag = underTest.hasProjectSubscribersForTypes(projectUuid, ImmutableSet.of(Notification2.class));
+
+ verify(propertiesDao).hasProjectNotificationSubscribersForDispatchers(projectUuid, ImmutableSet.of(dispatcherKey1A, dispatcherKey1B, dispatcherKey2));
+ verify(propertiesDao).hasProjectNotificationSubscribersForDispatchers(projectUuid, ImmutableSet.of(dispatcherKey2));
+ verifyNoMoreInteractions(propertiesDao);
+ assertThat(flag).isEqualTo(expected2);
+ }
+
+ @Test
+ public void hasProjectSubscribersForType_returns_false_if_set_is_empty() {
+ String dispatcherKey1A = randomAlphabetic(5);
+ String dispatcherKey1B = randomAlphabetic(6);
+ String projectUuid = randomAlphabetic(7);
+ NotificationHandler1A handler1A = mock(NotificationHandler1A.class);
+ when(handler1A.getMetadata()).thenReturn(Optional.of(NotificationDispatcherMetadata.create(dispatcherKey1A)));
+ NotificationHandler1B handler1B = mock(NotificationHandler1B.class);
+ when(handler1B.getMetadata()).thenReturn(Optional.of(NotificationDispatcherMetadata.create(dispatcherKey1B)));
+ NotificationHandler2 handler2 = mock(NotificationHandler2.class);
+ when(handler2.getMetadata()).thenReturn(Optional.empty());
+ NotificationService underTest = new NotificationService(dbClient, new NotificationHandler[] {handler1A, handler1B, handler2});
+
+ boolean flag = underTest.hasProjectSubscribersForTypes(projectUuid, ImmutableSet.of());
+
+ assertThat(flag).isFalse();
+ verify(propertiesDao).hasProjectNotificationSubscribersForDispatchers(projectUuid, ImmutableSet.of());
+ verifyNoMoreInteractions(propertiesDao);
+ }
+
+ @Test
+ public void hasProjectSubscribersForType_returns_false_for_type_which_have_no_handler() {
+ String dispatcherKey1A = randomAlphabetic(5);
+ String dispatcherKey1B = randomAlphabetic(6);
+ String projectUuid = randomAlphabetic(7);
+ NotificationHandler1A handler1A = mock(NotificationHandler1A.class);
+ when(handler1A.getMetadata()).thenReturn(Optional.of(NotificationDispatcherMetadata.create(dispatcherKey1A)));
+ NotificationHandler1B handler1B = mock(NotificationHandler1B.class);
+ when(handler1B.getMetadata()).thenReturn(Optional.of(NotificationDispatcherMetadata.create(dispatcherKey1B)));
+ NotificationService underTest = new NotificationService(dbClient, new NotificationHandler[] {handler1A, handler1B});
+
+ boolean flag = underTest.hasProjectSubscribersForTypes(projectUuid, ImmutableSet.of(Notification2.class));
+
+ assertThat(flag).isFalse();
+ verify(propertiesDao).hasProjectNotificationSubscribersForDispatchers(projectUuid, ImmutableSet.of());
+ verifyNoMoreInteractions(propertiesDao);
+ }
+
private static final class Notification1 extends Notification {
public Notification1() {
private EmailNotificationChannel emailNotificationChannel = mock(EmailNotificationChannel.class);
private QGChangeNotificationHandler underTest = new QGChangeNotificationHandler(notificationManager, emailNotificationChannel);
+ @Test
+ public void getMetadata_returns_same_instance_as_static_method() {
+ assertThat(underTest.getMetadata().get()).isSameAs(QGChangeNotificationHandler.newMetadata());
+ }
+
@Test
public void verify_qgChange_notification_dispatcher_key() {
NotificationDispatcherMetadata metadata = QGChangeNotificationHandler.newMetadata();
return assignee1 + "@giraffe";
}
-
-
}
package org.sonar.server.qualityprofile;
import java.util.Collection;
+import java.util.Optional;
import java.util.Set;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
+import org.sonar.server.notification.NotificationDispatcherMetadata;
import org.sonar.server.notification.NotificationHandler;
import org.sonar.server.notification.email.EmailNotificationChannel;
import org.sonar.server.notification.email.EmailNotificationChannel.EmailDeliveryRequest;
this.emailNotificationChannel = emailNotificationChannel;
}
+ @Override
+ public Optional<NotificationDispatcherMetadata> getMetadata() {
+ return Optional.empty();
+ }
+
@Override
public Class<BuiltInQPChangeNotification> getNotificationClass() {
return BuiltInQPChangeNotification.class;
underTest.stop();
}
- @Test
- public void hasProjectSubscribersForType() {
- setUpMocks();
-
- PropertiesDao dao = mock(PropertiesDao.class);
- when(dbClient.propertiesDao()).thenReturn(dao);
-
- // no subscribers
- when(dao.hasProjectNotificationSubscribersForDispatchers("PROJECT_UUID", Arrays.asList("CommentOnIssueAssignedToMe", "CommentOnIssueCreatedByMe"))).thenReturn(false);
- assertThat(service.hasProjectSubscribersForTypes("PROJECT_UUID", Sets.newHashSet("issue-changes"))).isFalse();
-
- // has subscribers on one dispatcher (among the two)
- when(dao.hasProjectNotificationSubscribersForDispatchers("PROJECT_UUID", Arrays.asList("CommentOnIssueAssignedToMe", "CommentOnIssueCreatedByMe"))).thenReturn(true);
- assertThat(service.hasProjectSubscribersForTypes("PROJECT_UUID", Sets.newHashSet("issue-changes"))).isTrue();
- }
-
private static Answer<Object> addUser(final String user, final NotificationChannel channel) {
return addUser(user, new NotificationChannel[] {channel});
}
private BuiltInQPChangeNotificationHandler underTest = new BuiltInQPChangeNotificationHandler(dbClient, emailNotificationChannel);
@Before
- public void wire_mocks() {
+ public void wire_mocks() {
when(dbClient.openSession(false)).thenReturn(dbSession);
when(dbClient.authorizationDao()).thenReturn(authorizationDao);
}
+ @Test
+ public void getMetadata_returns_empty() {
+ assertThat(underTest.getMetadata()).isEmpty();
+ }
+
@Test
public void getNotificationClass_is_BuiltInQPChangeNotification() {
assertThat(underTest.getNotificationClass()).isEqualTo(BuiltInQPChangeNotification.class);
notifications.forEach(Mockito::verifyZeroInteractions);
}
-
}