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.

LiveMeasureTreeUpdaterImpl.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2024 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.measure.live;
  21. import java.util.List;
  22. import java.util.Objects;
  23. import java.util.Optional;
  24. import org.sonar.api.config.Configuration;
  25. import org.sonar.api.measures.Metric;
  26. import org.sonar.db.DbClient;
  27. import org.sonar.db.DbSession;
  28. import org.sonar.db.component.BranchDto;
  29. import org.sonar.db.component.BranchType;
  30. import org.sonar.db.component.ComponentDto;
  31. import org.sonar.db.component.SnapshotDto;
  32. import org.sonar.db.measure.LiveMeasureDto;
  33. import org.sonar.server.measure.DebtRatingGrid;
  34. import org.sonar.server.measure.Rating;
  35. import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_KEY;
  36. import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_KEY;
  37. import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY;
  38. import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY;
  39. import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_KEY;
  40. import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_KEY;
  41. import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY;
  42. import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY;
  43. import static org.sonar.db.newcodeperiod.NewCodePeriodType.REFERENCE_BRANCH;
  44. public class LiveMeasureTreeUpdaterImpl implements LiveMeasureTreeUpdater {
  45. private final DbClient dbClient;
  46. private final MeasureUpdateFormulaFactory formulaFactory;
  47. public LiveMeasureTreeUpdaterImpl(DbClient dbClient, MeasureUpdateFormulaFactory formulaFactory) {
  48. this.dbClient = dbClient;
  49. this.formulaFactory = formulaFactory;
  50. }
  51. @Override
  52. public void update(DbSession dbSession, SnapshotDto lastAnalysis, Configuration config, ComponentIndex components, BranchDto branch, MeasureMatrix measures) {
  53. long beginningOfLeak = getBeginningOfLeakPeriod(lastAnalysis, branch);
  54. boolean shouldUseLeakFormulas = shouldUseLeakFormulas(lastAnalysis, branch);
  55. // 1. set new measure from issues to each component from touched components to the root
  56. updateMatrixWithIssues(dbSession, measures, components, config, shouldUseLeakFormulas, beginningOfLeak);
  57. // 2. aggregate new measures up the component tree
  58. updateMatrixWithHierarchy(measures, components, config, shouldUseLeakFormulas);
  59. }
  60. private void updateMatrixWithHierarchy(MeasureMatrix matrix, ComponentIndex components, Configuration config, boolean useLeakFormulas) {
  61. DebtRatingGrid debtRatingGrid = new DebtRatingGrid(config);
  62. FormulaContextImpl context = new FormulaContextImpl(matrix, components, debtRatingGrid);
  63. components.getSortedTree().forEach(c -> {
  64. for (MeasureUpdateFormula formula : formulaFactory.getFormulas()) {
  65. if (shouldComputeMetric(formula, useLeakFormulas, components.getBranch(), matrix)) {
  66. context.change(c, formula);
  67. try {
  68. formula.computeHierarchy(context);
  69. } catch (RuntimeException e) {
  70. throw new IllegalStateException("Fail to compute " + formula.getMetric().getKey() + " on "
  71. + context.getComponent().getKey() + " (uuid: " + context.getComponent().uuid() + ")", e);
  72. }
  73. }
  74. }
  75. });
  76. }
  77. private void updateMatrixWithIssues(DbSession dbSession, MeasureMatrix matrix, ComponentIndex components, Configuration config, boolean useLeakFormulas, long beginningOfLeak) {
  78. DebtRatingGrid debtRatingGrid = new DebtRatingGrid(config);
  79. FormulaContextImpl context = new FormulaContextImpl(matrix, components, debtRatingGrid);
  80. components.getSortedTree().forEach(c -> {
  81. IssueCounter issueCounter = new IssueCounter(dbClient.issueDao().selectIssueGroupsByComponent(dbSession, c, beginningOfLeak),
  82. dbClient.issueDao().selectIssueImpactGroupsByComponent(dbSession, c, beginningOfLeak));
  83. for (MeasureUpdateFormula formula : formulaFactory.getFormulas()) {
  84. if (shouldComputeMetric(formula, useLeakFormulas, components.getBranch(), matrix)) {
  85. context.change(c, formula);
  86. try {
  87. formula.compute(context, issueCounter);
  88. } catch (RuntimeException e) {
  89. throw new IllegalStateException("Fail to compute " + formula.getMetric().getKey() + " on "
  90. + context.getComponent().getKey() + " (uuid: " + context.getComponent().uuid() + ")", e);
  91. }
  92. }
  93. }
  94. });
  95. }
  96. private static boolean shouldComputeMetric(MeasureUpdateFormula formula, boolean useLeakFormulas, ComponentDto branchComponent,
  97. MeasureMatrix matrix) {
  98. // Use formula when the leak period is defined, it's a PR, or the formula is not about the leak period
  99. return (useLeakFormulas || !formula.isOnLeak())
  100. // Some metrics should only be computed if the metric has been computed on the branch before (during analysis).
  101. // Otherwise, the computed measure would only apply to the touched components and be incomplete.
  102. && (!formula.isOnlyIfComputedOnBranch() || matrix.getMeasure(branchComponent, formula.getMetric().getKey()).isPresent());
  103. }
  104. private static long getBeginningOfLeakPeriod(SnapshotDto lastAnalysis, BranchDto branch) {
  105. if (isPR(branch)) {
  106. return 0L;
  107. } else if (REFERENCE_BRANCH.name().equals(lastAnalysis.getPeriodMode())) {
  108. return -1;
  109. } else {
  110. return Optional.ofNullable(lastAnalysis.getPeriodDate()).orElse(Long.MAX_VALUE);
  111. }
  112. }
  113. private static boolean isPR(BranchDto branch) {
  114. return branch.getBranchType() == BranchType.PULL_REQUEST;
  115. }
  116. private static boolean shouldUseLeakFormulas(SnapshotDto lastAnalysis, BranchDto branch) {
  117. return lastAnalysis.getPeriodDate() != null || isPR(branch) || REFERENCE_BRANCH.name().equals(lastAnalysis.getPeriodMode());
  118. }
  119. public static class FormulaContextImpl implements MeasureUpdateFormula.Context {
  120. private final MeasureMatrix matrix;
  121. private final ComponentIndex componentIndex;
  122. private final DebtRatingGrid debtRatingGrid;
  123. private ComponentDto currentComponent;
  124. private MeasureUpdateFormula currentFormula;
  125. public FormulaContextImpl(MeasureMatrix matrix, ComponentIndex componentIndex, DebtRatingGrid debtRatingGrid) {
  126. this.matrix = matrix;
  127. this.componentIndex = componentIndex;
  128. this.debtRatingGrid = debtRatingGrid;
  129. }
  130. void change(ComponentDto component, MeasureUpdateFormula formula) {
  131. this.currentComponent = component;
  132. this.currentFormula = formula;
  133. }
  134. public List<Double> getChildrenValues() {
  135. List<ComponentDto> children = componentIndex.getChildren(currentComponent);
  136. return children.stream()
  137. .flatMap(c -> matrix.getMeasure(c, currentFormula.getMetric().getKey()).stream())
  138. .map(LiveMeasureDto::getValue)
  139. .filter(Objects::nonNull)
  140. .toList();
  141. }
  142. public List<String> getChildrenTextValues() {
  143. List<ComponentDto> children = componentIndex.getChildren(currentComponent);
  144. return children.stream()
  145. .flatMap(c -> matrix.getMeasure(c, currentFormula.getMetric().getKey()).stream())
  146. .map(LiveMeasureDto::getTextValue)
  147. .filter(Objects::nonNull)
  148. .toList();
  149. }
  150. /**
  151. * Some child components may not have the measures 'SECURITY_HOTSPOTS_TO_REVIEW_STATUS' and 'SECURITY_HOTSPOTS_REVIEWED_STATUS' saved for them,
  152. * so we may need to calculate them based on 'SECURITY_HOTSPOTS_REVIEWED' and 'SECURITY_HOTSPOTS'.
  153. */
  154. @Override
  155. public long getChildrenHotspotsReviewed() {
  156. return getChildrenHotspotsReviewed(SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY, SECURITY_HOTSPOTS_REVIEWED_KEY, SECURITY_HOTSPOTS_KEY);
  157. }
  158. /**
  159. * Some child components may not have the measure 'SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY'. We assume that 'SECURITY_HOTSPOTS_KEY' has the same value.
  160. */
  161. @Override
  162. public long getChildrenHotspotsToReview() {
  163. return componentIndex.getChildren(currentComponent)
  164. .stream()
  165. .map(c -> matrix.getMeasure(c, SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY).or(() -> matrix.getMeasure(c, SECURITY_HOTSPOTS_KEY)))
  166. .mapToLong(lmOpt -> lmOpt.flatMap(lm -> Optional.ofNullable(lm.getValue())).orElse(0D).longValue())
  167. .sum();
  168. }
  169. @Override
  170. public long getChildrenNewHotspotsReviewed() {
  171. return getChildrenHotspotsReviewed(NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY, NEW_SECURITY_HOTSPOTS_REVIEWED_KEY, NEW_SECURITY_HOTSPOTS_KEY);
  172. }
  173. /**
  174. * Some child components may not have the measure 'NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY'. We assume that 'NEW_SECURITY_HOTSPOTS_KEY' has the same value.
  175. */
  176. @Override
  177. public long getChildrenNewHotspotsToReview() {
  178. return componentIndex.getChildren(currentComponent)
  179. .stream()
  180. .map(c -> matrix.getMeasure(c, NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY).or(() -> matrix.getMeasure(c, NEW_SECURITY_HOTSPOTS_KEY)))
  181. .mapToLong(lmOpt -> lmOpt.flatMap(lm -> Optional.ofNullable(lm.getValue())).orElse(0D).longValue())
  182. .sum();
  183. }
  184. private long getChildrenHotspotsReviewed(String metricKey, String percMetricKey, String hotspotsMetricKey) {
  185. return componentIndex.getChildren(currentComponent)
  186. .stream()
  187. .mapToLong(c -> getHotspotsReviewed(c, metricKey, percMetricKey, hotspotsMetricKey))
  188. .sum();
  189. }
  190. private long getHotspotsReviewed(ComponentDto c, String metricKey, String percMetricKey, String hotspotsMetricKey) {
  191. Optional<LiveMeasureDto> measure = matrix.getMeasure(c, metricKey);
  192. return measure.map(lm -> Optional.ofNullable(lm.getValue()).orElse(0D).longValue())
  193. .orElseGet(() -> matrix.getMeasure(c, percMetricKey)
  194. .flatMap(percentage -> matrix.getMeasure(c, hotspotsMetricKey)
  195. .map(hotspots -> {
  196. double perc = Optional.ofNullable(percentage.getValue()).orElse(0D) / 100D;
  197. double toReview = Optional.ofNullable(hotspots.getValue()).orElse(0D);
  198. double reviewed = (toReview * perc) / (1D - perc);
  199. return Math.round(reviewed);
  200. }))
  201. .orElse(0L));
  202. }
  203. @Override
  204. public ComponentDto getComponent() {
  205. return currentComponent;
  206. }
  207. @Override
  208. public DebtRatingGrid getDebtRatingGrid() {
  209. return debtRatingGrid;
  210. }
  211. @Override
  212. public Optional<Double> getValue(Metric metric) {
  213. Optional<LiveMeasureDto> measure = matrix.getMeasure(currentComponent, metric.getKey());
  214. return measure.map(LiveMeasureDto::getValue);
  215. }
  216. @Override
  217. public Optional<String> getText(Metric metric) {
  218. Optional<LiveMeasureDto> measure = matrix.getMeasure(currentComponent, metric.getKey());
  219. return measure.map(LiveMeasureDto::getTextValue);
  220. }
  221. @Override
  222. public void setValue(double value) {
  223. String metricKey = currentFormula.getMetric().getKey();
  224. matrix.setValue(currentComponent, metricKey, value);
  225. }
  226. @Override
  227. public void setValue(Rating value) {
  228. String metricKey = currentFormula.getMetric().getKey();
  229. matrix.setValue(currentComponent, metricKey, value);
  230. }
  231. @Override
  232. public void setValue(String value) {
  233. String metricKey = currentFormula.getMetric().getKey();
  234. matrix.setValue(currentComponent, metricKey, value);
  235. }
  236. }
  237. }