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