diff options
author | Jean-Baptiste Lievremont <jean-baptiste.lievremont@sonarsource.com> | 2014-03-20 12:34:40 +0100 |
---|---|---|
committer | Jean-Baptiste Lievremont <jean-baptiste.lievremont@sonarsource.com> | 2014-03-20 12:35:42 +0100 |
commit | 77e103ed19bd216665ad3804c49bd483306b6a21 (patch) | |
tree | 772511ada238c4d5430f55c2039d57c1af4b787c /sonar-batch | |
parent | b9be7782fa7e316f682b6eca0843610584d70c29 (diff) | |
download | sonarqube-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')
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)); + } +} |