From b9ccb2eb4f0ef0c13d36a4aaa544104780018a85 Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Thu, 29 Dec 2022 17:10:26 +0100 Subject: [PATCH] SONAR-17816 Add isCaycCompliant flag to /api/qualitygates/project_status --- .../ws/QualityGateDetailsFormatter.java | 36 +++++++++++++++- .../ws/QualityGateDetailsFormatterTest.java | 26 ++++++++++++ .../cayc_compliant_qg.json | 41 +++++++++++++++++++ .../cayc_missing_metric.json | 32 +++++++++++++++ .../src/main/protobuf/ws-qualitygates.proto | 1 + 5 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 server/sonar-webserver-webapi/src/test/resources/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatterTest/cayc_compliant_qg.json create mode 100644 server/sonar-webserver-webapi/src/test/resources/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatterTest/cayc_missing_metric.json diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatter.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatter.java index d95dd78c98c..fcfbb7391a4 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatter.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatter.java @@ -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 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 optionalMeasureData; private final Optional 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()); } diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatterTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatterTest.java index 7df876bca08..febd8db74bd 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatterTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatterTest.java @@ -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)); } diff --git a/server/sonar-webserver-webapi/src/test/resources/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatterTest/cayc_compliant_qg.json b/server/sonar-webserver-webapi/src/test/resources/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatterTest/cayc_compliant_qg.json new file mode 100644 index 00000000000..32d45361fa7 --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/resources/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatterTest/cayc_compliant_qg.json @@ -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" + } + ] +} diff --git a/server/sonar-webserver-webapi/src/test/resources/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatterTest/cayc_missing_metric.json b/server/sonar-webserver-webapi/src/test/resources/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatterTest/cayc_missing_metric.json new file mode 100644 index 00000000000..45ace94cbc0 --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/resources/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatterTest/cayc_missing_metric.json @@ -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" + } + ] +} diff --git a/sonar-ws/src/main/protobuf/ws-qualitygates.proto b/sonar-ws/src/main/protobuf/ws-qualitygates.proto index e59873e098f..8c6b36a99d4 100644 --- a/sonar-ws/src/main/protobuf/ws-qualitygates.proto +++ b/sonar-ws/src/main/protobuf/ws-qualitygates.proto @@ -36,6 +36,7 @@ message ProjectStatusResponse { repeated Period periods = 3; optional bool ignoredConditions = 4; optional NewCodePeriod period = 5; + optional bool isCaycCompliant = 6; } message Condition { -- 2.39.5