@@ -23,7 +23,6 @@ package org.sonar.plugins.core; | |||
import com.google.common.collect.ImmutableList; | |||
import org.sonar.api.*; | |||
import org.sonar.api.checks.NoSonarFilter; | |||
import org.sonar.api.notifications.NotificationDispatcherMetadata; | |||
import org.sonar.api.resources.Java; | |||
import org.sonar.core.timemachine.Periods; | |||
import org.sonar.plugins.core.batch.IndexProjectPostJob; | |||
@@ -33,18 +32,25 @@ import org.sonar.plugins.core.charts.XradarChart; | |||
import org.sonar.plugins.core.colorizers.JavaColorizerFormat; | |||
import org.sonar.plugins.core.dashboards.*; | |||
import org.sonar.plugins.core.issue.*; | |||
import org.sonar.plugins.core.issue.notification.*; | |||
import org.sonar.plugins.core.measurefilters.MyFavouritesFilter; | |||
import org.sonar.plugins.core.measurefilters.ProjectFilter; | |||
import org.sonar.plugins.core.notifications.alerts.NewAlerts; | |||
import org.sonar.plugins.core.notifications.reviews.ChangesInReviewAssignedToMeOrCreatedByMe; | |||
import org.sonar.plugins.core.notifications.reviews.NewFalsePositiveReview; | |||
import org.sonar.plugins.core.notifications.violations.NewViolationsOnFirstDifferentialPeriod; | |||
import org.sonar.plugins.core.security.ApplyProjectRolesDecorator; | |||
import org.sonar.plugins.core.sensors.*; | |||
import org.sonar.plugins.core.timemachine.*; | |||
import org.sonar.plugins.core.web.Lcom4Viewer; | |||
import org.sonar.plugins.core.web.TestsViewer; | |||
import org.sonar.plugins.core.widgets.*; | |||
import org.sonar.plugins.core.widgets.issues.ActionPlansWidget; | |||
import org.sonar.plugins.core.widgets.issues.*; | |||
import org.sonar.plugins.core.widgets.reviews.FalsePositiveReviewsWidget; | |||
import org.sonar.plugins.core.widgets.reviews.MyReviewsWidget; | |||
import org.sonar.plugins.core.widgets.reviews.ProjectReviewsWidget; | |||
import org.sonar.plugins.core.widgets.reviews.ReviewsPerDeveloperWidget; | |||
import java.util.List; | |||
@@ -406,12 +412,17 @@ public final class CorePlugin extends SonarPlugin { | |||
ActionPlansWidget.class, | |||
UnresolvedIssuesPerAssigneeWidget.class, | |||
UnresolvedIssuesStatusesWidget.class, | |||
// issue notifications | |||
SendIssueNotificationsPostJob.class, | |||
NewIssuesEmailTemplate.class, | |||
IssueChangesEmailTemplate.class, | |||
ChangesOnMyIssueNotificationDispatcher.class, | |||
ChangesOnMyIssueNotificationDispatcher.newMetadata(), | |||
NewIssuesNotificationDispatcher.class, | |||
NewIssuesNotificationPostJob.class, | |||
NotificationDispatcherMetadata.create("NewIssues") | |||
.setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true)) | |||
.setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true)), | |||
NewIssuesNotificationDispatcher.newMetadata(), | |||
NewFalsePositiveNotificationDispatcher.class, | |||
NewFalsePositiveNotificationDispatcher.newMetadata(), | |||
// batch | |||
ProfileSensor.class, | |||
@@ -450,19 +461,7 @@ public final class CorePlugin extends SonarPlugin { | |||
// Notify alerts on my favourite projects | |||
NewAlerts.class, | |||
NotificationDispatcherMetadata.create("NewAlerts") | |||
.setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true)) | |||
.setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true)), | |||
// Notify reviews changes | |||
ChangesInReviewAssignedToMeOrCreatedByMe.class, | |||
NotificationDispatcherMetadata.create("ChangesInReviewAssignedToMeOrCreatedByMe") | |||
.setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true)) | |||
.setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true)), | |||
// Notify new false positive resolution | |||
NewFalsePositiveReview.class, | |||
NotificationDispatcherMetadata.create("NewFalsePositiveReview") | |||
.setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true)) | |||
.setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true)) | |||
NewAlerts.newMetadata() | |||
); | |||
} | |||
} |
@@ -0,0 +1,83 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2013 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.plugins.core.issue.notification; | |||
import com.google.common.base.Objects; | |||
import com.google.common.collect.Multimap; | |||
import org.sonar.api.notifications.*; | |||
import javax.annotation.Nullable; | |||
import java.util.Collection; | |||
/** | |||
* This dispatcher means: "notify me when a change is done on an issue that is assigned to me or reported by me". | |||
* | |||
* @since 3.6, but the feature exists since 2.10 ("review-changed" notification) | |||
*/ | |||
public class ChangesOnMyIssueNotificationDispatcher extends NotificationDispatcher { | |||
public static final String KEY = "ChangesOnMyIssue"; | |||
private NotificationManager notificationManager; | |||
public ChangesOnMyIssueNotificationDispatcher(NotificationManager notificationManager) { | |||
super("issue-changes"); | |||
this.notificationManager = notificationManager; | |||
} | |||
@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<String, NotificationChannel> subscribedRecipients = notificationManager.findNotificationSubscribers(this, projectKey); | |||
// See available fields in the class IssueNotifications. | |||
// All the following users can be null | |||
String changeAuthor = notification.getFieldValue("changeAuthor"); | |||
String reporter = notification.getFieldValue("reporter"); | |||
String assignee = notification.getFieldValue("assignee"); | |||
if (!Objects.equal(changeAuthor, reporter)) { | |||
addUserToContextIfSubscribed(context, reporter, subscribedRecipients); | |||
} | |||
if (!Objects.equal(changeAuthor, assignee)) { | |||
addUserToContextIfSubscribed(context, assignee, subscribedRecipients); | |||
} | |||
} | |||
private void addUserToContextIfSubscribed(Context context, @Nullable String user, Multimap<String, NotificationChannel> subscribedRecipients) { | |||
if (user != null) { | |||
Collection<NotificationChannel> channels = subscribedRecipients.get(user); | |||
for (NotificationChannel channel : channels) { | |||
context.addUser(user, channel); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,111 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2013 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.plugins.core.issue.notification; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.sonar.api.config.EmailSettings; | |||
import org.sonar.api.notifications.Notification; | |||
import org.sonar.api.user.User; | |||
import org.sonar.api.user.UserFinder; | |||
import org.sonar.plugins.emailnotifications.api.EmailMessage; | |||
import org.sonar.plugins.emailnotifications.api.EmailTemplate; | |||
import javax.annotation.Nullable; | |||
/** | |||
* Creates email message for notification "issue-changes". | |||
* | |||
* @since 3.6 | |||
*/ | |||
public class IssueChangesEmailTemplate extends EmailTemplate { | |||
private final EmailSettings settings; | |||
private final UserFinder userFinder; | |||
public IssueChangesEmailTemplate(EmailSettings settings, UserFinder userFinder) { | |||
this.settings = settings; | |||
this.userFinder = userFinder; | |||
} | |||
@Override | |||
public EmailMessage format(Notification notif) { | |||
if (!"issue-changes".equals(notif.getType())) { | |||
return null; | |||
} | |||
String issueKey = notif.getFieldValue("key"); | |||
String author = notif.getFieldValue("changeAuthor"); | |||
StringBuilder sb = new StringBuilder(); | |||
appendField(sb, "Project", null, notif.getFieldValue("projectName")); | |||
appendField(sb, "Component", null, StringUtils.defaultString(notif.getFieldValue("componentName"), notif.getFieldValue("componentKey"))); | |||
appendField(sb, "Rule", null, notif.getFieldValue("ruleName")); | |||
appendField(sb, "Message", null, notif.getFieldValue("message")); | |||
sb.append('\n'); | |||
appendField(sb, "Assignee", notif.getFieldValue("old.assignee"), notif.getFieldValue("new.assignee")); | |||
appendField(sb, "Severity", notif.getFieldValue("old.severity"), notif.getFieldValue("new.severity")); | |||
appendField(sb, "Resolution", notif.getFieldValue("old.resolution"), notif.getFieldValue("new.resolution")); | |||
appendField(sb, "Status", notif.getFieldValue("old.status"), notif.getFieldValue("new.status")); | |||
appendField(sb, "Message", notif.getFieldValue("old.message"), notif.getFieldValue("new.message")); | |||
appendFooter(sb, notif); | |||
EmailMessage message = new EmailMessage() | |||
.setMessageId("issue-changes/" + issueKey) | |||
.setSubject("Issue #" + issueKey) | |||
.setMessage(sb.toString()); | |||
if (author != null) { | |||
message.setFrom(getUserFullName(author)); | |||
} | |||
return message; | |||
} | |||
private void appendField(StringBuilder sb, String name, @Nullable String oldValue, @Nullable String newValue) { | |||
if (oldValue != null || newValue != null) { | |||
sb.append(name).append(": "); | |||
if (newValue != null) { | |||
sb.append(newValue); | |||
} | |||
if (oldValue != null) { | |||
sb.append(" (was ").append(oldValue).append(")"); | |||
} | |||
sb.append('\n'); | |||
} | |||
} | |||
private void appendFooter(StringBuilder sb, Notification notification) { | |||
String issueKey = notification.getFieldValue("key"); | |||
sb.append("\n") | |||
.append("See it in Sonar: ").append(settings.getServerBaseURL()).append("/issue/show/").append(issueKey).append('\n'); | |||
} | |||
private String getUserFullName(@Nullable String login) { | |||
if (login == null) { | |||
return null; | |||
} | |||
User user = userFinder.findByLogin(login); | |||
if (user == null) { | |||
// most probably user was deleted | |||
return login; | |||
} | |||
return StringUtils.defaultIfBlank(user.name(), login); | |||
} | |||
} |
@@ -18,50 +18,61 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonar.plugins.core.notifications.reviews; | |||
package org.sonar.plugins.core.issue.notification; | |||
import com.google.common.base.Objects; | |||
import com.google.common.collect.Multimap; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.sonar.api.notifications.Notification; | |||
import org.sonar.api.notifications.NotificationChannel; | |||
import org.sonar.api.notifications.NotificationDispatcher; | |||
import org.sonar.api.notifications.NotificationManager; | |||
import org.sonar.core.review.ReviewDto; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.notifications.*; | |||
import java.util.Collection; | |||
import java.util.Map; | |||
/** | |||
* This dispatcher means: "notify me when someone resolve a review as false positive". | |||
* | |||
* This dispatcher means: "notify me when someone resolves an issue as false positive". | |||
* | |||
* @since 3.6 | |||
*/ | |||
public class NewFalsePositiveReview extends NotificationDispatcher { | |||
public class NewFalsePositiveNotificationDispatcher extends NotificationDispatcher { | |||
public static final String KEY = "NewFalsePositiveIssue"; | |||
private NotificationManager notificationManager; | |||
private final NotificationManager notifications; | |||
public NewFalsePositiveNotificationDispatcher(NotificationManager notifications) { | |||
super("issue-changes"); | |||
this.notifications = notifications; | |||
} | |||
@Override | |||
public String getKey() { | |||
return KEY; | |||
} | |||
public NewFalsePositiveReview(NotificationManager notificationManager) { | |||
super("review-changed"); | |||
this.notificationManager = notificationManager; | |||
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 newResolution = notification.getFieldValue("new.resolution"); | |||
if (StringUtils.equals(newResolution, ReviewDto.RESOLUTION_FALSE_POSITIVE)) { | |||
String author = notification.getFieldValue("author"); | |||
int projectId = Integer.parseInt(notification.getFieldValue("projectId")); | |||
Multimap<String, NotificationChannel> subscribedRecipients = notificationManager.findSubscribedRecipientsForDispatcher(this, projectId); | |||
if (Objects.equal(newResolution, Issue.RESOLUTION_FALSE_POSITIVE)) { | |||
String author = notification.getFieldValue("changeAuthor"); | |||
String projectKey = notification.getFieldValue("projectKey"); | |||
Multimap<String, NotificationChannel> subscribedRecipients = notifications.findNotificationSubscribers(this, projectKey); | |||
notify(author, context, subscribedRecipients); | |||
} | |||
} | |||
private void notify(String author, Context context, Multimap<String, NotificationChannel> subscribedRecipients) { | |||
for (Map.Entry<String, Collection<NotificationChannel>> channelsByRecipients : subscribedRecipients.asMap().entrySet()) { | |||
String userLogin = channelsByRecipients.getKey(); | |||
if (!StringUtils.equals(author, userLogin)) { | |||
String login = channelsByRecipients.getKey(); | |||
// Do not notify the person that resolved the issue | |||
if (!Objects.equal(author, login)) { | |||
for (NotificationChannel channel : channelsByRecipients.getValue()) { | |||
context.addUser(userLogin, channel); | |||
context.addUser(login, channel); | |||
} | |||
} | |||
} |
@@ -17,16 +17,20 @@ | |||
* 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.plugins.core.issue; | |||
package org.sonar.plugins.core.issue.notification; | |||
import org.sonar.api.config.EmailSettings; | |||
import org.sonar.api.notifications.Notification; | |||
import org.sonar.api.utils.DateUtils; | |||
import org.sonar.plugins.emailnotifications.api.EmailMessage; | |||
import org.sonar.plugins.emailnotifications.api.EmailTemplate; | |||
import java.net.URLEncoder; | |||
import java.util.Date; | |||
/** | |||
* Creates email message for notification "new-violations". | |||
* | |||
* Creates email message for notification "new-issues". | |||
* | |||
* @since 2.10 | |||
*/ | |||
public class NewIssuesEmailTemplate extends EmailTemplate { | |||
@@ -51,18 +55,22 @@ public class NewIssuesEmailTemplate extends EmailTemplate { | |||
appendFooter(sb, notification); | |||
EmailMessage message = new EmailMessage() | |||
.setMessageId("new-issues/" + notification.getFieldValue("projectId")) | |||
.setSubject("New issues for project " + projectName) | |||
.setMessage(sb.toString()); | |||
.setMessageId("new-issues/" + notification.getFieldValue("projectKey")) | |||
.setSubject("New issues for project " + projectName) | |||
.setMessage(sb.toString()); | |||
return message; | |||
} | |||
private void appendFooter(StringBuilder sb, Notification notification) { | |||
String projectKey = notification.getFieldValue("projectKey"); | |||
String dateString = notification.getFieldValue("projectDate"); | |||
Date date = DateUtils.parseDateTime(dateString); | |||
String url = String.format("%s/issues/search?componentRoots=%s&createdAfter=%s", settings.getServerBaseURL(), URLEncoder.encode(projectKey), DateUtils.formatDate(date)); | |||
sb.append("\n") | |||
.append("See it in Sonar: ").append(settings.getServerBaseURL()).append("/drilldown/measures/").append(projectKey) | |||
.append("?metric=new_violations\n"); | |||
.append("See it in Sonar: ") | |||
.append(url) | |||
.append("\n"); | |||
} | |||
} |
@@ -17,24 +17,22 @@ | |||
* 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.plugins.core.issue; | |||
package org.sonar.plugins.core.issue.notification; | |||
import com.google.common.collect.Multimap; | |||
import org.sonar.api.notifications.Notification; | |||
import org.sonar.api.notifications.NotificationChannel; | |||
import org.sonar.api.notifications.NotificationDispatcher; | |||
import org.sonar.api.notifications.NotificationManager; | |||
import org.sonar.api.notifications.*; | |||
import java.util.Collection; | |||
import java.util.Map; | |||
/** | |||
* This dispatcher means: "notify me when new issues are introduced during project scan". | |||
* | |||
* | |||
* @since 2.14 | |||
*/ | |||
public class NewIssuesNotificationDispatcher extends NotificationDispatcher { | |||
public static final String KEY = "NewIssues"; | |||
private final NotificationManager manager; | |||
public NewIssuesNotificationDispatcher(NotificationManager manager) { | |||
@@ -42,10 +40,21 @@ public class NewIssuesNotificationDispatcher extends NotificationDispatcher { | |||
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) { | |||
int projectId = Integer.parseInt(notification.getFieldValue("projectId")); | |||
Multimap<String, NotificationChannel> subscribedRecipients = manager.findSubscribedRecipientsForDispatcher(this, projectId); | |||
String projectKey = notification.getFieldValue("projectKey"); | |||
Multimap<String, NotificationChannel> subscribedRecipients = manager.findNotificationSubscribers(this, projectKey); | |||
for (Map.Entry<String, Collection<NotificationChannel>> channelsByRecipients : subscribedRecipients.asMap().entrySet()) { | |||
String userLogin = channelsByRecipients.getKey(); |
@@ -17,27 +17,31 @@ | |||
* 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.plugins.core.issue; | |||
package org.sonar.plugins.core.issue.notification; | |||
import org.sonar.api.batch.PostJob; | |||
import org.sonar.api.batch.SensorContext; | |||
import org.sonar.api.notifications.Notification; | |||
import org.sonar.api.notifications.NotificationManager; | |||
import org.sonar.api.resources.Project; | |||
import org.sonar.api.rules.Rule; | |||
import org.sonar.api.rules.RuleFinder; | |||
import org.sonar.batch.issue.IssueCache; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.IssueChangeContext; | |||
import org.sonar.core.issue.IssueNotifications; | |||
/** | |||
* @since 3.6 | |||
*/ | |||
public class NewIssuesNotificationPostJob implements PostJob { | |||
public class SendIssueNotificationsPostJob implements PostJob { | |||
private final IssueCache issueCache; | |||
private final NotificationManager notifications; | |||
private final IssueNotifications notifications; | |||
private final RuleFinder ruleFinder; | |||
public NewIssuesNotificationPostJob(IssueCache issueCache, NotificationManager notifications) { | |||
public SendIssueNotificationsPostJob(IssueCache issueCache, IssueNotifications notifications, RuleFinder ruleFinder) { | |||
this.issueCache = issueCache; | |||
this.notifications = notifications; | |||
this.ruleFinder = ruleFinder; | |||
} | |||
@Override | |||
@@ -49,20 +53,18 @@ public class NewIssuesNotificationPostJob implements PostJob { | |||
private void sendNotifications(Project project) { | |||
int newIssues = 0; | |||
IssueChangeContext context = IssueChangeContext.createScan(project.getAnalysisDate()); | |||
for (DefaultIssue issue : issueCache.all()) { | |||
if (issue.isNew()) { | |||
if (issue.isNew() && issue.resolution() == null) { | |||
newIssues++; | |||
} | |||
if (issue.isChanged() && issue.diffs() != null) { | |||
Rule rule = ruleFinder.findByKey(issue.ruleKey()); | |||
notifications.sendChanges(issue, context, rule, project, null); | |||
} | |||
} | |||
if (newIssues > 0) { | |||
Notification notification = new Notification("new-issues") | |||
.setDefaultMessage(newIssues + " new issues on " + project.getLongName() + ".") | |||
.setFieldValue("count", String.valueOf(newIssues)) | |||
.setFieldValue("projectName", project.getLongName()) | |||
.setFieldValue("projectKey", project.getKey()) | |||
.setFieldValue("projectId", String.valueOf(project.getId())); | |||
notifications.scheduleForSending(notification); | |||
notifications.sendNewIssues(project, newIssues); | |||
} | |||
} | |||
} |
@@ -0,0 +1,23 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2013 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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. | |||
*/ | |||
@ParametersAreNonnullByDefault | |||
package org.sonar.plugins.core.issue.notification; | |||
import javax.annotation.ParametersAreNonnullByDefault; |
@@ -20,32 +20,41 @@ | |||
package org.sonar.plugins.core.notifications.alerts; | |||
import com.google.common.collect.Multimap; | |||
import org.sonar.api.notifications.Notification; | |||
import org.sonar.api.notifications.NotificationChannel; | |||
import org.sonar.api.notifications.NotificationDispatcher; | |||
import org.sonar.api.notifications.NotificationManager; | |||
import org.sonar.api.notifications.*; | |||
import java.util.Collection; | |||
import java.util.Map; | |||
/** | |||
* This dispatcher means: "notify me each new alert event". | |||
* | |||
* | |||
* @since 3.5 | |||
*/ | |||
public class NewAlerts extends NotificationDispatcher { | |||
private NotificationManager notificationManager; | |||
public static final String KEY = "NewAlerts"; | |||
private final NotificationManager notifications; | |||
public NewAlerts(NotificationManager notificationManager) { | |||
public NewAlerts(NotificationManager notifications) { | |||
super("alerts"); | |||
this.notificationManager = notificationManager; | |||
this.notifications = notifications; | |||
} | |||
@Override | |||
public String getKey() { | |||
return KEY; | |||
} | |||
public static NotificationDispatcherMetadata newMetadata() { | |||
return NotificationDispatcherMetadata.create(KEY) | |||
.setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true)) | |||
.setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true)); | |||
} | |||
@Override | |||
public void dispatch(Notification notification, Context context) { | |||
int projectId = Integer.parseInt(notification.getFieldValue("projectId")); | |||
Multimap<String, NotificationChannel> subscribedRecipients = notificationManager.findSubscribedRecipientsForDispatcher(this, projectId); | |||
Multimap<String, NotificationChannel> subscribedRecipients = notifications.findSubscribedRecipientsForDispatcher(this, projectId); | |||
for (Map.Entry<String, Collection<NotificationChannel>> channelsByRecipients : subscribedRecipients.asMap().entrySet()) { | |||
String userLogin = channelsByRecipients.getKey(); |
@@ -1,72 +0,0 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2013 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.plugins.core.notifications.reviews; | |||
import com.google.common.collect.Multimap; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.sonar.api.notifications.Notification; | |||
import org.sonar.api.notifications.NotificationChannel; | |||
import org.sonar.api.notifications.NotificationDispatcher; | |||
import org.sonar.api.notifications.NotificationManager; | |||
import java.util.Collection; | |||
/** | |||
* This dispatcher means: "notify me when someone changes review assigned to me or created by me". | |||
* | |||
* @since 2.10 | |||
*/ | |||
public class ChangesInReviewAssignedToMeOrCreatedByMe extends NotificationDispatcher { | |||
private NotificationManager notificationManager; | |||
public ChangesInReviewAssignedToMeOrCreatedByMe(NotificationManager notificationManager) { | |||
super("review-changed"); | |||
this.notificationManager = notificationManager; | |||
} | |||
@Override | |||
public void dispatch(Notification notification, Context context) { | |||
int projectId = Integer.parseInt(notification.getFieldValue("projectId")); | |||
Multimap<String, NotificationChannel> subscribedRecipients = notificationManager.findSubscribedRecipientsForDispatcher(this, projectId); | |||
String author = notification.getFieldValue("author"); | |||
String creator = notification.getFieldValue("creator"); | |||
String oldAssignee = notification.getFieldValue("old.assignee"); | |||
String assignee = notification.getFieldValue("assignee"); | |||
if (creator != null && !StringUtils.equals(author, creator)) { | |||
addUserToContextIfSubscribed(context, creator, subscribedRecipients); | |||
} | |||
if (oldAssignee != null && !StringUtils.equals(author, oldAssignee)) { | |||
addUserToContextIfSubscribed(context, oldAssignee, subscribedRecipients); | |||
} | |||
if (assignee != null && !StringUtils.equals(author, assignee)) { | |||
addUserToContextIfSubscribed(context, assignee, subscribedRecipients); | |||
} | |||
} | |||
private void addUserToContextIfSubscribed(Context context, String user, Multimap<String, NotificationChannel> subscribedRecipients) { | |||
Collection<NotificationChannel> channels = subscribedRecipients.get(user); | |||
for (NotificationChannel channel : channels) { | |||
context.addUser(user, channel); | |||
} | |||
} | |||
} |
@@ -1,58 +0,0 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2013 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.plugins.core.notifications.violations; | |||
import com.google.common.collect.Multimap; | |||
import org.sonar.api.notifications.Notification; | |||
import org.sonar.api.notifications.NotificationChannel; | |||
import org.sonar.api.notifications.NotificationDispatcher; | |||
import org.sonar.api.notifications.NotificationManager; | |||
import java.util.Collection; | |||
import java.util.Map; | |||
/** | |||
* This dispatcher means: "notify me when new violations are introduced during the first differential period". | |||
* | |||
* @since 2.14 | |||
*/ | |||
public class NewViolationsOnFirstDifferentialPeriod extends NotificationDispatcher { | |||
private NotificationManager notificationManager; | |||
public NewViolationsOnFirstDifferentialPeriod(NotificationManager notificationManager) { | |||
super("new-violations"); | |||
this.notificationManager = notificationManager; | |||
} | |||
@Override | |||
public void dispatch(Notification notification, Context context) { | |||
int projectId = Integer.parseInt(notification.getFieldValue("projectId")); | |||
Multimap<String, NotificationChannel> subscribedRecipients = notificationManager.findSubscribedRecipientsForDispatcher(this, projectId); | |||
for (Map.Entry<String, Collection<NotificationChannel>> channelsByRecipients : subscribedRecipients.asMap().entrySet()) { | |||
String userLogin = channelsByRecipients.getKey(); | |||
for (NotificationChannel channel : channelsByRecipients.getValue()) { | |||
context.addUser(userLogin, channel); | |||
} | |||
} | |||
} | |||
} |
@@ -1591,10 +1591,10 @@ server_id_configuration.does_not_match_organisation_pattern=Organisation does no | |||
# | |||
#------------------------------------------------------------------------------ | |||
notification.channel.EmailNotificationChannel=Email | |||
notification.dispatcher.ChangesInReviewAssignedToMeOrCreatedByMe=Changes in reviews assigned to me or created by me | |||
notification.dispatcher.ChangesOnMyIssue=Changes in issues assigned to me or reported by me | |||
notification.dispatcher.NewIssues=New issues | |||
notification.dispatcher.NewAlerts=New alerts | |||
notification.dispatcher.NewFalsePositiveReview=New false positive issue | |||
notification.dispatcher.NewFalsePositiveIssue=New false positive issue | |||
#------------------------------------------------------------------------------ |
@@ -0,0 +1,115 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2013 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.plugins.core.issue.notification; | |||
import com.google.common.collect.HashMultimap; | |||
import com.google.common.collect.Multimap; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import org.junit.runner.RunWith; | |||
import org.mockito.Mock; | |||
import org.mockito.runners.MockitoJUnitRunner; | |||
import org.sonar.api.notifications.*; | |||
import static org.fest.assertions.Assertions.assertThat; | |||
import static org.mockito.Matchers.any; | |||
import static org.mockito.Mockito.*; | |||
@RunWith(MockitoJUnitRunner.class) | |||
public class ChangesOnMyIssueNotificationDispatcherTest { | |||
@Mock | |||
NotificationManager notifications; | |||
@Mock | |||
NotificationDispatcher.Context context; | |||
@Mock | |||
NotificationChannel emailChannel; | |||
@Mock | |||
NotificationChannel twitterChannel; | |||
ChangesOnMyIssueNotificationDispatcher dispatcher; | |||
@Before | |||
public void setUp() { | |||
dispatcher = new ChangesOnMyIssueNotificationDispatcher(notifications); | |||
} | |||
@Test | |||
public void test_metadata() throws Exception { | |||
NotificationDispatcherMetadata metadata = ChangesOnMyIssueNotificationDispatcher.newMetadata(); | |||
assertThat(metadata.getDispatcherKey()).isEqualTo(dispatcher.getKey()); | |||
assertThat(metadata.getProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION)).isEqualTo("true"); | |||
assertThat(metadata.getProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION)).isEqualTo("true"); | |||
} | |||
@Test | |||
public void should_not_dispatch_if_other_notification_type() throws Exception { | |||
Notification notification = new Notification("other-notif"); | |||
dispatcher.performDispatch(notification, context); | |||
verify(context, never()).addUser(any(String.class), any(NotificationChannel.class)); | |||
} | |||
@Test | |||
public void should_dispatch_to_reporter_and_assignee() { | |||
Multimap<String, NotificationChannel> recipients = HashMultimap.create(); | |||
recipients.put("simon", emailChannel); | |||
recipients.put("freddy", twitterChannel); | |||
recipients.put("godin", twitterChannel); | |||
when(notifications.findNotificationSubscribers(dispatcher, "struts")).thenReturn(recipients); | |||
Notification notification = new Notification("issue-changes").setFieldValue("projectKey", "struts") | |||
.setFieldValue("changeAuthor", "olivier") | |||
.setFieldValue("reporter", "simon") | |||
.setFieldValue("assignee", "freddy"); | |||
dispatcher.performDispatch(notification, context); | |||
verify(context).addUser("simon", emailChannel); | |||
verify(context).addUser("freddy", twitterChannel); | |||
verify(context, never()).addUser("godin", twitterChannel); | |||
verifyNoMoreInteractions(context); | |||
} | |||
@Test | |||
public void should_not_dispatch_to_author_of_changes() { | |||
Multimap<String, NotificationChannel> recipients = HashMultimap.create(); | |||
recipients.put("simon", emailChannel); | |||
recipients.put("freddy", twitterChannel); | |||
recipients.put("godin", twitterChannel); | |||
when(notifications.findNotificationSubscribers(dispatcher, "struts")).thenReturn(recipients); | |||
// change author is the reporter | |||
dispatcher.performDispatch(new Notification("issue-changes").setFieldValue("projectKey", "struts") | |||
.setFieldValue("changeAuthor", "simon").setFieldValue("reporter", "simon"), context); | |||
// change author is the assignee | |||
dispatcher.performDispatch(new Notification("issue-changes").setFieldValue("projectKey", "struts") | |||
.setFieldValue("changeAuthor", "simon").setFieldValue("assignee", "simon"), context); | |||
// no change author | |||
dispatcher.performDispatch(new Notification("issue-changes").setFieldValue("projectKey", "struts") | |||
.setFieldValue("new.resolution", "FIXED"), context); | |||
verifyNoMoreInteractions(context); | |||
} | |||
} |
@@ -0,0 +1,98 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2013 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.plugins.core.issue.notification; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import org.junit.runner.RunWith; | |||
import org.mockito.Mock; | |||
import org.mockito.runners.MockitoJUnitRunner; | |||
import org.sonar.api.config.EmailSettings; | |||
import org.sonar.api.notifications.Notification; | |||
import org.sonar.api.user.User; | |||
import org.sonar.api.user.UserFinder; | |||
import org.sonar.plugins.emailnotifications.api.EmailMessage; | |||
import static org.fest.assertions.Assertions.assertThat; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
@RunWith(MockitoJUnitRunner.class) | |||
public class IssueChangesEmailTemplateTest { | |||
@Mock | |||
UserFinder userFinder; | |||
IssueChangesEmailTemplate template; | |||
@Before | |||
public void setUp() { | |||
EmailSettings settings = mock(EmailSettings.class); | |||
when(settings.getServerBaseURL()).thenReturn("http://nemo.sonarsource.org"); | |||
template = new IssueChangesEmailTemplate(settings, userFinder); | |||
} | |||
@Test | |||
public void should_ignore_non_issue_changes() { | |||
Notification notification = new Notification("other"); | |||
EmailMessage message = template.format(notification); | |||
assertThat(message).isNull(); | |||
} | |||
@Test | |||
public void should_format_change() { | |||
Notification notification = new Notification("issue-changes") | |||
.setFieldValue("projectName", "Struts") | |||
.setFieldValue("projectKey", "org.apache:struts") | |||
.setFieldValue("key", "ABCDE") | |||
.setFieldValue("ruleName", "Avoid Cycles") | |||
.setFieldValue("message", "Has 3 cycles") | |||
.setFieldValue("old.assignee", "simon") | |||
.setFieldValue("new.assignee", "louis") | |||
.setFieldValue("new.resolution", "FALSE-POSITIVE") | |||
.setFieldValue("new.status", "RESOLVED"); | |||
EmailMessage message = template.format(notification); | |||
assertThat(message.getMessageId()).isEqualTo("issue-changes/ABCDE"); | |||
assertThat(message.getSubject()).isEqualTo("Issue #ABCDE"); | |||
assertThat(message.getMessage()).contains("Rule: Avoid Cycles"); | |||
assertThat(message.getMessage()).contains("Message: Has 3 cycles"); | |||
assertThat(message.getMessage()).contains("Assignee: louis (was simon)"); | |||
assertThat(message.getMessage()).contains("Resolution: FALSE-POSITIVE"); | |||
assertThat(message.getMessage()).contains("See it in Sonar: http://nemo.sonarsource.org/issue/show/ABCDE"); | |||
assertThat(message.getFrom()).isNull(); | |||
} | |||
@Test | |||
public void notification_sender_should_be_the_author_of_change() { | |||
User user = mock(User.class); | |||
when(user.name()).thenReturn("Simon"); | |||
when(userFinder.findByLogin("simon")).thenReturn(user); | |||
Notification notification = new Notification("issue-changes") | |||
.setFieldValue("projectName", "Struts") | |||
.setFieldValue("projectKey", "org.apache:struts") | |||
.setFieldValue("changeAuthor", "simon"); | |||
EmailMessage message = template.format(notification); | |||
assertThat(message.getFrom()).isEqualTo("Simon"); | |||
} | |||
} |
@@ -0,0 +1,95 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2013 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.plugins.core.issue.notification; | |||
import com.google.common.collect.HashMultimap; | |||
import com.google.common.collect.Multimap; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import org.junit.runner.RunWith; | |||
import org.mockito.Mock; | |||
import org.mockito.runners.MockitoJUnitRunner; | |||
import org.sonar.api.notifications.*; | |||
import static org.fest.assertions.Assertions.assertThat; | |||
import static org.mockito.Matchers.any; | |||
import static org.mockito.Mockito.*; | |||
import static org.mockito.Mockito.verifyNoMoreInteractions; | |||
import static org.mockito.Mockito.when; | |||
@RunWith(MockitoJUnitRunner.class) | |||
public class NewFalsePositiveNotificationDispatcherTest { | |||
@Mock | |||
NotificationManager notifications; | |||
@Mock | |||
NotificationDispatcher.Context context; | |||
@Mock | |||
NotificationChannel emailChannel; | |||
@Mock | |||
NotificationChannel twitterChannel; | |||
NewFalsePositiveNotificationDispatcher dispatcher; | |||
@Before | |||
public void setUp() { | |||
dispatcher = new NewFalsePositiveNotificationDispatcher(notifications); | |||
} | |||
@Test | |||
public void test_metadata() throws Exception { | |||
NotificationDispatcherMetadata metadata = NewFalsePositiveNotificationDispatcher.newMetadata(); | |||
assertThat(metadata.getDispatcherKey()).isEqualTo(dispatcher.getKey()); | |||
assertThat(metadata.getProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION)).isEqualTo("true"); | |||
assertThat(metadata.getProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION)).isEqualTo("true"); | |||
} | |||
@Test | |||
public void should_not_dispatch_if_other_notification_type() throws Exception { | |||
Notification notification = new Notification("other"); | |||
dispatcher.performDispatch(notification, context); | |||
verify(context, never()).addUser(any(String.class), any(NotificationChannel.class)); | |||
} | |||
@Test | |||
public void should_dispatch_to_subscribers() { | |||
Multimap<String, NotificationChannel> recipients = HashMultimap.create(); | |||
recipients.put("simon", emailChannel); | |||
recipients.put("freddy", twitterChannel); | |||
recipients.put("godin", twitterChannel); | |||
when(notifications.findNotificationSubscribers(dispatcher, "struts")).thenReturn(recipients); | |||
Notification notification = new Notification("issue-changes").setFieldValue("projectKey", "struts") | |||
.setFieldValue("changeAuthor", "godin") | |||
.setFieldValue("new.resolution", "FALSE-POSITIVE") | |||
.setFieldValue("assignee", "freddy"); | |||
dispatcher.performDispatch(notification, context); | |||
verify(context).addUser("simon", emailChannel); | |||
verify(context).addUser("freddy", twitterChannel); | |||
// do not notify the person who flagged the issue as false-positive | |||
verify(context, never()).addUser("godin", twitterChannel); | |||
verifyNoMoreInteractions(context); | |||
} | |||
} |
@@ -17,7 +17,7 @@ | |||
* 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.plugins.core.issue; | |||
package org.sonar.plugins.core.issue.notification; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
@@ -25,9 +25,7 @@ import org.sonar.api.config.EmailSettings; | |||
import org.sonar.api.notifications.Notification; | |||
import org.sonar.plugins.emailnotifications.api.EmailMessage; | |||
import static org.hamcrest.Matchers.is; | |||
import static org.hamcrest.Matchers.nullValue; | |||
import static org.junit.Assert.assertThat; | |||
import static org.fest.assertions.Assertions.assertThat; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
@@ -46,7 +44,7 @@ public class NewIssuesEmailTemplateTest { | |||
public void shouldNotFormatIfNotCorrectNotification() { | |||
Notification notification = new Notification("other-notif"); | |||
EmailMessage message = template.format(notification); | |||
assertThat(message, nullValue()); | |||
assertThat(message).isNull(); | |||
} | |||
/** | |||
@@ -64,18 +62,18 @@ public class NewIssuesEmailTemplateTest { | |||
public void shouldFormatCommentAdded() { | |||
Notification notification = new Notification("new-issues") | |||
.setFieldValue("count", "32") | |||
.setFieldValue("projectName", "Foo") | |||
.setFieldValue("projectKey", "org.sonar.foo:foo") | |||
.setFieldValue("projectId", "45"); | |||
.setFieldValue("projectName", "Struts") | |||
.setFieldValue("projectKey", "org.apache:struts") | |||
.setFieldValue("projectDate", "2010-05-18T15:50:45+0100"); | |||
EmailMessage message = template.format(notification); | |||
assertThat(message.getMessageId(), is("new-issues/45")); | |||
assertThat(message.getSubject(), is("New issues for project Foo")); | |||
assertThat(message.getMessage(), is("" + | |||
"Project: Foo\n" + | |||
assertThat(message.getMessageId()).isEqualTo("new-issues/org.apache:struts"); | |||
assertThat(message.getSubject()).isEqualTo("New issues for project Struts"); | |||
assertThat(message.getMessage()).isEqualTo("" + | |||
"Project: Struts\n" + | |||
"32 new issues\n" + | |||
"\n" + | |||
"See it in Sonar: http://nemo.sonarsource.org/drilldown/measures/org.sonar.foo:foo?metric=new_violations\n")); | |||
"See it in Sonar: http://nemo.sonarsource.org/issues/search?componentRoots=org.apache%3Astruts&createdAfter=2010-05-18\n"); | |||
} | |||
} |
@@ -17,7 +17,7 @@ | |||
* 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.plugins.core.issue; | |||
package org.sonar.plugins.core.issue.notification; | |||
import com.google.common.collect.HashMultimap; | |||
import com.google.common.collect.Multimap; | |||
@@ -68,9 +68,9 @@ public class NewIssuesNotificationDispatcherTest { | |||
Multimap<String, NotificationChannel> recipients = HashMultimap.create(); | |||
recipients.put("user1", emailChannel); | |||
recipients.put("user2", twitterChannel); | |||
when(notifications.findSubscribedRecipientsForDispatcher(dispatcher, 34)).thenReturn(recipients); | |||
when(notifications.findNotificationSubscribers(dispatcher, "struts")).thenReturn(recipients); | |||
Notification notification = new Notification("new-issues").setFieldValue("projectId", "34"); | |||
Notification notification = new Notification("new-issues").setFieldValue("projectKey", "struts"); | |||
dispatcher.performDispatch(notification, context); | |||
verify(context).addUser("user1", emailChannel); |
@@ -17,27 +17,26 @@ | |||
* 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.plugins.core.issue; | |||
package org.sonar.plugins.core.issue.notification; | |||
import org.junit.Test; | |||
import org.junit.runner.RunWith; | |||
import org.mockito.ArgumentMatcher; | |||
import org.mockito.Mock; | |||
import org.mockito.runners.MockitoJUnitRunner; | |||
import org.sonar.api.batch.SensorContext; | |||
import org.sonar.api.notifications.Notification; | |||
import org.sonar.api.notifications.NotificationManager; | |||
import org.sonar.api.resources.Project; | |||
import org.sonar.api.rules.RuleFinder; | |||
import org.sonar.api.utils.DateUtils; | |||
import org.sonar.batch.issue.IssueCache; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.IssueNotifications; | |||
import java.util.Arrays; | |||
import static org.mockito.Matchers.argThat; | |||
import static org.mockito.Mockito.*; | |||
@RunWith(MockitoJUnitRunner.class) | |||
public class NewIssuesNotificationPostJobTest { | |||
public class SendIssueNotificationsPostJobTest { | |||
@Mock | |||
Project project; | |||
@@ -45,7 +44,10 @@ public class NewIssuesNotificationPostJobTest { | |||
IssueCache issueCache; | |||
@Mock | |||
NotificationManager notifications; | |||
IssueNotifications notifications; | |||
@Mock | |||
RuleFinder ruleFinder; | |||
@Mock | |||
SensorContext sensorContext; | |||
@@ -53,41 +55,38 @@ public class NewIssuesNotificationPostJobTest { | |||
@Test | |||
public void should_not_send_notif_if_past_scan() throws Exception { | |||
when(project.isLatestAnalysis()).thenReturn(false); | |||
when(project.getAnalysisDate()).thenReturn(DateUtils.parseDate("2013-05-18")); | |||
NewIssuesNotificationPostJob job = new NewIssuesNotificationPostJob(issueCache, notifications); | |||
SendIssueNotificationsPostJob job = new SendIssueNotificationsPostJob(issueCache, notifications, ruleFinder); | |||
job.executeOn(project, sensorContext); | |||
verifyZeroInteractions(notifications, issueCache, sensorContext); | |||
verifyZeroInteractions(notifications, issueCache, ruleFinder, sensorContext); | |||
} | |||
@Test | |||
public void should_send_notif_if_new_issues() throws Exception { | |||
when(project.isLatestAnalysis()).thenReturn(true); | |||
when(project.getAnalysisDate()).thenReturn(DateUtils.parseDate("2013-05-18")); | |||
when(issueCache.all()).thenReturn(Arrays.asList( | |||
new DefaultIssue().setNew(true), | |||
new DefaultIssue().setNew(false) | |||
)); | |||
NewIssuesNotificationPostJob job = new NewIssuesNotificationPostJob(issueCache, notifications); | |||
SendIssueNotificationsPostJob job = new SendIssueNotificationsPostJob(issueCache, notifications, ruleFinder); | |||
job.executeOn(project, sensorContext); | |||
verify(notifications).scheduleForSending(argThat(new ArgumentMatcher<Notification>() { | |||
@Override | |||
public boolean matches(Object o) { | |||
Notification n = (Notification) o; | |||
return n.getType().equals("new-issues") && n.getFieldValue("count").equals("1"); | |||
} | |||
})); | |||
verify(notifications).sendNewIssues(project, 1); | |||
} | |||
@Test | |||
public void should_not_send_notif_if_no_new_issues() throws Exception { | |||
when(project.isLatestAnalysis()).thenReturn(true); | |||
when(project.getAnalysisDate()).thenReturn(DateUtils.parseDate("2013-05-18")); | |||
when(issueCache.all()).thenReturn(Arrays.asList( | |||
new DefaultIssue().setNew(false) | |||
)); | |||
NewIssuesNotificationPostJob job = new NewIssuesNotificationPostJob(issueCache, notifications); | |||
SendIssueNotificationsPostJob job = new SendIssueNotificationsPostJob(issueCache, notifications, ruleFinder); | |||
job.executeOn(project, sensorContext); | |||
verifyZeroInteractions(notifications); |
@@ -1,118 +0,0 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2013 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.plugins.core.notifications.reviews; | |||
import com.google.common.collect.HashMultimap; | |||
import com.google.common.collect.Multimap; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import org.mockito.Mock; | |||
import org.mockito.MockitoAnnotations; | |||
import org.sonar.api.notifications.Notification; | |||
import org.sonar.api.notifications.NotificationChannel; | |||
import org.sonar.api.notifications.NotificationDispatcher; | |||
import org.sonar.api.notifications.NotificationManager; | |||
import static org.mockito.Matchers.any; | |||
import static org.mockito.Mockito.never; | |||
import static org.mockito.Mockito.verify; | |||
import static org.mockito.Mockito.verifyNoMoreInteractions; | |||
import static org.mockito.Mockito.when; | |||
public class ChangesInReviewAssignedToMeOrCreatedByMeTest { | |||
@Mock | |||
private NotificationManager notificationManager; | |||
@Mock | |||
private NotificationDispatcher.Context context; | |||
@Mock | |||
private NotificationChannel emailChannel; | |||
@Mock | |||
private NotificationChannel twitterChannel; | |||
private ChangesInReviewAssignedToMeOrCreatedByMe dispatcher; | |||
@Before | |||
public void before() { | |||
MockitoAnnotations.initMocks(this); | |||
dispatcher = new ChangesInReviewAssignedToMeOrCreatedByMe(notificationManager); | |||
} | |||
@Test | |||
public void should_not_dispatch_if_not_new_violations_notification() throws Exception { | |||
Notification notification = new Notification("other-notif"); | |||
dispatcher.performDispatch(notification, context); | |||
verify(context, never()).addUser(any(String.class), any(NotificationChannel.class)); | |||
} | |||
@Test | |||
public void should_dispatch_to_creator_and_assignee() { | |||
Multimap<String, NotificationChannel> recipients = HashMultimap.create(); | |||
recipients.put("simon", emailChannel); | |||
recipients.put("freddy", twitterChannel); | |||
recipients.put("godin", twitterChannel); | |||
when(notificationManager.findSubscribedRecipientsForDispatcher(dispatcher, 42)).thenReturn(recipients); | |||
Notification notification = new Notification("review-changed") | |||
.setFieldValue("projectId", "42") | |||
.setFieldValue("author", "olivier") | |||
.setFieldValue("creator", "simon") | |||
.setFieldValue("old.assignee", "godin") | |||
.setFieldValue("assignee", "freddy"); | |||
dispatcher.performDispatch(notification, context); | |||
verify(context).addUser("simon", emailChannel); | |||
verify(context).addUser("freddy", twitterChannel); | |||
verify(context).addUser("godin", twitterChannel); | |||
verifyNoMoreInteractions(context); | |||
} | |||
@Test | |||
public void should_not_dispatch_to_author_of_changes() { | |||
Multimap<String, NotificationChannel> recipients = HashMultimap.create(); | |||
recipients.put("simon", emailChannel); | |||
recipients.put("freddy", twitterChannel); | |||
recipients.put("godin", twitterChannel); | |||
when(notificationManager.findSubscribedRecipientsForDispatcher(dispatcher, 42)).thenReturn(recipients); | |||
dispatcher.performDispatch(new Notification("review-changed").setFieldValue("projectId", "42") | |||
.setFieldValue("author", "simon").setFieldValue("creator", "simon"), context); | |||
dispatcher.performDispatch(new Notification("review-changed").setFieldValue("projectId", "42") | |||
.setFieldValue("author", "simon").setFieldValue("assignee", "simon"), context); | |||
dispatcher.performDispatch(new Notification("review-changed").setFieldValue("projectId", "42") | |||
.setFieldValue("author", "simon").setFieldValue("old.assignee", "simon"), context); | |||
verifyNoMoreInteractions(context); | |||
} | |||
@Test | |||
public void should_not_dispatch_when_other_notification_type() { | |||
Notification notification = new Notification("other"); | |||
dispatcher.performDispatch(notification, context); | |||
verifyNoMoreInteractions(context); | |||
} | |||
} |
@@ -1,120 +0,0 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2013 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.plugins.core.notifications.reviews; | |||
import com.google.common.collect.HashMultimap; | |||
import com.google.common.collect.Multimap; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import org.mockito.Mock; | |||
import org.mockito.MockitoAnnotations; | |||
import org.sonar.api.notifications.Notification; | |||
import org.sonar.api.notifications.NotificationChannel; | |||
import org.sonar.api.notifications.NotificationDispatcher; | |||
import org.sonar.api.notifications.NotificationManager; | |||
import static org.mockito.Matchers.any; | |||
import static org.mockito.Mockito.never; | |||
import static org.mockito.Mockito.verify; | |||
import static org.mockito.Mockito.verifyNoMoreInteractions; | |||
import static org.mockito.Mockito.when; | |||
public class NewFalsePositiveReviewTest { | |||
@Mock | |||
private NotificationManager notificationManager; | |||
@Mock | |||
private NotificationDispatcher.Context context; | |||
@Mock | |||
private NotificationChannel emailChannel; | |||
@Mock | |||
private NotificationChannel twitterChannel; | |||
private NotificationDispatcher dispatcher; | |||
@Before | |||
public void before() { | |||
MockitoAnnotations.initMocks(this); | |||
dispatcher = new NewFalsePositiveReview(notificationManager); | |||
} | |||
@Test | |||
public void should_not_dispatch_if_not_reviews_notification() throws Exception { | |||
Notification notification = new Notification("other-notif"); | |||
dispatcher.performDispatch(notification, context); | |||
verify(context, never()).addUser(any(String.class), any(NotificationChannel.class)); | |||
} | |||
@Test | |||
public void should_dispatch_false_positive_resolution_to_every_subscribers() { | |||
Multimap<String, NotificationChannel> recipients = HashMultimap.create(); | |||
recipients.put("user1", emailChannel); | |||
recipients.put("user2", twitterChannel); | |||
when(notificationManager.findSubscribedRecipientsForDispatcher(dispatcher, 42)).thenReturn(recipients); | |||
dispatcher.performDispatch(new Notification("review-changed") | |||
.setFieldValue("projectId", "42") | |||
.setFieldValue("author", "user3") | |||
.setFieldValue("new.resolution", "FALSE-POSITIVE"), | |||
context); | |||
verify(context).addUser("user1", emailChannel); | |||
verify(context).addUser("user2", twitterChannel); | |||
verifyNoMoreInteractions(context); | |||
} | |||
@Test | |||
public void should_not_dispatch_to_author_of_changes() { | |||
Multimap<String, NotificationChannel> recipients = HashMultimap.create(); | |||
recipients.put("user1", emailChannel); | |||
recipients.put("user2", twitterChannel); | |||
when(notificationManager.findSubscribedRecipientsForDispatcher(dispatcher, 42)).thenReturn(recipients); | |||
dispatcher.performDispatch(new Notification("review-changed") | |||
.setFieldValue("projectId", "42") | |||
.setFieldValue("author", "user1") | |||
.setFieldValue("new.resolution", "FALSE-POSITIVE"), | |||
context); | |||
verify(context).addUser("user2", twitterChannel); | |||
verify(context, never()).addUser("user1", emailChannel); | |||
verifyNoMoreInteractions(context); | |||
} | |||
@Test | |||
public void should_not_dispatch_other_than_false_positive_resolution() { | |||
Multimap<String, NotificationChannel> recipients = HashMultimap.create(); | |||
recipients.put("user", emailChannel); | |||
when(notificationManager.findSubscribedRecipientsForDispatcher(dispatcher, 42)).thenReturn(recipients); | |||
dispatcher.performDispatch(new Notification("review-changed") | |||
.setFieldValue("projectId", "42") | |||
.setFieldValue("author", "user2") | |||
.setFieldValue("new.assignee", "user"), context); | |||
verify(context, never()).addUser(any(String.class), any(NotificationChannel.class)); | |||
} | |||
} |
@@ -1,84 +0,0 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2013 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.plugins.core.notifications.violations; | |||
import com.google.common.collect.HashMultimap; | |||
import com.google.common.collect.Multimap; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import org.mockito.Mock; | |||
import org.mockito.MockitoAnnotations; | |||
import org.sonar.api.notifications.Notification; | |||
import org.sonar.api.notifications.NotificationChannel; | |||
import org.sonar.api.notifications.NotificationDispatcher; | |||
import org.sonar.api.notifications.NotificationManager; | |||
import static org.mockito.Matchers.any; | |||
import static org.mockito.Mockito.never; | |||
import static org.mockito.Mockito.verify; | |||
import static org.mockito.Mockito.verifyNoMoreInteractions; | |||
import static org.mockito.Mockito.when; | |||
public class NewViolationsOnFirstDifferentialPeriodTest { | |||
@Mock | |||
private NotificationManager notificationManager; | |||
@Mock | |||
private NotificationDispatcher.Context context; | |||
@Mock | |||
private NotificationChannel emailChannel; | |||
@Mock | |||
private NotificationChannel twitterChannel; | |||
private NewViolationsOnFirstDifferentialPeriod dispatcher; | |||
@Before | |||
public void init() { | |||
MockitoAnnotations.initMocks(this); | |||
dispatcher = new NewViolationsOnFirstDifferentialPeriod(notificationManager); | |||
} | |||
@Test | |||
public void shouldNotDispatchIfNotNewViolationsNotification() throws Exception { | |||
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<String, NotificationChannel> recipients = HashMultimap.create(); | |||
recipients.put("user1", emailChannel); | |||
recipients.put("user2", twitterChannel); | |||
when(notificationManager.findSubscribedRecipientsForDispatcher(dispatcher, 34)).thenReturn(recipients); | |||
Notification notification = new Notification("new-violations").setFieldValue("projectId", "34"); | |||
dispatcher.performDispatch(notification, context); | |||
verify(context).addUser("user1", emailChannel); | |||
verify(context).addUser("user2", twitterChannel); | |||
verifyNoMoreInteractions(context); | |||
} | |||
} |
@@ -22,7 +22,6 @@ package org.sonar.plugins.emailnotifications; | |||
import com.google.common.collect.ImmutableList; | |||
import org.sonar.api.SonarPlugin; | |||
import org.sonar.plugins.emailnotifications.templates.alerts.AlertsEmailTemplate; | |||
import org.sonar.plugins.emailnotifications.templates.reviews.ReviewEmailTemplate; | |||
import java.util.List; | |||
@@ -32,7 +31,6 @@ public class EmailNotificationsPlugin extends SonarPlugin { | |||
EmailNotificationChannel.class, | |||
// Email templates | |||
ReviewEmailTemplate.class, | |||
AlertsEmailTemplate.class); | |||
} | |||
} |
@@ -1,128 +0,0 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2013 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.plugins.emailnotifications.templates.reviews; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.sonar.api.config.EmailSettings; | |||
import org.sonar.api.database.model.User; | |||
import org.sonar.api.notifications.Notification; | |||
import org.sonar.api.security.UserFinder; | |||
import org.sonar.plugins.emailnotifications.api.EmailMessage; | |||
import org.sonar.plugins.emailnotifications.api.EmailTemplate; | |||
/** | |||
* Creates email message for notification "review-changed". | |||
* | |||
* @since 2.10 | |||
*/ | |||
public class ReviewEmailTemplate extends EmailTemplate { | |||
private EmailSettings configuration; | |||
private UserFinder userFinder; | |||
public ReviewEmailTemplate(EmailSettings configuration, UserFinder userFinder) { | |||
this.configuration = configuration; | |||
this.userFinder = userFinder; | |||
} | |||
@Override | |||
public EmailMessage format(Notification notification) { | |||
if (!"review-changed".equals(notification.getType())) { | |||
return null; | |||
} | |||
String reviewId = notification.getFieldValue("reviewId"); | |||
String author = notification.getFieldValue("author"); | |||
StringBuilder sb = new StringBuilder(); | |||
append(sb, "Project", null, notification.getFieldValue("project")); | |||
append(sb, "Resource", null, notification.getFieldValue("resource")); | |||
sb.append('\n'); | |||
append(sb, null, null, notification.getFieldValue("title")); | |||
sb.append('\n'); | |||
append(sb, "Status", notification.getFieldValue("old.status"), notification.getFieldValue("new.status")); | |||
append(sb, "Resolution", notification.getFieldValue("old.resolution"), notification.getFieldValue("new.resolution")); | |||
append(sb, "Assignee", getUserFullName(notification.getFieldValue("old.assignee")), getUserFullName(notification.getFieldValue("new.assignee"))); | |||
appendComment(sb, notification); | |||
appendFooter(sb, notification); | |||
EmailMessage message = new EmailMessage() | |||
.setMessageId("review/" + reviewId) | |||
.setSubject("Review #" + reviewId + ("FALSE-POSITIVE".equals(notification.getFieldValue("new.resolution")) ? " - False Positive" : "")) | |||
.setMessage(sb.toString()); | |||
if (author != null) { | |||
message.setFrom(getUserFullName(author)); | |||
} | |||
return message; | |||
} | |||
private void append(StringBuilder sb, String name, String oldValue, String newValue) { | |||
if (oldValue != null || newValue != null) { | |||
if (name != null) { | |||
sb.append(name).append(": "); | |||
} | |||
if (newValue != null) { | |||
sb.append(newValue); | |||
} | |||
if (oldValue != null) { | |||
sb.append(" (was ").append(oldValue).append(")"); | |||
} | |||
sb.append('\n'); | |||
} | |||
} | |||
private void appendComment(StringBuilder sb, Notification notification) { | |||
String newComment = notification.getFieldValue("new.comment"); | |||
String oldComment = notification.getFieldValue("old.comment"); | |||
if (newComment != null) { | |||
// comment was added or modified | |||
sb.append("Comment:\n ").append(newComment).append('\n'); | |||
if (oldComment != null) { | |||
// comment was modified | |||
sb.append("Was:\n ").append(oldComment).append('\n'); | |||
} | |||
} else if (oldComment != null) { | |||
// comment was deleted | |||
sb.append("Comment deleted, was:\n ").append(oldComment).append('\n'); | |||
} | |||
} | |||
private void appendFooter(StringBuilder sb, Notification notification) { | |||
String reviewId = notification.getFieldValue("reviewId"); | |||
sb.append("\n") | |||
.append("See it in Sonar: ").append(configuration.getServerBaseURL()).append("/reviews/view/").append(reviewId).append('\n'); | |||
} | |||
/** | |||
* Visibility has been relaxed for tests. | |||
*/ | |||
String getUserFullName(String login) { | |||
if (login == null) { | |||
return null; | |||
} | |||
User user = userFinder.findByLogin(login); | |||
if (user == null) { | |||
// most probably user was deleted | |||
return login; | |||
} | |||
return StringUtils.defaultIfBlank(user.getName(), login); | |||
} | |||
} |
@@ -26,6 +26,6 @@ import static org.fest.assertions.Assertions.assertThat; | |||
public class EmailNotificationsPluginTest { | |||
@Test | |||
public void should_get_extensions() { | |||
assertThat(new EmailNotificationsPlugin().getExtensions()).hasSize(3); | |||
assertThat(new EmailNotificationsPlugin().getExtensions()).hasSize(2); | |||
} | |||
} |
@@ -1,489 +0,0 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2013 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.plugins.emailnotifications.templates.reviews; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import org.sonar.api.config.EmailSettings; | |||
import org.sonar.api.database.model.User; | |||
import org.sonar.api.notifications.Notification; | |||
import org.sonar.api.security.UserFinder; | |||
import org.sonar.plugins.emailnotifications.api.EmailMessage; | |||
import static org.fest.assertions.Assertions.assertThat; | |||
import static org.mockito.Matchers.eq; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
public class ReviewEmailTemplateTest { | |||
private ReviewEmailTemplate template; | |||
@Before | |||
public void setUp() { | |||
EmailSettings configuration = mock(EmailSettings.class); | |||
when(configuration.getServerBaseURL()).thenReturn("http://nemo.sonarsource.org"); | |||
UserFinder userFinder = mock(UserFinder.class); | |||
when(userFinder.findByLogin(eq("freddy.mallet"))).thenReturn(new User().setName("Freddy Mallet")); | |||
when(userFinder.findByLogin(eq("simon.brandhof"))).thenReturn(new User().setName("Simon Brandhof")); | |||
when(userFinder.findByLogin(eq("evgeny.mandrikov"))).thenReturn(new User().setName("Evgeny Mandrikov")); | |||
template = new ReviewEmailTemplate(configuration, userFinder); | |||
} | |||
/** | |||
* <pre> | |||
* Subject: Review #1 | |||
* From: Freddy Mallet | |||
* | |||
* Project: Sonar | |||
* Resource: org.sonar.server.ui.DefaultPages | |||
* | |||
* Utility classes should not have a public or default constructor. | |||
* | |||
* Comment: | |||
* This is my first comment | |||
* | |||
* See it in Sonar: http://nemo.sonarsource.org/review/view/1 | |||
* </pre> | |||
*/ | |||
@Test | |||
public void should_format_comment_added() { | |||
Notification notification = new Notification("review-changed") | |||
.setFieldValue("reviewId", "1") | |||
.setFieldValue("project", "Sonar") | |||
.setFieldValue("resource", "org.sonar.server.ui.DefaultPages") | |||
.setFieldValue("title", "Utility classes should not have a public or default constructor.") | |||
.setFieldValue("author", "freddy.mallet") | |||
.setFieldValue("old.comment", null) | |||
.setFieldValue("new.comment", "This is my first comment"); | |||
EmailMessage message = template.format(notification); | |||
assertThat(message.getMessageId()).isEqualTo("review/1"); | |||
assertThat(message.getSubject()).isEqualTo("Review #1"); | |||
assertThat(message.getFrom()).isEqualTo("Freddy Mallet"); | |||
assertThat(message.getMessage()).isEqualTo("" + | |||
"Project: Sonar\n" + | |||
"Resource: org.sonar.server.ui.DefaultPages\n" + | |||
"\n" + | |||
"Utility classes should not have a public or default constructor.\n" + | |||
"\n" + | |||
"Comment:\n" + | |||
" This is my first comment\n" + | |||
"\n" + | |||
"See it in Sonar: http://nemo.sonarsource.org/reviews/view/1\n"); | |||
} | |||
/** | |||
* <pre> | |||
* Subject: Review #1 | |||
* From: Freddy Mallet | |||
* | |||
* Project: Sonar | |||
* Resource: org.sonar.server.ui.DefaultPages | |||
* | |||
* Utility classes should not have a public or default constructor. | |||
* | |||
* Comment: | |||
* This is another comment | |||
* Was: | |||
* This is my first comment | |||
* | |||
* See it in Sonar: http://nemo.sonarsource.org/review/view/1 | |||
* </pre> | |||
*/ | |||
@Test | |||
public void should_format_commentedited() { | |||
Notification notification = new Notification("review-changed") | |||
.setFieldValue("reviewId", "1") | |||
.setFieldValue("project", "Sonar") | |||
.setFieldValue("resource", "org.sonar.server.ui.DefaultPages") | |||
.setFieldValue("title", "Utility classes should not have a public or default constructor.") | |||
.setFieldValue("author", "freddy.mallet") | |||
.setFieldValue("old.comment", "This is my first comment") | |||
.setFieldValue("new.comment", "This is another comment"); | |||
EmailMessage message = template.format(notification); | |||
assertThat(message.getMessageId()).isEqualTo("review/1"); | |||
assertThat(message.getSubject()).isEqualTo("Review #1"); | |||
assertThat(message.getFrom()).isEqualTo("Freddy Mallet"); | |||
assertThat(message.getMessage()).isEqualTo("" + | |||
"Project: Sonar\n" + | |||
"Resource: org.sonar.server.ui.DefaultPages\n" + | |||
"\n" + | |||
"Utility classes should not have a public or default constructor.\n" + | |||
"\n" + | |||
"Comment:\n" + | |||
" This is another comment\n" + | |||
"Was:\n" + | |||
" This is my first comment\n" + | |||
"\n" + | |||
"See it in Sonar: http://nemo.sonarsource.org/reviews/view/1\n"); | |||
} | |||
/** | |||
* <pre> | |||
* Subject: Review #1 | |||
* From: Freddy Mallet | |||
* | |||
* Project: Sonar | |||
* Resource: org.sonar.server.ui.DefaultPages | |||
* | |||
* Utility classes should not have a public or default constructor. | |||
* | |||
* Comment deleted, was: | |||
* This is deleted comment | |||
* | |||
* See it in Sonar: http://nemo.sonarsource.org/review/view/1 | |||
* </pre> | |||
*/ | |||
@Test | |||
public void should_format_comment_deleted() { | |||
Notification notification = new Notification("review-changed") | |||
.setFieldValue("reviewId", "1") | |||
.setFieldValue("project", "Sonar") | |||
.setFieldValue("resource", "org.sonar.server.ui.DefaultPages") | |||
.setFieldValue("title", "Utility classes should not have a public or default constructor.") | |||
.setFieldValue("old.comment", "This is deleted comment") | |||
.setFieldValue("new.comment", null) | |||
.setFieldValue("author", "freddy.mallet"); | |||
EmailMessage message = template.format(notification); | |||
assertThat(message.getMessageId()).isEqualTo("review/1"); | |||
assertThat(message.getSubject()).isEqualTo("Review #1"); | |||
assertThat(message.getFrom()).isEqualTo("Freddy Mallet"); | |||
assertThat(message.getMessage()).isEqualTo("" + | |||
"Project: Sonar\n" + | |||
"Resource: org.sonar.server.ui.DefaultPages\n" + | |||
"\n" + | |||
"Utility classes should not have a public or default constructor.\n" + | |||
"\n" + | |||
"Comment deleted, was:\n" + | |||
" This is deleted comment\n" + | |||
"\n" + | |||
"See it in Sonar: http://nemo.sonarsource.org/reviews/view/1\n"); | |||
} | |||
/** | |||
* <pre> | |||
* Subject: Review #1 | |||
* From: Freddy Mallet | |||
* | |||
* Project: Sonar | |||
* Resource: org.sonar.server.ui.DefaultPages | |||
* | |||
* Utility classes should not have a public or default constructor. | |||
* | |||
* Assignee: Evgeny Mandrikov | |||
* | |||
* See it in Sonar: http://nemo.sonarsource.org/review/view/1 | |||
* </pre> | |||
*/ | |||
@Test | |||
public void should_format_assigneed() { | |||
Notification notification = new Notification("review-changed") | |||
.setFieldValue("reviewId", "1") | |||
.setFieldValue("project", "Sonar") | |||
.setFieldValue("resource", "org.sonar.server.ui.DefaultPages") | |||
.setFieldValue("title", "Utility classes should not have a public or default constructor.") | |||
.setFieldValue("author", "freddy.mallet") | |||
.setFieldValue("old.assignee", null) | |||
.setFieldValue("new.assignee", "evgeny.mandrikov"); | |||
EmailMessage message = template.format(notification); | |||
assertThat(message.getMessageId()).isEqualTo("review/1"); | |||
assertThat(message.getSubject()).isEqualTo("Review #1"); | |||
assertThat(message.getFrom()).isEqualTo("Freddy Mallet"); | |||
assertThat(message.getMessage()).isEqualTo("" + | |||
"Project: Sonar\n" + | |||
"Resource: org.sonar.server.ui.DefaultPages\n" + | |||
"\n" + | |||
"Utility classes should not have a public or default constructor.\n" + | |||
"\n" + | |||
"Assignee: Evgeny Mandrikov\n" + | |||
"\n" + | |||
"See it in Sonar: http://nemo.sonarsource.org/reviews/view/1\n"); | |||
} | |||
/** | |||
* <pre> | |||
* Subject: Review #1 | |||
* From: Freddy Mallet | |||
* | |||
* Project: Sonar | |||
* Resource: org.sonar.server.ui.DefaultPages | |||
* | |||
* Utility classes should not have a public or default constructor. | |||
* | |||
* Assignee: Simon Brandhof (was Evgeny Mandrikov) | |||
* | |||
* See it in Sonar: http://nemo.sonarsource.org/review/view/1 | |||
* </pre> | |||
*/ | |||
@Test | |||
public void should_format_assigneed_to_another_person() { | |||
Notification notification = new Notification("review-changed") | |||
.setFieldValue("reviewId", "1") | |||
.setFieldValue("project", "Sonar") | |||
.setFieldValue("resource", "org.sonar.server.ui.DefaultPages") | |||
.setFieldValue("title", "Utility classes should not have a public or default constructor.") | |||
.setFieldValue("author", "freddy.mallet") | |||
.setFieldValue("old.assignee", "evgeny.mandrikov") | |||
.setFieldValue("new.assignee", "simon.brandhof"); | |||
EmailMessage message = template.format(notification); | |||
assertThat(message.getMessageId()).isEqualTo("review/1"); | |||
assertThat(message.getSubject()).isEqualTo("Review #1"); | |||
assertThat(message.getFrom()).isEqualTo("Freddy Mallet"); | |||
assertThat(message.getMessage()).isEqualTo("" + | |||
"Project: Sonar\n" + | |||
"Resource: org.sonar.server.ui.DefaultPages\n" + | |||
"\n" + | |||
"Utility classes should not have a public or default constructor.\n" + | |||
"\n" + | |||
"Assignee: Simon Brandhof (was Evgeny Mandrikov)\n" + | |||
"\n" + | |||
"See it in Sonar: http://nemo.sonarsource.org/reviews/view/1\n"); | |||
} | |||
/** | |||
* <pre> | |||
* Subject: Review #1 | |||
* From: Freddy Mallet | |||
* | |||
* Project: Sonar | |||
* Resource: org.sonar.server.ui.DefaultPages | |||
* | |||
* Utility classes should not have a public or default constructor. | |||
* | |||
* Assignee: (was Simon Brandhof) | |||
* | |||
* See it in Sonar: http://nemo.sonarsource.org/review/view/1 | |||
* </pre> | |||
*/ | |||
@Test | |||
public void should_format_unassigned() { | |||
Notification notification = new Notification("review-changed") | |||
.setFieldValue("reviewId", "1") | |||
.setFieldValue("project", "Sonar") | |||
.setFieldValue("resource", "org.sonar.server.ui.DefaultPages") | |||
.setFieldValue("title", "Utility classes should not have a public or default constructor.") | |||
.setFieldValue("author", "freddy.mallet") | |||
.setFieldValue("old.assignee", "simon.brandhof") | |||
.setFieldValue("new.assignee", null); | |||
EmailMessage message = template.format(notification); | |||
assertThat(message.getMessageId()).isEqualTo("review/1"); | |||
assertThat(message.getSubject()).isEqualTo("Review #1"); | |||
assertThat(message.getFrom()).isEqualTo("Freddy Mallet"); | |||
assertThat(message.getMessage()).isEqualTo("" + | |||
"Project: Sonar\n" + | |||
"Resource: org.sonar.server.ui.DefaultPages\n" + | |||
"\n" + | |||
"Utility classes should not have a public or default constructor.\n" + | |||
"\n" + | |||
"Assignee: (was Simon Brandhof)\n" + | |||
"\n" + | |||
"See it in Sonar: http://nemo.sonarsource.org/reviews/view/1\n"); | |||
} | |||
/** | |||
* <pre> | |||
* Subject: Review #1 | |||
* From: Sonar | |||
* | |||
* Project: Sonar | |||
* Resource: org.sonar.server.ui.DefaultPages | |||
* | |||
* Utility classes should not have a public or default constructor. | |||
* | |||
* Status: CLOSED (was OPEN) | |||
* | |||
* See it in Sonar: http://nemo.sonarsource.org/review/view/1 | |||
* </pre> | |||
*/ | |||
@Test | |||
public void should_format_closed() { | |||
Notification notification = new Notification("review-changed") | |||
.setFieldValue("reviewId", "1") | |||
.setFieldValue("project", "Sonar") | |||
.setFieldValue("resource", "org.sonar.server.ui.DefaultPages") | |||
.setFieldValue("title", "Utility classes should not have a public or default constructor.") | |||
.setFieldValue("old.status", "OPEN") | |||
.setFieldValue("new.status", "CLOSED"); | |||
EmailMessage message = template.format(notification); | |||
assertThat(message.getMessageId()).isEqualTo("review/1"); | |||
assertThat(message.getSubject()).isEqualTo("Review #1"); | |||
assertThat(message.getFrom()).isNull(); | |||
assertThat(message.getMessage()).isEqualTo("" + | |||
"Project: Sonar\n" + | |||
"Resource: org.sonar.server.ui.DefaultPages\n" + | |||
"\n" + | |||
"Utility classes should not have a public or default constructor.\n" + | |||
"\n" + | |||
"Status: CLOSED (was OPEN)\n" + | |||
"\n" + | |||
"See it in Sonar: http://nemo.sonarsource.org/reviews/view/1\n"); | |||
} | |||
/** | |||
* <pre> | |||
* Subject: Review #1 | |||
* From: Simon Brandhof | |||
* | |||
* Project: Sonar | |||
* Resource: org.sonar.server.ui.DefaultPages | |||
* | |||
* Utility classes should not have a public or default constructor. | |||
* | |||
* Status: REOPENED (was RESOLVED) | |||
* Resolution: (was FIXED) | |||
* | |||
* See it in Sonar: http://nemo.sonarsource.org/review/view/1 | |||
* </pre> | |||
*/ | |||
@Test | |||
public void should_format_reopened() { | |||
Notification notification = new Notification("review-changed") | |||
.setFieldValue("reviewId", "1") | |||
.setFieldValue("project", "Sonar") | |||
.setFieldValue("resource", "org.sonar.server.ui.DefaultPages") | |||
.setFieldValue("title", "Utility classes should not have a public or default constructor.") | |||
.setFieldValue("old.resolution", "FIXED") | |||
.setFieldValue("new.resolution", null) | |||
.setFieldValue("old.status", "RESOLVED") | |||
.setFieldValue("new.status", "REOPENED"); | |||
EmailMessage message = template.format(notification); | |||
assertThat(message.getMessageId()).isEqualTo("review/1"); | |||
assertThat(message.getSubject()).isEqualTo("Review #1"); | |||
assertThat(message.getFrom()).isNull(); | |||
assertThat(message.getMessage()).isEqualTo("" + | |||
"Project: Sonar\n" + | |||
"Resource: org.sonar.server.ui.DefaultPages\n" + | |||
"\n" + | |||
"Utility classes should not have a public or default constructor.\n" + | |||
"\n" + | |||
"Status: REOPENED (was RESOLVED)\n" + | |||
"Resolution: (was FIXED)\n" + | |||
"\n" + | |||
"See it in Sonar: http://nemo.sonarsource.org/reviews/view/1\n"); | |||
} | |||
/** | |||
* <pre> | |||
* Subject: Review #1 | |||
* From: Simon Brandhof | |||
* | |||
* Project: Sonar | |||
* Resource: org.sonar.server.ui.DefaultPages | |||
* | |||
* Utility classes should not have a public or default constructor. | |||
* | |||
* Status: RESOLVED (was OPEN) | |||
* Resolution: FIXED | |||
* | |||
* See it in Sonar: http://nemo.sonarsource.org/review/view/1 | |||
* </pre> | |||
*/ | |||
@Test | |||
public void should_format_resolved_as_fixed() { | |||
Notification notification = new Notification("review-changed") | |||
.setFieldValue("reviewId", "1") | |||
.setFieldValue("project", "Sonar") | |||
.setFieldValue("resource", "org.sonar.server.ui.DefaultPages") | |||
.setFieldValue("title", "Utility classes should not have a public or default constructor.") | |||
.setFieldValue("author", "simon.brandhof") | |||
.setFieldValue("old.status", "OPEN") | |||
.setFieldValue("old.resolution", null) | |||
.setFieldValue("new.status", "RESOLVED") | |||
.setFieldValue("new.resolution", "FIXED"); | |||
EmailMessage message = template.format(notification); | |||
assertThat(message.getMessageId()).isEqualTo("review/1"); | |||
assertThat(message.getSubject()).isEqualTo("Review #1"); | |||
assertThat(message.getFrom()).isEqualTo("Simon Brandhof"); | |||
assertThat(message.getMessage()).isEqualTo("" + | |||
"Project: Sonar\n" + | |||
"Resource: org.sonar.server.ui.DefaultPages\n" + | |||
"\n" + | |||
"Utility classes should not have a public or default constructor.\n" + | |||
"\n" + | |||
"Status: RESOLVED (was OPEN)\n" + | |||
"Resolution: FIXED\n" + | |||
"\n" + | |||
"See it in Sonar: http://nemo.sonarsource.org/reviews/view/1\n"); | |||
} | |||
/** | |||
* <pre> | |||
* Subject: Review #1 | |||
* From: Simon Brandhof | |||
* | |||
* Project: Sonar | |||
* Resource: org.sonar.server.ui.DefaultPages | |||
* | |||
* Utility classes should not have a public or default constructor. | |||
* | |||
* Status: RESOLVED (was REOPENED) | |||
* Resolution: FALSE-POSITIVE | |||
* Comment: | |||
* Because! | |||
* | |||
* See it in Sonar: http://nemo.sonarsource.org/review/view/1 | |||
* </pre> | |||
*/ | |||
@Test | |||
public void should_format_resolved_as_false_positive() { | |||
Notification notification = new Notification("review-changed") | |||
.setFieldValue("reviewId", "1") | |||
.setFieldValue("project", "Sonar") | |||
.setFieldValue("resource", "org.sonar.server.ui.DefaultPages") | |||
.setFieldValue("title", "Utility classes should not have a public or default constructor.") | |||
.setFieldValue("author", "freddy.mallet") | |||
.setFieldValue("old.status", "REOPENED") | |||
.setFieldValue("old.resolution", null) | |||
.setFieldValue("new.status", "RESOLVED") | |||
.setFieldValue("new.resolution", "FALSE-POSITIVE") | |||
.setFieldValue("new.comment", "Because!"); | |||
EmailMessage message = template.format(notification); | |||
assertThat(message.getMessageId()).isEqualTo("review/1"); | |||
assertThat(message.getSubject()).isEqualTo("Review #1 - False Positive"); | |||
assertThat(message.getFrom()).isEqualTo("Freddy Mallet"); | |||
assertThat(message.getMessage()).isEqualTo("" + | |||
"Project: Sonar\n" + | |||
"Resource: org.sonar.server.ui.DefaultPages\n" + | |||
"\n" + | |||
"Utility classes should not have a public or default constructor.\n" + | |||
"\n" + | |||
"Status: RESOLVED (was REOPENED)\n" + | |||
"Resolution: FALSE-POSITIVE\n" + | |||
"Comment:\n" + | |||
" Because!\n" + | |||
"\n" + | |||
"See it in Sonar: http://nemo.sonarsource.org/reviews/view/1\n"); | |||
} | |||
@Test | |||
public void should_not_format() { | |||
Notification notification = new Notification("other"); | |||
EmailMessage message = template.format(notification); | |||
assertThat(message).isNull(); | |||
} | |||
@Test | |||
public void should_return_full_name_or_login() { | |||
assertThat(template.getUserFullName("freddy.mallet")).isEqualTo("Freddy Mallet"); | |||
assertThat(template.getUserFullName("deleted")).isEqualTo("deleted"); | |||
assertThat(template.getUserFullName(null)).isNull(); | |||
} | |||
} |
@@ -43,6 +43,7 @@ import org.sonar.batch.scan.maven.MavenPluginExecutor; | |||
import org.sonar.batch.source.HighlightableBuilder; | |||
import org.sonar.batch.source.SymbolizableBuilder; | |||
import org.sonar.core.component.ScanGraph; | |||
import org.sonar.core.issue.IssueNotifications; | |||
import org.sonar.core.issue.IssueUpdater; | |||
import org.sonar.core.issue.workflow.FunctionExecutor; | |||
import org.sonar.core.issue.workflow.IssueWorkflow; | |||
@@ -99,6 +100,7 @@ public class ProjectScanContainer extends ComponentContainer { | |||
IssueCache.class, | |||
ScanIssueStorage.class, | |||
IssuePersister.class, | |||
IssueNotifications.class, | |||
// tests | |||
TestPlanPerspectiveLoader.class, |
@@ -0,0 +1,105 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2013 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.core.issue; | |||
import org.sonar.api.BatchComponent; | |||
import org.sonar.api.ServerComponent; | |||
import org.sonar.api.component.Component; | |||
import org.sonar.api.issue.IssueQueryResult; | |||
import org.sonar.api.notifications.Notification; | |||
import org.sonar.api.notifications.NotificationManager; | |||
import org.sonar.api.resources.Project; | |||
import org.sonar.api.rules.Rule; | |||
import org.sonar.api.utils.DateUtils; | |||
import org.sonar.core.i18n.RuleI18nManager; | |||
import javax.annotation.Nullable; | |||
import java.util.Locale; | |||
import java.util.Map; | |||
/** | |||
* Send notifications related to issues. | |||
* | |||
* @since 3.6 | |||
*/ | |||
public class IssueNotifications implements BatchComponent, ServerComponent { | |||
private final NotificationManager notificationsManager; | |||
private final RuleI18nManager ruleI18n; | |||
public IssueNotifications(NotificationManager notificationsManager, RuleI18nManager ruleI18n) { | |||
this.notificationsManager = notificationsManager; | |||
this.ruleI18n = ruleI18n; | |||
} | |||
public Notification sendNewIssues(Project project, int newIssues) { | |||
Notification notification = newNotification(project, "new-issues") | |||
.setDefaultMessage(newIssues + " new issues on " + project.getLongName() + ".") | |||
.setFieldValue("projectDate", DateUtils.formatDateTime(project.getAnalysisDate())) | |||
.setFieldValue("count", String.valueOf(newIssues)); | |||
notificationsManager.scheduleForSending(notification); | |||
return notification; | |||
} | |||
public Notification sendChanges(DefaultIssue issue, IssueChangeContext context, IssueQueryResult queryResult) { | |||
return sendChanges(issue, context, queryResult.rule(issue), queryResult.project(issue), queryResult.component(issue)); | |||
} | |||
public Notification sendChanges(DefaultIssue issue, IssueChangeContext context, Rule rule, Component project, @Nullable Component component) { | |||
Notification notification = newNotification(project, "issue-changes"); | |||
notification.setFieldValue("key", issue.key()); | |||
notification.setFieldValue("changeAuthor", context.login()); | |||
notification.setFieldValue("reporter", issue.reporter()); | |||
notification.setFieldValue("assignee", issue.assignee()); | |||
notification.setFieldValue("message", issue.message()); | |||
notification.setFieldValue("ruleName", ruleName(rule)); | |||
notification.setFieldValue("componentKey", issue.componentKey()); | |||
if (component != null) { | |||
notification.setFieldValue("componentName", component.name()); | |||
} | |||
for (Map.Entry<String, FieldDiffs.Diff> entry : issue.diffs().diffs().entrySet()) { | |||
String type = entry.getKey(); | |||
FieldDiffs.Diff diff = entry.getValue(); | |||
notification.setFieldValue("old." + type, diff.oldValue() != null ? diff.oldValue().toString() : null); | |||
notification.setFieldValue("new." + type, diff.newValue() != null ? diff.newValue().toString() : null); | |||
} | |||
notificationsManager.scheduleForSending(notification); | |||
return notification; | |||
} | |||
private String ruleName(@Nullable Rule rule) { | |||
// this code should definitely be shared in api | |||
if (rule == null) { | |||
return null; | |||
} | |||
String name = ruleI18n.getName(rule.getRepositoryKey(), rule.getKey(), Locale.ENGLISH); | |||
if (name == null) { | |||
name = rule.getName(); | |||
} | |||
return name; | |||
} | |||
private Notification newNotification(Component project, String key) { | |||
return new Notification(key) | |||
.setFieldValue("projectName", project.longName()) | |||
.setFieldValue("projectKey", project.key()); | |||
} | |||
} |
@@ -31,6 +31,7 @@ import org.sonar.api.notifications.NotificationManager; | |||
import org.sonar.core.properties.PropertiesDao; | |||
import org.sonar.jpa.session.DatabaseSessionFactory; | |||
import javax.annotation.Nullable; | |||
import java.util.Arrays; | |||
import java.util.Date; | |||
import java.util.List; | |||
@@ -97,7 +98,7 @@ public class DefaultNotificationManager implements NotificationManager { | |||
/** | |||
* {@inheritDoc} | |||
*/ | |||
public Multimap<String, NotificationChannel> findSubscribedRecipientsForDispatcher(NotificationDispatcher dispatcher, Integer resourceId) { | |||
public Multimap<String, NotificationChannel> findSubscribedRecipientsForDispatcher(NotificationDispatcher dispatcher, @Nullable Integer resourceId) { | |||
String dispatcherKey = dispatcher.getKey(); | |||
SetMultimap<String, NotificationChannel> recipients = HashMultimap.create(); | |||
@@ -116,6 +117,18 @@ public class DefaultNotificationManager implements NotificationManager { | |||
return recipients; | |||
} | |||
@Override | |||
public Multimap<String, NotificationChannel> findNotificationSubscribers(NotificationDispatcher dispatcher, @Nullable String componentKey) { | |||
String dispatcherKey = dispatcher.getKey(); | |||
SetMultimap<String, NotificationChannel> recipients = HashMultimap.create(); | |||
for (NotificationChannel channel : notificationChannels) { | |||
addUsersToRecipientListForChannel(propertiesDao.findNotificationSubscribers(dispatcherKey, channel.getKey(), componentKey), recipients, channel); | |||
} | |||
return recipients; | |||
} | |||
@VisibleForTesting | |||
protected List<NotificationChannel> getChannels() { | |||
return Arrays.asList(notificationChannels); |
@@ -59,6 +59,16 @@ public class PropertiesDao implements BatchComponent, ServerComponent { | |||
} | |||
} | |||
public List<String> findNotificationSubscribers(String notificationDispatcherKey, String notificationChannelKey, @Nullable String componentKey) { | |||
SqlSession session = mybatis.openSession(); | |||
PropertiesMapper mapper = session.getMapper(PropertiesMapper.class); | |||
try { | |||
return mapper.findNotificationSubscribers("notification." + notificationDispatcherKey + "." + notificationChannelKey, componentKey); | |||
} finally { | |||
MyBatis.closeQuietly(session); | |||
} | |||
} | |||
public List<PropertyDto> selectGlobalProperties() { | |||
SqlSession session = mybatis.openSession(); | |||
PropertiesMapper mapper = session.getMapper(PropertiesMapper.class); |
@@ -29,6 +29,8 @@ public interface PropertiesMapper { | |||
List<String> findUsersForNotification(@Param("notifKey") String notificationKey, @Nullable @Param("rId") Long resourceId); | |||
List<String> findNotificationSubscribers(@Param("propKey") String propertyKey, @Nullable @Param("componentKey") String componentKey); | |||
List<PropertyDto> selectGlobalProperties(); | |||
List<PropertyDto> selectProjectProperties(String resourceKey); |
@@ -15,6 +15,18 @@ | |||
</if> | |||
</select> | |||
<select id="findNotificationSubscribers" parameterType="map" resultType="String"> | |||
SELECT U.login | |||
FROM properties P, users U | |||
WHERE P.user_id = U.id AND P.prop_key = #{propKey} AND P.text_value LIKE 'true' | |||
AND ( | |||
P.resource_id is null | |||
<if test="componentKey != null"> | |||
OR P.resource_id in (select id from projects where kee=#{componentKey}) | |||
</if> | |||
) | |||
</select> | |||
<select id="selectGlobalProperties" resultType="Property"> | |||
select p.id as id, p.prop_key as "key", p.text_value as value, p.resource_id as resourceId, p.user_id as userId | |||
from properties p |
@@ -0,0 +1,40 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2013 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.core.issue; | |||
import org.junit.Test; | |||
import org.sonar.api.issue.Issue; | |||
import java.util.Arrays; | |||
import static org.fest.assertions.Assertions.assertThat; | |||
public class DefaultIssueQueryResultTest { | |||
@Test | |||
public void test_first_issue() { | |||
DefaultIssueQueryResult result = new DefaultIssueQueryResult(); | |||
assertThat(result.first()).isNull(); | |||
Issue first = new DefaultIssue(); | |||
Issue second = new DefaultIssue(); | |||
result.setIssues(Arrays.asList(first, second)); | |||
assertThat(result.first()).isSameAs(first); | |||
} | |||
} |
@@ -0,0 +1,93 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2013 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.core.issue; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import org.junit.runner.RunWith; | |||
import org.mockito.Mock; | |||
import org.mockito.Mockito; | |||
import org.mockito.runners.MockitoJUnitRunner; | |||
import org.sonar.api.component.Component; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.notifications.Notification; | |||
import org.sonar.api.notifications.NotificationManager; | |||
import org.sonar.api.resources.Project; | |||
import org.sonar.api.utils.DateUtils; | |||
import org.sonar.core.i18n.RuleI18nManager; | |||
import java.util.Arrays; | |||
import java.util.Date; | |||
import static org.fest.assertions.Assertions.assertThat; | |||
@RunWith(MockitoJUnitRunner.class) | |||
public class IssueNotificationsTest { | |||
@Mock | |||
NotificationManager manager; | |||
@Mock | |||
RuleI18nManager ruleI18n; | |||
IssueNotifications issueNotifications; | |||
@Before | |||
public void setUp() throws Exception { | |||
issueNotifications = new IssueNotifications(manager, ruleI18n); | |||
} | |||
@Test | |||
public void sendNewIssues() throws Exception { | |||
Date date = DateUtils.parseDateTime("2013-05-18T00:00:03+0200"); | |||
Project project = new Project("struts").setAnalysisDate(date); | |||
Notification notification = issueNotifications.sendNewIssues(project, 42); | |||
assertThat(notification.getFieldValue("count")).isEqualTo("42"); | |||
assertThat(notification.getFieldValue("projectDate")).isEqualTo("2013-05-18T00:00:03+0200"); | |||
Mockito.verify(manager).scheduleForSending(notification); | |||
} | |||
@Test | |||
public void sendChanges() throws Exception { | |||
IssueChangeContext context = IssueChangeContext.createScan(new Date()); | |||
DefaultIssue issue = new DefaultIssue() | |||
.setMessage("the message") | |||
.setKey("ABCDE") | |||
.setAssignee("freddy") | |||
.setFieldDiff(context, "resolution", null, "FIXED") | |||
.setFieldDiff(context, "status", "OPEN", "RESOLVED") | |||
.setComponentKey("struts:Action") | |||
.setProjectKey("struts"); | |||
DefaultIssueQueryResult queryResult = new DefaultIssueQueryResult(); | |||
queryResult.setIssues(Arrays.<Issue>asList(issue)); | |||
queryResult.addProjects(Arrays.<Component>asList(new Project("struts"))); | |||
Notification notification = issueNotifications.sendChanges(issue, context, queryResult); | |||
assertThat(notification.getFieldValue("message")).isEqualTo("the message"); | |||
assertThat(notification.getFieldValue("key")).isEqualTo("ABCDE"); | |||
assertThat(notification.getFieldValue("old.resolution")).isNull(); | |||
assertThat(notification.getFieldValue("new.resolution")).isEqualTo("FIXED"); | |||
assertThat(notification.getFieldValue("old.status")).isEqualTo("OPEN"); | |||
assertThat(notification.getFieldValue("new.status")).isEqualTo("RESOLVED"); | |||
Mockito.verify(manager).scheduleForSending(notification); | |||
} | |||
} |
@@ -116,7 +116,7 @@ public class DefaultNotificationManagerTest extends AbstractDbUnitTestCase { | |||
when(propertiesDao.findUsersForNotification("NewViolations", "Twitter", null)).thenReturn(Lists.newArrayList("user3")); | |||
when(propertiesDao.findUsersForNotification("NewAlerts", "Twitter", null)).thenReturn(Lists.newArrayList("user4")); | |||
Multimap<String, NotificationChannel> multiMap = manager.findSubscribedRecipientsForDispatcher(dispatcher, null); | |||
Multimap<String, NotificationChannel> multiMap = manager.findSubscribedRecipientsForDispatcher(dispatcher, (Integer)null); | |||
assertThat(multiMap.entries()).hasSize(3); | |||
Map<String, Collection<NotificationChannel>> map = multiMap.asMap(); | |||
@@ -126,4 +126,17 @@ public class DefaultNotificationManagerTest extends AbstractDbUnitTestCase { | |||
assertThat(map.get("user4")).isNull(); | |||
} | |||
@Test | |||
public void findNotificationSubscribers() { | |||
when(propertiesDao.findNotificationSubscribers("NewViolations", "Email", "struts")).thenReturn(Lists.newArrayList("user1", "user2")); | |||
when(propertiesDao.findNotificationSubscribers("NewViolations", "Twitter", "struts")).thenReturn(Lists.newArrayList("user2")); | |||
Multimap<String, NotificationChannel> multiMap = manager.findNotificationSubscribers(dispatcher, "struts"); | |||
assertThat(multiMap.entries()).hasSize(3); | |||
Map<String, Collection<NotificationChannel>> map = multiMap.asMap(); | |||
assertThat(map.get("user1")).containsOnly(emailChannel); | |||
assertThat(map.get("user2")).containsOnly(emailChannel, twitterChannel); | |||
assertThat(map.get("other")).isNull(); | |||
} | |||
} |
@@ -49,10 +49,10 @@ public class PropertiesDaoTest extends AbstractDaoTestCase { | |||
setupData("shouldFindUsersForNotification"); | |||
List<String> users = dao.findUsersForNotification("NewViolations", "Email", null); | |||
assertThat(users).hasSize(0); | |||
assertThat(users).isEmpty(); | |||
users = dao.findUsersForNotification("NewViolations", "Email", 78L); | |||
assertThat(users).hasSize(0); | |||
assertThat(users).isEmpty(); | |||
users = dao.findUsersForNotification("NewViolations", "Email", 45L); | |||
assertThat(users).hasSize(1); | |||
@@ -63,13 +63,37 @@ public class PropertiesDaoTest extends AbstractDaoTestCase { | |||
assertThat(users).containsOnly("user3"); | |||
users = dao.findUsersForNotification("NewViolations", "Twitter", 78L); | |||
assertThat(users).hasSize(0); | |||
assertThat(users).isEmpty(); | |||
users = dao.findUsersForNotification("NewViolations", "Twitter", 56L); | |||
assertThat(users).hasSize(2); | |||
assertThat(users).containsOnly("user1", "user3"); | |||
} | |||
@Test | |||
public void findNotificationSubscribers() { | |||
setupData("findNotificationSubscribers"); | |||
// Nobody is subscribed | |||
List<String> users = dao.findNotificationSubscribers("NotSexyDispatcher", "Email", "org.apache:struts"); | |||
assertThat(users).isEmpty(); | |||
// Global subscribers | |||
users = dao.findNotificationSubscribers("DispatcherWithGlobalSubscribers", "Email", "org.apache:struts"); | |||
assertThat(users).containsOnly("simon"); | |||
users = dao.findNotificationSubscribers("DispatcherWithGlobalSubscribers", "Email", null); | |||
assertThat(users).containsOnly("simon"); | |||
// Project subscribers | |||
users = dao.findNotificationSubscribers("DispatcherWithProjectSubscribers", "Email", "org.apache:struts"); | |||
assertThat(users).containsOnly("eric"); | |||
// Global + Project subscribers | |||
users = dao.findNotificationSubscribers("DispatcherWithGlobalAndProjectSubscribers", "Email", "org.apache:struts"); | |||
assertThat(users).containsOnly("eric", "simon"); | |||
} | |||
@Test | |||
public void selectGlobalProperties() { | |||
setupData("selectGlobalProperties"); |
@@ -0,0 +1,50 @@ | |||
<dataset> | |||
<users | |||
id="1" | |||
login="eric" | |||
/> | |||
<users | |||
id="2" | |||
login="simon" | |||
/> | |||
<projects id="42" kee="org.apache:struts"/> | |||
<properties | |||
id="1" | |||
prop_key="notification.DispatcherWithGlobalSubscribers.Email" | |||
text_value="true" | |||
resource_id="[null]" | |||
user_id="2"/> | |||
<properties | |||
id="2" | |||
prop_key="notification.DispatcherWithProjectSubscribers.Email" | |||
text_value="true" | |||
resource_id="42" | |||
user_id="1"/> | |||
<properties | |||
id="3" | |||
prop_key="notification.DispatcherWithGlobalAndProjectSubscribers.Email" | |||
text_value="true" | |||
resource_id="56" | |||
user_id="1"/> | |||
<properties | |||
id="4" | |||
prop_key="notification.DispatcherWithGlobalAndProjectSubscribers.Email" | |||
text_value="true" | |||
resource_id="42" | |||
user_id="1"/> | |||
<properties | |||
id="5" | |||
prop_key="notification.DispatcherWithGlobalAndProjectSubscribers.Email" | |||
text_value="true" | |||
resource_id="[null]" | |||
user_id="2"/> | |||
</dataset> |
@@ -25,7 +25,6 @@ import org.sonar.api.user.User; | |||
import org.sonar.api.utils.Paging; | |||
import javax.annotation.CheckForNull; | |||
import java.util.Collection; | |||
import java.util.List; | |||
@@ -35,6 +34,9 @@ import java.util.List; | |||
public interface IssueQueryResult { | |||
List<Issue> issues(); | |||
@CheckForNull | |||
Issue first(); | |||
Rule rule(Issue issue); | |||
/** |
@@ -23,8 +23,11 @@ import com.google.common.collect.Maps; | |||
import org.apache.commons.lang.builder.ReflectionToStringBuilder; | |||
import org.apache.commons.lang.builder.ToStringStyle; | |||
import javax.annotation.CheckForNull; | |||
import javax.annotation.Nullable; | |||
import java.io.Serializable; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
/** | |||
* <p> | |||
@@ -44,10 +47,8 @@ public class Notification implements Serializable { | |||
private static final String DEFAULT_MESSAGE_KEY = "default_message"; | |||
private String type; | |||
private HashMap<String, String> fields = Maps.newHashMap(); // NOSONAR false-positive due to serialization : usage of HashMap instead of | |||
// Map | |||
private final String type; | |||
private final Map<String, String> fields = Maps.newHashMap(); | |||
/** | |||
* <p> | |||
@@ -105,7 +106,7 @@ public class Notification implements Serializable { | |||
* @param value the value of the field | |||
* @return the notification itself | |||
*/ | |||
public Notification setFieldValue(String field, String value) { | |||
public Notification setFieldValue(String field, @Nullable String value) { | |||
fields.put(field, value); | |||
return this; | |||
} | |||
@@ -116,6 +117,7 @@ public class Notification implements Serializable { | |||
* @param field the field | |||
* @return the value of the field | |||
*/ | |||
@CheckForNull | |||
public String getFieldValue(String field) { | |||
return fields.get(field); | |||
} |
@@ -29,9 +29,9 @@ import org.sonar.api.ServerExtension; | |||
* </p> | |||
* For example: | |||
* <ul> | |||
* <li>notify me by email when someone comments on review created by me</li> | |||
* <li>notify me by twitter when someone comments on review assigned to me</li> | |||
* <li>notify me by Jabber when someone mentions me in comment for review</li> | |||
* <li>notify me by email when someone comments an issue reported by me</li> | |||
* <li>notify me by twitter when someone comments an issue assigned to me</li> | |||
* <li>notify me by Jabber when someone mentions me in an issue comment</li> | |||
* <li>send me by SMS when there are system notifications (like password reset, account creation, ...)</li> | |||
* </ul> | |||
* | |||
@@ -74,7 +74,7 @@ public abstract class NotificationDispatcher implements ServerExtension { | |||
/** | |||
* Creates a new generic dispatcher, used for any kind of notification. | |||
* <br/> | |||
* <p/> | |||
* Should be avoided and replaced by the other constructor - as it is easier to understand that a | |||
* dispatcher listens for a specific type of notification. | |||
*/ | |||
@@ -83,9 +83,9 @@ public abstract class NotificationDispatcher implements ServerExtension { | |||
} | |||
/** | |||
* Returns the unique key of this dispatcher. | |||
* | |||
* @return the key | |||
* The unique key of this dispatcher. By default it's the class name without the package prefix. | |||
* <p/> | |||
* The related label in l10n bundles is 'notification.dispatcher.<key>', for example 'notification.dispatcher.NewFalsePositive'. | |||
*/ | |||
public String getKey() { | |||
return getClass().getSimpleName(); | |||
@@ -95,9 +95,6 @@ public abstract class NotificationDispatcher implements ServerExtension { | |||
* <p> | |||
* Performs the dispatch. | |||
* </p> | |||
* | |||
* @param notification the notification that will be sent | |||
* @param the context linked to this notification | |||
*/ | |||
public final void performDispatch(Notification notification, Context context) { | |||
if (StringUtils.equals(notification.getType(), notificationType) || StringUtils.equals("", notificationType)) { | |||
@@ -110,9 +107,6 @@ public abstract class NotificationDispatcher implements ServerExtension { | |||
* Implements the logic that defines which users will receive the notification. | |||
* </p> | |||
* The purpose of this method is to populate the context object with users, based on the type of notification and the content of the notification. | |||
* | |||
* @param notification the notification that will be sent | |||
* @param the context linked to this notification | |||
*/ | |||
public abstract void dispatch(Notification notification, Context context); | |||
@@ -27,12 +27,10 @@ import java.util.Map; | |||
/** | |||
* <p> | |||
* Notification dispatchers (see {@link NotificationDispatcher}) can define their own metadata class in order | |||
* to tell more about them. | |||
* <br/> | |||
* Instances of those classes must be passed to Pico container (generally in the | |||
* {@link SonarPlugin#getExtensions()} method implementation). | |||
* </p> | |||
* | |||
* to tell more about them. | |||
* <p/> | |||
* Instances of these classes must be declared in {@link org.sonar.api.SonarPlugin#getExtensions()}. | |||
* | |||
* @since 3.5 | |||
*/ | |||
public final class NotificationDispatcherMetadata implements ServerExtension { | |||
@@ -50,6 +48,9 @@ public final class NotificationDispatcherMetadata implements ServerExtension { | |||
/** | |||
* Creates a new metadata instance for the given dispatcher. | |||
* <p/> | |||
* By default the key is the class name without package. It can be changed by overriding | |||
* {@link org.sonar.api.notifications.NotificationDispatcher#getKey()}. | |||
*/ | |||
public static NotificationDispatcherMetadata create(String dispatcherKey) { | |||
return new NotificationDispatcherMetadata(dispatcherKey); |
@@ -60,4 +60,5 @@ public interface NotificationManager extends ServerComponent, BatchComponent { | |||
*/ | |||
Multimap<String, NotificationChannel> findSubscribedRecipientsForDispatcher(NotificationDispatcher dispatcher, @Nullable Integer resourceId); | |||
Multimap<String, NotificationChannel> findNotificationSubscribers(NotificationDispatcher dispatcher, @Nullable String componentKey); | |||
} |
@@ -26,6 +26,7 @@ import org.apache.commons.lang.StringUtils; | |||
import org.apache.commons.lang.builder.ToStringBuilder; | |||
import org.apache.maven.project.MavenProject; | |||
import org.sonar.api.CoreProperties; | |||
import org.sonar.api.component.Component; | |||
import java.util.ArrayList; | |||
import java.util.Date; | |||
@@ -36,7 +37,7 @@ import java.util.List; | |||
* | |||
* @since 1.10 | |||
*/ | |||
public class Project extends Resource { | |||
public class Project extends Resource implements Component { | |||
public static final String SCOPE = Scopes.PROJECT; | |||
@@ -459,4 +460,24 @@ public class Project extends Resource { | |||
.append("qualifier", getQualifier()) | |||
.toString(); | |||
} | |||
@Override | |||
public String key() { | |||
return getKey(); | |||
} | |||
@Override | |||
public String name() { | |||
return getName(); | |||
} | |||
@Override | |||
public String longName() { | |||
return getLongName(); | |||
} | |||
@Override | |||
public String qualifier() { | |||
return getQualifier(); | |||
} | |||
} |
@@ -101,6 +101,11 @@ public class DefaultIssueQueryResult implements IssueQueryResult { | |||
return issues; | |||
} | |||
@Override | |||
public Issue first() { | |||
return issues != null && !issues.isEmpty() ? issues.get(0) : null; | |||
} | |||
@Override | |||
public Rule rule(Issue issue) { | |||
return rulesByKey.get(issue.ruleKey()); |
@@ -40,7 +40,6 @@ import org.sonar.server.platform.UserSession; | |||
import org.sonar.server.util.RubyUtils; | |||
import javax.annotation.Nullable; | |||
import java.text.SimpleDateFormat; | |||
import java.util.Collection; | |||
import java.util.Date; | |||
@@ -141,7 +140,7 @@ public class InternalRubyIssueService implements ServerComponent { | |||
} | |||
if (result.ok()) { | |||
DefaultIssue issue = (DefaultIssue)new DefaultIssueBuilder() | |||
DefaultIssue issue = (DefaultIssue) new DefaultIssueBuilder() | |||
.componentKey(componentKey) | |||
.line(RubyUtils.toInteger(params.get("line"))) | |||
.message(params.get("message")) | |||
@@ -160,8 +159,7 @@ public class InternalRubyIssueService implements ServerComponent { | |||
return result; | |||
} | |||
public Collection<ActionPlan> findOpenActionPlans(String issueKey) { | |||
String componentKey = issueService.loadIssue(issueKey).componentKey(); | |||
public Collection<ActionPlan> findOpenActionPlansForComponent(String componentKey) { | |||
return actionPlanService.findOpenByComponentKey(componentKey); | |||
} | |||
@@ -22,11 +22,14 @@ package org.sonar.server.issue; | |||
import com.google.common.base.Strings; | |||
import org.sonar.api.ServerComponent; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.IssueQuery; | |||
import org.sonar.api.issue.IssueQueryResult; | |||
import org.sonar.api.rules.Rule; | |||
import org.sonar.api.rules.RuleFinder; | |||
import org.sonar.api.web.UserRole; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.IssueChangeContext; | |||
import org.sonar.core.issue.IssueNotifications; | |||
import org.sonar.core.issue.IssueUpdater; | |||
import org.sonar.core.issue.db.IssueStorage; | |||
import org.sonar.core.issue.workflow.IssueWorkflow; | |||
@@ -34,7 +37,7 @@ import org.sonar.core.issue.workflow.Transition; | |||
import org.sonar.server.platform.UserSession; | |||
import javax.annotation.Nullable; | |||
import java.util.Arrays; | |||
import java.util.Collections; | |||
import java.util.Date; | |||
import java.util.List; | |||
@@ -50,19 +53,22 @@ public class IssueService implements ServerComponent { | |||
private final IssueStorage issueStorage; | |||
private final ActionPlanService actionPlanService; | |||
private final RuleFinder ruleFinder; | |||
private final IssueNotifications issueNotifications; | |||
public IssueService(DefaultIssueFinder finder, | |||
IssueWorkflow workflow, | |||
IssueStorage issueStorage, | |||
IssueUpdater issueUpdater, | |||
ActionPlanService actionPlanService, | |||
RuleFinder ruleFinder) { | |||
RuleFinder ruleFinder, | |||
IssueNotifications issueNotifications) { | |||
this.finder = finder; | |||
this.workflow = workflow; | |||
this.issueStorage = issueStorage; | |||
this.issueUpdater = issueUpdater; | |||
this.actionPlanService = actionPlanService; | |||
this.ruleFinder = ruleFinder; | |||
this.issueNotifications = issueNotifications; | |||
} | |||
/** | |||
@@ -71,7 +77,7 @@ public class IssueService implements ServerComponent { | |||
* Never return null, but return an empty list if the issue does not exist. | |||
*/ | |||
public List<Transition> listTransitions(String issueKey) { | |||
return listTransitions(loadIssue(issueKey)); | |||
return listTransitions(loadIssue(issueKey).first()); | |||
} | |||
/** | |||
@@ -86,22 +92,26 @@ public class IssueService implements ServerComponent { | |||
public Issue doTransition(String issueKey, String transition, UserSession userSession) { | |||
verifyLoggedIn(userSession); | |||
DefaultIssue issue = loadIssue(issueKey); | |||
IssueQueryResult queryResult = loadIssue(issueKey); | |||
DefaultIssue issue = (DefaultIssue) queryResult.first(); | |||
IssueChangeContext context = IssueChangeContext.createUser(new Date(), userSession.login()); | |||
if (workflow.doTransition(issue, transition, context)) { | |||
issueStorage.save(issue); | |||
issueNotifications.sendChanges(issue, context, queryResult); | |||
} | |||
return issue; | |||
} | |||
public Issue assign(String issueKey, @Nullable String assigneeLogin, UserSession userSession) { | |||
public Issue assign(String issueKey, @Nullable String assignee, UserSession userSession) { | |||
verifyLoggedIn(userSession); | |||
DefaultIssue issue = loadIssue(issueKey); | |||
IssueQueryResult queryResult = loadIssue(issueKey); | |||
DefaultIssue issue = (DefaultIssue) queryResult.first(); | |||
// TODO check that assignee exists | |||
IssueChangeContext context = IssueChangeContext.createUser(new Date(), userSession.login()); | |||
if (issueUpdater.assign(issue, assigneeLogin, context)) { | |||
if (issueUpdater.assign(issue, assignee, context)) { | |||
issueStorage.save(issue); | |||
issueNotifications.sendChanges(issue, context, queryResult); | |||
} | |||
return issue; | |||
} | |||
@@ -112,21 +122,24 @@ public class IssueService implements ServerComponent { | |||
} | |||
verifyLoggedIn(userSession); | |||
DefaultIssue issue = loadIssue(issueKey); | |||
IssueQueryResult queryResult = loadIssue(issueKey); | |||
DefaultIssue issue = (DefaultIssue) queryResult.first(); | |||
IssueChangeContext context = IssueChangeContext.createUser(new Date(), userSession.login()); | |||
if (issueUpdater.plan(issue, actionPlanKey, context)) { | |||
issueStorage.save(issue); | |||
issueNotifications.sendChanges(issue, context, queryResult); | |||
} | |||
return issue; | |||
} | |||
public Issue setSeverity(String issueKey, String severity, UserSession userSession) { | |||
verifyLoggedIn(userSession); | |||
DefaultIssue issue = loadIssue(issueKey); | |||
IssueQueryResult queryResult = loadIssue(issueKey); | |||
DefaultIssue issue = (DefaultIssue) queryResult.first(); | |||
IssueChangeContext context = IssueChangeContext.createUser(new Date(), userSession.login()); | |||
if (issueUpdater.setManualSeverity(issue, severity, context)) { | |||
issueStorage.save(issue); | |||
issueNotifications.sendChanges(issue, context, queryResult); | |||
} | |||
return issue; | |||
} | |||
@@ -150,11 +163,12 @@ public class IssueService implements ServerComponent { | |||
} | |||
public DefaultIssue loadIssue(String issueKey) { | |||
return finder.findByKey(issueKey, UserRole.USER); | |||
public IssueQueryResult loadIssue(String issueKey) { | |||
IssueQuery query = IssueQuery.builder().issueKeys(Arrays.asList(issueKey)).requiredRole(UserRole.USER).build(); | |||
return finder.find(query); | |||
} | |||
public List<String> listStatus(){ | |||
public List<String> listStatus() { | |||
return workflow.statusKeys(); | |||
} | |||
@@ -40,6 +40,7 @@ import org.sonar.core.config.Logback; | |||
import org.sonar.core.i18n.GwtI18n; | |||
import org.sonar.core.i18n.I18nManager; | |||
import org.sonar.core.i18n.RuleI18nManager; | |||
import org.sonar.core.issue.IssueNotifications; | |||
import org.sonar.core.issue.IssueUpdater; | |||
import org.sonar.core.issue.workflow.FunctionExecutor; | |||
import org.sonar.core.issue.workflow.IssueWorkflow; | |||
@@ -267,6 +268,7 @@ public final class Platform { | |||
servicesContainer.addSingleton(PublicRubyIssueService.class); | |||
servicesContainer.addSingleton(InternalRubyIssueService.class); | |||
servicesContainer.addSingleton(ActionPlanService.class); | |||
servicesContainer.addSingleton(IssueNotifications.class); | |||
// rules | |||
servicesContainer.addSingleton(RubyRuleService.class); |
@@ -50,6 +50,8 @@ class IssueController < ApplicationController | |||
@issue_result = Api.issues.find(params[:issue]) | |||
@issue = @issue_result.issues().get(0) | |||
bad_request('Unknown issue') unless @issue | |||
action_type = params[:id] | |||
render :partial => "issue/#{action_type}_form" | |||
end |
@@ -1,6 +1,6 @@ | |||
<% | |||
plans_select_box_id = "plans-#{params[:issue]}" | |||
plans = Internal.issues.findOpenActionPlans(params[:issue]) | |||
plans = Internal.issues.findOpenActionPlansForComponent(@issue.componentKey()) | |||
if plans.empty? |
@@ -20,6 +20,7 @@ | |||
# | |||
# Sonar 3.6 | |||
# See SONAR-4283 | |||
# | |||
class ReplaceReviewNotifications < ActiveRecord::Migration | |||
@@ -31,5 +32,16 @@ class ReplaceReviewNotifications < ActiveRecord::Migration | |||
prop.prop_key = prop.prop_key.gsub(/NewViolationsOnFirstDifferentialPeriod/, 'NewIssues') | |||
prop.save | |||
end | |||
Property.find(:all, :conditions => ['prop_key like ?', 'notification.ChangesInReviewAssignedToMeOrCreatedByMe.%']).each do |prop| | |||
prop.prop_key = prop.prop_key.gsub(/ChangesInReviewAssignedToMeOrCreatedByMe/, 'ChangesOnMyIssue') | |||
prop.save | |||
end | |||
Property.find(:all, :conditions => ['prop_key like ?', 'notification.NewFalsePositiveReview.%']).each do |prop| | |||
prop.prop_key = prop.prop_key.gsub(/NewFalsePositiveReview/, 'NewFalsePositiveIssue') | |||
prop.save | |||
end | |||
end | |||
end |