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