aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/qualitygate/ConditionUtils.java16
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/qualitygate/QualityGateDetails.java93
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/qualitygate/QualityGateVerifier.java18
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/qualitygate/QualityGateVerifierTest.java35
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java6
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/resources/CoreMetricsTest.java2
6 files changed, 146 insertions, 24 deletions
diff --git a/sonar-batch/src/main/java/org/sonar/batch/qualitygate/ConditionUtils.java b/sonar-batch/src/main/java/org/sonar/batch/qualitygate/ConditionUtils.java
index 7515efc5b5a..59b3209dfef 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/qualitygate/ConditionUtils.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/qualitygate/ConditionUtils.java
@@ -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
index 00000000000..ca85bdc85d8
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/qualitygate/QualityGateDetails.java
@@ -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;
+ }
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/qualitygate/QualityGateVerifier.java b/sonar-batch/src/main/java/org/sonar/batch/qualitygate/QualityGateVerifier.java
index a0cc9b3e150..406ca87648c 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/qualitygate/QualityGateVerifier.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/qualitygate/QualityGateVerifier.java
@@ -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);
}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/qualitygate/QualityGateVerifierTest.java b/sonar-batch/src/test/java/org/sonar/batch/qualitygate/QualityGateVerifierTest.java
index f1671fe5ece..e79d27bc99d 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/qualitygate/QualityGateVerifierTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/qualitygate/QualityGateVerifierTest.java
@@ -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) {
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java
index b06dd3ad8c6..7ff4bb07bec 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java
@@ -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")
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/resources/CoreMetricsTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/resources/CoreMetricsTest.java
index fccbb8047c8..1c65ebfb51f 100644
--- a/sonar-plugin-api/src/test/java/org/sonar/api/resources/CoreMetricsTest.java
+++ b/sonar-plugin-api/src/test/java/org/sonar/api/resources/CoreMetricsTest.java
@@ -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);
}
}