123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- /*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info 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.ce.task.projectanalysis.step;
-
- import com.google.common.collect.ImmutableMap;
- import com.google.common.collect.Multimap;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.List;
- import java.util.Map;
- import java.util.Optional;
- import java.util.Set;
- import java.util.function.Function;
- import java.util.stream.Collectors;
- import javax.annotation.Nullable;
- import org.apache.commons.lang3.StringUtils;
- import org.sonar.api.measures.CoreMetrics;
- import org.sonar.ce.task.projectanalysis.component.Component;
- import org.sonar.ce.task.projectanalysis.component.CrawlerDepthLimit;
- import org.sonar.ce.task.projectanalysis.component.DepthTraversalTypeAwareCrawler;
- import org.sonar.ce.task.projectanalysis.component.TreeRootHolder;
- import org.sonar.ce.task.projectanalysis.component.TypeAwareVisitorAdapter;
- import org.sonar.ce.task.projectanalysis.measure.Measure;
- import org.sonar.ce.task.projectanalysis.measure.MeasureRepository;
- import org.sonar.ce.task.projectanalysis.measure.QualityGateStatus;
- import org.sonar.ce.task.projectanalysis.measure.qualitygatedetails.EvaluatedCondition;
- import org.sonar.ce.task.projectanalysis.measure.qualitygatedetails.QualityGateDetailsData;
- import org.sonar.ce.task.projectanalysis.metric.Metric;
- import org.sonar.ce.task.projectanalysis.metric.MetricRepository;
- import org.sonar.ce.task.projectanalysis.qualitygate.Condition;
- import org.sonar.ce.task.projectanalysis.qualitygate.ConditionEvaluator;
- import org.sonar.ce.task.projectanalysis.qualitygate.ConditionStatus;
- import org.sonar.ce.task.projectanalysis.qualitygate.EvaluationResult;
- import org.sonar.ce.task.projectanalysis.qualitygate.EvaluationResultTextConverter;
- import org.sonar.ce.task.projectanalysis.qualitygate.MutableQualityGateStatusHolder;
- import org.sonar.ce.task.projectanalysis.qualitygate.QualityGate;
- import org.sonar.ce.task.projectanalysis.qualitygate.QualityGateHolder;
- import org.sonar.ce.task.step.ComputationStep;
- import org.sonar.core.util.stream.MoreCollectors;
-
- import static java.lang.String.format;
- import static org.sonar.ce.task.projectanalysis.component.ComponentVisitor.Order.PRE_ORDER;
- import static org.sonar.ce.task.projectanalysis.qualitygate.ConditionStatus.NO_VALUE_STATUS;
- import static org.sonar.ce.task.projectanalysis.qualitygate.ConditionStatus.create;
-
- /**
- * This step:
- * <ul>
- * <li>updates the QualityGateStatus of all the project's measures for the metrics of the conditions of the current
- * QualityGate (retrieved from {@link QualityGateHolder})</li>
- * <li>computes the measures on the project for metrics {@link CoreMetrics#QUALITY_GATE_DETAILS_KEY} and
- * {@link CoreMetrics#ALERT_STATUS_KEY}</li>
- * </ul>
- *
- */
- public class QualityGateMeasuresStep implements ComputationStep {
- private final TreeRootHolder treeRootHolder;
- private final QualityGateHolder qualityGateHolder;
- private final MutableQualityGateStatusHolder qualityGateStatusHolder;
- private final MeasureRepository measureRepository;
- private final MetricRepository metricRepository;
- private final EvaluationResultTextConverter evaluationResultTextConverter;
- private final SmallChangesetQualityGateSpecialCase smallChangesetQualityGateSpecialCase;
-
- public QualityGateMeasuresStep(TreeRootHolder treeRootHolder,
- QualityGateHolder qualityGateHolder, MutableQualityGateStatusHolder qualityGateStatusHolder,
- MeasureRepository measureRepository, MetricRepository metricRepository,
- EvaluationResultTextConverter evaluationResultTextConverter, SmallChangesetQualityGateSpecialCase smallChangesetQualityGateSpecialCase) {
- this.treeRootHolder = treeRootHolder;
- this.qualityGateHolder = qualityGateHolder;
- this.qualityGateStatusHolder = qualityGateStatusHolder;
- this.evaluationResultTextConverter = evaluationResultTextConverter;
- this.measureRepository = measureRepository;
- this.metricRepository = metricRepository;
- this.smallChangesetQualityGateSpecialCase = smallChangesetQualityGateSpecialCase;
- }
-
- @Override
- public void execute(ComputationStep.Context context) {
- 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> 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 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.ce.task.projectanalysis.qualitygate.QualityGateStatus convert(Measure.Level globalLevel) {
- switch (globalLevel) {
- case OK:
- return org.sonar.ce.task.projectanalysis.qualitygate.QualityGateStatus.OK;
- case ERROR:
- return org.sonar.ce.task.projectanalysis.qualitygate.QualityGateStatus.ERROR;
- default:
- throw new IllegalArgumentException(format(
- "Unsupported value '%s' of Measure.Level can not be converted to QualityGateStatus",
- globalLevel));
- }
- }
-
- private static Map<Condition, ConditionStatus> createStatusPerCondition(Collection<Condition> conditions, Collection<EvaluatedCondition> evaluatedConditions) {
- Map<Condition, EvaluatedCondition> evaluatedConditionPerCondition = evaluatedConditions.stream()
- .collect(Collectors.toMap(EvaluatedCondition::getCondition, Function.identity()));
-
- ImmutableMap.Builder<Condition, ConditionStatus> 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<Condition> conditions, QualityGateDetailsDataBuilder builder) {
- Multimap<Metric, Condition> conditionsPerMetric = conditions.stream().collect(MoreCollectors.index(Condition::getMetric, Function.identity()));
- boolean ignoredConditions = false;
- for (Map.Entry<Metric, Collection<Condition>> entry : conditionsPerMetric.asMap().entrySet()) {
- Metric metric = entry.getKey();
- Optional<Measure> measure = measureRepository.getRawMeasure(project, metric);
- if (!measure.isPresent()) {
- continue;
- }
-
- final MetricEvaluationResult metricEvaluationResult = evaluateQualityGate(measure.get(), entry.getValue());
- final MetricEvaluationResult finalMetricEvaluationResult;
- if (smallChangesetQualityGateSpecialCase.appliesTo(project, metricEvaluationResult)) {
- finalMetricEvaluationResult = smallChangesetQualityGateSpecialCase.apply(metricEvaluationResult);
- ignoredConditions = true;
- } else {
- finalMetricEvaluationResult = metricEvaluationResult;
- }
- String text = evaluationResultTextConverter.asText(finalMetricEvaluationResult.condition, finalMetricEvaluationResult.evaluationResult);
- builder.addLabel(text);
-
- Measure updatedMeasure = Measure.updatedMeasureBuilder(measure.get())
- .setQualityGateStatus(new QualityGateStatus(finalMetricEvaluationResult.evaluationResult.level(), text))
- .create();
- measureRepository.update(project, metric, updatedMeasure);
-
- builder.addEvaluatedCondition(finalMetricEvaluationResult);
- }
- builder.setIgnoredConditions(ignoredConditions);
- }
-
- private static MetricEvaluationResult evaluateQualityGate(Measure measure, Collection<Condition> conditions) {
- ConditionEvaluator conditionEvaluator = new ConditionEvaluator();
- MetricEvaluationResult metricEvaluationResult = null;
- for (Condition newCondition : conditions) {
- EvaluationResult newEvaluationResult = conditionEvaluator.evaluate(newCondition, measure);
- if (metricEvaluationResult == null || newEvaluationResult.level().ordinal() > metricEvaluationResult.evaluationResult.level().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(), builder.isIgnoredConditions()).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<String> labels = new ArrayList<>();
- private List<EvaluatedCondition> evaluatedConditions = new ArrayList<>();
- private boolean ignoredConditions;
-
- public Measure.Level getGlobalLevel() {
- return globalLevel;
- }
-
- public void addLabel(@Nullable String label) {
- if (StringUtils.isNotBlank(label)) {
- labels.add(label);
- }
- }
-
- public List<String> getLabels() {
- return labels;
- }
-
- public void addEvaluatedCondition(MetricEvaluationResult metricEvaluationResult) {
- Measure.Level level = metricEvaluationResult.evaluationResult.level();
- if (Measure.Level.ERROR == level) {
- globalLevel = Measure.Level.ERROR;
- }
- evaluatedConditions.add(
- new EvaluatedCondition(metricEvaluationResult.condition, level, metricEvaluationResult.evaluationResult.value()));
- }
-
- public List<EvaluatedCondition> getEvaluatedConditions() {
- return evaluatedConditions;
- }
-
- public boolean isIgnoredConditions() {
- return ignoredConditions;
- }
-
- public QualityGateDetailsDataBuilder setIgnoredConditions(boolean ignoredConditions) {
- this.ignoredConditions = ignoredConditions;
- return this;
- }
- }
-
- static class MetricEvaluationResult {
- final EvaluationResult evaluationResult;
- final Condition condition;
-
- MetricEvaluationResult(EvaluationResult evaluationResult, Condition condition) {
- this.evaluationResult = evaluationResult;
- this.condition = condition;
- }
- }
-
- }
|