]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-2733 Display duplicated blocks by group in the resource viewer
authorFabrice Bellingard <bellingard@gmail.com>
Wed, 26 Oct 2011 17:18:08 +0000 (19:18 +0200)
committerFabrice Bellingard <bellingard@gmail.com>
Wed, 26 Oct 2011 17:18:56 +0000 (19:18 +0200)
First shot, needs improvements.

12 files changed:
plugins/sonar-core-gwt/src/main/java/org/sonar/plugins/core/duplicationsviewer/DuplicationsViewerDefinition.java [deleted file]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspot_metric.html.erb
plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties
sonar-server/src/main/java/org/sonar/server/ui/DefaultPages.java
sonar-server/src/main/webapp/WEB-INF/app/controllers/resource_controller.rb
sonar-server/src/main/webapp/WEB-INF/app/views/resource/_duplications.html.erb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/views/resource/_duplications_source_snippet.html.erb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/views/resource/_header_duplications.html.erb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/views/resource/index.html.erb
sonar-server/src/main/webapp/javascripts/application.js
sonar-server/src/main/webapp/stylesheets/style.css

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 (file)
index 17c7ffa..0000000
+++ /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;
-  }
-
-}
index 6d71e3a79711194707ba62349a3bbcf281a8d6f5..e0aba2cc264d11c58ce469ad2df0190f4bcac574 100644 (file)
@@ -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);
 
index 94769ac1b4ed06f7f51e9545bc9d79eead2fc631..250b7fc553d20f612e72ed334c9ab4fc8781444e 100644 (file)
@@ -58,9 +58,9 @@
   <tbody>
 <%
   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
index 54ae0a501c8439ed5d5fc1645d6e0258605328b9..33f4ff2e83efa3c518a599c11c55f36ab3d812f3 100644 (file)
@@ -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
index ed97615931294862c95340a5ff8410ab1e7c9ae2..a68cbc86c19eb8fd4240f9dbc5980e67f750664d 100644 (file)
@@ -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";
+    }
+  }
 }
index 2ca2eadfa51f71487f1464fd93978c89cad767c1..61c30b9aee9a91cd80e31c64080ba006512fed2f 100644 (file)
 # 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 (file)
index 0000000..fc9bbb1
--- /dev/null
@@ -0,0 +1,90 @@
+<% if @duplication_groups.empty? %>
+
+  <%= message('duplications.no_duplicated_block') -%>
+
+<% else %>
+
+  <table id="duplicationsTable" class="data max-width">
+    <thead>
+      <tr>
+        <th class="thin nowrap"><%= message('duplications.blocks') -%></th>
+        <th class="thin nowrap"><%= message('duplications.number_of_lines') -%></th>
+        <th class="thin nowrap"><%= message('duplications.from_line') -%></th>
+        <th class="thin nowrap"><%= message('duplications.file') -%></th>
+        <th><%= message('duplications.details') -%></th>
+        <th class="thin nowrap"></th>
+      </tr>
+    </thead>
+    
+    <tbody>
+    <% 
+      @duplication_groups.each_with_index do |group, group_index|
+        group_size = group.size()
+        row_style = cycle 'even','odd', :name => ('duplications')
+    %>
+      <tr class="<%= row_style -%>">
+        <td rowspan="<%= group_size+2 -%>" class="center">
+          <br/><b><%= group_size -%></b>
+        </td>
+        <td colspan="4" style="height:2px"></td>
+        <td rowspan="<%= group_size+2 -%>"></td>
+      </tr>
+    <%
+        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
+    %>
+      <tr class="hoverable <%= row_style -%>">        
+        <td class="center group-item">
+          <div class="<%= groupClass -%> <%= groupRowClass -%> clickable <%= 'selected' if resource==@resource -%>" 
+               onclick="updateDuplicationLines(this, '<%= groupClass -%>', '<%= groupRowClass -%>'); <%= update_snippet_script -%>" style="border-right-width: 0px;">
+          <%= dup[:lines_count] -%>
+          <div>
+        </td>
+        <td class="center group-item">
+          <div class="group<%= group_index -%> <%= groupRowClass -%> clickable <%= 'selected' if resource==@resource -%>" 
+               onclick="updateDuplicationLines(this, '<%= groupClass -%>', '<%= groupRowClass -%>'); <%= update_snippet_script -%>" style="border-right-width: 0px;border-left-width: 0px;">
+            <%= from_line -%>
+          </div>
+        </td>
+        <td class="left group-item">
+          <% if resource==@resource
+               cell_content = resource.name
+             else
+               cell_content = link_to_resource(resource, resource.name)
+             end
+          %>
+          <div class="group<%= group_index -%> <%= groupRowClass -%> clickable <%= 'selected' if resource==@resource -%>" 
+               onclick="updateDuplicationLines(this, '<%= groupClass -%>', '<%= groupRowClass -%>'); <%= update_snippet_script -%>" style="padding-right: 20px; border-right-width: 0px;border-left-width: 0px;">
+            <%= cell_content -%>
+          <div>
+        </td>
+        
+        <% if index==0 %>
+        <td rowspan="<%= group_size+1 -%>" class="source-snippet">
+          <div id="source-<%= group_index -%>">
+            <%= render :partial => 'duplications_source_snippet', :locals => {:resource => resource, :from_line => from_line, :to_line => to_line} -%>            
+          </div>
+        </td>
+        <% end %>
+      </tr>
+    <% 
+        end
+    %>
+      <tr class="<%= row_style -%>" style="padding-bottom:100px">
+        <td colspan="3"></td>
+      </tr>
+      <tr class="<%= row_style -%>">
+        <td colspan="6" style="height:4px"></td>
+      </tr>
+    <%
+      end 
+    %>
+    </tbody>
+  </table>  
+
+<% 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 (file)
index 0000000..7b4faa9
--- /dev/null
@@ -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 (file)
index 0000000..9928248
--- /dev/null
@@ -0,0 +1,22 @@
+<div id="source_header" class="tab_header">
+  <table class="metrics">
+    <tr>
+        <% m=measure('duplicated_lines_density') %>
+        <td class="value big"><%= m ? format_measure(m) : "0%" -%></td>
+        <td class="sep"> </td>
+        <% m=measure('lines') %>
+        <td class="name"><%= message('metric.lines.name') -%>:</td>
+        <td class="value"><%= m ? format_measure(m) : "0" -%></td>
+        <td class="sep"> </td>
+        <% m=measure('duplicated_lines') %>
+        <td class="name"><%= message('metric.duplicated_lines.name') -%>:</td>
+        <td class="value"><%= m ? format_measure(m) : "0" -%></td>
+        <td class="sep"> </td>
+        <% m=measure('duplicated_blocks') %>
+        <td class="name"><%= message('metric.duplicated_blocks.name') -%>:</td>
+        <td class="value"><%= m ? format_measure(m) : "0" -%></td>
+        <td class="sep"> </td>
+    </tr>
+  </table>
+</div>
+
index 7ec54ed21cdea97e89660d8f0c722d1711b06c80..31c2aa66209f5c198a1fd717728cb79262fd5993 100644 (file)
@@ -13,7 +13,9 @@
   </table>
 <% end %>
 
+
 <% if @lines && @lines.size>0 %>
+
 <table id="sources" class="sources2 code" cellpadding="0" cellspacing="0" border="0">
   <%
     current_revision=nil
   <% if @filtered && !has_displayed_lines %>
   <p><%= message('no_lines_match_your_filter_criteria') -%></p>
   <% end %>
+  
+<% else %>
+
+  <%= render :partial => 'duplications' -%>
+  
 <% end %>
\ No newline at end of file
index 4de5a61ed91842f9bc5145d92dbad9dba2ccf09a..5927f31860fec881b9a298fd0da3f329f37fddfb 100644 (file)
@@ -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;
index 037c14f70f77dd9b1d10adfe4000b13bb42f48b2..24ed0b4dd7ee74d72a208e033ca18aba771638a9 100644 (file)
@@ -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 {