]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-4927 Store quality gate details in dedicated measure
authorJean-Baptiste Lievremont <jean-baptiste.lievremont@sonarsource.com>
Thu, 24 Apr 2014 15:09:41 +0000 (17:09 +0200)
committerJean-Baptiste Lievremont <jean-baptiste.lievremont@sonarsource.com>
Thu, 24 Apr 2014 15:21:30 +0000 (17:21 +0200)
sonar-batch/src/main/java/org/sonar/batch/qualitygate/ConditionUtils.java
sonar-batch/src/main/java/org/sonar/batch/qualitygate/QualityGateDetails.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/qualitygate/QualityGateVerifier.java
sonar-batch/src/test/java/org/sonar/batch/qualitygate/QualityGateVerifierTest.java
sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java
sonar-plugin-api/src/test/java/org/sonar/api/resources/CoreMetricsTest.java

index 7515efc5b5a87ab78b49c729bd061add8822ba09..59b3209dfefedf5b7b9f88c9f96eb8cdd7b44be4 100644 (file)
@@ -178,22 +178,24 @@ class ConditionUtils {
     return metric.getType() == Metric.ValueType.WORK_DUR;
   }
 
-  private static Double getValue(ResolvedCondition condition, Measure measure) {
+  static Double getValue(ResolvedCondition condition, Measure measure) {
     Integer period = condition.period();
+    Double value;
     if (period == null) {
-      return measure.getValue();
+      value = measure.getValue();
     } else if (period == 1) {
-      return measure.getVariation1();
+      value = measure.getVariation1();
     } else if (period == 2) {
-      return measure.getVariation2();
+      value = measure.getVariation2();
     } else if (period == 3) {
-      return measure.getVariation3();
+      value = measure.getVariation3();
     } else if (period == 4) {
-      return measure.getVariation4();
+      value = measure.getVariation4();
     } else if (period == 5) {
-      return measure.getVariation5();
+      value = measure.getVariation5();
     } else {
       throw new IllegalStateException("Following index period is not allowed : " + Double.toString(period));
     }
+    return value;
   }
 }
diff --git a/sonar-batch/src/main/java/org/sonar/batch/qualitygate/QualityGateDetails.java b/sonar-batch/src/main/java/org/sonar/batch/qualitygate/QualityGateDetails.java
new file mode 100644 (file)
index 0000000..ca85bdc
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.batch.qualitygate;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.inject.internal.util.Lists;
+import org.sonar.api.measures.Metric.Level;
+
+import java.util.List;
+
+/**
+ * Holds the details of a quality gate evaluation (status per condition)
+ *
+ * @since 4.4
+ */
+class QualityGateDetails {
+
+  private static final String FIELD_LEVEL = "level";
+
+  private Level level = Level.OK;
+
+  private List<EvaluatedCondition> conditions = Lists.newArrayList();
+
+  void setLevel(Level level) {
+    this.level = level;
+  }
+
+  void addCondition(ResolvedCondition condition, Level level, Double actualValue) {
+    conditions.add(new EvaluatedCondition(condition, level, actualValue));
+  }
+
+  String toJson() {
+    JsonObject details = new JsonObject();
+    details.addProperty(FIELD_LEVEL, level.toString());
+    JsonArray conditionResults = new JsonArray();
+    for (EvaluatedCondition condition: this.conditions) {
+      conditionResults.add(condition.toJson());
+    }
+    details.add("conditions", conditionResults);
+    return details.toString();
+  }
+
+  static class EvaluatedCondition {
+
+    private ResolvedCondition condition;
+
+    private Level level;
+
+    private String actualValue;
+
+    EvaluatedCondition(ResolvedCondition condition, Level level, Double actualValue) {
+      this.condition = condition;
+      this.level = level;
+      this.actualValue = actualValue == null ? "" : actualValue.toString();
+    }
+
+    JsonObject toJson() {
+      JsonObject result = new JsonObject();
+      result.addProperty("metric", condition.metricKey());
+      result.addProperty("op", condition.operator());
+      if (condition.period() != null) {
+        result.addProperty("period", condition.period());
+      }
+      if (condition.warningThreshold() != null) {
+        result.addProperty("warning", condition.warningThreshold());
+      }
+      if (condition.errorThreshold() != null) {
+        result.addProperty("error", condition.errorThreshold());
+      }
+      result.addProperty("actual", actualValue);
+      result.addProperty(FIELD_LEVEL, level.toString());
+      return result;
+    }
+  }
+}
index a0cc9b3e1507ba42e686cd0bae7a91760cd57d49..406ca87648c90fdc12a3b39b096d32d5990fb9cb 100644 (file)
@@ -108,6 +108,7 @@ public class QualityGateVerifier implements Decorator {
 
   private void checkProjectConditions(Resource resource, DecoratorContext context) {
     Metric.Level globalLevel = Metric.Level.OK;
+    QualityGateDetails details = new QualityGateDetails();
     List<String> labels = Lists.newArrayList();
 
     for (ResolvedCondition condition : qualityGate.conditions()) {
@@ -115,9 +116,6 @@ public class QualityGateVerifier implements Decorator {
       if (measure != null) {
         Metric.Level level = ConditionUtils.getLevel(condition, measure);
 
-        /*
-         * This should probably be done only after migration from alerts
-         */
         measure.setAlertStatus(level);
         String text = getText(condition, level);
         if (!StringUtils.isBlank(text)) {
@@ -133,6 +131,8 @@ public class QualityGateVerifier implements Decorator {
         } else if (Metric.Level.ERROR == level) {
           globalLevel = Metric.Level.ERROR;
         }
+
+        details.addCondition(condition, level, ConditionUtils.getValue(condition, measure));
       }
     }
 
@@ -140,6 +140,12 @@ public class QualityGateVerifier implements Decorator {
     globalMeasure.setAlertStatus(globalLevel);
     globalMeasure.setAlertText(StringUtils.join(labels, ", "));
     context.saveMeasure(globalMeasure);
+
+    details.setLevel(globalLevel);
+    Measure detailsMeasure = new Measure(CoreMetrics.QUALITY_GATE_DETAILS, details.toJson());
+    detailsMeasure.setAlertStatus(globalLevel);
+    context.saveMeasure(detailsMeasure);
+
   }
 
   private String getText(ResolvedCondition condition, Metric.Level level) {
@@ -175,12 +181,16 @@ public class QualityGateVerifier implements Decorator {
   private String alertValue(ResolvedCondition condition, Metric.Level level) {
     String value = level.equals(Metric.Level.ERROR) ? condition.errorThreshold() : condition.warningThreshold();
     if (condition.metric().getType().equals(Metric.ValueType.WORK_DUR)) {
-      return durations.format(Locale.ENGLISH, Duration.create(Long.parseLong(value)), Durations.DurationFormat.SHORT);
+      return formatDuration(value);
     } else {
       return value;
     }
   }
 
+  private String formatDuration(String value) {
+    return durations.format(Locale.ENGLISH, Duration.create(Long.parseLong(value)), Durations.DurationFormat.SHORT);
+  }
+
   private String operatorLabel(String operator) {
     return OPERATOR_LABELS.get(operator);
   }
index f1671fe5ecede5daf9580c01972ec2f52eac8c90..e79d27bc99d4bc5654503f07c8d1a07a58a2a52e 100644 (file)
@@ -19,6 +19,8 @@
  */
 package org.sonar.batch.qualitygate;
 
+import org.sonar.api.measures.Metric.Level;
+
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import org.apache.commons.lang.NotImplementedException;
@@ -46,14 +48,8 @@ import java.util.ArrayList;
 import java.util.Locale;
 
 import static org.fest.assertions.Assertions.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.mockito.Matchers.*;
+import static org.mockito.Mockito.*;
 
 public class QualityGateVerifierTest {
 
@@ -132,9 +128,16 @@ public class QualityGateVerifierTest {
 
     verifier.decorate(project, context);
 
-    verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.ALERT_STATUS, Metric.Level.OK.toString())));
     verify(index).updateMeasure(eq(project), argThat(hasLevel(measureClasses, Metric.Level.OK)));
     verify(index).updateMeasure(eq(project), argThat(hasLevel(measureCoverage, Metric.Level.OK)));
+    verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.ALERT_STATUS, Metric.Level.OK.toString())));
+    verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.QUALITY_GATE_DETAILS, "{\"level\":\"OK\","
+      + "\"conditions\":"
+      + "["
+      + "{\"metric\":\"classes\",\"op\":\"GT\",\"warning\":\"20\",\"actual\":\"20.0\",\"level\":\"OK\"},"
+      + "{\"metric\":\"coverage\",\"op\":\"GT\",\"warning\":\"35.0\",\"actual\":\"35.0\",\"level\":\"OK\"}"
+      + "]"
+      + "}")));
   }
 
   @Test
@@ -394,12 +397,20 @@ public class QualityGateVerifierTest {
     when(i18n.message(any(Locale.class), eq("metric.tech_debt.name"), anyString())).thenReturn("The Debt");
     when(durations.format(any(Locale.class), eq(Duration.create(3600L)), eq(Durations.DurationFormat.SHORT))).thenReturn("1h");
 
-    when(context.getMeasure(metric)).thenReturn(new Measure(metric, 1800d));
-    ArrayList<ResolvedCondition> conditions = Lists.newArrayList(mockCondition(metric, QualityGateConditionDto.OPERATOR_LESS_THAN, "3600", null));
+    when(context.getMeasure(metric)).thenReturn(new Measure(metric, 7200d));
+    ArrayList<ResolvedCondition> conditions = Lists.newArrayList(mockCondition(metric, QualityGateConditionDto.OPERATOR_GREATER_THAN, "3600", null));
     when(qualityGate.conditions()).thenReturn(conditions);
     verifier.decorate(project, context);
 
-    verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.ERROR, "The Debt < 1h")));
+    // First call to saveMeasure is for the update of debt
+    verify(index).updateMeasure(eq(project), argThat(matchesMetric(metric, Level.ERROR, "The Debt > 1h")));
+    verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.ERROR, "The Debt > 1h")));
+    verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.QUALITY_GATE_DETAILS, "{\"level\":\"ERROR\","
+        + "\"conditions\":"
+        + "["
+        + "{\"metric\":\"tech_debt\",\"op\":\"GT\",\"error\":\"3600\",\"actual\":\"7200.0\",\"level\":\"ERROR\"}"
+        + "]"
+        + "}")));
   }
 
   private ArgumentMatcher<Measure> matchesMetric(final Metric metric, final Metric.Level alertStatus, final String alertText) {
index b06dd3ad8c6eb8692020bdd2bf5ee653f1cb5559..7ff4bb07bec34eab1af4264596b732faa62c2e33 100644 (file)
@@ -2201,6 +2201,12 @@ public final class CoreMetrics {
     .setDomain(DOMAIN_GENERAL)
     .create();
 
+  public static final String QUALITY_GATE_DETAILS_KEY = "quality_gate_details";
+  public static final Metric QUALITY_GATE_DETAILS = new Metric.Builder(QUALITY_GATE_DETAILS_KEY, "Quality Gate Details", Metric.ValueType.DATA)
+    .setDescription("The project detailed status with regard to its quality gate.")
+    .setDomain(DOMAIN_GENERAL)
+    .create();
+
   public static final String PROFILE_KEY = "profile";
   public static final Metric PROFILE = new Metric.Builder(PROFILE_KEY, "Profile", Metric.ValueType.DATA)
     .setDescription("Selected quality profile")
index fccbb8047c8c4e0e732c2aa7f1a1e36eeb92ca0b..1c65ebfb51f92ef9863ed61e3a06c0a0aa5c2d75 100644 (file)
@@ -31,7 +31,7 @@ public class CoreMetricsTest {
   @Test
   public void shouldReadMetricsFromClassReflection() {
     List<Metric> metrics = CoreMetrics.getMetrics();
-    assertThat(metrics).hasSize(150);
+    assertThat(metrics).hasSize(151);
     assertThat(metrics).contains(CoreMetrics.NCLOC, CoreMetrics.DIRECTORIES);
   }
 }