@@ -22,6 +22,7 @@ package org.sonar.ce.task.projectanalysis.api.posttask; | |||
import java.util.Set; | |||
import java.util.function.Function; | |||
import org.sonar.api.ce.posttask.QualityGate; | |||
import org.sonar.api.ce.posttask.QualityGate.EvaluationStatus; | |||
import org.sonar.api.measures.Metric; | |||
import org.sonar.core.util.stream.MoreCollectors; | |||
import org.sonar.server.qualitygate.Condition; | |||
@@ -37,13 +38,13 @@ public class QGToEvaluatedQG implements Function<QualityGate, EvaluatedQualityGa | |||
@Override public EvaluatedQualityGate apply(QualityGate qg) { | |||
EvaluatedQualityGate.Builder builder = EvaluatedQualityGate.newBuilder(); | |||
Set<org.sonar.server.qualitygate.Condition> conditions = qg.getConditions().stream() | |||
Set<Condition> conditions = qg.getConditions().stream() | |||
.map(q -> { | |||
org.sonar.server.qualitygate.Condition condition = new org.sonar.server.qualitygate.Condition(q.getMetricKey(), Condition.Operator.valueOf(q.getOperator().name()), | |||
Condition condition = new Condition(q.getMetricKey(), Condition.Operator.valueOf(q.getOperator().name()), | |||
q.getErrorThreshold()); | |||
builder.addCondition(condition, | |||
builder.addEvaluatedCondition(condition, | |||
EvaluatedCondition.EvaluationStatus.valueOf(q.getStatus().name()), | |||
q.getStatus() == org.sonar.api.ce.posttask.QualityGate.EvaluationStatus.NO_VALUE ? null : q.getValue()); | |||
q.getStatus() == EvaluationStatus.NO_VALUE ? null : q.getValue()); | |||
return condition; | |||
}) | |||
.collect(MoreCollectors.toSet()); |
@@ -150,7 +150,7 @@ public class WebhookPostTaskTest { | |||
webQualityGate = EvaluatedQualityGate.newBuilder() | |||
.setQualityGate(new org.sonar.server.qualitygate.QualityGate(qualityGate.getId(), qualityGate.getName(), Collections.singleton(qgCondition))) | |||
.setStatus(Metric.Level.valueOf(qualityGate.getStatus().name())) | |||
.addCondition(qgCondition, EvaluatedCondition.EvaluationStatus.valueOf(condition.getStatus().name()), condition.getValue()) | |||
.addEvaluatedCondition(qgCondition, EvaluatedCondition.EvaluationStatus.valueOf(condition.getStatus().name()), condition.getValue()) | |||
.build(); | |||
} | |||
@@ -20,11 +20,18 @@ | |||
package org.sonar.server.qualitygate; | |||
import com.google.common.collect.ImmutableSet; | |||
import java.util.HashMap; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.Collection; | |||
import java.util.Comparator; | |||
import java.util.LinkedHashMap; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.Objects; | |||
import java.util.Set; | |||
import java.util.function.Function; | |||
import java.util.stream.Collectors; | |||
import java.util.stream.IntStream; | |||
import javax.annotation.Nullable; | |||
import javax.annotation.concurrent.Immutable; | |||
import org.sonar.api.measures.Metric; | |||
@@ -32,15 +39,25 @@ import org.sonar.server.qualitygate.EvaluatedCondition.EvaluationStatus; | |||
import static com.google.common.base.Preconditions.checkArgument; | |||
import static java.util.Objects.requireNonNull; | |||
import static org.sonar.api.measures.CoreMetrics.COVERAGE_KEY; | |||
import static org.sonar.api.measures.CoreMetrics.DUPLICATED_LINES_DENSITY_KEY; | |||
import static org.sonar.api.measures.CoreMetrics.NEW_COVERAGE_KEY; | |||
import static org.sonar.api.measures.CoreMetrics.NEW_DUPLICATED_LINES_DENSITY_KEY; | |||
import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY; | |||
import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_RATING_KEY; | |||
import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_RATING_KEY; | |||
import static org.sonar.api.measures.CoreMetrics.RELIABILITY_RATING_KEY; | |||
import static org.sonar.api.measures.CoreMetrics.SECURITY_RATING_KEY; | |||
import static org.sonar.api.measures.CoreMetrics.SQALE_RATING_KEY; | |||
@Immutable | |||
public class EvaluatedQualityGate { | |||
private final QualityGate qualityGate; | |||
private final Metric.Level status; | |||
private final Set<EvaluatedCondition> evaluatedConditions; | |||
private final Collection<EvaluatedCondition> evaluatedConditions; | |||
private final boolean ignoredConditionsOnSmallChangeset; | |||
private EvaluatedQualityGate(QualityGate qualityGate, Metric.Level status, Set<EvaluatedCondition> evaluatedConditions, boolean ignoredConditionsOnSmallChangeset) { | |||
private EvaluatedQualityGate(QualityGate qualityGate, Metric.Level status, Collection<EvaluatedCondition> evaluatedConditions, boolean ignoredConditionsOnSmallChangeset) { | |||
this.qualityGate = requireNonNull(qualityGate, "qualityGate can't be null"); | |||
this.status = requireNonNull(status, "status can't be null"); | |||
this.evaluatedConditions = evaluatedConditions; | |||
@@ -55,7 +72,7 @@ public class EvaluatedQualityGate { | |||
return status; | |||
} | |||
public Set<EvaluatedCondition> getEvaluatedConditions() { | |||
public Collection<EvaluatedCondition> getEvaluatedConditions() { | |||
return evaluatedConditions; | |||
} | |||
@@ -96,9 +113,21 @@ public class EvaluatedQualityGate { | |||
} | |||
public static final class Builder { | |||
private static final List<String> CONDITIONS_ORDER = Arrays.asList(NEW_SECURITY_RATING_KEY, SECURITY_RATING_KEY, NEW_RELIABILITY_RATING_KEY, | |||
RELIABILITY_RATING_KEY, NEW_MAINTAINABILITY_RATING_KEY, SQALE_RATING_KEY, NEW_COVERAGE_KEY, COVERAGE_KEY, NEW_DUPLICATED_LINES_DENSITY_KEY, | |||
DUPLICATED_LINES_DENSITY_KEY); | |||
private static final Map<String, Integer> CONDITIONS_ORDER_IDX = IntStream.range(0, CONDITIONS_ORDER.size()).boxed() | |||
.collect(Collectors.toMap(CONDITIONS_ORDER::get, x -> x)); | |||
private static final Comparator<EvaluatedCondition> CONDITION_COMPARATOR = (c1, c2) -> { | |||
Function<EvaluatedCondition, Integer> byList = c -> CONDITIONS_ORDER_IDX.getOrDefault(c.getCondition().getMetricKey(), Integer.MAX_VALUE); | |||
Function<EvaluatedCondition, String> byMetricKey = c -> c.getCondition().getMetricKey(); | |||
return Comparator.comparing(byList).thenComparing(byMetricKey).compare(c1, c2); | |||
}; | |||
private QualityGate qualityGate; | |||
private Metric.Level status; | |||
private final Map<Condition, EvaluatedCondition> evaluatedConditions = new HashMap<>(); | |||
private final Map<Condition, EvaluatedCondition> evaluatedConditions = new LinkedHashMap<>(); | |||
private boolean ignoredConditionsOnSmallChangeset = false; | |||
private Builder() { | |||
@@ -120,12 +149,12 @@ public class EvaluatedQualityGate { | |||
return this; | |||
} | |||
public Builder addCondition(Condition condition, EvaluationStatus status, @Nullable String value) { | |||
public Builder addEvaluatedCondition(Condition condition, EvaluationStatus status, @Nullable String value) { | |||
evaluatedConditions.put(condition, new EvaluatedCondition(condition, status, value)); | |||
return this; | |||
} | |||
public Builder addCondition(EvaluatedCondition c) { | |||
public Builder addEvaluatedCondition(EvaluatedCondition c) { | |||
evaluatedConditions.put(c.getCondition(), c); | |||
return this; | |||
} | |||
@@ -135,14 +164,17 @@ public class EvaluatedQualityGate { | |||
} | |||
public EvaluatedQualityGate build() { | |||
checkEvaluatedConditions(qualityGate, evaluatedConditions); | |||
List<EvaluatedCondition> sortedEvaluatedConditions = new ArrayList<>(evaluatedConditions.values()); | |||
sortedEvaluatedConditions.sort(CONDITION_COMPARATOR); | |||
return new EvaluatedQualityGate( | |||
this.qualityGate, | |||
this.status, | |||
checkEvaluatedConditions(qualityGate, evaluatedConditions), | |||
sortedEvaluatedConditions, | |||
ignoredConditionsOnSmallChangeset); | |||
} | |||
private static Set<EvaluatedCondition> checkEvaluatedConditions(QualityGate qualityGate, Map<Condition, EvaluatedCondition> evaluatedConditions) { | |||
private static void checkEvaluatedConditions(QualityGate qualityGate, Map<Condition, EvaluatedCondition> evaluatedConditions) { | |||
Set<Condition> conditions = qualityGate.getConditions(); | |||
Set<Condition> conditionsNotEvaluated = conditions.stream() | |||
@@ -154,8 +186,6 @@ public class EvaluatedQualityGate { | |||
.filter(c -> !conditions.contains(c)) | |||
.collect(Collectors.toSet()); | |||
checkArgument(unknownConditions.isEmpty(), "Evaluation provided for unknown conditions: %s", unknownConditions); | |||
return ImmutableSet.copyOf(evaluatedConditions.values()); | |||
} | |||
} | |||
} |
@@ -54,10 +54,10 @@ public class QualityGateEvaluatorImpl implements QualityGateEvaluator { | |||
EvaluatedCondition evaluation = ConditionEvaluator.evaluate(condition, measures); | |||
if (isSmallChangeset && evaluation.getStatus() != EvaluationStatus.OK && METRICS_TO_IGNORE_ON_SMALL_CHANGESETS.contains(metricKey)) { | |||
result.addCondition(new EvaluatedCondition(evaluation.getCondition(), EvaluationStatus.OK, evaluation.getValue().orElse(null))); | |||
result.addEvaluatedCondition(new EvaluatedCondition(evaluation.getCondition(), EvaluationStatus.OK, evaluation.getValue().orElse(null))); | |||
result.setIgnoredConditionsOnSmallChangeset(true); | |||
} else { | |||
result.addCondition(evaluation); | |||
result.addEvaluatedCondition(evaluation); | |||
} | |||
}); | |||
@@ -20,11 +20,14 @@ | |||
package org.sonar.server.qualitygate; | |||
import com.google.common.collect.ImmutableSet; | |||
import java.util.Arrays; | |||
import java.util.HashSet; | |||
import java.util.Random; | |||
import org.apache.commons.lang.RandomStringUtils; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.sonar.api.measures.CoreMetrics; | |||
import org.sonar.api.measures.Metric.Level; | |||
import static java.util.Collections.emptySet; | |||
@@ -36,9 +39,13 @@ public class EvaluatedQualityGateTest { | |||
private static final String QUALITY_GATE_ID = "qg_id"; | |||
private static final String QUALITY_GATE_NAME = "qg_name"; | |||
private static final QualityGate NO_CONDITION_QUALITY_GATE = new QualityGate(QUALITY_GATE_ID, QUALITY_GATE_NAME, emptySet()); | |||
private static final Condition CONDITION_1 = new Condition("metric_key", Condition.Operator.LESS_THAN, "2"); | |||
private static final Condition CONDITION_2 = new Condition("metric_key_2", Condition.Operator.GREATER_THAN, "6"); | |||
private static final Condition CONDITION_1 = new Condition("metric_key_1", Condition.Operator.LESS_THAN, "2"); | |||
private static final Condition CONDITION_2 = new Condition("a_metric", Condition.Operator.GREATER_THAN, "6"); | |||
private static final Condition CONDITION_3 = new Condition(CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY, Condition.Operator.GREATER_THAN, "6"); | |||
private static final QualityGate ONE_CONDITION_QUALITY_GATE = new QualityGate(QUALITY_GATE_ID, QUALITY_GATE_NAME, singleton(CONDITION_1)); | |||
private static final QualityGate ALL_CONDITIONS_QUALITY_GATE = new QualityGate(QUALITY_GATE_ID, QUALITY_GATE_NAME, | |||
new HashSet<>(Arrays.asList(CONDITION_1, CONDITION_2, CONDITION_3))); | |||
@Rule | |||
public ExpectedException expectedException = ExpectedException.none(); | |||
@@ -66,7 +73,7 @@ public class EvaluatedQualityGateTest { | |||
expectedException.expect(NullPointerException.class); | |||
expectedException.expectMessage("condition can't be null"); | |||
builder.addCondition(null, EvaluatedCondition.EvaluationStatus.ERROR, "a_value"); | |||
builder.addEvaluatedCondition(null, EvaluatedCondition.EvaluationStatus.ERROR, "a_value"); | |||
} | |||
@Test | |||
@@ -74,12 +81,12 @@ public class EvaluatedQualityGateTest { | |||
expectedException.expect(NullPointerException.class); | |||
expectedException.expectMessage("status can't be null"); | |||
builder.addCondition(new Condition("metric_key", Condition.Operator.LESS_THAN, "2"), null, "a_value"); | |||
builder.addEvaluatedCondition(new Condition("metric_key", Condition.Operator.LESS_THAN, "2"), null, "a_value"); | |||
} | |||
@Test | |||
public void addCondition_accepts_null_value() { | |||
builder.addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.NO_VALUE, null); | |||
builder.addEvaluatedCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.NO_VALUE, null); | |||
assertThat(builder.getEvaluatedConditions()) | |||
.containsOnly(new EvaluatedCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.NO_VALUE, null)); | |||
@@ -94,7 +101,7 @@ public class EvaluatedQualityGateTest { | |||
public void build_fails_with_IAE_if_condition_added_and_no_on_QualityGate() { | |||
builder.setQualityGate(NO_CONDITION_QUALITY_GATE) | |||
.setStatus(randomStatus) | |||
.addCondition(CONDITION_1, randomEvaluationStatus, randomValue); | |||
.addEvaluatedCondition(CONDITION_1, randomEvaluationStatus, randomValue); | |||
expectedException.expect(IllegalArgumentException.class); | |||
expectedException.expectMessage("Evaluation provided for unknown conditions: [" + CONDITION_1 + "]"); | |||
@@ -113,12 +120,28 @@ public class EvaluatedQualityGateTest { | |||
builder.build(); | |||
} | |||
@Test | |||
public void getEvaluatedConditions_is_sorted() { | |||
EvaluatedQualityGate underTest = builder | |||
.setQualityGate(ALL_CONDITIONS_QUALITY_GATE) | |||
.setStatus(randomStatus) | |||
.addEvaluatedCondition(CONDITION_1, randomEvaluationStatus, randomValue) | |||
.addEvaluatedCondition(CONDITION_2, randomEvaluationStatus, randomValue) | |||
.addEvaluatedCondition(CONDITION_3, randomEvaluationStatus, randomValue) | |||
.build(); | |||
assertThat(underTest.getQualityGate()).isEqualTo(ALL_CONDITIONS_QUALITY_GATE); | |||
assertThat(underTest.getStatus()).isEqualTo(randomStatus); | |||
assertThat(underTest.getEvaluatedConditions()).extracting(c -> c.getCondition().getMetricKey()) | |||
.contains(CONDITION_3.getMetricKey(), CONDITION_2.getMetricKey(), CONDITION_1.getMetricKey()); | |||
} | |||
@Test | |||
public void verify_getters() { | |||
EvaluatedQualityGate underTest = builder | |||
.setQualityGate(ONE_CONDITION_QUALITY_GATE) | |||
.setStatus(randomStatus) | |||
.addCondition(CONDITION_1, randomEvaluationStatus, randomValue) | |||
.addEvaluatedCondition(CONDITION_1, randomEvaluationStatus, randomValue) | |||
.build(); | |||
assertThat(underTest.getQualityGate()).isEqualTo(ONE_CONDITION_QUALITY_GATE); | |||
@@ -145,8 +168,8 @@ public class EvaluatedQualityGateTest { | |||
EvaluatedQualityGate underTest = builder | |||
.setQualityGate(qualityGate) | |||
.setStatus(randomStatus) | |||
.addCondition(CONDITION_1, randomEvaluationStatus, randomValue) | |||
.addCondition(CONDITION_2, EvaluatedCondition.EvaluationStatus.ERROR, "bad") | |||
.addEvaluatedCondition(CONDITION_1, randomEvaluationStatus, randomValue) | |||
.addEvaluatedCondition(CONDITION_2, EvaluatedCondition.EvaluationStatus.ERROR, "bad") | |||
.build(); | |||
assertThat(underTest.getQualityGate()).isEqualTo(qualityGate); | |||
@@ -161,7 +184,7 @@ public class EvaluatedQualityGateTest { | |||
EvaluatedQualityGate.Builder builder = this.builder | |||
.setQualityGate(ONE_CONDITION_QUALITY_GATE) | |||
.setStatus(Level.ERROR) | |||
.addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.ERROR, "foo"); | |||
.addEvaluatedCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.ERROR, "foo"); | |||
EvaluatedQualityGate underTest = builder.build(); | |||
assertThat(underTest).isEqualTo(builder.build()); | |||
@@ -173,7 +196,7 @@ public class EvaluatedQualityGateTest { | |||
assertThat(underTest).isNotEqualTo(newBuilder() | |||
.setQualityGate(ONE_CONDITION_QUALITY_GATE) | |||
.setStatus(Level.ERROR) | |||
.addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.OK, "foo") | |||
.addEvaluatedCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.OK, "foo") | |||
.build()); | |||
} | |||
@@ -182,7 +205,7 @@ public class EvaluatedQualityGateTest { | |||
EvaluatedQualityGate.Builder builder = this.builder | |||
.setQualityGate(ONE_CONDITION_QUALITY_GATE) | |||
.setStatus(Level.ERROR) | |||
.addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.ERROR, "foo"); | |||
.addEvaluatedCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.ERROR, "foo"); | |||
EvaluatedQualityGate underTest = builder.build(); | |||
assertThat(underTest.hashCode()).isEqualTo(builder.build().hashCode()); | |||
@@ -194,7 +217,7 @@ public class EvaluatedQualityGateTest { | |||
assertThat(underTest.hashCode()).isNotEqualTo(newBuilder() | |||
.setQualityGate(ONE_CONDITION_QUALITY_GATE) | |||
.setStatus(Level.ERROR) | |||
.addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.OK, "foo") | |||
.addEvaluatedCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.OK, "foo") | |||
.build().hashCode()); | |||
} | |||
} |
@@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableSet; | |||
import java.util.Set; | |||
import java.util.stream.Collectors; | |||
import org.junit.Test; | |||
import org.sonar.api.measures.CoreMetrics; | |||
import org.sonar.api.measures.Metric; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
@@ -54,6 +55,23 @@ public class QualityGateEvaluatorImplTest { | |||
assertThat(underTest.getMetricKeys(gate)).containsAll(metricKeys); | |||
} | |||
@Test | |||
public void evaluated_conditions_are_sorted() { | |||
Set<String> metricKeys = ImmutableSet.of("foo", "bar", CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY); | |||
Set<Condition> conditions = metricKeys.stream().map(key -> { | |||
Condition condition = mock(Condition.class); | |||
when(condition.getMetricKey()).thenReturn(key); | |||
return condition; | |||
}).collect(Collectors.toSet()); | |||
QualityGate gate = mock(QualityGate.class); | |||
when(gate.getConditions()).thenReturn(conditions); | |||
QualityGateEvaluator.Measures measures = mock(QualityGateEvaluator.Measures.class); | |||
assertThat(underTest.evaluate(gate, measures).getEvaluatedConditions()).extracting(x -> x.getCondition().getMetricKey()) | |||
.containsExactly(CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY, "bar", "foo"); | |||
} | |||
@Test | |||
public void evaluate_is_OK_for_empty_qgate() { | |||
QualityGate gate = mock(QualityGate.class); |
@@ -61,7 +61,7 @@ public class WebhookPayloadFactoryImplTest { | |||
EvaluatedQualityGate gate = EvaluatedQualityGate.newBuilder() | |||
.setQualityGate(new QualityGate("G1", "Gate One", singleton(condition))) | |||
.setStatus(Metric.Level.ERROR) | |||
.addCondition(condition, EvaluatedCondition.EvaluationStatus.ERROR, "74.0") | |||
.addEvaluatedCondition(condition, EvaluatedCondition.EvaluationStatus.ERROR, "74.0") | |||
.build(); | |||
ProjectAnalysis analysis = newAnalysis(task, gate, null, 1_500_000_000_000L, emptyMap()); | |||
@@ -105,7 +105,7 @@ public class WebhookPayloadFactoryImplTest { | |||
EvaluatedQualityGate gate = EvaluatedQualityGate.newBuilder() | |||
.setQualityGate(new QualityGate("G1", "Gate One", singleton(condition))) | |||
.setStatus(Metric.Level.ERROR) | |||
.addCondition(condition, EvaluatedCondition.EvaluationStatus.NO_VALUE, null) | |||
.addEvaluatedCondition(condition, EvaluatedCondition.EvaluationStatus.NO_VALUE, null) | |||
.build(); | |||
ProjectAnalysis analysis = newAnalysis(task, gate, null, 1_500_000_000_000L, emptyMap()); | |||
@@ -211,7 +211,7 @@ public class LiveQualityGateComputerImplTest { | |||
this.measures = measures; | |||
EvaluatedQualityGate.Builder builder = EvaluatedQualityGate.newBuilder().setQualityGate(gate).setStatus(Metric.Level.OK); | |||
for (Condition condition : gate.getConditions()) { | |||
builder.addCondition(condition, EvaluatedCondition.EvaluationStatus.OK, "bar"); | |||
builder.addEvaluatedCondition(condition, EvaluatedCondition.EvaluationStatus.OK, "bar"); | |||
} | |||
return builder.build(); | |||
} |
@@ -23,7 +23,6 @@ import { ComponentNavMeta } from '../ComponentNavMeta'; | |||
import { | |||
mockShortLivingBranch, | |||
mockComponent, | |||
mockCurrentUser, | |||
mockLongLivingBranch, | |||
mockPullRequest | |||
} from '../../../../../helpers/testMocks'; | |||
@@ -51,7 +50,9 @@ function shallowRender(props = {}) { | |||
<ComponentNavMeta | |||
branchLike={mockShortLivingBranch()} | |||
component={mockComponent({ analysisDate: '2017-01-02T00:00:00.000Z', version: '0.0.1' })} | |||
currentUser={mockCurrentUser({ isLoggedIn: false })} | |||
currentUser={{ | |||
isLoggedIn: false | |||
}} | |||
warnings={[]} | |||
{...props} | |||
/> |