aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java93
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ProfileEventsSensor.java100
-rw-r--r--plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/description.html.erb4
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ProfileEventsSensorTest.java167
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/rule/QProfileDecorator.java7
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/rule/QProfileEventsDecorator.java157
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/rule/UsedQProfiles.java34
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java2
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/rule/QProfileEventsDecoratorTest.java213
9 files changed, 480 insertions, 297 deletions
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 6a7499e2332..eb8468ba5b6 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,7 +20,11 @@
package org.sonar.plugins.core;
import com.google.common.collect.ImmutableList;
-import org.sonar.api.*;
+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.checks.NoSonarFilter;
import org.sonar.core.timemachine.Periods;
import org.sonar.plugins.core.batch.IndexProjectPostJob;
@@ -28,19 +32,89 @@ 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.issue.*;
-import org.sonar.plugins.core.issue.notification.*;
+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.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.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.*;
-import org.sonar.plugins.core.timemachine.*;
+import org.sonar.plugins.core.sensors.BranchCoverageDecorator;
+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.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.ProjectLinksSensor;
+import org.sonar.plugins.core.sensors.UnitTestDecorator;
+import org.sonar.plugins.core.sensors.VersionEventsSensor;
+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.web.TestsViewer;
-import org.sonar.plugins.core.widgets.*;
-import org.sonar.plugins.core.widgets.issues.*;
-import org.sonar.plugins.core.widgets.measures.*;
+import org.sonar.plugins.core.widgets.AlertsWidget;
+import org.sonar.plugins.core.widgets.BubbleChartWidget;
+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.DocumentationCommentsWidget;
+import org.sonar.plugins.core.widgets.DuplicationsWidget;
+import org.sonar.plugins.core.widgets.EventsWidget;
+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.ProjectFileCloudWidget;
+import org.sonar.plugins.core.widgets.SizeWidget;
+import org.sonar.plugins.core.widgets.TechnicalDebtPyramidWidget;
+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.WelcomeWidget;
+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.measures.MeasureFilterAsBubbleChartWidget;
+import org.sonar.plugins.core.widgets.measures.MeasureFilterAsCloudWidget;
+import org.sonar.plugins.core.widgets.measures.MeasureFilterAsHistogramWidget;
+import org.sonar.plugins.core.widgets.measures.MeasureFilterAsPieChartWidget;
+import org.sonar.plugins.core.widgets.measures.MeasureFilterListWidget;
+import org.sonar.plugins.core.widgets.measures.MeasureFilterTreemapWidget;
import java.util.List;
@@ -285,7 +359,6 @@ public final class CorePlugin extends SonarPlugin {
NewFalsePositiveNotificationDispatcher.newMetadata(),
// batch
- ProfileEventsSensor.class,
ProjectLinksSensor.class,
UnitTestDecorator.class,
VersionEventsSensor.class,
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ProfileEventsSensor.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ProfileEventsSensor.java
deleted file mode 100644
index c21c6304458..00000000000
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ProfileEventsSensor.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * 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.plugins.core.sensors;
-
-import org.sonar.api.batch.*;
-import org.sonar.api.batch.fs.FileSystem;
-import org.sonar.api.measures.CoreMetrics;
-import org.sonar.api.measures.Measure;
-import org.sonar.api.measures.Metric;
-import org.sonar.api.profiles.RulesProfile;
-import org.sonar.api.resources.Project;
-import org.sonar.batch.rule.RulesProfileWrapper;
-
-import java.util.List;
-
-public class ProfileEventsSensor implements Sensor {
-
- private final RulesProfile profile;
- private final TimeMachine timeMachine;
- private final FileSystem fs;
-
- public ProfileEventsSensor(RulesProfile profile, TimeMachine timeMachine, FileSystem fs) {
- this.profile = profile;
- this.timeMachine = timeMachine;
- this.fs = fs;
- }
-
- public boolean shouldExecuteOnProject(Project project) {
- // Views will define a fake profile
- return profile instanceof RulesProfileWrapper;
- }
-
- public void analyse(Project project, SensorContext context) {
- RulesProfileWrapper profileWrapper = (RulesProfileWrapper) profile;
- for (String languageKey : fs.languages()) {
- RulesProfile realProfile = profileWrapper.getProfileByLanguage(languageKey);
- Measure pastProfileMeasure = getPreviousMeasure(project, CoreMetrics.PROFILE);
- if (pastProfileMeasure == null) {
- // first analysis
- return;
- }
- int pastProfileId = pastProfileMeasure.getIntValue();
- Measure pastProfileVersionMeasure = getPreviousMeasure(project, CoreMetrics.PROFILE_VERSION);
- final int pastProfileVersion;
- // first analysis with versions
- if (pastProfileVersionMeasure == null) {
- pastProfileVersion = 1;
- } else {
- pastProfileVersion = pastProfileVersionMeasure.getIntValue();
- }
- String pastProfile = formatProfileDescription(pastProfileMeasure.getData(), pastProfileVersion);
-
- int currentProfileId = realProfile.getId();
- int currentProfileVersion = realProfile.getVersion();
- String currentProfile = formatProfileDescription(realProfile.getName(), currentProfileVersion);
-
- if ((pastProfileId != currentProfileId) || (pastProfileVersion != currentProfileVersion)) {
- // A different profile is used for this project or new version of same profile
- context.createEvent(project, currentProfile, currentProfile + " is used instead of " + pastProfile, Event.CATEGORY_PROFILE, null);
- }
- }
- }
-
- private static String formatProfileDescription(String name, int version) {
- return name + " version " + version;
- }
-
- private Measure getPreviousMeasure(Project project, Metric metric) {
- TimeMachineQuery query = new TimeMachineQuery(project)
- .setOnlyLastAnalysis(true)
- .setMetrics(metric);
- List<Measure> measures = timeMachine.getMeasures(query);
- if (measures.isEmpty()) {
- return null;
- }
- return measures.get(0);
- }
-
- @Override
- public String toString() {
- return getClass().getSimpleName();
- }
-}
diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/description.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/description.html.erb
index 77931f40854..1d0fcb9c409 100644
--- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/description.html.erb
+++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/description.html.erb
@@ -25,7 +25,7 @@
<td><%= message('widget.description.profiles') -%>:</td>
<td><span id="resource_profile">
<% profiles.each_with_index do |profile, i| %>
- <%= Api::Utils.language_name(profile['language']) -%>: <%= link_to profile['name'], {:controller => '/rules_configuration', :action => 'index', :id => profile['id']}, :id => profile['language'] + '_profile_link' -%></span> (<%= message('widget.description.profile_version_x', :params => profile['version']) -%>)
+ <%= Api::Utils.language_name(profile['language']) -%>: <%= link_to profile['name'], {:controller => '/profiles', :action => 'show', :id => profile['id']}, :id => profile['language'] + '_profile_link' -%></span> (<%= message('widget.description.profile_version_x', :params => profile['version']) -%>)
<% if i < (profiles.size - 1) %>
<br/>
<% end %>
@@ -39,7 +39,7 @@
%>
<tr>
<td><%= message('widget.description.profile') -%>:</td>
- <td><span id="resource_profile"><%= link_to profile_measure.data, {:controller => '/rules_configuration', :action => 'index', :id => profile_measure.value.to_i}, :id => 'profile_link' -%></span> (<%= message('widget.description.profile_version_x', :params => format_measure('profile_version', :default => '1')) -%>)</td>
+ <td><span id="resource_profile"><%= link_to profile_measure.data, {:controller => '/profiles', :action => 'show', :id => profile_measure.value.to_i}, :id => 'profile_link' -%></span> (<%= message('widget.description.profile_version_x', :params => format_measure('profile_version', :default => '1')) -%>)</td>
</tr>
<% end
end %>
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ProfileEventsSensorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ProfileEventsSensorTest.java
deleted file mode 100644
index 41e50402699..00000000000
--- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ProfileEventsSensorTest.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * 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.plugins.core.sensors;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.api.batch.Event;
-import org.sonar.api.batch.SensorContext;
-import org.sonar.api.batch.TimeMachine;
-import org.sonar.api.batch.TimeMachineQuery;
-import org.sonar.api.batch.fs.FileSystem;
-import org.sonar.api.batch.fs.internal.DefaultFileSystem;
-import org.sonar.api.measures.CoreMetrics;
-import org.sonar.api.measures.Measure;
-import org.sonar.api.profiles.RulesProfile;
-import org.sonar.api.resources.Project;
-import org.sonar.batch.rule.RulesProfileWrapper;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Date;
-
-import static org.fest.assertions.Assertions.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.same;
-import static org.mockito.Mockito.*;
-
-public class ProfileEventsSensorTest {
-
- Project project;
- SensorContext context;
- FileSystem fs;
- RulesProfileWrapper wrapper;
- RulesProfile profile;
-
- @Before
- public void prepare() {
- project = mock(Project.class);
- context = mock(SensorContext.class);
-
- fs = new DefaultFileSystem().addLanguages("java");
- profile = mock(RulesProfile.class);
- when(profile.getLanguage()).thenReturn("java");
- wrapper = new RulesProfileWrapper(profile);
- }
-
- @Test
- public void shouldExecuteWhenProfileWithId() {
- when(profile.getId()).thenReturn(123);
- ProfileEventsSensor sensor = new ProfileEventsSensor(wrapper, null, fs);
-
- assertThat(sensor.shouldExecuteOnProject(project)).isTrue();
- verifyZeroInteractions(project);
- }
-
- @Test
- public void shouldNotExecuteIfProfileIsNotWrapper() {
- RulesProfile profile = mock(RulesProfile.class);
- when(profile.getId()).thenReturn(null);
- ProfileEventsSensor sensor = new ProfileEventsSensor(profile, null, fs);
-
- assertThat(sensor.shouldExecuteOnProject(project)).isFalse();
- verifyZeroInteractions(project);
- }
-
- @Test
- public void shouldDoNothingIfNoProfileChange() {
- mockProfileWithVersion(1);
- TimeMachine timeMachine = mockTM(22.0, "Foo", 1.0); // Same profile, same version
- ProfileEventsSensor sensor = new ProfileEventsSensor(wrapper, timeMachine, fs);
-
- sensor.analyse(project, context);
-
- verifyZeroInteractions(context);
- }
-
- @Test
- public void shouldCreateEventIfProfileChange() {
- mockProfileWithVersion(1);
- TimeMachine timeMachine = mockTM(21.0, "Bar", 1.0); // Different profile
- ProfileEventsSensor sensor = new ProfileEventsSensor(wrapper, timeMachine, fs);
-
- sensor.analyse(project, context);
-
- verify(context).createEvent(same(project),
- eq("Foo version 1"),
- eq("Foo version 1 is used instead of Bar version 1"),
- same(Event.CATEGORY_PROFILE), any(Date.class));
- }
-
- @Test
- public void shouldCreateEventIfProfileVersionChange() {
- mockProfileWithVersion(2);
- TimeMachine timeMachine = mockTM(22.0, "Foo", 1.0); // Same profile, different version
- ProfileEventsSensor sensor = new ProfileEventsSensor(wrapper, timeMachine, fs);
-
- sensor.analyse(project, context);
-
- verify(context).createEvent(same(project),
- eq("Foo version 2"),
- eq("Foo version 2 is used instead of Foo version 1"),
- same(Event.CATEGORY_PROFILE), any(Date.class));
- }
-
- @Test
- public void shouldNotCreateEventIfFirstAnalysis() {
- mockProfileWithVersion(2);
- TimeMachine timeMachine = mockTM(null, null);
- ProfileEventsSensor sensor = new ProfileEventsSensor(wrapper, timeMachine, fs);
-
- sensor.analyse(project, context);
-
- verifyZeroInteractions(context);
- }
-
- @Test
- public void shouldCreateEventIfFirstAnalysisWithVersionsAndVersionMoreThan1() {
- mockProfileWithVersion(2);
- TimeMachine timeMachine = mockTM(22.0, "Foo", null);
- ProfileEventsSensor sensor = new ProfileEventsSensor(wrapper, timeMachine, fs);
-
- sensor.analyse(project, context);
-
- verify(context).createEvent(same(project),
- eq("Foo version 2"),
- eq("Foo version 2 is used instead of Foo version 1"),
- same(Event.CATEGORY_PROFILE), any(Date.class));
- }
-
- private void mockProfileWithVersion(int version) {
- when(profile.getId()).thenReturn(22);
- when(profile.getName()).thenReturn("Foo");
- when(profile.getVersion()).thenReturn(version);
- }
-
- private TimeMachine mockTM(double profileId, String profileName, Double versionValue) {
- return mockTM(new Measure(CoreMetrics.PROFILE, profileId, profileName), versionValue == null ? null : new Measure(CoreMetrics.PROFILE_VERSION, versionValue));
- }
-
- private TimeMachine mockTM(Measure result1, Measure result2) {
- TimeMachine timeMachine = mock(TimeMachine.class);
-
- when(timeMachine.getMeasures(any(TimeMachineQuery.class)))
- .thenReturn(result1 == null ? Collections.<Measure>emptyList() : Arrays.asList(result1))
- .thenReturn(result2 == null ? Collections.<Measure>emptyList() : Arrays.asList(result2));
-
- return timeMachine;
- }
-}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/rule/QProfileDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/rule/QProfileDecorator.java
index 7c31d566b4c..b51dcf8a9c0 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/rule/QProfileDecorator.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/rule/QProfileDecorator.java
@@ -21,8 +21,10 @@ package org.sonar.batch.rule;
import org.sonar.api.batch.Decorator;
import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependedUpon;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
import org.sonar.api.resources.Project;
import org.sonar.api.resources.Resource;
import org.sonar.api.resources.ResourceUtils;
@@ -32,6 +34,11 @@ import org.sonar.api.resources.ResourceUtils;
*/
public class QProfileDecorator implements Decorator {
+ @DependedUpon
+ public Metric provides() {
+ return CoreMetrics.PROFILES;
+ }
+
public boolean shouldExecuteOnProject(Project project) {
return project.getModules().size() > 0;
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/rule/QProfileEventsDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/rule/QProfileEventsDecorator.java
new file mode 100644
index 00000000000..d2a79e8275e
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/rule/QProfileEventsDecorator.java
@@ -0,0 +1,157 @@
+/*
+ * 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.rule;
+
+import com.google.common.collect.Maps;
+import org.sonar.api.batch.Decorator;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependsUpon;
+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.resources.Language;
+import org.sonar.api.resources.Languages;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.ResourceUtils;
+import org.sonar.batch.rule.ModuleQProfiles.QProfile;
+import org.sonar.core.qualityprofile.db.QualityProfileDao;
+import org.sonar.core.qualityprofile.db.QualityProfileDto;
+
+import java.util.List;
+import java.util.Map;
+
+public class QProfileEventsDecorator implements Decorator {
+
+ private final TimeMachine timeMachine;
+ private final QualityProfileDao qualityProfileDao;
+ private final Languages languages;
+
+ public QProfileEventsDecorator(TimeMachine timeMachine, QualityProfileDao qualityProfileDao, Languages languages) {
+ this.timeMachine = timeMachine;
+ this.qualityProfileDao = qualityProfileDao;
+ this.languages = languages;
+ }
+
+ @DependsUpon
+ public Metric dependsUpon() {
+ return CoreMetrics.PROFILES;
+ }
+
+ public boolean shouldExecuteOnProject(Project project) {
+ return true;
+ }
+
+ @Override
+ public void decorate(Resource resource, DecoratorContext context) {
+ if (!ResourceUtils.isProject(resource)) {
+ return;
+ }
+
+ // Load current profiles
+ Measure profilesMeasure = context.getMeasure(CoreMetrics.PROFILES);
+ UsedQProfiles currentProfiles = UsedQProfiles.fromJSON(profilesMeasure.getData());
+
+ // Now load previous profiles
+ UsedQProfiles pastProfiles;
+ // First try with new metric
+ Measure pastProfilesMeasure = getPreviousMeasure(resource, CoreMetrics.PROFILES);
+ if (pastProfilesMeasure != null) {
+ pastProfiles = UsedQProfiles.fromJSON(pastProfilesMeasure.getData());
+ } else {
+ // Fallback to old metric
+ Measure pastProfileMeasure = getPreviousMeasure(resource, CoreMetrics.PROFILE);
+ if (pastProfileMeasure == null) {
+ // first analysis
+ return;
+ }
+ int pastProfileId = pastProfileMeasure.getIntValue();
+ String pastProfileName = pastProfileMeasure.getData();
+ QualityProfileDto pastProfile = qualityProfileDao.selectById(pastProfileId);
+ String pastProfileLanguage = "unknow";
+ if (pastProfile != null) {
+ pastProfileLanguage = pastProfile.getLanguage();
+ }
+ Measure pastProfileVersionMeasure = getPreviousMeasure(resource, CoreMetrics.PROFILE_VERSION);
+ final int pastProfileVersion;
+ // first analysis with versions
+ if (pastProfileVersionMeasure == null) {
+ pastProfileVersion = 1;
+ } else {
+ pastProfileVersion = pastProfileVersionMeasure.getIntValue();
+ }
+ pastProfiles = UsedQProfiles.fromProfiles(new ModuleQProfiles.QProfile(pastProfileId, pastProfileName, pastProfileLanguage, pastProfileVersion));
+ }
+
+ // Now create appropriate events
+ Map<Integer, QProfile> pastProfilesById = Maps.newHashMap(pastProfiles.profilesById());
+ for (QProfile profile : currentProfiles.profilesById().values()) {
+ if (pastProfilesById.containsKey(profile.id())) {
+ QProfile pastProfile = pastProfilesById.get(profile.id());
+ if (pastProfile.version() < profile.version()) {
+ // New version of the same QP
+ usedProfile(context, profile);
+ }
+ pastProfilesById.remove(profile.id());
+ } else {
+ usedProfile(context, profile);
+ }
+ }
+ for (QProfile profile : pastProfilesById.values()) {
+ // Following profiles are no more used
+ stopUsedProfile(context, profile);
+ }
+ }
+
+ private void stopUsedProfile(DecoratorContext context, QProfile profile) {
+ Language language = languages.get(profile.language());
+ String languageName = language != null ? language.getName() : profile.language();
+ context.createEvent("Stop using " + format(profile) + " (" + languageName + ")", format(profile) + " no more used for " + languageName, Event.CATEGORY_PROFILE, null);
+ }
+
+ private void usedProfile(DecoratorContext context, QProfile profile) {
+ Language language = languages.get(profile.language());
+ String languageName = language != null ? language.getName() : profile.language();
+ context.createEvent("Use " + format(profile) + " (" + languageName + ")", format(profile) + " used for " + languageName, Event.CATEGORY_PROFILE, null);
+ }
+
+ private String format(QProfile profile) {
+ return profile.name() + " version " + profile.version();
+ }
+
+ private Measure getPreviousMeasure(Resource project, Metric metric) {
+ TimeMachineQuery query = new TimeMachineQuery(project)
+ .setOnlyLastAnalysis(true)
+ .setMetrics(metric);
+ List<Measure> measures = timeMachine.getMeasures(query);
+ if (measures.isEmpty()) {
+ return null;
+ }
+ return measures.get(0);
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/rule/UsedQProfiles.java b/sonar-batch/src/main/java/org/sonar/batch/rule/UsedQProfiles.java
index 13893c64811..1b1115ed18b 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/rule/UsedQProfiles.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/rule/UsedQProfiles.java
@@ -19,6 +19,7 @@
*/
package org.sonar.batch.rule;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
@@ -36,7 +37,7 @@ import java.util.Map;
@Immutable
public class UsedQProfiles {
- private Map<String, Map<Integer, ModuleQProfiles.QProfile>> profilesByLanguage = Maps.newLinkedHashMap();
+ private Map<Integer, ModuleQProfiles.QProfile> profilesById = Maps.newLinkedHashMap();
private UsedQProfiles() {
}
@@ -71,15 +72,13 @@ public class UsedQProfiles {
StringWriter json = new StringWriter();
JsonWriter writer = JsonWriter.of(json);
writer.beginArray();
- for (String languageKey : profilesByLanguage.keySet()) {
- for (ModuleQProfiles.QProfile qProfile : profilesByLanguage.get(languageKey).values()) {
- writer.beginObject()
- .prop("id", qProfile.id())
- .prop("name", qProfile.name())
- .prop("version", qProfile.version())
- .prop("language", qProfile.language())
- .endObject();
- }
+ for (ModuleQProfiles.QProfile qProfile : profilesById.values()) {
+ writer.beginObject()
+ .prop("id", qProfile.id())
+ .prop("name", qProfile.name())
+ .prop("version", qProfile.version())
+ .prop("language", qProfile.language())
+ .endObject();
}
writer.endArray();
writer.close();
@@ -91,14 +90,11 @@ public class UsedQProfiles {
}
private void add(ModuleQProfiles.QProfile profile) {
- if (!profilesByLanguage.containsKey(profile.language())) {
- profilesByLanguage.put(profile.language(), Maps.<Integer, ModuleQProfiles.QProfile>newLinkedHashMap());
- }
- QProfile alreadyAdded = profilesByLanguage.get(profile.language()).get(profile.id());
+ QProfile alreadyAdded = profilesById.get(profile.id());
if (alreadyAdded == null
// Keep only latest version
|| profile.version() > alreadyAdded.version()) {
- profilesByLanguage.get(profile.language()).put(profile.id(), profile);
+ profilesById.put(profile.id(), profile);
}
}
@@ -110,10 +106,12 @@ public class UsedQProfiles {
}
private UsedQProfiles mergeInPlace(UsedQProfiles other) {
- for (Map<Integer, QProfile> byIds : other.profilesByLanguage.values()) {
- this.addAll(byIds.values());
- }
+ this.addAll(other.profilesById.values());
return this;
}
+ public Map<Integer, ModuleQProfiles.QProfile> profilesById() {
+ return ImmutableMap.copyOf(profilesById);
+ }
+
}
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 c2fb81ba982..fc5da97d56d 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
@@ -60,6 +60,7 @@ import org.sonar.batch.qualitygate.QualityGateVerifier;
import org.sonar.batch.rule.ActiveRulesProvider;
import org.sonar.batch.rule.ModuleQProfiles;
import org.sonar.batch.rule.QProfileDecorator;
+import org.sonar.batch.rule.QProfileEventsDecorator;
import org.sonar.batch.rule.QProfileSensor;
import org.sonar.batch.rule.QProfileVerifier;
import org.sonar.batch.rule.RulesProfileProvider;
@@ -157,6 +158,7 @@ public class ModuleScanContainer extends ComponentContainer {
new RulesProfileProvider(),
QProfileSensor.class,
QProfileDecorator.class,
+ QProfileEventsDecorator.class,
CheckFactory.class,
// report
diff --git a/sonar-batch/src/test/java/org/sonar/batch/rule/QProfileEventsDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/rule/QProfileEventsDecoratorTest.java
new file mode 100644
index 00000000000..3fe9497050c
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/rule/QProfileEventsDecoratorTest.java
@@ -0,0 +1,213 @@
+/*
+ * 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.rule;
+
+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.resources.Java;
+import org.sonar.api.resources.Languages;
+import org.sonar.api.resources.Project;
+import org.sonar.core.qualityprofile.db.QualityProfileDao;
+import org.sonar.core.qualityprofile.db.QualityProfileDto;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class QProfileEventsDecoratorTest {
+
+ Project project = new Project("myProject");
+ DecoratorContext decoratorContext = mock(DecoratorContext.class);
+ TimeMachine timeMachine = mock(TimeMachine.class);
+ private Languages languages = mock(Languages.class);
+ private QualityProfileDao qualityProfileDao = mock(QualityProfileDao.class);
+ QProfileEventsDecorator decorator = new QProfileEventsDecorator(timeMachine, qualityProfileDao, languages);
+
+ @Test
+ public void shouldExecuteOnProjects() {
+ assertThat(decorator.shouldExecuteOnProject(project)).isTrue();
+ }
+
+ @Test
+ public void shouldDoNothingIfNoProfileChange() {
+ Measure previousMeasure = new Measure(CoreMetrics.PROFILES, "[{\"id\":2,\"name\":\"Java Two\",\"version\":20,\"language\":\"java\"}]");
+ Measure newMeasure = new Measure(CoreMetrics.PROFILES, "[{\"id\":2,\"name\":\"Java Two\",\"version\":20,\"language\":\"java\"}]");
+
+ when(timeMachine.getMeasures(any(TimeMachineQuery.class)))
+ .thenReturn(Arrays.asList(previousMeasure));
+ when(decoratorContext.getMeasure(CoreMetrics.PROFILES)).thenReturn(newMeasure);
+
+ decorator.decorate(project, decoratorContext);
+
+ verify(decoratorContext, never()).createEvent(anyString(), anyString(), anyString(), any(Date.class));
+ }
+
+ @Test
+ public void shouldDoNothingIfNoProfileChange_fallbackOldProfileMeasure() {
+ mockTMWithDeprecatedProfileMeasures(2, "Java Two", 20);
+ when(qualityProfileDao.selectById(20)).thenReturn(new QualityProfileDto().setLanguage("java"));
+ Measure newMeasure = new Measure(CoreMetrics.PROFILES, "[{\"id\":2,\"name\":\"Java Two\",\"version\":20,\"language\":\"java\"}]");
+
+ when(decoratorContext.getMeasure(CoreMetrics.PROFILES)).thenReturn(newMeasure);
+
+ when(languages.get("java")).thenReturn(Java.INSTANCE);
+
+ decorator.decorate(project, decoratorContext);
+
+ verify(decoratorContext, never()).createEvent(anyString(), anyString(), anyString(), any(Date.class));
+ }
+
+ @Test
+ public void shouldCreateEventIfProfileChange() {
+ Measure previousMeasure = new Measure(CoreMetrics.PROFILES, "[{\"id\":2,\"name\":\"Java Two\",\"version\":20,\"language\":\"java\"}]");
+ // Different profile
+ Measure newMeasure = new Measure(CoreMetrics.PROFILES, "[{\"id\":3,\"name\":\"Java Other\",\"version\":1,\"language\":\"java\"}]");
+
+ when(timeMachine.getMeasures(any(TimeMachineQuery.class)))
+ .thenReturn(Arrays.asList(previousMeasure));
+ when(decoratorContext.getMeasure(CoreMetrics.PROFILES)).thenReturn(newMeasure);
+
+ when(languages.get("java")).thenReturn(Java.INSTANCE);
+
+ decorator.decorate(project, decoratorContext);
+
+ verify(decoratorContext).createEvent(
+ eq("Use Java Other version 1 (Java)"),
+ eq("Java Other version 1 used for Java"),
+ same(Event.CATEGORY_PROFILE), any(Date.class));
+ }
+
+ @Test
+ public void shouldCreateEventIfProfileChange_fallbackOldProfileMeasure() {
+ mockTMWithDeprecatedProfileMeasures(2, "Java Two", 20);
+ when(qualityProfileDao.selectById(20)).thenReturn(new QualityProfileDto().setLanguage("java"));
+ // Different profile
+ Measure newMeasure = new Measure(CoreMetrics.PROFILES, "[{\"id\":3,\"name\":\"Java Other\",\"version\":1,\"language\":\"java\"}]");
+
+ when(decoratorContext.getMeasure(CoreMetrics.PROFILES)).thenReturn(newMeasure);
+
+ when(languages.get("java")).thenReturn(Java.INSTANCE);
+
+ decorator.decorate(project, decoratorContext);
+
+ verify(decoratorContext).createEvent(
+ eq("Use Java Other version 1 (Java)"),
+ eq("Java Other version 1 used for Java"),
+ same(Event.CATEGORY_PROFILE), any(Date.class));
+ }
+
+ @Test
+ public void shouldCreateEventIfProfileVersionChange() {
+ Measure previousMeasure = new Measure(CoreMetrics.PROFILES, "[{\"id\":2,\"name\":\"Java Two\",\"version\":20,\"language\":\"java\"}]");
+ // Same profile, different version
+ Measure newMeasure = new Measure(CoreMetrics.PROFILES, "[{\"id\":2,\"name\":\"Java Two\",\"version\":21,\"language\":\"java\"}]");
+
+ when(timeMachine.getMeasures(any(TimeMachineQuery.class)))
+ .thenReturn(Arrays.asList(previousMeasure));
+ when(decoratorContext.getMeasure(CoreMetrics.PROFILES)).thenReturn(newMeasure);
+
+ when(languages.get("java")).thenReturn(Java.INSTANCE);
+
+ decorator.decorate(project, decoratorContext);
+
+ verify(decoratorContext).createEvent(
+ eq("Use Java Two version 21 (Java)"),
+ eq("Java Two version 21 used for Java"),
+ same(Event.CATEGORY_PROFILE), any(Date.class));
+ }
+
+ @Test
+ public void shouldCreateEventIfProfileVersionChange_fallbackOldProfileMeasure() {
+ mockTMWithDeprecatedProfileMeasures(2, "Java Two", 20);
+ when(qualityProfileDao.selectById(20)).thenReturn(new QualityProfileDto().setLanguage("java"));
+ // Same profile, different version
+ Measure newMeasure = new Measure(CoreMetrics.PROFILES, "[{\"id\":2,\"name\":\"Java Two\",\"version\":21,\"language\":\"java\"}]");
+
+ when(decoratorContext.getMeasure(CoreMetrics.PROFILES)).thenReturn(newMeasure);
+
+ when(languages.get("java")).thenReturn(Java.INSTANCE);
+
+ decorator.decorate(project, decoratorContext);
+
+ verify(decoratorContext).createEvent(
+ eq("Use Java Two version 21 (Java)"),
+ eq("Java Two version 21 used for Java"),
+ same(Event.CATEGORY_PROFILE), any(Date.class));
+ }
+
+ @Test
+ public void shouldCreateEventIfProfileVersionChange_fallbackOldProfileMeasure_noVersion() {
+ mockTMWithDeprecatedProfileMeasures(2, "Java Two", null);
+ when(qualityProfileDao.selectById(20)).thenReturn(new QualityProfileDto().setLanguage("java"));
+ // Same profile, different version
+ Measure newMeasure = new Measure(CoreMetrics.PROFILES, "[{\"id\":2,\"name\":\"Java Two\",\"version\":21,\"language\":\"java\"}]");
+
+ when(decoratorContext.getMeasure(CoreMetrics.PROFILES)).thenReturn(newMeasure);
+
+ when(languages.get("java")).thenReturn(Java.INSTANCE);
+
+ decorator.decorate(project, decoratorContext);
+
+ verify(decoratorContext).createEvent(
+ eq("Use Java Two version 21 (Java)"),
+ eq("Java Two version 21 used for Java"),
+ same(Event.CATEGORY_PROFILE), any(Date.class));
+ }
+
+ @Test
+ public void shouldNotCreateEventIfFirstAnalysis() {
+ Measure newMeasure = new Measure(CoreMetrics.PROFILES, "[{\"id\":2,\"name\":\"Java Two\",\"version\":21,\"language\":\"java\"}]");
+
+ when(decoratorContext.getMeasure(CoreMetrics.PROFILES)).thenReturn(newMeasure);
+
+ when(languages.get("java")).thenReturn(Java.INSTANCE);
+
+ decorator.decorate(project, decoratorContext);
+
+ verify(decoratorContext, never()).createEvent(anyString(), anyString(), anyString(), any(Date.class));
+ }
+
+ private void mockTMWithDeprecatedProfileMeasures(double profileId, String profileName, Integer versionValue) {
+ mockTM(new Measure(CoreMetrics.PROFILE, profileId, profileName), versionValue == null ? null : new Measure(CoreMetrics.PROFILE_VERSION, Double.valueOf(versionValue)));
+ }
+
+ private void mockTM(Measure result1, Measure result2) {
+ when(timeMachine.getMeasures(any(TimeMachineQuery.class)))
+ .thenReturn(Collections.<Measure>emptyList())
+ .thenReturn(result1 == null ? Collections.<Measure>emptyList() : Arrays.asList(result1))
+ .thenReturn(result2 == null ? Collections.<Measure>emptyList() : Arrays.asList(result2));
+
+ }
+}