return globalStats.getTotal();
}
+ @Override
+ public String toString() {
+ return "DistributedMetricStatsInt{" +
+ "globalStats=" + globalStats +
+ ", statsPerLabel=" + statsPerLabel +
+ '}';
+ }
}
return onLeak + offLeak;
}
+ @Override
+ public String toString() {
+ return "MetricStatsInt{" +
+ "onLeak=" + onLeak +
+ ", offLeak=" + offLeak +
+ '}';
+ }
}
return onLeak + offLeak;
}
+ @Override
+ public String toString() {
+ return "MetricStatsLong{" +
+ "onLeak=" + onLeak +
+ ", offLeak=" + offLeak +
+ '}';
+ }
}
*/
package org.sonar.server.issue.notification;
+import com.google.common.collect.ImmutableMap;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
import java.util.function.ToIntFunction;
import javax.annotation.Nullable;
import org.sonar.api.notifications.Notification;
import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
+import org.sonar.db.RowNotFoundException;
+import org.sonar.db.component.ComponentDto;
import org.sonar.db.rule.RuleDefinitionDto;
import org.sonar.server.issue.notification.NewIssuesStatistics.Metric;
import org.sonar.server.user.index.UserDoc;
private void setRuleStatistics(DbSession dbSession, NewIssuesStatistics.Stats stats) {
Metric metric = Metric.RULE;
+ List<Map.Entry<String, MetricStatsInt>> fiveBiggest = fiveBiggest(stats.getDistributedMetricStats(metric), MetricStatsInt::getOnLeak);
+ Set<RuleKey> ruleKeys = fiveBiggest
+ .stream()
+ .map(Map.Entry::getKey)
+ .map(RuleKey::parse)
+ .collect(MoreCollectors.toSet(fiveBiggest.size()));
+ ImmutableMap<String, RuleDefinitionDto> ruleByRuleKey = dbClient.ruleDao().selectDefinitionByKeys(dbSession, ruleKeys)
+ .stream()
+ .collect(MoreCollectors.uniqueIndex(s -> s.getKey().toString()));
int i = 1;
- for (Map.Entry<String, MetricStatsInt> ruleStats : fiveBiggest(stats.getDistributedMetricStats(metric), MetricStatsInt::getOnLeak)) {
+ for (Map.Entry<String, MetricStatsInt> ruleStats : fiveBiggest) {
String ruleKey = ruleStats.getKey();
- RuleDefinitionDto rule = dbClient.ruleDao().selectOrFailDefinitionByKey(dbSession, RuleKey.parse(ruleKey));
+ RuleDefinitionDto rule = Optional.ofNullable(ruleByRuleKey.get(ruleKey))
+ .orElseThrow(() -> new RowNotFoundException(String.format("Rule with key '%s' does not exist", ruleKey)));
String name = rule.getName() + " (" + rule.getLanguage() + ")";
setFieldValue(metric + DOT + i + LABEL, name);
setFieldValue(metric + DOT + i + COUNT, String.valueOf(ruleStats.getValue().getOnLeak()));
private void setComponentsStatistics(DbSession dbSession, NewIssuesStatistics.Stats stats) {
Metric metric = Metric.COMPONENT;
int i = 1;
- for (Map.Entry<String, MetricStatsInt> componentStats : fiveBiggest(stats.getDistributedMetricStats(metric), MetricStatsInt::getOnLeak)) {
+ List<Map.Entry<String, MetricStatsInt>> fiveBiggest = fiveBiggest(stats.getDistributedMetricStats(metric), MetricStatsInt::getOnLeak);
+ Set<String> componentUuids = fiveBiggest
+ .stream()
+ .map(Map.Entry::getKey)
+ .collect(MoreCollectors.toSet(fiveBiggest.size()));
+ Map<String, ComponentDto> componentDtosByUuid = dbClient.componentDao().selectByUuids(dbSession, componentUuids)
+ .stream()
+ .collect(MoreCollectors.uniqueIndex(ComponentDto::uuid));
+ for (Map.Entry<String, MetricStatsInt> componentStats : fiveBiggest) {
String uuid = componentStats.getKey();
- String componentName = dbClient.componentDao().selectOrFailByUuid(dbSession, uuid).name();
+ String componentName = Optional.ofNullable(componentDtosByUuid.get(uuid))
+ .map(ComponentDto::name)
+ .orElseThrow(() -> new RowNotFoundException(String.format("Component with uuid '%s' not found", uuid)));
setFieldValue(metric + DOT + i + LABEL, componentName);
setFieldValue(metric + DOT + i + COUNT, String.valueOf(componentStats.getValue().getOnLeak()));
i++;
}
}
+ @Override
+ public String toString() {
+ return "NewIssuesStatistics{" +
+ "assigneesStatistics=" + assigneesStatistics +
+ ", globalStatistics=" + globalStatistics +
+ '}';
+ }
+
public static class Stats {
private final Predicate<Issue> onLeakPredicate;
private final Map<Metric, DistributedMetricStatsInt> distributions = new EnumMap<>(Metric.class);
public void add(Issue issue) {
boolean isOnLeak = onLeakPredicate.test(issue);
distributions.get(SEVERITY).increment(issue.severity(), isOnLeak);
- distributions.get(COMPONENT).increment(issue.componentUuid(), isOnLeak);
+ String componentUuid = issue.componentUuid();
+ if (componentUuid != null) {
+ distributions.get(COMPONENT).increment(componentUuid, isOnLeak);
+ }
RuleKey ruleKey = issue.ruleKey();
if (ruleKey != null) {
distributions.get(RULE).increment(ruleKey.toString(), isOnLeak);
return getDistributedMetricStats(SEVERITY).getOffLeak() > 0;
}
+ @Override
+ public String toString() {
+ return "Stats{" +
+ "distributions=" + distributions +
+ ", effortStats=" + effortStats +
+ '}';
+ }
}
}
*/
package org.sonar.server.issue.notification;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.Date;
+import java.util.Random;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.junit.Before;
import org.junit.Test;
-import org.mockito.Mockito;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.Severity;
import org.sonar.api.utils.DateUtils;
import org.sonar.api.utils.Duration;
import org.sonar.api.utils.Durations;
import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDao;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.rule.RuleDao;
import org.sonar.db.rule.RuleDefinitionDto;
import org.sonar.server.user.index.UserIndex;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyCollection;
+import static org.mockito.Matchers.same;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.ASSIGNEE;
public class NewIssuesNotificationTest {
- NewIssuesStatistics.Stats stats = new NewIssuesStatistics.Stats(i -> true);
- UserIndex userIndex = mock(UserIndex.class);
- DbClient dbClient = mock(DbClient.class, Mockito.RETURNS_DEEP_STUBS);
- Durations durations = mock(Durations.class);
- NewIssuesNotification underTest = new NewIssuesNotification(userIndex, dbClient, durations);
+ private NewIssuesStatistics.Stats stats = new NewIssuesStatistics.Stats(i -> true);
+ private UserIndex userIndex = mock(UserIndex.class);
+ private DbClient dbClient = mock(DbClient.class);
+ private DbSession dbSession = mock(DbSession.class);
+ private ComponentDao componentDao = mock(ComponentDao.class);
+ private RuleDao ruleDao = mock(RuleDao.class);
+ private Durations durations = mock(Durations.class);
+ private NewIssuesNotification underTest = new NewIssuesNotification(userIndex, dbClient, durations);
+
+ @Before
+ public void setUp() throws Exception {
+ when(dbClient.openSession(anyBoolean())).thenReturn(dbSession);
+ when(dbClient.componentDao()).thenReturn(componentDao);
+ when(dbClient.ruleDao()).thenReturn(ruleDao);
+ when(componentDao.selectByUuids(same(dbSession), anyCollection())).thenReturn(Collections.emptyList());
+ }
@Test
public void set_project_without_branch() {
public void set_statistics() {
addIssueNTimes(newIssue1(), 5);
addIssueNTimes(newIssue2(), 3);
- when(dbClient.componentDao().selectOrFailByUuid(any(DbSession.class), eq("file-uuid")).name()).thenReturn("file-name");
- when(dbClient.componentDao().selectOrFailByUuid(any(DbSession.class), eq("directory-uuid")).name()).thenReturn("directory-name");
- when(dbClient.ruleDao().selectOrFailDefinitionByKey(any(DbSession.class), eq(RuleKey.of("SonarQube", "rule-the-world")))).thenReturn(newRule("Rule the World", "Java"));
- when(dbClient.ruleDao().selectOrFailDefinitionByKey(any(DbSession.class), eq(RuleKey.of("SonarQube", "rule-the-universe")))).thenReturn(newRule("Rule the Universe", "Clojure"));
+ when(componentDao.selectByUuids(dbSession, ImmutableSet.of("file-uuid", "directory-uuid")))
+ .thenReturn(Arrays.asList(
+ new ComponentDto().setUuid("file-uuid").setName("file-name"),
+ new ComponentDto().setUuid("directory-uuid").setName("directory-name")));
+ RuleKey rule1 = RuleKey.of("SonarQube", "rule-the-world");
+ RuleKey rule2 = RuleKey.of("SonarQube", "rule-the-universe");
+ when(ruleDao.selectDefinitionByKeys(dbSession, ImmutableSet.of(rule1, rule2)))
+ .thenReturn(
+ ImmutableList.of(newRule(rule1, "Rule the World", "Java"), newRule(rule2, "Rule the Universe", "Clojure")));
underTest.setStatistics("project-long-name", stats);
assertThat(underTest.getDefaultMessage()).startsWith("8 new issues on project-long-name");
}
+ @Test
+ public void add_only_5_assignees_with_biggest_issue_counts() {
+ Random random = new Random();
+ String[] assignees = IntStream.range(0, 6 + random.nextInt(10)).mapToObj(s -> "assignee" + s).toArray(String[]::new);
+ NewIssuesStatistics.Stats stats = new NewIssuesStatistics.Stats(i -> true);
+ int i = assignees.length;
+ for (String assignee : assignees) {
+ IntStream.range(0, i).mapToObj(j -> new DefaultIssue().setAssignee(assignee)).forEach(stats::add);
+ i--;
+ }
+
+ underTest.setStatistics(randomAlphanumeric(20), stats);
+
+ for (int j = 0; j < 5; j++) {
+ String fieldBase = ASSIGNEE + "." + (j + 1);
+ assertThat(underTest.getFieldValue(fieldBase + ".label")).as("label of %s", fieldBase).isEqualTo(assignees[j]);
+ assertThat(underTest.getFieldValue(fieldBase + ".count")).as("count of %s", fieldBase).isEqualTo(String.valueOf(assignees.length - j));
+ }
+ assertThat(underTest.getFieldValue(ASSIGNEE + ".6.label")).isNull();
+ assertThat(underTest.getFieldValue(ASSIGNEE + ".6.count")).isNull();
+ }
+
+ @Test
+ public void add_only_5_components_with_biggest_issue_counts() {
+ Random random = new Random();
+ String[] componentUuids = IntStream.range(0, 6 + random.nextInt(10)).mapToObj(s -> "component_uuid_" + s).toArray(String[]::new);
+ NewIssuesStatistics.Stats stats = new NewIssuesStatistics.Stats(i -> true);
+ int i = componentUuids.length;
+ for (String component : componentUuids) {
+ IntStream.range(0, i).mapToObj(j -> new DefaultIssue().setComponentUuid(component)).forEach(stats::add);
+ i--;
+ }
+ when(componentDao.selectByUuids(dbSession, Arrays.stream(componentUuids).limit(5).collect(Collectors.toSet())))
+ .thenReturn(
+ Arrays.stream(componentUuids).map(uuid -> new ComponentDto().setUuid(uuid).setName("name_" + uuid)).collect(MoreCollectors.toList()));
+
+ underTest.setStatistics(randomAlphanumeric(20), stats);
+
+ for (int j = 0; j < 5; j++) {
+ String fieldBase = COMPONENT + "." + (j + 1);
+ assertThat(underTest.getFieldValue(fieldBase + ".label")).as("label of %s", fieldBase).isEqualTo("name_" + componentUuids[j]);
+ assertThat(underTest.getFieldValue(fieldBase + ".count")).as("count of %s", fieldBase).isEqualTo(String.valueOf(componentUuids.length - j));
+ }
+ assertThat(underTest.getFieldValue(COMPONENT + ".6.label")).isNull();
+ assertThat(underTest.getFieldValue(COMPONENT + ".6.count")).isNull();
+ }
+
+ @Test
+ public void add_only_5_rules_with_biggest_issue_counts() {
+ Random random = new Random();
+ String repository = randomAlphanumeric(4);
+ String[] ruleKeys = IntStream.range(0, 6 + random.nextInt(10)).mapToObj(s -> "rule_" + s).toArray(String[]::new);
+ NewIssuesStatistics.Stats stats = new NewIssuesStatistics.Stats(i -> true);
+ int i = ruleKeys.length;
+ for (String ruleKey : ruleKeys) {
+ IntStream.range(0, i).mapToObj(j -> new DefaultIssue().setRuleKey(RuleKey.of(repository, ruleKey))).forEach(stats::add);
+ i--;
+ }
+ when(ruleDao.selectDefinitionByKeys(dbSession, Arrays.stream(ruleKeys).limit(5).map(s -> RuleKey.of(repository, s)).collect(MoreCollectors.toSet(5))))
+ .thenReturn(
+ Arrays.stream(ruleKeys).limit(5).map(ruleKey -> new RuleDefinitionDto()
+ .setRuleKey(RuleKey.of(repository, ruleKey))
+ .setName("name_" + ruleKey)
+ .setLanguage("language_" + ruleKey))
+ .collect(MoreCollectors.toList(5)));
+
+ underTest.setStatistics(randomAlphanumeric(20), stats);
+
+ for (int j = 0; j < 5; j++) {
+ String fieldBase = RULE + "." + (j + 1);
+ assertThat(underTest.getFieldValue(fieldBase + ".label")).as("label of %s", fieldBase).isEqualTo("name_" + ruleKeys[j] + " (language_" + ruleKeys[j] + ")");
+ assertThat(underTest.getFieldValue(fieldBase + ".count")).as("count of %s", fieldBase).isEqualTo(String.valueOf(ruleKeys.length - j));
+ }
+ assertThat(underTest.getFieldValue(RULE + ".6.label")).isNull();
+ assertThat(underTest.getFieldValue(RULE + ".6.count")).isNull();
+ }
+
@Test
public void set_debt() {
when(durations.format(any(Duration.class))).thenReturn("55 min");
.setEffort(Duration.create(10L));
}
- private RuleDefinitionDto newRule(String name, String language) {
+ private RuleDefinitionDto newRule(RuleKey ruleKey, String name, String language) {
return new RuleDefinitionDto()
+ .setRuleKey(ruleKey)
.setName(name)
.setLanguage(language);
}
*/
package org.sonar.server.issue.notification;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import java.util.Collections;
import java.util.List;
}
@Test
- public void add_counts_component_if_null_globally_and_per_assignee_as_it_should_not_be_null() {
+ public void add_does_not_count_component_if_null_neither_globally_nor_per_assignee() {
String assignee = randomAlphanumeric(10);
underTest.add(new DefaultIssue().setComponentUuid(null).setAssignee(assignee).setNew(new Random().nextBoolean()));
DistributedMetricStatsInt assigneeDistribution = underTest.getAssigneesStatistics().get(assignee).getDistributedMetricStats(Metric.COMPONENT);
Stream.of(globalDistribution, assigneeDistribution)
.forEach(distribution -> {
- assertThat(distribution.getTotal()).isEqualTo(1);
- assertThat(distribution.getForLabel(null).isPresent()).isTrue();
+ assertThat(distribution.getTotal()).isEqualTo(0);
+ assertThat(distribution.getForLabel(null).isPresent()).isFalse();
});
}
}
@Test
- public void add_does_not_count_ruleKey_if_neither_neither_globally_nor_per_assignee() {
+ public void add_does_not_count_ruleKey_if_null_neither_globally_nor_per_assignee() {
String assignee = randomAlphanumeric(10);
underTest.add(new DefaultIssue().setRuleKey(null).setAssignee(assignee).setNew(new Random().nextBoolean()));
.forEach(distribution -> assertThat(distribution.getTotal()).isEqualTo(0));
}
- private void assertStats(DistributedMetricStatsInt distribution, String label, int onLeak, int offLeak, int total) {
- Optional<MetricStatsInt> statsOption = distribution.getForLabel(label);
- assertThat(statsOption.isPresent()).describedAs("distribution for label %s not found", label).isTrue();
- MetricStatsInt stats = statsOption.get();
- assertThat(stats.getOnLeak()).isEqualTo(onLeak);
- assertThat(stats.getOffLeak()).isEqualTo(offLeak);
- assertThat(stats.getTotal()).isEqualTo(total);
- }
-
@Test
public void add_counts_issue_per_severity_per_assignee() {
String assignee = randomAlphanumeric(20);
assertThat(underTest.globalStatistics().hasIssues()).isFalse();
}
- @CheckForNull
- private Integer countDistributionTotal(Metric metric, String label) {
- return underTest.globalStatistics()
- .getDistributedMetricStats(metric)
- .getForLabel(label)
- .map(MetricStatsInt::getTotal)
- .orElse(null);
+ @Test
+ public void verify_toString() {
+ String componentUuid = randomAlphanumeric(2);
+ String tag = randomAlphanumeric(3);
+ String assignee = randomAlphanumeric(4);
+ int effort = 10 + new Random().nextInt(5);
+ RuleKey ruleKey = RuleKey.of(randomAlphanumeric(5), randomAlphanumeric(6));
+ underTest.add(new DefaultIssue()
+ .setSeverity(Severity.BLOCKER)
+ .setComponentUuid(componentUuid)
+ .setTags(ImmutableSet.of(tag))
+ .setAssignee(assignee)
+ .setRuleKey(ruleKey)
+ .setEffort(Duration.create(effort)));
+
+ assertThat(underTest.toString())
+ .isEqualTo("NewIssuesStatistics{" +
+ "assigneesStatistics={" + assignee + "=" +
+ "Stats{distributions={" +
+ "SEVERITY=DistributedMetricStatsInt{globalStats=MetricStatsInt{onLeak=1, offLeak=0}, " +
+ "statsPerLabel={" + Severity.BLOCKER + "=MetricStatsInt{onLeak=1, offLeak=0}}}, " +
+ "TAG=DistributedMetricStatsInt{globalStats=MetricStatsInt{onLeak=1, offLeak=0}, " +
+ "statsPerLabel={" + tag + "=MetricStatsInt{onLeak=1, offLeak=0}}}, " +
+ "COMPONENT=DistributedMetricStatsInt{globalStats=MetricStatsInt{onLeak=1, offLeak=0}, " +
+ "statsPerLabel={" + componentUuid + "=MetricStatsInt{onLeak=1, offLeak=0}}}, " +
+ "ASSIGNEE=DistributedMetricStatsInt{globalStats=MetricStatsInt{onLeak=1, offLeak=0}, " +
+ "statsPerLabel={" + assignee + "=MetricStatsInt{onLeak=1, offLeak=0}}}, " +
+ "RULE=DistributedMetricStatsInt{globalStats=MetricStatsInt{onLeak=1, offLeak=0}, " +
+ "statsPerLabel={" + ruleKey.toString() + "=MetricStatsInt{onLeak=1, offLeak=0}}}}, " +
+ "effortStats=MetricStatsLong{onLeak=" + effort + ", offLeak=0}}}, " +
+ "globalStatistics=Stats{distributions={" +
+ "SEVERITY=DistributedMetricStatsInt{globalStats=MetricStatsInt{onLeak=1, offLeak=0}, " +
+ "statsPerLabel={" + Severity.BLOCKER + "=MetricStatsInt{onLeak=1, offLeak=0}}}, " +
+ "TAG=DistributedMetricStatsInt{globalStats=MetricStatsInt{onLeak=1, offLeak=0}, " +
+ "statsPerLabel={" + tag + "=MetricStatsInt{onLeak=1, offLeak=0}}}, " +
+ "COMPONENT=DistributedMetricStatsInt{globalStats=MetricStatsInt{onLeak=1, offLeak=0}, " +
+ "statsPerLabel={" + componentUuid + "=MetricStatsInt{onLeak=1, offLeak=0}}}, " +
+ "ASSIGNEE=DistributedMetricStatsInt{globalStats=MetricStatsInt{onLeak=1, offLeak=0}, " +
+ "statsPerLabel={" + assignee + "=MetricStatsInt{onLeak=1, offLeak=0}}}, " +
+ "RULE=DistributedMetricStatsInt{globalStats=MetricStatsInt{onLeak=1, offLeak=0}, " +
+ "statsPerLabel={" + ruleKey.toString() + "=MetricStatsInt{onLeak=1, offLeak=0}}}}, " +
+ "effortStats=MetricStatsLong{onLeak=" + effort + ", offLeak=0}}}");
}
@CheckForNull
- private Integer countDistributionOnLeak(Metric metric, String label) {
+ private Integer countDistributionTotal(Metric metric, String label) {
return underTest.globalStatistics()
.getDistributedMetricStats(metric)
.getForLabel(label)
- .map(MetricStatsInt::getOnLeak)
+ .map(MetricStatsInt::getTotal)
.orElse(null);
}
- @CheckForNull
- private Integer countDistributionOffLeak(Metric metric, String label) {
- return underTest.globalStatistics()
- .getDistributedMetricStats(metric)
- .getForLabel(label)
- .map(MetricStatsInt::getOffLeak)
- .orElse(null);
+ private void assertStats(DistributedMetricStatsInt distribution, String label, int onLeak, int offLeak, int total) {
+ Optional<MetricStatsInt> statsOption = distribution.getForLabel(label);
+ assertThat(statsOption.isPresent()).describedAs("distribution for label %s not found", label).isTrue();
+ MetricStatsInt stats = statsOption.get();
+ assertThat(stats.getOnLeak()).isEqualTo(onLeak);
+ assertThat(stats.getOffLeak()).isEqualTo(offLeak);
+ assertThat(stats.getTotal()).isEqualTo(total);
}
- private DefaultIssue defaultIssue() {
- return new DefaultIssue()
- .setAssignee("maynard")
- .setComponentUuid("file-uuid")
- .setNew(true)
- .setSeverity(Severity.INFO)
- .setRuleKey(RuleKey.of("SonarQube", "rule-the-world"))
- .setTags(Lists.newArrayList("bug", "owasp"))
- .setEffort(Duration.create(5L));
- }
}