First shot, needs improvements.
+++ /dev/null
-/*
- * 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;
- }
-
-}
*/
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;
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(
extensions.add(UserManagedMetrics.class);
// pages
- extensions.add(DuplicationsViewerDefinition.class);
extensions.add(TestsViewerDefinition.class);
extensions.add(Hotspots.class);
<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
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
# 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
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
*/
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() {
}
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";
+ }
+ }
}
# 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])
render_coverage()
elsif (@extension.getId()=='source')
render_source()
+ elsif (@extension.getId()=='duplications')
+ render_duplications()
else
render_extension()
end
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
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
--- /dev/null
+<% 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
--- /dev/null
+<%= resource.key -%>
+<%= snapshot_source_to_html(resource.last_snapshot, {:line_range => (from_line)..(to_line)}) -%>
--- /dev/null
+<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>
+
</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
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;
+/* 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;
float: left;
position: relative;
}
-table.nowrap td, td.nowrap {
+table.nowrap td, td.nowrap, th.nowrap {
white-space: nowrap;
}
.background-gray {