3 * Copyright (C) 2009-2019 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package org.sonar.server.issue.notification;
22 import java.util.Arrays;
23 import java.util.Comparator;
24 import java.util.Date;
25 import java.util.List;
27 import java.util.Objects;
28 import java.util.Optional;
29 import java.util.function.ToIntFunction;
30 import javax.annotation.CheckForNull;
31 import javax.annotation.Nullable;
32 import javax.annotation.concurrent.Immutable;
33 import org.sonar.api.notifications.Notification;
34 import org.sonar.api.rule.RuleKey;
35 import org.sonar.api.rules.RuleType;
36 import org.sonar.api.utils.DateUtils;
37 import org.sonar.api.utils.Duration;
38 import org.sonar.api.utils.Durations;
39 import org.sonar.core.util.stream.MoreCollectors;
40 import org.sonar.server.issue.notification.NewIssuesStatistics.Metric;
42 import static java.util.Objects.requireNonNull;
43 import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_BRANCH;
44 import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_PROJECT_VERSION;
45 import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_PULL_REQUEST;
46 import static org.sonar.server.issue.notification.NewIssuesEmailTemplate.FIELD_PROJECT_DATE;
47 import static org.sonar.server.issue.notification.NewIssuesEmailTemplate.FIELD_PROJECT_KEY;
48 import static org.sonar.server.issue.notification.NewIssuesEmailTemplate.FIELD_PROJECT_NAME;
49 import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.RULE_TYPE;
51 public class NewIssuesNotification extends Notification {
53 public static final String TYPE = "new-issues";
54 private static final long serialVersionUID = -6305871981920103093L;
55 private static final String COUNT = ".count";
56 private static final String LABEL = ".label";
57 private static final String DOT = ".";
59 private final transient DetailsSupplier detailsSupplier;
60 private final transient Durations durations;
62 public NewIssuesNotification(Durations durations, DetailsSupplier detailsSupplier) {
63 this(TYPE, durations, detailsSupplier);
66 protected NewIssuesNotification(String type, Durations durations, DetailsSupplier detailsSupplier) {
68 this.durations = durations;
69 this.detailsSupplier = detailsSupplier;
72 public interface DetailsSupplier {
74 * @throws NullPointerException if {@code ruleKey} is {@code null}
76 Optional<RuleDefinition> getRuleDefinitionByRuleKey(RuleKey ruleKey);
79 * @throws NullPointerException if {@code uuid} is {@code null}
81 Optional<String> getComponentNameByUuid(String uuid);
84 * @throws NullPointerException if {@code uuid} is {@code null}
86 Optional<String> getUserNameByUuid(String uuid);
90 public static final class RuleDefinition {
91 private final String name;
92 private final String language;
94 public RuleDefinition(String name, @Nullable String language) {
95 this.name = requireNonNull(name, "name can't be null");
96 this.language = language;
99 public String getName() {
104 public String getLanguage() {
109 public boolean equals(Object o) {
113 if (o == null || getClass() != o.getClass()) {
116 RuleDefinition that = (RuleDefinition) o;
117 return name.equals(that.name) && Objects.equals(language, that.language);
121 public int hashCode() {
122 return Objects.hash(name, language);
126 public String toString() {
127 return "RuleDefinition{" + name + " (" + language + ')' + '}';
131 public NewIssuesNotification setAnalysisDate(Date d) {
132 setFieldValue(FIELD_PROJECT_DATE, DateUtils.formatDateTime(d));
136 public NewIssuesNotification setProject(String projectKey, String projectName, @Nullable String branchName, @Nullable String pullRequest) {
137 setFieldValue(FIELD_PROJECT_NAME, projectName);
138 setFieldValue(FIELD_PROJECT_KEY, projectKey);
139 if (branchName != null) {
140 setFieldValue(FIELD_BRANCH, branchName);
142 if (pullRequest != null) {
143 setFieldValue(FIELD_PULL_REQUEST, pullRequest);
149 public String getProjectKey() {
150 return getFieldValue(FIELD_PROJECT_KEY);
153 public NewIssuesNotification setProjectVersion(@Nullable String version) {
154 if (version != null) {
155 setFieldValue(FIELD_PROJECT_VERSION, version);
160 public NewIssuesNotification setStatistics(String projectName, NewIssuesStatistics.Stats stats) {
161 setDefaultMessage(stats.getDistributedMetricStats(RULE_TYPE).getOnLeak() + " new issues on " + projectName + ".\n");
163 setRuleTypeStatistics(stats);
164 setAssigneesStatistics(stats);
165 setTagsStatistics(stats);
166 setComponentsStatistics(stats);
167 setRuleStatistics(stats);
172 private void setRuleStatistics(NewIssuesStatistics.Stats stats) {
173 Metric metric = Metric.RULE;
174 List<Map.Entry<String, MetricStatsInt>> fiveBiggest = fiveBiggest(stats.getDistributedMetricStats(metric), MetricStatsInt::getOnLeak);
176 for (Map.Entry<String, MetricStatsInt> ruleStats : fiveBiggest) {
177 String ruleKey = ruleStats.getKey();
178 RuleDefinition rule = detailsSupplier.getRuleDefinitionByRuleKey(RuleKey.parse(ruleKey))
179 .orElseThrow(() -> new IllegalStateException(String.format("Rule with key '%s' does not exist", ruleKey)));
180 String name = rule.getName() + " (" + rule.getLanguage() + ")";
181 setFieldValue(metric + DOT + i + LABEL, name);
182 setFieldValue(metric + DOT + i + COUNT, String.valueOf(ruleStats.getValue().getOnLeak()));
187 private void setComponentsStatistics(NewIssuesStatistics.Stats stats) {
188 Metric metric = Metric.COMPONENT;
190 List<Map.Entry<String, MetricStatsInt>> fiveBiggest = fiveBiggest(stats.getDistributedMetricStats(metric), MetricStatsInt::getOnLeak);
191 for (Map.Entry<String, MetricStatsInt> componentStats : fiveBiggest) {
192 String uuid = componentStats.getKey();
193 String componentName = detailsSupplier.getComponentNameByUuid(uuid)
194 .orElseThrow(() -> new IllegalStateException(String.format("Component with uuid '%s' not found", uuid)));
195 setFieldValue(metric + DOT + i + LABEL, componentName);
196 setFieldValue(metric + DOT + i + COUNT, String.valueOf(componentStats.getValue().getOnLeak()));
201 private void setTagsStatistics(NewIssuesStatistics.Stats stats) {
202 Metric metric = Metric.TAG;
204 for (Map.Entry<String, MetricStatsInt> tagStats : fiveBiggest(stats.getDistributedMetricStats(metric), MetricStatsInt::getOnLeak)) {
205 setFieldValue(metric + DOT + i + COUNT, String.valueOf(tagStats.getValue().getOnLeak()));
206 setFieldValue(metric + DOT + i + LABEL, tagStats.getKey());
211 private void setAssigneesStatistics(NewIssuesStatistics.Stats stats) {
212 Metric metric = Metric.ASSIGNEE;
213 List<Map.Entry<String, MetricStatsInt>> entries = fiveBiggest(stats.getDistributedMetricStats(metric), MetricStatsInt::getOnLeak);
216 for (Map.Entry<String, MetricStatsInt> assigneeStats : entries) {
217 String assigneeUuid = assigneeStats.getKey();
218 String name = detailsSupplier.getUserNameByUuid(assigneeUuid).orElse(assigneeUuid);
219 setFieldValue(metric + DOT + i + LABEL, name);
220 setFieldValue(metric + DOT + i + COUNT, String.valueOf(assigneeStats.getValue().getOnLeak()));
225 private static List<Map.Entry<String, MetricStatsInt>> fiveBiggest(DistributedMetricStatsInt distributedMetricStatsInt, ToIntFunction<MetricStatsInt> biggerCriteria) {
226 Comparator<Map.Entry<String, MetricStatsInt>> comparator = Comparator.comparingInt(a -> biggerCriteria.applyAsInt(a.getValue()));
227 return distributedMetricStatsInt.getForLabels()
230 .filter(i -> biggerCriteria.applyAsInt(i.getValue()) > 0)
231 .sorted(comparator.reversed())
233 .collect(MoreCollectors.toList(5));
236 public NewIssuesNotification setDebt(Duration debt) {
237 setFieldValue(Metric.EFFORT + COUNT, durations.format(debt));
241 private void setRuleTypeStatistics(NewIssuesStatistics.Stats stats) {
242 DistributedMetricStatsInt distributedMetricStats = stats.getDistributedMetricStats(RULE_TYPE);
243 setFieldValue(RULE_TYPE + COUNT, String.valueOf(distributedMetricStats.getOnLeak()));
244 Arrays.stream(RuleType.values())
245 .forEach(ruleType -> setFieldValue(
246 RULE_TYPE + DOT + ruleType + COUNT,
247 String.valueOf(distributedMetricStats.getForLabel(ruleType.name()).map(MetricStatsInt::getOnLeak).orElse(0))));
251 public boolean equals(Object obj) {
252 return super.equals(obj);
256 public int hashCode() {
257 return super.hashCode();