]> source.dussan.org Git - sonarqube.git/commitdiff
notify users when they are automatically assigned to issues - SONAR-6106 SONAR-6045 142/head
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Thu, 5 Mar 2015 18:08:35 +0000 (19:08 +0100)
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Tue, 10 Mar 2015 14:28:41 +0000 (15:28 +0100)
24 files changed:
server/sonar-server/src/main/java/org/sonar/server/component/db/ComponentDao.java
server/sonar-server/src/main/java/org/sonar/server/computation/step/SendIssueNotificationsStep.java
server/sonar-server/src/main/java/org/sonar/server/issue/notification/AbstractNewIssuesEmailTemplate.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/notification/MyNewIssuesEmailTemplate.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/notification/MyNewIssuesNotification.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/notification/MyNewIssuesNotificationDispatcher.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesEmailTemplate.java
server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesNotification.java
server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesNotificationFactory.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesStatistics.java
server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java
server/sonar-server/src/test/java/org/sonar/server/computation/step/SendIssueNotificationsStepTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/notification/MyNewIssuesEmailTemplateTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/issue/notification/MyNewIssuesNotificationDispatcherTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/issue/notification/MyNewIssuesNotificationTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesEmailTemplateTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationDispatcherTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesStatisticsTest.java
server/sonar-server/src/test/resources/org/sonar/server/issue/notification/MyNewIssuesEmailTemplateTest/email_with_all_details.txt [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/issue/notification/MyNewIssuesEmailTemplateTest/email_with_no_assignee_tags_components.txt [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/issue/notification/NewIssuesEmailTemplateTest/email_with_all_details.txt [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/issue/notification/NewIssuesEmailTemplateTest/email_with_partial_details.txt [new file with mode: 0644]
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 27cd270de816f51e356f25b4d16918bb5c97ad4d..2177792947ac752d6effdc0c9a95ec0b4ba3c90b 100644 (file)
@@ -36,7 +36,6 @@ import org.sonar.server.db.BaseDao;
 import org.sonar.server.exceptions.NotFoundException;
 
 import javax.annotation.CheckForNull;
-
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
index f67a92860d0ea0e4359d174fb92be76f94862e21..9bda954d6ec2d84a9726579782de9612f727ed40 100644 (file)
@@ -22,18 +22,16 @@ package org.sonar.server.computation.step;
 import com.google.common.collect.ImmutableSet;
 import org.sonar.api.issue.internal.DefaultIssue;
 import org.sonar.api.resources.Qualifiers;
-import org.sonar.api.utils.Durations;
 import org.sonar.core.component.ComponentDto;
 import org.sonar.server.computation.ComputationContext;
 import org.sonar.server.computation.issue.IssueCache;
 import org.sonar.server.computation.issue.RuleCache;
-import org.sonar.server.issue.notification.IssueChangeNotification;
-import org.sonar.server.issue.notification.NewIssuesNotification;
-import org.sonar.server.issue.notification.NewIssuesStatistics;
+import org.sonar.server.issue.notification.*;
 import org.sonar.server.notifications.NotificationService;
 import org.sonar.server.util.CloseableIterator;
 
 import java.util.Date;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -45,18 +43,18 @@ public class SendIssueNotificationsStep implements ComputationStep {
   /**
    * Types of the notifications sent by this step
    */
-  static final Set<String> NOTIF_TYPES = ImmutableSet.of(IssueChangeNotification.TYPE, NewIssuesNotification.TYPE);
+  static final Set<String> NOTIF_TYPES = ImmutableSet.of(IssueChangeNotification.TYPE, NewIssuesNotification.TYPE, MyNewIssuesNotification.TYPE);
 
   private final IssueCache issueCache;
   private final RuleCache rules;
   private final NotificationService service;
-  private final Durations durations;
+  private NewIssuesNotificationFactory newIssuesNotificationFactory;
 
-  public SendIssueNotificationsStep(IssueCache issueCache, RuleCache rules, NotificationService service, Durations durations) {
+  public SendIssueNotificationsStep(IssueCache issueCache, RuleCache rules, NotificationService service, NewIssuesNotificationFactory newIssuesNotificationFactory) {
     this.issueCache = issueCache;
     this.rules = rules;
     this.service = service;
-    this.durations = durations;
+    this.newIssuesNotificationFactory = newIssuesNotificationFactory;
   }
 
   @Override
@@ -94,15 +92,33 @@ public class SendIssueNotificationsStep implements ComputationStep {
     sendNewIssuesStatistics(context, newIssuesStats);
   }
 
-  private void sendNewIssuesStatistics(ComputationContext context, NewIssuesStatistics stats) {
-    if (stats.hasIssues()) {
+  private void sendNewIssuesStatistics(ComputationContext context, NewIssuesStatistics statistics) {
+    if (statistics.hasIssues()) {
+      NewIssuesStatistics.Stats globalStatistics = statistics.globalStatistics();
       ComponentDto project = context.getProject();
-      NewIssuesNotification notification = new NewIssuesNotification();
-      notification.setProject(project);
-      notification.setAnalysisDate(new Date(context.getReportMetadata().getAnalysisDate()));
-      notification.setStatistics(project, stats);
-      notification.setDebt(durations.encode(stats.debt()));
+      NewIssuesNotification notification = newIssuesNotificationFactory
+        .newNewIssuesNotication()
+        .setProject(project)
+        .setAnalysisDate(new Date(context.getReportMetadata().getAnalysisDate()))
+        .setStatistics(project, globalStatistics)
+        .setDebt(globalStatistics.debt());
       service.deliver(notification);
+
+      // send email to each user having issues
+      for (Map.Entry<String, NewIssuesStatistics.Stats> assigneeAndStatisticsTuple : statistics.assigneesStatistics().entrySet()) {
+        String assignee = assigneeAndStatisticsTuple.getKey();
+        NewIssuesStatistics.Stats assigneeStatistics = assigneeAndStatisticsTuple.getValue();
+        MyNewIssuesNotification myNewIssuesNotification = newIssuesNotificationFactory
+          .newMyNewIssuesNotification()
+          .setAssignee(assignee);
+        myNewIssuesNotification
+          .setProject(project)
+          .setAnalysisDate(new Date(context.getReportMetadata().getAnalysisDate()))
+          .setStatistics(project, assigneeStatistics)
+          .setDebt(assigneeStatistics.debt());
+
+        service.deliver(myNewIssuesNotification);
+      }
     }
   }
 
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/notification/AbstractNewIssuesEmailTemplate.java b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/AbstractNewIssuesEmailTemplate.java
new file mode 100644 (file)
index 0000000..6617ea1
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.server.issue.notification;
+
+import com.google.common.collect.Lists;
+import org.sonar.api.config.EmailSettings;
+import org.sonar.api.i18n.I18n;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.plugins.emailnotifications.api.EmailMessage;
+import org.sonar.plugins.emailnotifications.api.EmailTemplate;
+import org.sonar.server.issue.notification.NewIssuesStatistics.METRIC;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Locale;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Base class to create emails for new issues
+ */
+public abstract class AbstractNewIssuesEmailTemplate extends EmailTemplate {
+
+  protected static final char NEW_LINE = '\n';
+  protected static final String TAB = "    ";
+  protected static final String DOT = ".";
+  protected static final String COUNT = DOT + "count";
+  protected static final String LABEL = DOT + "label";
+
+  static final String FIELD_PROJECT_NAME = "projectName";
+  static final String FIELD_PROJECT_KEY = "projectKey";
+  static final String FIELD_PROJECT_DATE = "projectDate";
+  static final String FIELD_PROJECT_UUID = "projectUuid";
+  static final String FIELD_ASSIGNEE = "assignee";
+
+  protected final EmailSettings settings;
+  protected final I18n i18n;
+
+  public AbstractNewIssuesEmailTemplate(EmailSettings settings, I18n i18n) {
+    this.settings = settings;
+    this.i18n = i18n;
+  }
+
+  public static String encode(String toEncode) {
+    try {
+      return URLEncoder.encode(toEncode, "UTF-8");
+    } catch (UnsupportedEncodingException e) {
+      throw new IllegalStateException("Encoding not supported", e);
+    }
+  }
+
+  @Override
+  public EmailMessage format(Notification notification) {
+    if (shouldNotFormat(notification)) {
+      return null;
+    }
+    String projectName = checkNotNull(notification.getFieldValue(FIELD_PROJECT_NAME));
+
+    StringBuilder message = new StringBuilder();
+    message.append("Project: ").append(projectName).append(NEW_LINE).append(NEW_LINE);
+    appendSeverity(message, notification);
+    appendAssignees(message, notification);
+    appendTags(message, notification);
+    appendComponents(message, notification);
+    appendFooter(message, notification);
+
+    return new EmailMessage()
+      .setMessageId(notification.getType() + "/" + notification.getFieldValue(FIELD_PROJECT_KEY))
+      .setSubject(subject(notification, projectName))
+      .setMessage(message.toString());
+  }
+
+  protected abstract boolean shouldNotFormat(Notification notification);
+
+  protected String subject(Notification notification, String projectName) {
+    return String.format("%s: %s new issues (new debt: %s)",
+      projectName,
+      notification.getFieldValue(METRIC.SEVERITY + COUNT),
+      notification.getFieldValue(METRIC.DEBT + COUNT));
+  }
+
+  private boolean doNotHaveValue(Notification notification, METRIC metric) {
+    return notification.getFieldValue(metric + DOT + "1" + LABEL) == null;
+  }
+
+  private void genericAppendOfMetric(METRIC metric, String label, StringBuilder message, Notification notification) {
+    if (doNotHaveValue(notification, metric)) {
+      return;
+    }
+
+    message
+      .append(TAB)
+      .append(label)
+      .append(NEW_LINE);
+    int i = 1;
+    while (notification.getFieldValue(metric + DOT + i + LABEL) != null && i <= 5) {
+      String name = notification.getFieldValue(metric + DOT + i + LABEL);
+      message
+        .append(TAB).append(TAB)
+        .append(name)
+        .append(": ")
+        .append(notification.getFieldValue(metric + DOT + i + COUNT))
+        .append(NEW_LINE);
+      i += 1;
+    }
+
+    message.append(NEW_LINE);
+  }
+
+  protected void appendAssignees(StringBuilder message, Notification notification) {
+    genericAppendOfMetric(METRIC.ASSIGNEE, "Assignees", message, notification);
+  }
+
+  protected void appendComponents(StringBuilder message, Notification notification) {
+    genericAppendOfMetric(METRIC.COMPONENT, "Most impacted files", message, notification);
+  }
+
+  protected void appendTags(StringBuilder message, Notification notification) {
+    genericAppendOfMetric(METRIC.TAGS, "Tags", message, notification);
+  }
+
+  protected void appendSeverity(StringBuilder message, Notification notification) {
+    message
+      .append(String.format("%s new issues (new debt: %s)",
+        notification.getFieldValue(METRIC.SEVERITY + COUNT),
+        notification.getFieldValue(METRIC.DEBT + COUNT)))
+      .append(NEW_LINE).append(NEW_LINE)
+      .append(TAB)
+      .append("Severity")
+      .append(NEW_LINE)
+      .append(TAB)
+      .append(TAB);
+
+    for (Iterator<String> severityIterator = Lists.reverse(Severity.ALL).iterator(); severityIterator.hasNext();) {
+      String severity = severityIterator.next();
+      String severityLabel = i18n.message(getLocale(), "severity." + severity, severity);
+      message.append(severityLabel).append(": ").append(notification.getFieldValue(METRIC.SEVERITY + DOT + severity + COUNT));
+      if (severityIterator.hasNext()) {
+        message.append(TAB);
+      }
+    }
+
+    message
+      .append(NEW_LINE)
+      .append(NEW_LINE);
+  }
+
+  protected void appendFooter(StringBuilder message, Notification notification) {
+    String projectUuid = notification.getFieldValue(FIELD_PROJECT_UUID);
+    String dateString = notification.getFieldValue(FIELD_PROJECT_DATE);
+    if (projectUuid != null && dateString != null) {
+      Date date = DateUtils.parseDateTime(dateString);
+      String url = String.format("%s/issues/search#projectUuids=%s|createdAt=%s",
+        settings.getServerBaseURL(), encode(projectUuid), encode(DateUtils.formatDateTime(date)));
+      message
+        .append("See it in SonarQube: ")
+        .append(url)
+        .append(NEW_LINE);
+    }
+  }
+
+  private Locale getLocale() {
+    return Locale.ENGLISH;
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/notification/MyNewIssuesEmailTemplate.java b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/MyNewIssuesEmailTemplate.java
new file mode 100644 (file)
index 0000000..8cd969c
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.server.issue.notification;
+
+import org.sonar.api.config.EmailSettings;
+import org.sonar.api.i18n.I18n;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.server.issue.notification.NewIssuesStatistics.METRIC;
+
+import java.util.Date;
+
+/**
+ * Creates email message for notification "my-new-issues".
+ */
+public class MyNewIssuesEmailTemplate extends AbstractNewIssuesEmailTemplate {
+
+  public MyNewIssuesEmailTemplate(EmailSettings settings, I18n i18n) {
+    super(settings, i18n);
+  }
+
+  @Override
+  protected boolean shouldNotFormat(Notification notification) {
+    return !MyNewIssuesNotification.TYPE.equals(notification.getType());
+  }
+
+  @Override
+  protected void appendAssignees(StringBuilder message, Notification notification) {
+    // do nothing as we don't want to print assignees, it's a personalized email for one person
+  }
+
+  @Override
+  protected String subject(Notification notification, String projectName) {
+    return String.format("You have %s new issues on project %s",
+      notification.getFieldValue(METRIC.SEVERITY + COUNT),
+      projectName);
+  }
+
+  @Override
+  protected void appendFooter(StringBuilder message, Notification notification) {
+    String projectUuid = notification.getFieldValue(FIELD_PROJECT_UUID);
+    String dateString = notification.getFieldValue(FIELD_PROJECT_DATE);
+    String assignee = notification.getFieldValue(FIELD_ASSIGNEE);
+    if (projectUuid != null && dateString != null && assignee != null) {
+      Date date = DateUtils.parseDateTime(dateString);
+      String url = String.format("%s/issues/search#projectUuids=%s|assignees=%s|createdAt=%s",
+        settings.getServerBaseURL(),
+        encode(projectUuid),
+        encode(assignee),
+        encode(DateUtils.formatDateTime(date)));
+      message.append("See it in SonarQube: ").append(url).append(NEW_LINE);
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/notification/MyNewIssuesNotification.java b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/MyNewIssuesNotification.java
new file mode 100644 (file)
index 0000000..b0c0e92
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.server.issue.notification;
+
+import org.sonar.api.utils.Durations;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.user.index.UserIndex;
+
+import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_ASSIGNEE;
+
+public class MyNewIssuesNotification extends NewIssuesNotification {
+
+  public static final String TYPE = "my-new-issues";
+
+  MyNewIssuesNotification(UserIndex userIndex, DbClient dbClient, Durations durations) {
+    super(TYPE, userIndex, dbClient, durations);
+  }
+
+  public MyNewIssuesNotification setAssignee(String assignee) {
+    setFieldValue(FIELD_ASSIGNEE, assignee);
+
+    return this;
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/notification/MyNewIssuesNotificationDispatcher.java b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/MyNewIssuesNotificationDispatcher.java
new file mode 100644 (file)
index 0000000..de3c1cc
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.server.issue.notification;
+
+import com.google.common.collect.Multimap;
+import org.sonar.api.notifications.*;
+
+import java.util.Collection;
+
+/**
+ * This dispatcher means: "notify me when new issues are introduced during project analysis"
+ */
+public class MyNewIssuesNotificationDispatcher extends NotificationDispatcher {
+
+  public static final String KEY = "MyNewIssues";
+  private final NotificationManager manager;
+
+  public MyNewIssuesNotificationDispatcher(NotificationManager manager) {
+    super(MyNewIssuesNotification.TYPE);
+    this.manager = manager;
+  }
+
+  public static NotificationDispatcherMetadata newMetadata() {
+    return NotificationDispatcherMetadata.create(KEY)
+      .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
+      .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
+  }
+
+  @Override
+  public String getKey() {
+    return KEY;
+  }
+
+  @Override
+  public void dispatch(Notification notification, Context context) {
+    String projectKey = notification.getFieldValue("projectKey");
+    String assignee = notification.getFieldValue("assignee");
+    Multimap<String, NotificationChannel> subscribedRecipients = manager.findNotificationSubscribers(this, projectKey);
+
+    Collection<NotificationChannel> channels = subscribedRecipients.get(assignee);
+    for (NotificationChannel channel : channels) {
+      context.addUser(assignee, channel);
+    }
+  }
+
+}
index df98f6eee5e13155824daff0cb5a1d1de4870821..e1966d423d422bb7c28a5f3f0cf57a5f0fc29b6d 100644 (file)
  */
 package org.sonar.server.issue.notification;
 
-import com.google.common.base.Objects;
-import com.google.common.collect.Lists;
 import org.sonar.api.config.EmailSettings;
 import org.sonar.api.i18n.I18n;
 import org.sonar.api.notifications.Notification;
-import org.sonar.api.rule.Severity;
-import org.sonar.api.utils.DateUtils;
-import org.sonar.plugins.emailnotifications.api.EmailMessage;
-import org.sonar.plugins.emailnotifications.api.EmailTemplate;
-import org.sonar.server.issue.notification.NewIssuesStatistics.METRIC;
-import org.sonar.server.user.index.UserDoc;
-import org.sonar.server.user.index.UserIndex;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.util.Date;
-import java.util.Iterator;
-import java.util.Locale;
 
 /**
  * Creates email message for notification "new-issues".
  */
-public class NewIssuesEmailTemplate extends EmailTemplate {
-
-  public static final String FIELD_PROJECT_NAME = "projectName";
-  public static final String FIELD_PROJECT_KEY = "projectKey";
-  public static final String FIELD_PROJECT_DATE = "projectDate";
-  public static final String FIELD_PROJECT_UUID = "projectUuid";
-
-  private static final char NEW_LINE = '\n';
-  private static final String TAB = "   ";
-  private static final String DOT = ".";
-  private static final String COUNT = DOT + "count";
-  private static final String LABEL = DOT + "label";
-
-  private final EmailSettings settings;
-  private final I18n i18n;
-  private final UserIndex userIndex;
+public class NewIssuesEmailTemplate extends AbstractNewIssuesEmailTemplate {
 
-  public NewIssuesEmailTemplate(EmailSettings settings, I18n i18n, UserIndex userIndex) {
-    this.settings = settings;
-    this.i18n = i18n;
-    this.userIndex = userIndex;
-  }
-
-  public static String encode(String toEncode) {
-    try {
-      return URLEncoder.encode(toEncode, "UTF-8");
-    } catch (UnsupportedEncodingException e) {
-      throw new IllegalStateException("Encoding not supported", e);
-    }
+  public NewIssuesEmailTemplate(EmailSettings settings, I18n i18n) {
+    super(settings, i18n);
   }
 
   @Override
-  public EmailMessage format(Notification notification) {
-    if (!NewIssuesNotification.TYPE.equals(notification.getType())) {
-      return null;
-    }
-    String projectName = notification.getFieldValue(FIELD_PROJECT_NAME);
-
-    StringBuilder message = new StringBuilder();
-    message.append("Project: ").append(projectName).append(NEW_LINE).append(NEW_LINE);
-    appendSeverity(message, notification);
-    appendAssignees(message, notification);
-    appendTags(message, notification);
-    appendComponents(message, notification);
-    appendFooter(message, notification);
-
-    return new EmailMessage()
-      .setMessageId("new-issues/" + notification.getFieldValue(FIELD_PROJECT_KEY))
-      .setSubject(projectName + ": " + notification.getFieldValue(METRIC.SEVERITY + COUNT) + " new issues")
-      .setMessage(message.toString());
-  }
-
-  private void appendComponents(StringBuilder message, Notification notification) {
-    if (notification.getFieldValue(METRIC.COMPONENT + ".1.label") == null) {
-      return;
-    }
-
-    message.append("   Components:\n");
-    int i = 1;
-    while (notification.getFieldValue(METRIC.COMPONENT + DOT + i + LABEL) != null && i <= 5) {
-      String component = notification.getFieldValue(METRIC.COMPONENT + DOT + i + LABEL);
-      message
-        .append(TAB).append(TAB)
-        .append(component)
-        .append(" : ")
-        .append(notification.getFieldValue(METRIC.COMPONENT + DOT + i + COUNT))
-        .append("\n");
-      i += 1;
-    }
+  protected boolean shouldNotFormat(Notification notification) {
+    return !NewIssuesNotification.TYPE.equals(notification.getType());
   }
-
-  private void appendAssignees(StringBuilder message, Notification notification) {
-    if (notification.getFieldValue(METRIC.LOGIN + DOT + "1" + LABEL) == null) {
-      return;
-    }
-
-    message.append(TAB + "Assignee - ");
-    int i = 1;
-    while (notification.getFieldValue(METRIC.LOGIN + DOT + i + LABEL) != null && i <= 5) {
-      String login = notification.getFieldValue(METRIC.LOGIN + DOT + i + LABEL);
-      UserDoc user = userIndex.getNullableByLogin(login);
-      String name = user == null ? null : user.name();
-      message.append(Objects.firstNonNull(name, login))
-        .append(": ")
-        .append(notification.getFieldValue(METRIC.LOGIN + DOT + i + COUNT));
-      if (i < 5) {
-        message.append(TAB);
-      }
-      i += 1;
-    }
-
-    message.append("\n");
-  }
-
-  private void appendTags(StringBuilder message, Notification notification) {
-    if (notification.getFieldValue(METRIC.TAGS + DOT + "1" + LABEL) == null) {
-      return;
-    }
-
-    message.append(TAB + "Tags - ");
-    int i = 1;
-    while (notification.getFieldValue(METRIC.TAGS + DOT + i + LABEL) != null && i <= 5) {
-      String tag = notification.getFieldValue(METRIC.TAGS + DOT + i + LABEL);
-      message.append(tag)
-        .append(": ")
-        .append(notification.getFieldValue(METRIC.TAGS + DOT + i + COUNT));
-      if (i < 5) {
-        message.append(TAB);
-      }
-      i += 1;
-    }
-    message.append(NEW_LINE);
-  }
-
-  private void appendSeverity(StringBuilder message, Notification notification) {
-    message.append(notification.getFieldValue(METRIC.SEVERITY + COUNT)).append(" new issues - Total debt: ")
-      .append(notification.getFieldValue(METRIC.DEBT + COUNT))
-      .append(NEW_LINE).append(NEW_LINE)
-      .append(TAB + "Severity - ");
-    for (Iterator<String> severityIterator = Lists.reverse(Severity.ALL).iterator(); severityIterator.hasNext();) {
-      String severity = severityIterator.next();
-      String severityLabel = i18n.message(getLocale(), "severity." + severity, severity);
-      message.append(severityLabel).append(": ").append(notification.getFieldValue(METRIC.SEVERITY + DOT + severity + COUNT));
-      if (severityIterator.hasNext()) {
-        message.append(TAB);
-      }
-    }
-    message.append(NEW_LINE);
-  }
-
-  private void appendFooter(StringBuilder message, Notification notification) {
-    String projectUuid = notification.getFieldValue(FIELD_PROJECT_UUID);
-    String dateString = notification.getFieldValue(FIELD_PROJECT_DATE);
-    if (projectUuid != null && dateString != null) {
-      Date date = DateUtils.parseDateTime(dateString);
-      String url = String.format("%s/issues/search#projectUuids=%s|createdAt=%s",
-        settings.getServerBaseURL(), encode(projectUuid), encode(DateUtils.formatDateTime(date)));
-      message.append("\n").append("See it in SonarQube: ").append(url).append(NEW_LINE);
-    }
-  }
-
-  private Locale getLocale() {
-    return Locale.ENGLISH;
-  }
-
 }
index ae387b1598d06adc28655e95d2f1a30501c28590..29784d29f0173a119d1ead09aad5ed9742bd13ff 100644 (file)
@@ -24,8 +24,14 @@ import org.sonar.api.component.Component;
 import org.sonar.api.notifications.Notification;
 import org.sonar.api.rule.Severity;
 import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.Duration;
+import org.sonar.api.utils.Durations;
 import org.sonar.core.component.ComponentDto;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.server.db.DbClient;
 import org.sonar.server.issue.notification.NewIssuesStatistics.METRIC;
+import org.sonar.server.user.index.UserDoc;
+import org.sonar.server.user.index.UserIndex;
 
 import java.util.Date;
 import java.util.List;
@@ -35,11 +41,25 @@ import static org.sonar.server.issue.notification.NewIssuesStatistics.METRIC.SEV
 
 public class NewIssuesNotification extends Notification {
 
+  private static final long serialVersionUID = -6305871981920103093L;
+
   public static final String TYPE = "new-issues";
   private static final String COUNT = ".count";
+  private static final String LABEL = ".label";
+
+  private final transient UserIndex userIndex;
+  private final transient DbClient dbClient;
+  private final transient Durations durations;
+
+  NewIssuesNotification(UserIndex userIndex, DbClient dbClient, Durations durations) {
+    this(TYPE, userIndex, dbClient, durations);
+  }
 
-  public NewIssuesNotification() {
-    super(TYPE);
+  protected NewIssuesNotification(String type, UserIndex userIndex, DbClient dbClient, Durations durations) {
+    super(type);
+    this.userIndex = userIndex;
+    this.dbClient = dbClient;
+    this.durations = durations;
   }
 
   public NewIssuesNotification setAnalysisDate(Date d) {
@@ -54,31 +74,57 @@ public class NewIssuesNotification extends Notification {
     return this;
   }
 
-  public NewIssuesNotification setStatistics(Component project, NewIssuesStatistics stats) {
+  public NewIssuesNotification setStatistics(Component project, NewIssuesStatistics.Stats stats) {
     setDefaultMessage(stats.countForMetric(SEVERITY) + " new issues on " + project.longName() + ".\n");
 
     setSeverityStatistics(stats);
-    setTop5CountsForMetric(stats, METRIC.LOGIN);
-    setTop5CountsForMetric(stats, METRIC.TAGS);
-    setTop5CountsForMetric(stats, METRIC.COMPONENT);
+    setAssigneesStatistics(stats);
+    setTagsStatistics(stats);
+    setComponentsStatistics(stats);
 
     return this;
   }
 
-  public NewIssuesNotification setDebt(String debt) {
-    setFieldValue(METRIC.DEBT + COUNT, debt);
-    return this;
+  protected void setComponentsStatistics(NewIssuesStatistics.Stats stats) {
+    METRIC metric = METRIC.COMPONENT;
+    List<Multiset.Entry<String>> componentStats = stats.statsForMetric(metric);
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      for (int i = 0; i < 5 && i < componentStats.size(); i++) {
+        String uuid = componentStats.get(i).getElement();
+        String componentName = dbClient.componentDao().getByUuid(dbSession, uuid).name();
+        setFieldValue(metric + "." + (i + 1) + LABEL, componentName);
+        setFieldValue(metric + "." + (i + 1) + COUNT, String.valueOf(componentStats.get(i).getCount()));
+      }
+    }
   }
 
-  private void setTop5CountsForMetric(NewIssuesStatistics stats, METRIC metric) {
-    List<Multiset.Entry<String>> loginStats = stats.statsForMetric(metric);
-    for (int i = 0; i < 5 && i < loginStats.size(); i++) {
-      setFieldValue(metric + "." + (i + 1) + COUNT, String.valueOf(loginStats.get(i).getCount()));
-      setFieldValue(metric + "." + (i + 1) + ".label", loginStats.get(i).getElement());
+  protected void setTagsStatistics(NewIssuesStatistics.Stats stats) {
+    METRIC metric = METRIC.TAGS;
+    List<Multiset.Entry<String>> metricStats = stats.statsForMetric(metric);
+    for (int i = 0; i < 5 && i < metricStats.size(); i++) {
+      setFieldValue(metric + "." + (i + 1) + COUNT, String.valueOf(metricStats.get(i).getCount()));
+      setFieldValue(metric + "." + (i + 1) + ".label", metricStats.get(i).getElement());
     }
   }
 
-  private void setSeverityStatistics(NewIssuesStatistics stats) {
+  protected void setAssigneesStatistics(NewIssuesStatistics.Stats stats) {
+    METRIC metric = METRIC.ASSIGNEE;
+    List<Multiset.Entry<String>> metricStats = stats.statsForMetric(metric);
+    for (int i = 0; i < 5 && i < metricStats.size(); i++) {
+      String login = metricStats.get(i).getElement();
+      UserDoc user = userIndex.getNullableByLogin(login);
+      String name = user == null ? login : user.name();
+      setFieldValue(metric + "." + (i + 1) + LABEL, name);
+      setFieldValue(metric + "." + (i + 1) + COUNT, String.valueOf(metricStats.get(i).getCount()));
+    }
+  }
+
+  public NewIssuesNotification setDebt(Duration debt) {
+    setFieldValue(METRIC.DEBT + COUNT, durations.encode(debt));
+    return this;
+  }
+
+  protected void setSeverityStatistics(NewIssuesStatistics.Stats stats) {
     setFieldValue(SEVERITY + COUNT, String.valueOf(stats.countForMetric(SEVERITY)));
     for (String severity : Severity.ALL) {
       setFieldValue(SEVERITY + "." + severity + COUNT, String.valueOf(stats.countForMetric(SEVERITY, severity)));
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesNotificationFactory.java b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesNotificationFactory.java
new file mode 100644 (file)
index 0000000..6686587
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.server.issue.notification;
+
+import org.sonar.api.ServerComponent;
+import org.sonar.api.utils.Durations;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.user.index.UserIndex;
+
+public class NewIssuesNotificationFactory implements ServerComponent {
+  private final UserIndex userIndex;
+  private final DbClient dbClient;
+  private final Durations durations;
+
+  public NewIssuesNotificationFactory(UserIndex userIndex, DbClient dbClient, Durations durations) {
+    this.userIndex = userIndex;
+    this.dbClient = dbClient;
+    this.durations = durations;
+  }
+
+  public MyNewIssuesNotification newMyNewIssuesNotification() {
+    return new MyNewIssuesNotification(userIndex, dbClient, durations);
+  }
+
+  public NewIssuesNotification newNewIssuesNotication() {
+    return new NewIssuesNotification(userIndex, dbClient, durations);
+  }
+}
index e2ca668151c9f60b6f3267b148463bce3739d53c..7e346c918e302551c8cd3765f1f8896f2fe460d7 100644 (file)
@@ -27,7 +27,7 @@ import org.sonar.api.utils.Duration;
 import org.sonar.core.util.MultiSets;
 
 import java.util.EnumMap;
-import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -35,58 +35,50 @@ import static com.google.common.base.Preconditions.checkArgument;
 import static org.sonar.server.issue.notification.NewIssuesStatistics.METRIC.*;
 
 public class NewIssuesStatistics {
-  private Map<String, Stats> statisticsByLogin = new HashMap<>();
+  private Map<String, Stats> assigneesStatistics = new LinkedHashMap<>();
   private Stats globalStatistics = new Stats();
 
   public void add(Issue issue) {
     globalStatistics.add(issue);
     String login = issue.assignee();
     if (login != null) {
-      statisticsForLogin(login).add(issue);
+      getOrCreate(login).add(issue);
     }
   }
 
-  private Stats statisticsForLogin(String login) {
-    if (statisticsByLogin.get(login) == null) {
-      statisticsByLogin.put(login, new Stats());
+  private Stats getOrCreate(String assignee) {
+    if (assigneesStatistics.get(assignee) == null) {
+      assigneesStatistics.put(assignee, new Stats());
     }
-    return statisticsByLogin.get(login);
+    return assigneesStatistics.get(assignee);
   }
 
-  public int countForMetric(METRIC metric) {
-    return globalStatistics.distributionFor(metric).size();
+  public Map<String, Stats> assigneesStatistics() {
+    return assigneesStatistics;
   }
 
-  public int countForMetric(METRIC metric, String label) {
-    return globalStatistics.distributionFor(metric).count(label);
-  }
-
-  public List<Multiset.Entry<String>> statsForMetric(METRIC metric) {
-    return MultiSets.listOrderedByHighestCounts(globalStatistics.distributionFor(metric));
-  }
-
-  public Duration debt() {
-    return globalStatistics.debt();
+  public Stats globalStatistics() {
+    return globalStatistics;
   }
 
   public boolean hasIssues() {
     return globalStatistics.hasIssues();
   }
 
-  public enum METRIC {
-    SEVERITY(true), TAGS(true), COMPONENT(true), LOGIN(true), DEBT(false);
-    private final boolean computeDistribution;
+  enum METRIC {
+    SEVERITY(true), TAGS(true), COMPONENT(true), ASSIGNEE(true), DEBT(false);
+    private final boolean isComputedByDistribution;
 
-    METRIC(boolean computeDistribution) {
-      this.computeDistribution = computeDistribution;
+    METRIC(boolean isComputedByDistribution) {
+      this.isComputedByDistribution = isComputedByDistribution;
     }
 
     boolean isComputedByDistribution() {
-      return this.computeDistribution;
+      return this.isComputedByDistribution;
     }
   }
 
-  private static class Stats {
+  public static class Stats {
     private final Map<METRIC, Multiset<String>> distributions = new EnumMap<>(METRIC.class);
     private long debtInMinutes = 0L;
 
@@ -102,7 +94,7 @@ public class NewIssuesStatistics {
       distributions.get(SEVERITY).add(issue.severity());
       distributions.get(COMPONENT).add(issue.componentUuid());
       if (issue.assignee() != null) {
-        distributions.get(LOGIN).add(issue.assignee());
+        distributions.get(ASSIGNEE).add(issue.assignee());
       }
       for (String tag : issue.tags()) {
         distributions.get(TAGS).add(tag);
@@ -113,9 +105,12 @@ public class NewIssuesStatistics {
       }
     }
 
-    public Multiset<String> distributionFor(METRIC metric) {
-      checkArgument(metric.isComputedByDistribution());
-      return distributions.get(metric);
+    public int countForMetric(METRIC metric) {
+      return distributionFor(metric).size();
+    }
+
+    public int countForMetric(METRIC metric, String label) {
+      return distributionFor(metric).count(label);
     }
 
     public Duration debt() {
@@ -125,5 +120,14 @@ public class NewIssuesStatistics {
     public boolean hasIssues() {
       return distributions.get(SEVERITY) != null;
     }
+
+    public List<Multiset.Entry<String>> statsForMetric(METRIC metric) {
+      return MultiSets.listOrderedByHighestCounts(distributionFor(metric));
+    }
+
+    private Multiset<String> distributionFor(METRIC metric) {
+      checkArgument(metric.isComputedByDistribution());
+      return distributions.get(metric);
+    }
   }
 }
index f153093fbc94cb8ad4708f7b6e28264924936e9a..0d19001274f2eec557729e996af52f3355ab71a1 100644 (file)
@@ -538,13 +538,17 @@ class ServerComponents {
     pico.addSingleton(IssueActionsWriter.class);
     pico.addSingleton(IssueQueryService.class);
     pico.addSingleton(NewIssuesEmailTemplate.class);
+    pico.addSingleton(MyNewIssuesEmailTemplate.class);
     pico.addSingleton(IssueChangesEmailTemplate.class);
     pico.addSingleton(ChangesOnMyIssueNotificationDispatcher.class);
     pico.addSingleton(ChangesOnMyIssueNotificationDispatcher.newMetadata());
     pico.addSingleton(NewIssuesNotificationDispatcher.class);
     pico.addSingleton(NewIssuesNotificationDispatcher.newMetadata());
+    pico.addSingleton(MyNewIssuesNotificationDispatcher.class);
+    pico.addSingleton(MyNewIssuesNotificationDispatcher.newMetadata());
     pico.addSingleton(DoNotFixNotificationDispatcher.class);
     pico.addSingleton(DoNotFixNotificationDispatcher.newMetadata());
+    pico.addSingleton(NewIssuesNotificationFactory.class);
 
     // issue filters
     pico.addSingleton(IssueFilterService.class);
index a2d4eb842d0d068307fe8b5c8e53451e4db34135..3742441d5b26a909cb4675ed95d16aa80fd41a62 100644 (file)
@@ -27,7 +27,6 @@ import org.mockito.Mockito;
 import org.sonar.api.issue.internal.DefaultIssue;
 import org.sonar.api.notifications.Notification;
 import org.sonar.api.rule.Severity;
-import org.sonar.api.utils.Durations;
 import org.sonar.api.utils.System2;
 import org.sonar.batch.protocol.output.BatchReport;
 import org.sonar.server.computation.ComputationContext;
@@ -35,6 +34,7 @@ import org.sonar.server.computation.issue.IssueCache;
 import org.sonar.server.computation.issue.RuleCache;
 import org.sonar.server.issue.notification.IssueChangeNotification;
 import org.sonar.server.issue.notification.NewIssuesNotification;
+import org.sonar.server.issue.notification.NewIssuesNotificationFactory;
 import org.sonar.server.notifications.NotificationService;
 
 import java.io.IOException;
@@ -51,13 +51,13 @@ public class SendIssueNotificationsStepTest extends BaseStepTest {
   NotificationService notifService = mock(NotificationService.class);
   ComputationContext context = mock(ComputationContext.class, Mockito.RETURNS_DEEP_STUBS);
   IssueCache issueCache;
-  Durations durations = mock(Durations.class);
+  NewIssuesNotificationFactory newIssuesNotificationFactory = mock(NewIssuesNotificationFactory.class, Mockito.RETURNS_DEEP_STUBS);
   SendIssueNotificationsStep sut;
 
   @Before
   public void setUp() throws Exception {
     issueCache = new IssueCache(temp.newFile(), System2.INSTANCE);
-    sut = new SendIssueNotificationsStep(issueCache, ruleCache, notifService, durations);
+    sut = new SendIssueNotificationsStep(issueCache, ruleCache, notifService, newIssuesNotificationFactory);
   }
 
   @Test
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/notification/MyNewIssuesEmailTemplateTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/notification/MyNewIssuesEmailTemplateTest.java
new file mode 100644 (file)
index 0000000..509c6d8
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.server.issue.notification;
+
+import com.google.common.base.Charsets;
+import org.apache.commons.io.IOUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.sonar.api.config.EmailSettings;
+import org.sonar.api.notifications.Notification;
+import org.sonar.core.i18n.DefaultI18n;
+import org.sonar.plugins.emailnotifications.api.EmailMessage;
+import org.sonar.server.user.index.UserDoc;
+import org.sonar.server.user.index.UserIndex;
+
+import java.util.Date;
+import java.util.Locale;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.server.issue.notification.NewIssuesStatistics.METRIC.*;
+
+public class MyNewIssuesEmailTemplateTest {
+
+  MyNewIssuesEmailTemplate sut;
+  DefaultI18n i18n;
+  UserIndex userIndex;
+  Date date;
+
+  @Before
+  public void setUp() {
+    EmailSettings settings = mock(EmailSettings.class);
+    when(settings.getServerBaseURL()).thenReturn("http://nemo.sonarsource.org");
+    i18n = mock(DefaultI18n.class);
+    date = new Date();
+    userIndex = mock(UserIndex.class);
+    // returns the login passed in parameter
+    when(userIndex.getNullableByLogin(anyString())).thenAnswer(new Answer<UserDoc>() {
+      @Override
+      public UserDoc answer(InvocationOnMock invocationOnMock) throws Throwable {
+        return new UserDoc().setName((String) invocationOnMock.getArguments()[0]);
+      }
+    });
+    when(i18n.message(any(Locale.class), eq("severity.BLOCKER"), anyString())).thenReturn("Blocker");
+    when(i18n.message(any(Locale.class), eq("severity.CRITICAL"), anyString())).thenReturn("Critical");
+    when(i18n.message(any(Locale.class), eq("severity.MAJOR"), anyString())).thenReturn("Major");
+    when(i18n.message(any(Locale.class), eq("severity.MINOR"), anyString())).thenReturn("Minor");
+    when(i18n.message(any(Locale.class), eq("severity.INFO"), anyString())).thenReturn("Info");
+
+    sut = new MyNewIssuesEmailTemplate(settings, i18n);
+  }
+
+  @Test
+  public void no_format_if_not_the_correct_notif() {
+    Notification notification = new Notification("new-issues");
+    EmailMessage message = sut.format(notification);
+    assertThat(message).isNull();
+  }
+
+  @Test
+  public void format_email_with_all_fields_filled() throws Exception {
+    Notification notification = newNotification();
+    addTags(notification);
+    addComponents(notification);
+
+    EmailMessage message = sut.format(notification);
+
+    // TODO datetime to be completed when test is isolated from JVM timezone
+    String file = IOUtils.toString(getClass().getResource("MyNewIssuesEmailTemplateTest/email_with_all_details.txt"), Charsets.UTF_8);
+    assertThat(message.getMessage()).startsWith(file);
+  }
+
+  @Test
+  public void message_id() throws Exception {
+    Notification notification = newNotification();
+
+    EmailMessage message = sut.format(notification);
+
+    assertThat(message.getMessageId()).isEqualTo("my-new-issues/org.apache:struts");
+  }
+
+  @Test
+  public void subject() throws Exception {
+    Notification notification = newNotification();
+
+    EmailMessage message = sut.format(notification);
+
+    assertThat(message.getSubject()).isEqualTo("You have 32 new issues on project Struts");
+  }
+
+  @Test
+  public void format_email_with_no_assignees_tags_nor_components() throws Exception {
+    Notification notification = newNotification();
+
+    EmailMessage message = sut.format(notification);
+
+    // TODO datetime to be completed when test is isolated from JVM timezone
+    String file = IOUtils.toString(getClass().getResource("MyNewIssuesEmailTemplateTest/email_with_no_assignee_tags_components.txt"), Charsets.UTF_8);
+    assertThat(message.getMessage()).startsWith(file);
+  }
+
+  @Test
+  public void do_not_add_footer_when_properties_missing() {
+    Notification notification = new Notification(MyNewIssuesNotification.TYPE)
+      .setFieldValue(SEVERITY + ".count", "32")
+      .setFieldValue("projectName", "Struts");
+
+    EmailMessage message = sut.format(notification);
+    assertThat(message.getMessage()).doesNotContain("See it");
+  }
+
+  private Notification newNotification() {
+    return new Notification(MyNewIssuesNotification.TYPE)
+      .setFieldValue("projectName", "Struts")
+      .setFieldValue("projectKey", "org.apache:struts")
+      .setFieldValue("projectUuid", "ABCDE")
+      .setFieldValue("projectDate", "2010-05-18T14:50:45+0000")
+      .setFieldValue("assignee", "lo.gin")
+      .setFieldValue(DEBT + ".count", "1d3h")
+      .setFieldValue(SEVERITY + ".count", "32")
+      .setFieldValue(SEVERITY + ".INFO.count", "1")
+      .setFieldValue(SEVERITY + ".MINOR.count", "3")
+      .setFieldValue(SEVERITY + ".MAJOR.count", "10")
+      .setFieldValue(SEVERITY + ".CRITICAL.count", "5")
+      .setFieldValue(SEVERITY + ".BLOCKER.count", "0");
+  }
+
+  private void addTags(Notification notification) {
+    notification
+      .setFieldValue(TAGS + ".1.label", "oscar")
+      .setFieldValue(TAGS + ".1.count", "3")
+      .setFieldValue(TAGS + ".2.label", "cesar")
+      .setFieldValue(TAGS + ".2.count", "10");
+  }
+
+  private void addComponents(Notification notification) {
+    notification
+      .setFieldValue(COMPONENT + ".1.label", "/path/to/file")
+      .setFieldValue(COMPONENT + ".1.count", "3")
+      .setFieldValue(COMPONENT + ".2.label", "/path/to/directory")
+      .setFieldValue(COMPONENT + ".2.count", "7");
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/notification/MyNewIssuesNotificationDispatcherTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/notification/MyNewIssuesNotificationDispatcherTest.java
new file mode 100644 (file)
index 0000000..ca9b627
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.server.issue.notification;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.notifications.NotificationChannel;
+import org.sonar.api.notifications.NotificationDispatcher;
+import org.sonar.api.notifications.NotificationManager;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.*;
+
+public class MyNewIssuesNotificationDispatcherTest {
+
+  private MyNewIssuesNotificationDispatcher sut;
+
+  private NotificationManager notificationManager = mock(NotificationManager.class);
+  private NotificationDispatcher.Context context = mock(NotificationDispatcher.Context.class);
+  private NotificationChannel emailChannel = mock(NotificationChannel.class);
+  private NotificationChannel twitterChannel = mock(NotificationChannel.class);
+
+
+  @Before
+  public void setUp() {
+    sut = new MyNewIssuesNotificationDispatcher(notificationManager);
+  }
+
+  @Test
+  public void do_not_dispatch_if_no_new_notification() throws Exception {
+    Notification notification = new Notification("other-notif");
+    sut.performDispatch(notification, context);
+
+    verify(context, never()).addUser(any(String.class), any(NotificationChannel.class));
+  }
+
+  @Test
+  public void dispatch_to_users_who_have_subscribed_to_notification_and_project() {
+    Multimap<String, NotificationChannel> recipients = HashMultimap.create();
+    recipients.put("user1", emailChannel);
+    recipients.put("user2", twitterChannel);
+    when(notificationManager.findNotificationSubscribers(sut, "struts")).thenReturn(recipients);
+
+    Notification notification = new Notification(MyNewIssuesNotification.TYPE)
+      .setFieldValue("projectKey", "struts")
+      .setFieldValue("assignee", "user1");
+    sut.performDispatch(notification, context);
+
+    verify(context).addUser("user1", emailChannel);
+    verifyNoMoreInteractions(context);
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/notification/MyNewIssuesNotificationTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/notification/MyNewIssuesNotificationTest.java
new file mode 100644 (file)
index 0000000..80ac178
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.server.issue.notification;
+
+import org.junit.Test;
+import org.sonar.api.utils.Durations;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.user.index.UserIndex;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_ASSIGNEE;
+
+public class MyNewIssuesNotificationTest {
+
+  MyNewIssuesNotification sut = new MyNewIssuesNotification(mock(UserIndex.class), mock(DbClient.class), mock(Durations.class));
+
+  @Test
+  public void set_assignee() throws Exception {
+    sut.setAssignee("myAssignee");
+
+    assertThat(sut.getFieldValue(FIELD_ASSIGNEE)).isEqualTo("myAssignee");
+  }
+
+  @Test
+  public void set_with_a_specific_type() throws Exception {
+    assertThat(sut.getType()).isEqualTo(MyNewIssuesNotification.TYPE);
+
+  }
+}
index b8c03d252ac8874994ab7ca365919da46cfbd77b..4de5ac346a305e5e325d13bb428713fc0fa2bbea 100644 (file)
@@ -19,6 +19,8 @@
  */
 package org.sonar.server.issue.notification;
 
+import com.google.common.base.Charsets;
+import org.apache.commons.io.IOUtils;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.invocation.InvocationOnMock;
@@ -40,16 +42,6 @@ import static org.sonar.server.issue.notification.NewIssuesStatistics.METRIC.*;
 
 public class NewIssuesEmailTemplateTest {
 
-  private static final String EMAIL_HEADER = "Project: Struts\n\n";
-  private static final String EMAIL_TOTAL_ISSUES = "32 new issues - Total debt: 1d3h\n\n";
-  private static final String EMAIL_ISSUES = "   Severity - Blocker: 0   Critical: 5   Major: 10   Minor: 3   Info: 1\n";
-  private static final String EMAIL_ASSIGNEES = "   Assignee - robin.williams: 5   al.pacino: 7   \n";
-  private static final String EMAIL_TAGS = "   Tags - oscar: 3   cesar: 10   \n";
-  private static final String EMAIL_COMPONENTS = "   Components:\n" +
-    "      /path/to/file : 3\n" +
-    "      /path/to/directory : 7\n";
-  private static final String EMAIL_FOOTER = "\nSee it in SonarQube: http://nemo.sonarsource.org/issues/search#projectUuids=ABCDE|createdAt=2010-05-1";
-
   NewIssuesEmailTemplate template;
   DefaultI18n i18n;
   UserIndex userIndex;
@@ -73,36 +65,36 @@ public class NewIssuesEmailTemplateTest {
     when(i18n.message(any(Locale.class), eq("severity.MINOR"), anyString())).thenReturn("Minor");
     when(i18n.message(any(Locale.class), eq("severity.INFO"), anyString())).thenReturn("Info");
 
-    template = new NewIssuesEmailTemplate(settings, i18n, userIndex);
+    template = new NewIssuesEmailTemplate(settings, i18n);
   }
 
   @Test
-  public void shouldNotFormatIfNotCorrectNotification() {
-    Notification notification = new Notification("other-notif");
+  public void no_format_is_not_the_correct_notification() {
+    Notification notification = new Notification("my-new-issues");
     EmailMessage message = template.format(notification);
     assertThat(message).isNull();
   }
 
-  /**
-   * <pre>
-   * Subject: Project Struts, new issues
-   * From: Sonar
-   *
-   * Project: Foo
-   * 32 new issues - Total debt: 1d3h
-   *
-   *    Severity - Blocker: 0   Critical: 5   Major: 10   Minor: 3   Info: 1
-   *    Assignee - robin.williams: 5   al.pacino: 7
-   *    Tags - oscar: 3   cesar:10
-   *    Components:
-   *       /path/to/file : 3
-   *       /path/to/directoy : 7
-   *
-   * See it in SonarQube: http://nemo.sonarsource.org/drilldown/measures/org.sonar.foo:foo?metric=new_violations
-   * </pre>
-   */
   @Test
-  public void format_email_with_all_fields_filled() {
+  public void message_id() throws Exception {
+    Notification notification = newNotification();
+
+    EmailMessage message = template.format(notification);
+
+    assertThat(message.getMessageId()).isEqualTo("new-issues/org.apache:struts");
+  }
+
+  @Test
+  public void subject() throws Exception {
+    Notification notification = newNotification();
+
+    EmailMessage message = template.format(notification);
+
+    assertThat(message.getSubject()).isEqualTo("Struts: 32 new issues (new debt: 1d3h)");
+  }
+
+  @Test
+  public void format_email_with_all_fields_filled() throws Exception {
     Notification notification = newNotification();
     addAssignees(notification);
     addTags(notification);
@@ -110,55 +102,35 @@ public class NewIssuesEmailTemplateTest {
 
     EmailMessage message = template.format(notification);
 
-    assertThat(message.getMessageId()).isEqualTo("new-issues/org.apache:struts");
-    assertThat(message.getSubject()).isEqualTo("Struts: 32 new issues");
-
     // TODO datetime to be completed when test is isolated from JVM timezone
-    assertThat(message.getMessage()).startsWith("" +
-      EMAIL_HEADER +
-      EMAIL_TOTAL_ISSUES +
-      EMAIL_ISSUES +
-      EMAIL_ASSIGNEES +
-      EMAIL_TAGS +
-      EMAIL_COMPONENTS +
-      EMAIL_FOOTER);
+    String expectedContent = IOUtils.toString(getClass().getResource("NewIssuesEmailTemplateTest/email_with_all_details.txt"), Charsets.UTF_8);
+    assertThat(message.getMessage()).startsWith(expectedContent);
   }
 
   @Test
   public void format_email_with_no_assignees_tags_nor_components() throws Exception {
     Notification notification = newNotification();
 
-    when(i18n.message(any(Locale.class), eq("severity.BLOCKER"), anyString())).thenReturn("Blocker");
-    when(i18n.message(any(Locale.class), eq("severity.CRITICAL"), anyString())).thenReturn("Critical");
-    when(i18n.message(any(Locale.class), eq("severity.MAJOR"), anyString())).thenReturn("Major");
-    when(i18n.message(any(Locale.class), eq("severity.MINOR"), anyString())).thenReturn("Minor");
-    when(i18n.message(any(Locale.class), eq("severity.INFO"), anyString())).thenReturn("Info");
-
     EmailMessage message = template.format(notification);
 
-    assertThat(message.getMessageId()).isEqualTo("new-issues/org.apache:struts");
-    assertThat(message.getSubject()).isEqualTo("Struts: 32 new issues");
-
     // TODO datetime to be completed when test is isolated from JVM timezone
-    assertThat(message.getMessage()).startsWith("" +
-      EMAIL_HEADER +
-      EMAIL_TOTAL_ISSUES +
-      EMAIL_ISSUES +
-      EMAIL_FOOTER);
+    String expectedContent = IOUtils.toString(getClass().getResource("NewIssuesEmailTemplateTest/email_with_partial_details.txt"), Charsets.UTF_8);
+    assertThat(message.getMessage()).startsWith(expectedContent);
   }
 
   @Test
   public void do_not_add_footer_when_properties_missing() {
-    Notification notification = new NewIssuesNotification()
+    Notification notification = new Notification(NewIssuesNotification.TYPE)
       .setFieldValue(SEVERITY + ".count", "32")
       .setFieldValue("projectName", "Struts");
 
     EmailMessage message = template.format(notification);
+
     assertThat(message.getMessage()).doesNotContain("See it");
   }
 
   private Notification newNotification() {
-    return new NewIssuesNotification()
+    return new Notification(NewIssuesNotification.TYPE)
       .setFieldValue("projectName", "Struts")
       .setFieldValue("projectKey", "org.apache:struts")
       .setFieldValue("projectUuid", "ABCDE")
@@ -174,10 +146,10 @@ public class NewIssuesEmailTemplateTest {
 
   private void addAssignees(Notification notification) {
     notification
-      .setFieldValue(LOGIN + ".1.label", "robin.williams")
-      .setFieldValue(LOGIN + ".1.count", "5")
-      .setFieldValue(LOGIN + ".2.label", "al.pacino")
-      .setFieldValue(LOGIN + ".2.count", "7");
+      .setFieldValue(ASSIGNEE + ".1.label", "robin.williams")
+      .setFieldValue(ASSIGNEE + ".1.count", "5")
+      .setFieldValue(ASSIGNEE + ".2.label", "al.pacino")
+      .setFieldValue(ASSIGNEE + ".2.count", "7");
   }
 
   private void addTags(Notification notification) {
index 04a3b1ae3fb2f63874dcf0649073afd2a83bc1c3..5be50dc2393952cce1404cb378b22dce106687f0 100644 (file)
@@ -23,8 +23,6 @@ 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;
@@ -35,23 +33,14 @@ import static org.mockito.Mockito.*;
 
 public class NewIssuesNotificationDispatcherTest {
 
-  @Mock
-  private NotificationManager notifications;
-
-  @Mock
-  private NotificationDispatcher.Context context;
-
-  @Mock
-  private NotificationChannel emailChannel;
-
-  @Mock
-  private NotificationChannel twitterChannel;
-
-  private NewIssuesNotificationDispatcher dispatcher;
+  private NotificationManager notifications = mock(NotificationManager.class);
+  private NotificationDispatcher.Context context = mock(NotificationDispatcher.Context.class);
+  private NotificationChannel emailChannel = mock(NotificationChannel.class);
+  private NotificationChannel twitterChannel = mock(NotificationChannel.class);
+  private NewIssuesNotificationDispatcher dispatcher = mock(NewIssuesNotificationDispatcher.class);
 
   @Before
-  public void init() {
-    MockitoAnnotations.initMocks(this);
+  public void setUp() {
     dispatcher = new NewIssuesNotificationDispatcher(notifications);
   }
 
@@ -70,7 +59,7 @@ public class NewIssuesNotificationDispatcherTest {
     recipients.put("user2", twitterChannel);
     when(notifications.findNotificationSubscribers(dispatcher, "struts")).thenReturn(recipients);
 
-    Notification notification = new NewIssuesNotification().setFieldValue("projectKey", "struts");
+    Notification notification = new Notification(NewIssuesNotification.TYPE).setFieldValue("projectKey", "struts");
     dispatcher.performDispatch(notification, context);
 
     verify(context).addUser("user1", emailChannel);
index 73c52db97c8d0303bf269ac90161b6f5a7b0c3fb..18ab1361d4b5158b7ccc150339d03f876a6c6e17 100644 (file)
@@ -22,22 +22,34 @@ package org.sonar.server.issue.notification;
 
 import com.google.common.collect.Lists;
 import org.junit.Test;
+import org.mockito.Mockito;
 import org.sonar.api.issue.internal.DefaultIssue;
 import org.sonar.api.rule.Severity;
 import org.sonar.api.utils.DateUtils;
 import org.sonar.api.utils.Duration;
+import org.sonar.api.utils.Durations;
 import org.sonar.core.component.ComponentDto;
+import org.sonar.core.persistence.DbSession;
 import org.sonar.server.component.ComponentTesting;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.user.index.UserIndex;
 
 import java.util.Date;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 import static org.sonar.server.issue.notification.NewIssuesStatistics.METRIC.*;
 
 public class NewIssuesNotificationTest {
 
-  NewIssuesNotification sut = new NewIssuesNotification();
-  NewIssuesStatistics stats = new NewIssuesStatistics();
+  NewIssuesStatistics.Stats stats = new NewIssuesStatistics.Stats();
+  UserIndex userIndex = mock(UserIndex.class);
+  DbClient dbClient = mock(DbClient.class, Mockito.RETURNS_DEEP_STUBS);
+  Durations durations = mock(Durations.class);
+  NewIssuesNotification sut = new NewIssuesNotification(userIndex, dbClient, durations);
 
   @Test
   public void set_project() throws Exception {
@@ -68,29 +80,33 @@ public class NewIssuesNotificationTest {
       .setLongName("project-long-name");
     addIssueNTimes(newIssue1(), 5);
     addIssueNTimes(newIssue2(), 3);
+    when(dbClient.componentDao().getByUuid(any(DbSession.class), eq("file-uuid")).name()).thenReturn("file-name");
+    when(dbClient.componentDao().getByUuid(any(DbSession.class), eq("directory-uuid")).name()).thenReturn("directory-name");
 
     sut.setStatistics(component, stats);
 
     assertThat(sut.getFieldValue(SEVERITY + ".INFO.count")).isEqualTo("5");
     assertThat(sut.getFieldValue(SEVERITY + ".BLOCKER.count")).isEqualTo("3");
-    assertThat(sut.getFieldValue(LOGIN + ".1.label")).isEqualTo("maynard");
-    assertThat(sut.getFieldValue(LOGIN + ".1.count")).isEqualTo("5");
-    assertThat(sut.getFieldValue(LOGIN + ".2.label")).isEqualTo("keenan");
-    assertThat(sut.getFieldValue(LOGIN + ".2.count")).isEqualTo("3");
+    assertThat(sut.getFieldValue(ASSIGNEE + ".1.label")).isEqualTo("maynard");
+    assertThat(sut.getFieldValue(ASSIGNEE + ".1.count")).isEqualTo("5");
+    assertThat(sut.getFieldValue(ASSIGNEE + ".2.label")).isEqualTo("keenan");
+    assertThat(sut.getFieldValue(ASSIGNEE + ".2.count")).isEqualTo("3");
     assertThat(sut.getFieldValue(TAGS + ".1.label")).isEqualTo("owasp");
     assertThat(sut.getFieldValue(TAGS + ".1.count")).isEqualTo("8");
     assertThat(sut.getFieldValue(TAGS + ".2.label")).isEqualTo("bug");
     assertThat(sut.getFieldValue(TAGS + ".2.count")).isEqualTo("5");
-    assertThat(sut.getFieldValue(COMPONENT + ".1.label")).isEqualTo("file-uuid");
+    assertThat(sut.getFieldValue(COMPONENT + ".1.label")).isEqualTo("file-name");
     assertThat(sut.getFieldValue(COMPONENT + ".1.count")).isEqualTo("5");
-    assertThat(sut.getFieldValue(COMPONENT + ".2.label")).isEqualTo("directory-uuid");
+    assertThat(sut.getFieldValue(COMPONENT + ".2.label")).isEqualTo("directory-name");
     assertThat(sut.getFieldValue(COMPONENT + ".2.count")).isEqualTo("3");
     assertThat(sut.getDefaultMessage()).startsWith("8 new issues on project-long-name");
   }
 
   @Test
   public void set_debt() throws Exception {
-    sut.setDebt("55min");
+    when(durations.encode(any(Duration.class))).thenReturn("55min");
+
+    sut.setDebt(Duration.create(55));
 
     assertThat(sut.getFieldValue(DEBT + ".count")).isEqualTo("55min");
   }
index 2bc1c2c3d660578fb931843c7e849c218b57308e..00ecf99da51e9b466f8979093c99522ea0bb8c7f 100644 (file)
@@ -41,21 +41,21 @@ public class NewIssuesStatisticsTest {
     sut.add(issue.setAssignee("james"));
     sut.add(issue.setAssignee("keenan"));
 
-    assertThat(countDistribution(METRIC.LOGIN, "maynard")).isEqualTo(1);
-    assertThat(countDistribution(METRIC.LOGIN, "james")).isEqualTo(1);
-    assertThat(countDistribution(METRIC.LOGIN, "keenan")).isEqualTo(1);
-    assertThat(countDistribution(METRIC.LOGIN, "wrong.login")).isEqualTo(0);
+    assertThat(countDistribution(METRIC.ASSIGNEE, "maynard")).isEqualTo(1);
+    assertThat(countDistribution(METRIC.ASSIGNEE, "james")).isEqualTo(1);
+    assertThat(countDistribution(METRIC.ASSIGNEE, "keenan")).isEqualTo(1);
+    assertThat(countDistribution(METRIC.ASSIGNEE, "wrong.login")).isEqualTo(0);
     assertThat(countDistribution(METRIC.COMPONENT, "file-uuid")).isEqualTo(3);
     assertThat(countDistribution(METRIC.COMPONENT, "wrong-uuid")).isEqualTo(0);
     assertThat(countDistribution(METRIC.SEVERITY, Severity.INFO)).isEqualTo(3);
     assertThat(countDistribution(METRIC.SEVERITY, Severity.CRITICAL)).isEqualTo(0);
     assertThat(countDistribution(METRIC.TAGS, "owasp")).isEqualTo(3);
     assertThat(countDistribution(METRIC.TAGS, "wrong-tag")).isEqualTo(0);
-    assertThat(sut.debt().toMinutes()).isEqualTo(15L);
+    assertThat(sut.globalStatistics().debt().toMinutes()).isEqualTo(15L);
   }
 
   private int countDistribution(METRIC metric, String label) {
-    return sut.countForMetric(metric, label);
+    return sut.globalStatistics().countForMetric(metric, label);
   }
 
   private DefaultIssue defaultIssue() {
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/issue/notification/MyNewIssuesEmailTemplateTest/email_with_all_details.txt b/server/sonar-server/src/test/resources/org/sonar/server/issue/notification/MyNewIssuesEmailTemplateTest/email_with_all_details.txt
new file mode 100644 (file)
index 0000000..39fa601
--- /dev/null
@@ -0,0 +1,16 @@
+Project: Struts
+
+32 new issues (new debt: 1d3h)
+
+    Severity
+        Blocker: 0    Critical: 5    Major: 10    Minor: 3    Info: 1
+
+    Tags
+        oscar: 3
+        cesar: 10
+
+    Most impacted files
+        /path/to/file: 3
+        /path/to/directory: 7
+
+See it in SonarQube: http://nemo.sonarsource.org/issues/search#projectUuids=ABCDE|assignees=lo.gin|createdAt=2010-05-1
\ No newline at end of file
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/issue/notification/MyNewIssuesEmailTemplateTest/email_with_no_assignee_tags_components.txt b/server/sonar-server/src/test/resources/org/sonar/server/issue/notification/MyNewIssuesEmailTemplateTest/email_with_no_assignee_tags_components.txt
new file mode 100644 (file)
index 0000000..a502a40
--- /dev/null
@@ -0,0 +1,8 @@
+Project: Struts
+
+32 new issues (new debt: 1d3h)
+
+    Severity
+        Blocker: 0    Critical: 5    Major: 10    Minor: 3    Info: 1
+
+See it in SonarQube: http://nemo.sonarsource.org/issues/search#projectUuids=ABCDE|assignees=lo.gin|createdAt=2010-05-1
\ No newline at end of file
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/issue/notification/NewIssuesEmailTemplateTest/email_with_all_details.txt b/server/sonar-server/src/test/resources/org/sonar/server/issue/notification/NewIssuesEmailTemplateTest/email_with_all_details.txt
new file mode 100644 (file)
index 0000000..bddf6f7
--- /dev/null
@@ -0,0 +1,20 @@
+Project: Struts
+
+32 new issues (new debt: 1d3h)
+
+    Severity
+        Blocker: 0    Critical: 5    Major: 10    Minor: 3    Info: 1
+
+    Assignees
+        robin.williams: 5
+        al.pacino: 7
+
+    Tags
+        oscar: 3
+        cesar: 10
+
+    Most impacted files
+        /path/to/file: 3
+        /path/to/directory: 7
+
+See it in SonarQube: http://nemo.sonarsource.org/issues/search#projectUuids=ABCDE|createdAt=2010-05-1
\ No newline at end of file
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/issue/notification/NewIssuesEmailTemplateTest/email_with_partial_details.txt b/server/sonar-server/src/test/resources/org/sonar/server/issue/notification/NewIssuesEmailTemplateTest/email_with_partial_details.txt
new file mode 100644 (file)
index 0000000..95b996c
--- /dev/null
@@ -0,0 +1,8 @@
+Project: Struts
+
+32 new issues (new debt: 1d3h)
+
+    Severity
+        Blocker: 0    Critical: 5    Major: 10    Minor: 3    Info: 1
+
+See it in SonarQube: http://nemo.sonarsource.org/issues/search#projectUuids=ABCDE|createdAt=2010-05-1
\ No newline at end of file
index 0b0e44bf64b02943a1f862043fa6d63c5f7303a0..50b4924df33809cc62b68cfdb2f93f7ea88fe5b7 100644 (file)
@@ -2040,6 +2040,7 @@ notification.dispatcher.ChangesOnMyIssue=Changes in issues assigned to me or rep
 notification.dispatcher.NewIssues=New issues
 notification.dispatcher.NewAlerts=New quality gate status
 notification.dispatcher.NewFalsePositiveIssue=Issues resolved as false positive or won't fix
+notification.dispatcher.MyNewIssues=My New Issues
 
 
 #------------------------------------------------------------------------------