You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

NewIssuesNotification.java 9.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2018 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  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.
  10. *
  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.
  15. *
  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.
  19. */
  20. package org.sonar.server.issue.notification;
  21. import java.util.Arrays;
  22. import java.util.Comparator;
  23. import java.util.Date;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.Optional;
  27. import java.util.Set;
  28. import java.util.function.ToIntFunction;
  29. import javax.annotation.Nullable;
  30. import org.sonar.api.notifications.Notification;
  31. import org.sonar.api.rule.RuleKey;
  32. import org.sonar.api.rules.RuleType;
  33. import org.sonar.api.utils.DateUtils;
  34. import org.sonar.api.utils.Duration;
  35. import org.sonar.api.utils.Durations;
  36. import org.sonar.core.util.stream.MoreCollectors;
  37. import org.sonar.db.DbClient;
  38. import org.sonar.db.DbSession;
  39. import org.sonar.db.RowNotFoundException;
  40. import org.sonar.db.component.ComponentDto;
  41. import org.sonar.db.rule.RuleDefinitionDto;
  42. import org.sonar.server.issue.notification.NewIssuesStatistics.Metric;
  43. import org.sonar.server.user.index.UserDoc;
  44. import org.sonar.server.user.index.UserIndex;
  45. import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_BRANCH;
  46. import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_PROJECT_VERSION;
  47. import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_PULL_REQUEST;
  48. import static org.sonar.server.issue.notification.NewIssuesEmailTemplate.FIELD_PROJECT_DATE;
  49. import static org.sonar.server.issue.notification.NewIssuesEmailTemplate.FIELD_PROJECT_KEY;
  50. import static org.sonar.server.issue.notification.NewIssuesEmailTemplate.FIELD_PROJECT_NAME;
  51. import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.RULE_TYPE;
  52. 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 = ".";
  58. private final transient UserIndex userIndex;
  59. private final transient DbClient dbClient;
  60. private final transient Durations durations;
  61. NewIssuesNotification(UserIndex userIndex, DbClient dbClient, Durations durations) {
  62. this(TYPE, userIndex, dbClient, durations);
  63. }
  64. protected NewIssuesNotification(String type, UserIndex userIndex, DbClient dbClient, Durations durations) {
  65. super(type);
  66. this.userIndex = userIndex;
  67. this.dbClient = dbClient;
  68. this.durations = durations;
  69. }
  70. public NewIssuesNotification setAnalysisDate(Date d) {
  71. setFieldValue(FIELD_PROJECT_DATE, DateUtils.formatDateTime(d));
  72. return this;
  73. }
  74. public NewIssuesNotification setProject(String projectKey, String projectName, @Nullable String branchName, @Nullable String pullRequest) {
  75. setFieldValue(FIELD_PROJECT_NAME, projectName);
  76. setFieldValue(FIELD_PROJECT_KEY, projectKey);
  77. if (branchName != null) {
  78. setFieldValue(FIELD_BRANCH, branchName);
  79. }
  80. if (pullRequest != null) {
  81. setFieldValue(FIELD_PULL_REQUEST, pullRequest);
  82. }
  83. return this;
  84. }
  85. public NewIssuesNotification setProjectVersion(@Nullable String version) {
  86. if (version != null) {
  87. setFieldValue(FIELD_PROJECT_VERSION, version);
  88. }
  89. return this;
  90. }
  91. public NewIssuesNotification setStatistics(String projectName, NewIssuesStatistics.Stats stats) {
  92. setDefaultMessage(stats.getDistributedMetricStats(RULE_TYPE).getOnLeak() + " new issues on " + projectName + ".\n");
  93. try (DbSession dbSession = dbClient.openSession(false)) {
  94. setRuleTypeStatistics(stats);
  95. setAssigneesStatistics(stats);
  96. setTagsStatistics(stats);
  97. setComponentsStatistics(dbSession, stats);
  98. setRuleStatistics(dbSession, stats);
  99. }
  100. return this;
  101. }
  102. private void setRuleStatistics(DbSession dbSession, NewIssuesStatistics.Stats stats) {
  103. Metric metric = Metric.RULE;
  104. List<Map.Entry<String, MetricStatsInt>> fiveBiggest = fiveBiggest(stats.getDistributedMetricStats(metric), MetricStatsInt::getOnLeak);
  105. Set<RuleKey> ruleKeys = fiveBiggest
  106. .stream()
  107. .map(Map.Entry::getKey)
  108. .map(RuleKey::parse)
  109. .collect(MoreCollectors.toSet(fiveBiggest.size()));
  110. Map<String, RuleDefinitionDto> ruleByRuleKey = dbClient.ruleDao().selectDefinitionByKeys(dbSession, ruleKeys)
  111. .stream()
  112. .collect(MoreCollectors.uniqueIndex(s -> s.getKey().toString()));
  113. int i = 1;
  114. for (Map.Entry<String, MetricStatsInt> ruleStats : fiveBiggest) {
  115. String ruleKey = ruleStats.getKey();
  116. RuleDefinitionDto rule = Optional.ofNullable(ruleByRuleKey.get(ruleKey))
  117. .orElseThrow(() -> new RowNotFoundException(String.format("Rule with key '%s' does not exist", ruleKey)));
  118. String name = rule.getName() + " (" + rule.getLanguage() + ")";
  119. setFieldValue(metric + DOT + i + LABEL, name);
  120. setFieldValue(metric + DOT + i + COUNT, String.valueOf(ruleStats.getValue().getOnLeak()));
  121. i++;
  122. }
  123. }
  124. private void setComponentsStatistics(DbSession dbSession, NewIssuesStatistics.Stats stats) {
  125. Metric metric = Metric.COMPONENT;
  126. int i = 1;
  127. List<Map.Entry<String, MetricStatsInt>> fiveBiggest = fiveBiggest(stats.getDistributedMetricStats(metric), MetricStatsInt::getOnLeak);
  128. Set<String> componentUuids = fiveBiggest
  129. .stream()
  130. .map(Map.Entry::getKey)
  131. .collect(MoreCollectors.toSet(fiveBiggest.size()));
  132. Map<String, ComponentDto> componentDtosByUuid = dbClient.componentDao().selectByUuids(dbSession, componentUuids)
  133. .stream()
  134. .collect(MoreCollectors.uniqueIndex(ComponentDto::uuid));
  135. for (Map.Entry<String, MetricStatsInt> componentStats : fiveBiggest) {
  136. String uuid = componentStats.getKey();
  137. String componentName = Optional.ofNullable(componentDtosByUuid.get(uuid))
  138. .map(ComponentDto::name)
  139. .orElseThrow(() -> new RowNotFoundException(String.format("Component with uuid '%s' not found", uuid)));
  140. setFieldValue(metric + DOT + i + LABEL, componentName);
  141. setFieldValue(metric + DOT + i + COUNT, String.valueOf(componentStats.getValue().getOnLeak()));
  142. i++;
  143. }
  144. }
  145. private void setTagsStatistics(NewIssuesStatistics.Stats stats) {
  146. Metric metric = Metric.TAG;
  147. int i = 1;
  148. for (Map.Entry<String, MetricStatsInt> tagStats : fiveBiggest(stats.getDistributedMetricStats(metric), MetricStatsInt::getOnLeak)) {
  149. setFieldValue(metric + DOT + i + COUNT, String.valueOf(tagStats.getValue().getOnLeak()));
  150. setFieldValue(metric + DOT + i + LABEL, tagStats.getKey());
  151. i++;
  152. }
  153. }
  154. private void setAssigneesStatistics(NewIssuesStatistics.Stats stats) {
  155. Metric metric = Metric.ASSIGNEE;
  156. int i = 1;
  157. for (Map.Entry<String, MetricStatsInt> assigneeStats : fiveBiggest(stats.getDistributedMetricStats(metric), MetricStatsInt::getOnLeak)) {
  158. String login = assigneeStats.getKey();
  159. UserDoc user = userIndex.getNullableByLogin(login);
  160. String name = user == null ? login : user.name();
  161. setFieldValue(metric + DOT + i + LABEL, name);
  162. setFieldValue(metric + DOT + i + COUNT, String.valueOf(assigneeStats.getValue().getOnLeak()));
  163. i++;
  164. }
  165. }
  166. private static List<Map.Entry<String, MetricStatsInt>> fiveBiggest(DistributedMetricStatsInt distributedMetricStatsInt, ToIntFunction<MetricStatsInt> biggerCriteria) {
  167. Comparator<Map.Entry<String, MetricStatsInt>> comparator = Comparator.comparingInt(a -> biggerCriteria.applyAsInt(a.getValue()));
  168. return distributedMetricStatsInt.getForLabels()
  169. .entrySet()
  170. .stream()
  171. .sorted(comparator.reversed())
  172. .limit(5)
  173. .collect(MoreCollectors.toList(5));
  174. }
  175. public NewIssuesNotification setDebt(Duration debt) {
  176. setFieldValue(Metric.EFFORT + COUNT, durations.format(debt));
  177. return this;
  178. }
  179. private void setRuleTypeStatistics(NewIssuesStatistics.Stats stats) {
  180. DistributedMetricStatsInt distributedMetricStats = stats.getDistributedMetricStats(RULE_TYPE);
  181. setFieldValue(RULE_TYPE + COUNT, String.valueOf(distributedMetricStats.getOnLeak()));
  182. Arrays.stream(RuleType.values())
  183. .forEach(ruleType -> setFieldValue(
  184. RULE_TYPE + DOT + ruleType + COUNT,
  185. String.valueOf(distributedMetricStats.getForLabel(ruleType.name()).map(MetricStatsInt::getOnLeak).orElse(0))));
  186. }
  187. @Override
  188. public boolean equals(Object obj) {
  189. return super.equals(obj);
  190. }
  191. @Override
  192. public int hashCode() {
  193. return super.hashCode();
  194. }
  195. }