aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>2015-02-27 15:32:30 +0100
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>2015-03-04 08:43:53 +0100
commitef937b146a699cc85accbdf65973abcaf8fc8e0f (patch)
tree5befcbc929711f2060a4fcb31abbd9845a7c4703 /server
parent6f9777c016289556b7981aa894dc6a4739aa0d9d (diff)
downloadsonarqube-ef937b146a699cc85accbdf65973abcaf8fc8e0f.tar.gz
sonarqube-ef937b146a699cc85accbdf65973abcaf8fc8e0f.zip
add information to the "New Issues" notification - SONAR-6045
Diffstat (limited to 'server')
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/ComputationContext.java5
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/step/SendIssueNotificationsStep.java17
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesEmailTemplate.java128
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesNotification.java53
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesStatistics.java128
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/step/SendIssueNotificationsStepTest.java7
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesEmailTemplateTest.java146
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationTest.java102
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesStatisticsTest.java70
9 files changed, 551 insertions, 105 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationContext.java b/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationContext.java
index 82d5e2dd4c3..9df00c9c6a5 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationContext.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationContext.java
@@ -24,8 +24,6 @@ import org.sonar.api.config.Settings;
import org.sonar.batch.protocol.output.BatchReport;
import org.sonar.batch.protocol.output.BatchReportReader;
import org.sonar.core.component.ComponentDto;
-import org.sonar.core.computation.db.AnalysisReportDto;
-import org.sonar.server.computation.step.ParseReportStep;
import static com.google.common.base.Preconditions.checkState;
@@ -33,10 +31,9 @@ public class ComputationContext {
private final BatchReportReader reportReader;
private final ComponentDto project;
- private Settings projectSettings;
-
// cache of metadata as it's frequently accessed
private final BatchReport.Metadata reportMetadata;
+ private Settings projectSettings;
public ComputationContext(BatchReportReader reportReader, ComponentDto project) {
this.reportReader = reportReader;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/SendIssueNotificationsStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/SendIssueNotificationsStep.java
index 1d3fc613aad..f67a92860d0 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/SendIssueNotificationsStep.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/SendIssueNotificationsStep.java
@@ -22,12 +22,14 @@ 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.notifications.NotificationService;
import org.sonar.server.util.CloseableIterator;
@@ -48,11 +50,13 @@ public class SendIssueNotificationsStep implements ComputationStep {
private final IssueCache issueCache;
private final RuleCache rules;
private final NotificationService service;
+ private final Durations durations;
- public SendIssueNotificationsStep(IssueCache issueCache, RuleCache rules, NotificationService service) {
+ public SendIssueNotificationsStep(IssueCache issueCache, RuleCache rules, NotificationService service, Durations durations) {
this.issueCache = issueCache;
this.rules = rules;
this.service = service;
+ this.durations = durations;
}
@Override
@@ -68,13 +72,13 @@ public class SendIssueNotificationsStep implements ComputationStep {
}
private void doExecute(ComputationContext context) {
- NewIssuesNotification.Stats newIssueStats = new NewIssuesNotification.Stats();
+ NewIssuesStatistics newIssuesStats = new NewIssuesStatistics();
CloseableIterator<DefaultIssue> issues = issueCache.traverse();
try {
while (issues.hasNext()) {
DefaultIssue issue = issues.next();
if (issue.isNew() && issue.resolution() == null) {
- newIssueStats.add(issue);
+ newIssuesStats.add(issue);
} else if (issue.isChanged() && issue.mustSendNotifications()) {
IssueChangeNotification changeNotification = new IssueChangeNotification();
changeNotification.setRuleName(rules.ruleName(issue.ruleKey()));
@@ -87,16 +91,17 @@ public class SendIssueNotificationsStep implements ComputationStep {
} finally {
issues.close();
}
- sendNewIssuesStatistics(context, newIssueStats);
+ sendNewIssuesStatistics(context, newIssuesStats);
}
- private void sendNewIssuesStatistics(ComputationContext context, NewIssuesNotification.Stats stats) {
- if (stats.size() > 0) {
+ private void sendNewIssuesStatistics(ComputationContext context, NewIssuesStatistics stats) {
+ if (stats.hasIssues()) {
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()));
service.deliver(notification);
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesEmailTemplate.java b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesEmailTemplate.java
index 9caba17934a..d545ab9e9a1 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesEmailTemplate.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesEmailTemplate.java
@@ -19,6 +19,7 @@
*/
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;
@@ -27,6 +28,9 @@ 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;
@@ -42,13 +46,24 @@ 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 final EmailSettings settings;
private final I18n i18n;
+ private final UserIndex userIndex;
- public NewIssuesEmailTemplate(EmailSettings settings, I18n i18n) {
+ 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);
+ }
}
@Override
@@ -58,44 +73,105 @@ public class NewIssuesEmailTemplate extends EmailTemplate {
}
String projectName = notification.getFieldValue(FIELD_PROJECT_NAME);
- StringBuilder sb = new StringBuilder();
- sb.append("Project: ").append(projectName).append("\n\n");
- sb.append(notification.getFieldValue("count")).append(" new issues").append("\n\n");
- sb.append(" ");
+ StringBuilder message = new StringBuilder();
+ message.append("Project: ").append(projectName).append("\n\n");
+ 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 + "." + i + ".label") != null && i <= 5) {
+ String component = notification.getFieldValue(METRIC.COMPONENT + "." + i + ".label");
+ message.append(" ")
+ .append(component)
+ .append(" : ")
+ .append(notification.getFieldValue(METRIC.COMPONENT + "." + i + ".count"))
+ .append("\n");
+ i += 1;
+ }
+ }
+
+ private void appendAssignees(StringBuilder message, Notification notification) {
+ if (notification.getFieldValue(METRIC.LOGIN + ".1.label") == null) {
+ return;
+ }
+
+ message.append(" Assignee - ");
+ int i = 1;
+ while (notification.getFieldValue(METRIC.LOGIN + "." + i + ".label") != null && i <= 5) {
+ String login = notification.getFieldValue(METRIC.LOGIN + "." + 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 + "." + i + ".count"));
+ if (i < 5) {
+ message.append(" ");
+ }
+ i += 1;
+ }
+
+ message.append("\n");
+ }
+
+ private void appendTags(StringBuilder message, Notification notification) {
+ if (notification.getFieldValue(METRIC.TAGS + ".1.label") == null) {
+ return;
+ }
+
+ message.append(" Tags - ");
+ int i = 1;
+ while (notification.getFieldValue(METRIC.TAGS + "." + i + ".label") != null && i <= 5) {
+ String tag = notification.getFieldValue(METRIC.TAGS + "." + i + ".label");
+ message.append(tag)
+ .append(": ")
+ .append(notification.getFieldValue(METRIC.TAGS + "." + i + ".count"));
+ if (i < 5) {
+ message.append(" ");
+ }
+ i += 1;
+ }
+ message.append("\n");
+ }
+
+ 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("\n\n")
+ .append(" Severity - ");
for (Iterator<String> severityIterator = Lists.reverse(Severity.ALL).iterator(); severityIterator.hasNext();) {
String severity = severityIterator.next();
String severityLabel = i18n.message(getLocale(), "severity." + severity, severity);
- sb.append(severityLabel).append(": ").append(notification.getFieldValue("count-" + severity));
+ message.append(severityLabel).append(": ").append(notification.getFieldValue(METRIC.SEVERITY + "." + severity + ".count"));
if (severityIterator.hasNext()) {
- sb.append(" ");
+ message.append(" ");
}
}
- sb.append('\n');
-
- appendFooter(sb, notification);
-
- return new EmailMessage()
- .setMessageId("new-issues/" + notification.getFieldValue(FIELD_PROJECT_KEY))
- .setSubject(projectName + ": new issues")
- .setMessage(sb.toString());
+ message.append('\n');
}
- private void appendFooter(StringBuilder sb, Notification notification) {
- String projectUuid = notification.getFieldValue("projectUuid");
+ 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)));
- sb.append("\n").append("See it in SonarQube: ").append(url).append("\n");
- }
- }
-
- public static String encode(String toEncode) {
- try {
- return URLEncoder.encode(toEncode, "UTF-8");
- } catch (UnsupportedEncodingException e) {
- throw new IllegalStateException("Encoding not supported", e);
+ message.append("\n").append("See it in SonarQube: ").append(url).append("\n");
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesNotification.java b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesNotification.java
index ccade3f7025..a3f08c2a58b 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesNotification.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesNotification.java
@@ -19,16 +19,19 @@
*/
package org.sonar.server.issue.notification;
-import com.google.common.collect.HashMultiset;
import com.google.common.collect.Multiset;
import org.sonar.api.component.Component;
-import org.sonar.api.issue.Issue;
import org.sonar.api.notifications.Notification;
import org.sonar.api.rule.Severity;
import org.sonar.api.utils.DateUtils;
import org.sonar.core.component.ComponentDto;
+import org.sonar.server.issue.notification.NewIssuesStatistics.METRIC;
import java.util.Date;
+import java.util.List;
+
+import static org.sonar.server.issue.notification.NewIssuesEmailTemplate.*;
+import static org.sonar.server.issue.notification.NewIssuesStatistics.METRIC.SEVERITY;
public class NewIssuesNotification extends Notification {
@@ -39,39 +42,45 @@ public class NewIssuesNotification extends Notification {
}
public NewIssuesNotification setAnalysisDate(Date d) {
- setFieldValue("projectDate", DateUtils.formatDateTime(d));
+ setFieldValue(FIELD_PROJECT_DATE, DateUtils.formatDateTime(d));
return this;
}
public NewIssuesNotification setProject(ComponentDto project) {
- setFieldValue("projectName", project.longName());
- setFieldValue("projectKey", project.key());
- setFieldValue("projectUuid", project.uuid());
+ setFieldValue(FIELD_PROJECT_NAME, project.longName());
+ setFieldValue(FIELD_PROJECT_KEY, project.key());
+ setFieldValue(FIELD_PROJECT_UUID, project.uuid());
return this;
}
- public NewIssuesNotification setStatistics(Component project, Stats stats) {
- setDefaultMessage(stats.size() + " new issues on " + project.longName() + ".\n");
- setFieldValue("count", String.valueOf(stats.size()));
- for (String severity : Severity.ALL) {
- setFieldValue("count-" + severity, String.valueOf(stats.countIssuesWithSeverity(severity)));
- }
+ public NewIssuesNotification setStatistics(Component project, NewIssuesStatistics 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);
+
return this;
}
- public static class Stats {
- private final Multiset<String> set = HashMultiset.create();
-
- public void add(Issue issue) {
- set.add(issue.severity());
- }
+ public NewIssuesNotification setDebt(String debt) {
+ setFieldValue(METRIC.DEBT + ".count", debt);
+ return this;
+ }
- public int countIssuesWithSeverity(String severity) {
- return set.count(severity);
+ 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());
}
+ }
- public int size() {
- return set.size();
+ private void setSeverityStatistics(NewIssuesStatistics 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/NewIssuesStatistics.java b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesStatistics.java
new file mode 100644
index 00000000000..de4c45ee43e
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesStatistics.java
@@ -0,0 +1,128 @@
+/*
+ * 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.HashMultiset;
+import com.google.common.collect.Multiset;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.utils.Duration;
+import org.sonar.core.util.MultiSets;
+
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+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 Stats globalStatistics = new Stats();
+
+ public void add(Issue issue) {
+ globalStatistics.add(issue);
+ String login = issue.assignee();
+ if (login != null) {
+ statisticsForLogin(login).add(issue);
+ }
+ }
+
+ private Stats statisticsForLogin(String login) {
+ if (statisticsByLogin.get(login) == null) {
+ statisticsByLogin.put(login, new Stats());
+ }
+ return statisticsByLogin.get(login);
+ }
+
+ public int countForMetric(METRIC metric) {
+ return globalStatistics.distributionFor(metric).size();
+ }
+
+ 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 boolean hasIssues() {
+ return globalStatistics.hasIssues();
+ }
+
+ public enum METRIC {
+ SEVERITY(true), TAGS(true), COMPONENT(true), LOGIN(true), DEBT(false);
+ private final boolean computeDistribution;
+
+ METRIC(boolean computeDistribution) {
+ this.computeDistribution = computeDistribution;
+ }
+
+ boolean isComputedByDistribution() {
+ return this.computeDistribution;
+ }
+ }
+
+ private static class Stats {
+ private final Map<METRIC, Multiset<String>> distributions = new EnumMap<>(METRIC.class);
+ private long debtInMinutes = 0L;
+
+ public Stats() {
+ for (METRIC metric : METRIC.values()) {
+ if (metric.isComputedByDistribution()) {
+ distributions.put(metric, HashMultiset.<String>create());
+ }
+ }
+ }
+
+ public void add(Issue issue) {
+ distributions.get(SEVERITY).add(issue.severity());
+ distributions.get(COMPONENT).add(issue.componentUuid());
+ if (issue.assignee() != null) {
+ distributions.get(LOGIN).add(issue.assignee());
+ }
+ for (String tag : issue.tags()) {
+ distributions.get(TAGS).add(tag);
+ }
+ if (issue.debt() != null) {
+ debtInMinutes += issue.debt().toMinutes();
+ }
+ }
+
+ public Multiset<String> distributionFor(METRIC metric) {
+ checkArgument(metric.isComputedByDistribution());
+ return distributions.get(metric);
+ }
+
+ public Duration debt() {
+ return Duration.create(debtInMinutes);
+ }
+
+ public boolean hasIssues() {
+ return distributions.get(SEVERITY) != null;
+ }
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/SendIssueNotificationsStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/SendIssueNotificationsStepTest.java
index e0797f9ee94..a2d4eb842d0 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/computation/step/SendIssueNotificationsStepTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/SendIssueNotificationsStepTest.java
@@ -27,6 +27,7 @@ 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;
@@ -50,12 +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);
SendIssueNotificationsStep sut;
@Before
public void setUp() throws Exception {
issueCache = new IssueCache(temp.newFile(), System2.INSTANCE);
- sut = new SendIssueNotificationsStep(issueCache, ruleCache, notifService);
+ sut = new SendIssueNotificationsStep(issueCache, ruleCache, notifService, durations);
}
@Test
@@ -70,7 +72,8 @@ public class SendIssueNotificationsStepTest extends BaseStepTest {
@Test
public void send_notifications_if_subscribers() throws Exception {
- issueCache.newAppender().append(new DefaultIssue().setSeverity(Severity.BLOCKER)).close();
+ issueCache.newAppender().append(new DefaultIssue()
+ .setSeverity(Severity.BLOCKER)).close();
when(context.getProject().uuid()).thenReturn("PROJECT_UUID");
when(context.getReportMetadata()).thenReturn(BatchReport.Metadata.newBuilder().build());
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesEmailTemplateTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesEmailTemplateTest.java
index c436301c8cc..b8c03d252ac 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesEmailTemplateTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesEmailTemplateTest.java
@@ -21,36 +21,59 @@ package org.sonar.server.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.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.Locale;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
+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.*;
-@RunWith(MockitoJUnitRunner.class)
public class NewIssuesEmailTemplateTest {
- NewIssuesEmailTemplate template;
+ 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";
- @Mock
+ NewIssuesEmailTemplate template;
DefaultI18n i18n;
+ UserIndex userIndex;
@Before
public void setUp() {
EmailSettings settings = mock(EmailSettings.class);
when(settings.getServerBaseURL()).thenReturn("http://nemo.sonarsource.org");
- template = new NewIssuesEmailTemplate(settings, i18n);
+ i18n = mock(DefaultI18n.class);
+ 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");
+
+ template = new NewIssuesEmailTemplate(settings, i18n, userIndex);
}
@Test
@@ -66,24 +89,44 @@ public class NewIssuesEmailTemplateTest {
* From: Sonar
*
* Project: Foo
- * 32 new issues
+ * 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 shouldFormatCommentAdded() {
- Notification notification = new NewIssuesNotification()
- .setFieldValue("count", "32")
- .setFieldValue("count-INFO", "1")
- .setFieldValue("count-MINOR", "3")
- .setFieldValue("count-MAJOR", "10")
- .setFieldValue("count-CRITICAL", "5")
- .setFieldValue("count-BLOCKER", "0")
- .setFieldValue("projectName", "Struts")
- .setFieldValue("projectKey", "org.apache:struts")
- .setFieldValue("projectUuid", "ABCDE")
- .setFieldValue("projectDate", "2010-05-18T14:50:45+0000");
+ public void format_email_with_all_fields_filled() {
+ Notification notification = newNotification();
+ addAssignees(notification);
+ addTags(notification);
+ addComponents(notification);
+
+ 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);
+ }
+
+ @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");
@@ -92,27 +135,64 @@ public class NewIssuesEmailTemplateTest {
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: new issues");
+ assertThat(message.getSubject()).isEqualTo("Struts: 32 new issues");
// TODO datetime to be completed when test is isolated from JVM timezone
assertThat(message.getMessage()).startsWith("" +
- "Project: Struts\n" +
- "\n" +
- "32 new issues\n" +
- "\n" +
- " Blocker: 0 Critical: 5 Major: 10 Minor: 3 Info: 1\n" +
- "\n" +
- "See it in SonarQube: http://nemo.sonarsource.org/issues/search#projectUuids=ABCDE|createdAt=2010-05-1");
+ EMAIL_HEADER +
+ EMAIL_TOTAL_ISSUES +
+ EMAIL_ISSUES +
+ EMAIL_FOOTER);
}
@Test
- public void shouldNotAddFooterIfMissingProperties() {
+ public void do_not_add_footer_when_properties_missing() {
Notification notification = new NewIssuesNotification()
- .setFieldValue("count", "32")
+ .setFieldValue(SEVERITY + ".count", "32")
.setFieldValue("projectName", "Struts");
EmailMessage message = template.format(notification);
assertThat(message.getMessage()).doesNotContain("See it");
}
+
+ private Notification newNotification() {
+ return new NewIssuesNotification()
+ .setFieldValue("projectName", "Struts")
+ .setFieldValue("projectKey", "org.apache:struts")
+ .setFieldValue("projectUuid", "ABCDE")
+ .setFieldValue("projectDate", "2010-05-18T14:50:45+0000")
+ .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 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");
+ }
+
+ 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/NewIssuesNotificationTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationTest.java
index 4c208547406..73c52db97c8 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationTest.java
@@ -17,27 +17,105 @@
* 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.junit.Test;
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.core.component.ComponentDto;
+import org.sonar.server.component.ComponentTesting;
+
+import java.util.Date;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.issue.notification.NewIssuesStatistics.METRIC.*;
public class NewIssuesNotificationTest {
+ NewIssuesNotification sut = new NewIssuesNotification();
+ NewIssuesStatistics stats = new NewIssuesStatistics();
+
+ @Test
+ public void set_project() throws Exception {
+ ComponentDto component = ComponentTesting.newProjectDto()
+ .setLongName("project-long-name")
+ .setUuid("project-uuid")
+ .setKey("project-key");
+
+ sut.setProject(component);
+
+ assertThat(sut.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_NAME)).isEqualTo("project-long-name");
+ assertThat(sut.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_UUID)).isEqualTo("project-uuid");
+ assertThat(sut.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_KEY)).isEqualTo("project-key");
+ }
+
+ @Test
+ public void set_date() throws Exception {
+ Date date = new Date();
+
+ sut.setAnalysisDate(date);
+
+ assertThat(sut.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_DATE)).isEqualTo(DateUtils.formatDateTime(date));
+ }
+
@Test
- public void stats() {
- NewIssuesNotification.Stats sut = new NewIssuesNotification.Stats();
- assertThat(sut.size()).isEqualTo(0);
-
- sut.add(new DefaultIssue().setSeverity("MINOR"));
- sut.add(new DefaultIssue().setSeverity("BLOCKER"));
- sut.add(new DefaultIssue().setSeverity("MINOR"));
-
- assertThat(sut.size()).isEqualTo(3);
- assertThat(sut.countIssuesWithSeverity("INFO")).isEqualTo(0);
- assertThat(sut.countIssuesWithSeverity("MINOR")).isEqualTo(2);
- assertThat(sut.countIssuesWithSeverity("BLOCKER")).isEqualTo(1);
+ public void set_statistics() throws Exception {
+ ComponentDto component = ComponentTesting.newProjectDto()
+ .setLongName("project-long-name");
+ addIssueNTimes(newIssue1(), 5);
+ addIssueNTimes(newIssue2(), 3);
+
+ 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(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.count")).isEqualTo("5");
+ assertThat(sut.getFieldValue(COMPONENT + ".2.label")).isEqualTo("directory-uuid");
+ 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");
+
+ assertThat(sut.getFieldValue(DEBT + ".count")).isEqualTo("55min");
+ }
+
+ private void addIssueNTimes(DefaultIssue issue, int times) {
+ for (int i = 0; i < times; i++) {
+ stats.add(issue);
+ }
+ }
+
+ private DefaultIssue newIssue1() {
+ return new DefaultIssue()
+ .setAssignee("maynard")
+ .setComponentUuid("file-uuid")
+ .setSeverity(Severity.INFO)
+ .setTags(Lists.newArrayList("bug", "owasp"))
+ .setDebt(Duration.create(5L));
+ }
+
+ private DefaultIssue newIssue2() {
+ return new DefaultIssue()
+ .setAssignee("keenan")
+ .setComponentUuid("directory-uuid")
+ .setSeverity(Severity.BLOCKER)
+ .setTags(Lists.newArrayList("owasp"))
+ .setDebt(Duration.create(10L));
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesStatisticsTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesStatisticsTest.java
new file mode 100644
index 00000000000..2bc1c2c3d66
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesStatisticsTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.junit.Test;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.utils.Duration;
+import org.sonar.server.issue.notification.NewIssuesStatistics.METRIC;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class NewIssuesStatisticsTest {
+
+ NewIssuesStatistics sut = new NewIssuesStatistics();
+
+ @Test
+ public void add_issues_with_correct_global_statistics() throws Exception {
+ DefaultIssue issue = defaultIssue();
+
+ sut.add(issue);
+ 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.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);
+ }
+
+ private int countDistribution(METRIC metric, String label) {
+ return sut.countForMetric(metric, label);
+ }
+
+ private DefaultIssue defaultIssue() {
+ return new DefaultIssue()
+ .setAssignee("maynard")
+ .setComponentUuid("file-uuid")
+ .setNew(true)
+ .setSeverity(Severity.INFO)
+ .setTags(Lists.newArrayList("bug", "owasp"))
+ .setDebt(Duration.create(5L));
+ }
+}