]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-17816 Add isCaycCompliant flag to /api/qualitygates/project_status
authorJeremy Davis <jeremy.davis@sonarsource.com>
Thu, 29 Dec 2022 16:10:26 +0000 (17:10 +0100)
committersonartech <sonartech@sonarsource.com>
Mon, 2 Jan 2023 20:03:09 +0000 (20:03 +0000)
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatter.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatterTest.java
server/sonar-webserver-webapi/src/test/resources/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatterTest/cayc_compliant_qg.json [new file with mode: 0644]
server/sonar-webserver-webapi/src/test/resources/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatterTest/cayc_missing_metric.json [new file with mode: 0644]
sonar-ws/src/main/protobuf/ws-qualitygates.proto

index d95dd78c98c089ab402acc0ee5564921f8e09e54..fcfbb7391a473d3ea62239d6b614b6035fa8794d 100644 (file)
@@ -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());
     }
index 7df876bca08340309bfc9169295443641b9fb5d8..febd8db74bd5a1cba1ed50ea903e35b346725b9e 100644 (file)
@@ -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 (file)
index 0000000..32d4536
--- /dev/null
@@ -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 (file)
index 0000000..45ace94
--- /dev/null
@@ -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"
+    }
+  ]
+}
index e59873e098f6a49b3fdfc8bb525782fa785c3f04..8c6b36a99d4678adbc08dc721b02f79db33c8006 100644 (file)
@@ -36,6 +36,7 @@ message ProjectStatusResponse {
     repeated Period periods = 3;
     optional bool ignoredConditions = 4;
     optional NewCodePeriod period = 5;
+    optional bool isCaycCompliant = 6;
   }
 
   message Condition {