3 * Copyright (C) 2009-2017 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.server.computation.task.projectanalysis.step;
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;
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;
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;
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>
74 * It must be executed after the computation of differential measures {@link ComputeMeasureVariationsStep}
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);
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;
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;
102 public void execute() {
103 new DepthTraversalTypeAwareCrawler(
104 new TypeAwareVisitorAdapter(CrawlerDepthLimit.PROJECT, PRE_ORDER) {
106 public void visitProject(Component project) {
107 executeForProject(project);
109 }).visit(treeRootHolder.getRoot());
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);
118 addProjectMeasure(project, builder);
120 updateQualityGateStatusHolder(qualityGate.get(), builder);
124 private void updateQualityGateStatusHolder(QualityGate qualityGate, QualityGateDetailsDataBuilder builder) {
125 qualityGateStatusHolder.setStatus(convert(builder.getGlobalLevel()), createStatusPerCondition(qualityGate.getConditions(), builder.getEvaluatedConditions()));
128 private static ConditionStatus.EvaluationStatus toEvaluationStatus(Measure.Level globalLevel) {
129 switch (globalLevel) {
131 return ConditionStatus.EvaluationStatus.OK;
133 return ConditionStatus.EvaluationStatus.WARN;
135 return ConditionStatus.EvaluationStatus.ERROR;
137 throw new IllegalArgumentException(format(
138 "Unsupported value '%s' of Measure.Level can not be converted to EvaluationStatus",
143 private static org.sonar.server.computation.task.projectanalysis.qualitygate.QualityGateStatus convert(Measure.Level globalLevel) {
144 switch (globalLevel) {
146 return org.sonar.server.computation.task.projectanalysis.qualitygate.QualityGateStatus.OK;
148 return org.sonar.server.computation.task.projectanalysis.qualitygate.QualityGateStatus.WARN;
150 return org.sonar.server.computation.task.projectanalysis.qualitygate.QualityGateStatus.ERROR;
152 throw new IllegalArgumentException(format(
153 "Unsupported value '%s' of Measure.Level can not be converted to QualityGateStatus",
158 private static Map<Condition, ConditionStatus> createStatusPerCondition(Iterable<Condition> conditions, Iterable<EvaluatedCondition> evaluatedConditions) {
159 Map<Condition, EvaluatedCondition> evaluatedConditionPerCondition = from(evaluatedConditions)
160 .uniqueIndex(EvaluatedConditionToCondition.INSTANCE);
162 ImmutableMap.Builder<Condition, ConditionStatus> builder = ImmutableMap.builder();
163 for (Condition condition : conditions) {
164 EvaluatedCondition evaluatedCondition = evaluatedConditionPerCondition.get(condition);
166 if (evaluatedCondition == null) {
167 builder.put(condition, NO_VALUE_STATUS);
169 builder.put(condition, create(toEvaluationStatus(evaluatedCondition.getLevel()), evaluatedCondition.getActualValue()));
172 return builder.build();
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()) {
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);
189 Measure updatedMeasure = Measure.updatedMeasureBuilder(measure.get())
190 .setQualityGateStatus(new QualityGateStatus(finalMetricEvaluationResult.evaluationResult.getLevel(), text))
192 measureRepository.update(project, metric, updatedMeasure);
194 builder.addEvaluatedCondition(finalMetricEvaluationResult);
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);
207 return metricEvaluationResult;
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);
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);
224 public String getDescription() {
225 return "Compute Quality Gate measures";
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<>();
233 public Measure.Level getGlobalLevel() {
237 public void addLabel(@Nullable String label) {
238 if (StringUtils.isNotBlank(label)) {
243 public List<String> getLabels() {
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;
252 } else if (Measure.Level.ERROR == level) {
253 globalLevel = Measure.Level.ERROR;
255 evaluatedConditions.add(
256 new EvaluatedCondition(metricEvaluationResult.condition, level, metricEvaluationResult.evaluationResult.getValue()));
259 public List<EvaluatedCondition> getEvaluatedConditions() {
260 return evaluatedConditions;
264 private enum EvaluatedConditionToCondition implements Function<EvaluatedCondition, Condition> {
269 public Condition apply(@Nonnull EvaluatedCondition input) {
270 return input.getCondition();
274 static class MetricEvaluationResult {
275 final EvaluationResult evaluationResult;
276 final Condition condition;
278 MetricEvaluationResult(EvaluationResult evaluationResult, Condition condition) {
279 this.evaluationResult = evaluationResult;
280 this.condition = condition;