aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-server
diff options
context:
space:
mode:
authorFabrice Bellingard <bellingard@gmail.com>2011-10-26 19:18:08 +0200
committerFabrice Bellingard <bellingard@gmail.com>2011-10-26 19:18:56 +0200
commitaf58e35a58579484d6521bd3e675c90e43c18623 (patch)
treef41f4dbd6540d223a2767134837340104f4c3a19 /sonar-server
parenta589e2539c1d163337daf23b682c95d492b3fbb9 (diff)
downloadsonarqube-af58e35a58579484d6521bd3e675c90e43c18623.tar.gz
sonarqube-af58e35a58579484d6521bd3e675c90e43c18623.zip
SONAR-2733 Display duplicated blocks by group in the resource viewer
First shot, needs improvements.
Diffstat (limited to 'sonar-server')
-rw-r--r--sonar-server/src/main/java/org/sonar/server/ui/DefaultPages.java21
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/controllers/resource_controller.rb36
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/resource/_duplications.html.erb90
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/resource/_duplications_source_snippet.html.erb2
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/resource/_header_duplications.html.erb22
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/resource/index.html.erb7
-rw-r--r--sonar-server/src/main/webapp/javascripts/application.js11
-rw-r--r--sonar-server/src/main/webapp/stylesheets/style.css40
8 files changed, 225 insertions, 4 deletions
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 %>
+
+ <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
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 @@
+<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>
+
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 @@
</table>
<% end %>
+
<% if @lines && @lines.size>0 %>
+
<table id="sources" class="sources2 code" cellpadding="0" cellspacing="0" border="0">
<%
current_revision=nil
@@ -120,4 +122,9 @@
<% 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
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 {