aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorJacek <jacek.poreda@sonarsource.com>2019-12-13 11:20:52 +0100
committerSonarTech <sonartech@sonarsource.com>2020-01-13 20:46:28 +0100
commitec1f6fbe740e121bbf14d4138663e353144d54a2 (patch)
tree7b44d36d65c86626c6f7468e252cba5e4648664d /server
parentfc60b0a3b35ebab8ac47a3547ccd231537f4765a (diff)
downloadsonarqube-ec1f6fbe740e121bbf14d4138663e353144d54a2.tar.gz
sonarqube-ec1f6fbe740e121bbf14d4138663e353144d54a2.zip
SONAR-12745 Adjust `changes on my issue` email notification for Security Hotspots
Diffstat (limited to 'server')
-rw-r--r--server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/notification/NotificationFactory.java2
-rw-r--r--server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/SendIssueNotificationsStepTest.java7
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/ChangesOnMyIssuesEmailTemplate.java28
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/IssueChangesEmailTemplate.java75
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/IssuesChangesNotificationBuilder.java19
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/IssuesChangesNotificationSerializer.java12
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/RuleGroup.java65
-rw-r--r--server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/ChangesOnMyIssueNotificationHandlerTest.java8
-rw-r--r--server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/ChangesOnMyIssuesEmailTemplateTest.java289
-rw-r--r--server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/ChangesOnMyIssuesNotificationTest.java7
-rw-r--r--server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/FPOrWontFixNotificationHandlerTest.java5
-rw-r--r--server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/FPOrWontFixNotificationTest.java11
-rw-r--r--server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/FpOrWontFixEmailTemplateTest.java131
-rw-r--r--server/sonar-server-common/src/testFixtures/java/org/sonar/server/issue/notification/IssuesChangesNotificationBuilderTesting.java34
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/BulkChangeAction.java2
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/IssueUpdater.java3
16 files changed, 556 insertions, 142 deletions
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/notification/NotificationFactory.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/notification/NotificationFactory.java
index a8af2f653ab..c66877890cb 100644
--- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/notification/NotificationFactory.java
+++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/notification/NotificationFactory.java
@@ -111,7 +111,7 @@ public class NotificationFactory {
private IssuesChangesNotificationBuilder.Rule getRuleByRuleKey(RuleKey ruleKey) {
return ruleRepository.findByKey(ruleKey)
- .map(t -> new IssuesChangesNotificationBuilder.Rule(ruleKey, t.getName()))
+ .map(t -> new IssuesChangesNotificationBuilder.Rule(ruleKey, t.getType(), t.getName()))
.orElseThrow(() -> new IllegalStateException("Can not find rule " + ruleKey + " in RuleRepository"));
}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/SendIssueNotificationsStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/SendIssueNotificationsStepTest.java
index ab65a1138f1..2c34348cc4f 100644
--- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/SendIssueNotificationsStepTest.java
+++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/SendIssueNotificationsStepTest.java
@@ -92,6 +92,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
import static org.sonar.ce.task.projectanalysis.component.Component.Type;
import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
import static org.sonar.ce.task.projectanalysis.step.SendIssueNotificationsStep.NOTIF_TYPES;
@@ -133,7 +134,7 @@ public class SendIssueNotificationsStepTest extends BaseStepTest {
public DbTester db = DbTester.create(System2.INSTANCE);
private final Random random = new Random();
- private final RuleType[] RULE_TYPES_EXCEPT_HOTSPOTS = Stream.of(RuleType.values()).filter(r -> r != RuleType.SECURITY_HOTSPOT).toArray(RuleType[]::new);
+ private final RuleType[] RULE_TYPES_EXCEPT_HOTSPOTS = Stream.of(RuleType.values()).filter(r -> r != SECURITY_HOTSPOT).toArray(RuleType[]::new);
private final RuleType randomRuleType = RULE_TYPES_EXCEPT_HOTSPOTS[random.nextInt(RULE_TYPES_EXCEPT_HOTSPOTS.length)];
@SuppressWarnings("unchecked")
private Class<Map<String, UserDto>> assigneeCacheType = (Class<Map<String, UserDto>>) (Object) Map.class;
@@ -460,7 +461,7 @@ public class SendIssueNotificationsStepTest extends BaseStepTest {
.isEmpty();
Tuple[] expected = stream(users).map(user -> tuple(user.getUuid(), user.getUuid(), user.getId(), user.getLogin())).toArray(Tuple[]::new);
assertThat(cache.entrySet())
- .extracting(t -> t.getKey(), t -> t.getValue().getUuid(), t -> t.getValue().getId(), t -> t.getValue().getLogin())
+ .extracting(Map.Entry::getKey, t -> t.getValue().getUuid(), t -> t.getValue().getId(), t -> t.getValue().getLogin())
.containsOnly(expected);
}
@@ -488,7 +489,7 @@ public class SendIssueNotificationsStepTest extends BaseStepTest {
}
@Test
- public void dont_send_issues_change_notification_for_hotspot() {
+ public void do_not_send_new_issues_notifications_for_hotspot() {
UserDto user = db.users().insertUser();
ComponentDto project = newPrivateProjectDto(newOrganizationDto()).setDbKey(PROJECT.getDbKey()).setLongName(PROJECT.getName());
ComponentDto file = newFileDto(project).setDbKey(FILE.getDbKey()).setLongName(FILE.getName());
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/ChangesOnMyIssuesEmailTemplate.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/ChangesOnMyIssuesEmailTemplate.java
index a1424caa0b8..812551a41cc 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/ChangesOnMyIssuesEmailTemplate.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/ChangesOnMyIssuesEmailTemplate.java
@@ -21,7 +21,6 @@ package org.sonar.server.issue.notification;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.SetMultimap;
-import java.util.Collection;
import java.util.List;
import javax.annotation.CheckForNull;
import org.sonar.api.config.EmailSettings;
@@ -34,9 +33,13 @@ import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.User
import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.UserChange;
import static com.google.common.base.Preconditions.checkState;
+import static java.util.function.Function.identity;
import static org.sonar.api.issue.Issue.STATUS_CLOSED;
import static org.sonar.api.issue.Issue.STATUS_OPEN;
import static org.sonar.core.util.stream.MoreCollectors.index;
+import static org.sonar.server.issue.notification.RuleGroup.ISSUES;
+import static org.sonar.server.issue.notification.RuleGroup.SECURITY_HOTSPOTS;
+import static org.sonar.server.issue.notification.RuleGroup.resolveGroup;
/**
* Creates email message for notification "Changes on my issues".
@@ -109,33 +112,28 @@ public class ChangesOnMyIssuesEmailTemplate extends IssueChangesEmailTemplate {
return new EmailMessage()
.setFrom(user.getName().orElse(user.getLogin()))
.setMessageId("changes-on-my-issues")
- .setSubject("A manual update has changed some of your issues")
+ .setSubject("A manual update has changed some of your issues/hotspots")
.setHtmlMessage(buildMultiProjectMessage(notification));
}
private String buildMultiProjectMessage(ChangesOnMyIssuesNotification notification) {
+ ListMultimap<RuleGroup, ChangedIssue> issuesAndHotspots = notification.getChangedIssues().values().stream()
+ .collect(index(changedIssue -> resolveGroup(changedIssue.getRule().getRuleType()), identity()));
+
+ List<ChangedIssue> issues = issuesAndHotspots.get(ISSUES);
+ List<ChangedIssue> hotspots = issuesAndHotspots.get(SECURITY_HOTSPOTS);
+
StringBuilder sb = new StringBuilder();
paragraph(sb, s -> s.append("Hi,"));
- paragraph(sb, s -> {
- SetMultimap<Project, ChangedIssue> changedIssues = notification.getChangedIssues();
- s.append("A manual change has updated ").append(issuesOrAnIssue(changedIssues))
- .append(" assigned to you:");
- });
+ paragraph(sb, s -> s.append("A manual change has updated ").append(RuleGroup.formatIssuesOrHotspots(issues, hotspots)).append(" assigned to you:"));
- addIssuesByProjectThenRule(sb, notification.getChangedIssues());
+ addIssuesAndHotspotsByProjectThenRule(sb, notification.getChangedIssues());
addFooter(sb, NOTIFICATION_NAME_I18N_KEY);
return sb.toString();
}
- private static String issueOrIssues(Collection<?> collection) {
- if (collection.size() > 1) {
- return "issues";
- }
- return "issue";
- }
-
private static String issuesOrAnIssue(SetMultimap<Project, ChangedIssue> changedIssues) {
if (changedIssues.size() > 1) {
return "issues";
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/IssueChangesEmailTemplate.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/IssueChangesEmailTemplate.java
index 4774ba5663f..53d4f8650e5 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/IssueChangesEmailTemplate.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/IssueChangesEmailTemplate.java
@@ -31,10 +31,13 @@ import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
+import java.util.Set;
import java.util.SortedSet;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
+import javax.annotation.Nullable;
import org.sonar.api.config.EmailSettings;
+import org.sonar.api.rules.RuleType;
import org.sonar.core.i18n.I18n;
import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.ChangedIssue;
import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.Project;
@@ -42,7 +45,13 @@ import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.Rule
import static java.net.URLEncoder.encode;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.function.Function.identity;
import static org.sonar.core.util.stream.MoreCollectors.index;
+import static org.sonar.server.issue.notification.RuleGroup.ISSUES;
+import static org.sonar.server.issue.notification.RuleGroup.SECURITY_HOTSPOTS;
+import static org.sonar.server.issue.notification.RuleGroup.formatIssueOrHotspot;
+import static org.sonar.server.issue.notification.RuleGroup.formatIssuesOrHotspots;
+import static org.sonar.server.issue.notification.RuleGroup.resolveGroup;
public abstract class IssueChangesEmailTemplate implements EmailTemplate {
@@ -50,6 +59,7 @@ public abstract class IssueChangesEmailTemplate implements EmailTemplate {
private static final Comparator<Project> PROJECT_COMPARATOR = Comparator.comparing(Project::getProjectName)
.thenComparing(t -> t.getBranchName().orElse(""));
private static final Comparator<ChangedIssue> CHANGED_ISSUE_KEY_COMPARATOR = Comparator.comparing(ChangedIssue::getKey, Comparator.naturalOrder());
+
/**
* Assuming:
* <ul>
@@ -97,6 +107,38 @@ public abstract class IssueChangesEmailTemplate implements EmailTemplate {
});
}
+ void addIssuesAndHotspotsByProjectThenRule(StringBuilder sb, SetMultimap<Project, ChangedIssue> issuesByProject) {
+ issuesByProject.keySet().stream()
+ .sorted(PROJECT_COMPARATOR)
+ .forEach(project -> {
+ String encodedProjectParams = toUrlParams(project);
+ paragraph(sb, s -> toString(s, project));
+
+ Set<ChangedIssue> changedIssues = issuesByProject.get(project);
+ ListMultimap<RuleGroup, ChangedIssue> issuesAndHotspots = changedIssues.stream()
+ .collect(index(changedIssue -> resolveGroup(changedIssue.getRule().getRuleType()), identity()));
+
+ List<ChangedIssue> issues = issuesAndHotspots.get(ISSUES);
+ List<ChangedIssue> hotspots = issuesAndHotspots.get(SECURITY_HOTSPOTS);
+
+ boolean hasSecurityHotspots = !hotspots.isEmpty();
+ boolean hasOtherIssues = !issues.isEmpty();
+
+ if (hasOtherIssues) {
+ addIssuesByRule(sb, issues, projectIssuePageHref(encodedProjectParams));
+ }
+
+ if (hasSecurityHotspots && hasOtherIssues) {
+ paragraph(sb, stringBuilder -> {
+ });
+ }
+
+ if (hasSecurityHotspots) {
+ addIssuesByRule(sb, hotspots, securityHotspotPageHref(encodedProjectParams));
+ }
+ });
+ }
+
void addIssuesByRule(StringBuilder sb, Collection<ChangedIssue> changedIssues, BiConsumer<StringBuilder, Collection<ChangedIssue>> issuePageHref) {
ListMultimap<Rule, ChangedIssue> issuesByRule = changedIssues.stream()
.collect(index(ChangedIssue::getRule, t -> t));
@@ -114,21 +156,22 @@ public abstract class IssueChangesEmailTemplate implements EmailTemplate {
Collection<ChangedIssue> issues = issuesByRule.get(rule);
sb.append("<li>").append("Rule ").append(" <em>").append(rule.getName()).append("</em> - ");
- appendIssueLinks(sb, issuePageHref, issues);
+ appendIssueLinks(sb, issuePageHref, issues, rule.getRuleType());
sb.append("</li>");
}
sb.append("</ul>");
}
- private static void appendIssueLinks(StringBuilder sb, BiConsumer<StringBuilder, Collection<ChangedIssue>> issuePageHref, Collection<ChangedIssue> issues) {
+ private static void appendIssueLinks(StringBuilder sb, BiConsumer<StringBuilder, Collection<ChangedIssue>> issuePageHref, Collection<ChangedIssue> issues,
+ @Nullable RuleType ruleType) {
SortedSet<ChangedIssue> sortedIssues = ImmutableSortedSet.copyOf(CHANGED_ISSUE_KEY_COMPARATOR, issues);
int issueCount = issues.size();
if (issueCount == 1) {
- link(sb, s -> issuePageHref.accept(s, sortedIssues), s -> s.append("See the single issue"));
+ link(sb, s -> issuePageHref.accept(s, sortedIssues), s -> s.append("See the single ").append(formatIssueOrHotspot(ruleType)));
} else if (issueCount <= MAX_ISSUES_BY_LINK) {
- link(sb, s -> issuePageHref.accept(s, sortedIssues), s -> s.append("See all ").append(issueCount).append(" issues"));
+ link(sb, s -> issuePageHref.accept(s, sortedIssues), s -> s.append("See all ").append(issueCount).append(" ").append(formatIssuesOrHotspots(ruleType)));
} else {
- sb.append("See issues");
+ sb.append("See ").append(formatIssuesOrHotspots(ruleType));
List<List<ChangedIssue>> issueGroups = Lists.partition(ImmutableList.copyOf(sortedIssues), MAX_ISSUES_BY_LINK);
Iterator<List<ChangedIssue>> issueGroupsIterator = issueGroups.iterator();
int[] groupIndex = new int[] {0};
@@ -160,6 +203,21 @@ public abstract class IssueChangesEmailTemplate implements EmailTemplate {
};
}
+ BiConsumer<StringBuilder, Collection<ChangedIssue>> securityHotspotPageHref(String projectParams) {
+ return (s, issues) -> {
+ s.append(settings.getServerBaseURL()).append("/security_hotspots?").append(projectParams)
+ .append("&hotspots=");
+
+ Iterator<ChangedIssue> issueIterator = issues.iterator();
+ while (issueIterator.hasNext()) {
+ s.append(urlEncode(issueIterator.next().getKey()));
+ if (issueIterator.hasNext()) {
+ s.append(URL_ENCODED_COMMA);
+ }
+ }
+ };
+ }
+
private static Consumer<StringBuilder> issueGroupLabel(StringBuilder sb, int[] groupIndex, List<ChangedIssue> issueGroup) {
return s -> {
int firstIssueNumber = (groupIndex[0] * MAX_ISSUES_BY_LINK) + 1;
@@ -207,4 +265,11 @@ public abstract class IssueChangesEmailTemplate implements EmailTemplate {
}
}
+ protected static String issueOrIssues(Collection<?> collection) {
+ if (collection.size() > 1) {
+ return "issues";
+ }
+ return "issue";
+ }
+
}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/IssuesChangesNotificationBuilder.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/IssuesChangesNotificationBuilder.java
index c1508db355c..5779d4989b3 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/IssuesChangesNotificationBuilder.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/IssuesChangesNotificationBuilder.java
@@ -27,6 +27,7 @@ import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.RuleType;
import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Objects.requireNonNull;
@@ -233,10 +234,16 @@ public class IssuesChangesNotificationBuilder {
@Immutable
public static final class Rule {
private final RuleKey key;
+ private final RuleType ruleType;
private final String name;
- public Rule(RuleKey key, String name) {
+ public Rule(RuleKey key, @Nullable String ruleType, String name) {
+ this(key, ruleType != null ? RuleType.valueOf(ruleType) : null, name);
+ }
+
+ public Rule(RuleKey key, @Nullable RuleType ruleType, String name) {
this.key = requireNonNull(key, KEY_CANT_BE_NULL_MESSAGE);
+ this.ruleType = ruleType;
this.name = requireNonNull(name, "name can't be null");
}
@@ -244,6 +251,11 @@ public class IssuesChangesNotificationBuilder {
return key;
}
+ @CheckForNull
+ public RuleType getRuleType() {
+ return ruleType;
+ }
+
public String getName() {
return name;
}
@@ -257,18 +269,19 @@ public class IssuesChangesNotificationBuilder {
return false;
}
Rule that = (Rule) o;
- return key.equals(that.key) && name.equals(that.name);
+ return key.equals(that.key) && ruleType.equals(that.ruleType) && name.equals(that.name);
}
@Override
public int hashCode() {
- return Objects.hash(key, name);
+ return Objects.hash(key, ruleType, name);
}
@Override
public String toString() {
return "Rule{" +
"key=" + key +
+ ", type=" + ruleType +
", name='" + name + '\'' +
'}';
}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/IssuesChangesNotificationSerializer.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/IssuesChangesNotificationSerializer.java
index 1c5c652e8e0..4b172445ab1 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/IssuesChangesNotificationSerializer.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/IssuesChangesNotificationSerializer.java
@@ -35,6 +35,7 @@ import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.User
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
+import static java.util.Optional.ofNullable;
import static org.sonar.core.util.stream.MoreCollectors.toSet;
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
@@ -44,6 +45,7 @@ public class IssuesChangesNotificationSerializer {
private static final String FIELD_CHANGE_AUTHOR_UUID = "change.author.uuid";
private static final String FIELD_CHANGE_AUTHOR_LOGIN = "change.author.login";
private static final String FIELD_CHANGE_AUTHOR_NAME = "change.author.name";
+ private static final String FIELD_PREFIX_RULES = "rules.";
public IssuesChangesNotification serialize(IssuesChangesNotificationBuilder builder) {
IssuesChangesNotification res = new IssuesChangesNotification();
@@ -160,7 +162,10 @@ public class IssuesChangesNotificationSerializer {
issues.stream()
.map(ChangedIssue::getRule)
.collect(Collectors.toSet())
- .forEach(rule -> res.setFieldValue("rules." + rule.getKey(), rule.getName()));
+ .forEach(rule -> {
+ res.setFieldValue(FIELD_PREFIX_RULES + rule.getKey(), rule.getName());
+ ofNullable(rule.getRuleType()).ifPresent(ruleType -> res.setFieldValue(FIELD_PREFIX_RULES + rule.getKey() + ".type", rule.getRuleType().name()));
+ });
}
private static Map<RuleKey, Rule> readRules(IssuesChangesNotification notification, List<Issue> issues) {
@@ -173,10 +178,11 @@ public class IssuesChangesNotificationSerializer {
}
private static Rule readRule(IssuesChangesNotification notification, RuleKey ruleKey) {
- String fieldName = "rules." + ruleKey;
+ String fieldName = FIELD_PREFIX_RULES + ruleKey;
String ruleName = notification.getFieldValue(fieldName);
+ String ruleType = notification.getFieldValue(fieldName + ".type");
checkState(ruleName != null, "can not find field %s", ruleKey);
- return new Rule(ruleKey, ruleName);
+ return new Rule(ruleKey, ruleType, ruleName);
}
private static void serializeProjects(IssuesChangesNotification res, Set<ChangedIssue> issues) {
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/RuleGroup.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/RuleGroup.java
new file mode 100644
index 00000000000..2bce7b3f996
--- /dev/null
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/RuleGroup.java
@@ -0,0 +1,65 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.issue.notification;
+
+import java.util.Collection;
+import javax.annotation.Nullable;
+import org.sonar.api.rules.RuleType;
+
+import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
+
+enum RuleGroup {
+ SECURITY_HOTSPOTS,
+ ISSUES;
+
+ static RuleGroup resolveGroup(@Nullable RuleType ruleType) {
+ return SECURITY_HOTSPOT.equals(ruleType) ? SECURITY_HOTSPOTS : ISSUES;
+ }
+
+ static String formatIssuesOrHotspots(Collection<?> issues, Collection<?> hotspots) {
+ if (!issues.isEmpty() && !hotspots.isEmpty()) {
+ return "issues/hotspots";
+ }
+ if (issues.size() == 1) {
+ return "an issue";
+ }
+ if (issues.size() > 1) {
+ return "issues";
+ }
+ if (hotspots.size() == 1) {
+ return "a hotspot";
+ }
+ return "hotspots";
+ }
+
+ static String formatIssueOrHotspot(@Nullable RuleType ruleType) {
+ if (SECURITY_HOTSPOT.equals(ruleType)) {
+ return "hotspot";
+ }
+ return "issue";
+ }
+
+ static String formatIssuesOrHotspots(@Nullable RuleType ruleType) {
+ if (SECURITY_HOTSPOT.equals(ruleType)) {
+ return "hotspots";
+ }
+ return "issues";
+ }
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/ChangesOnMyIssueNotificationHandlerTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/ChangesOnMyIssueNotificationHandlerTest.java
index da9a10978fd..e18f8b3be9e 100644
--- a/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/ChangesOnMyIssueNotificationHandlerTest.java
+++ b/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/ChangesOnMyIssueNotificationHandlerTest.java
@@ -36,7 +36,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
-import org.sonar.api.rule.RuleKey;
import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.AnalysisChange;
import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.Change;
@@ -61,6 +60,7 @@ import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.sonar.core.util.stream.MoreCollectors.index;
import static org.sonar.core.util.stream.MoreCollectors.unorderedIndex;
+import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.newRandomNotAHotspotRule;
import static org.sonar.server.notification.NotificationDispatcherMetadata.GLOBAL_NOTIFICATION;
import static org.sonar.server.notification.NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION;
import static org.sonar.server.notification.NotificationManager.SubscriberPermissionsOnProject.ALL_MUST_HAVE_ROLE_USER;
@@ -354,10 +354,10 @@ public class ChangesOnMyIssueNotificationHandlerTest {
.collect(toSet());
when(notificationManager.findSubscribedEmailRecipients(
CHANGE_ON_MY_ISSUES_DISPATCHER_KEY, project1.getKey(), ImmutableSet.of(assignee1.getLogin()), ALL_MUST_HAVE_ROLE_USER))
- .thenReturn(ImmutableSet.of(emailRecipientOf(assignee1.getLogin())));
+ .thenReturn(ImmutableSet.of(emailRecipientOf(assignee1.getLogin())));
when(notificationManager.findSubscribedEmailRecipients(
CHANGE_ON_MY_ISSUES_DISPATCHER_KEY, project2.getKey(), ImmutableSet.of(assignee2.getLogin()), ALL_MUST_HAVE_ROLE_USER))
- .thenReturn(ImmutableSet.of(emailRecipientOf(assignee2.getLogin())));
+ .thenReturn(ImmutableSet.of(emailRecipientOf(assignee2.getLogin())));
int deliveredCount = new Random().nextInt(100);
when(emailNotificationChannel.deliverAll(anySet())).thenReturn(deliveredCount);
@@ -437,7 +437,7 @@ public class ChangesOnMyIssueNotificationHandlerTest {
}
private static Rule newRule() {
- return new Rule(RuleKey.of(randomAlphabetic(3), randomAlphabetic(4)), randomAlphabetic(5));
+ return newRandomNotAHotspotRule(randomAlphabetic(5));
}
private static Set<IssuesChangesNotification> randomSetOfNotifications(@Nullable String projectKey, @Nullable String assignee, @Nullable String changeAuthor) {
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/ChangesOnMyIssuesEmailTemplateTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/ChangesOnMyIssuesEmailTemplateTest.java
index 6017143a11c..ad7343ccced 100644
--- a/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/ChangesOnMyIssuesEmailTemplateTest.java
+++ b/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/ChangesOnMyIssuesEmailTemplateTest.java
@@ -32,11 +32,13 @@ import java.util.Set;
import java.util.function.Function;
import java.util.stream.IntStream;
import java.util.stream.Stream;
+import org.elasticsearch.common.util.set.Sets;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.sonar.api.config.EmailSettings;
import org.sonar.api.notifications.Notification;
+import org.sonar.api.rules.RuleType;
import org.sonar.core.i18n.I18n;
import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.AnalysisChange;
import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.Change;
@@ -60,14 +62,24 @@ import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
import static org.sonar.api.issue.Issue.STATUS_OPEN;
import static org.sonar.api.issue.Issue.STATUS_REOPENED;
import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
+import static org.sonar.api.issue.Issue.STATUS_REVIEWED;
+import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW;
+import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
+import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.newAnalysisChange;
import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.newBranch;
import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.newChangedIssue;
import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.newProject;
+import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.newRandomNotAHotspotRule;
import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.newRule;
+import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.newSecurityHotspotRule;
+import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.newUserChange;
+import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.randomRuleTypeHotspotExcluded;
@RunWith(DataProviderRunner.class)
public class ChangesOnMyIssuesEmailTemplateTest {
private static final String[] ISSUE_STATUSES = {STATUS_OPEN, STATUS_RESOLVED, STATUS_CONFIRMED, STATUS_REOPENED, STATUS_CLOSED};
+ private static final String[] SECURITY_HOTSPOTS_STATUSES = {STATUS_TO_REVIEW, STATUS_REVIEWED};
+
@org.junit.Rule
public ExpectedException expectedException = ExpectedException.none();
@@ -84,7 +96,7 @@ public class ChangesOnMyIssuesEmailTemplateTest {
@Test
public void formats_fails_with_ISE_if_change_from_Analysis_and_no_issue() {
- AnalysisChange analysisChange = IssuesChangesNotificationBuilderTesting.newAnalysisChange();
+ AnalysisChange analysisChange = newAnalysisChange();
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("changedIssues can't be empty");
@@ -95,9 +107,9 @@ public class ChangesOnMyIssuesEmailTemplateTest {
@Test
public void format_sets_message_id_with_project_key_of_first_issue_in_set_when_change_from_Analysis() {
Set<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(4))
- .mapToObj(i -> newChangedIssue(i + "", randomValidStatus(), newProject("prj_" + i), newRule("rule_" + i)))
+ .mapToObj(i -> newChangedIssue(i + "", randomValidStatus(), newProject("prj_" + i), newRandomNotAHotspotRule("rule_" + i)))
.collect(toSet());
- AnalysisChange analysisChange = IssuesChangesNotificationBuilderTesting.newAnalysisChange();
+ AnalysisChange analysisChange = newAnalysisChange();
EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(analysisChange, changedIssues));
@@ -107,7 +119,7 @@ public class ChangesOnMyIssuesEmailTemplateTest {
@Test
public void format_sets_subject_with_project_name_of_first_issue_in_set_when_change_from_Analysis() {
Set<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(4))
- .mapToObj(i -> newChangedIssue(i + "", randomValidStatus(), newProject("prj_" + i), newRule("rule_" + i)))
+ .mapToObj(i -> newChangedIssue(i + "", randomValidStatus(), newProject("prj_" + i), newRandomNotAHotspotRule("rule_" + i)))
.collect(toSet());
AnalysisChange analysisChange = IssuesChangesNotificationBuilderTesting.newAnalysisChange();
@@ -120,9 +132,9 @@ public class ChangesOnMyIssuesEmailTemplateTest {
@Test
public void format_sets_subject_with_project_name_and_branch_name_of_first_issue_in_set_when_change_from_Analysis() {
Set<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(4))
- .mapToObj(i -> newChangedIssue(i + "", randomValidStatus(), newBranch("prj_" + i, "br_" + i), newRule("rule_" + i)))
+ .mapToObj(i -> newChangedIssue(i + "", randomValidStatus(), newBranch("prj_" + i, "br_" + i), newRandomNotAHotspotRule("rule_" + i)))
.collect(toSet());
- AnalysisChange analysisChange = IssuesChangesNotificationBuilderTesting.newAnalysisChange();
+ AnalysisChange analysisChange = newAnalysisChange();
EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(analysisChange, changedIssues));
@@ -133,9 +145,9 @@ public class ChangesOnMyIssuesEmailTemplateTest {
@Test
public void format_set_html_message_with_header_dealing_with_plural_when_change_from_Analysis() {
Set<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(4))
- .mapToObj(i -> newChangedIssue(i + "", randomValidStatus(), newProject("prj_" + i), newRule("rule_" + i)))
+ .mapToObj(i -> newChangedIssue(i + "", randomValidStatus(), newProject("prj_" + i), newRandomNotAHotspotRule("rule_" + i)))
.collect(toSet());
- AnalysisChange analysisChange = IssuesChangesNotificationBuilderTesting.newAnalysisChange();
+ AnalysisChange analysisChange = newAnalysisChange();
EmailMessage singleIssueMessage = underTest.format(new ChangesOnMyIssuesNotification(analysisChange, changedIssues.stream().limit(1).collect(toSet())));
EmailMessage multiIssueMessage = underTest.format(new ChangesOnMyIssuesNotification(analysisChange, changedIssues));
@@ -151,9 +163,9 @@ public class ChangesOnMyIssuesEmailTemplateTest {
@Test
public void format_sets_static_message_id_when_change_from_User() {
Set<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(4))
- .mapToObj(i -> newChangedIssue(i + "", randomValidStatus(), newProject("prj_" + i), newRule("rule_" + i)))
+ .mapToObj(i -> newChangedIssue(i + "", randomValidStatus(), newProject("prj_" + i), newRandomNotAHotspotRule("rule_" + i)))
.collect(toSet());
- UserChange userChange = IssuesChangesNotificationBuilderTesting.newUserChange();
+ UserChange userChange = newUserChange();
EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(userChange, changedIssues));
@@ -163,21 +175,21 @@ public class ChangesOnMyIssuesEmailTemplateTest {
@Test
public void format_sets_static_subject_when_change_from_User() {
Set<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(4))
- .mapToObj(i -> newChangedIssue(i + "", randomValidStatus(), newProject("prj_" + i), newRule("rule_" + i)))
+ .mapToObj(i -> newChangedIssue(i + "", randomValidStatus(), newProject("prj_" + i), newRandomNotAHotspotRule("rule_" + i)))
.collect(toSet());
- UserChange userChange = IssuesChangesNotificationBuilderTesting.newUserChange();
+ UserChange userChange = newUserChange();
EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(userChange, changedIssues));
- assertThat(emailMessage.getSubject()).isEqualTo("A manual update has changed some of your issues");
+ assertThat(emailMessage.getSubject()).isEqualTo("A manual update has changed some of your issues/hotspots");
}
@Test
- public void format_set_html_message_with_header_dealing_with_plural_when_change_from_User() {
+ public void format_set_html_message_with_header_dealing_with_plural_issues_when_change_from_User() {
Set<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(4))
- .mapToObj(i -> newChangedIssue(i + "", randomValidStatus(), newProject("prj_" + i), newRule("rule_" + i)))
+ .mapToObj(i -> newChangedIssue(i + "", randomValidStatus(), newProject("prj_" + i), newRandomNotAHotspotRule("rule_" + i)))
.collect(toSet());
- UserChange userChange = IssuesChangesNotificationBuilderTesting.newUserChange();
+ UserChange userChange = newUserChange();
EmailMessage singleIssueMessage = underTest.format(new ChangesOnMyIssuesNotification(
userChange, changedIssues.stream().limit(1).collect(toSet())));
@@ -196,25 +208,90 @@ public class ChangesOnMyIssuesEmailTemplateTest {
}
@Test
+ public void format_set_html_message_with_header_dealing_with_plural_security_hotspots_when_change_from_User() {
+ Set<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(4))
+ .mapToObj(i -> newChangedIssue(i + "", randomValidStatus(), newProject("prj_" + i), newSecurityHotspotRule("rule_" + i)))
+ .collect(toSet());
+ UserChange userChange = newUserChange();
+
+ EmailMessage singleIssueMessage = underTest.format(new ChangesOnMyIssuesNotification(
+ userChange, changedIssues.stream().limit(1).collect(toSet())));
+ EmailMessage multiIssueMessage = underTest.format(new ChangesOnMyIssuesNotification(userChange, changedIssues));
+
+ HtmlFragmentAssert.assertThat(singleIssueMessage.getMessage())
+ .hasParagraph("Hi,")
+ .withoutLink()
+ .hasParagraph("A manual change has updated a hotspot assigned to you:")
+ .withoutLink();
+ HtmlFragmentAssert.assertThat(multiIssueMessage.getMessage())
+ .hasParagraph("Hi,")
+ .withoutLink()
+ .hasParagraph("A manual change has updated hotspots assigned to you:")
+ .withoutLink();
+ }
+
+ @Test
+ public void format_set_html_message_with_header_dealing_with_plural_security_hotspots_and_issues_when_change_from_User() {
+ Set<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(4))
+ .mapToObj(i -> newChangedIssue(i + "", randomValidStatus(), newProject("prj_" + i), newRandomNotAHotspotRule("rule_" + i)))
+ .collect(toSet());
+
+ Set<ChangedIssue> changedHotspots = IntStream.range(0, 2 + new Random().nextInt(4))
+ .mapToObj(i -> newChangedIssue(i + "", randomValidStatus(), newProject("prj_" + i), newSecurityHotspotRule("rule_" + i)))
+ .collect(toSet());
+
+ Set<ChangedIssue> issuesAndHotspots = Sets.union(changedIssues, changedHotspots);
+
+ UserChange userChange = newUserChange();
+
+ EmailMessage multiIssueMessage = underTest.format(new ChangesOnMyIssuesNotification(userChange, issuesAndHotspots));
+
+ HtmlFragmentAssert.assertThat(multiIssueMessage.getMessage())
+ .hasParagraph("Hi,")
+ .withoutLink()
+ .hasParagraph("A manual change has updated issues/hotspots assigned to you:")
+ .withoutLink();
+ }
+
+ @Test
@UseDataProvider("issueStatuses")
- public void format_set_html_message_with_footer_when_change_from_user(String issueStatus) {
- UserChange userChange = IssuesChangesNotificationBuilderTesting.newUserChange();
+ public void format_set_html_message_with_footer_when_issue_change_from_user(String issueStatus) {
+ UserChange userChange = newUserChange();
format_set_html_message_with_footer(userChange, issueStatus, c -> c
// skip content
- .hasParagraph() // open/closed issue
- .hasList() // rule list
- );
+ .hasParagraph() // skip project header
+ .hasList(), // rule list,
+ randomRuleTypeHotspotExcluded());
}
@Test
@UseDataProvider("issueStatuses")
- public void format_set_html_message_with_footer_when_change_from_analysis(String issueStatus) {
- AnalysisChange analysisChange = IssuesChangesNotificationBuilderTesting.newAnalysisChange();
+ public void format_set_html_message_with_footer_when_issue_change_from_analysis(String issueStatus) {
+ AnalysisChange analysisChange = newAnalysisChange();
format_set_html_message_with_footer(analysisChange, issueStatus, c -> c
- // skip content
.hasParagraph() // status
- .hasList() // rule list
- );
+ .hasList(), // rule list,
+ randomRuleTypeHotspotExcluded());
+ }
+
+ @Test
+ @UseDataProvider("securityHotspotsStatuses")
+ public void format_set_html_message_with_footer_when_security_hotspot_change_from_analysis(String securityHotspotStatus) {
+ AnalysisChange analysisChange = newAnalysisChange();
+ format_set_html_message_with_footer(analysisChange, securityHotspotStatus, c -> c
+ .hasParagraph()
+ .hasList(), // rule list
+ SECURITY_HOTSPOT);
+ }
+
+ @Test
+ @UseDataProvider("securityHotspotsStatuses")
+ public void format_set_html_message_with_footer_when_security_hotspot_change_from_user(String securityHotspotStatus) {
+ UserChange userChange = newUserChange();
+ format_set_html_message_with_footer(userChange, securityHotspotStatus, c -> c
+ .hasParagraph()
+ .hasList(), // rule list
+ SECURITY_HOTSPOT);
}
@DataProvider
@@ -224,14 +301,21 @@ public class ChangesOnMyIssuesEmailTemplateTest {
.toArray(Object[][]::new);
}
- private void format_set_html_message_with_footer(Change change, String issueStatus, Function<HtmlParagraphAssert, HtmlListAssert> skipContent) {
+ @DataProvider
+ public static Object[][] securityHotspotsStatuses() {
+ return Arrays.stream(SECURITY_HOTSPOTS_STATUSES)
+ .map(t -> new Object[] {t})
+ .toArray(Object[][]::new);
+ }
+
+ private void format_set_html_message_with_footer(Change change, String issueStatus, Function<HtmlParagraphAssert, HtmlListAssert> skipContent, RuleType ruleType) {
String wordingNotification = randomAlphabetic(20);
String host = randomAlphabetic(15);
when(i18n.message(Locale.ENGLISH, "notification.dispatcher.ChangesOnMyIssue", "notification.dispatcher.ChangesOnMyIssue"))
.thenReturn(wordingNotification);
when(emailSettings.getServerBaseURL()).thenReturn(host);
Project project = newProject("foo");
- Rule rule = newRule("bar");
+ Rule rule = newRule("bar", ruleType);
Set<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(4))
.mapToObj(i -> newChangedIssue(i + "", issueStatus, project, rule))
.collect(toSet());
@@ -260,11 +344,11 @@ public class ChangesOnMyIssuesEmailTemplateTest {
@Test
public void format_set_html_message_with_issues_grouped_by_status_closed_or_any_other_when_change_from_analysis() {
Project project = newProject("foo");
- Rule rule = newRule("bar");
+ Rule rule = newRandomNotAHotspotRule("bar");
Set<ChangedIssue> changedIssues = Arrays.stream(ISSUE_STATUSES)
.map(status -> newChangedIssue(status + "", status, project, rule))
.collect(toSet());
- AnalysisChange analysisChange = IssuesChangesNotificationBuilderTesting.newAnalysisChange();
+ AnalysisChange analysisChange = newAnalysisChange();
EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(analysisChange, changedIssues));
@@ -282,16 +366,16 @@ public class ChangesOnMyIssuesEmailTemplateTest {
}
@Test
- public void format_set_html_message_with_status_title_handles_plural_when_change_from_analysis() {
+ public void format_set_html_message_with_issue_status_title_handles_plural_when_change_from_analysis() {
Project project = newProject("foo");
- Rule rule = newRule("bar");
+ Rule rule = newRandomNotAHotspotRule("bar");
Set<ChangedIssue> closedIssues = IntStream.range(0, 2 + new Random().nextInt(5))
.mapToObj(status -> newChangedIssue(status + "", STATUS_CLOSED, project, rule))
.collect(toSet());
Set<ChangedIssue> openIssues = IntStream.range(0, 2 + new Random().nextInt(5))
.mapToObj(status -> newChangedIssue(status + "", STATUS_OPEN, project, rule))
.collect(toSet());
- AnalysisChange analysisChange = IssuesChangesNotificationBuilderTesting.newAnalysisChange();
+ AnalysisChange analysisChange = newAnalysisChange();
EmailMessage closedIssuesMessage = underTest.format(new ChangesOnMyIssuesNotification(analysisChange, closedIssues));
EmailMessage openIssuesMessage = underTest.format(new ChangesOnMyIssuesNotification(analysisChange, openIssues));
@@ -313,8 +397,8 @@ public class ChangesOnMyIssuesEmailTemplateTest {
Project project = newProject("1");
String ruleName = randomAlphabetic(8);
String host = randomAlphabetic(15);
- ChangedIssue changedIssue = newChangedIssue("key", randomValidStatus(), project, ruleName);
- AnalysisChange analysisChange = IssuesChangesNotificationBuilderTesting.newAnalysisChange();
+ ChangedIssue changedIssue = newChangedIssue("key", randomValidStatus(), project, ruleName, randomRuleTypeHotspotExcluded());
+ AnalysisChange analysisChange = newAnalysisChange();
when(emailSettings.getServerBaseURL()).thenReturn(host);
EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(analysisChange, ImmutableSet.of(changedIssue)));
@@ -333,8 +417,8 @@ public class ChangesOnMyIssuesEmailTemplateTest {
Project project = newProject("1");
String ruleName = randomAlphabetic(8);
String host = randomAlphabetic(15);
- ChangedIssue changedIssue = newChangedIssue("key", randomValidStatus(), project, ruleName);
- UserChange userChange = IssuesChangesNotificationBuilderTesting.newUserChange();
+ ChangedIssue changedIssue = newChangedIssue("key", randomValidStatus(), project, ruleName, randomRuleTypeHotspotExcluded());
+ UserChange userChange = newUserChange();
when(emailSettings.getServerBaseURL()).thenReturn(host);
EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(userChange, ImmutableSet.of(changedIssue)));
@@ -355,8 +439,8 @@ public class ChangesOnMyIssuesEmailTemplateTest {
String ruleName = randomAlphabetic(8);
String host = randomAlphabetic(15);
String key = "key";
- ChangedIssue changedIssue = newChangedIssue(key, randomValidStatus(), project, ruleName);
- AnalysisChange analysisChange = IssuesChangesNotificationBuilderTesting.newAnalysisChange();
+ ChangedIssue changedIssue = newChangedIssue(key, randomValidStatus(), project, ruleName, randomRuleTypeHotspotExcluded());
+ AnalysisChange analysisChange = newAnalysisChange();
when(emailSettings.getServerBaseURL()).thenReturn(host);
EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(analysisChange, ImmutableSet.of(changedIssue)));
@@ -378,8 +462,8 @@ public class ChangesOnMyIssuesEmailTemplateTest {
String ruleName = randomAlphabetic(8);
String host = randomAlphabetic(15);
String key = "key";
- ChangedIssue changedIssue = newChangedIssue(key, randomValidStatus(), project, ruleName);
- UserChange userChange = IssuesChangesNotificationBuilderTesting.newUserChange();
+ ChangedIssue changedIssue = newChangedIssue(key, randomValidStatus(), project, ruleName, randomRuleTypeHotspotExcluded());
+ UserChange userChange = newUserChange();
when(emailSettings.getServerBaseURL()).thenReturn(host);
EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(userChange, ImmutableSet.of(changedIssue)));
@@ -399,12 +483,12 @@ public class ChangesOnMyIssuesEmailTemplateTest {
Project project = newProject("1");
String ruleName = randomAlphabetic(8);
String host = randomAlphabetic(15);
- Rule rule = newRule(ruleName);
+ Rule rule = newRule(ruleName, randomRuleTypeHotspotExcluded());
String issueStatus = randomValidStatus();
List<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(5))
.mapToObj(i -> newChangedIssue("issue_" + i, issueStatus, project, rule))
.collect(toList());
- AnalysisChange analysisChange = IssuesChangesNotificationBuilderTesting.newAnalysisChange();
+ AnalysisChange analysisChange = newAnalysisChange();
when(emailSettings.getServerBaseURL()).thenReturn(host);
EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(analysisChange, ImmutableSet.copyOf(changedIssues)));
@@ -426,11 +510,11 @@ public class ChangesOnMyIssuesEmailTemplateTest {
Project project = newProject("1");
String ruleName = randomAlphabetic(8);
String host = randomAlphabetic(15);
- Rule rule = newRule(ruleName);
+ Rule rule = newRule(ruleName, randomRuleTypeHotspotExcluded());
List<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(5))
.mapToObj(i -> newChangedIssue("issue_" + i, randomValidStatus(), project, rule))
.collect(toList());
- UserChange userChange = IssuesChangesNotificationBuilderTesting.newUserChange();
+ UserChange userChange = newUserChange();
when(emailSettings.getServerBaseURL()).thenReturn(host);
EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(userChange, ImmutableSet.copyOf(changedIssues)));
@@ -453,12 +537,12 @@ public class ChangesOnMyIssuesEmailTemplateTest {
Project project = newBranch("1", branchName);
String ruleName = randomAlphabetic(8);
String host = randomAlphabetic(15);
- Rule rule = newRule(ruleName);
+ Rule rule = newRule(ruleName, randomRuleTypeHotspotExcluded());
String status = randomValidStatus();
List<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(5))
.mapToObj(i -> newChangedIssue("issue_" + i, status, project, rule))
.collect(toList());
- AnalysisChange analysisChange = IssuesChangesNotificationBuilderTesting.newAnalysisChange();
+ AnalysisChange analysisChange = newAnalysisChange();
when(emailSettings.getServerBaseURL()).thenReturn(host);
EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(analysisChange, ImmutableSet.copyOf(changedIssues)));
@@ -481,11 +565,11 @@ public class ChangesOnMyIssuesEmailTemplateTest {
Project project = newBranch("1", branchName);
String ruleName = randomAlphabetic(8);
String host = randomAlphabetic(15);
- Rule rule = newRule(ruleName);
+ Rule rule = newRandomNotAHotspotRule(ruleName);
List<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(5))
.mapToObj(i -> newChangedIssue("issue_" + i, randomValidStatus(), project, rule))
.collect(toList());
- UserChange userChange = IssuesChangesNotificationBuilderTesting.newUserChange();
+ UserChange userChange = newUserChange();
when(emailSettings.getServerBaseURL()).thenReturn(host);
EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(userChange, ImmutableSet.copyOf(changedIssues)));
@@ -512,10 +596,10 @@ public class ChangesOnMyIssuesEmailTemplateTest {
Project project3 = newProject("C");
String host = randomAlphabetic(15);
List<ChangedIssue> changedIssues = Stream.of(project1, project1Branch1, project1Branch2, project2, project2Branch1, project3)
- .map(project -> newChangedIssue("issue_" + project.getUuid(), randomValidStatus(), project, newRule(randomAlphabetic(2))))
+ .map(project -> newChangedIssue("issue_" + project.getUuid(), randomValidStatus(), project, newRule(randomAlphabetic(2), randomRuleTypeHotspotExcluded())))
.collect(toList());
Collections.shuffle(changedIssues);
- UserChange userChange = IssuesChangesNotificationBuilderTesting.newUserChange();
+ UserChange userChange = newUserChange();
when(emailSettings.getServerBaseURL()).thenReturn(host);
EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(userChange, ImmutableSet.copyOf(changedIssues)));
@@ -541,17 +625,18 @@ public class ChangesOnMyIssuesEmailTemplateTest {
@Test
public void formats_returns_html_message_with_rules_ordered_by_name_when_analysis_change() {
Project project = newProject("1");
- Rule rule1 = newRule("1");
- Rule rule2 = newRule("a");
- Rule rule3 = newRule("b");
- Rule rule4 = newRule("X");
+ Rule rule1 = newRandomNotAHotspotRule("1");
+ Rule rule2 = newRandomNotAHotspotRule("a");
+ Rule rule3 = newRandomNotAHotspotRule("b");
+ Rule rule4 = newRandomNotAHotspotRule("X");
+
String host = randomAlphabetic(15);
String issueStatus = randomValidStatus();
List<ChangedIssue> changedIssues = Stream.of(rule1, rule2, rule3, rule4)
.map(rule -> newChangedIssue("issue_" + rule.getName(), issueStatus, project, rule))
.collect(toList());
Collections.shuffle(changedIssues);
- AnalysisChange analysisChange = IssuesChangesNotificationBuilderTesting.newAnalysisChange();
+ AnalysisChange analysisChange = newAnalysisChange();
when(emailSettings.getServerBaseURL()).thenReturn(host);
EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(analysisChange, ImmutableSet.copyOf(changedIssues)));
@@ -569,30 +654,43 @@ public class ChangesOnMyIssuesEmailTemplateTest {
}
@Test
- public void formats_returns_html_message_with_rules_ordered_by_name_when_analysis_change_when_user_analysis() {
+ public void formats_returns_html_message_with_rules_ordered_by_name_user_change() {
Project project = newProject("1");
- Rule rule1 = newRule("1");
- Rule rule2 = newRule("a");
- Rule rule3 = newRule("b");
- Rule rule4 = newRule("X");
+ Rule rule1 = newRandomNotAHotspotRule("1");
+ Rule rule2 = newRandomNotAHotspotRule("a");
+ Rule rule3 = newRandomNotAHotspotRule("b");
+ Rule rule4 = newRandomNotAHotspotRule("X");
+
+ Rule hotspot1 = newSecurityHotspotRule("S");
+ Rule hotspot2 = newSecurityHotspotRule("Z");
+ Rule hotspot3 = newSecurityHotspotRule("N");
+ Rule hotspot4 = newSecurityHotspotRule("M");
+
String host = randomAlphabetic(15);
- List<ChangedIssue> changedIssues = Stream.of(rule1, rule2, rule3, rule4)
+ List<ChangedIssue> changedIssues = Stream.of(rule1, rule2, rule3, rule4, hotspot1, hotspot2, hotspot3, hotspot4)
.map(rule -> newChangedIssue("issue_" + rule.getName(), randomValidStatus(), project, rule))
.collect(toList());
Collections.shuffle(changedIssues);
- UserChange userChange = IssuesChangesNotificationBuilderTesting.newUserChange();
+ UserChange userChange = newUserChange();
when(emailSettings.getServerBaseURL()).thenReturn(host);
EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(userChange, ImmutableSet.copyOf(changedIssues)));
HtmlFragmentAssert.assertThat(emailMessage.getMessage())
- .hasParagraph().hasParagraph() // skip header
- .hasParagraph(project.getProjectName())
+ .hasParagraph()
+ .hasParagraph()
+ .hasParagraph() // skip project name
.hasList(
"Rule " + rule1.getName() + " - See the single issue",
"Rule " + rule2.getName() + " - See the single issue",
"Rule " + rule3.getName() + " - See the single issue",
"Rule " + rule4.getName() + " - See the single issue")
+ .hasEmptyParagraph()
+ .hasList(
+ "Rule " + hotspot1.getName() + " - See the single hotspot",
+ "Rule " + hotspot2.getName() + " - See the single hotspot",
+ "Rule " + hotspot3.getName() + " - See the single hotspot",
+ "Rule " + hotspot4.getName() + " - See the single hotspot")
.hasParagraph().hasParagraph() // skip footer
.noMoreBlock();
}
@@ -600,11 +698,13 @@ public class ChangesOnMyIssuesEmailTemplateTest {
@Test
public void formats_returns_html_message_with_multiple_links_by_rule_of_groups_of_up_to_40_issues_when_analysis_change() {
Project project1 = newProject("1");
- Rule rule1 = newRule("1");
- Rule rule2 = newRule("a");
+ Rule rule1 = newRandomNotAHotspotRule("1");
+ Rule rule2 = newRandomNotAHotspotRule("a");
+
String host = randomAlphabetic(15);
String issueStatusClosed = STATUS_CLOSED;
String otherIssueStatus = STATUS_RESOLVED;
+
List<ChangedIssue> changedIssues = Stream.of(
IntStream.range(0, 39).mapToObj(i -> newChangedIssue("39_" + i, issueStatusClosed, project1, rule1)),
IntStream.range(0, 40).mapToObj(i -> newChangedIssue("40_" + i, issueStatusClosed, project1, rule2)),
@@ -612,8 +712,9 @@ public class ChangesOnMyIssuesEmailTemplateTest {
IntStream.range(0, 6).mapToObj(i -> newChangedIssue("6_" + i, otherIssueStatus, project1, rule1)))
.flatMap(t -> t)
.collect(toList());
+
Collections.shuffle(changedIssues);
- AnalysisChange analysisChange = IssuesChangesNotificationBuilderTesting.newAnalysisChange();
+ AnalysisChange analysisChange = newAnalysisChange();
when(emailSettings.getServerBaseURL()).thenReturn(host);
EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(analysisChange, ImmutableSet.copyOf(changedIssues)));
@@ -651,23 +752,32 @@ public class ChangesOnMyIssuesEmailTemplateTest {
}
@Test
- public void formats_returns_html_message_with_multiple_links_by_rule_of_groups_of_up_to_40_issues_when_user_change() {
+ public void formats_returns_html_message_with_multiple_links_by_rule_of_groups_of_up_to_40_issues_and_hotspots_when_user_change() {
Project project1 = newProject("1");
Project project2 = newProject("V");
Project project2Branch = newBranch("V", "AB");
- Rule rule1 = newRule("1");
- Rule rule2 = newRule("a");
+ Rule rule1 = newRule("1", randomRuleTypeHotspotExcluded());
+ Rule rule2 = newRule("a", randomRuleTypeHotspotExcluded());
+
+ Rule hotspot1 = newSecurityHotspotRule("h1");
+ Rule hotspot2 = newSecurityHotspotRule("h2");
+
String status = randomValidStatus();
String host = randomAlphabetic(15);
List<ChangedIssue> changedIssues = Stream.of(
IntStream.range(0, 39).mapToObj(i -> newChangedIssue("39_" + i, status, project1, rule1)),
IntStream.range(0, 40).mapToObj(i -> newChangedIssue("40_" + i, status, project1, rule2)),
IntStream.range(0, 81).mapToObj(i -> newChangedIssue("1-40_41-80_1_" + i, status, project2, rule2)),
- IntStream.range(0, 6).mapToObj(i -> newChangedIssue("6_" + i, status, project2Branch, rule1)))
+ IntStream.range(0, 6).mapToObj(i -> newChangedIssue("6_" + i, status, project2Branch, rule1)),
+
+ IntStream.range(0, 39).mapToObj(i -> newChangedIssue("39_" + i, STATUS_REVIEWED, project1, hotspot1)),
+ IntStream.range(0, 40).mapToObj(i -> newChangedIssue("40_" + i, STATUS_REVIEWED, project1, hotspot2)),
+ IntStream.range(0, 81).mapToObj(i -> newChangedIssue("1-40_41-80_1_" + i, STATUS_TO_REVIEW, project2, hotspot2)),
+ IntStream.range(0, 6).mapToObj(i -> newChangedIssue("6_" + i, STATUS_TO_REVIEW, project2Branch, hotspot1)))
.flatMap(t -> t)
.collect(toList());
Collections.shuffle(changedIssues);
- UserChange userChange = IssuesChangesNotificationBuilderTesting.newUserChange();
+ UserChange userChange = newUserChange();
when(emailSettings.getServerBaseURL()).thenReturn(host);
EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(userChange, ImmutableSet.copyOf(changedIssues)));
@@ -685,8 +795,20 @@ public class ChangesOnMyIssuesEmailTemplateTest {
.withLink("See all 40 issues",
host + "/project/issues?id=" + project1.getKey()
+ "&issues=" + IntStream.range(0, 40).mapToObj(i -> "40_" + i).sorted().collect(joining("%2C")))
+ .hasEmptyParagraph()
+ .hasList()
+ .withItemTexts(
+ "Rule " + hotspot1.getName() + " - See all 39 hotspots",
+ "Rule " + hotspot2.getName() + " - See all 40 hotspots")
+ .withLink("See all 39 hotspots",
+ host + "/security_hotspots?id=" + project1.getKey()
+ + "&hotspots=" + IntStream.range(0, 39).mapToObj(i -> "39_" + i).sorted().collect(joining("%2C")))
+ .withLink("See all 40 hotspots",
+ host + "/security_hotspots?id=" + project1.getKey()
+ + "&hotspots=" + IntStream.range(0, 40).mapToObj(i -> "40_" + i).sorted().collect(joining("%2C")))
.hasParagraph(project2.getProjectName())
- .hasList("Rule " + rule2.getName() + " - See issues 1-40 41-80 81")
+ .hasList(
+ "Rule " + rule2.getName() + " - See issues 1-40 41-80 81")
.withLink("1-40",
host + "/project/issues?id=" + project2.getKey()
+ "&issues=" + IntStream.range(0, 81).mapToObj(i -> "1-40_41-80_1_" + i).sorted().limit(40).collect(joining("%2C")))
@@ -696,11 +818,28 @@ public class ChangesOnMyIssuesEmailTemplateTest {
.withLink("81",
host + "/project/issues?id=" + project2.getKey()
+ "&issues=" + "1-40_41-80_1_9" + "&open=" + "1-40_41-80_1_9")
+ .hasEmptyParagraph()
+ .hasList("Rule " + hotspot2.getName() + " - See hotspots 1-40 41-80 81")
+ .withLink("1-40",
+ host + "/security_hotspots?id=" + project2.getKey()
+ + "&hotspots=" + IntStream.range(0, 81).mapToObj(i -> "1-40_41-80_1_" + i).sorted().limit(40).collect(joining("%2C")))
+ .withLink("41-80",
+ host + "/security_hotspots?id=" + project2.getKey()
+ + "&hotspots=" + IntStream.range(0, 81).mapToObj(i -> "1-40_41-80_1_" + i).sorted().skip(40).limit(40).collect(joining("%2C")))
+ .withLink("81",
+ host + "/security_hotspots?id=" + project2.getKey()
+ + "&hotspots=" + "1-40_41-80_1_9")
.hasParagraph(project2Branch.getProjectName() + ", " + project2Branch.getBranchName().get())
- .hasList("Rule " + rule1.getName() + " - See all 6 issues")
+ .hasList(
+ "Rule " + rule1.getName() + " - See all 6 issues")
.withLink("See all 6 issues",
host + "/project/issues?id=" + project2Branch.getKey() + "&branch=" + project2Branch.getBranchName().get()
+ "&issues=" + IntStream.range(0, 6).mapToObj(i -> "6_" + i).sorted().collect(joining("%2C")))
+ .hasEmptyParagraph()
+ .hasList("Rule " + hotspot1.getName() + " - See all 6 hotspots")
+ .withLink("See all 6 hotspots",
+ host + "/security_hotspots?id=" + project2Branch.getKey() + "&branch=" + project2Branch.getBranchName().get()
+ + "&hotspots=" + IntStream.range(0, 6).mapToObj(i -> "6_" + i).sorted().collect(joining("%2C")))
.hasParagraph().hasParagraph() // skip footer
.noMoreBlock();
}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/ChangesOnMyIssuesNotificationTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/ChangesOnMyIssuesNotificationTest.java
index 35dbe097585..5bce5adc5a8 100644
--- a/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/ChangesOnMyIssuesNotificationTest.java
+++ b/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/ChangesOnMyIssuesNotificationTest.java
@@ -31,6 +31,7 @@ import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.User
import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
+import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.newRandomNotAHotspotRule;
public class ChangesOnMyIssuesNotificationTest {
@Test
@@ -45,7 +46,8 @@ public class ChangesOnMyIssuesNotificationTest {
@Test
public void equals_is_based_on_change_and_issues() {
AnalysisChange analysisChange = new AnalysisChange(new Random().nextLong());
- ChangedIssue changedIssue = IssuesChangesNotificationBuilderTesting.newChangedIssue("doo", IssuesChangesNotificationBuilderTesting.newProject("prj"), IssuesChangesNotificationBuilderTesting.newRule("rul"));
+ ChangedIssue changedIssue = IssuesChangesNotificationBuilderTesting.newChangedIssue("doo", IssuesChangesNotificationBuilderTesting.newProject("prj"),
+ newRandomNotAHotspotRule("rul"));
ChangesOnMyIssuesNotification underTest = new ChangesOnMyIssuesNotification(analysisChange, ImmutableSet.of(changedIssue));
assertThat(underTest)
@@ -59,7 +61,8 @@ public class ChangesOnMyIssuesNotificationTest {
@Test
public void hashcode_is_based_on_change_and_issues() {
AnalysisChange analysisChange = new AnalysisChange(new Random().nextLong());
- ChangedIssue changedIssue = IssuesChangesNotificationBuilderTesting.newChangedIssue("doo", IssuesChangesNotificationBuilderTesting.newProject("prj"), IssuesChangesNotificationBuilderTesting.newRule("rul"));
+ ChangedIssue changedIssue = IssuesChangesNotificationBuilderTesting.newChangedIssue("doo", IssuesChangesNotificationBuilderTesting.newProject("prj"),
+ newRandomNotAHotspotRule("rul"));
ChangesOnMyIssuesNotification underTest = new ChangesOnMyIssuesNotification(analysisChange, ImmutableSet.of(changedIssue));
assertThat(underTest.hashCode())
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/FPOrWontFixNotificationHandlerTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/FPOrWontFixNotificationHandlerTest.java
index 67ec465b4ef..b0d74a7fdaa 100644
--- a/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/FPOrWontFixNotificationHandlerTest.java
+++ b/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/FPOrWontFixNotificationHandlerTest.java
@@ -34,12 +34,10 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.sonar.api.issue.Issue;
-import org.sonar.api.rule.RuleKey;
import org.sonar.server.issue.notification.FPOrWontFixNotification.FpOrWontFix;
import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.Change;
import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.ChangedIssue;
import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.Project;
-import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.Rule;
import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.User;
import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.UserChange;
import org.sonar.server.notification.NotificationDispatcherMetadata;
@@ -66,6 +64,7 @@ import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE;
import static org.sonar.api.issue.Issue.RESOLUTION_WONT_FIX;
import static org.sonar.core.util.stream.MoreCollectors.index;
import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.newProject;
+import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.newRandomNotAHotspotRule;
import static org.sonar.server.notification.NotificationDispatcherMetadata.GLOBAL_NOTIFICATION;
import static org.sonar.server.notification.NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION;
import static org.sonar.server.notification.NotificationManager.SubscriberPermissionsOnProject.ALL_MUST_HAVE_ROLE_USER;
@@ -477,7 +476,7 @@ public class FPOrWontFixNotificationHandlerTest {
.setAssignee(new User(randomAlphabetic(3), randomAlphabetic(4), randomAlphabetic(5)))
.setNewStatus(randomAlphabetic(12))
.setNewResolution(randomAlphabetic(13))
- .setRule(new Rule(RuleKey.of(randomAlphabetic(6), randomAlphabetic(7)), randomAlphabetic(8)))
+ .setRule(newRandomNotAHotspotRule(randomAlphabetic(8)))
.setProject(new Project.Builder(randomAlphabetic(9))
.setKey(randomAlphabetic(10))
.setProjectName(randomAlphabetic(11))
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/FPOrWontFixNotificationTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/FPOrWontFixNotificationTest.java
index ed1e5e59e5c..83174524808 100644
--- a/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/FPOrWontFixNotificationTest.java
+++ b/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/FPOrWontFixNotificationTest.java
@@ -26,7 +26,6 @@ import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.junit.Test;
-import org.sonar.api.rule.RuleKey;
import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.AnalysisChange;
import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.ChangedIssue;
import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.Project;
@@ -36,18 +35,19 @@ import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.User
import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.server.issue.notification.FPOrWontFixNotification.FpOrWontFix.FP;
import static org.sonar.server.issue.notification.FPOrWontFixNotification.FpOrWontFix.WONT_FIX;
+import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.newRandomNotAHotspotRule;
public class FPOrWontFixNotificationTest {
@Test
public void equals_is_based_on_issues_change_and_resolution() {
- Rule rule = new Rule(RuleKey.of("repo", "rule_key"), "rule_name");
+ Rule rule = newRandomNotAHotspotRule("rule_name");
Project project = new Project.Builder("prj_uuid").setKey("prj_key").setProjectName("prj_name").build();
Set<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(5))
.mapToObj(i -> new ChangedIssue.Builder("key_" + i)
.setNewStatus("status")
.setRule(rule)
.setProject(project)
- .build())
+ .build())
.collect(Collectors.toSet());
AnalysisChange change = new AnalysisChange(12);
User user = new User("uuid", "login", null);
@@ -64,16 +64,17 @@ public class FPOrWontFixNotificationTest {
.isNotEqualTo(new FPOrWontFixNotification(new IssuesChangesNotificationBuilder.UserChange(12, user), changedIssues, WONT_FIX))
.isNotEqualTo(new FPOrWontFixNotification(change, changedIssues, FP));
}
+
@Test
public void hashcode_is_based_on_issues_change_and_resolution() {
- Rule rule = new Rule(RuleKey.of("repo", "rule_key"), "rule_name");
+ Rule rule = newRandomNotAHotspotRule("rule_name");
Project project = new Project.Builder("prj_uuid").setKey("prj_key").setProjectName("prj_name").build();
Set<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(5))
.mapToObj(i -> new ChangedIssue.Builder("key_" + i)
.setNewStatus("status")
.setRule(rule)
.setProject(project)
- .build())
+ .build())
.collect(Collectors.toSet());
AnalysisChange change = new AnalysisChange(12);
User user = new User("uuid", "login", null);
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/FpOrWontFixEmailTemplateTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/FpOrWontFixEmailTemplateTest.java
index c0479b9f387..38e3ad3f6ee 100644
--- a/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/FpOrWontFixEmailTemplateTest.java
+++ b/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/FpOrWontFixEmailTemplateTest.java
@@ -34,6 +34,7 @@ import org.junit.runner.RunWith;
import org.sonar.api.config.EmailSettings;
import org.sonar.api.notifications.Notification;
import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.RuleType;
import org.sonar.core.i18n.I18n;
import org.sonar.server.issue.notification.FPOrWontFixNotification.FpOrWontFix;
import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.AnalysisChange;
@@ -51,8 +52,12 @@ import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
import static org.sonar.server.issue.notification.FPOrWontFixNotification.FpOrWontFix.FP;
import static org.sonar.server.issue.notification.FPOrWontFixNotification.FpOrWontFix.WONT_FIX;
+import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.newRandomNotAHotspotRule;
+import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.newSecurityHotspotRule;
+import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.randomRuleTypeHotspotExcluded;
@RunWith(DataProviderRunner.class)
public class FpOrWontFixEmailTemplateTest {
@@ -160,7 +165,7 @@ public class FpOrWontFixEmailTemplateTest {
Project project = newProject("1");
String ruleName = randomAlphabetic(8);
String host = randomAlphabetic(15);
- ChangedIssue changedIssue = newChangedIssue("key", project, ruleName);
+ ChangedIssue changedIssue = newChangedIssue("key", project, ruleName, randomRuleTypeHotspotExcluded());
when(emailSettings.getServerBaseURL()).thenReturn(host);
EmailMessage emailMessage = underTest.format(new FPOrWontFixNotification(change, ImmutableSet.of(changedIssue), fpOrWontFix));
@@ -176,13 +181,33 @@ public class FpOrWontFixEmailTemplateTest {
@Test
@UseDataProvider("fpOrWontFixValuesByUserOrAnalysisChange")
+ public void formats_returns_html_message_for_single_hotspot_on_master(Change change, FpOrWontFix fpOrWontFix) {
+ Project project = newProject("1");
+ String ruleName = randomAlphabetic(8);
+ String host = randomAlphabetic(15);
+ ChangedIssue changedIssue = newChangedIssue("key", project, ruleName, SECURITY_HOTSPOT);
+ when(emailSettings.getServerBaseURL()).thenReturn(host);
+
+ EmailMessage emailMessage = underTest.format(new FPOrWontFixNotification(change, ImmutableSet.of(changedIssue), fpOrWontFix));
+
+ HtmlFragmentAssert.assertThat(emailMessage.getMessage())
+ .hasParagraph().hasParagraph() // skip header
+ .hasParagraph(project.getProjectName())
+ .hasList("Rule " + ruleName + " - See the single hotspot")
+ .withLink("See the single hotspot", host + "/project/issues?id=" + project.getKey() + "&issues=" + changedIssue.getKey() + "&open=" + changedIssue.getKey())
+ .hasParagraph().hasParagraph() // skip footer
+ .noMoreBlock();
+ }
+
+ @Test
+ @UseDataProvider("fpOrWontFixValuesByUserOrAnalysisChange")
public void formats_returns_html_message_for_single_issue_on_branch(Change change, FpOrWontFix fpOrWontFix) {
String branchName = randomAlphabetic(6);
Project project = newBranch("1", branchName);
String ruleName = randomAlphabetic(8);
String host = randomAlphabetic(15);
String key = "key";
- ChangedIssue changedIssue = newChangedIssue(key, project, ruleName);
+ ChangedIssue changedIssue = newChangedIssue(key, project, ruleName, randomRuleTypeHotspotExcluded());
when(emailSettings.getServerBaseURL()).thenReturn(host);
EmailMessage emailMessage = underTest.format(new FPOrWontFixNotification(change, ImmutableSet.of(changedIssue), fpOrWontFix));
@@ -199,11 +224,34 @@ public class FpOrWontFixEmailTemplateTest {
@Test
@UseDataProvider("fpOrWontFixValuesByUserOrAnalysisChange")
+ public void formats_returns_html_message_for_single_hotspot_on_branch(Change change, FpOrWontFix fpOrWontFix) {
+ String branchName = randomAlphabetic(6);
+ Project project = newBranch("1", branchName);
+ String ruleName = randomAlphabetic(8);
+ String host = randomAlphabetic(15);
+ String key = "key";
+ ChangedIssue changedIssue = newChangedIssue(key, project, ruleName, SECURITY_HOTSPOT);
+ when(emailSettings.getServerBaseURL()).thenReturn(host);
+
+ EmailMessage emailMessage = underTest.format(new FPOrWontFixNotification(change, ImmutableSet.of(changedIssue), fpOrWontFix));
+
+ HtmlFragmentAssert.assertThat(emailMessage.getMessage())
+ .hasParagraph().hasParagraph() // skip header
+ .hasParagraph(project.getProjectName() + ", " + branchName)
+ .hasList("Rule " + ruleName + " - See the single hotspot")
+ .withLink("See the single hotspot",
+ host + "/project/issues?id=" + project.getKey() + "&branch=" + branchName + "&issues=" + changedIssue.getKey() + "&open=" + changedIssue.getKey())
+ .hasParagraph().hasParagraph() // skip footer
+ .noMoreBlock();
+ }
+
+ @Test
+ @UseDataProvider("fpOrWontFixValuesByUserOrAnalysisChange")
public void formats_returns_html_message_for_multiple_issues_of_same_rule_on_same_project_on_master(Change change, FpOrWontFix fpOrWontFix) {
Project project = newProject("1");
String ruleName = randomAlphabetic(8);
String host = randomAlphabetic(15);
- Rule rule = newRule(ruleName);
+ Rule rule = newRandomNotAHotspotRule(ruleName);
List<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(5))
.mapToObj(i -> newChangedIssue("issue_" + i, project, rule))
.collect(toList());
@@ -225,12 +273,38 @@ public class FpOrWontFixEmailTemplateTest {
@Test
@UseDataProvider("fpOrWontFixValuesByUserOrAnalysisChange")
+ public void formats_returns_html_message_for_multiple_hotspots_of_same_rule_on_same_project_on_master(Change change, FpOrWontFix fpOrWontFix) {
+ Project project = newProject("1");
+ String ruleName = randomAlphabetic(8);
+ String host = randomAlphabetic(15);
+ Rule rule = newSecurityHotspotRule(ruleName);
+ List<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(5))
+ .mapToObj(i -> newChangedIssue("issue_" + i, project, rule))
+ .collect(toList());
+ when(emailSettings.getServerBaseURL()).thenReturn(host);
+
+ EmailMessage emailMessage = underTest.format(new FPOrWontFixNotification(change, ImmutableSet.copyOf(changedIssues), fpOrWontFix));
+
+ String expectedHref = host + "/project/issues?id=" + project.getKey()
+ + "&issues=" + changedIssues.stream().map(ChangedIssue::getKey).collect(joining("%2C"));
+ String expectedLinkText = "See all " + changedIssues.size() + " hotspots";
+ HtmlFragmentAssert.assertThat(emailMessage.getMessage())
+ .hasParagraph().hasParagraph() // skip header
+ .hasParagraph(project.getProjectName())
+ .hasList("Rule " + ruleName + " - " + expectedLinkText)
+ .withLink(expectedLinkText, expectedHref)
+ .hasParagraph().hasParagraph() // skip footer
+ .noMoreBlock();
+ }
+
+ @Test
+ @UseDataProvider("fpOrWontFixValuesByUserOrAnalysisChange")
public void formats_returns_html_message_for_multiple_issues_of_same_rule_on_same_project_on_branch(Change change, FpOrWontFix fpOrWontFix) {
String branchName = randomAlphabetic(19);
Project project = newBranch("1", branchName);
String ruleName = randomAlphabetic(8);
String host = randomAlphabetic(15);
- Rule rule = newRule(ruleName);
+ Rule rule = newRandomNotAHotspotRule(ruleName);
List<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(5))
.mapToObj(i -> newChangedIssue("issue_" + i, project, rule))
.collect(toList());
@@ -252,6 +326,33 @@ public class FpOrWontFixEmailTemplateTest {
@Test
@UseDataProvider("fpOrWontFixValuesByUserOrAnalysisChange")
+ public void formats_returns_html_message_for_multiple_hotspots_of_same_rule_on_same_project_on_branch(Change change, FpOrWontFix fpOrWontFix) {
+ String branchName = randomAlphabetic(19);
+ Project project = newBranch("1", branchName);
+ String ruleName = randomAlphabetic(8);
+ String host = randomAlphabetic(15);
+ Rule rule = newSecurityHotspotRule(ruleName);
+ List<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(5))
+ .mapToObj(i -> newChangedIssue("issue_" + i, project, rule))
+ .collect(toList());
+ when(emailSettings.getServerBaseURL()).thenReturn(host);
+
+ EmailMessage emailMessage = underTest.format(new FPOrWontFixNotification(change, ImmutableSet.copyOf(changedIssues), fpOrWontFix));
+
+ String expectedHref = host + "/project/issues?id=" + project.getKey() + "&branch=" + branchName
+ + "&issues=" + changedIssues.stream().map(ChangedIssue::getKey).collect(joining("%2C"));
+ String expectedLinkText = "See all " + changedIssues.size() + " hotspots";
+ HtmlFragmentAssert.assertThat(emailMessage.getMessage())
+ .hasParagraph().hasParagraph() // skip header
+ .hasParagraph(project.getProjectName() + ", " + branchName)
+ .hasList("Rule " + ruleName + " - " + expectedLinkText)
+ .withLink(expectedLinkText, expectedHref)
+ .hasParagraph().hasParagraph() // skip footer
+ .noMoreBlock();
+ }
+
+ @Test
+ @UseDataProvider("fpOrWontFixValuesByUserOrAnalysisChange")
public void formats_returns_html_message_with_projects_ordered_by_name(Change change, FpOrWontFix fpOrWontFix) {
Project project1 = newProject("1");
Project project1Branch1 = newBranch("1", "a");
@@ -261,7 +362,7 @@ public class FpOrWontFixEmailTemplateTest {
Project project3 = newProject("C");
String host = randomAlphabetic(15);
List<ChangedIssue> changedIssues = Stream.of(project1, project1Branch1, project1Branch2, project2, project2Branch1, project3)
- .map(project -> newChangedIssue("issue_" + project.getUuid(), project, newRule(randomAlphabetic(2))))
+ .map(project -> newChangedIssue("issue_" + project.getUuid(), project, newRandomNotAHotspotRule(randomAlphabetic(2))))
.collect(toList());
Collections.shuffle(changedIssues);
when(emailSettings.getServerBaseURL()).thenReturn(host);
@@ -290,10 +391,10 @@ public class FpOrWontFixEmailTemplateTest {
@UseDataProvider("fpOrWontFixValuesByUserOrAnalysisChange")
public void formats_returns_html_message_with_rules_ordered_by_name(Change change, FpOrWontFix fpOrWontFix) {
Project project = newProject("1");
- Rule rule1 = newRule("1");
- Rule rule2 = newRule("a");
- Rule rule3 = newRule("b");
- Rule rule4 = newRule("X");
+ Rule rule1 = newRandomNotAHotspotRule("1");
+ Rule rule2 = newRandomNotAHotspotRule("a");
+ Rule rule3 = newRandomNotAHotspotRule("b");
+ Rule rule4 = newRandomNotAHotspotRule("X");
String host = randomAlphabetic(15);
List<ChangedIssue> changedIssues = Stream.of(rule1, rule2, rule3, rule4)
.map(rule -> newChangedIssue("issue_" + rule.getName(), project, rule))
@@ -321,8 +422,8 @@ public class FpOrWontFixEmailTemplateTest {
Project project1 = newProject("1");
Project project2 = newProject("V");
Project project2Branch = newBranch("V", "AB");
- Rule rule1 = newRule("1");
- Rule rule2 = newRule("a");
+ Rule rule1 = newRandomNotAHotspotRule("1");
+ Rule rule2 = newRandomNotAHotspotRule("a");
String host = randomAlphabetic(15);
List<ChangedIssue> changedIssues = Stream.of(
IntStream.range(0, 39).mapToObj(i -> newChangedIssue("39_" + i, project1, rule1)),
@@ -393,8 +494,8 @@ public class FpOrWontFixEmailTemplateTest {
};
}
- private static ChangedIssue newChangedIssue(String key, Project project, String ruleName) {
- return newChangedIssue(key, project, newRule(ruleName));
+ private static ChangedIssue newChangedIssue(String key, Project project, String ruleName, RuleType ruleType) {
+ return newChangedIssue(key, project, newRule(ruleName, ruleType));
}
private static ChangedIssue newChangedIssue(String key, Project project, Rule rule) {
@@ -405,8 +506,8 @@ public class FpOrWontFixEmailTemplateTest {
.build();
}
- private static Rule newRule(String ruleName) {
- return new Rule(RuleKey.of(randomAlphabetic(6), randomAlphabetic(7)), ruleName);
+ private static Rule newRule(String ruleName, RuleType ruleType) {
+ return new Rule(RuleKey.of(randomAlphabetic(6), randomAlphabetic(7)), ruleType, ruleName);
}
private static Project newProject(String uuid) {
diff --git a/server/sonar-server-common/src/testFixtures/java/org/sonar/server/issue/notification/IssuesChangesNotificationBuilderTesting.java b/server/sonar-server-common/src/testFixtures/java/org/sonar/server/issue/notification/IssuesChangesNotificationBuilderTesting.java
index ee1727a0407..2d2c39e1f06 100644
--- a/server/sonar-server-common/src/testFixtures/java/org/sonar/server/issue/notification/IssuesChangesNotificationBuilderTesting.java
+++ b/server/sonar-server-common/src/testFixtures/java/org/sonar/server/issue/notification/IssuesChangesNotificationBuilderTesting.java
@@ -21,6 +21,7 @@ package org.sonar.server.issue.notification;
import java.util.Random;
import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.RuleType;
import org.sonar.db.DbTester;
import org.sonar.db.component.BranchDto;
import org.sonar.db.component.ComponentDto;
@@ -36,15 +37,24 @@ import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.User
import static com.google.common.base.Preconditions.checkArgument;
import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.sonar.api.rules.RuleType.BUG;
+import static org.sonar.api.rules.RuleType.CODE_SMELL;
+import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
+import static org.sonar.api.rules.RuleType.VULNERABILITY;
public class IssuesChangesNotificationBuilderTesting {
+ private static final RuleType[] RULE_TYPES = {CODE_SMELL, BUG, VULNERABILITY, SECURITY_HOTSPOT};
+
+ private IssuesChangesNotificationBuilderTesting() {
+ }
+
public static Rule ruleOf(RuleDto rule) {
- return new Rule(rule.getKey(), rule.getName());
+ return new Rule(rule.getKey(), RuleType.valueOfNullable(rule.getType()), rule.getName());
}
public static Rule ruleOf(RuleDefinitionDto rule) {
- return new Rule(rule.getKey(), rule.getName());
+ return new Rule(rule.getKey(), RuleType.valueOfNullable(rule.getType()), rule.getName());
}
public static User userOf(UserDto changeAuthor) {
@@ -76,8 +86,8 @@ public class IssuesChangesNotificationBuilderTesting {
.build();
}
- static ChangedIssue newChangedIssue(String key, String status, Project project, String ruleName) {
- return newChangedIssue(key, status, project, newRule(ruleName));
+ static ChangedIssue newChangedIssue(String key, String status, Project project, String ruleName, RuleType ruleType) {
+ return newChangedIssue(key, status, project, newRule(ruleName, ruleType));
}
static ChangedIssue newChangedIssue(String key, String status, Project project, Rule rule) {
@@ -88,8 +98,16 @@ public class IssuesChangesNotificationBuilderTesting {
.build();
}
- static Rule newRule(String ruleName) {
- return new Rule(RuleKey.of(randomAlphabetic(6), randomAlphabetic(7)), ruleName);
+ static Rule newRule(String ruleName, RuleType ruleType) {
+ return new Rule(RuleKey.of(randomAlphabetic(6), randomAlphabetic(7)), ruleType, ruleName);
+ }
+
+ static Rule newRandomNotAHotspotRule(String ruleName) {
+ return newRule(ruleName, randomRuleTypeHotspotExcluded());
+ }
+
+ static Rule newSecurityHotspotRule(String ruleName) {
+ return newRule(ruleName, SECURITY_HOTSPOT);
}
static Project newProject(String uuid) {
@@ -107,4 +125,8 @@ public class IssuesChangesNotificationBuilderTesting {
static AnalysisChange newAnalysisChange() {
return new AnalysisChange(new Random().nextLong());
}
+
+ static RuleType randomRuleTypeHotspotExcluded() {
+ return RULE_TYPES[new Random().nextInt(RULE_TYPES.length - 1)];
+ }
}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/BulkChangeAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/BulkChangeAction.java
index d67f119cbd7..26ffc7aacb4 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/BulkChangeAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/BulkChangeAction.java
@@ -306,7 +306,7 @@ public class BulkChangeAction implements IssuesWsAction {
.setNewStatus(issue.status())
.setNewResolution(issue.resolution())
.setAssignee(assignee.map(u -> new User(u.getUuid(), u.getLogin(), u.getName())).orElse(null))
- .setRule(new IssuesChangesNotificationBuilder.Rule(ruleDefinitionDto.getKey(), ruleDefinitionDto.getName()))
+ .setRule(new IssuesChangesNotificationBuilder.Rule(ruleDefinitionDto.getKey(), RuleType.valueOfNullable(ruleDefinitionDto.getType()), ruleDefinitionDto.getName()))
.setProject(new Project.Builder(projectDto.uuid())
.setKey(projectDto.getKey())
.setProjectName(projectDto.name())
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/IssueUpdater.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/IssueUpdater.java
index c393ce34697..0e01c13fa7e 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/IssueUpdater.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/IssueUpdater.java
@@ -24,6 +24,7 @@ import java.util.Optional;
import javax.annotation.Nullable;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.rules.RuleType;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.core.util.stream.MoreCollectors;
@@ -112,7 +113,7 @@ public class IssueUpdater {
.setNewResolution(issue.resolution())
.setNewStatus(issue.status())
.setAssignee(assignee.map(assigneeDto -> new User(assigneeDto.getUuid(), assigneeDto.getLogin(), assigneeDto.getName())).orElse(null))
- .setRule(rule.map(r -> new Rule(r.getKey(), r.getName())).get())
+ .setRule(rule.map(r -> new Rule(r.getKey(), RuleType.valueOfNullable(r.getType()), r.getName())).get())
.setProject(new Project.Builder(project.uuid())
.setKey(project.getKey())
.setProjectName(project.name())