aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-batch
diff options
context:
space:
mode:
authorJean-Baptiste Lievremont <jean-baptiste.lievremont@sonarsource.com>2014-03-20 12:34:40 +0100
committerJean-Baptiste Lievremont <jean-baptiste.lievremont@sonarsource.com>2014-03-20 12:35:42 +0100
commit77e103ed19bd216665ad3804c49bd483306b6a21 (patch)
tree772511ada238c4d5430f55c2039d57c1af4b787c /sonar-batch
parentb9be7782fa7e316f682b6eca0843610584d70c29 (diff)
downloadsonarqube-77e103ed19bd216665ad3804c49bd483306b6a21.tar.gz
sonarqube-77e103ed19bd216665ad3804c49bd483306b6a21.zip
SONAR-4366 Move a core plugin component to batch (remove server dependency on batch component)
Diffstat (limited to 'sonar-batch')
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/qualitygate/GenerateQualityGateEvents.java122
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java3
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/qualitygate/GenerateQualityGateEventsTest.java200
3 files changed, 325 insertions, 0 deletions
diff --git a/sonar-batch/src/main/java/org/sonar/batch/qualitygate/GenerateQualityGateEvents.java b/sonar-batch/src/main/java/org/sonar/batch/qualitygate/GenerateQualityGateEvents.java
new file mode 100644
index 00000000000..2d99ed9015f
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/qualitygate/GenerateQualityGateEvents.java
@@ -0,0 +1,122 @@
+/*
+ * 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 org.sonar.api.batch.*;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.measures.Metric.Level;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.notifications.NotificationManager;
+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 java.util.List;
+
+public class GenerateQualityGateEvents implements Decorator {
+
+ private final QualityGate qualityGate;
+ private final TimeMachine timeMachine;
+ private NotificationManager notificationManager;
+
+ public GenerateQualityGateEvents(QualityGate qualityGate, TimeMachine timeMachine, NotificationManager notificationManager) {
+ this.qualityGate = qualityGate;
+ this.timeMachine = timeMachine;
+ this.notificationManager = notificationManager;
+ }
+
+ public boolean shouldExecuteOnProject(Project project) {
+ return qualityGate.isEnabled();
+ }
+
+ @DependsUpon
+ public Metric dependsUponAlertStatus() {
+ return CoreMetrics.ALERT_STATUS;
+ }
+
+ public void decorate(Resource resource, DecoratorContext context) {
+ if (!shouldDecorateResource(resource)) {
+ return;
+ }
+ Measure currentStatus = context.getMeasure(CoreMetrics.ALERT_STATUS);
+ if (currentStatus == null) {
+ return;
+ }
+
+ TimeMachineQuery query = new TimeMachineQuery(resource).setOnlyLastAnalysis(true).setMetrics(CoreMetrics.ALERT_STATUS);
+ List<Measure> measures = timeMachine.getMeasures(query);
+
+ Measure pastStatus = measures != null && measures.size() == 1 ? measures.get(0) : null;
+ String alertText = currentStatus.getAlertText();
+ Level alertLevel = currentStatus.getDataAsLevel();
+ String alertName = null;
+ boolean isNewAlert = true;
+ if (pastStatus != null && pastStatus.getDataAsLevel() != alertLevel) {
+ // The alert status has changed
+ alertName = getName(pastStatus, currentStatus);
+ if (pastStatus.getDataAsLevel() != Metric.Level.OK) {
+ // There was already a Orange/Red alert, so this is no new alert: it has just changed
+ isNewAlert = false;
+ }
+ createEvent(context, alertName, alertText);
+ notifyUsers(resource, alertName, alertText, alertLevel, isNewAlert);
+
+ } else if (pastStatus == null && alertLevel != Metric.Level.OK) {
+ // There were no defined alerts before, so this one is a new one
+ alertName = getName(currentStatus);
+ createEvent(context, alertName, alertText);
+ notifyUsers(resource, alertName, alertText, alertLevel, isNewAlert);
+ }
+
+ }
+
+ protected void notifyUsers(Resource resource, String alertName, String alertText, Level alertLevel, boolean isNewAlert) {
+ Notification notification = new Notification("alerts")
+ .setDefaultMessage("Alert on " + resource.getLongName() + ": " + alertName)
+ .setFieldValue("projectName", resource.getLongName())
+ .setFieldValue("projectKey", resource.getKey())
+ .setFieldValue("projectId", String.valueOf(resource.getId()))
+ .setFieldValue("alertName", alertName)
+ .setFieldValue("alertText", alertText)
+ .setFieldValue("alertLevel", alertLevel.toString())
+ .setFieldValue("isNewAlert", Boolean.toString(isNewAlert));
+ notificationManager.scheduleForSending(notification);
+ }
+
+ private boolean shouldDecorateResource(Resource resource) {
+ return ResourceUtils.isRootProject(resource);
+ }
+
+ private String getName(Measure pastStatus, Measure currentStatus) {
+ return getName(currentStatus) + " (was " + getName(pastStatus) + ")";
+
+ }
+
+ private String getName(Measure currentStatus) {
+ return currentStatus.getDataAsLevel().getColorName();
+ }
+
+ private void createEvent(DecoratorContext context, String name, String description) {
+ context.createEvent(name, description, Event.CATEGORY_ALERT, null);
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java
index 73c2f6803bd..835aa9be91f 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java
@@ -19,6 +19,8 @@
*/
package org.sonar.batch.scan;
+import org.sonar.batch.qualitygate.GenerateQualityGateEvents;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.BatchExtension;
@@ -125,6 +127,7 @@ public class ModuleScanContainer extends ComponentContainer {
// quality gates
new QualityGateProvider(),
QualityGateVerifier.class,
+ GenerateQualityGateEvents.class,
// rules
ModuleQProfiles.class,
diff --git a/sonar-batch/src/test/java/org/sonar/batch/qualitygate/GenerateQualityGateEventsTest.java b/sonar-batch/src/test/java/org/sonar/batch/qualitygate/GenerateQualityGateEventsTest.java
new file mode 100644
index 00000000000..edf86e96729
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/qualitygate/GenerateQualityGateEventsTest.java
@@ -0,0 +1,200 @@
+/*
+ * 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 org.sonar.batch.qualitygate.GenerateQualityGateEvents;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.Event;
+import org.sonar.api.batch.TimeMachine;
+import org.sonar.api.batch.TimeMachineQuery;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.notifications.NotificationManager;
+import org.sonar.api.resources.File;
+import org.sonar.api.resources.Project;
+import org.sonar.api.test.ProjectTestBuilder;
+import org.sonar.batch.qualitygate.QualityGate;
+
+import java.util.Arrays;
+import java.util.Date;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.*;
+import static org.mockito.Mockito.*;
+
+public class GenerateQualityGateEventsTest {
+ private GenerateQualityGateEvents decorator;
+ private DecoratorContext context;
+ private QualityGate qualityGate;
+ private TimeMachine timeMachine;
+ private NotificationManager notificationManager;
+ private Project project;
+
+ @Before
+ public void setup() {
+ context = mock(DecoratorContext.class);
+ timeMachine = mock(TimeMachine.class);
+ qualityGate = mock(QualityGate.class);
+ notificationManager = mock(NotificationManager.class);
+ decorator = new GenerateQualityGateEvents(qualityGate, timeMachine, notificationManager);
+ project = new ProjectTestBuilder().build();
+ }
+
+ @Test
+ public void shouldDependUponAlertStatus() {
+ assertThat(decorator.dependsUponAlertStatus()).isEqualTo(CoreMetrics.ALERT_STATUS);
+ }
+
+ @Test
+ public void shouldNotDecorateIfNoThresholds() {
+ assertThat(decorator.shouldExecuteOnProject(project)).isFalse();
+ }
+
+ @Test
+ public void shouldDecorateIfQualityGateEnabled() {
+ when(qualityGate.isEnabled()).thenReturn(true);
+ assertThat(decorator.shouldExecuteOnProject(project)).isTrue();
+ }
+
+ @Test
+ public void shouldNotDecorateIfNotRootProject() {
+ decorator.decorate(new File("Foo"), context);
+ verify(context, never()).createEvent(anyString(), anyString(), anyString(), (Date) isNull());
+ }
+
+ @Test
+ public void shouldCreateEventWhenNewErrorAlert() {
+ when(context.getMeasure(CoreMetrics.ALERT_STATUS)).thenReturn(newAlertStatus(Metric.Level.ERROR, "desc"));
+
+ decorator.decorate(project, context);
+
+ verify(context).createEvent(Metric.Level.ERROR.getColorName(), "desc", Event.CATEGORY_ALERT, null);
+ verifyNotificationSent("Red", "desc", "ERROR", "true");
+ }
+
+ @Test
+ public void shouldCreateEventWhenNewWarnAlert() {
+ when(context.getMeasure(CoreMetrics.ALERT_STATUS)).thenReturn(newAlertStatus(Metric.Level.WARN, "desc"));
+
+ decorator.decorate(project, context);
+
+ verify(context).createEvent(Metric.Level.WARN.getColorName(), "desc", Event.CATEGORY_ALERT, null);
+ verifyNotificationSent("Orange", "desc", "WARN", "true");
+ }
+
+ @Test
+ public void shouldCreateEventWhenWarnToError() {
+ when(timeMachine.getMeasures(any(TimeMachineQuery.class))).thenReturn(Arrays.asList(newAlertStatus(Metric.Level.WARN, "desc")));
+ when(context.getMeasure(CoreMetrics.ALERT_STATUS)).thenReturn(newAlertStatus(Metric.Level.ERROR, "desc"));
+
+ decorator.decorate(project, context);
+
+ verify(context).createEvent("Red (was Orange)", "desc", Event.CATEGORY_ALERT, null);
+ verifyNotificationSent("Red (was Orange)", "desc", "ERROR", "false");
+ }
+
+ @Test
+ public void shouldCreateEventWhenErrorToOk() {
+ when(timeMachine.getMeasures(any(TimeMachineQuery.class))).thenReturn(Arrays.asList(newAlertStatus(Metric.Level.ERROR, "desc")));
+ when(context.getMeasure(CoreMetrics.ALERT_STATUS)).thenReturn(newAlertStatus(Metric.Level.OK, null));
+
+ decorator.decorate(project, context);
+
+ verify(context).createEvent("Green (was Red)", null, Event.CATEGORY_ALERT, null);
+ verifyNotificationSent("Green (was Red)", null, "OK", "false");
+ }
+
+ @Test
+ public void shouldCreateEventWhenOkToError() {
+ when(timeMachine.getMeasures(any(TimeMachineQuery.class))).thenReturn(Arrays.asList(newAlertStatus(Metric.Level.OK, null)));
+ when(context.getMeasure(CoreMetrics.ALERT_STATUS)).thenReturn(newAlertStatus(Metric.Level.ERROR, "desc"));
+
+ decorator.decorate(project, context);
+
+ verify(context).createEvent("Red (was Green)", "desc", Event.CATEGORY_ALERT, null);
+ verifyNotificationSent("Red (was Green)", "desc", "ERROR", "true");
+ }
+
+ @Test
+ public void shouldCreateEventWhenErrorToWarn() {
+ when(timeMachine.getMeasures(any(TimeMachineQuery.class))).thenReturn(Arrays.asList(newAlertStatus(Metric.Level.ERROR, "desc")));
+ when(context.getMeasure(CoreMetrics.ALERT_STATUS)).thenReturn(newAlertStatus(Metric.Level.WARN, "desc"));
+
+ decorator.decorate(project, context);
+
+ verify(context).createEvent("Orange (was Red)", "desc", Event.CATEGORY_ALERT, null);
+ verifyNotificationSent("Orange (was Red)", "desc", "WARN", "false");
+ }
+
+ @Test
+ public void shouldNotCreateEventWhenNoAlertStatus() {
+ decorator.decorate(project, context);
+
+ verify(context, never()).createEvent(anyString(), anyString(), anyString(), (Date) isNull());
+ verify(notificationManager, never()).scheduleForSending(any(Notification.class));
+ }
+
+ @Test
+ public void shouldNotCreateEventWhenSameLevel() {
+ when(timeMachine.getMeasures(any(TimeMachineQuery.class))).thenReturn(Arrays.asList(newAlertStatus(Metric.Level.ERROR, "desc")));
+ when(context.getMeasure(CoreMetrics.ALERT_STATUS)).thenReturn(newAlertStatus(Metric.Level.ERROR, "desc"));
+
+ decorator.decorate(project, context);
+
+ verify(context, never()).createEvent(anyString(), anyString(), anyString(), (Date) isNull());
+ verify(notificationManager, never()).scheduleForSending(any(Notification.class));
+ }
+
+ @Test
+ public void shouldNotCreateEventIfNoMoreAlertStatus() {
+ when(timeMachine.getMeasures(any(TimeMachineQuery.class))).thenReturn(Arrays.asList(newAlertStatus(Metric.Level.ERROR, "desc")));
+ when(context.getMeasure(CoreMetrics.ALERT_STATUS)).thenReturn(null);
+
+ decorator.decorate(project, context);
+
+ verify(context, never()).createEvent(anyString(), anyString(), anyString(), (Date) isNull());
+ verify(notificationManager, never()).scheduleForSending(any(Notification.class));
+ }
+
+ private Measure newAlertStatus(Metric.Level level, String label) {
+ Measure measure = new Measure(CoreMetrics.ALERT_STATUS, level);
+ measure.setAlertStatus(level);
+ measure.setAlertText(label);
+ return measure;
+ }
+
+ private void verifyNotificationSent(String alertName, String alertText, String alertLevel, String isNewAlert) {
+ Notification notification = new Notification("alerts")
+ .setDefaultMessage("Alert on " + project.getLongName() + ": " + alertName)
+ .setFieldValue("projectName", project.getLongName())
+ .setFieldValue("projectKey", project.getKey())
+ .setFieldValue("projectId", String.valueOf(project.getId()))
+ .setFieldValue("alertName", alertName)
+ .setFieldValue("alertText", alertText)
+ .setFieldValue("alertLevel", alertLevel)
+ .setFieldValue("isNewAlert", isNewAlert);
+ verify(notificationManager, times(1)).scheduleForSending(eq(notification));
+ }
+}