]> source.dussan.org Git - sonarqube.git/blob
c7b9bdd378992c72b3d355a8136fa0b654323cfc
[sonarqube.git] /
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.ce.task.projectanalysis.step;
21
22 import com.google.common.collect.ImmutableMap;
23 import com.google.common.collect.Multimap;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Optional;
29 import java.util.Set;
30 import java.util.function.Function;
31 import java.util.stream.Collectors;
32 import javax.annotation.Nullable;
33 import org.apache.commons.lang.StringUtils;
34 import org.sonar.api.measures.CoreMetrics;
35 import org.sonar.ce.task.projectanalysis.component.Component;
36 import org.sonar.ce.task.projectanalysis.component.CrawlerDepthLimit;
37 import org.sonar.ce.task.projectanalysis.component.DepthTraversalTypeAwareCrawler;
38 import org.sonar.ce.task.projectanalysis.component.TreeRootHolder;
39 import org.sonar.ce.task.projectanalysis.component.TypeAwareVisitorAdapter;
40 import org.sonar.ce.task.projectanalysis.measure.Measure;
41 import org.sonar.ce.task.projectanalysis.measure.MeasureRepository;
42 import org.sonar.ce.task.projectanalysis.measure.QualityGateStatus;
43 import org.sonar.ce.task.projectanalysis.measure.qualitygatedetails.EvaluatedCondition;
44 import org.sonar.ce.task.projectanalysis.measure.qualitygatedetails.QualityGateDetailsData;
45 import org.sonar.ce.task.projectanalysis.metric.Metric;
46 import org.sonar.ce.task.projectanalysis.metric.MetricRepository;
47 import org.sonar.ce.task.projectanalysis.qualitygate.Condition;
48 import org.sonar.ce.task.projectanalysis.qualitygate.ConditionEvaluator;
49 import org.sonar.ce.task.projectanalysis.qualitygate.ConditionStatus;
50 import org.sonar.ce.task.projectanalysis.qualitygate.EvaluationResult;
51 import org.sonar.ce.task.projectanalysis.qualitygate.EvaluationResultTextConverter;
52 import org.sonar.ce.task.projectanalysis.qualitygate.MutableQualityGateStatusHolder;
53 import org.sonar.ce.task.projectanalysis.qualitygate.QualityGate;
54 import org.sonar.ce.task.projectanalysis.qualitygate.QualityGateHolder;
55 import org.sonar.ce.task.step.ComputationStep;
56 import org.sonar.core.util.stream.MoreCollectors;
57
58 import static java.lang.String.format;
59 import static org.sonar.ce.task.projectanalysis.component.ComponentVisitor.Order.PRE_ORDER;
60 import static org.sonar.ce.task.projectanalysis.qualitygate.ConditionStatus.NO_VALUE_STATUS;
61 import static org.sonar.ce.task.projectanalysis.qualitygate.ConditionStatus.create;
62
63 /**
64  * This step:
65  * <ul>
66  * <li>updates the QualityGateStatus of all the project's measures for the metrics of the conditions of the current
67  * QualityGate (retrieved from {@link QualityGateHolder})</li>
68  * <li>computes the measures on the project for metrics {@link CoreMetrics#QUALITY_GATE_DETAILS_KEY} and
69  * {@link CoreMetrics#ALERT_STATUS_KEY}</li>
70  * </ul>
71  *
72  */
73 public class QualityGateMeasuresStep implements ComputationStep {
74   private final TreeRootHolder treeRootHolder;
75   private final QualityGateHolder qualityGateHolder;
76   private final MutableQualityGateStatusHolder qualityGateStatusHolder;
77   private final MeasureRepository measureRepository;
78   private final MetricRepository metricRepository;
79   private final EvaluationResultTextConverter evaluationResultTextConverter;
80   private final SmallChangesetQualityGateSpecialCase smallChangesetQualityGateSpecialCase;
81
82   public QualityGateMeasuresStep(TreeRootHolder treeRootHolder,
83     QualityGateHolder qualityGateHolder, MutableQualityGateStatusHolder qualityGateStatusHolder,
84     MeasureRepository measureRepository, MetricRepository metricRepository,
85     EvaluationResultTextConverter evaluationResultTextConverter, SmallChangesetQualityGateSpecialCase smallChangesetQualityGateSpecialCase) {
86     this.treeRootHolder = treeRootHolder;
87     this.qualityGateHolder = qualityGateHolder;
88     this.qualityGateStatusHolder = qualityGateStatusHolder;
89     this.evaluationResultTextConverter = evaluationResultTextConverter;
90     this.measureRepository = measureRepository;
91     this.metricRepository = metricRepository;
92     this.smallChangesetQualityGateSpecialCase = smallChangesetQualityGateSpecialCase;
93   }
94
95   @Override
96   public void execute(ComputationStep.Context context) {
97     new DepthTraversalTypeAwareCrawler(
98       new TypeAwareVisitorAdapter(CrawlerDepthLimit.PROJECT, PRE_ORDER) {
99         @Override
100         public void visitProject(Component project) {
101           executeForProject(project);
102         }
103       }).visit(treeRootHolder.getRoot());
104   }
105
106   private void executeForProject(Component project) {
107     Optional<QualityGate> qualityGate = qualityGateHolder.getQualityGate();
108     if (qualityGate.isPresent()) {
109       QualityGateDetailsDataBuilder builder = new QualityGateDetailsDataBuilder();
110       updateMeasures(project, qualityGate.get().getConditions(), builder);
111
112       addProjectMeasure(project, builder);
113
114       updateQualityGateStatusHolder(qualityGate.get(), builder);
115     }
116   }
117
118   private void updateQualityGateStatusHolder(QualityGate qualityGate, QualityGateDetailsDataBuilder builder) {
119     qualityGateStatusHolder.setStatus(convert(builder.getGlobalLevel()), createStatusPerCondition(qualityGate.getConditions(), builder.getEvaluatedConditions()));
120   }
121
122   private static ConditionStatus.EvaluationStatus toEvaluationStatus(Measure.Level globalLevel) {
123     switch (globalLevel) {
124       case OK:
125         return ConditionStatus.EvaluationStatus.OK;
126       case ERROR:
127         return ConditionStatus.EvaluationStatus.ERROR;
128       default:
129         throw new IllegalArgumentException(format(
130           "Unsupported value '%s' of Measure.Level can not be converted to EvaluationStatus",
131           globalLevel));
132     }
133   }
134
135   private static org.sonar.ce.task.projectanalysis.qualitygate.QualityGateStatus convert(Measure.Level globalLevel) {
136     switch (globalLevel) {
137       case OK:
138         return org.sonar.ce.task.projectanalysis.qualitygate.QualityGateStatus.OK;
139       case ERROR:
140         return org.sonar.ce.task.projectanalysis.qualitygate.QualityGateStatus.ERROR;
141       default:
142         throw new IllegalArgumentException(format(
143           "Unsupported value '%s' of Measure.Level can not be converted to QualityGateStatus",
144           globalLevel));
145     }
146   }
147
148   private static Map<Condition, ConditionStatus> createStatusPerCondition(Collection<Condition> conditions, Collection<EvaluatedCondition> evaluatedConditions) {
149     Map<Condition, EvaluatedCondition> evaluatedConditionPerCondition = evaluatedConditions.stream()
150       .collect(Collectors.toMap(EvaluatedCondition::getCondition, Function.identity()));
151
152     ImmutableMap.Builder<Condition, ConditionStatus> builder = ImmutableMap.builder();
153     for (Condition condition : conditions) {
154       EvaluatedCondition evaluatedCondition = evaluatedConditionPerCondition.get(condition);
155
156       if (evaluatedCondition == null) {
157         builder.put(condition, NO_VALUE_STATUS);
158       } else {
159         builder.put(condition, create(toEvaluationStatus(evaluatedCondition.getLevel()), evaluatedCondition.getActualValue()));
160       }
161     }
162     return builder.build();
163   }
164
165   private void updateMeasures(Component project, Set<Condition> conditions, QualityGateDetailsDataBuilder builder) {
166     Multimap<Metric, Condition> conditionsPerMetric = conditions.stream().collect(MoreCollectors.index(Condition::getMetric, Function.identity()));
167     boolean ignoredConditions = false;
168     for (Map.Entry<Metric, Collection<Condition>> entry : conditionsPerMetric.asMap().entrySet()) {
169       Metric metric = entry.getKey();
170       Optional<Measure> measure = measureRepository.getRawMeasure(project, metric);
171       if (!measure.isPresent()) {
172         continue;
173       }
174
175       final MetricEvaluationResult metricEvaluationResult = evaluateQualityGate(measure.get(), entry.getValue());
176       final MetricEvaluationResult finalMetricEvaluationResult;
177       if (smallChangesetQualityGateSpecialCase.appliesTo(project, metricEvaluationResult)) {
178         finalMetricEvaluationResult = smallChangesetQualityGateSpecialCase.apply(metricEvaluationResult);
179         ignoredConditions = true;
180       } else {
181         finalMetricEvaluationResult = metricEvaluationResult;
182       }
183       String text = evaluationResultTextConverter.asText(finalMetricEvaluationResult.condition, finalMetricEvaluationResult.evaluationResult);
184       builder.addLabel(text);
185
186       Measure updatedMeasure = Measure.updatedMeasureBuilder(measure.get())
187         .setQualityGateStatus(new QualityGateStatus(finalMetricEvaluationResult.evaluationResult.level(), text))
188         .create();
189       measureRepository.update(project, metric, updatedMeasure);
190
191       builder.addEvaluatedCondition(finalMetricEvaluationResult);
192     }
193     builder.setIgnoredConditions(ignoredConditions);
194   }
195
196   private static MetricEvaluationResult evaluateQualityGate(Measure measure, Collection<Condition> conditions) {
197     ConditionEvaluator conditionEvaluator = new ConditionEvaluator();
198     MetricEvaluationResult metricEvaluationResult = null;
199     for (Condition newCondition : conditions) {
200       EvaluationResult newEvaluationResult = conditionEvaluator.evaluate(newCondition, measure);
201       if (metricEvaluationResult == null || newEvaluationResult.level().ordinal() > metricEvaluationResult.evaluationResult.level().ordinal()) {
202         metricEvaluationResult = new MetricEvaluationResult(newEvaluationResult, newCondition);
203       }
204     }
205     return metricEvaluationResult;
206   }
207
208   private void addProjectMeasure(Component project, QualityGateDetailsDataBuilder builder) {
209     Measure globalMeasure = Measure.newMeasureBuilder()
210       .setQualityGateStatus(new QualityGateStatus(builder.getGlobalLevel(), StringUtils.join(builder.getLabels(), ", ")))
211       .create(builder.getGlobalLevel());
212     Metric metric = metricRepository.getByKey(CoreMetrics.ALERT_STATUS_KEY);
213     measureRepository.add(project, metric, globalMeasure);
214
215     String detailMeasureValue = new QualityGateDetailsData(builder.getGlobalLevel(), builder.getEvaluatedConditions(), builder.isIgnoredConditions()).toJson();
216     Measure detailsMeasure = Measure.newMeasureBuilder().create(detailMeasureValue);
217     Metric qgDetailsMetric = metricRepository.getByKey(CoreMetrics.QUALITY_GATE_DETAILS_KEY);
218     measureRepository.add(project, qgDetailsMetric, detailsMeasure);
219   }
220
221   @Override
222   public String getDescription() {
223     return "Compute Quality Gate measures";
224   }
225
226   private static final class QualityGateDetailsDataBuilder {
227     private Measure.Level globalLevel = Measure.Level.OK;
228     private List<String> labels = new ArrayList<>();
229     private List<EvaluatedCondition> evaluatedConditions = new ArrayList<>();
230     private boolean ignoredConditions;
231
232     public Measure.Level getGlobalLevel() {
233       return globalLevel;
234     }
235
236     public void addLabel(@Nullable String label) {
237       if (StringUtils.isNotBlank(label)) {
238         labels.add(label);
239       }
240     }
241
242     public List<String> getLabels() {
243       return labels;
244     }
245
246     public void addEvaluatedCondition(MetricEvaluationResult metricEvaluationResult) {
247       Measure.Level level = metricEvaluationResult.evaluationResult.level();
248       if (Measure.Level.ERROR == level) {
249         globalLevel = Measure.Level.ERROR;
250       }
251       evaluatedConditions.add(
252         new EvaluatedCondition(metricEvaluationResult.condition, level, metricEvaluationResult.evaluationResult.value()));
253     }
254
255     public List<EvaluatedCondition> getEvaluatedConditions() {
256       return evaluatedConditions;
257     }
258
259     public boolean isIgnoredConditions() {
260       return ignoredConditions;
261     }
262
263     public QualityGateDetailsDataBuilder setIgnoredConditions(boolean ignoredConditions) {
264       this.ignoredConditions = ignoredConditions;
265       return this;
266     }
267   }
268
269   static class MetricEvaluationResult {
270     final EvaluationResult evaluationResult;
271     final Condition condition;
272
273     MetricEvaluationResult(EvaluationResult evaluationResult, Condition condition) {
274       this.evaluationResult = evaluationResult;
275       this.condition = condition;
276     }
277   }
278
279 }