]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-2496 Support 'previous-version' value for differential views
authorFabrice Bellingard <bellingard@gmail.com>
Tue, 10 Jul 2012 08:46:19 +0000 (10:46 +0200)
committerFabrice Bellingard <bellingard@gmail.com>
Thu, 12 Jul 2012 08:33:31 +0000 (10:33 +0200)
15 files changed:
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchModule.java
sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshot.java
sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinder.java
sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinderByPreviousVersion.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/components/TimeMachineConfiguration.java
sonar-batch/src/test/java/org/sonar/batch/components/PastSnapshotFinderByPreviousVersionTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/components/PastSnapshotFinderByVersionTest.java
sonar-batch/src/test/java/org/sonar/batch/components/PastSnapshotFinderTest.java
sonar-batch/src/test/resources/org/sonar/batch/components/PastSnapshotFinderByPreviousVersionTest/shared.xml [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java
sonar-server/src/main/webapp/WEB-INF/app/helpers/application_helper.rb
sonar-server/src/main/webapp/WEB-INF/app/helpers/dashboard_helper.rb
sonar-server/src/main/webapp/WEB-INF/app/helpers/filters_helper.rb

index 6e8241b91f4bd70b579cb394975c6a47f2c1f1f5..7c7052e8c35a4a78e722a38227d4107c256e723a 100644 (file)
 package org.sonar.plugins.core;
 
 import com.google.common.collect.ImmutableList;
-import org.sonar.api.*;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.Extension;
+import org.sonar.api.Properties;
+import org.sonar.api.Property;
+import org.sonar.api.PropertyType;
+import org.sonar.api.SonarPlugin;
 import org.sonar.api.checks.NoSonarFilter;
 import org.sonar.api.resources.Java;
 import org.sonar.plugins.core.batch.ExcludedResourceFilter;
@@ -31,19 +36,79 @@ 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.*;
+import org.sonar.plugins.core.dashboards.DefaultDashboard;
+import org.sonar.plugins.core.dashboards.HotspotsDashboard;
+import org.sonar.plugins.core.dashboards.MyFavouritesDashboard;
+import org.sonar.plugins.core.dashboards.ProjectsDashboard;
+import org.sonar.plugins.core.dashboards.ReviewsDashboard;
+import org.sonar.plugins.core.dashboards.TimeMachineDashboard;
+import org.sonar.plugins.core.dashboards.TreemapDashboard;
 import org.sonar.plugins.core.filters.MyFavouritesFilter;
 import org.sonar.plugins.core.filters.ProjectFilter;
 import org.sonar.plugins.core.filters.TreeMapFilter;
 import org.sonar.plugins.core.security.ApplyProjectRolesDecorator;
-import org.sonar.plugins.core.security.DefaultResourcePermissions;
-import org.sonar.plugins.core.sensors.*;
+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.DirectoriesDecorator;
+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.ManualViolationInjector;
+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.ReviewNotifications;
+import org.sonar.plugins.core.sensors.ReviewWorkflowDecorator;
+import org.sonar.plugins.core.sensors.ReviewsMeasuresDecorator;
+import org.sonar.plugins.core.sensors.UnitTestDecorator;
+import org.sonar.plugins.core.sensors.VersionEventsSensor;
+import org.sonar.plugins.core.sensors.ViolationSeverityUpdater;
+import org.sonar.plugins.core.sensors.ViolationsDecorator;
+import org.sonar.plugins.core.sensors.ViolationsDensityDecorator;
+import org.sonar.plugins.core.sensors.WeightedViolationsDecorator;
 import org.sonar.plugins.core.testdetailsviewer.TestsViewerDefinition;
-import org.sonar.plugins.core.timemachine.*;
+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.NewViolationsDecorator;
+import org.sonar.plugins.core.timemachine.ReferenceAnalysis;
+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.ViolationPersisterDecorator;
+import org.sonar.plugins.core.timemachine.ViolationTrackingDecorator;
 import org.sonar.plugins.core.web.Lcom4Viewer;
-import org.sonar.plugins.core.widgets.*;
+import org.sonar.plugins.core.widgets.AlertsWidget;
+import org.sonar.plugins.core.widgets.CommentsDuplicationsWidget;
+import org.sonar.plugins.core.widgets.ComplexityWidget;
+import org.sonar.plugins.core.widgets.CoverageWidget;
+import org.sonar.plugins.core.widgets.CustomMeasuresWidget;
+import org.sonar.plugins.core.widgets.DescriptionWidget;
+import org.sonar.plugins.core.widgets.EventsWidget;
+import org.sonar.plugins.core.widgets.FilterWidget;
+import org.sonar.plugins.core.widgets.HotspotMetricWidget;
+import org.sonar.plugins.core.widgets.HotspotMostViolatedResourcesWidget;
+import org.sonar.plugins.core.widgets.HotspotMostViolatedRulesWidget;
+import org.sonar.plugins.core.widgets.ItCoverageWidget;
+import org.sonar.plugins.core.widgets.RulesWidget;
+import org.sonar.plugins.core.widgets.SizeWidget;
+import org.sonar.plugins.core.widgets.TimeMachineWidget;
+import org.sonar.plugins.core.widgets.TimelineWidget;
+import org.sonar.plugins.core.widgets.TreemapWidget;
 import org.sonar.plugins.core.widgets.actionPlans.ActionPlansWidget;
-import org.sonar.plugins.core.widgets.reviews.*;
+import org.sonar.plugins.core.widgets.reviews.FalsePositiveReviewsWidget;
+import org.sonar.plugins.core.widgets.reviews.MyReviewsWidget;
+import org.sonar.plugins.core.widgets.reviews.PlannedReviewsWidget;
+import org.sonar.plugins.core.widgets.reviews.ProjectReviewsWidget;
+import org.sonar.plugins.core.widgets.reviews.ReviewsMetricsWidget;
+import org.sonar.plugins.core.widgets.reviews.ReviewsPerDeveloperWidget;
+import org.sonar.plugins.core.widgets.reviews.UnplannedReviewsWidget;
 
 import java.util.List;
 
@@ -139,7 +204,8 @@ import java.util.List;
     name = "Period 1",
     description = "Period used to compare measures and track new violations. Values are : <ul class='bullet'><li>Number of days before " +
       "analysis, for example 5.</li><li>A custom date. Format is yyyy-MM-dd, for example 2010-12-25</li><li>'previous_analysis' to " +
-      "compare to previous analysis</li></ul>" + "<p>When specifying a number of days or a date, the snapshot selected for comparison is " +
+      "compare to previous analysis</li><li>'previous_version' to compare to the previous version in the project history</li></ul>" +
+      "<p>When specifying a number of days or a date, the snapshot selected for comparison is " +
       " the first one available inside the corresponding time range. </p>" +
       "<p>Changing this property only takes effect after subsequent project inspections.<p/>",
     project = false,
@@ -167,7 +233,8 @@ import java.util.List;
     name = "Period 4",
     description = "Period used to compare measures and track new violations. This property is specific to the project. Values are : " +
       "<ul class='bullet'><li>Number of days before analysis, for example 5.</li><li>A custom date. Format is yyyy-MM-dd, " +
-      "for example 2010-12-25</li><li>'previous_analysis' to compare to previous analysis</li><li>A version, for example 1.2</li></ul>" +
+      "for example 2010-12-25</li><li>'previous_analysis' to compare to previous analysis</li>" +
+      "<li>'previous_version' to compare to the previous version in the project history</li><li>A version, for example 1.2</li></ul>" +
       "<p>When specifying a number of days or a date, the snapshot selected for comparison is the first one available inside the corresponding time range. </p>" +
       "<p>Changing this property only takes effect after subsequent project inspections.<p/>",
     project = true,
@@ -226,111 +293,111 @@ public final class CorePlugin extends SonarPlugin {
   @SuppressWarnings("unchecked")
   public List<Class<? extends Extension>> getExtensions() {
     return ImmutableList.of(
-      DefaultResourceTypes.class,
-      UserManagedMetrics.class,
-      ProjectFileSystemLogger.class,
+        DefaultResourceTypes.class,
+        UserManagedMetrics.class,
+        ProjectFileSystemLogger.class,
 
-      // maven
-      MavenInitializer.class,
+        // maven
+        MavenInitializer.class,
 
-      // languages
-      Java.class,
+        // languages
+        Java.class,
 
-      // pages
-      TestsViewerDefinition.class,
-      Lcom4Viewer.class,
+        // pages
+        TestsViewerDefinition.class,
+        Lcom4Viewer.class,
 
-      // filters
-      ProjectFilter.class,
-      TreeMapFilter.class,
-      MyFavouritesFilter.class,
+        // filters
+        ProjectFilter.class,
+        TreeMapFilter.class,
+        MyFavouritesFilter.class,
 
-      // widgets
-      AlertsWidget.class,
-      CoverageWidget.class,
-      ItCoverageWidget.class,
-      CommentsDuplicationsWidget.class,
-      DescriptionWidget.class,
-      ComplexityWidget.class,
-      RulesWidget.class,
-      SizeWidget.class,
-      EventsWidget.class,
-      CustomMeasuresWidget.class,
-      TimelineWidget.class,
-      TimeMachineWidget.class,
-      HotspotMetricWidget.class,
-      HotspotMostViolatedResourcesWidget.class,
-      HotspotMostViolatedRulesWidget.class,
-      MyReviewsWidget.class,
-      ProjectReviewsWidget.class,
-      FalsePositiveReviewsWidget.class,
-      ReviewsPerDeveloperWidget.class,
-      PlannedReviewsWidget.class,
-      UnplannedReviewsWidget.class,
-      ActionPlansWidget.class,
-      ReviewsMetricsWidget.class,
-      TreemapWidget.class,
-      FilterWidget.class,
+        // widgets
+        AlertsWidget.class,
+        CoverageWidget.class,
+        ItCoverageWidget.class,
+        CommentsDuplicationsWidget.class,
+        DescriptionWidget.class,
+        ComplexityWidget.class,
+        RulesWidget.class,
+        SizeWidget.class,
+        EventsWidget.class,
+        CustomMeasuresWidget.class,
+        TimelineWidget.class,
+        TimeMachineWidget.class,
+        HotspotMetricWidget.class,
+        HotspotMostViolatedResourcesWidget.class,
+        HotspotMostViolatedRulesWidget.class,
+        MyReviewsWidget.class,
+        ProjectReviewsWidget.class,
+        FalsePositiveReviewsWidget.class,
+        ReviewsPerDeveloperWidget.class,
+        PlannedReviewsWidget.class,
+        UnplannedReviewsWidget.class,
+        ActionPlansWidget.class,
+        ReviewsMetricsWidget.class,
+        TreemapWidget.class,
+        FilterWidget.class,
 
-      // dashboards
-      DefaultDashboard.class,
-      HotspotsDashboard.class,
-      ReviewsDashboard.class,
-      TimeMachineDashboard.class,
-      ProjectsDashboard.class,
-      TreemapDashboard.class,
-      MyFavouritesDashboard.class,
+        // dashboards
+        DefaultDashboard.class,
+        HotspotsDashboard.class,
+        ReviewsDashboard.class,
+        TimeMachineDashboard.class,
+        ProjectsDashboard.class,
+        TreemapDashboard.class,
+        MyFavouritesDashboard.class,
 
-      // chart
-      XradarChart.class,
-      DistributionBarChart.class,
-      DistributionAreaChart.class,
+        // chart
+        XradarChart.class,
+        DistributionBarChart.class,
+        DistributionAreaChart.class,
 
-      // colorizers
-      JavaColorizerFormat.class,
+        // colorizers
+        JavaColorizerFormat.class,
 
-      // batch
-      ProfileSensor.class,
-      ProfileEventsSensor.class,
-      ProjectLinksSensor.class,
-      UnitTestDecorator.class,
-      VersionEventsSensor.class,
-      CheckAlertThresholds.class,
-      GenerateAlertEvents.class,
-      ViolationsDecorator.class,
-      WeightedViolationsDecorator.class,
-      ViolationsDensityDecorator.class,
-      LineCoverageDecorator.class,
-      CoverageDecorator.class,
-      BranchCoverageDecorator.class,
-      ItLineCoverageDecorator.class,
-      ItCoverageDecorator.class,
-      ItBranchCoverageDecorator.class,
-      DefaultResourcePermissions.class,
-      ApplyProjectRolesDecorator.class,
-      ExcludedResourceFilter.class,
-      CommentDensityDecorator.class,
-      NoSonarFilter.class,
-      DirectoriesDecorator.class,
-      FilesDecorator.class,
-      ReviewNotifications.class,
-      ReviewWorkflowDecorator.class,
-      ReferenceAnalysis.class,
-      ManualMeasureDecorator.class,
-      ManualViolationInjector.class,
-      ViolationSeverityUpdater.class,
-      IndexProjectPostJob.class,
-      ReviewsMeasuresDecorator.class,
+        // batch
+        ProfileSensor.class,
+        ProfileEventsSensor.class,
+        ProjectLinksSensor.class,
+        UnitTestDecorator.class,
+        VersionEventsSensor.class,
+        CheckAlertThresholds.class,
+        GenerateAlertEvents.class,
+        ViolationsDecorator.class,
+        WeightedViolationsDecorator.class,
+        ViolationsDensityDecorator.class,
+        LineCoverageDecorator.class,
+        CoverageDecorator.class,
+        BranchCoverageDecorator.class,
+        ItLineCoverageDecorator.class,
+        ItCoverageDecorator.class,
+        ItBranchCoverageDecorator.class,
+        DefaultResourcePermissions.class,
+        ApplyProjectRolesDecorator.class,
+        ExcludedResourceFilter.class,
+        CommentDensityDecorator.class,
+        NoSonarFilter.class,
+        DirectoriesDecorator.class,
+        FilesDecorator.class,
+        ReviewNotifications.class,
+        ReviewWorkflowDecorator.class,
+        ReferenceAnalysis.class,
+        ManualMeasureDecorator.class,
+        ManualViolationInjector.class,
+        ViolationSeverityUpdater.class,
+        IndexProjectPostJob.class,
+        ReviewsMeasuresDecorator.class,
 
-      // time machine
-      TendencyDecorator.class,
-      VariationDecorator.class,
-      ViolationTrackingDecorator.class,
-      ViolationPersisterDecorator.class,
-      NewViolationsDecorator.class,
-      TimeMachineConfigurationPersister.class,
-      NewCoverageFileAnalyzer.class,
-      NewItCoverageFileAnalyzer.class,
-      NewCoverageAggregator.class);
+        // time machine
+        TendencyDecorator.class,
+        VariationDecorator.class,
+        ViolationTrackingDecorator.class,
+        ViolationPersisterDecorator.class,
+        NewViolationsDecorator.class,
+        TimeMachineConfigurationPersister.class,
+        NewCoverageFileAnalyzer.class,
+        NewItCoverageFileAnalyzer.class,
+        NewCoverageAggregator.class);
   }
 }
index d1fb5fb7af7189ed1fe632f905926fbba8f69320..e6020ecbdccd9eb2b049d436d1c9414514e371ac 100644 (file)
@@ -152,6 +152,8 @@ added_over_x_days=Added over {0} days
 added_since=Added since {0}
 added_since_previous_analysis=Added since previous analysis
 added_since_previous_analysis_detailed=Added since previous analysis ({0})
+added_since_previous_version=Added since previous version
+added_since_previous_version_detailed=Added since previous version ({0})
 added_since_version=Added since version {0}
 alerts_feed=Alerts feed
 all_violations=All violations
@@ -167,6 +169,7 @@ deactivate_all=Deactivate all
 default_severity=Default severity
 default_sort_on=Default sort on
 delta_since_previous_analysis=&Delta; since previous analysis
+delta_since_previous_version=&Delta; since previous version
 delta_over_x_days=&Delta; over {0} days
 delta_since=&Delta; since {0}
 delta_since_version=&Delta; since version {0}
@@ -208,6 +211,8 @@ since_previous_analysis=since previous analysis
 since_previous_analysis_detailed=since previous analysis ({0})
 since_version=since version {0}
 since_version_detailed=since version {0} ({1})
+since_previous_version=since previous version
+since_previous_version_detailed=since previous version ({0})
 time_changes=Time changes
 
 #------------------------------------------------------------------------------
index d4ebebb4889ba232bbe5a6e87f992518d4bced53..45020ddf099a37f4f8b59174db1654636de0de1e 100644 (file)
@@ -29,8 +29,23 @@ import org.sonar.batch.DefaultFileLinesContextFactory;
 import org.sonar.batch.DefaultResourceCreationLock;
 import org.sonar.batch.ProjectConfigurator;
 import org.sonar.batch.ProjectTree;
-import org.sonar.batch.components.*;
-import org.sonar.batch.index.*;
+import org.sonar.batch.components.PastMeasuresLoader;
+import org.sonar.batch.components.PastSnapshotFinder;
+import org.sonar.batch.components.PastSnapshotFinderByDate;
+import org.sonar.batch.components.PastSnapshotFinderByDays;
+import org.sonar.batch.components.PastSnapshotFinderByPreviousAnalysis;
+import org.sonar.batch.components.PastSnapshotFinderByPreviousVersion;
+import org.sonar.batch.components.PastSnapshotFinderByVersion;
+import org.sonar.batch.index.DefaultIndex;
+import org.sonar.batch.index.DefaultPersistenceManager;
+import org.sonar.batch.index.DefaultResourcePersister;
+import org.sonar.batch.index.DependencyPersister;
+import org.sonar.batch.index.EventPersister;
+import org.sonar.batch.index.LinkPersister;
+import org.sonar.batch.index.MeasurePersister;
+import org.sonar.batch.index.MemoryOptimizer;
+import org.sonar.batch.index.ReadOnlyPersistenceManager;
+import org.sonar.batch.index.SourcePersister;
 import org.sonar.core.metric.CacheMetricFinder;
 import org.sonar.core.notification.DefaultNotificationManager;
 import org.sonar.core.rule.CacheRuleFinder;
@@ -79,6 +94,7 @@ public class BatchModule extends Module {
     addCoreSingleton(PastSnapshotFinderByDays.class);
     addCoreSingleton(PastSnapshotFinderByPreviousAnalysis.class);
     addCoreSingleton(PastSnapshotFinderByVersion.class);
+    addCoreSingleton(PastSnapshotFinderByPreviousVersion.class);
     addCoreSingleton(PastMeasuresLoader.class);
     addCoreSingleton(PastSnapshotFinder.class);
     addCoreSingleton(DefaultNotificationManager.class);
index ad5c54a59e9eee9a91fed5f65af215acb6391c36..f4087abe0393e256a24680587a029d5983279b28 100644 (file)
@@ -121,6 +121,13 @@ public class PastSnapshot {
       }
       return label;
     }
+    if (StringUtils.equals(mode, CoreProperties.TIMEMACHINE_MODE_PREVIOUS_VERSION)) {
+      String label = "Compare to previous version";
+      if (isRelatedToSnapshot()) {
+        label += String.format(" (%s)", DateUtils.formatDate(getDate()));
+      }
+      return label;
+    }
     if (StringUtils.equals(mode, CoreProperties.TIMEMACHINE_MODE_DATE)) {
       String label = "Compare to date " + DateUtils.formatDate(getTargetDate());
       if (isRelatedToSnapshot()) {
index 2734e1650777ffd3ee7192341a562dd70fb63be2..7968679b26e1cd4859f2fa2cb45a1bc155dc8f11 100644 (file)
@@ -39,13 +39,16 @@ public class PastSnapshotFinder implements BatchExtension {
   private PastSnapshotFinderByVersion finderByVersion;
   private PastSnapshotFinderByDate finderByDate;
   private PastSnapshotFinderByPreviousAnalysis finderByPreviousAnalysis;
+  private PastSnapshotFinderByPreviousVersion finderByPreviousVersion;
 
   public PastSnapshotFinder(PastSnapshotFinderByDays finderByDays, PastSnapshotFinderByVersion finderByVersion,
-                            PastSnapshotFinderByDate finderByDate, PastSnapshotFinderByPreviousAnalysis finderByPreviousAnalysis) {
+      PastSnapshotFinderByDate finderByDate, PastSnapshotFinderByPreviousAnalysis finderByPreviousAnalysis,
+      PastSnapshotFinderByPreviousVersion finderByPreviousVersion) {
     this.finderByDays = finderByDays;
     this.finderByVersion = finderByVersion;
     this.finderByDate = finderByDate;
     this.finderByPreviousAnalysis = finderByPreviousAnalysis;
+    this.finderByPreviousVersion = finderByPreviousVersion;
   }
 
   public PastSnapshot find(Snapshot projectSnapshot, Configuration conf, int index) {
@@ -94,7 +97,10 @@ public class PastSnapshotFinder implements BatchExtension {
       if (result == null) {
         result = findByPreviousAnalysis(projectSnapshot, property);
         if (result == null) {
-          result = findByVersion(projectSnapshot, property);
+          result = findByPreviousVersion(projectSnapshot, property);
+          if (result == null) {
+            result = findByVersion(projectSnapshot, property);
+          }
         }
       }
     }
@@ -114,6 +120,14 @@ public class PastSnapshotFinder implements BatchExtension {
     return pastSnapshot;
   }
 
+  private PastSnapshot findByPreviousVersion(Snapshot projectSnapshot, String property) {
+    PastSnapshot pastSnapshot = null;
+    if (StringUtils.equals(CoreProperties.TIMEMACHINE_MODE_PREVIOUS_VERSION, property)) {
+      pastSnapshot = finderByPreviousVersion.findByPreviousVersion(projectSnapshot);
+    }
+    return pastSnapshot;
+  }
+
   private PastSnapshot findByDate(Snapshot projectSnapshot, String property) {
     SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
     try {
diff --git a/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinderByPreviousVersion.java b/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinderByPreviousVersion.java
new file mode 100644 (file)
index 0000000..ea2f23e
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * 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.batch.components;
+
+import org.sonar.api.BatchExtension;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.resources.Qualifiers;
+
+import java.util.Date;
+import java.util.List;
+
+public class PastSnapshotFinderByPreviousVersion implements BatchExtension {
+
+  private final DatabaseSession session;
+
+  public PastSnapshotFinderByPreviousVersion(DatabaseSession session) {
+    this.session = session;
+  }
+
+  PastSnapshot findByPreviousVersion(Snapshot projectSnapshot) {
+    String hql = "from " + Snapshot.class.getSimpleName() +
+      " where version!=:version AND version!='' AND resourceId=:resourceId AND status=:status AND qualifier<>:lib order by createdAt desc";
+    List<Snapshot> snapshots = session.createQuery(hql)
+        .setParameter("version", projectSnapshot.getVersion())
+        .setParameter("resourceId", projectSnapshot.getResourceId())
+        .setParameter("status", Snapshot.STATUS_PROCESSED)
+        .setParameter("lib", Qualifiers.LIBRARY)
+        .setMaxResults(1)
+        .getResultList();
+
+    PastSnapshot result;
+    if (snapshots.isEmpty()) {
+      result = new PastSnapshot(CoreProperties.TIMEMACHINE_MODE_PREVIOUS_VERSION);
+    } else {
+      Snapshot snapshot = snapshots.get(0);
+      Date targetDate = snapshot.getCreatedAt();
+      result = new PastSnapshot(CoreProperties.TIMEMACHINE_MODE_PREVIOUS_VERSION, targetDate, snapshot).setModeParameter(snapshot.getVersion());
+    }
+    return result;
+  }
+
+}
index b5e567ce18de43877e83b35c2b6f7fce3bdbf932..f162a65dc2f3864f0dae80a08a01e1b1eaef33ec 100644 (file)
  */
 package org.sonar.batch.components;
 
-import java.util.Date;
-import java.util.List;
-
-import javax.persistence.Query;
-
+import com.google.common.collect.Lists;
 import org.apache.commons.configuration.Configuration;
 import org.apache.commons.lang.StringUtils;
 import org.slf4j.LoggerFactory;
@@ -35,7 +31,10 @@ import org.sonar.api.resources.Project;
 import org.sonar.api.resources.Qualifiers;
 import org.sonar.api.utils.Logs;
 
-import com.google.common.collect.Lists;
+import javax.persistence.Query;
+
+import java.util.Date;
+import java.util.List;
 
 public class TimeMachineConfiguration implements BatchExtension {
 
@@ -83,6 +82,7 @@ public class TimeMachineConfiguration implements BatchExtension {
       snapshot.setResourceId(projectId.intValue());
       snapshot.setCreatedAt(project.getAnalysisDate());
       snapshot.setBuildDate(new Date());
+      snapshot.setVersion(project.getAnalysisVersion());
     }
     return snapshot;
   }
diff --git a/sonar-batch/src/test/java/org/sonar/batch/components/PastSnapshotFinderByPreviousVersionTest.java b/sonar-batch/src/test/java/org/sonar/batch/components/PastSnapshotFinderByPreviousVersionTest.java
new file mode 100644 (file)
index 0000000..793c800
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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.batch.components;
+
+import org.junit.Test;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.jpa.test.AbstractDbUnitTestCase;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class PastSnapshotFinderByPreviousVersionTest extends AbstractDbUnitTestCase {
+
+  @Test
+  public void shouldFindByPreviousVersion() {
+    setupData("shared");
+    PastSnapshotFinderByPreviousVersion finder = new PastSnapshotFinderByPreviousVersion(getSession());
+
+    Snapshot currentProjectSnapshot = getSession().getSingleResult(Snapshot.class, "id", 1010);
+    PastSnapshot foundSnapshot = finder.findByPreviousVersion(currentProjectSnapshot);
+    assertThat(foundSnapshot.getProjectSnapshotId()).isEqualTo(1009);
+    assertThat(foundSnapshot.getMode()).isEqualTo(CoreProperties.TIMEMACHINE_MODE_PREVIOUS_VERSION);
+    assertThat(foundSnapshot.getModeParameter()).isEqualTo("1.1");
+
+    // and test also another version to verify that unprocessed snapshots are ignored
+    currentProjectSnapshot = getSession().getSingleResult(Snapshot.class, "id", 1009);
+    assertThat(finder.findByPreviousVersion(currentProjectSnapshot).getProjectSnapshotId()).isEqualTo(1003);
+  }
+
+}
index 3b8b61236ad6c3d8b50f061d57dc30f6c0bd3d9a..2c952a4cf9af5514e083a81fb94f093cba934116 100644 (file)
@@ -37,18 +37,4 @@ public class PastSnapshotFinderByVersionTest extends AbstractDbUnitTestCase {
     assertThat(finder.findByVersion(currentProjectSnapshot, "1.1").getProjectSnapshotId()).isEqualTo(1009);
   }
 
-  /**
-   * Revert SONAR-3407
-   */
-  @Test
-  public void doNotFailIfUnknownVersion() {
-    setupData("shared");
-
-    Snapshot currentProjectSnapshot = getSession().getSingleResult(Snapshot.class, "id", 1010);
-    PastSnapshotFinderByVersion finder = new PastSnapshotFinderByVersion(getSession());
-
-    PastSnapshot pastSnapshot = finder.findByVersion(currentProjectSnapshot, "0.1.2");
-    assertThat(pastSnapshot).isNotNull();
-    assertThat(pastSnapshot.getDate()).isNull();
-  }
 }
index 5f03dce9160a24d248357cf2ec7457b0afde02d8..f25775962c67cf91fc538c0bf24bc261e0533853 100644 (file)
  */
 package org.sonar.batch.components;
 
+import org.apache.commons.configuration.BaseConfiguration;
+import org.apache.commons.configuration.Configuration;
 import org.apache.commons.configuration.PropertiesConfiguration;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 import org.sonar.api.CoreProperties;
 import org.sonar.api.database.model.Snapshot;
 
@@ -38,25 +42,44 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
 import static org.mockito.Matchers.anyObject;
 import static org.mockito.Matchers.argThat;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 public class PastSnapshotFinderTest {
 
+  @Mock
   private PastSnapshotFinderByDays finderByDays;
+  @Mock
   private PastSnapshotFinderByDate finderByDate;
+  @Mock
   private PastSnapshotFinderByVersion finderByVersion;
+  @Mock
   private PastSnapshotFinderByPreviousAnalysis finderByPreviousAnalysis;
+  @Mock
+  private PastSnapshotFinderByPreviousVersion finderByPreviousVersion;
+
   private PastSnapshotFinder finder;
 
   @Before
   public void initFinders() {
-    finderByDays = mock(PastSnapshotFinderByDays.class);
-    finderByDate = mock(PastSnapshotFinderByDate.class);
-    finderByVersion = mock(PastSnapshotFinderByVersion.class);
-    finderByPreviousAnalysis = mock(PastSnapshotFinderByPreviousAnalysis.class);
-    finder = new PastSnapshotFinder(finderByDays, finderByVersion, finderByDate, finderByPreviousAnalysis);
+    MockitoAnnotations.initMocks(this);
+
+    finder = new PastSnapshotFinder(finderByDays, finderByVersion, finderByDate, finderByPreviousAnalysis, finderByPreviousVersion);
+  }
+
+  @Test
+  public void shouldFind() {
+    Configuration conf = new BaseConfiguration();
+    conf.addProperty("sonar.timemachine.period5", "1.2");
+
+    when(finderByVersion.findByVersion(null, "1.2")).thenReturn(new PastSnapshot("version", new Date(), new Snapshot()));
+
+    PastSnapshot variationSnapshot = finder.find(null, conf, 5);
+
+    verify(finderByVersion).findByVersion(null, "1.2");
+    assertThat(variationSnapshot.getIndex(), is(5));
+    assertThat(variationSnapshot.getMode(), is("version"));
+    assertThat(variationSnapshot.getProjectSnapshot(), not(nullValue()));
   }
 
   @Test
@@ -136,6 +159,22 @@ public class PastSnapshotFinderTest {
     assertNull(variationSnapshot);
   }
 
+  @Test
+  public void shouldFindByPreviousVersion() throws ParseException {
+    final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
+    final Date date = format.parse("2010-05-18");
+    Snapshot snapshot = new Snapshot();
+    snapshot.setCreatedAt(date);
+    when(finderByPreviousVersion.findByPreviousVersion(null)).thenReturn(new PastSnapshot(CoreProperties.TIMEMACHINE_MODE_PREVIOUS_VERSION, date, snapshot));
+
+    PastSnapshot variationSnapshot = finder.find(null, 2, CoreProperties.TIMEMACHINE_MODE_PREVIOUS_VERSION);
+
+    verify(finderByPreviousVersion).findByPreviousVersion(null);
+    assertThat(variationSnapshot.getIndex(), is(2));
+    assertThat(variationSnapshot.getMode(), is(CoreProperties.TIMEMACHINE_MODE_PREVIOUS_VERSION));
+    assertThat(variationSnapshot.getProjectSnapshot(), not(nullValue()));
+  }
+
   @Test
   public void shouldFindByVersion() {
     when(finderByVersion.findByVersion(null, "1.2")).thenReturn(new PastSnapshot("version", new Date(), new Snapshot()));
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/components/PastSnapshotFinderByPreviousVersionTest/shared.xml b/sonar-batch/src/test/resources/org/sonar/batch/components/PastSnapshotFinderByPreviousVersionTest/shared.xml
new file mode 100644 (file)
index 0000000..98c40a5
--- /dev/null
@@ -0,0 +1,42 @@
+<dataset>
+
+  <projects long_name="[null]" id="1" scope="PRJ" qualifier="TRK" kee="project" name="project"
+            root_id="[null]"
+            description="[null]"
+            enabled="true" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+
+  <!-- version 1.1-SNAPSHOT -->
+  <snapshots purge_status="[null]" period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="1000"
+             project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+             scope="PRJ" qualifier="TRK" created_at="2008-11-01 13:58:00.00" build_date="2008-11-01 13:58:00.00" version="1.1-SNAPSHOT" path=""
+             status="P" islast="false" depth="0" />
+
+
+  <!-- version 1.1-SNAPSHOT -->
+  <snapshots purge_status="[null]" period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="1003"
+             project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+             scope="PRJ" qualifier="TRK" created_at="2008-11-02 13:58:00.00" build_date="2008-11-02 13:58:00.00" version="1.1-SNAPSHOT" path=""
+             status="P" islast="true" depth="0" />
+
+
+  <!-- unprocessed version 1.1 (to ignore) -->
+  <snapshots purge_status="[null]" period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="1006"
+             project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+             scope="PRJ" qualifier="TRK" created_at="2008-11-03 13:58:00.00" build_date="2008-11-03 13:58:00.00" version="1.1" path=""
+             status="U" islast="false" depth="0" />
+
+
+  <!-- version 1.1 -->
+  <snapshots purge_status="[null]" period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="1009"
+             project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+             scope="PRJ" qualifier="TRK" created_at="2008-11-04 13:58:00.00" build_date="2008-11-04 13:58:00.00" version="1.1" path=""
+             status="P" islast="false" depth="0" />
+
+  <!-- current analysis -->
+  <snapshots purge_status="[null]" period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="1010"
+             project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+             scope="PRJ" qualifier="TRK" created_at="2008-11-05 13:58:00.00" build_date="2008-11-05 13:58:00.00" version="1.2-SNAPSHOT" path=""
+             status="U" islast="false" depth="0" />
+
+</dataset>
\ No newline at end of file
index 8bae9424b1c253d3e44d8ceda4df016f5f180e3a..17e0c48776e44783a2eaa21684adcc70c81a282a 100644 (file)
@@ -32,7 +32,6 @@ public interface CoreProperties {
    */
   String ENCRYPTION_SECRET_KEY_PATH = "sonar.secretKeyPath";
 
-
   /**
    * @since 2.11
    */
@@ -68,7 +67,6 @@ public interface CoreProperties {
    */
   String CATEGORY_DIFFERENTIAL_VIEWS = "differentialViews";
 
-
   /* Global settings */
   String SONAR_HOME = "SONAR_HOME";
   String PROJECT_BRANCH_PROPERTY = "sonar.branch";
@@ -272,6 +270,7 @@ public interface CoreProperties {
   String TIMEMACHINE_MODE_DATE = "date";
   String TIMEMACHINE_MODE_VERSION = "version";
   String TIMEMACHINE_MODE_DAYS = "days";
+  String TIMEMACHINE_MODE_PREVIOUS_VERSION = "previous_version";
   String TIMEMACHINE_DEFAULT_PERIOD_1 = TIMEMACHINE_MODE_PREVIOUS_ANALYSIS;
   String TIMEMACHINE_DEFAULT_PERIOD_2 = "5";
   String TIMEMACHINE_DEFAULT_PERIOD_3 = "30";
index 871c773e30ac84389b3c124bb6564d7721bcb8ba..fb2dbf84bd2877defd1bf84a5ab1dd2fe72b0baa 100644 (file)
@@ -102,6 +102,12 @@ module ApplicationHelper
         else
           label = message('since_previous_analysis')
         end
+      elsif mode=='previous_version'
+        unless mode_param.nil?
+          label = message('since_previous_version_detailed', :params => mode_param.to_s)
+        else
+          label = message('since_previous_version')
+        end
       elsif mode=='date'
         label = message('since_x', :params => date.strftime("%Y %b %d").to_s)
       end
index c46fa0cb7b68fcb8136445dabba45f0b6097d070..1461413d9364ee4d84957165e4094206e953de70 100644 (file)
@@ -21,7 +21,6 @@ module DashboardHelper
   include WidgetPropertiesHelper
   include MetricsHelper
   include FiltersHelper
-
   def dashboard_action(action_name, opts={})
     if @resource
       { :action => action_name, :did => @dashboard.id, :id => @resource.id }.merge!(opts)
@@ -35,59 +34,65 @@ module DashboardHelper
   end
 
   def formatted_value(measure, default='')
-       measure ? measure.formatted_value : default
+    measure ? measure.formatted_value : default
   end
 
   def measure(metric_key)
-       @snapshot.measure(metric_key)
+    @snapshot.measure(metric_key)
   end
 
   def period_select_options(snapshot, index)
-       label=period_label(snapshot, index)
-       if label
-         selected=(params[:period]==index.to_s ? 'selected' : '')
-         "<option value='#{index}' #{selected}>&Delta; #{label}</option>"
-       else
-         nil
-       end
+    label=period_label(snapshot, index)
+    if label
+      selected=(params[:period]==index.to_s ? 'selected' : '')
+      "<option value='#{index}' #{selected}>&Delta; #{label}</option>"
+    else
+      nil
+    end
   end
 
   def violation_period_select_options(snapshot, index)
-       return nil if snapshot.nil? || snapshot.project_snapshot.nil?
-       mode=snapshot.project_snapshot.send "period#{index}_mode"
-       mode_param=snapshot.project_snapshot.send "period#{index}_param"
-       date=snapshot.project_snapshot.send "period#{index}_date"
-
-       if mode
-         if mode=='days'
-               label = message('added_over_x_days', :params => mode_param.to_s)
-         elsif mode=='version'
-               label = message('added_since_version', :params => mode_param.to_s)
-         elsif mode=='previous_analysis'
-               if !date.nil?
-                 label = message('added_since_previous_analysis_detailed', :params => date.strftime("%Y %b. %d").to_s)
-               else
-                 label = message('added_since_previous_analysis')
-               end
-         elsif mode=='date'
-               label = message('added_since', :params => date.strftime("%Y %b %d").to_s)
-         end
-         if label
-               selected=(params[:period]==index.to_s ? 'selected' : '')
-               "<option value='#{index}' #{selected}>#{label}</option>"
-         end
-       else
-         nil
-       end
+    return nil if snapshot.nil? || snapshot.project_snapshot.nil?
+    mode=snapshot.project_snapshot.send "period#{index}_mode"
+    mode_param=snapshot.project_snapshot.send "period#{index}_param"
+    date=snapshot.project_snapshot.send "period#{index}_date"
+
+    if mode
+      if mode=='days'
+        label = message('added_over_x_days', :params => mode_param.to_s)
+      elsif mode=='version'
+        label = message('added_since_version', :params => mode_param.to_s)
+      elsif mode=='previous_analysis'
+        if !date.nil?
+          label = message('added_since_previous_analysis_detailed', :params => date.strftime("%Y %b. %d").to_s)
+        else
+          label = message('added_since_previous_analysis')
+        end
+      elsif mode=='previous_version'
+        unless mode_param.nil?
+          label = message('added_since_previous_version_detailed', :params => mode_param.to_s)
+        else
+          label = message('added_since_previous_version')
+        end
+      elsif mode=='date'
+        label = message('added_since', :params => date.strftime("%Y %b %d").to_s)
+      end
+      if label
+        selected=(params[:period]==index.to_s ? 'selected' : '')
+        "<option value='#{index}' #{selected}>#{label}</option>"
+      end
+    else
+      nil
+    end
 
   end
 
   def measure_or_variation_value(measure)
-       if measure
-         @period_index ? measure.variation(@period_index) : measure.value
-       else
-         nil
-       end
+    if measure
+      @period_index ? measure.variation(@period_index) : measure.value
+    else
+      nil
+    end
   end
 
   def switch_to_widget_resource(widget)
index b608b9fae2b9720056b20551ba07f84d608015c2..1d303e9541ab6fe2f7725eb4d2ba54236c7827e3 100644 (file)
@@ -72,6 +72,8 @@ module FiltersHelper
   def period_name(property)
     if property=='previous_analysis'
       message('delta_since_previous_analysis')
+    elsif property=='previous_version'
+      message('delta_since_previous_version')
     elsif property =~ /^[\d]+(\.[\d]+){0,1}$/
       # is integer
       message('delta_over_x_days', :params => property)