From b4b3b848a8e671a08ecb0fd9941714696efc822b Mon Sep 17 00:00:00 2001 From: Julien Lancelot Date: Tue, 17 Sep 2013 16:19:26 +0200 Subject: [PATCH] SONAR-4625 New issues email should contain total issues for each issue type --- .../notification/NewIssuesEmailTemplate.java | 29 +++++++++-- .../SendIssueNotificationsPostJob.java | 7 +-- .../NewIssuesEmailTemplateTest.java | 26 +++++++++- .../SendIssueNotificationsPostJobTest.java | 17 +++--- .../sonar/core/issue/IssueNotifications.java | 14 +++-- .../sonar/core/issue/IssuesBySeverity.java | 50 ++++++++++++++++++ .../core/issue/IssueNotificationsTest.java | 26 ++++++---- .../core/issue/IssuesBySeverityTest.java | 52 +++++++++++++++++++ 8 files changed, 192 insertions(+), 29 deletions(-) create mode 100644 sonar-core/src/main/java/org/sonar/core/issue/IssuesBySeverity.java create mode 100644 sonar-core/src/test/java/org/sonar/core/issue/IssuesBySeverityTest.java diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/NewIssuesEmailTemplate.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/NewIssuesEmailTemplate.java index 6d4c99f7b18..37d74602045 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/NewIssuesEmailTemplate.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/NewIssuesEmailTemplate.java @@ -19,8 +19,11 @@ */ package org.sonar.plugins.core.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; @@ -28,6 +31,8 @@ import org.sonar.plugins.emailnotifications.api.EmailTemplate; 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". @@ -37,9 +42,11 @@ import java.util.Date; public class NewIssuesEmailTemplate extends EmailTemplate { private final EmailSettings settings; + private final I18n i18n; - public NewIssuesEmailTemplate(EmailSettings settings) { + public NewIssuesEmailTemplate(EmailSettings settings, I18n i18n) { this.settings = settings; + this.i18n = i18n; } @Override @@ -48,11 +55,21 @@ public class NewIssuesEmailTemplate extends EmailTemplate { return null; } String projectName = notification.getFieldValue("projectName"); - String violationsCount = notification.getFieldValue("count"); StringBuilder sb = new StringBuilder(); - sb.append("Project: ").append(projectName).append('\n'); - sb.append(violationsCount).append(" new issues").append('\n'); + sb.append("Project: ").append(projectName).append("\n\n"); + sb.append(notification.getFieldValue("count")).append(" new issues").append("\n\n"); + sb.append(" "); + for (Iterator 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)); + if (severityIterator.hasNext()) { + sb.append(" "); + } + } + sb.append('\n'); + appendFooter(sb, notification); EmailMessage message = new EmailMessage() @@ -82,4 +99,8 @@ public class NewIssuesEmailTemplate extends EmailTemplate { } } + private Locale getLocale() { + return Locale.ENGLISH; + } + } diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/SendIssueNotificationsPostJob.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/SendIssueNotificationsPostJob.java index 0cbe7456c6e..6f79d1bf85a 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/SendIssueNotificationsPostJob.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/SendIssueNotificationsPostJob.java @@ -29,6 +29,7 @@ import org.sonar.api.rules.RuleFinder; import org.sonar.batch.issue.IssueCache; import org.sonar.core.DryRunIncompatible; import org.sonar.core.issue.IssueNotifications; +import org.sonar.core.issue.IssuesBySeverity; import java.util.LinkedHashMap; import java.util.Map; @@ -55,12 +56,12 @@ public class SendIssueNotificationsPostJob implements PostJob { } private void sendNotifications(Project project) { - int newIssues = 0; + IssuesBySeverity newIssues = new IssuesBySeverity(); IssueChangeContext context = IssueChangeContext.createScan(project.getAnalysisDate()); Map shouldSentNotification = new LinkedHashMap(); for (DefaultIssue issue : issueCache.all()) { if (issue.isNew() && issue.resolution() == null) { - newIssues++; + newIssues.add(issue); } if (!issue.isNew() && issue.isChanged() && issue.mustSendNotifications()) { Rule rule = ruleFinder.findByKey(issue.ruleKey()); @@ -73,7 +74,7 @@ public class SendIssueNotificationsPostJob implements PostJob { if (!shouldSentNotification.isEmpty()) { notifications.sendChanges(shouldSentNotification, context, project, null); } - if (newIssues > 0) { + if (newIssues.size() > 0) { notifications.sendNewIssues(project, newIssues); } } diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/NewIssuesEmailTemplateTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/NewIssuesEmailTemplateTest.java index 9b3b1deb392..35d0468f1ef 100644 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/NewIssuesEmailTemplateTest.java +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/NewIssuesEmailTemplateTest.java @@ -22,26 +22,36 @@ package org.sonar.plugins.core.issue.notification; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; import org.sonar.api.config.EmailSettings; import org.sonar.api.notifications.Notification; +import org.sonar.core.i18n.I18nManager; import org.sonar.plugins.emailnotifications.api.EmailMessage; +import java.util.Locale; import java.util.TimeZone; import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Matchers.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +@RunWith(MockitoJUnitRunner.class) public class NewIssuesEmailTemplateTest { NewIssuesEmailTemplate template; TimeZone initialTimeZone = TimeZone.getDefault(); + @Mock + I18nManager i18n; + @Before public void setUp() { EmailSettings settings = mock(EmailSettings.class); when(settings.getServerBaseURL()).thenReturn("http://nemo.sonarsource.org"); - template = new NewIssuesEmailTemplate(settings); + template = new NewIssuesEmailTemplate(settings, i18n); TimeZone.setDefault(TimeZone.getTimeZone("GMT")); } @@ -73,17 +83,31 @@ public class NewIssuesEmailTemplateTest { public void shouldFormatCommentAdded() { Notification notification = new Notification("new-issues") .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("projectDate", "2010-05-18T14:50:45+0000"); + 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: new issues"); assertThat(message.getMessage()).isEqualTo("" + "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?componentRoots=org.apache%3Astruts&createdAt=2010-05-18T14%3A50%3A45%2B0000\n"); } diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/SendIssueNotificationsPostJobTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/SendIssueNotificationsPostJobTest.java index 82a1fe70ff4..6c612f78635 100644 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/SendIssueNotificationsPostJobTest.java +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/SendIssueNotificationsPostJobTest.java @@ -21,6 +21,7 @@ package org.sonar.plugins.core.issue.notification; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; @@ -35,19 +36,17 @@ import org.sonar.api.rules.RuleFinder; import org.sonar.api.utils.DateUtils; import org.sonar.batch.issue.IssueCache; import org.sonar.core.issue.IssueNotifications; +import org.sonar.core.issue.IssuesBySeverity; import java.util.Arrays; import java.util.Map; +import static org.fest.assertions.Assertions.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.argThat; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.isNull; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; @RunWith(MockitoJUnitRunner.class) public class SendIssueNotificationsPostJobTest { @@ -70,14 +69,16 @@ public class SendIssueNotificationsPostJobTest { public void should_send_notif_if_new_issues() throws Exception { when(project.getAnalysisDate()).thenReturn(DateUtils.parseDate("2013-05-18")); when(issueCache.all()).thenReturn(Arrays.asList( - new DefaultIssue().setNew(true), - new DefaultIssue().setNew(false) + new DefaultIssue().setNew(true).setSeverity("MAJOR"), + new DefaultIssue().setNew(false).setSeverity("MINOR") )); SendIssueNotificationsPostJob job = new SendIssueNotificationsPostJob(issueCache, notifications, ruleFinder); job.executeOn(project, sensorContext); - verify(notifications).sendNewIssues(project, 1); + ArgumentCaptor argument = ArgumentCaptor.forClass(IssuesBySeverity.class); + verify(notifications).sendNewIssues(eq(project), argument.capture()); + assertThat(argument.getValue().size()).isEqualTo(1); } @Test diff --git a/sonar-core/src/main/java/org/sonar/core/issue/IssueNotifications.java b/sonar-core/src/main/java/org/sonar/core/issue/IssueNotifications.java index 5619105e218..b157843c5d3 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/IssueNotifications.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/IssueNotifications.java @@ -31,6 +31,7 @@ import org.sonar.api.issue.internal.IssueChangeContext; import org.sonar.api.notifications.Notification; import org.sonar.api.notifications.NotificationManager; import org.sonar.api.resources.Project; +import org.sonar.api.rule.Severity; import org.sonar.api.rules.Rule; import org.sonar.api.utils.DateUtils; import org.sonar.core.i18n.RuleI18nManager; @@ -58,11 +59,14 @@ public class IssueNotifications implements BatchComponent, ServerComponent { this.ruleI18n = ruleI18n; } - public Notification sendNewIssues(Project project, int newIssues) { + public Notification sendNewIssues(Project project, IssuesBySeverity newIssues) { Notification notification = newNotification(project, "new-issues") - .setDefaultMessage(newIssues + " new issues on " + project.getLongName() + ".") + .setDefaultMessage(newIssues.size() + " new issues on " + project.getLongName() + ".\n") .setFieldValue("projectDate", DateUtils.formatDateTime(project.getAnalysisDate())) - .setFieldValue("count", String.valueOf(newIssues)); + .setFieldValue("count", String.valueOf(newIssues.size())); + for (String severity : Severity.ALL) { + notification.setFieldValue("count-"+ severity, String.valueOf(newIssues.issues(severity))); + } notificationsManager.scheduleForSending(notification); return notification; } @@ -146,4 +150,8 @@ public class IssueNotifications implements BatchComponent, ServerComponent { .setFieldValue("projectName", project.longName()) .setFieldValue("projectKey", project.key()); } + + private Locale getLocale() { + return Locale.ENGLISH; + } } diff --git a/sonar-core/src/main/java/org/sonar/core/issue/IssuesBySeverity.java b/sonar-core/src/main/java/org/sonar/core/issue/IssuesBySeverity.java new file mode 100644 index 00000000000..cef55ed6b4c --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/issue/IssuesBySeverity.java @@ -0,0 +1,50 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.core.issue; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; +import org.sonar.api.issue.Issue; +import org.sonar.api.rule.Severity; + +public class IssuesBySeverity { + + Multimap issuesBySeverity = ArrayListMultimap.create(); + + public IssuesBySeverity() { + this.issuesBySeverity = ArrayListMultimap.create(); + } + + public void add(Issue issue) { + for (String severity : Severity.ALL) { + if (severity.equals(issue.severity())) { + issuesBySeverity.put(severity, issue); + } + } + } + + public int issues(String severity){ + return issuesBySeverity.get(severity).size(); + } + + public int size(){ + return issuesBySeverity.values().size(); + } +} diff --git a/sonar-core/src/test/java/org/sonar/core/issue/IssueNotificationsTest.java b/sonar-core/src/test/java/org/sonar/core/issue/IssueNotificationsTest.java index 806dcea781a..18f6d635cda 100644 --- a/sonar-core/src/test/java/org/sonar/core/issue/IssueNotificationsTest.java +++ b/sonar-core/src/test/java/org/sonar/core/issue/IssueNotificationsTest.java @@ -42,6 +42,8 @@ import java.util.Date; import static org.fest.assertions.Assertions.assertThat; import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class IssueNotificationsTest { @@ -63,9 +65,13 @@ public class IssueNotificationsTest { public void should_send_new_issues() throws Exception { Date date = DateUtils.parseDateTime("2013-05-18T13:00:03+0200"); Project project = new Project("struts").setAnalysisDate(date); - Notification notification = issueNotifications.sendNewIssues(project, 42); + IssuesBySeverity issuesBySeverity = mock(IssuesBySeverity.class); + when(issuesBySeverity.size()).thenReturn(42); + when(issuesBySeverity.issues("MINOR")).thenReturn(10); + Notification notification = issueNotifications.sendNewIssues(project, issuesBySeverity); assertThat(notification.getFieldValue("count")).isEqualTo("42"); + assertThat(notification.getFieldValue("count-MINOR")).isEqualTo("10"); assertThat(DateUtils.parseDateTime(notification.getFieldValue("projectDate"))).isEqualTo(date); Mockito.verify(manager).scheduleForSending(notification); } @@ -83,8 +89,8 @@ public class IssueNotificationsTest { .setSendNotifications(true) .setComponentKey("struts:Action") .setProjectKey("struts"); - DefaultIssueQueryResult queryResult = new DefaultIssueQueryResult(Arrays. asList(issue)); - queryResult.addProjects(Arrays. asList(new Project("struts"))); + DefaultIssueQueryResult queryResult = new DefaultIssueQueryResult(Arrays.asList(issue)); + queryResult.addProjects(Arrays.asList(new Project("struts"))); Notification notification = issueNotifications.sendChanges(issue, context, queryResult).get(0); @@ -110,8 +116,8 @@ public class IssueNotificationsTest { .setAssignee("freddy") .setComponentKey("struts:Action") .setProjectKey("struts"); - DefaultIssueQueryResult queryResult = new DefaultIssueQueryResult(Arrays. asList(issue)); - queryResult.addProjects(Arrays. asList(new Project("struts"))); + DefaultIssueQueryResult queryResult = new DefaultIssueQueryResult(Arrays.asList(issue)); + queryResult.addProjects(Arrays.asList(new Project("struts"))); Notification notification = issueNotifications.sendChanges(issue, context, queryResult, "I don't know how to fix it?"); @@ -132,9 +138,9 @@ public class IssueNotificationsTest { .setSendNotifications(true) .setComponentKey("struts:Action") .setProjectKey("struts"); - DefaultIssueQueryResult queryResult = new DefaultIssueQueryResult(Arrays. asList(issue)); - queryResult.addProjects(Arrays. asList(new Project("struts"))); - queryResult.addComponents(Arrays. asList(new ResourceComponent(new File("struts:Action").setEffectiveKey("struts:Action")))); + DefaultIssueQueryResult queryResult = new DefaultIssueQueryResult(Arrays.asList(issue)); + queryResult.addProjects(Arrays.asList(new Project("struts"))); + queryResult.addComponents(Arrays.asList(new ResourceComponent(new File("struts:Action").setEffectiveKey("struts:Action")))); Notification notification = issueNotifications.sendChanges(issue, context, queryResult).get(0); @@ -155,8 +161,8 @@ public class IssueNotificationsTest { .setKey("ABCDE") .setComponentKey("struts:Action") .setProjectKey("struts"); - DefaultIssueQueryResult queryResult = new DefaultIssueQueryResult(Arrays. asList(issue)); - queryResult.addProjects(Arrays. asList(new Project("struts"))); + DefaultIssueQueryResult queryResult = new DefaultIssueQueryResult(Arrays.asList(issue)); + queryResult.addProjects(Arrays.asList(new Project("struts"))); Notification notification = issueNotifications.sendChanges(issue, context, queryResult, null); diff --git a/sonar-core/src/test/java/org/sonar/core/issue/IssuesBySeverityTest.java b/sonar-core/src/test/java/org/sonar/core/issue/IssuesBySeverityTest.java new file mode 100644 index 00000000000..80dc170ba1b --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/issue/IssuesBySeverityTest.java @@ -0,0 +1,52 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.core.issue; + +import org.junit.Test; +import org.sonar.api.issue.internal.DefaultIssue; + +import static org.fest.assertions.Assertions.assertThat; + +public class IssuesBySeverityTest { + + @Test + public void add_issue(){ + IssuesBySeverity issuesBySeverity = new IssuesBySeverity(); + issuesBySeverity.add(new DefaultIssue().setSeverity("MINOR")); + assertThat(issuesBySeverity.size()).isEqualTo(1); + } + + @Test + public void get_issues_by_severity(){ + IssuesBySeverity issuesBySeverity = new IssuesBySeverity(); + issuesBySeverity.add(new DefaultIssue().setSeverity("MINOR")); + issuesBySeverity.add(new DefaultIssue().setSeverity("MINOR")); + issuesBySeverity.add(new DefaultIssue().setSeverity("MAJOR")); + assertThat(issuesBySeverity.issues("MINOR")).isEqualTo(2); + assertThat(issuesBySeverity.issues("MAJOR")).isEqualTo(1); + } + + @Test + public void get_zero_issues_on_empty_severity(){ + IssuesBySeverity issuesBySeverity = new IssuesBySeverity(); + issuesBySeverity.add(new DefaultIssue().setSeverity("MAJOR")); + assertThat(issuesBySeverity.issues("MINOR")).isEqualTo(0); + } +} -- 2.39.5