3 * Copyright (C) 2009-2023 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.Comparator;
23 import java.util.Date;
24 import java.util.List;
26 import java.util.Optional;
27 import java.util.function.ToIntFunction;
28 import javax.annotation.CheckForNull;
29 import javax.annotation.Nullable;
30 import org.sonar.api.notifications.Notification;
31 import org.sonar.api.rule.RuleKey;
32 import org.sonar.api.utils.DateUtils;
33 import org.sonar.api.utils.Durations;
34 import org.sonar.server.issue.notification.NewIssuesStatistics.Metric;
36 import static java.util.Objects.requireNonNull;
37 import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_BRANCH;
38 import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_PROJECT_VERSION;
39 import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_PULL_REQUEST;
40 import static org.sonar.server.issue.notification.NewIssuesEmailTemplate.FIELD_PROJECT_DATE;
41 import static org.sonar.server.issue.notification.NewIssuesEmailTemplate.FIELD_PROJECT_KEY;
42 import static org.sonar.server.issue.notification.NewIssuesEmailTemplate.FIELD_PROJECT_NAME;
43 import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.ISSUE;
45 public class NewIssuesNotification extends Notification {
47 public static final String TYPE = "new-issues";
48 private static final long serialVersionUID = -6305871981920103093L;
49 private static final String COUNT = ".count";
50 private static final String LABEL = ".label";
51 private static final String DOT = ".";
53 private final transient DetailsSupplier detailsSupplier;
54 private final transient Durations durations;
56 public NewIssuesNotification(Durations durations, DetailsSupplier detailsSupplier) {
57 this(TYPE, durations, detailsSupplier);
60 protected NewIssuesNotification(String type, Durations durations, DetailsSupplier detailsSupplier) {
62 this.durations = durations;
63 this.detailsSupplier = detailsSupplier;
66 public interface DetailsSupplier {
68 * @throws NullPointerException if {@code ruleKey} is {@code null}
70 Optional<RuleDefinition> getRuleDefinitionByRuleKey(RuleKey ruleKey);
73 * @throws NullPointerException if {@code uuid} is {@code null}
75 Optional<String> getComponentNameByUuid(String uuid);
78 * @throws NullPointerException if {@code uuid} is {@code null}
80 Optional<String> getUserNameByUuid(String uuid);
83 public record RuleDefinition(String name, String language) {
84 public RuleDefinition(String name, @Nullable String language) {
85 this.name = requireNonNull(name, "name can't be null");
86 this.language = language;
90 public String toString() {
91 return "RuleDefinition{" + name + " (" + language + ')' + '}';
95 public NewIssuesNotification setAnalysisDate(Date d) {
96 setFieldValue(FIELD_PROJECT_DATE, DateUtils.formatDateTime(d));
100 public NewIssuesNotification setProject(String projectKey, String projectName, @Nullable String branchName, @Nullable String pullRequest) {
101 setFieldValue(FIELD_PROJECT_NAME, projectName);
102 setFieldValue(FIELD_PROJECT_KEY, projectKey);
103 if (branchName != null) {
104 setFieldValue(FIELD_BRANCH, branchName);
106 if (pullRequest != null) {
107 setFieldValue(FIELD_PULL_REQUEST, pullRequest);
113 public String getProjectKey() {
114 return getFieldValue(FIELD_PROJECT_KEY);
117 public NewIssuesNotification setProjectVersion(@Nullable String version) {
118 if (version != null) {
119 setFieldValue(FIELD_PROJECT_VERSION, version);
124 public NewIssuesNotification setStatistics(String projectName, NewIssuesStatistics.Stats stats) {
125 setDefaultMessage(stats.getIssueCount().getOnCurrentAnalysis() + " new issues on " + projectName + ".\n");
127 setIssueStatistics(stats);
128 setAssigneesStatistics(stats);
129 setTagsStatistics(stats);
130 setComponentsStatistics(stats);
131 setRuleStatistics(stats);
136 private void setRuleStatistics(NewIssuesStatistics.Stats stats) {
137 Metric metric = Metric.RULE;
138 List<Map.Entry<String, MetricStatsInt>> fiveBiggest = fiveBiggest(stats.getDistributedMetricStats(metric), MetricStatsInt::getOnCurrentAnalysis);
140 for (Map.Entry<String, MetricStatsInt> ruleStats : fiveBiggest) {
141 String ruleKey = ruleStats.getKey();
142 RuleDefinition rule = detailsSupplier.getRuleDefinitionByRuleKey(RuleKey.parse(ruleKey))
143 .orElseThrow(() -> new IllegalStateException(String.format("Rule with key '%s' does not exist", ruleKey)));
144 String name = rule.name() + " (" + rule.language() + ")";
145 setFieldValue(metric + DOT + i + LABEL, name);
146 setFieldValue(metric + DOT + i + COUNT, String.valueOf(ruleStats.getValue().getOnCurrentAnalysis()));
151 private void setComponentsStatistics(NewIssuesStatistics.Stats stats) {
152 Metric metric = Metric.COMPONENT;
154 List<Map.Entry<String, MetricStatsInt>> fiveBiggest = fiveBiggest(stats.getDistributedMetricStats(metric), MetricStatsInt::getOnCurrentAnalysis);
155 for (Map.Entry<String, MetricStatsInt> componentStats : fiveBiggest) {
156 String uuid = componentStats.getKey();
157 String componentName = detailsSupplier.getComponentNameByUuid(uuid)
158 .orElseThrow(() -> new IllegalStateException(String.format("Component with uuid '%s' not found", uuid)));
159 setFieldValue(metric + DOT + i + LABEL, componentName);
160 setFieldValue(metric + DOT + i + COUNT, String.valueOf(componentStats.getValue().getOnCurrentAnalysis()));
165 private void setTagsStatistics(NewIssuesStatistics.Stats stats) {
166 Metric metric = Metric.TAG;
168 for (Map.Entry<String, MetricStatsInt> tagStats : fiveBiggest(stats.getDistributedMetricStats(metric), MetricStatsInt::getOnCurrentAnalysis)) {
169 setFieldValue(metric + DOT + i + COUNT, String.valueOf(tagStats.getValue().getOnCurrentAnalysis()));
170 setFieldValue(metric + DOT + i + LABEL, tagStats.getKey());
175 private void setAssigneesStatistics(NewIssuesStatistics.Stats stats) {
176 Metric metric = Metric.ASSIGNEE;
177 List<Map.Entry<String, MetricStatsInt>> entries = fiveBiggest(stats.getDistributedMetricStats(metric), MetricStatsInt::getOnCurrentAnalysis);
180 for (Map.Entry<String, MetricStatsInt> assigneeStats : entries) {
181 String assigneeUuid = assigneeStats.getKey();
182 String name = detailsSupplier.getUserNameByUuid(assigneeUuid).orElse(assigneeUuid);
183 setFieldValue(metric + DOT + i + LABEL, name);
184 setFieldValue(metric + DOT + i + COUNT, String.valueOf(assigneeStats.getValue().getOnCurrentAnalysis()));
189 private static List<Map.Entry<String, MetricStatsInt>> fiveBiggest(DistributedMetricStatsInt distributedMetricStatsInt, ToIntFunction<MetricStatsInt> biggerCriteria) {
190 Comparator<Map.Entry<String, MetricStatsInt>> comparator = Comparator.comparingInt(a -> biggerCriteria.applyAsInt(a.getValue()));
191 return distributedMetricStatsInt.getForLabels()
194 .filter(i -> biggerCriteria.applyAsInt(i.getValue()) > 0)
195 .sorted(comparator.reversed())
200 private void setIssueStatistics(NewIssuesStatistics.Stats stats) {
201 setFieldValue(ISSUE + COUNT, String.valueOf(stats.getIssueCount().getOnCurrentAnalysis()));
205 public boolean equals(Object obj) {
206 return super.equals(obj);
210 public int hashCode() {
211 return super.hashCode();