From af58e35a58579484d6521bd3e675c90e43c18623 Mon Sep 17 00:00:00 2001 From: Fabrice Bellingard Date: Wed, 26 Oct 2011 19:18:08 +0200 Subject: [PATCH] SONAR-2733 Display duplicated blocks by group in the resource viewer First shot, needs improvements. --- .../DuplicationsViewerDefinition.java | 41 --------- .../org/sonar/plugins/core/CorePlugin.java | 51 +++++++++-- .../core/widgets/hotspot_metric.html.erb | 4 +- .../resources/org/sonar/l10n/core.properties | 16 +++- .../org/sonar/server/ui/DefaultPages.java | 21 ++++- .../app/controllers/resource_controller.rb | 36 +++++++- .../app/views/resource/_duplications.html.erb | 90 +++++++++++++++++++ .../_duplications_source_snippet.html.erb | 2 + .../resource/_header_duplications.html.erb | 22 +++++ .../WEB-INF/app/views/resource/index.html.erb | 7 ++ .../main/webapp/javascripts/application.js | 11 +++ .../src/main/webapp/stylesheets/style.css | 40 ++++++++- 12 files changed, 286 insertions(+), 55 deletions(-) delete mode 100644 plugins/sonar-core-gwt/src/main/java/org/sonar/plugins/core/duplicationsviewer/DuplicationsViewerDefinition.java create mode 100644 sonar-server/src/main/webapp/WEB-INF/app/views/resource/_duplications.html.erb create mode 100644 sonar-server/src/main/webapp/WEB-INF/app/views/resource/_duplications_source_snippet.html.erb create mode 100644 sonar-server/src/main/webapp/WEB-INF/app/views/resource/_header_duplications.html.erb diff --git a/plugins/sonar-core-gwt/src/main/java/org/sonar/plugins/core/duplicationsviewer/DuplicationsViewerDefinition.java b/plugins/sonar-core-gwt/src/main/java/org/sonar/plugins/core/duplicationsviewer/DuplicationsViewerDefinition.java deleted file mode 100644 index 17c7ffa95b2..00000000000 --- a/plugins/sonar-core-gwt/src/main/java/org/sonar/plugins/core/duplicationsviewer/DuplicationsViewerDefinition.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Sonar, open source software quality management tool. - * Copyright (C) 2008-2011 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.plugins.core.duplicationsviewer; - -import org.sonar.api.measures.CoreMetrics; -import org.sonar.api.resources.Resource; -import org.sonar.api.web.*; -import org.sonar.plugins.core.duplicationsviewer.client.DuplicationsViewer; - -@ResourceQualifier({Resource.QUALIFIER_CLASS,Resource.QUALIFIER_FILE}) -@NavigationSection(NavigationSection.RESOURCE_TAB) -@DefaultTab(metrics={CoreMetrics.DUPLICATED_LINES_KEY, CoreMetrics.DUPLICATED_BLOCKS_KEY, CoreMetrics.DUPLICATED_FILES_KEY, CoreMetrics.DUPLICATED_LINES_DENSITY_KEY}) -@UserRole(UserRole.CODEVIEWER) -public class DuplicationsViewerDefinition extends GwtPage { - - public String getTitle() { - return "Duplications"; - } - - public String getGwtId() { - return DuplicationsViewer.GWT_ID; - } - -} 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 6d71e3a7971..e0aba2cc264 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 @@ -19,7 +19,8 @@ */ package org.sonar.plugins.core; -import com.google.common.collect.Lists; +import java.util.List; + import org.sonar.api.CoreProperties; import org.sonar.api.Properties; import org.sonar.api.Property; @@ -33,16 +34,53 @@ 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.duplicationsviewer.DuplicationsViewerDefinition; import org.sonar.plugins.core.hotspots.Hotspots; import org.sonar.plugins.core.metrics.UserManagedMetrics; import org.sonar.plugins.core.security.ApplyProjectRolesDecorator; -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.CloseReviewsDecorator; +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.LineCoverageDecorator; +import org.sonar.plugins.core.sensors.ManualMeasureDecorator; +import org.sonar.plugins.core.sensors.ProfileEventsSensor; +import org.sonar.plugins.core.sensors.ProfileSensor; +import org.sonar.plugins.core.sensors.ProjectLinksSensor; +import org.sonar.plugins.core.sensors.UnitTestDecorator; +import org.sonar.plugins.core.sensors.VersionEventsSensor; +import org.sonar.plugins.core.sensors.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.widgets.*; +import org.sonar.plugins.core.timemachine.NewCoverageAggregator; +import org.sonar.plugins.core.timemachine.NewCoverageFileAnalyzer; +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.widgets.AlertsWidget; +import org.sonar.plugins.core.widgets.CodeCoverageWidget; +import org.sonar.plugins.core.widgets.CommentsDuplicationsWidget; +import org.sonar.plugins.core.widgets.ComplexityWidget; +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.HotspotMetricWidget; +import org.sonar.plugins.core.widgets.HotspotMostViolatedResourcesWidget; +import org.sonar.plugins.core.widgets.HotspotMostViolatedRulesWidget; +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 java.util.List; +import com.google.common.collect.Lists; @Properties({ @Property( @@ -228,7 +266,6 @@ public class CorePlugin extends SonarPlugin { extensions.add(UserManagedMetrics.class); // pages - extensions.add(DuplicationsViewerDefinition.class); extensions.add(TestsViewerDefinition.class); extensions.add(Hotspots.class); diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspot_metric.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspot_metric.html.erb index 94769ac1b4e..250b7fc553d 100644 --- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspot_metric.html.erb +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspot_metric.html.erb @@ -58,9 +58,9 @@ <% if metric.direction > 0 - metric_max_value = snapshots.last.measure(metric).value + metric_max_value = measures.last.value else - metric_max_value = snapshots.first.measure(metric).value + metric_max_value = measures.first.value end measures.each do |measure| resource = snapshots_by_id[measure.snapshot_id].resource diff --git a/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties b/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties index 54ae0a501c8..33f4ff2e83e 100644 --- a/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties +++ b/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties @@ -273,6 +273,7 @@ coverage.page=Coverage default_dashboards.page=Default Dashboards default_filters.page=Default Filters dependencies.page=Dependencies +duplications.page=Duplications email_configuration.page=Email Settings event_categories.page=Event Categories filters.page=Filters @@ -301,7 +302,6 @@ update_center.page=Update Center # GWT pages org.sonar.plugins.core.hotspots.GwtHotspots.page=Hotspots -org.sonar.plugins.core.duplicationsviewer.DuplicationsViewer.page=Duplications org.sonar.plugins.design.ui.page.DesignPage.page=Design org.sonar.plugins.design.ui.libraries.LibrariesPage.page=Libraries org.sonar.plugins.design.ui.dependencies.DependenciesTab.page=Dependencies @@ -591,6 +591,20 @@ violations_drilldown.any_rule=Any rule violations_drilldown.no_violations=No violations +#------------------------------------------------------------------------------ +# +# DUPLICATION TAB +# +#------------------------------------------------------------------------------ + +duplications.no_duplicated_block=No duplicated block. +duplications.blocks=Blocks +duplications.number_of_lines=Nb Lines +duplications.from_line=From line +duplications.file=File +duplications.details=Details + + #------------------------------------------------------------------------------ # # MANUAL MEASURES diff --git a/sonar-server/src/main/java/org/sonar/server/ui/DefaultPages.java b/sonar-server/src/main/java/org/sonar/server/ui/DefaultPages.java index ed976159312..a68cbc86c19 100644 --- a/sonar-server/src/main/java/org/sonar/server/ui/DefaultPages.java +++ b/sonar-server/src/main/java/org/sonar/server/ui/DefaultPages.java @@ -28,7 +28,7 @@ import org.sonar.api.web.*; */ public final class DefaultPages { - private static final View[] PAGES = { new SourceTab(), new CoverageTab(), new ViolationsTab() }; + private static final View[] PAGES = { new SourceTab(), new CoverageTab(), new ViolationsTab(), new DuplicationsTab() }; private DefaultPages() { } @@ -103,4 +103,23 @@ public final class DefaultPages { return "Violations"; } } + + @NavigationSection(NavigationSection.RESOURCE_TAB) + @DefaultTab(metrics = {CoreMetrics.DUPLICATED_LINES_KEY, CoreMetrics.DUPLICATED_BLOCKS_KEY, CoreMetrics.DUPLICATED_FILES_KEY, CoreMetrics.DUPLICATED_LINES_DENSITY_KEY}) + @ResourceQualifier({Qualifiers.FILE, Qualifiers.CLASS}) + @UserRole(UserRole.CODEVIEWER) + private static final class DuplicationsTab implements RubyRailsPage { + public String getTemplate() { + //not used, hardcoded in BrowseController + return "browse/index"; + } + + public String getId() { + return "duplications"; + } + + public String getTitle() { + return "Duplications"; + } + } } diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/resource_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/resource_controller.rb index 2ca2eadfa51..61c30b9aee9 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/resource_controller.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/resource_controller.rb @@ -17,10 +17,14 @@ # License along with Sonar; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 # +require "rexml/document" + class ResourceController < ApplicationController + include REXML + SECTION=Navigation::SECTION_RESOURCE - helper :dashboard + helper :dashboard, SourceHelper def index @resource = Project.by_key(params[:id]) @@ -38,6 +42,8 @@ class ResourceController < ApplicationController render_coverage() elsif (@extension.getId()=='source') render_source() + elsif (@extension.getId()=='duplications') + render_duplications() else render_extension() end @@ -48,6 +54,15 @@ class ResourceController < ApplicationController access_denied end end + + def show_duplication_snippet + @resource = Project.by_key(params[:id]) + if (@resource && has_role?(:user, @resource)) + render :partial => 'duplications_source_snippet', :locals => {:resource => @resource, :from_line => params[:from_line].to_i, :to_line => params[:to_line].to_i} + else + access_denied + end + end private @@ -165,7 +180,24 @@ class ResourceController < ApplicationController end render :action => 'index', :layout => !request.xhr? end - + + + def render_duplications + duplications_data = @snapshot.measure('duplications_data'); + + @duplication_groups = [] + if duplications_data && duplications_data.measure_data && duplications_data.measure_data.data + dups = Document.new duplications_data.measure_data.data.to_s + dups.elements.each("duplications/duplication") do |dup| + group = [] + group << {:lines_count => dup.attributes['lines'], :from_line => dup.attributes['start'], :resource => @resource} + group << {:lines_count => dup.attributes['lines'], :from_line => dup.attributes['target-start'], :resource => Project.by_key(dup.attributes['target-resource'])} + @duplication_groups << group + end + end + + render :action => 'index', :layout => !request.xhr? + end def render_violations diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_duplications.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_duplications.html.erb new file mode 100644 index 00000000000..fc9bbb1b809 --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_duplications.html.erb @@ -0,0 +1,90 @@ +<% if @duplication_groups.empty? %> + + <%= message('duplications.no_duplicated_block') -%> + +<% else %> + + + + + + + + + + + + + + + <% + @duplication_groups.each_with_index do |group, group_index| + group_size = group.size() + row_style = cycle 'even','odd', :name => ('duplications') + %> + + + + + + <% + group.each_with_index do |dup, index| + resource = dup[:resource] + from_line = dup[:from_line].to_i + to_line = from_line+5 + update_snippet_script = "new Ajax.Updater('source-#{group_index}', '#{url_for :action => :show_duplication_snippet, :params => {:id => resource.id, :from_line => from_line, :to_line => to_line}}', {asynchronous:true, evalScripts:true}); return false;" + groupClass = "group" + group_index.to_s + groupRowClass = "row" + group_index.to_s + "-" + index.to_s + %> + + + + + + <% if index==0 %> + + <% end %> + + <% + end + %> + + + + + + + <% + end + %> + +
<%= message('duplications.blocks') -%><%= message('duplications.number_of_lines') -%><%= message('duplications.from_line') -%><%= message('duplications.file') -%><%= message('duplications.details') -%>
+
<%= group_size -%> +
+
+ <%= dup[:lines_count] -%> +
+
+
+ <%= from_line -%> +
+
+ <% if resource==@resource + cell_content = resource.name + else + cell_content = link_to_resource(resource, resource.name) + end + %> +
+ <%= cell_content -%> +
+
+
+ <%= render :partial => 'duplications_source_snippet', :locals => {:resource => resource, :from_line => from_line, :to_line => to_line} -%> +
+
+ +<% end %> \ No newline at end of file diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_duplications_source_snippet.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_duplications_source_snippet.html.erb new file mode 100644 index 00000000000..7b4faa9a565 --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_duplications_source_snippet.html.erb @@ -0,0 +1,2 @@ +<%= resource.key -%> +<%= snapshot_source_to_html(resource.last_snapshot, {:line_range => (from_line)..(to_line)}) -%> diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_header_duplications.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_header_duplications.html.erb new file mode 100644 index 00000000000..99282485557 --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_header_duplications.html.erb @@ -0,0 +1,22 @@ +
+ + + <% m=measure('duplicated_lines_density') %> + + + <% m=measure('lines') %> + + + + <% m=measure('duplicated_lines') %> + + + + <% m=measure('duplicated_blocks') %> + + + + +
<%= m ? format_measure(m) : "0%" -%> <%= message('metric.lines.name') -%>:<%= m ? format_measure(m) : "0" -%> <%= message('metric.duplicated_lines.name') -%>:<%= m ? format_measure(m) : "0" -%> <%= message('metric.duplicated_blocks.name') -%>:<%= m ? format_measure(m) : "0" -%>
+
+ diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/resource/index.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/resource/index.html.erb index 7ec54ed21cd..31c2aa66209 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/resource/index.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/resource/index.html.erb @@ -13,7 +13,9 @@ <% end %> + <% if @lines && @lines.size>0 %> + <% current_revision=nil @@ -120,4 +122,9 @@ <% if @filtered && !has_displayed_lines %>

<%= message('no_lines_match_your_filter_criteria') -%>

<% end %> + +<% else %> + + <%= render :partial => 'duplications' -%> + <% end %> \ No newline at end of file diff --git a/sonar-server/src/main/webapp/javascripts/application.js b/sonar-server/src/main/webapp/javascripts/application.js index 4de5a61ed91..5927f31860f 100644 --- a/sonar-server/src/main/webapp/javascripts/application.js +++ b/sonar-server/src/main/webapp/javascripts/application.js @@ -90,6 +90,17 @@ function hideElement( elementId ) { return false; } +function updateDuplicationLines(selectedDiv, groupClass, groupRowClass) { + divs = $$('.'+groupClass); + for ( i = 0; i < divs.size(); i++) { + divs[i].removeClassName('selected'); + } + divs = $$('.'+groupRowClass); + for ( i = 0; i < divs.size(); i++) { + divs[i].addClassName('selected'); + } +} + var projects; function autocompleteProjects(APIURL, projectURL, searchInput, searchResult) { if (projects != null) return; diff --git a/sonar-server/src/main/webapp/stylesheets/style.css b/sonar-server/src/main/webapp/stylesheets/style.css index 037c14f70f7..24ed0b4dd7e 100644 --- a/sonar-server/src/main/webapp/stylesheets/style.css +++ b/sonar-server/src/main/webapp/stylesheets/style.css @@ -917,6 +917,44 @@ span.rulename a:hover { +/* DUPLICATIONS */ + +#duplicationsTable > tbody > tr { + padding-top: 2px; +} + +#duplicationsTable td.group-item { + padding: 0px; + height: 1px; +} + +#duplicationsTable td.group-item > div { + padding: 3px; + margin: 2px 0px; +} + +#duplicationsTable td.group-item > div.selected { + background-color: #fff; + border: 1px solid #DDDDDD; +} + +#duplicationsTable td.source-snippet { + background-color: #ddd; + border: 1px solid #DDDDDD; + padding: 0px; +} + +#duplicationsTable td.source-snippet > div { + padding: 2px; + background-color: #fff; +} + +.clickable { + cursor: pointer; +} + + + /* REVIEWS */ div#review .actions{ visibility: hidden; @@ -1903,7 +1941,7 @@ ul.horizontal li { float: left; position: relative; } -table.nowrap td, td.nowrap { +table.nowrap td, td.nowrap, th.nowrap { white-space: nowrap; } .background-gray { -- 2.39.5