From: Julien Lancelot Date: Thu, 7 Nov 2013 10:00:58 +0000 (+0100) Subject: SONAR-4776 Execute new technical debt decorator X-Git-Tag: 4.1-RC1~312 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=efd0c6cadd2657646596efd503514bd7382463f7;p=sonarqube.git SONAR-4776 Execute new technical debt decorator --- 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 abcac026977..265dcba65df 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 @@ -20,87 +20,37 @@ 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(); diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/NewTechnicalDebtDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/NewTechnicalDebtDecorator.java index 814ff1ef263..7e003efd5c6 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/NewTechnicalDebtDecorator.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/NewTechnicalDebtDecorator.java @@ -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 issues, Multimap changelogList, Date periodDate){ + private Double calculateNewTechnicalDebtValue(Collection issues, Multimap changelogList, @Nullable Date periodDate){ double value = 0; for (Issue issue : issues) { WorkDayDuration currentTechnicalDebt = ((DefaultIssue) issue).technicalDebt(); List 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; } diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecorator.java index 35d86251e85..e269bbbae36 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecorator.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecorator.java @@ -101,16 +101,7 @@ public final class TechnicalDebtDecorator implements Decorator { } } - public static List extensions() { - ImmutableList.Builder extensions = ImmutableList.builder(); - extensions.addAll(definitions()); - extensions.add( - TechnicalDebtDecorator.class, TechnicalDebtCalculator.class - ); - return extensions.build(); - } - - private static List definitions() { + public static List definitions() { return ImmutableList.of( PropertyDefinition.builder(TechnicalDebtConverter.PROPERTY_HOURS_IN_DAY) .name("Number of working hours in a day") diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/NewTechnicalDebtDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/NewTechnicalDebtDecoratorTest.java index 6cc00ae1d56..12fe60f2ff3 100644 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/NewTechnicalDebtDecoratorTest.java +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/NewTechnicalDebtDecoratorTest.java @@ -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 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 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 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 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( diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecoratorTest.java index 96988f33f0d..4ee7fac1346 100644 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecoratorTest.java +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecoratorTest.java @@ -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); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/components/TimeMachineConfiguration.java b/sonar-batch/src/main/java/org/sonar/batch/components/TimeMachineConfiguration.java index bb8be2a2947..cae6da082c9 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/components/TimeMachineConfiguration.java +++ b/sonar-batch/src/main/java/org/sonar/batch/components/TimeMachineConfiguration.java @@ -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); } diff --git a/sonar-core/src/main/java/org/sonar/core/issue/IssueChangelogFinder.java b/sonar-core/src/main/java/org/sonar/core/issue/IssueChangelogFinder.java index b92ce3a0fe0..717d1b6c0fe 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/IssueChangelogFinder.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/IssueChangelogFinder.java @@ -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;