3 * Copyright (C) 2009-2019 SonarSource SA
4 * mailto:info AT sonarsource DOT com
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.
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.
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.
20 package org.sonar.ce.task.projectanalysis.step;
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;
29 import java.util.Optional;
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;
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;
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>
73 * It must be executed after the computation of differential measures {@link ComputeMeasureVariationsStep}
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;
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;
98 public void execute(ComputationStep.Context context) {
99 new DepthTraversalTypeAwareCrawler(
100 new TypeAwareVisitorAdapter(CrawlerDepthLimit.PROJECT, PRE_ORDER) {
102 public void visitProject(Component project) {
103 executeForProject(project);
105 }).visit(treeRootHolder.getRoot());
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);
114 addProjectMeasure(project, builder);
116 updateQualityGateStatusHolder(qualityGate.get(), builder);
120 private void updateQualityGateStatusHolder(QualityGate qualityGate, QualityGateDetailsDataBuilder builder) {
121 qualityGateStatusHolder.setStatus(convert(builder.getGlobalLevel()), createStatusPerCondition(qualityGate.getConditions(), builder.getEvaluatedConditions()));
124 private static ConditionStatus.EvaluationStatus toEvaluationStatus(Measure.Level globalLevel) {
125 switch (globalLevel) {
127 return ConditionStatus.EvaluationStatus.OK;
129 return ConditionStatus.EvaluationStatus.WARN;
131 return ConditionStatus.EvaluationStatus.ERROR;
133 throw new IllegalArgumentException(format(
134 "Unsupported value '%s' of Measure.Level can not be converted to EvaluationStatus",
139 private static org.sonar.ce.task.projectanalysis.qualitygate.QualityGateStatus convert(Measure.Level globalLevel) {
140 switch (globalLevel) {
142 return org.sonar.ce.task.projectanalysis.qualitygate.QualityGateStatus.OK;
144 return org.sonar.ce.task.projectanalysis.qualitygate.QualityGateStatus.WARN;
146 return org.sonar.ce.task.projectanalysis.qualitygate.QualityGateStatus.ERROR;
148 throw new IllegalArgumentException(format(
149 "Unsupported value '%s' of Measure.Level can not be converted to QualityGateStatus",
154 private static Map<Condition, ConditionStatus> createStatusPerCondition(Iterable<Condition> conditions, Iterable<EvaluatedCondition> evaluatedConditions) {
155 Map<Condition, EvaluatedCondition> evaluatedConditionPerCondition = from(evaluatedConditions)
156 .uniqueIndex(EvaluatedConditionToCondition.INSTANCE);
158 ImmutableMap.Builder<Condition, ConditionStatus> builder = ImmutableMap.builder();
159 for (Condition condition : conditions) {
160 EvaluatedCondition evaluatedCondition = evaluatedConditionPerCondition.get(condition);
162 if (evaluatedCondition == null) {
163 builder.put(condition, NO_VALUE_STATUS);
165 builder.put(condition, create(toEvaluationStatus(evaluatedCondition.getLevel()), evaluatedCondition.getActualValue()));
168 return builder.build();
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()) {
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;
187 finalMetricEvaluationResult = metricEvaluationResult;
189 String text = evaluationResultTextConverter.asText(finalMetricEvaluationResult.condition, finalMetricEvaluationResult.evaluationResult);
190 builder.addLabel(text);
192 Measure updatedMeasure = Measure.updatedMeasureBuilder(measure.get())
193 .setQualityGateStatus(new QualityGateStatus(finalMetricEvaluationResult.evaluationResult.getLevel(), text))
195 measureRepository.update(project, metric, updatedMeasure);
197 builder.addEvaluatedCondition(finalMetricEvaluationResult);
199 builder.setIgnoredConditions(ignoredConditions);
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);
211 return metricEvaluationResult;
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);
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);
228 public String getDescription() {
229 return "Compute Quality Gate measures";
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;
238 public Measure.Level getGlobalLevel() {
242 public void addLabel(@Nullable String label) {
243 if (StringUtils.isNotBlank(label)) {
248 public List<String> getLabels() {
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;
257 } else if (Measure.Level.ERROR == level) {
258 globalLevel = Measure.Level.ERROR;
260 evaluatedConditions.add(
261 new EvaluatedCondition(metricEvaluationResult.condition, level, metricEvaluationResult.evaluationResult.getValue()));
264 public List<EvaluatedCondition> getEvaluatedConditions() {
265 return evaluatedConditions;
268 public boolean isIgnoredConditions() {
269 return ignoredConditions;
272 public QualityGateDetailsDataBuilder setIgnoredConditions(boolean ignoredConditions) {
273 this.ignoredConditions = ignoredConditions;
278 private enum EvaluatedConditionToCondition implements Function<EvaluatedCondition, Condition> {
283 public Condition apply(@Nonnull EvaluatedCondition input) {
284 return input.getCondition();
288 static class MetricEvaluationResult {
289 final EvaluationResult evaluationResult;
290 final Condition condition;
292 MetricEvaluationResult(EvaluationResult evaluationResult, Condition condition) {
293 this.evaluationResult = evaluationResult;
294 this.condition = condition;