]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-4776 Execute new technical debt decorator
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Thu, 7 Nov 2013 10:00:58 +0000 (11:00 +0100)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Thu, 7 Nov 2013 10:00:58 +0000 (11:00 +0100)
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/NewTechnicalDebtDecorator.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecorator.java
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/NewTechnicalDebtDecoratorTest.java
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecoratorTest.java
sonar-batch/src/main/java/org/sonar/batch/components/TimeMachineConfiguration.java
sonar-core/src/main/java/org/sonar/core/issue/IssueChangelogFinder.java

index abcac0269772436085408879abf0d653859afaa0..265dcba65df2ee1d593e95e040e4ecff84b8df79 100644 (file)
 package org.sonar.plugins.core;
 
 import com.google.common.collect.ImmutableList;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.Properties;
-import org.sonar.api.Property;
-import org.sonar.api.PropertyType;
-import org.sonar.api.SonarPlugin;
+import org.sonar.api.*;
 import org.sonar.api.checks.NoSonarFilter;
 import org.sonar.api.config.PropertyDefinition;
 import org.sonar.api.resources.Java;
 import org.sonar.api.resources.Qualifiers;
 import org.sonar.batch.components.PastSnapshotFinder;
+import org.sonar.core.issue.IssueChangelogFinder;
+import org.sonar.core.technicaldebt.TechnicalDebtCalculator;
+import org.sonar.core.technicaldebt.TechnicalDebtConverter;
 import org.sonar.core.timemachine.Periods;
 import org.sonar.plugins.core.batch.IndexProjectPostJob;
 import org.sonar.plugins.core.charts.DistributionAreaChart;
 import org.sonar.plugins.core.charts.DistributionBarChart;
 import org.sonar.plugins.core.charts.XradarChart;
 import org.sonar.plugins.core.colorizers.JavaColorizerFormat;
-import org.sonar.plugins.core.dashboards.GlobalDefaultDashboard;
-import org.sonar.plugins.core.dashboards.ProjectDefaultDashboard;
-import org.sonar.plugins.core.dashboards.ProjectHotspotDashboard;
-import org.sonar.plugins.core.dashboards.ProjectIssuesDashboard;
-import org.sonar.plugins.core.dashboards.ProjectTimeMachineDashboard;
-import org.sonar.plugins.core.issue.CountFalsePositivesDecorator;
-import org.sonar.plugins.core.issue.CountUnresolvedIssuesDecorator;
-import org.sonar.plugins.core.issue.InitialOpenIssuesSensor;
-import org.sonar.plugins.core.issue.InitialOpenIssuesStack;
-import org.sonar.plugins.core.issue.IssueHandlers;
-import org.sonar.plugins.core.issue.IssueTracking;
-import org.sonar.plugins.core.issue.IssueTrackingDecorator;
-import org.sonar.plugins.core.issue.IssuesDensityDecorator;
-import org.sonar.plugins.core.issue.WeightedIssuesDecorator;
+import org.sonar.plugins.core.dashboards.*;
+import org.sonar.plugins.core.issue.*;
 import org.sonar.plugins.core.issue.ignore.IgnoreIssuesPlugin;
-import org.sonar.plugins.core.issue.notification.ChangesOnMyIssueNotificationDispatcher;
-import org.sonar.plugins.core.issue.notification.IssueChangesEmailTemplate;
-import org.sonar.plugins.core.issue.notification.NewFalsePositiveNotificationDispatcher;
-import org.sonar.plugins.core.issue.notification.NewIssuesEmailTemplate;
-import org.sonar.plugins.core.issue.notification.NewIssuesNotificationDispatcher;
-import org.sonar.plugins.core.issue.notification.SendIssueNotificationsPostJob;
+import org.sonar.plugins.core.issue.notification.*;
 import org.sonar.plugins.core.measurefilters.MyFavouritesFilter;
 import org.sonar.plugins.core.measurefilters.ProjectFilter;
 import org.sonar.plugins.core.notifications.alerts.NewAlerts;
 import org.sonar.plugins.core.security.ApplyProjectRolesDecorator;
-import org.sonar.plugins.core.sensors.BranchCoverageDecorator;
-import org.sonar.plugins.core.sensors.CheckAlertThresholds;
-import org.sonar.plugins.core.sensors.CommentDensityDecorator;
-import org.sonar.plugins.core.sensors.CoverageDecorator;
-import org.sonar.plugins.core.sensors.CoverageMeasurementFilter;
-import org.sonar.plugins.core.sensors.DirectoriesDecorator;
-import org.sonar.plugins.core.sensors.FileHashSensor;
-import org.sonar.plugins.core.sensors.FilesDecorator;
-import org.sonar.plugins.core.sensors.GenerateAlertEvents;
-import org.sonar.plugins.core.sensors.ItBranchCoverageDecorator;
-import org.sonar.plugins.core.sensors.ItCoverageDecorator;
-import org.sonar.plugins.core.sensors.ItLineCoverageDecorator;
-import org.sonar.plugins.core.sensors.LineCoverageDecorator;
-import org.sonar.plugins.core.sensors.ManualMeasureDecorator;
-import org.sonar.plugins.core.sensors.OverallBranchCoverageDecorator;
-import org.sonar.plugins.core.sensors.OverallCoverageDecorator;
-import org.sonar.plugins.core.sensors.OverallLineCoverageDecorator;
-import org.sonar.plugins.core.sensors.ProfileEventsSensor;
-import org.sonar.plugins.core.sensors.ProfileSensor;
-import org.sonar.plugins.core.sensors.ProjectLinksSensor;
-import org.sonar.plugins.core.sensors.UnitTestDecorator;
-import org.sonar.plugins.core.sensors.VersionEventsSensor;
+import org.sonar.plugins.core.sensors.*;
+import org.sonar.plugins.core.technicaldebt.NewTechnicalDebtDecorator;
 import org.sonar.plugins.core.technicaldebt.TechnicalDebtDecorator;
-import org.sonar.plugins.core.timemachine.NewCoverageAggregator;
-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.TendencyDecorator;
-import org.sonar.plugins.core.timemachine.TimeMachineConfigurationPersister;
-import org.sonar.plugins.core.timemachine.VariationDecorator;
+import org.sonar.plugins.core.timemachine.*;
 import org.sonar.plugins.core.web.Lcom4Viewer;
 import org.sonar.plugins.core.web.TestsViewer;
 import org.sonar.plugins.core.widgets.*;
-import org.sonar.plugins.core.widgets.issues.ActionPlansWidget;
-import org.sonar.plugins.core.widgets.issues.FalsePositiveIssuesWidget;
-import org.sonar.plugins.core.widgets.issues.IssueFilterWidget;
-import org.sonar.plugins.core.widgets.issues.IssuesWidget;
-import org.sonar.plugins.core.widgets.issues.MyUnresolvedIssuesWidget;
-import org.sonar.plugins.core.widgets.issues.UnresolvedIssuesPerAssigneeWidget;
-import org.sonar.plugins.core.widgets.issues.UnresolvedIssuesStatusesWidget;
+import org.sonar.plugins.core.widgets.issues.*;
 
 import java.util.Arrays;
 import java.util.List;
@@ -327,6 +277,7 @@ public final class CorePlugin extends SonarPlugin {
       UnresolvedIssuesStatusesWidget.class,
       IssueFilterWidget.class,
       org.sonar.api.issue.NoSonarFilter.class,
+      IssueChangelogFinder.class,
 
       // issue notifications
       SendIssueNotificationsPostJob.class,
@@ -339,6 +290,12 @@ public final class CorePlugin extends SonarPlugin {
       NewFalsePositiveNotificationDispatcher.class,
       NewFalsePositiveNotificationDispatcher.newMetadata(),
 
+      // technical debt
+      TechnicalDebtConverter.class,
+      TechnicalDebtCalculator.class,
+      TechnicalDebtDecorator.class,
+      NewTechnicalDebtDecorator.class,
+
       // batch
       ProfileSensor.class,
       ProfileEventsSensor.class,
@@ -383,7 +340,7 @@ public final class CorePlugin extends SonarPlugin {
     extensions.addAll(IgnoreIssuesPlugin.getExtensions());
     extensions.addAll(CoverageMeasurementFilter.getPropertyDefinitions());
     extensions.addAll(PastSnapshotFinder.getPropertyDefinitions());
-    extensions.addAll(TechnicalDebtDecorator.extensions());
+    extensions.addAll(TechnicalDebtDecorator.definitions());
     extensions.addAll(propertyDefinitions());
 
     return extensions.build();
index 814ff1ef26352a1b0f122796b8a2e45e93fd1a15..7e003efd5c6eda5172b14a2bf4aa64f419261fa8 100644 (file)
@@ -43,11 +43,10 @@ import org.sonar.core.issue.IssueChangelogFinder;
 import org.sonar.core.issue.IssueUpdater;
 import org.sonar.core.technicaldebt.TechnicalDebtConverter;
 
+import javax.annotation.Nullable;
+
 import java.io.Serializable;
-import java.util.Collection;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 import static com.google.common.collect.Lists.newArrayList;
 
@@ -102,13 +101,13 @@ public final class NewTechnicalDebtDecorator implements Decorator {
     context.saveMeasure(measure);
   }
 
-  private Double calculateNewTechnicalDebtValue(Collection<Issue> issues, Multimap<Issue, FieldDiffs> changelogList, Date periodDate){
+  private Double calculateNewTechnicalDebtValue(Collection<Issue> issues, Multimap<Issue, FieldDiffs> changelogList, @Nullable Date periodDate){
     double value = 0;
     for (Issue issue : issues) {
       WorkDayDuration currentTechnicalDebt = ((DefaultIssue) issue).technicalDebt();
       List<FieldDiffs> technicalDebtChangelog = changesOnField(IssueUpdater.TECHNICAL_DEBT, changelogList.get(issue));
       if (technicalDebtChangelog.isEmpty()) {
-        if (issue.creationDate().after(periodDate)) {
+        if (isAfter(issue.creationDate(), periodDate)) {
           value += technicalDebtConverter.toDays(currentTechnicalDebt);
         }
       } else {
@@ -123,14 +122,12 @@ public final class NewTechnicalDebtDecorator implements Decorator {
 
     // Changelog have to be sorted from oldest to newest to catch oldest value just before the period date -> By default it's sorted from newest to oldest
     for (FieldDiffs fieldDiffs : Lists.reverse(technicalDebtChangelog)) {
-      if (fieldDiffs.createdAt().after(periodDate)) {
+      if (isAfter(fieldDiffs.createdAt(), periodDate)) {
         WorkDayDuration pastTechnicalDebt = newValue(IssueUpdater.TECHNICAL_DEBT, fieldDiffs);
-        // Can new technical debt be null ? It shouldn't but...
         double pastTechnicalDebtValue = technicalDebtConverter.toDays(pastTechnicalDebt);
         return currentTechnicalDebtValue - pastTechnicalDebtValue;
       }
     }
-    // FIXME is it possible to have null?
     return null;
   }
 
@@ -154,6 +151,10 @@ public final class NewTechnicalDebtDecorator implements Decorator {
     return null;
   }
 
+  private boolean isAfter(Date currentDate, @Nullable Date pastDate) {
+    return pastDate == null || (currentDate != null && DateUtils.truncatedCompareTo(currentDate, pastDate, Calendar.SECOND) > 0);
+  }
+
   private boolean shouldSaveNewMetrics(DecoratorContext context) {
     return context.getMeasure(CoreMetrics.NEW_TECHNICAL_DEBT) == null;
   }
index 35d86251e85a7447979d5ce82db90ca385152bed..e269bbbae36dc99a62d47fc3417c9c78497e475a 100644 (file)
@@ -101,16 +101,7 @@ public final class TechnicalDebtDecorator implements Decorator {
     }
   }
 
-  public static List<?> extensions() {
-    ImmutableList.Builder<Object> extensions = ImmutableList.builder();
-    extensions.addAll(definitions());
-    extensions.add(
-      TechnicalDebtDecorator.class, TechnicalDebtCalculator.class
-    );
-    return extensions.build();
-  }
-
-  private static List<PropertyDefinition> definitions() {
+  public static List<PropertyDefinition> definitions() {
     return ImmutableList.of(
       PropertyDefinition.builder(TechnicalDebtConverter.PROPERTY_HOURS_IN_DAY)
         .name("Number of working hours in a day")
index 6cc00ae1d568e46fd75d4261c4d0fdc7a43125b0..12fe60f2ff38048495c4e6252531262584e0efad 100644 (file)
@@ -142,6 +142,48 @@ public class NewTechnicalDebtDecoratorTest {
     verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 3.0, 4.0)));
   }
 
+  @Test
+  public void save_on_one_issue_with_changelog_having_null_value() {
+    Issue issue = new DefaultIssue().setKey("A").setCreationDate(fiveDaysAgo).setTechnicalDebt(fiveDaysDebt);
+    when(issuable.issues()).thenReturn(newArrayList(issue));
+
+    Multimap<Issue, FieldDiffs> changelogList = ArrayListMultimap.create();
+    // Changes should be sorted from newest to oldest
+    changelogList.putAll(issue, newArrayList(
+      new FieldDiffs().setDiff("technicalDebt", null, fiveDaysDebt).setCreatedAt(rightNow),
+      new FieldDiffs().setDiff("technicalDebt", oneDaysDebt, null).setCreatedAt(fourDaysAgo),
+      new FieldDiffs().setDiff("technicalDebt", null, oneDaysDebt).setCreatedAt(nineDaysAgo)
+    ));
+    when(changelogFinder.findByIssues(anyCollection())).thenReturn(changelogList);
+
+    decorator.decorate(resource, context);
+
+    // remember : period1 is 5daysAgo, period2 is 10daysAgo
+    verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 5.0, 4.0)));
+  }
+
+  @Test
+  public void save_on_one_issue_with_changelog_and_periods_have_no_dates() {
+    when(timeMachineConfiguration.periods()).thenReturn(newArrayList(new Period(1, null, null), new Period(2, null, null)));
+
+    Issue issue = new DefaultIssue().setKey("A").setCreationDate(fiveDaysAgo).setTechnicalDebt(fiveDaysDebt);
+    when(issuable.issues()).thenReturn(newArrayList(issue));
+
+    Multimap<Issue, FieldDiffs> changelogList = ArrayListMultimap.create();
+    // Changes should be sorted from newest to oldest
+    changelogList.putAll(issue, newArrayList(
+      new FieldDiffs().setDiff("technicalDebt", null, fiveDaysDebt).setCreatedAt(rightNow),
+      new FieldDiffs().setDiff("technicalDebt", oneDaysDebt, null).setCreatedAt(fourDaysAgo),
+      new FieldDiffs().setDiff("technicalDebt", null, oneDaysDebt).setCreatedAt(nineDaysAgo)
+    ));
+    when(changelogFinder.findByIssues(anyCollection())).thenReturn(changelogList);
+
+    decorator.decorate(resource, context);
+
+    // remember : period1 is 5daysAgo, period2 is 10daysAgo
+    verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 4.0, 4.0)));
+  }
+
   @Test
   public void save_on_issues_with_changelog() {
     Issue issue1 = new DefaultIssue().setKey("A").setCreationDate(fiveDaysAgo).setTechnicalDebt(fiveDaysDebt);
@@ -182,6 +224,38 @@ public class NewTechnicalDebtDecoratorTest {
     verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0, 5.0)));
   }
 
+  @Test
+  public void save_on_one_issue_without_technical_debt_and_without_changelog() {
+    when(issuable.issues()).thenReturn(newArrayList(
+      (Issue) new DefaultIssue().setKey("A").setCreationDate(nineDaysAgo).setTechnicalDebt(null))
+    );
+
+    Multimap<Issue, FieldDiffs> changelogList = ArrayListMultimap.create();
+    when(changelogFinder.findByIssues(anyCollection())).thenReturn(changelogList);
+
+    decorator.decorate(resource, context);
+
+    // remember : period1 is 5daysAgo, period2 is 10daysAgo
+    verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0, 0.0)));
+  }
+
+  @Test
+  public void save_on_one_issue_without_changelog_and_periods_have_no_dates() {
+    when(timeMachineConfiguration.periods()).thenReturn(newArrayList(new Period(1, null, null), new Period(2, null, null)));
+
+    when(issuable.issues()).thenReturn(newArrayList(
+      (Issue) new DefaultIssue().setKey("A").setCreationDate(nineDaysAgo).setTechnicalDebt(fiveDaysDebt))
+    );
+
+    Multimap<Issue, FieldDiffs> changelogList = ArrayListMultimap.create();
+    when(changelogFinder.findByIssues(anyCollection())).thenReturn(changelogList);
+
+    decorator.decorate(resource, context);
+
+    // remember : period1 is null, period2 is null
+    verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 5.0, 5.0)));
+  }
+
   @Test
   public void save_on_issues_without_changelog() {
     when(issuable.issues()).thenReturn(newArrayList(
index 96988f33f0d7802f1394e12edff0b8cc8532750b..4ee7fac134626da060669989e740adea5be89617 100644 (file)
@@ -146,8 +146,8 @@ public class TechnicalDebtDecoratorTest {
   }
 
   @Test
-  public void check_extensions() {
-    assertThat(TechnicalDebtDecorator.extensions()).hasSize(1 /* properties */ + 2 /* extensions */);
+  public void check_definitions() {
+    assertThat(TechnicalDebtDecorator.definitions()).hasSize(1);
   }
 
 }
index bb8be2a2947a8cd99e4592f783653fd53897dc08..cae6da082c9895e7b545b653fceec17292fea486 100644 (file)
@@ -62,6 +62,7 @@ public class TimeMachineConfiguration implements BatchExtension {
       pastSnapshot.setIndex(projectPastSnapshot.getIndex());
       pastSnapshot.setModeParameter(projectPastSnapshot.getModeParameter());
       modulePastSnapshots.add(pastSnapshot);
+      // When no snapshot is found, date of the period is null
       periods.add(new Period(pastSnapshot.getIndex(), pastSnapshot.getTargetDate(), snapshot != null ? snapshot.getCreatedAt() : null));
       log(pastSnapshot);
     }
index b92ce3a0fe05d89c79057faa98908acec69a52ef..717d1b6c0fef4a01988b6bfcb55da3e6a28ba889 100644 (file)
@@ -25,7 +25,7 @@ import com.google.common.base.Predicate;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Multimap;
-import org.sonar.api.BatchComponent;
+import org.sonar.api.BatchExtension;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.issue.internal.FieldDiffs;
 import org.sonar.core.issue.db.IssueChangeDao;
@@ -37,7 +37,7 @@ import java.util.List;
 
 import static com.google.common.collect.Lists.newArrayList;
 
-public class IssueChangelogFinder implements BatchComponent {
+public class IssueChangelogFinder implements BatchExtension {
 
   private final IssueChangeDao dao;