aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJulien Lancelot <julien.lancelot@sonarsource.com>2014-05-07 15:05:05 +0200
committerJulien Lancelot <julien.lancelot@sonarsource.com>2014-05-07 15:05:05 +0200
commit8f936044deed48e659bffaa9e6587fe22143a192 (patch)
tree1b055d6807c8323cbcd0dee50f29dc69fa0a60e7
parent12077a645e769be0d4387f30a1e5f5c1966dc08e (diff)
downloadsonarqube-8f936044deed48e659bffaa9e6587fe22143a192.tar.gz
sonarqube-8f936044deed48e659bffaa9e6587fe22143a192.zip
SONAR-5130 Show distribution of LOC and TechDebt by language
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/SizeWidget.java2
-rw-r--r--plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/size.html.erb35
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/language/LanguageDistributionDecorator.java76
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java4
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/language/LanguageDistributionDecoratorTest.java134
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties2
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java15
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/resources/CoreMetricsTest.java6
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/helpers/application_helper.rb2
9 files changed, 271 insertions, 5 deletions
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/SizeWidget.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/SizeWidget.java
index b3330c7833f..0df49408671 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/SizeWidget.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/SizeWidget.java
@@ -22,6 +22,6 @@ package org.sonar.plugins.core.widgets;
public class SizeWidget extends CoreWidget {
public SizeWidget() {
- super("size", "Size metrics", "/org/sonar/plugins/core/widgets/size.html.erb");
+ super("size", "Size metrics", "/Users/julienlancelot/Dev/Sources/sonar/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/size.html.erb");
}
}
diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/size.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/size.html.erb
index e3f741587f4..f30117a5bde 100644
--- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/size.html.erb
+++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/size.html.erb
@@ -1,6 +1,7 @@
<%
lines=measure('lines')
ncloc=measure('ncloc')
+ ncloc_language_distribution=measure('ncloc_language_distribution')
classes=measure('classes')
files=measure('files')
functions=measure('functions')
@@ -8,6 +9,7 @@
if measure('lines') || ncloc
files=measure('files')
statements=measure('statements')
+ languages = Api::Utils.java_facade.getLanguages()
%>
<table width="100%">
<tr>
@@ -15,7 +17,16 @@
<div class="dashbox">
<% if ncloc %>
- <h3><%= message('widget.size.lines_of_code') -%></h3>
+ <%
+ ncloc_language_dist_hash = Hash[*(ncloc_language_distribution.data.split(';').map { |elt| elt.split('=') }.flatten)] if ncloc_language_distribution
+ if ncloc_language_dist_hash && ncloc_language_dist_hash.size == 1
+ language_key = ncloc_language_dist_hash.first()[0].to_s
+ language = languages.find { |l| l.getKey()==language_key }
+ %>
+ <h3><%= message('widget.size.lines_of_code_with_language', :params => (language ? language.getName() : language_key)) -%></h3>
+ <% else %>
+ <h3><%= message('widget.size.lines_of_code') -%></h3>
+ <% end %>
<p>
<span class="big"><%= format_measure(ncloc, :suffix => '', :url => url_for_drilldown(ncloc)) -%></span>
<%= dashboard_configuration.selected_period? ? format_variation(ncloc) : trend_icon(ncloc) -%>
@@ -45,6 +56,28 @@
<% if projects %>
<p><%= format_measure(projects, :suffix => message('widget.size.projects.suffix')) -%> <%= dashboard_configuration.selected_period? ? format_variation(projects) : trend_icon(projects) -%></p>
<% end %>
+
+ <% if ncloc_language_dist_hash && ncloc_language_dist_hash.size > 1 %>
+ <table class="clear width100">
+ <%
+ max = ncloc_language_dist_hash.max_by{|_k,v| v.to_i}[1].to_i
+ # Sort lines language distribution by reverse number of lines
+ ncloc_language_dist_hash.sort {|v1,v2| v2[1].to_i <=> v1[1].to_i }.each do |language_key, language_ncloc|
+ tooltip = ncloc.format_numeric_value(language_ncloc) + message('widget.size.lines_of_code.suffix')
+ %>
+ <tr>
+ <td>
+ <% language = languages.find { |l| l.getKey()==language_key.to_s } -%>
+ <%= language ? language.getName() : language_key -%>
+ </td>
+ <td>&nbsp;</td>
+ <td align="left" style="padding-bottom:2px; padding-top:2px;">
+ <%= barchart(:width => 70, :percent => (100 * language_ncloc.to_i / max).to_i, :tooltip => tooltip)%>
+ </td>
+ </tr>
+ <% end %>
+ </table>
+ <% end %>
</div>
</td>
<td width="10"> </td>
diff --git a/sonar-batch/src/main/java/org/sonar/batch/language/LanguageDistributionDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/language/LanguageDistributionDecorator.java
new file mode 100644
index 00000000000..ca77b7c239c
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/language/LanguageDistributionDecorator.java
@@ -0,0 +1,76 @@
+/*
+ * 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.language;
+
+import com.google.common.collect.ImmutableList;
+import org.sonar.api.batch.Decorator;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependedUpon;
+import org.sonar.api.batch.DependsUpon;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.CountDistributionBuilder;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Language;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.ResourceUtils;
+
+import java.util.List;
+
+public class LanguageDistributionDecorator implements Decorator {
+
+ public boolean shouldExecuteOnProject(Project project) {
+ return true;
+ }
+
+ @DependsUpon
+ public List<Metric> dependsUponMetrics() {
+ return ImmutableList.of(CoreMetrics.LINES);
+ }
+
+ @DependedUpon
+ public List<Metric> generatesMetrics() {
+ return ImmutableList.of(
+ CoreMetrics.NCLOC_LANGUAGE_DISTRIBUTION
+ );
+ }
+
+ public void decorate(Resource resource, DecoratorContext context) {
+ CountDistributionBuilder nclocDistribution = new CountDistributionBuilder(CoreMetrics.NCLOC_LANGUAGE_DISTRIBUTION);
+ if (ResourceUtils.isFile(resource)) {
+ Language language = resource.getLanguage();
+ Measure ncloc = context.getMeasure(CoreMetrics.NCLOC);
+ if (language != null && ncloc != null) {
+ nclocDistribution.add(language.getKey(), ncloc.getIntValue());
+ }
+ } else {
+ for (Measure measure : context.getChildrenMeasures(CoreMetrics.NCLOC_LANGUAGE_DISTRIBUTION)) {
+ nclocDistribution.add(measure);
+ }
+ }
+ Measure measure = nclocDistribution.build(false);
+ if (measure != null) {
+ context.saveMeasure(measure);
+ }
+ }
+
+}
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 f4769d38316..1db4a9a5054 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
@@ -46,6 +46,7 @@ import org.sonar.batch.issue.ignore.pattern.IssueExclusionPatternInitializer;
import org.sonar.batch.issue.ignore.pattern.IssueInclusionPatternInitializer;
import org.sonar.batch.issue.ignore.scanner.IssueExclusionsLoader;
import org.sonar.batch.issue.ignore.scanner.IssueExclusionsRegexpScanner;
+import org.sonar.batch.language.LanguageDistributionDecorator;
import org.sonar.batch.phases.PhaseExecutor;
import org.sonar.batch.phases.PhasesTimeProfiler;
import org.sonar.batch.qualitygate.GenerateQualityGateEvents;
@@ -150,6 +151,9 @@ public class ModuleScanContainer extends ComponentContainer {
EnforceIssuesFilter.class,
IgnoreIssuesFilter.class,
+ // language
+ LanguageDistributionDecorator.class,
+
ScanPerspectives.class);
}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/language/LanguageDistributionDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/language/LanguageDistributionDecoratorTest.java
new file mode 100644
index 00000000000..fadb7e82166
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/language/LanguageDistributionDecoratorTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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.language;
+
+import com.google.common.collect.ImmutableMap;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.resources.Language;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.Scopes;
+import org.sonar.api.utils.KeyValueFormat;
+
+import java.util.Collections;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class LanguageDistributionDecoratorTest {
+
+ @Mock
+ DecoratorContext context;
+
+ @Mock
+ Resource resource;
+
+ @Captor
+ ArgumentCaptor<Measure> measureCaptor;
+
+ LanguageDistributionDecorator decorator;
+
+ @Before
+ public void setUp() throws Exception {
+ decorator = new LanguageDistributionDecorator();
+ }
+
+ @Test
+ public void depended_upon_metric() {
+ assertThat(decorator.generatesMetrics()).hasSize(1);
+ }
+
+ @Test
+ public void depens_upon_metric() {
+ assertThat(decorator.dependsUponMetrics()).hasSize(1);
+ }
+
+ @Test
+ public void save_ncloc_language_distribution_on_file() {
+ Language language = mock(Language.class);
+ when(language.getKey()).thenReturn("xoo");
+
+ when(resource.getScope()).thenReturn(Scopes.FILE);
+ when(resource.getLanguage()).thenReturn(language);
+ when(context.getMeasure(CoreMetrics.NCLOC)).thenReturn(new Measure(CoreMetrics.NCLOC, 200.0));
+
+ decorator.decorate(resource, context);
+
+ verify(context).saveMeasure(measureCaptor.capture());
+
+ Measure result = measureCaptor.getValue();
+ assertThat(result.getMetric()).isEqualTo(CoreMetrics.NCLOC_LANGUAGE_DISTRIBUTION);
+ assertThat(result.getData()).isEqualTo("xoo=200");
+ }
+
+ @Test
+ public void save_ncloc_language_distribution_on_project() {
+ when(resource.getScope()).thenReturn(Scopes.PROJECT);
+ when(context.getChildrenMeasures(CoreMetrics.NCLOC_LANGUAGE_DISTRIBUTION)).thenReturn(newArrayList(
+ new Measure(CoreMetrics.NCLOC_LANGUAGE_DISTRIBUTION, KeyValueFormat.format(ImmutableMap.of("java", 20))),
+ new Measure(CoreMetrics.NCLOC_LANGUAGE_DISTRIBUTION, KeyValueFormat.format(ImmutableMap.of("xoo", 150))),
+ new Measure(CoreMetrics.NCLOC_LANGUAGE_DISTRIBUTION, KeyValueFormat.format(ImmutableMap.of("xoo", 50)))
+ ));
+
+ decorator.decorate(resource, context);
+
+ verify(context).saveMeasure(measureCaptor.capture());
+
+ Measure result = measureCaptor.getValue();
+ assertThat(result.getMetric()).isEqualTo(CoreMetrics.NCLOC_LANGUAGE_DISTRIBUTION);
+ assertThat(result.getData()).isEqualTo("java=20;xoo=200");
+ }
+
+ @Test
+ public void not_save_language_distribution_on_file_if_no_measure() {
+ Language language = mock(Language.class);
+ when(language.getKey()).thenReturn("xoo");
+
+ when(resource.getScope()).thenReturn(Scopes.FILE);
+ when(resource.getLanguage()).thenReturn(language);
+ when(context.getMeasure(CoreMetrics.NCLOC)).thenReturn(null);
+
+ decorator.decorate(resource, context);
+
+ verify(context, never()).saveMeasure(measureCaptor.capture());
+ }
+
+ @Test
+ public void not_save_language_distribution_on_project_if_no_chidren_measures() {
+ when(resource.getScope()).thenReturn(Scopes.PROJECT);
+ when(context.getChildrenMeasures(CoreMetrics.NCLOC_LANGUAGE_DISTRIBUTION)).thenReturn(Collections.<Measure>emptyList());
+
+ decorator.decorate(resource, context);
+
+ verify(context, never()).saveMeasure(measureCaptor.capture());
+ }
+
+}
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index ffc8bc14b22..c8ff38228ea 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -1077,6 +1077,8 @@ widget.rules.removed=Removed:
widget.size.name=Size Metrics
widget.size.description=Reports general metrics on the size of the project.
widget.size.lines_of_code=Lines of code
+widget.size.lines_of_code_with_language=Lines of code ({0})
+widget.size.lines_of_code.suffix=\ lines of code
widget.size.lines=Lines
widget.size.generated.suffix=\ generated
widget.size.lines.suffix=\ lines
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java
index 852678a40af..1ac74b83a42 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java
@@ -85,6 +85,21 @@ public final class CoreMetrics {
.setFormula(new SumChildValuesFormula(false))
.create();
+ /**
+ * @since 4.4
+ */
+ public static final String NCLOC_LANGUAGE_DISTRIBUTION_KEY = "ncloc_language_distribution";
+
+ /**
+ * @since 4.4
+ */
+ public static final Metric NCLOC_LANGUAGE_DISTRIBUTION = new Metric.Builder(NCLOC_LANGUAGE_DISTRIBUTION_KEY, "Lines of code per language", Metric.ValueType.DATA)
+ .setDescription("Non Commenting Lines of Code Distributed By Language")
+ .setDirection(Metric.DIRECTION_WORST)
+ .setQualitative(false)
+ .setDomain(DOMAIN_SIZE)
+ .create();
+
public static final String GENERATED_NCLOC_KEY = "generated_ncloc";
public static final Metric GENERATED_NCLOC = new Metric.Builder(GENERATED_NCLOC_KEY, "Generated lines of code", Metric.ValueType.INT)
.setDescription("Generated non Commenting Lines of Code")
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/resources/CoreMetricsTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/resources/CoreMetricsTest.java
index 1c65ebfb51f..086005be0f8 100644
--- a/sonar-plugin-api/src/test/java/org/sonar/api/resources/CoreMetricsTest.java
+++ b/sonar-plugin-api/src/test/java/org/sonar/api/resources/CoreMetricsTest.java
@@ -28,10 +28,12 @@ import java.util.List;
import static org.fest.assertions.Assertions.assertThat;
public class CoreMetricsTest {
+
@Test
- public void shouldReadMetricsFromClassReflection() {
+ public void read_metrics_from_class_reflection() {
List<Metric> metrics = CoreMetrics.getMetrics();
- assertThat(metrics).hasSize(151);
+ assertThat(metrics).hasSize(152);
assertThat(metrics).contains(CoreMetrics.NCLOC, CoreMetrics.DIRECTORIES);
}
+
}
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/helpers/application_helper.rb b/sonar-server/src/main/webapp/WEB-INF/app/helpers/application_helper.rb
index 08227825e4b..4b1a24aa00d 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/helpers/application_helper.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/helpers/application_helper.rb
@@ -391,7 +391,7 @@ module ApplicationHelper
end
align=(percent<0 ? 'float: right;' : nil)
- "<div class='barchart' style='width: #{width}px'><div style='width: #{percent.abs}%;background-color:#{color};#{align}'></div></div>"
+ "<div class='barchart' style='width: #{width}px' title='#{options[:tooltip]}'><div style='width: #{percent.abs}%;background-color:#{color};#{align}'></div></div>"
end
def chart(parameters, options={})