@@ -23,19 +23,34 @@ import com.google.gson.JsonArray; | |||
import com.google.gson.JsonElement; | |||
import com.google.gson.JsonObject; | |||
import com.google.gson.JsonParser; | |||
import java.util.Map; | |||
import java.util.Optional; | |||
import java.util.function.Predicate; | |||
import java.util.stream.Stream; | |||
import java.util.stream.StreamSupport; | |||
import javax.annotation.Nullable; | |||
import org.sonar.api.measures.Metric; | |||
import org.sonar.db.component.SnapshotDto; | |||
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse; | |||
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse.NewCodePeriod; | |||
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse.Period; | |||
import static com.google.common.base.Strings.isNullOrEmpty; | |||
import static java.util.stream.Collectors.toUnmodifiableMap; | |||
import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_RATING; | |||
import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_RATING; | |||
import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED; | |||
import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_RATING; | |||
import static org.sonar.api.utils.DateUtils.formatDateTime; | |||
public class QualityGateDetailsFormatter { | |||
public static final String METRIC_KEY = "metric"; | |||
private static final Map<String, Double> CAYC_REQUIREMENTS = Stream.of( | |||
NEW_MAINTAINABILITY_RATING, | |||
NEW_RELIABILITY_RATING, | |||
NEW_SECURITY_HOTSPOTS_REVIEWED, | |||
NEW_SECURITY_RATING | |||
).collect(toUnmodifiableMap(Metric::getKey, Metric::getBestValue)); | |||
private final Optional<String> optionalMeasureData; | |||
private final Optional<SnapshotDto> optionalSnapshot; | |||
private final ProjectStatusResponse.ProjectStatus.Builder projectStatusBuilder; | |||
@@ -58,11 +73,30 @@ public class QualityGateDetailsFormatter { | |||
formatIgnoredConditions(json); | |||
formatConditions(json.getAsJsonArray("conditions")); | |||
formatCleanAsYouCodeCompliant(json.getAsJsonArray("conditions")); | |||
formatPeriods(); | |||
return projectStatusBuilder.build(); | |||
} | |||
private void formatCleanAsYouCodeCompliant(@Nullable JsonArray jsonConditions) { | |||
if (jsonConditions == null) { | |||
return; | |||
} | |||
long matchCount = jsonConditions.asList().stream() | |||
.map(JsonElement::getAsJsonObject) | |||
.filter(jsonObject -> CAYC_REQUIREMENTS.containsKey(jsonObject.get(METRIC_KEY).getAsString())) | |||
.filter(jsonObject -> { | |||
String metricKey = jsonObject.get(METRIC_KEY).getAsString(); | |||
Double value = jsonObject.get("error").getAsDouble(); | |||
return CAYC_REQUIREMENTS.get(metricKey).compareTo(value) == 0; | |||
}) | |||
.count(); | |||
projectStatusBuilder.setIsCaycCompliant(matchCount == CAYC_REQUIREMENTS.size()); | |||
} | |||
private void formatIgnoredConditions(JsonObject json) { | |||
JsonElement ignoredConditions = json.get("ignoredConditions"); | |||
if (ignoredConditions != null) { | |||
@@ -167,7 +201,7 @@ public class QualityGateDetailsFormatter { | |||
} | |||
private static void formatConditionMetric(ProjectStatusResponse.Condition.Builder conditionBuilder, JsonObject jsonCondition) { | |||
JsonElement metric = jsonCondition.get("metric"); | |||
JsonElement metric = jsonCondition.get(METRIC_KEY); | |||
if (metric != null && !isNullOrEmpty(metric.getAsString())) { | |||
conditionBuilder.setMetricKey(metric.getAsString()); | |||
} |
@@ -21,9 +21,11 @@ package org.sonar.server.qualitygate.ws; | |||
import java.io.IOException; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.Optional; | |||
import javax.annotation.Nullable; | |||
import org.apache.commons.io.IOUtils; | |||
import org.apache.commons.lang.text.StrSubstitutor; | |||
import org.junit.Test; | |||
import org.sonar.db.component.SnapshotDto; | |||
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse; | |||
@@ -143,6 +145,30 @@ public class QualityGateDetailsFormatterTest { | |||
.hasMessageContaining("Unknown quality gate comparator 'UNKNOWN'"); | |||
} | |||
@Test | |||
public void verify_cayc_quality_gate_checked() throws IOException { | |||
String measureDataRaw = IOUtils.toString(getClass().getResource("QualityGateDetailsFormatterTest/cayc_compliant_qg.json")); | |||
String measureDataCompliant = StrSubstitutor.replace(measureDataRaw, Map.of("nmr_error", "1.0")); | |||
underTest = newQualityGateDetailsFormatter(measureDataCompliant, null); | |||
ProjectStatus result = underTest.format(); | |||
assertThat(result.getIsCaycCompliant()).isTrue(); | |||
String measureDataNonCompliant = StrSubstitutor.replace(measureDataRaw, Map.of("nmr_error", "2.0")); | |||
underTest = newQualityGateDetailsFormatter(measureDataNonCompliant, null); | |||
result = underTest.format(); | |||
assertThat(result.getIsCaycCompliant()).isFalse(); | |||
} | |||
@Test | |||
public void verify_cayc_quality_gate_with_missing_metric() throws IOException { | |||
String measureData = IOUtils.toString(getClass().getResource("QualityGateDetailsFormatterTest/cayc_missing_metric.json")); | |||
underTest = newQualityGateDetailsFormatter(measureData, null); | |||
ProjectStatus result = underTest.format(); | |||
assertThat(result.getIsCaycCompliant()).isFalse(); | |||
} | |||
private static QualityGateDetailsFormatter newQualityGateDetailsFormatter(@Nullable String measureData, @Nullable SnapshotDto snapshotDto) { | |||
return new QualityGateDetailsFormatter(Optional.ofNullable(measureData), Optional.ofNullable(snapshotDto)); | |||
} |
@@ -0,0 +1,41 @@ | |||
{ | |||
"level": "ERROR", | |||
"conditions": [ | |||
{ | |||
"metric": "new_maintainability_rating", | |||
"op": "LT", | |||
"period": 1, | |||
"warning": "", | |||
"error": "${nmr_error}", | |||
"actual": "2", | |||
"level": "ERROR" | |||
}, | |||
{ | |||
"metric": "new_reliability_rating", | |||
"op": "LT", | |||
"period": 1, | |||
"warning": "", | |||
"error": "1.0", | |||
"actual": "1", | |||
"level": "OK" | |||
}, | |||
{ | |||
"metric": "new_security_hotspots_reviewed", | |||
"op": "GT", | |||
"period": 1, | |||
"warning": "", | |||
"error": "100.0", | |||
"actual": "100.0", | |||
"level": "OK" | |||
}, | |||
{ | |||
"metric": "new_security_rating", | |||
"op": "LT", | |||
"period": 1, | |||
"warning": "", | |||
"error": "1.0", | |||
"actual": "2", | |||
"level": "ERROR" | |||
} | |||
] | |||
} |
@@ -0,0 +1,32 @@ | |||
{ | |||
"level": "ERROR", | |||
"conditions": [ | |||
{ | |||
"metric": "new_reliability_rating", | |||
"op": "LT", | |||
"period": 1, | |||
"warning": "", | |||
"error": "1.0", | |||
"actual": "1", | |||
"level": "OK" | |||
}, | |||
{ | |||
"metric": "new_security_hotspots_reviewed", | |||
"op": "GT", | |||
"period": 1, | |||
"warning": "", | |||
"error": "100.0", | |||
"actual": "100.0", | |||
"level": "OK" | |||
}, | |||
{ | |||
"metric": "new_security_rating", | |||
"op": "LT", | |||
"period": 1, | |||
"warning": "", | |||
"error": "1.0", | |||
"actual": "2", | |||
"level": "ERROR" | |||
} | |||
] | |||
} |
@@ -36,6 +36,7 @@ message ProjectStatusResponse { | |||
repeated Period periods = 3; | |||
optional bool ignoredConditions = 4; | |||
optional NewCodePeriod period = 5; | |||
optional bool isCaycCompliant = 6; | |||
} | |||
message Condition { |