import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.utils.Duration;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.util.CloseableIterator;
import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
}
private void doExecute(Component project) {
- NewIssuesStatistics newIssuesStats = new NewIssuesStatistics();
+ long analysisDate = analysisMetadataHolder.getAnalysisDate();
+ NewIssuesStatistics newIssuesStats = new NewIssuesStatistics(i -> i.isNew() && i.creationDate().getTime() >= truncateToSeconds(analysisDate));
CloseableIterator<DefaultIssue> issues = issueCache.traverse();
try {
processIssues(newIssuesStats, issues, project);
issues.close();
}
if (newIssuesStats.hasIssues()) {
- long analysisDate = analysisMetadataHolder.getAnalysisDate();
sendNewIssuesNotification(newIssuesStats, project, analysisDate);
sendNewIssuesNotificationToAssignees(newIssuesStats, project, analysisDate);
}
}
+ /**
+ * Truncated the analysis date to seconds before comparing it to {@link Issue#creationDate()} is required because
+ * {@link DefaultIssue#setCreationDate(Date)} does it.
+ */
+ private static long truncateToSeconds(long analysisDate) {
+ Instant instant = new Date(analysisDate).toInstant();
+ instant = instant.truncatedTo(ChronoUnit.SECONDS);
+ return Date.from(instant).getTime();
+ }
+
private void processIssues(NewIssuesStatistics newIssuesStats, CloseableIterator<DefaultIssue> issues, Component project) {
while (issues.hasNext()) {
DefaultIssue issue = issues.next();
.setProject(project.getPublicKey(), project.getUuid(), project.getName(), getBranchName())
.setAnalysisDate(new Date(analysisDate))
.setStatistics(project.getName(), globalStatistics)
- .setDebt(globalStatistics.debt());
+ .setDebt(Duration.create(globalStatistics.effort().getTotal()));
service.deliver(notification);
}
.setProject(project.getPublicKey(), project.getUuid(), project.getName(), getBranchName())
.setAnalysisDate(new Date(analysisDate))
.setStatistics(project.getName(), assigneeStatistics)
- .setDebt(assigneeStatistics.debt());
+ .setDebt(Duration.create(assigneeStatistics.effort().getTotal()));
service.deliver(myNewIssuesNotification);
}
return String.format("%s: %s new issues (new debt: %s)",
projectName,
notification.getFieldValue(Metric.SEVERITY + COUNT),
- notification.getFieldValue(Metric.DEBT + COUNT));
+ notification.getFieldValue(Metric.EFFORT + COUNT));
}
private static boolean doNotHaveValue(Notification notification, Metric metric) {
message
.append(String.format("%s new issues (new debt: %s)",
notification.getFieldValue(Metric.SEVERITY + COUNT),
- notification.getFieldValue(Metric.DEBT + COUNT)))
+ notification.getFieldValue(Metric.EFFORT + COUNT)))
.append(NEW_LINE).append(NEW_LINE)
.append(TAB)
.append("Severity")
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+public class DistributedMetricStatsInt {
+ private MetricStatsInt globalStats = new MetricStatsInt();
+ private Map<String, MetricStatsInt> statsPerLabel = new HashMap<>();
+
+ DistributedMetricStatsInt increment(String label, boolean onLeak) {
+ this.globalStats.increment(onLeak);
+ statsPerLabel.computeIfAbsent(label, l -> new MetricStatsInt()).increment(onLeak);
+ return this;
+ }
+
+ Map<String, MetricStatsInt> getForLabels() {
+ return Collections.unmodifiableMap(statsPerLabel);
+ }
+
+ public Optional<MetricStatsInt> getForLabel(String label) {
+ return Optional.ofNullable(statsPerLabel.get(label));
+ }
+
+ public int getOnLeak() {
+ return globalStats.getOnLeak();
+ }
+
+ public int getOffLeak() {
+ return globalStats.getOffLeak();
+ }
+
+ public int getTotal() {
+ return globalStats.getTotal();
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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;
+
+public class MetricStatsInt {
+ private int onLeak = 0;
+ private int offLeak = 0;
+
+ MetricStatsInt increment(boolean onLeak) {
+ if (onLeak) {
+ this.onLeak += 1;
+ } else {
+ this.offLeak += 1;
+ }
+ return this;
+ }
+
+ public int getOnLeak() {
+ return onLeak;
+ }
+
+ public int getOffLeak() {
+ return offLeak;
+ }
+
+ public int getTotal() {
+ return onLeak + offLeak;
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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;
+
+public class MetricStatsLong {
+ private long onLeak = 0;
+ private long offLeak = 0;
+
+ MetricStatsLong add(long toAdd, boolean onLeak) {
+ if (onLeak) {
+ this.onLeak += toAdd;
+ } else {
+ this.offLeak += toAdd;
+ }
+ return this;
+ }
+
+ public long getOnLeak() {
+ return onLeak;
+ }
+
+ public long getOffLeak() {
+ return offLeak;
+ }
+
+ public long getTotal() {
+ return onLeak + offLeak;
+ }
+
+}
*/
package org.sonar.server.issue.notification;
-import com.google.common.collect.Multiset;
+import java.util.Comparator;
import java.util.Date;
import java.util.List;
+import java.util.Map;
+import java.util.function.ToIntFunction;
import javax.annotation.Nullable;
import org.sonar.api.notifications.Notification;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.utils.DateUtils;
import org.sonar.api.utils.Duration;
import org.sonar.api.utils.Durations;
+import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.rule.RuleDefinitionDto;
}
public NewIssuesNotification setStatistics(String projectName, NewIssuesStatistics.Stats stats) {
- setDefaultMessage(stats.countForMetric(SEVERITY) + " new issues on " + projectName + ".\n");
+ setDefaultMessage(stats.getDistributedMetricStats(SEVERITY).getTotal() + " new issues on " + projectName + ".\n");
try (DbSession dbSession = dbClient.openSession(false)) {
setSeverityStatistics(stats);
return this;
}
- protected void setRuleStatistics(DbSession dbSession, NewIssuesStatistics.Stats stats) {
+ private void setRuleStatistics(DbSession dbSession, NewIssuesStatistics.Stats stats) {
Metric metric = Metric.RULE;
- List<Multiset.Entry<String>> metricStats = stats.statsForMetric(metric);
- for (int i = 0; i < 5 && i < metricStats.size(); i++) {
- String ruleKey = metricStats.get(i).getElement();
+ int i = 1;
+ for (Map.Entry<String, MetricStatsInt> ruleStats : fiveBiggest(stats.getDistributedMetricStats(metric), MetricStatsInt::getTotal)) {
+ String ruleKey = ruleStats.getKey();
RuleDefinitionDto rule = dbClient.ruleDao().selectOrFailDefinitionByKey(dbSession, RuleKey.parse(ruleKey));
String name = rule.getName() + " (" + rule.getLanguage() + ")";
- setFieldValue(metric + DOT + (i + 1) + LABEL, name);
- setFieldValue(metric + DOT + (i + 1) + COUNT, String.valueOf(metricStats.get(i).getCount()));
+ setFieldValue(metric + DOT + i + LABEL, name);
+ setFieldValue(metric + DOT + i + COUNT, String.valueOf(ruleStats.getValue().getTotal()));
+ i++;
}
}
- protected void setComponentsStatistics(DbSession dbSession, NewIssuesStatistics.Stats stats) {
+ private void setComponentsStatistics(DbSession dbSession, NewIssuesStatistics.Stats stats) {
Metric metric = Metric.COMPONENT;
- List<Multiset.Entry<String>> componentStats = stats.statsForMetric(metric);
- for (int i = 0; i < 5 && i < componentStats.size(); i++) {
- String uuid = componentStats.get(i).getElement();
+ int i = 1;
+ for (Map.Entry<String, MetricStatsInt> componentStats : fiveBiggest(stats.getDistributedMetricStats(metric), MetricStatsInt::getTotal)) {
+ String uuid = componentStats.getKey();
String componentName = dbClient.componentDao().selectOrFailByUuid(dbSession, uuid).name();
- setFieldValue(metric + DOT + (i + 1) + LABEL, componentName);
- setFieldValue(metric + DOT + (i + 1) + COUNT, String.valueOf(componentStats.get(i).getCount()));
+ setFieldValue(metric + DOT + i + LABEL, componentName);
+ setFieldValue(metric + DOT + i + COUNT, String.valueOf(componentStats.getValue().getTotal()));
+ i++;
}
}
- protected void setTagsStatistics(NewIssuesStatistics.Stats stats) {
+ private void setTagsStatistics(NewIssuesStatistics.Stats stats) {
Metric metric = Metric.TAG;
- List<Multiset.Entry<String>> metricStats = stats.statsForMetric(metric);
- for (int i = 0; i < 5 && i < metricStats.size(); i++) {
- setFieldValue(metric + DOT + (i + 1) + COUNT, String.valueOf(metricStats.get(i).getCount()));
- setFieldValue(metric + DOT + (i + 1) + ".label", metricStats.get(i).getElement());
+ int i = 1;
+ for (Map.Entry<String, MetricStatsInt> tagStats : fiveBiggest(stats.getDistributedMetricStats(metric), MetricStatsInt::getTotal)) {
+ setFieldValue(metric + DOT + i + COUNT, String.valueOf(tagStats.getValue().getTotal()));
+ setFieldValue(metric + DOT + i + ".label", tagStats.getKey());
+ i++;
}
}
- protected void setAssigneesStatistics(NewIssuesStatistics.Stats stats) {
+ private void setAssigneesStatistics(NewIssuesStatistics.Stats stats) {
Metric metric = Metric.ASSIGNEE;
- List<Multiset.Entry<String>> metricStats = stats.statsForMetric(metric);
- for (int i = 0; i < 5 && i < metricStats.size(); i++) {
- String login = metricStats.get(i).getElement();
+ ToIntFunction<MetricStatsInt> biggerCriteria = MetricStatsInt::getTotal;
+ int i = 1;
+ for (Map.Entry<String, MetricStatsInt> assigneeStats : fiveBiggest(stats.getDistributedMetricStats(metric), biggerCriteria)) {
+ String login = assigneeStats.getKey();
UserDoc user = userIndex.getNullableByLogin(login);
String name = user == null ? login : user.name();
- setFieldValue(metric + DOT + (i + 1) + LABEL, name);
- setFieldValue(metric + DOT + (i + 1) + COUNT, String.valueOf(metricStats.get(i).getCount()));
+ setFieldValue(metric + DOT + i + LABEL, name);
+ setFieldValue(metric + DOT + i + COUNT, String.valueOf(biggerCriteria.applyAsInt(assigneeStats.getValue())));
+ i++;
}
}
+ private static List<Map.Entry<String, MetricStatsInt>> fiveBiggest(DistributedMetricStatsInt distributedMetricStatsInt, ToIntFunction<MetricStatsInt> biggerCriteria) {
+ Comparator<Map.Entry<String, MetricStatsInt>> comparator = Comparator.comparingInt(a -> biggerCriteria.applyAsInt(a.getValue()));
+ return distributedMetricStatsInt.getForLabels()
+ .entrySet()
+ .stream()
+ .sorted(comparator.reversed())
+ .limit(5)
+ .collect(MoreCollectors.toList(5));
+ }
+
public NewIssuesNotification setDebt(Duration debt) {
- setFieldValue(Metric.DEBT + COUNT, durations.format(debt));
+ setFieldValue(Metric.EFFORT + COUNT, durations.format(debt));
return this;
}
- protected void setSeverityStatistics(NewIssuesStatistics.Stats stats) {
- setFieldValue(SEVERITY + COUNT, String.valueOf(stats.countForMetric(SEVERITY)));
+ private void setSeverityStatistics(NewIssuesStatistics.Stats stats) {
+ DistributedMetricStatsInt distributedMetricStats = stats.getDistributedMetricStats(SEVERITY);
+ setFieldValue(SEVERITY + COUNT, String.valueOf(distributedMetricStats.getTotal()));
for (String severity : Severity.ALL) {
- setFieldValue(SEVERITY + DOT + severity + COUNT, String.valueOf(stats.countForMetric(SEVERITY, severity)));
+ setFieldValue(
+ SEVERITY + DOT + severity + COUNT,
+ String.valueOf(distributedMetricStats.getForLabel(severity).map(MetricStatsInt::getTotal).orElse(0)));
}
}
*/
package org.sonar.server.issue.notification;
-import com.google.common.collect.HashMultiset;
-import com.google.common.collect.Multiset;
-import com.google.common.collect.Multisets;
import java.util.EnumMap;
import java.util.LinkedHashMap;
-import java.util.List;
import java.util.Map;
+import java.util.function.Predicate;
import org.sonar.api.issue.Issue;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.utils.Duration;
-import static com.google.common.base.Preconditions.checkArgument;
import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.ASSIGNEE;
import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.COMPONENT;
import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.RULE;
import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.TAG;
public class NewIssuesStatistics {
- private Map<String, Stats> assigneesStatistics = new LinkedHashMap<>();
- private Stats globalStatistics = new Stats();
+ private final Predicate<Issue> onLeakPredicate;
+ private final Map<String, Stats> assigneesStatistics = new LinkedHashMap<>();
+ private final Stats globalStatistics;
+
+ public NewIssuesStatistics(Predicate<Issue> onLeakPredicate) {
+ this.onLeakPredicate = onLeakPredicate;
+ this.globalStatistics = new Stats(onLeakPredicate);
+ }
public void add(Issue issue) {
globalStatistics.add(issue);
String login = issue.assignee();
if (login != null) {
- getOrCreate(login).add(issue);
+ assigneesStatistics.computeIfAbsent(login, a -> new Stats(onLeakPredicate)).add(issue);
}
}
- private Stats getOrCreate(String assignee) {
- return assigneesStatistics.computeIfAbsent(assignee, a -> new Stats());
- }
-
public Map<String, Stats> assigneesStatistics() {
return assigneesStatistics;
}
}
enum Metric {
- SEVERITY(true), TAG(true), COMPONENT(true), ASSIGNEE(true), DEBT(false), RULE(true);
+ SEVERITY(true), TAG(true), COMPONENT(true), ASSIGNEE(true), EFFORT(false), RULE(true);
private final boolean isComputedByDistribution;
Metric(boolean isComputedByDistribution) {
}
public static class Stats {
- private final Map<Metric, Multiset<String>> distributions = new EnumMap<>(Metric.class);
- private long debtInMinutes = 0L;
+ private final Predicate<Issue> onLeakPredicate;
+ private final Map<Metric, DistributedMetricStatsInt> distributions = new EnumMap<>(Metric.class);
+ private MetricStatsLong effortStats = new MetricStatsLong();
- public Stats() {
+ public Stats(Predicate<Issue> onLeakPredicate) {
+ this.onLeakPredicate = onLeakPredicate;
for (Metric metric : Metric.values()) {
if (metric.isComputedByDistribution()) {
- distributions.put(metric, HashMultiset.<String>create());
+ distributions.put(metric, new DistributedMetricStatsInt());
}
}
}
public void add(Issue issue) {
- distributions.get(SEVERITY).add(issue.severity());
- distributions.get(COMPONENT).add(issue.componentUuid());
+ boolean isOnLeak = onLeakPredicate.test(issue);
+ distributions.get(SEVERITY).increment(issue.severity(), isOnLeak);
+ distributions.get(COMPONENT).increment(issue.componentUuid(), isOnLeak);
RuleKey ruleKey = issue.ruleKey();
if (ruleKey != null) {
- distributions.get(RULE).add(ruleKey.toString());
+ distributions.get(RULE).increment(ruleKey.toString(), isOnLeak);
}
- if (issue.assignee() != null) {
- distributions.get(ASSIGNEE).add(issue.assignee());
+ String assignee = issue.assignee();
+ if (assignee != null) {
+ distributions.get(ASSIGNEE).increment(assignee, isOnLeak);
}
for (String tag : issue.tags()) {
- distributions.get(TAG).add(tag);
+ distributions.get(TAG).increment(tag, isOnLeak);
}
- Duration debt = issue.debt();
- if (debt != null) {
- debtInMinutes += debt.toMinutes();
+ Duration effort = issue.effort();
+ if (effort != null) {
+ effortStats.add(effort.toMinutes(), isOnLeak);
}
}
- public int countForMetric(Metric metric) {
- return distributionFor(metric).size();
- }
-
- public int countForMetric(Metric metric, String label) {
- return distributionFor(metric).count(label);
+ public DistributedMetricStatsInt getDistributedMetricStats(Metric metric) {
+ return distributions.get(metric);
}
- public Duration debt() {
- return Duration.create(debtInMinutes);
+ public MetricStatsLong effort() {
+ return effortStats;
}
public boolean hasIssues() {
- return !distributionFor(SEVERITY).isEmpty();
- }
-
- public List<Multiset.Entry<String>> statsForMetric(Metric metric) {
- return Multisets.copyHighestCountFirst(distributionFor(metric)).entrySet().asList();
+ return getDistributedMetricStats(SEVERITY).getTotal() > 0;
}
- private Multiset<String> distributionFor(Metric metric) {
- checkArgument(metric.isComputedByDistribution());
- return distributions.get(metric);
- }
}
+
}
@Test
public void send_global_new_issues_notification() throws Exception {
issueCache.newAppender().append(
- new DefaultIssue().setSeverity(Severity.BLOCKER).setEffort(ISSUE_DURATION)).close();
+ new DefaultIssue().setSeverity(Severity.BLOCKER).setEffort(ISSUE_DURATION)
+ .setCreationDate(new Date(ANALYSE_DATE)))
+ .close();
when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), SendIssueNotificationsStep.NOTIF_TYPES)).thenReturn(true);
ComponentDto branch = newProjectBranch(project, newBranchDto(project).setKey(BRANCH_NAME));
ComponentDto file = newFileDto(branch);
treeRootHolder.setRoot(builder(Type.PROJECT, 2).setKey(branch.getDbKey()).setPublicKey(branch.getKey()).setName(branch.longName()).setUuid(branch.uuid()).addChildren(
- builder(Component.Type.FILE, 11).setKey(file.getDbKey()).setPublicKey(file.getKey()).setName(file.longName()).build()
- ).build());
+ builder(Component.Type.FILE, 11).setKey(file.getDbKey()).setPublicKey(file.getKey()).setName(file.longName()).build()).build());
issueCache.newAppender().append(
new DefaultIssue().setSeverity(Severity.BLOCKER).setEffort(ISSUE_DURATION)).close();
@Test
public void send_new_issues_notification_to_user() throws Exception {
issueCache.newAppender().append(
- new DefaultIssue().setSeverity(Severity.BLOCKER).setEffort(ISSUE_DURATION).setAssignee(ISSUE_ASSIGNEE)).close();
+ new DefaultIssue().setSeverity(Severity.BLOCKER).setEffort(ISSUE_DURATION).setAssignee(ISSUE_ASSIGNEE)
+ .setCreationDate(new Date(ANALYSE_DATE)))
+ .close();
when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), SendIssueNotificationsStep.NOTIF_TYPES)).thenReturn(true);
ComponentDto file = newFileDto(project).setDbKey(FILE.getKey()).setLongName(FILE.getName());
RuleDefinitionDto ruleDefinitionDto = newRule();
DefaultIssue issue = newIssue(ruleDefinitionDto, project, file).toDefaultIssue()
- .setNew(false)
- .setChanged(true)
- .setSendNotifications(true);
+ .setNew(false).setChanged(true).setSendNotifications(true).setCreationDate(new Date(ANALYSE_DATE));
ruleRepository.add(ruleDefinitionDto.getKey()).setName(ruleDefinitionDto.getName());
issueCache.newAppender().append(issue).close();
when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), SendIssueNotificationsStep.NOTIF_TYPES)).thenReturn(true);
ComponentDto branch = newProjectBranch(project, newBranchDto(project).setKey(BRANCH_NAME));
ComponentDto file = newFileDto(branch);
treeRootHolder.setRoot(builder(Type.PROJECT, 2).setKey(branch.getDbKey()).setPublicKey(branch.getKey()).setName(branch.longName()).setUuid(branch.uuid()).addChildren(
- builder(Component.Type.FILE, 11).setKey(file.getDbKey()).setPublicKey(file.getKey()).setName(file.longName()).build()
- ).build());
+ builder(Component.Type.FILE, 11).setKey(file.getDbKey()).setPublicKey(file.getKey()).setName(file.longName()).build()).build());
RuleDefinitionDto ruleDefinitionDto = newRule();
DefaultIssue issue = newIssue(ruleDefinitionDto, branch, file).toDefaultIssue()
.setNew(false)
package org.sonar.server.issue.notification;
import java.io.IOException;
-import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Locale;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.COMPONENT;
-import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.DEBT;
+import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.EFFORT;
import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.RULE;
import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.SEVERITY;
import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.TAG;
.setFieldValue("projectUuid", "ABCDE")
.setFieldValue("projectDate", "2010-05-18T14:50:45+0000")
.setFieldValue("assignee", "lo.gin")
- .setFieldValue(DEBT + ".count", "1d3h")
+ .setFieldValue(EFFORT + ".count", "1d3h")
.setFieldValue(SEVERITY + ".count", "32")
.setFieldValue(SEVERITY + ".INFO.count", "1")
.setFieldValue(SEVERITY + ".MINOR.count", "3")
import java.io.IOException;
import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang.StringUtils;
import org.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import static org.mockito.Mockito.when;
import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.ASSIGNEE;
import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.COMPONENT;
-import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.DEBT;
+import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.EFFORT;
import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.RULE;
import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.SEVERITY;
import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.TAG;
.setFieldValue("projectKey", "org.apache:struts")
.setFieldValue("projectUuid", "ABCDE")
.setFieldValue("projectDate", "2010-05-18T14:50:45+0000")
- .setFieldValue(DEBT + ".count", "1d3h")
+ .setFieldValue(EFFORT + ".count", "1d3h")
.setFieldValue(SEVERITY + ".count", "32")
.setFieldValue(SEVERITY + ".INFO.count", "1")
.setFieldValue(SEVERITY + ".MINOR.count", "3")
import static org.mockito.Mockito.when;
import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.ASSIGNEE;
import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.COMPONENT;
-import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.DEBT;
+import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.EFFORT;
import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.RULE;
import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.SEVERITY;
import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.TAG;
public class NewIssuesNotificationTest {
- NewIssuesStatistics.Stats stats = new NewIssuesStatistics.Stats();
+ 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);
underTest.setDebt(Duration.create(55));
- assertThat(underTest.getFieldValue(DEBT + ".count")).isEqualTo("55 min");
+ assertThat(underTest.getFieldValue(EFFORT + ".count")).isEqualTo("55 min");
}
private void addIssueNTimes(DefaultIssue issue, int times) {
package org.sonar.server.issue.notification;
import com.google.common.collect.Lists;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.Random;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import javax.annotation.CheckForNull;
import org.junit.Test;
+import org.sonar.api.issue.Issue;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.Severity;
import org.sonar.api.utils.Duration;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.server.issue.notification.NewIssuesStatistics.Metric;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
import static org.assertj.core.api.Assertions.assertThat;
public class NewIssuesStatisticsTest {
- NewIssuesStatistics underTest = new NewIssuesStatistics();
+ NewIssuesStatistics underTest = new NewIssuesStatistics(Issue::isNew);
@Test
public void add_issues_with_correct_global_statistics() {
- DefaultIssue issue = defaultIssue();
+ DefaultIssue issue = 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));
underTest.add(issue);
underTest.add(issue.setAssignee("james"));
underTest.add(issue.setAssignee("keenan"));
- assertThat(countDistribution(Metric.ASSIGNEE, "maynard")).isEqualTo(1);
- assertThat(countDistribution(Metric.ASSIGNEE, "james")).isEqualTo(1);
- assertThat(countDistribution(Metric.ASSIGNEE, "keenan")).isEqualTo(1);
- assertThat(countDistribution(Metric.ASSIGNEE, "wrong.login")).isEqualTo(0);
- assertThat(countDistribution(Metric.COMPONENT, "file-uuid")).isEqualTo(3);
- assertThat(countDistribution(Metric.COMPONENT, "wrong-uuid")).isEqualTo(0);
- assertThat(countDistribution(Metric.SEVERITY, Severity.INFO)).isEqualTo(3);
- assertThat(countDistribution(Metric.SEVERITY, Severity.CRITICAL)).isEqualTo(0);
- assertThat(countDistribution(Metric.TAG, "owasp")).isEqualTo(3);
- assertThat(countDistribution(Metric.TAG, "wrong-tag")).isEqualTo(0);
- assertThat(countDistribution(Metric.RULE, "SonarQube:rule-the-world")).isEqualTo(3);
- assertThat(countDistribution(Metric.RULE, "SonarQube:has-a-fake-rule")).isEqualTo(0);
- assertThat(underTest.globalStatistics().debt().toMinutes()).isEqualTo(15L);
+ assertThat(countDistributionTotal(Metric.ASSIGNEE, "maynard")).isEqualTo(1);
+ assertThat(countDistributionTotal(Metric.ASSIGNEE, "james")).isEqualTo(1);
+ assertThat(countDistributionTotal(Metric.ASSIGNEE, "keenan")).isEqualTo(1);
+ assertThat(countDistributionTotal(Metric.ASSIGNEE, "wrong.login")).isNull();
+ assertThat(countDistributionTotal(Metric.COMPONENT, "file-uuid")).isEqualTo(3);
+ assertThat(countDistributionTotal(Metric.COMPONENT, "wrong-uuid")).isNull();
+ assertThat(countDistributionTotal(Metric.SEVERITY, Severity.INFO)).isEqualTo(3);
+ assertThat(countDistributionTotal(Metric.SEVERITY, Severity.CRITICAL)).isNull();
+ assertThat(countDistributionTotal(Metric.TAG, "owasp")).isEqualTo(3);
+ assertThat(countDistributionTotal(Metric.TAG, "wrong-tag")).isNull();
+ assertThat(countDistributionTotal(Metric.RULE, "SonarQube:rule-the-world")).isEqualTo(3);
+ assertThat(countDistributionTotal(Metric.RULE, "SonarQube:has-a-fake-rule")).isNull();
+ assertThat(underTest.globalStatistics().effort().getTotal()).isEqualTo(15L);
assertThat(underTest.globalStatistics().hasIssues()).isTrue();
assertThat(underTest.hasIssues()).isTrue();
assertThat(underTest.assigneesStatistics().get("maynard").hasIssues()).isTrue();
}
+ @Test
+ public void add_counts_issue_per_severity_on_leak_globally_and_per_assignee() {
+ String assignee = randomAlphanumeric(10);
+ Severity.ALL.stream()
+ .map(severity -> new DefaultIssue().setSeverity(severity).setAssignee(assignee).setNew(true))
+ .forEach(underTest::add);
+
+ DistributedMetricStatsInt globalDistribution = underTest.globalStatistics().getDistributedMetricStats(Metric.SEVERITY);
+ DistributedMetricStatsInt assigneeDistribution = underTest.assigneesStatistics().get(assignee).getDistributedMetricStats(Metric.SEVERITY);
+ Stream.of(globalDistribution, assigneeDistribution)
+ .forEach(distribution -> {
+ assertStats(distribution, Severity.INFO, 1, 0, 1);
+ assertStats(distribution, Severity.MAJOR, 1, 0, 1);
+ assertStats(distribution, Severity.CRITICAL, 1, 0, 1);
+ assertStats(distribution, Severity.MINOR, 1, 0, 1);
+ assertStats(distribution, Severity.BLOCKER, 1, 0, 1);
+ });
+ }
+
+ @Test
+ public void add_counts_issue_per_severity_off_leak_globally_and_per_assignee() {
+ String assignee = randomAlphanumeric(10);
+ Severity.ALL.stream()
+ .map(severity -> new DefaultIssue().setSeverity(severity).setAssignee(assignee).setNew(false))
+ .forEach(underTest::add);
+
+ DistributedMetricStatsInt globalDistribution = underTest.globalStatistics().getDistributedMetricStats(Metric.SEVERITY);
+ DistributedMetricStatsInt assigneeDistribution = underTest.assigneesStatistics().get(assignee).getDistributedMetricStats(Metric.SEVERITY);
+ Stream.of(globalDistribution, assigneeDistribution)
+ .forEach(distribution -> {
+ assertStats(distribution, Severity.INFO, 0, 1, 1);
+ assertStats(distribution, Severity.MAJOR, 0, 1, 1);
+ assertStats(distribution, Severity.CRITICAL, 0, 1, 1);
+ assertStats(distribution, Severity.MINOR, 0, 1, 1);
+ assertStats(distribution, Severity.BLOCKER, 0, 1, 1);
+ });
+ }
+
+ @Test
+ public void add_counts_severity_if_null_globally_and_per_assignee_as_it_should_not_be_null() {
+ String assignee = randomAlphanumeric(10);
+ underTest.add(new DefaultIssue().setSeverity(null).setAssignee(assignee).setNew(new Random().nextBoolean()));
+
+ DistributedMetricStatsInt globalDistribution = underTest.globalStatistics().getDistributedMetricStats(Metric.SEVERITY);
+ DistributedMetricStatsInt assigneeDistribution = underTest.assigneesStatistics().get(assignee).getDistributedMetricStats(Metric.SEVERITY);
+ Stream.of(globalDistribution, assigneeDistribution)
+ .forEach(distribution -> {
+ assertThat(distribution.getTotal()).isEqualTo(1);
+ assertThat(distribution.getForLabel(null).isPresent()).isTrue();
+ });
+ }
+
+ @Test
+ public void add_counts_issue_per_component_on_leak_globally_and_per_assignee() {
+ List<String> componentUuids = IntStream.range(0, 1 + new Random().nextInt(10)).mapToObj(i -> randomAlphabetic(3)).collect(Collectors.toList());
+ String assignee = randomAlphanumeric(10);
+ componentUuids.stream()
+ .map(componentUuid -> new DefaultIssue().setComponentUuid(componentUuid).setAssignee(assignee).setNew(true))
+ .forEach(underTest::add);
+
+ DistributedMetricStatsInt globalDistribution = underTest.globalStatistics().getDistributedMetricStats(Metric.COMPONENT);
+ DistributedMetricStatsInt assigneeDistribution = underTest.assigneesStatistics().get(assignee).getDistributedMetricStats(Metric.COMPONENT);
+ Stream.of(globalDistribution, assigneeDistribution)
+ .forEach(distribution -> componentUuids.forEach(componentUuid -> assertStats(distribution, componentUuid, 1, 0, 1)));
+ }
+
+ @Test
+ public void add_counts_issue_per_component_off_leak_globally_and_per_assignee() {
+ List<String> componentUuids = IntStream.range(0, 1 + new Random().nextInt(10)).mapToObj(i -> randomAlphabetic(3)).collect(Collectors.toList());
+ String assignee = randomAlphanumeric(10);
+ componentUuids.stream()
+ .map(componentUuid -> new DefaultIssue().setComponentUuid(componentUuid).setAssignee(assignee).setNew(false))
+ .forEach(underTest::add);
+
+ DistributedMetricStatsInt globalDistribution = underTest.globalStatistics().getDistributedMetricStats(Metric.COMPONENT);
+ NewIssuesStatistics.Stats stats = underTest.assigneesStatistics().get(assignee);
+ DistributedMetricStatsInt assigneeDistribution = stats.getDistributedMetricStats(Metric.COMPONENT);
+ Stream.of(globalDistribution, assigneeDistribution)
+ .forEach(distribution -> componentUuids.forEach(componentUuid -> assertStats(distribution, componentUuid, 0, 1, 1)));
+ }
+
+ @Test
+ public void add_counts_component_if_null_globally_and_per_assignee_as_it_should_not_be_null() {
+ String assignee = randomAlphanumeric(10);
+ underTest.add(new DefaultIssue().setComponentUuid(null).setAssignee(assignee).setNew(new Random().nextBoolean()));
+
+ DistributedMetricStatsInt globalDistribution = underTest.globalStatistics().getDistributedMetricStats(Metric.COMPONENT);
+ DistributedMetricStatsInt assigneeDistribution = underTest.assigneesStatistics().get(assignee).getDistributedMetricStats(Metric.COMPONENT);
+ Stream.of(globalDistribution, assigneeDistribution)
+ .forEach(distribution -> {
+ assertThat(distribution.getTotal()).isEqualTo(1);
+ assertThat(distribution.getForLabel(null).isPresent()).isTrue();
+ });
+ }
+
+ @Test
+ public void add_counts_issue_per_ruleKey_on_leak_globally_and_per_assignee() {
+ String repository = randomAlphanumeric(3);
+ List<String> ruleKeys = IntStream.range(0, 1 + new Random().nextInt(10)).mapToObj(i -> randomAlphabetic(3)).collect(Collectors.toList());
+ String assignee = randomAlphanumeric(10);
+ ruleKeys.stream()
+ .map(ruleKey -> new DefaultIssue().setRuleKey(RuleKey.of(repository, ruleKey)).setAssignee(assignee).setNew(true))
+ .forEach(underTest::add);
+
+ DistributedMetricStatsInt globalDistribution = underTest.globalStatistics().getDistributedMetricStats(Metric.RULE);
+ NewIssuesStatistics.Stats stats = underTest.assigneesStatistics().get(assignee);
+ DistributedMetricStatsInt assigneeDistribution = stats.getDistributedMetricStats(Metric.RULE);
+ Stream.of(globalDistribution, assigneeDistribution)
+ .forEach(distribution -> ruleKeys.forEach(ruleKey -> assertStats(distribution, RuleKey.of(repository, ruleKey).toString(), 1, 0, 1)));
+ }
+
+ @Test
+ public void add_counts_issue_per_ruleKey_off_leak_globally_and_per_assignee() {
+ String repository = randomAlphanumeric(3);
+ List<String> ruleKeys = IntStream.range(0, 1 + new Random().nextInt(10)).mapToObj(i -> randomAlphabetic(3)).collect(Collectors.toList());
+ String assignee = randomAlphanumeric(10);
+ ruleKeys.stream()
+ .map(ruleKey -> new DefaultIssue().setRuleKey(RuleKey.of(repository, ruleKey)).setAssignee(assignee).setNew(false))
+ .forEach(underTest::add);
+
+ DistributedMetricStatsInt globalDistribution = underTest.globalStatistics().getDistributedMetricStats(Metric.RULE);
+ DistributedMetricStatsInt assigneeDistribution = underTest.assigneesStatistics().get(assignee).getDistributedMetricStats(Metric.RULE);
+ Stream.of(globalDistribution, assigneeDistribution)
+ .forEach(distribution -> ruleKeys.forEach(ruleKey -> assertStats(distribution, RuleKey.of(repository, ruleKey).toString(), 0, 1, 1)));
+ }
+
+ @Test
+ public void add_does_not_count_ruleKey_if_neither_neither_globally_nor_per_assignee() {
+ String assignee = randomAlphanumeric(10);
+ underTest.add(new DefaultIssue().setRuleKey(null).setAssignee(assignee).setNew(new Random().nextBoolean()));
+
+ DistributedMetricStatsInt globalDistribution = underTest.globalStatistics().getDistributedMetricStats(Metric.RULE);
+ DistributedMetricStatsInt assigneeDistribution = underTest.assigneesStatistics().get(assignee).getDistributedMetricStats(Metric.RULE);
+ Stream.of(globalDistribution, assigneeDistribution)
+ .forEach(distribution -> {
+ assertThat(distribution.getTotal()).isEqualTo(0);
+ assertThat(distribution.getForLabel(null).isPresent()).isFalse();
+ });
+ }
+
+ @Test
+ public void add_counts_issue_per_assignee_on_leak_globally_and_per_assignee() {
+ List<String> assignees = IntStream.range(0, 1 + new Random().nextInt(10)).mapToObj(i -> randomAlphabetic(3)).collect(Collectors.toList());
+ assignees.stream()
+ .map(assignee -> new DefaultIssue().setAssignee(assignee).setNew(true))
+ .forEach(underTest::add);
+
+ DistributedMetricStatsInt globalDistribution = underTest.globalStatistics().getDistributedMetricStats(Metric.ASSIGNEE);
+ assignees.forEach(assignee -> assertStats(globalDistribution, assignee, 1, 0, 1));
+ assignees.forEach(assignee -> {
+ NewIssuesStatistics.Stats stats = underTest.assigneesStatistics().get(assignee);
+ DistributedMetricStatsInt assigneeStats = stats.getDistributedMetricStats(Metric.ASSIGNEE);
+ assertThat(assigneeStats.getOnLeak()).isEqualTo(1);
+ assertThat(assigneeStats.getOffLeak()).isEqualTo(0);
+ assertThat(assigneeStats.getTotal()).isEqualTo(1);
+ assignees.forEach(s -> {
+ Optional<MetricStatsInt> forLabelOpts = assigneeStats.getForLabel(s);
+ if (s.equals(assignee)) {
+ assertThat(forLabelOpts.isPresent()).isTrue();
+ MetricStatsInt forLabel = forLabelOpts.get();
+ assertThat(forLabel.getOnLeak()).isEqualTo(1);
+ assertThat(forLabel.getOffLeak()).isEqualTo(0);
+ assertThat(forLabel.getTotal()).isEqualTo(1);
+ } else {
+ assertThat(forLabelOpts.isPresent()).isFalse();
+ }
+ });
+ });
+ }
+
+ @Test
+ public void add_counts_issue_per_assignee_off_leak_globally_and_per_assignee() {
+ List<String> assignees = IntStream.range(0, 1 + new Random().nextInt(10)).mapToObj(i -> randomAlphabetic(3)).collect(Collectors.toList());
+ assignees.stream()
+ .map(assignee -> new DefaultIssue().setAssignee(assignee).setNew(false))
+ .forEach(underTest::add);
+
+ DistributedMetricStatsInt globalDistribution = underTest.globalStatistics().getDistributedMetricStats(Metric.ASSIGNEE);
+ assignees.forEach(assignee -> assertStats(globalDistribution, assignee, 0, 1, 1));
+ assignees.forEach(assignee -> {
+ NewIssuesStatistics.Stats stats = underTest.assigneesStatistics().get(assignee);
+ DistributedMetricStatsInt assigneeStats = stats.getDistributedMetricStats(Metric.ASSIGNEE);
+ assertThat(assigneeStats.getOnLeak()).isEqualTo(0);
+ assertThat(assigneeStats.getOffLeak()).isEqualTo(1);
+ assertThat(assigneeStats.getTotal()).isEqualTo(1);
+ assignees.forEach(s -> {
+ Optional<MetricStatsInt> forLabelOpts = assigneeStats.getForLabel(s);
+ if (s.equals(assignee)) {
+ assertThat(forLabelOpts.isPresent()).isTrue();
+ MetricStatsInt forLabel = forLabelOpts.get();
+ assertThat(forLabel.getOnLeak()).isEqualTo(0);
+ assertThat(forLabel.getOffLeak()).isEqualTo(1);
+ assertThat(forLabel.getTotal()).isEqualTo(1);
+ } else {
+ assertThat(forLabelOpts.isPresent()).isFalse();
+ }
+ });
+ });
+ }
+
+ @Test
+ public void add_does_not_assignee_if_empty_neither_globally_nor_per_assignee() {
+ underTest.add(new DefaultIssue().setAssignee(null).setNew(new Random().nextBoolean()));
+
+ DistributedMetricStatsInt globalDistribution = underTest.globalStatistics().getDistributedMetricStats(Metric.ASSIGNEE);
+ assertThat(globalDistribution.getTotal()).isEqualTo(0);
+ assertThat(globalDistribution.getForLabel(null).isPresent()).isFalse();
+ assertThat(underTest.assigneesStatistics()).isEmpty();
+ }
+
+ @Test
+ public void add_counts_issue_per_tags_on_leak_globally_and_per_assignee() {
+ List<String> tags = IntStream.range(0, 1 + new Random().nextInt(10)).mapToObj(i -> randomAlphabetic(3)).collect(Collectors.toList());
+ String assignee = randomAlphanumeric(10);
+ underTest.add(new DefaultIssue().setTags(tags).setAssignee(assignee).setNew(true));
+
+ DistributedMetricStatsInt globalDistribution = underTest.globalStatistics().getDistributedMetricStats(Metric.TAG);
+ DistributedMetricStatsInt assigneeDistribution = underTest.assigneesStatistics().get(assignee).getDistributedMetricStats(Metric.TAG);
+ Stream.of(globalDistribution, assigneeDistribution)
+ .forEach(distribution -> tags.forEach(tag -> assertStats(distribution, tag, 1, 0, 1)));
+ }
+
+ @Test
+ public void add_counts_issue_per_tags_off_leak_globally_and_per_assignee() {
+ List<String> tags = IntStream.range(0, 1 + new Random().nextInt(10)).mapToObj(i -> randomAlphabetic(3)).collect(Collectors.toList());
+ String assignee = randomAlphanumeric(10);
+ underTest.add(new DefaultIssue().setTags(tags).setAssignee(assignee).setNew(false));
+
+ DistributedMetricStatsInt globalDistribution = underTest.globalStatistics().getDistributedMetricStats(Metric.TAG);
+ DistributedMetricStatsInt assigneeDistribution = underTest.assigneesStatistics().get(assignee).getDistributedMetricStats(Metric.TAG);
+ Stream.of(globalDistribution, assigneeDistribution)
+ .forEach(distribution -> tags.forEach(tag -> assertStats(distribution, tag, 0, 1, 1)));
+ }
+
+ @Test
+ public void add_does_not_count_tags_if_empty_neither_globally_nor_per_assignee() {
+ String assignee = randomAlphanumeric(10);
+ underTest.add(new DefaultIssue().setTags(Collections.emptyList()).setAssignee(assignee).setNew(new Random().nextBoolean()));
+
+ DistributedMetricStatsInt globalDistribution = underTest.globalStatistics().getDistributedMetricStats(Metric.TAG);
+ DistributedMetricStatsInt assigneeDistribution = underTest.assigneesStatistics().get(assignee).getDistributedMetricStats(Metric.TAG);
+ Stream.of(globalDistribution, assigneeDistribution)
+ .forEach(distribution -> {
+ assertThat(distribution.getTotal()).isEqualTo(0);
+ assertThat(distribution.getForLabel(null).isPresent()).isFalse();
+ });
+ }
+
+ @Test
+ public void add_sums_effort_on_leak_globally_and_per_assignee() {
+ Random random = new Random();
+ List<Integer> efforts = IntStream.range(0, 1 + random.nextInt(10)).mapToObj(i -> 10_000 * i).collect(Collectors.toList());
+ int expected = efforts.stream().mapToInt(s -> s).sum();
+ String assignee = randomAlphanumeric(10);
+ efforts.stream()
+ .map(effort -> new DefaultIssue().setEffort(Duration.create(effort)).setAssignee(assignee).setNew(true))
+ .forEach(underTest::add);
+
+ MetricStatsLong globalDistribution = underTest.globalStatistics().effort();
+ MetricStatsLong assigneeDistribution = underTest.assigneesStatistics().get(assignee).effort();
+ Stream.of(globalDistribution, assigneeDistribution)
+ .forEach(distribution -> {
+ assertThat(distribution.getOnLeak()).isEqualTo(expected);
+ assertThat(distribution.getOffLeak()).isEqualTo(0);
+ assertThat(distribution.getTotal()).isEqualTo(expected);
+ });
+ }
+
+ @Test
+ public void add_sums_effort_off_leak_globally_and_per_assignee() {
+ Random random = new Random();
+ List<Integer> efforts = IntStream.range(0, 1 + random.nextInt(10)).mapToObj(i -> 10_000 * i).collect(Collectors.toList());
+ int expected = efforts.stream().mapToInt(s -> s).sum();
+ String assignee = randomAlphanumeric(10);
+ efforts.stream()
+ .map(effort -> new DefaultIssue().setEffort(Duration.create(effort)).setAssignee(assignee).setNew(false))
+ .forEach(underTest::add);
+
+ MetricStatsLong globalDistribution = underTest.globalStatistics().effort();
+ MetricStatsLong assigneeDistribution = underTest.assigneesStatistics().get(assignee).effort();
+ Stream.of(globalDistribution, assigneeDistribution)
+ .forEach(distribution -> {
+ assertThat(distribution.getOnLeak()).isEqualTo(0);
+ assertThat(distribution.getOffLeak()).isEqualTo(expected);
+ assertThat(distribution.getTotal()).isEqualTo(expected);
+ });
+ }
+
+ @Test
+ public void add_does_not_sum_effort_if_null_neither_globally_nor_per_assignee() {
+ String assignee = randomAlphanumeric(10);
+ underTest.add(new DefaultIssue().setEffort(null).setAssignee(assignee).setNew(new Random().nextBoolean()));
+
+ MetricStatsLong globalDistribution = underTest.globalStatistics().effort();
+ MetricStatsLong assigneeDistribution = underTest.assigneesStatistics().get(assignee).effort();
+ Stream.of(globalDistribution, assigneeDistribution)
+ .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);
+ Severity.ALL.stream().map(severity -> new DefaultIssue()
+ .setSeverity(severity)
+ .setAssignee(assignee)).forEach(underTest::add);
+
+ assertThat(underTest.globalStatistics()
+ .getDistributedMetricStats(Metric.SEVERITY)
+ .getForLabel(Severity.INFO)
+ .map(MetricStatsInt::getTotal)
+ .orElse(null)).isEqualTo(1);
+ assertThat(countDistributionTotal(Metric.SEVERITY, Severity.MINOR)).isEqualTo(1);
+ assertThat(countDistributionTotal(Metric.SEVERITY, Severity.CRITICAL)).isEqualTo(1);
+ assertThat(countDistributionTotal(Metric.SEVERITY, Severity.BLOCKER)).isEqualTo(1);
+ assertThat(countDistributionTotal(Metric.SEVERITY, Severity.MAJOR)).isEqualTo(1);
+ }
+
@Test
public void do_not_have_issues_when_no_issue_added() {
assertThat(underTest.globalStatistics().hasIssues()).isFalse();
}
- private int countDistribution(Metric metric, String label) {
- return underTest.globalStatistics().countForMetric(metric, label);
+ @CheckForNull
+ private Integer countDistributionTotal(Metric metric, String label) {
+ return underTest.globalStatistics()
+ .getDistributedMetricStats(metric)
+ .getForLabel(label)
+ .map(MetricStatsInt::getTotal)
+ .orElse(null);
+ }
+
+ @CheckForNull
+ private Integer countDistributionOnLeak(Metric metric, String label) {
+ return underTest.globalStatistics()
+ .getDistributedMetricStats(metric)
+ .getForLabel(label)
+ .map(MetricStatsInt::getOnLeak)
+ .orElse(null);
+ }
+
+ @CheckForNull
+ private Integer countDistributionOffLeak(Metric metric, String label) {
+ return underTest.globalStatistics()
+ .getDistributedMetricStats(metric)
+ .getForLabel(label)
+ .map(MetricStatsInt::getOffLeak)
+ .orElse(null);
}
private DefaultIssue defaultIssue() {