aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java2
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/AlertUtils.java85
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/CheckAlertThresholds.java24
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/Periods.java86
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/TimeMachineConfigurationPersister.java21
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/CheckAlertThresholdsTest.java123
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/PeriodsTest.java132
-rw-r--r--sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java2
-rw-r--r--sonar-core/src/main/resources/org/sonar/core/persistence/rows-h2.sql1
-rw-r--r--sonar-core/src/main/resources/org/sonar/core/persistence/schema-h2.ddl3
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/profiles/Alert.java40
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/db/migrate/359_add_period_to_alerts.rb31
-rw-r--r--sonar-server/src/test/resources/org/sonar/server/startup/RegisterMetricsTest/cleanAlerts-result.xml8
-rw-r--r--sonar-server/src/test/resources/org/sonar/server/startup/RegisterMetricsTest/cleanAlerts.xml8
14 files changed, 505 insertions, 61 deletions
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
index 0592005e12e..3fd67c43ea9 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
@@ -79,6 +79,7 @@ import org.sonar.plugins.core.timemachine.NewCoverageFileAnalyzer;
import org.sonar.plugins.core.timemachine.NewItCoverageFileAnalyzer;
import org.sonar.plugins.core.timemachine.NewOverallCoverageFileAnalyzer;
import org.sonar.plugins.core.timemachine.NewViolationsDecorator;
+import org.sonar.plugins.core.timemachine.Periods;
import org.sonar.plugins.core.timemachine.ReferenceAnalysis;
import org.sonar.plugins.core.timemachine.TendencyDecorator;
import org.sonar.plugins.core.timemachine.TimeMachineConfigurationPersister;
@@ -395,6 +396,7 @@ public final class CorePlugin extends SonarPlugin {
UserManagedMetrics.class,
ProjectFileSystemLogger.class,
DatabaseSemaphoreImpl.class,
+ Periods.class,
// maven
MavenInitializer.class,
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/AlertUtils.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/AlertUtils.java
index 9fe246f5fdf..cec2e22be17 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/AlertUtils.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/AlertUtils.java
@@ -26,6 +26,7 @@ import org.sonar.api.measures.Metric;
import org.sonar.api.profiles.Alert;
public final class AlertUtils {
+
private AlertUtils() {
}
@@ -43,73 +44,87 @@ public final class AlertUtils {
}
private static boolean evaluateAlert(Alert alert, Measure measure, Metric.Level alertLevel) {
- String valueToEval;
- if (alertLevel.equals(Metric.Level.ERROR)) {
- valueToEval = alert.getValueError();
-
- } else if (alertLevel.equals(Metric.Level.WARN)) {
- valueToEval = alert.getValueWarning();
-
- } else {
- throw new IllegalStateException(alertLevel.toString());
- }
-
+ String valueToEval = getValueToEval(alert, alertLevel);
if (StringUtils.isEmpty(valueToEval)) {
return false;
}
Comparable criteriaValue = getValueForComparison(alert.getMetric(), valueToEval);
- Comparable metricValue = getMeasureValue(alert.getMetric(), measure);
+ Comparable metricValue = getMeasureValue(alert, measure);
int comparison = metricValue.compareTo(criteriaValue);
return !(// NOSONAR complexity of this boolean expression is under control
- (alert.isNotEqualsOperator() && comparison == 0)
- || (alert.isGreaterOperator() && comparison != 1)
- || (alert.isSmallerOperator() && comparison != -1)
- || (alert.isEqualsOperator() && comparison != 0));
+ (alert.isNotEqualsOperator() && comparison == 0)
+ || (alert.isGreaterOperator() && comparison != 1)
+ || (alert.isSmallerOperator() && comparison != -1)
+ || (alert.isEqualsOperator() && comparison != 0));
+ }
+
+ private static String getValueToEval(Alert alert, Metric.Level alertLevel) {
+ if (alertLevel.equals(Metric.Level.ERROR)) {
+ return alert.getValueError();
+ } else if (alertLevel.equals(Metric.Level.WARN)) {
+ return alert.getValueWarning();
+ } else {
+ throw new IllegalStateException(alertLevel.toString());
+ }
}
private static Comparable<?> getValueForComparison(Metric metric, String value) {
if (metric.getType() == Metric.ValueType.FLOAT ||
- metric.getType() == Metric.ValueType.PERCENT) {
+ metric.getType() == Metric.ValueType.PERCENT ||
+ metric.getType() == Metric.ValueType.RATING
+ ) {
return Double.parseDouble(value);
}
if (metric.getType() == Metric.ValueType.INT ||
- metric.getType() == Metric.ValueType.MILLISEC) {
+ metric.getType() == Metric.ValueType.MILLISEC) {
return value.contains(".") ? Integer.parseInt(value.substring(0, value.indexOf('.'))) : Integer.parseInt(value);
}
if (metric.getType() == Metric.ValueType.STRING ||
- metric.getType() == Metric.ValueType.LEVEL) {
+ metric.getType() == Metric.ValueType.LEVEL) {
return value;
}
if (metric.getType() == Metric.ValueType.BOOL) {
return Integer.parseInt(value);
}
- if (metric.getType() == Metric.ValueType.RATING) {
- return Double.parseDouble(value);
- }
throw new NotImplementedException(metric.getType().toString());
}
- private static Comparable<?> getMeasureValue(Metric metric, Measure measure) {
+ private static Comparable<?> getMeasureValue(Alert alert, Measure measure) {
+ Metric metric = alert.getMetric();
if (metric.getType() == Metric.ValueType.FLOAT ||
- metric.getType() == Metric.ValueType.PERCENT) {
- return measure.getValue();
+ metric.getType() == Metric.ValueType.PERCENT ||
+ metric.getType() == Metric.ValueType.RATING) {
+ return getValue(alert, measure);
}
if (metric.getType() == Metric.ValueType.INT ||
- metric.getType() == Metric.ValueType.MILLISEC) {
- return measure.getValue().intValue();
+ metric.getType() == Metric.ValueType.MILLISEC) {
+ return getValue(alert, measure).intValue();
}
- if (metric.getType() == Metric.ValueType.STRING ||
- metric.getType() == Metric.ValueType.LEVEL) {
- return measure.getData();
+ if (alert.getPeriod() == null) {
+ if (metric.getType() == Metric.ValueType.STRING ||
+ metric.getType() == Metric.ValueType.LEVEL) {
+ return measure.getData();
+ }
+ if (metric.getType() == Metric.ValueType.BOOL) {
+ return measure.getValue().intValue();
+ }
}
- if (metric.getType() == Metric.ValueType.BOOL) {
- return measure.getValue().intValue();
- }
- if (metric.getType() == Metric.ValueType.RATING) {
+ throw new NotImplementedException(metric.getType().toString());
+ }
+
+ private static Double getValue(Alert alert, Measure measure) {
+ if (alert.getPeriod() == null) {
return measure.getValue();
+ } else if (alert.getPeriod() == 1) {
+ return measure.getVariation1();
+ } else if (alert.getPeriod() == 2) {
+ return measure.getVariation2();
+ } else if (alert.getPeriod() == 3) {
+ return measure.getVariation3();
+ } else {
+ throw new IllegalStateException("Following index period is not allowed : " + Double.toString(alert.getPeriod()));
}
- throw new NotImplementedException(metric.getType().toString());
}
}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/CheckAlertThresholds.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/CheckAlertThresholds.java
index 3b2e329755e..d8a3837ae28 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/CheckAlertThresholds.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/CheckAlertThresholds.java
@@ -21,7 +21,11 @@ package org.sonar.plugins.core.sensors;
import com.google.common.collect.Lists;
import org.apache.commons.lang.StringUtils;
-import org.sonar.api.batch.*;
+import org.sonar.api.batch.Decorator;
+import org.sonar.api.batch.DecoratorBarriers;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependedUpon;
+import org.sonar.api.batch.DependsUpon;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.Measure;
import org.sonar.api.measures.Metric;
@@ -30,15 +34,19 @@ import org.sonar.api.profiles.RulesProfile;
import org.sonar.api.resources.Project;
import org.sonar.api.resources.Resource;
import org.sonar.api.resources.ResourceUtils;
+import org.sonar.plugins.core.timemachine.Periods;
import java.util.List;
public class CheckAlertThresholds implements Decorator {
private final RulesProfile profile;
+ private final Periods periods;
- public CheckAlertThresholds(RulesProfile profile) {
+
+ public CheckAlertThresholds(RulesProfile profile, Periods periods) {
this.profile = profile;
+ this.periods = periods;
}
@DependedUpon
@@ -47,6 +55,11 @@ public class CheckAlertThresholds implements Decorator {
}
@DependsUpon
+ public String dependsOnVariations() {
+ return DecoratorBarriers.END_OF_TIME_MACHINE;
+ }
+
+ @DependsUpon
public List<Metric> dependsUponMetrics() {
List<Metric> metrics = Lists.newLinkedList();
for (Alert alert : profile.getAlerts()) {
@@ -110,7 +123,12 @@ public class CheckAlertThresholds implements Decorator {
if (level == Metric.Level.OK) {
return null;
}
- return alert.getAlertLabel(level);
+ String alertLabel = alert.getAlertLabel(level);
+ Integer alertPeriod = alert.getPeriod();
+ if (alertPeriod != null){
+ alertLabel += " " + periods.getLabel(alertPeriod);
+ }
+ return alertLabel;
}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/Periods.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/Periods.java
new file mode 100644
index 00000000000..c8424b62163
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/Periods.java
@@ -0,0 +1,86 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.core.timemachine;
+
+import org.sonar.api.BatchExtension;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.i18n.I18n;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+public class Periods implements BatchExtension {
+
+ private final Snapshot snapshot;
+ private final I18n i18n;
+
+ public Periods(Snapshot snapshot, I18n i18n) {
+ this.snapshot = snapshot;
+ this.i18n = i18n;
+ }
+
+ public String getLabel(int periodIndex) {
+ String mode = snapshot.getPeriodMode(periodIndex);
+ String param = snapshot.getPeriodModeParameter(periodIndex);
+ Date date = snapshot.getPeriodDate(periodIndex);
+
+ if (mode.equals(CoreProperties.TIMEMACHINE_MODE_DAYS)) {
+ return message("over_x_days", param);
+ } else if (mode.equals(CoreProperties.TIMEMACHINE_MODE_VERSION)) {
+ if (date != null) {
+ return message("since_version_detailed", param, convertDate(date));
+ } else {
+ return message("since_version", param);
+ }
+ } else if (mode.equals(CoreProperties.TIMEMACHINE_MODE_PREVIOUS_ANALYSIS)) {
+ if (date != null) {
+ return message("since_previous_analysis_detailed", convertDate(date));
+ } else {
+ return message("since_previous_analysis");
+ }
+ } else if (mode.equals(CoreProperties.TIMEMACHINE_MODE_PREVIOUS_VERSION)) {
+ if (param != null) {
+ return message("since_previous_version_detailed", param);
+ } else {
+ return message("since_previous_version");
+ }
+ } else if (mode.equals(CoreProperties.TIMEMACHINE_MODE_DATE)) {
+ return message("since_x", convertDate(date));
+ } else {
+ throw new IllegalStateException("This mode is not supported : " + mode);
+ }
+ }
+
+ private String message(String key, Object... parameters) {
+ return i18n.message(getLocale(), key, null, parameters);
+ }
+
+ private String convertDate(Date date){
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy MMM dd");
+ return dateFormat.format(date);
+ }
+
+ private Locale getLocale() {
+ return Locale.ENGLISH;
+ }
+
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/TimeMachineConfigurationPersister.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/TimeMachineConfigurationPersister.java
index 54165af632f..355feb19f5a 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/TimeMachineConfigurationPersister.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/TimeMachineConfigurationPersister.java
@@ -20,8 +20,9 @@
package org.sonar.plugins.core.timemachine;
import org.sonar.api.batch.Decorator;
+import org.sonar.api.batch.DecoratorBarriers;
import org.sonar.api.batch.DecoratorContext;
-import org.sonar.core.DryRunIncompatible;
+import org.sonar.api.batch.DependedUpon;
import org.sonar.api.database.DatabaseSession;
import org.sonar.api.database.model.Snapshot;
import org.sonar.api.resources.Project;
@@ -29,10 +30,12 @@ import org.sonar.api.resources.Resource;
import org.sonar.api.resources.ResourceUtils;
import org.sonar.batch.components.PastSnapshot;
import org.sonar.batch.components.TimeMachineConfiguration;
+import org.sonar.core.DryRunIncompatible;
import java.util.List;
@DryRunIncompatible
+@DependedUpon(DecoratorBarriers.END_OF_TIME_MACHINE)
public final class TimeMachineConfigurationPersister implements Decorator {
private TimeMachineConfiguration configuration;
@@ -54,11 +57,10 @@ public final class TimeMachineConfigurationPersister implements Decorator {
void persistConfiguration() {
List<PastSnapshot> pastSnapshots = configuration.getProjectPastSnapshots();
for (PastSnapshot pastSnapshot : pastSnapshots) {
- projectSnapshot = session.reattach(Snapshot.class, projectSnapshot.getId());
- projectSnapshot.setPeriodMode(pastSnapshot.getIndex(), pastSnapshot.getMode());
- projectSnapshot.setPeriodModeParameter(pastSnapshot.getIndex(), pastSnapshot.getModeParameter());
- projectSnapshot.setPeriodDate(pastSnapshot.getIndex(), pastSnapshot.getTargetDate());
- session.save(projectSnapshot);
+ Snapshot snapshot = session.reattach(Snapshot.class, projectSnapshot.getId());
+ updatePeriodParams(snapshot, pastSnapshot);
+ updatePeriodParams(projectSnapshot, pastSnapshot);
+ session.save(snapshot);
}
session.commit();
}
@@ -66,4 +68,11 @@ public final class TimeMachineConfigurationPersister implements Decorator {
public boolean shouldExecuteOnProject(Project project) {
return true;
}
+
+ private void updatePeriodParams(Snapshot snapshot, PastSnapshot pastSnapshot) {
+ int periodIndex = pastSnapshot.getIndex();
+ snapshot.setPeriodMode(periodIndex, pastSnapshot.getMode());
+ snapshot.setPeriodModeParameter(periodIndex, pastSnapshot.getModeParameter());
+ snapshot.setPeriodDate(periodIndex, pastSnapshot.getTargetDate());
+ }
}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/CheckAlertThresholdsTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/CheckAlertThresholdsTest.java
index 1843350e396..87b4590b626 100644
--- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/CheckAlertThresholdsTest.java
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/CheckAlertThresholdsTest.java
@@ -19,6 +19,7 @@
*/
package org.sonar.plugins.core.sensors;
+import org.apache.commons.lang.NotImplementedException;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentMatcher;
@@ -29,39 +30,50 @@ import org.sonar.api.measures.Metric;
import org.sonar.api.profiles.Alert;
import org.sonar.api.profiles.RulesProfile;
import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Qualifiers;
import org.sonar.api.resources.Resource;
import org.sonar.api.test.IsMeasure;
+import org.sonar.plugins.core.timemachine.Periods;
import java.util.ArrayList;
import java.util.Arrays;
import static org.junit.Assert.assertFalse;
import static org.mockito.Matchers.argThat;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
public class CheckAlertThresholdsTest {
+
private CheckAlertThresholds decorator;
private DecoratorContext context;
private RulesProfile profile;
private Measure measureClasses;
private Measure measureCoverage;
+ private Measure measureComplexity;
private Resource project;
-
+ private Periods periods;
@Before
public void setup() {
context = mock(DecoratorContext.class);
+ periods = mock(Periods.class);
measureClasses = new Measure(CoreMetrics.CLASSES, 20d);
measureCoverage = new Measure(CoreMetrics.COVERAGE, 35d);
+ measureComplexity = new Measure(CoreMetrics.COMPLEXITY, 50d);
when(context.getMeasure(CoreMetrics.CLASSES)).thenReturn(measureClasses);
when(context.getMeasure(CoreMetrics.COVERAGE)).thenReturn(measureCoverage);
+ when(context.getMeasure(CoreMetrics.COMPLEXITY)).thenReturn(measureComplexity);
profile = mock(RulesProfile.class);
- decorator = new CheckAlertThresholds(profile);
+ decorator = new CheckAlertThresholds(profile, periods);
project = mock(Resource.class);
- when(project.getQualifier()).thenReturn(Resource.QUALIFIER_PROJECT);
+ when(project.getQualifier()).thenReturn(Qualifiers.PROJECT);
}
@Test
@@ -71,7 +83,7 @@ public class CheckAlertThresholdsTest {
}
@Test
- public void shouldBeOkWhenNoAlerts() {
+ public void shouldBeOkWhenNoAlert() {
when(profile.getAlerts()).thenReturn(Arrays.asList(
new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "20"),
new Alert(null, CoreMetrics.COVERAGE, Alert.OPERATOR_GREATER, null, "35.0")));
@@ -130,11 +142,13 @@ public class CheckAlertThresholdsTest {
when(alert1.getMetric()).thenReturn(CoreMetrics.CLASSES);
when(alert1.getValueError()).thenReturn("10000"); // there are 20 classes, error threshold is higher => alert
when(alert1.getAlertLabel(Metric.Level.ERROR)).thenReturn("error classes");
+ when(alert1.getPeriod()).thenReturn(null);
Alert alert2 = mock(Alert.class);
when(alert2.getMetric()).thenReturn(CoreMetrics.COVERAGE);
when(alert2.getValueWarning()).thenReturn("80"); // coverage is 35%, warning threshold is higher => alert
when(alert2.getAlertLabel(Metric.Level.WARN)).thenReturn("warning coverage");
+ when(alert2.getPeriod()).thenReturn(null);
when(profile.getAlerts()).thenReturn(Arrays.asList(alert1, alert2));
decorator.decorate(project, context);
@@ -142,6 +156,105 @@ public class CheckAlertThresholdsTest {
verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.ERROR, "error classes, warning coverage")));
}
+ @Test
+ public void shouldBeOkIfPeriodVariationIsEnough() {
+ measureClasses.setVariation1(0d);
+ measureCoverage.setVariation2(50d);
+ measureComplexity.setVariation3(2d);
+
+ when(profile.getAlerts()).thenReturn(Arrays.asList(
+ new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "10", 1), // ok because no variation
+ new Alert(null, CoreMetrics.COVERAGE, Alert.OPERATOR_SMALLER, null, "40.0", 2), // ok because coverage increases of 50%, which is more than 40%
+ new Alert(null, CoreMetrics.COMPLEXITY, Alert.OPERATOR_GREATER, null, "5", 3) // ok because complexity increases of 2, which is less than 5
+ ));
+
+ decorator.decorate(project, context);
+
+ verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.OK, null)));
+
+ verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.OK)));
+ verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.OK)));
+ verify(context).saveMeasure(argThat(hasLevel(measureComplexity, Metric.Level.OK)));
+ }
+
+ @Test
+ public void shouldGenerateWarningIfPeriodVariationIsNotEnough() {
+ measureClasses.setVariation1(40d);
+ measureCoverage.setVariation2(5d);
+ measureComplexity.setVariation3(70d);
+
+ when(profile.getAlerts()).thenReturn(Arrays.asList(
+ new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "30", 1), // generates warning because classes increases of 40, which is greater than 30
+ new Alert(null, CoreMetrics.COVERAGE, Alert.OPERATOR_SMALLER, null, "10.0", 2), // generates warning because coverage increases of 5%, which is smaller than 10%
+ new Alert(null, CoreMetrics.COMPLEXITY, Alert.OPERATOR_GREATER, null, "60", 3) // generates warning because complexity increases of 70, which is smaller than 60
+ ));
+
+ decorator.decorate(project, context);
+
+ verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.WARN, null)));
+
+ verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.WARN)));
+ verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.WARN)));
+ verify(context).saveMeasure(argThat(hasLevel(measureComplexity, Metric.Level.WARN)));
+ }
+
+ @Test
+ public void shouldVariationPeriodValueCouldBeUsedForRatingMetric() {
+ Metric ratingMetric = new Metric.Builder("key.rating", "name.rating", Metric.ValueType.RATING).create();
+ Measure measureRatingMetric = new Measure(ratingMetric, 150d);
+ measureRatingMetric.setVariation1(50d);
+ when(context.getMeasure(ratingMetric)).thenReturn(measureRatingMetric);
+
+ when(profile.getAlerts()).thenReturn(Arrays.asList(
+ new Alert(null, ratingMetric, Alert.OPERATOR_GREATER, null, "100", 1)
+ ));
+
+ decorator.decorate(project, context);
+
+ verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.OK, null)));
+ verify(context).saveMeasure(argThat(hasLevel(measureRatingMetric, Metric.Level.OK)));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void shouldAllowOnlyVariationPeriodOneGlobalPeriods() {
+ measureClasses.setVariation4(40d);
+
+ when(profile.getAlerts()).thenReturn(Arrays.asList(
+ new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "30", 4)
+ ));
+
+ decorator.decorate(project, context);
+ }
+
+ @Test(expected = NotImplementedException.class)
+ public void shouldNotAllowPeriodVariationAlertOnStringMetric() {
+ Measure measure = new Measure(CoreMetrics.SCM_AUTHORS_BY_LINE, 100d);
+ measure.setVariation1(50d);
+ when(context.getMeasure(CoreMetrics.SCM_AUTHORS_BY_LINE)).thenReturn(measure);
+
+ when(profile.getAlerts()).thenReturn(Arrays.asList(
+ new Alert(null, CoreMetrics.SCM_AUTHORS_BY_LINE, Alert.OPERATOR_GREATER, null, "30", 1)
+ ));
+
+ decorator.decorate(project, context);
+ }
+
+ @Test
+ public void shouldLabelAlertContainsPeriod() {
+ measureClasses.setVariation1(40d);
+
+ Alert alert = mock(Alert.class);
+ when(alert.getMetric()).thenReturn(CoreMetrics.CLASSES);
+ when(alert.getValueError()).thenReturn("30");
+ when(alert.getAlertLabel(Metric.Level.ERROR)).thenReturn("error classes");
+ when(alert.getPeriod()).thenReturn(1);
+
+ when(profile.getAlerts()).thenReturn(Arrays.asList(alert));
+ decorator.decorate(project, context);
+
+ verify(periods).getLabel(1);
+ }
+
private ArgumentMatcher<Measure> matchesMetric(final Metric metric, final Metric.Level alertStatus, final String alertText) {
return new ArgumentMatcher<Measure>() {
@Override
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/PeriodsTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/PeriodsTest.java
new file mode 100644
index 00000000000..da6aa2c623d
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/PeriodsTest.java
@@ -0,0 +1,132 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.core.timemachine;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.i18n.I18n;
+
+import java.util.Date;
+import java.util.Locale;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class PeriodsTest {
+
+ private Periods periods;
+
+ private Snapshot snapshot;
+ private I18n i18n;
+
+ private int periodIndex;
+ private String param;
+
+ @Before
+ public void before() {
+ periodIndex = 1;
+ param = "10";
+
+ snapshot = mock(Snapshot.class);
+ i18n = mock(I18n.class);
+
+ when(snapshot.getPeriodModeParameter(periodIndex)).thenReturn(param);
+
+ periods = new Periods(snapshot, i18n);
+ }
+
+ @Test
+ public void shouldReturnLabelInModeDays() {
+ when(snapshot.getPeriodMode(periodIndex)).thenReturn(CoreProperties.TIMEMACHINE_MODE_DAYS);
+ when(snapshot.getPeriodDate(periodIndex)).thenReturn(new Date());
+ when(snapshot.getPeriodModeParameter(periodIndex)).thenReturn(param);
+
+ periods.getLabel(periodIndex);
+ verify(i18n).message(Mockito.any(Locale.class), Mockito.eq("over_x_days"), Mockito.isNull(String.class), Mockito.eq(param));
+ }
+
+ @Test
+ public void shouldReturnLabelInModeVersion() {
+ when(snapshot.getPeriodMode(periodIndex)).thenReturn(CoreProperties.TIMEMACHINE_MODE_VERSION);
+ when(snapshot.getPeriodDate(periodIndex)).thenReturn(new Date());
+ when(snapshot.getPeriodModeParameter(periodIndex)).thenReturn(param);
+
+ periods.getLabel(periodIndex);
+
+ verify(i18n).message(Mockito.any(Locale.class), Mockito.eq("since_version_detailed"), Mockito.isNull(String.class), Mockito.eq(param), Mockito.anyString());
+ }
+
+ @Test
+ public void shouldReturnLabelInModePreviousAnalysis() {
+ when(snapshot.getPeriodMode(periodIndex)).thenReturn(CoreProperties.TIMEMACHINE_MODE_VERSION);
+ when(snapshot.getPeriodDate(periodIndex)).thenReturn(new Date());
+ when(snapshot.getPeriodModeParameter(periodIndex)).thenReturn(param);
+
+ periods.getLabel(periodIndex);
+
+ verify(i18n).message(Mockito.any(Locale.class), Mockito.eq("since_version_detailed"), Mockito.isNull(String.class), Mockito.eq(param), Mockito.anyString());
+
+ when(snapshot.getPeriodMode(periodIndex)).thenReturn(CoreProperties.TIMEMACHINE_MODE_VERSION);
+ when(snapshot.getPeriodDate(periodIndex)).thenReturn(null);
+ when(snapshot.getPeriodModeParameter(periodIndex)).thenReturn(param);
+
+ periods.getLabel(periodIndex);
+
+ verify(i18n).message(Mockito.any(Locale.class), Mockito.eq("since_version"), Mockito.isNull(String.class), Mockito.eq(param));
+ }
+
+ @Test
+ public void shouldReturnLabelInModePreviousVersion() {
+ when(snapshot.getPeriodMode(periodIndex)).thenReturn(CoreProperties.TIMEMACHINE_MODE_PREVIOUS_VERSION);
+ when(snapshot.getPeriodModeParameter(periodIndex)).thenReturn(param);
+
+ periods.getLabel(periodIndex);
+
+ verify(i18n).message(Mockito.any(Locale.class), Mockito.eq("since_previous_version_detailed"), Mockito.isNull(String.class), Mockito.eq(param));
+
+ when(snapshot.getPeriodMode(periodIndex)).thenReturn(CoreProperties.TIMEMACHINE_MODE_PREVIOUS_VERSION);
+ when(snapshot.getPeriodModeParameter(periodIndex)).thenReturn(null);
+
+ periods.getLabel(periodIndex);
+
+ verify(i18n).message(Mockito.any(Locale.class), Mockito.eq("since_previous_version"), Mockito.isNull(String.class));
+ }
+
+ @Test
+ public void shouldReturnLabelInModeDate() {
+ when(snapshot.getPeriodMode(periodIndex)).thenReturn(CoreProperties.TIMEMACHINE_MODE_DATE);
+ when(snapshot.getPeriodDate(periodIndex)).thenReturn(new Date());
+
+ periods.getLabel(periodIndex);
+
+ verify(i18n).message(Mockito.any(Locale.class), Mockito.eq("since_x"), Mockito.isNull(String.class), Mockito.anyString());
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void shouldNotSupportUnknownMode() {
+ when(snapshot.getPeriodMode(periodIndex)).thenReturn("Unknown mode");
+
+ periods.getLabel(periodIndex);
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java b/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java
index 02c2f840137..f2d34dcd089 100644
--- a/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java
+++ b/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java
@@ -32,7 +32,7 @@ import java.util.List;
*/
public class DatabaseVersion implements BatchComponent, ServerComponent {
- public static final int LAST_VERSION = 358;
+ public static final int LAST_VERSION = 359;
public static enum Status {
UP_TO_DATE, REQUIRES_UPGRADE, REQUIRES_DOWNGRADE, FRESH_INSTALL
diff --git a/sonar-core/src/main/resources/org/sonar/core/persistence/rows-h2.sql b/sonar-core/src/main/resources/org/sonar/core/persistence/rows-h2.sql
index a46a8fdb1b9..aba45d45a1b 100644
--- a/sonar-core/src/main/resources/org/sonar/core/persistence/rows-h2.sql
+++ b/sonar-core/src/main/resources/org/sonar/core/persistence/rows-h2.sql
@@ -186,6 +186,7 @@ INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('355');
INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('356');
INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('357');
INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('358');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('359');
INSERT INTO USERS(ID, LOGIN, NAME, EMAIL, CRYPTED_PASSWORD, SALT, CREATED_AT, UPDATED_AT, REMEMBER_TOKEN, REMEMBER_TOKEN_EXPIRES_AT) VALUES (1, 'admin', 'Administrator', '', 'a373a0e667abb2604c1fd571eb4ad47fe8cc0878', '48bc4b0d93179b5103fd3885ea9119498e9d161b', '2011-09-26 22:27:48.0', '2011-09-26 22:27:48.0', null, null);
ALTER TABLE USERS ALTER COLUMN ID RESTART WITH 2;
diff --git a/sonar-core/src/main/resources/org/sonar/core/persistence/schema-h2.ddl b/sonar-core/src/main/resources/org/sonar/core/persistence/schema-h2.ddl
index ea4589cf455..8ad5551c6d3 100644
--- a/sonar-core/src/main/resources/org/sonar/core/persistence/schema-h2.ddl
+++ b/sonar-core/src/main/resources/org/sonar/core/persistence/schema-h2.ddl
@@ -201,7 +201,8 @@ CREATE TABLE "ALERTS" (
"METRIC_ID" INTEGER,
"OPERATOR" VARCHAR(3),
"VALUE_ERROR" VARCHAR(64),
- "VALUE_WARNING" VARCHAR(64)
+ "VALUE_WARNING" VARCHAR(64),
+ "PERIOD" INTEGER,
);
CREATE TABLE "PROPERTIES" (
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/profiles/Alert.java b/sonar-plugin-api/src/main/java/org/sonar/api/profiles/Alert.java
index f415df3b798..891e1e955d9 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/profiles/Alert.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/profiles/Alert.java
@@ -25,7 +25,12 @@ import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.sonar.api.database.BaseIdentifiable;
import org.sonar.api.measures.Metric;
-import javax.persistence.*;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
/**
* Class to map alerts with hibernate model
@@ -72,6 +77,9 @@ public class Alert extends BaseIdentifiable implements Cloneable {
@Column(name = "value_warning", updatable = false, nullable = true, length = 64)
private String valueWarning;
+ @Column(name = "period", updatable = false, nullable = true)
+ private Integer period;
+
/**
* Default constructor
*/
@@ -97,6 +105,20 @@ public class Alert extends BaseIdentifiable implements Cloneable {
}
/**
+ * Creates an alert
+ *
+ * @param rulesProfile the profile used to trigger the alert
+ * @param metric the metric tested for the alert
+ * @param operator the operator defined
+ * @param valueError the error value
+ * @param valueWarning the warning value
+ */
+ public Alert(RulesProfile rulesProfile, Metric metric, String operator, String valueError, String valueWarning, Integer period) {
+ this(rulesProfile, metric, operator, valueError, valueWarning);
+ this.period = period;
+ }
+
+ /**
* @return the alert profile
*/
public RulesProfile getRulesProfile() {
@@ -167,6 +189,20 @@ public class Alert extends BaseIdentifiable implements Cloneable {
}
/**
+ * @return the period
+ */
+ public Integer getPeriod() {
+ return period;
+ }
+
+ /**
+ * Sets the period if any
+ */
+ public void setPeriod(Integer period) {
+ this.period = period;
+ }
+
+ /**
* @return whether the operator is greater than
*/
public boolean isGreaterOperator() {
@@ -205,7 +241,7 @@ public class Alert extends BaseIdentifiable implements Cloneable {
@Override
public Object clone() {
- return new Alert(getRulesProfile(), getMetric(), getOperator(), getValueError(), getValueWarning());
+ return new Alert(getRulesProfile(), getMetric(), getOperator(), getValueError(), getValueWarning(), getPeriod());
}
}
diff --git a/sonar-server/src/main/webapp/WEB-INF/db/migrate/359_add_period_to_alerts.rb b/sonar-server/src/main/webapp/WEB-INF/db/migrate/359_add_period_to_alerts.rb
new file mode 100644
index 00000000000..9a946e3b1d6
--- /dev/null
+++ b/sonar-server/src/main/webapp/WEB-INF/db/migrate/359_add_period_to_alerts.rb
@@ -0,0 +1,31 @@
+#
+# Sonar, entreprise quality control tool.
+# Copyright (C) 2008-2012 SonarSource
+# mailto:contact AT sonarsource DOT com
+#
+# Sonar 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.
+#
+# Sonar 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 Sonar; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+#
+
+#
+# Sonar 3.4
+#
+class AddPeriodToAlerts < ActiveRecord::Migration
+
+ def self.up
+ add_column 'alerts', 'period', :integer, :null => true
+ end
+
+end
+
diff --git a/sonar-server/src/test/resources/org/sonar/server/startup/RegisterMetricsTest/cleanAlerts-result.xml b/sonar-server/src/test/resources/org/sonar/server/startup/RegisterMetricsTest/cleanAlerts-result.xml
index bf416f8256a..9acc236c088 100644
--- a/sonar-server/src/test/resources/org/sonar/server/startup/RegisterMetricsTest/cleanAlerts-result.xml
+++ b/sonar-server/src/test/resources/org/sonar/server/startup/RegisterMetricsTest/cleanAlerts-result.xml
@@ -10,13 +10,13 @@
<rules_profiles id="2" version="1" used_profile="true" name="profile2" language="JAV" />
<!-- ok -->
- <alerts id="1" profile_id="1" metric_id="1" operator=">" value_error="30" value_warning="[null]"/>
- <alerts id="2" profile_id="2" metric_id="1" operator=">" value_error="[null]" value_warning="150"/>
+ <alerts id="1" profile_id="1" metric_id="1" operator=">" value_error="30" value_warning="[null]" period="[null]"/>
+ <alerts id="2" profile_id="2" metric_id="1" operator=">" value_error="[null]" value_warning="150" period="[null]"/>
<!-- disabled metric -->
- <!--<alerts id="3" profile_id="1" metric_id="2" operator=">" value_error="30" value_warning="[null]"/>-->
+ <!--<alerts id="3" profile_id="1" metric_id="2" operator=">" value_error="30" value_warning="[null]" period="[null]"/>-->
<!-- unknown metric -->
- <!--<alerts id="4" profile_id="1" metric_id="999" operator=">" value_error="30" value_warning="[null]"/>-->
+ <!--<alerts id="4" profile_id="1" metric_id="999" operator=">" value_error="30" value_warning="[null]" period="[null]"/>-->
</dataset> \ No newline at end of file
diff --git a/sonar-server/src/test/resources/org/sonar/server/startup/RegisterMetricsTest/cleanAlerts.xml b/sonar-server/src/test/resources/org/sonar/server/startup/RegisterMetricsTest/cleanAlerts.xml
index 9e033faaa2f..1f12416b02f 100644
--- a/sonar-server/src/test/resources/org/sonar/server/startup/RegisterMetricsTest/cleanAlerts.xml
+++ b/sonar-server/src/test/resources/org/sonar/server/startup/RegisterMetricsTest/cleanAlerts.xml
@@ -11,13 +11,13 @@
<rules_profiles id="2" version="1" used_profile="true" name="profile2" language="JAV" />
<!-- ok -->
- <alerts id="1" profile_id="1" metric_id="1" operator=">" value_error="30" value_warning="[null]"/>
- <alerts id="2" profile_id="2" metric_id="1" operator=">" value_error="[null]" value_warning="150"/>
+ <alerts id="1" profile_id="1" metric_id="1" operator=">" value_error="30" value_warning="[null]" period="[null]"/>
+ <alerts id="2" profile_id="2" metric_id="1" operator=">" value_error="[null]" value_warning="150" period="[null]"/>
<!-- disabled metric -->
- <alerts id="3" profile_id="1" metric_id="2" operator=">" value_error="30" value_warning="[null]"/>
+ <alerts id="3" profile_id="1" metric_id="2" operator=">" value_error="30" value_warning="[null]" period="[null]"/>
<!-- unknown metric -->
- <alerts id="4" profile_id="1" metric_id="999" operator=">" value_error="30" value_warning="[null]"/>
+ <alerts id="4" profile_id="1" metric_id="999" operator=">" value_error="30" value_warning="[null]" period="[null]"/>
</dataset> \ No newline at end of file