
org.sonar.server.computation.step.QualityGateMeasuresStep Maven / Gradle / Ivy
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.computation.step;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Ordering;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.server.computation.component.Component;
import org.sonar.server.computation.component.CrawlerDepthLimit;
import org.sonar.server.computation.component.DepthTraversalTypeAwareCrawler;
import org.sonar.server.computation.component.TreeRootHolder;
import org.sonar.server.computation.component.TypeAwareVisitorAdapter;
import org.sonar.server.computation.measure.Measure;
import org.sonar.server.computation.measure.MeasureRepository;
import org.sonar.server.computation.measure.QualityGateStatus;
import org.sonar.server.computation.measure.qualitygatedetails.EvaluatedCondition;
import org.sonar.server.computation.measure.qualitygatedetails.QualityGateDetailsData;
import org.sonar.server.computation.metric.Metric;
import org.sonar.server.computation.metric.MetricRepository;
import org.sonar.server.computation.qualitygate.Condition;
import org.sonar.server.computation.qualitygate.ConditionEvaluator;
import org.sonar.server.computation.qualitygate.ConditionStatus;
import org.sonar.server.computation.qualitygate.EvaluationResult;
import org.sonar.server.computation.qualitygate.EvaluationResultTextConverter;
import org.sonar.server.computation.qualitygate.MutableQualityGateStatusHolder;
import org.sonar.server.computation.qualitygate.QualityGate;
import org.sonar.server.computation.qualitygate.QualityGateHolder;
import static com.google.common.collect.FluentIterable.from;
import static java.lang.String.format;
import static org.sonar.server.computation.component.ComponentVisitor.Order.PRE_ORDER;
import static org.sonar.server.computation.qualitygate.ConditionStatus.NO_VALUE_STATUS;
import static org.sonar.server.computation.qualitygate.ConditionStatus.create;
/**
* This step:
*
* - updates the QualityGateStatus of all the project's measures for the metrics of the conditions of the current
* QualityGate (retrieved from {@link QualityGateHolder})
* - computes the measures on the project for metrics {@link CoreMetrics#QUALITY_GATE_DETAILS_KEY} and
* {@link CoreMetrics#ALERT_STATUS_KEY}
*
*
* It must be executed after the computation of differential measures {@link ComputeMeasureVariationsStep}
*/
public class QualityGateMeasuresStep implements ComputationStep {
private static final Ordering PERIOD_ORDERING = Ordering.natural().nullsLast()
.onResultOf(ConditionToPeriod.INSTANCE);
private final TreeRootHolder treeRootHolder;
private final QualityGateHolder qualityGateHolder;
private final MutableQualityGateStatusHolder qualityGateStatusHolder;
private final MeasureRepository measureRepository;
private final MetricRepository metricRepository;
private final EvaluationResultTextConverter evaluationResultTextConverter;
public QualityGateMeasuresStep(TreeRootHolder treeRootHolder,
QualityGateHolder qualityGateHolder, MutableQualityGateStatusHolder qualityGateStatusHolder,
MeasureRepository measureRepository, MetricRepository metricRepository,
EvaluationResultTextConverter evaluationResultTextConverter) {
this.treeRootHolder = treeRootHolder;
this.qualityGateHolder = qualityGateHolder;
this.qualityGateStatusHolder = qualityGateStatusHolder;
this.evaluationResultTextConverter = evaluationResultTextConverter;
this.measureRepository = measureRepository;
this.metricRepository = metricRepository;
}
@Override
public void execute() {
new DepthTraversalTypeAwareCrawler(
new TypeAwareVisitorAdapter(CrawlerDepthLimit.PROJECT, PRE_ORDER) {
@Override
public void visitProject(Component project) {
executeForProject(project);
}
}).visit(treeRootHolder.getRoot());
}
private void executeForProject(Component project) {
Optional qualityGate = qualityGateHolder.getQualityGate();
if (qualityGate.isPresent()) {
QualityGateDetailsDataBuilder builder = new QualityGateDetailsDataBuilder();
updateMeasures(project, qualityGate.get().getConditions(), builder);
addProjectMeasure(project, builder);
updateQualityGateStatusHolder(qualityGate.get(), builder);
}
}
private void updateQualityGateStatusHolder(QualityGate qualityGate, QualityGateDetailsDataBuilder builder) {
qualityGateStatusHolder.setStatus(convert(builder.getGlobalLevel()), createStatusPerCondition(qualityGate.getConditions(), builder.getEvaluatedConditions()));
}
private static ConditionStatus.EvaluationStatus toEvaluationStatus(Measure.Level globalLevel) {
switch (globalLevel) {
case OK:
return ConditionStatus.EvaluationStatus.OK;
case WARN:
return ConditionStatus.EvaluationStatus.WARN;
case ERROR:
return ConditionStatus.EvaluationStatus.ERROR;
default:
throw new IllegalArgumentException(format(
"Unsupported value '%s' of Measure.Level can not be converted to EvaluationStatus",
globalLevel));
}
}
private static org.sonar.server.computation.qualitygate.QualityGateStatus convert(Measure.Level globalLevel) {
switch (globalLevel) {
case OK:
return org.sonar.server.computation.qualitygate.QualityGateStatus.OK;
case WARN:
return org.sonar.server.computation.qualitygate.QualityGateStatus.WARN;
case ERROR:
return org.sonar.server.computation.qualitygate.QualityGateStatus.ERROR;
default:
throw new IllegalArgumentException(format(
"Unsupported value '%s' of Measure.Level can not be converted to QualityGateStatus",
globalLevel));
}
}
private static Map createStatusPerCondition(Iterable conditions, Iterable evaluatedConditions) {
Map evaluatedConditionPerCondition = from(evaluatedConditions)
.uniqueIndex(EvaluatedConditionToCondition.INSTANCE);
ImmutableMap.Builder builder = ImmutableMap.builder();
for (Condition condition : conditions) {
EvaluatedCondition evaluatedCondition = evaluatedConditionPerCondition.get(condition);
if (evaluatedCondition == null) {
builder.put(condition, NO_VALUE_STATUS);
} else {
builder.put(condition, create(toEvaluationStatus(evaluatedCondition.getLevel()), evaluatedCondition.getActualValue()));
}
}
return builder.build();
}
private void updateMeasures(Component project, Set conditions, QualityGateDetailsDataBuilder builder) {
Multimap conditionsPerMetric = from(conditions).index(ConditionToMetric.INSTANCE);
for (Map.Entry> entry : conditionsPerMetric.asMap().entrySet()) {
Metric metric = entry.getKey();
Optional measure = measureRepository.getRawMeasure(project, metric);
if (!measure.isPresent()) {
continue;
}
MetricEvaluationResult metricEvaluationResult = evaluateQualityGate(measure.get(), entry.getValue());
String text = evaluationResultTextConverter.asText(metricEvaluationResult.condition, metricEvaluationResult.evaluationResult);
builder.addLabel(text);
Measure updatedMeasure = Measure.updatedMeasureBuilder(measure.get())
.setQualityGateStatus(new QualityGateStatus(metricEvaluationResult.evaluationResult.getLevel(), text))
.create();
measureRepository.update(project, metric, updatedMeasure);
builder.addEvaluatedCondition(metricEvaluationResult);
}
}
private static MetricEvaluationResult evaluateQualityGate(Measure measure, Collection conditions) {
ConditionEvaluator conditionEvaluator = new ConditionEvaluator();
MetricEvaluationResult metricEvaluationResult = null;
for (Condition newCondition : PERIOD_ORDERING.immutableSortedCopy(conditions)) {
EvaluationResult newEvaluationResult = conditionEvaluator.evaluate(newCondition, measure);
if (metricEvaluationResult == null || newEvaluationResult.getLevel().ordinal() > metricEvaluationResult.evaluationResult.getLevel().ordinal()) {
metricEvaluationResult = new MetricEvaluationResult(newEvaluationResult, newCondition);
}
}
return metricEvaluationResult;
}
private void addProjectMeasure(Component project, QualityGateDetailsDataBuilder builder) {
Measure globalMeasure = Measure.newMeasureBuilder()
.setQualityGateStatus(new QualityGateStatus(builder.getGlobalLevel(), StringUtils.join(builder.getLabels(), ", ")))
.create(builder.getGlobalLevel());
Metric metric = metricRepository.getByKey(CoreMetrics.ALERT_STATUS_KEY);
measureRepository.add(project, metric, globalMeasure);
String detailMeasureValue = new QualityGateDetailsData(builder.getGlobalLevel(), builder.getEvaluatedConditions()).toJson();
Measure detailsMeasure = Measure.newMeasureBuilder().create(detailMeasureValue);
Metric qgDetailsMetric = metricRepository.getByKey(CoreMetrics.QUALITY_GATE_DETAILS_KEY);
measureRepository.add(project, qgDetailsMetric, detailsMeasure);
}
@Override
public String getDescription() {
return "Compute Quality Gate measures";
}
private static final class QualityGateDetailsDataBuilder {
private Measure.Level globalLevel = Measure.Level.OK;
private List labels = new ArrayList<>();
private List evaluatedConditions = new ArrayList<>();
public Measure.Level getGlobalLevel() {
return globalLevel;
}
public void addLabel(@Nullable String label) {
if (StringUtils.isNotBlank(label)) {
labels.add(label);
}
}
public List getLabels() {
return labels;
}
public void addEvaluatedCondition(MetricEvaluationResult metricEvaluationResult) {
Measure.Level level = metricEvaluationResult.evaluationResult.getLevel();
if (Measure.Level.WARN == level && this.globalLevel != Measure.Level.ERROR) {
globalLevel = Measure.Level.WARN;
} else if (Measure.Level.ERROR == level) {
globalLevel = Measure.Level.ERROR;
}
evaluatedConditions.add(
new EvaluatedCondition(metricEvaluationResult.condition, level, metricEvaluationResult.evaluationResult.getValue()));
}
public List getEvaluatedConditions() {
return evaluatedConditions;
}
}
private enum EvaluatedConditionToCondition implements Function {
INSTANCE;
@Override
@Nonnull
public Condition apply(@Nonnull EvaluatedCondition input) {
return input.getCondition();
}
}
private static class MetricEvaluationResult {
private final EvaluationResult evaluationResult;
private final Condition condition;
private MetricEvaluationResult(EvaluationResult evaluationResult, Condition condition) {
this.evaluationResult = evaluationResult;
this.condition = condition;
}
}
private enum ConditionToMetric implements Function {
INSTANCE;
@Override
@Nonnull
public Metric apply(@Nonnull Condition input) {
return input.getMetric();
}
}
public enum ConditionToPeriod implements Function {
INSTANCE;
@Override
@Nullable
public Integer apply(@Nonnull Condition input) {
return input.getPeriod();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy