From: Jean-Baptiste Lievremont Date: Thu, 24 Apr 2014 15:09:41 +0000 (+0200) Subject: SONAR-4927 Store quality gate details in dedicated measure X-Git-Tag: 4.4-RC1~1405 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=631797e48ccb77632bc5e6f4f2256561b1aa59f9;p=sonarqube.git SONAR-4927 Store quality gate details in dedicated measure --- 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 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 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 conditions = Lists.newArrayList(mockCondition(metric, QualityGateConditionDto.OPERATOR_LESS_THAN, "3600", null)); + when(context.getMeasure(metric)).thenReturn(new Measure(metric, 7200d)); + ArrayList 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 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 metrics = CoreMetrics.getMetrics(); - assertThat(metrics).hasSize(150); + assertThat(metrics).hasSize(151); assertThat(metrics).contains(CoreMetrics.NCLOC, CoreMetrics.DIRECTORIES); } }