aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFabrice Bellingard <fabrice.bellingard@sonarsource.com>2012-11-29 11:28:02 +0100
committerFabrice Bellingard <fabrice.bellingard@sonarsource.com>2012-11-29 13:46:38 +0100
commitc0449e0a612fc44c4a4bff06a35c1f744c898608 (patch)
tree322fce23a111977b4c27933c6f67576e0553998a
parent9322b252de6c294af1b54377c9df3a7c737771f8 (diff)
downloadsonarqube-c0449e0a612fc44c4a4bff06a35c1f744c898608.tar.gz
sonarqube-c0449e0a612fc44c4a4bff06a35c1f744c898608.zip
SONAR-37 & SONAR-2911 Add comparison tool
- To compare projects to each others (SONAR-37) - To compare X versions of a project (SONAR-2911)
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/DefaultResourceTypes.java23
-rw-r--r--plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties19
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceType.java2
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/controllers/comparison_controller.rb87
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/helpers/application_helper.rb1
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/comparison/_versions.html.erb8
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/comparison/index.html.erb216
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb4
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_tools.html.erb1
-rw-r--r--sonar-server/src/main/webapp/images/controls/move_down.pngbin0 -> 3068 bytes
-rw-r--r--sonar-server/src/main/webapp/images/controls/move_left.pngbin0 -> 336 bytes
-rw-r--r--sonar-server/src/main/webapp/images/controls/move_right.pngbin0 -> 342 bytes
-rw-r--r--sonar-server/src/main/webapp/images/controls/move_up.pngbin0 -> 3039 bytes
13 files changed, 350 insertions, 11 deletions
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/DefaultResourceTypes.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/DefaultResourceTypes.java
index bc3125b2d07..72bd2cb244e 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/DefaultResourceTypes.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/DefaultResourceTypes.java
@@ -39,27 +39,28 @@ public final class DefaultResourceTypes extends ExtensionProvider implements Bat
.setProperty("hasRolePolicy", true)
.setProperty("updatable_key", true)
.setProperty("supports_measure_filters", true)
+ .setProperty("comparable", true)
.build())
.addType(ResourceType.builder(Qualifiers.MODULE)
.setProperty("updatable_key", true)
.setProperty("supports_measure_filters", true)
.build())
.addType(ResourceType.builder(Qualifiers.DIRECTORY)
- .setProperty("supports_measure_filters", true)
- .build())
+ .setProperty("supports_measure_filters", true)
+ .build())
.addType(ResourceType.builder(Qualifiers.PACKAGE)
- .build())
+ .build())
.addType(ResourceType.builder(Qualifiers.FILE)
- .hasSourceCode()
- .setProperty("supports_measure_filters", true)
- .build())
+ .hasSourceCode()
+ .setProperty("supports_measure_filters", true)
+ .build())
.addType(ResourceType.builder(Qualifiers.CLASS)
- .hasSourceCode()
- .build())
+ .hasSourceCode()
+ .build())
.addType(ResourceType.builder(Qualifiers.UNIT_TEST_FILE)
- .hasSourceCode()
- .setProperty("supports_measure_filters", true)
- .build())
+ .hasSourceCode()
+ .setProperty("supports_measure_filters", true)
+ .build())
.addRelations(Qualifiers.PROJECT, Qualifiers.MODULE)
.addRelations(Qualifiers.MODULE, Qualifiers.DIRECTORY, Qualifiers.PACKAGE)
diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties b/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties
index c882eef8435..8becc0439de 100644
--- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties
+++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties
@@ -354,6 +354,7 @@ update_key.page=Update Key
project_quality_profiles.page=Quality Profiles
bulk_deletion.page=Bulk Deletion
system_measure_filters.page=System Measure Filters
+comparison.page=Comparison
# GWT pages
@@ -513,6 +514,24 @@ reviews.filtered_by.to=To date
#------------------------------------------------------------------------------
#
+# COMPARISON
+#
+#------------------------------------------------------------------------------
+
+comparison.compare=Compare
+comparison.add_a_new_metric=Add a new metric
+comparison.select_resource_to_compare=Select a resource to compare
+comparison.select_version=Select a version
+comparison.remove_resource=Remove resource
+comparison.remove_metric=Remove metric
+comparison.move_left=Move left
+comparison.move_right=Move right
+comparison.move_down=Move down
+comparison.move_up=Move up
+
+
+#------------------------------------------------------------------------------
+#
# ACTION PLANS
#
#------------------------------------------------------------------------------
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceType.java b/sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceType.java
index bab797e764b..bda5124bff6 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceType.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceType.java
@@ -45,8 +45,10 @@ import java.util.Map;
* <li>"updatable_key" (since 3.2): if set to "true", then it is possible to update the key of this resource</li>
* <li>"supportsGlobalDashboards" (since 3.2): if true, this resource can be displayed in global dashboards</li>
* <li>"hasRolePolicy" : if true, roles configuration is available in sidebar</li>
+ * <li>"comparable" (since 3.4) : if true, the resource can be compared to other resources</li>
* </ul>
*
+ * @see DefaultResourceTypes in Sonar Core Plugin to see the default resource types
* @since 2.14
*/
@Beta
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/comparison_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/comparison_controller.rb
new file mode 100644
index 00000000000..6de07617062
--- /dev/null
+++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/comparison_controller.rb
@@ -0,0 +1,87 @@
+#
+# Sonar, entreprise quality control tool.
+# Copyright (C) 2008-2012 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
+#
+
+class ComparisonController < ApplicationController
+
+ def index
+ snapshots = []
+ resource_key = params[:resource]
+ if resource_key && !resource_key.blank?
+ # the request comes from a project: let's select its 5 latest versions
+ project = Project.by_key(resource_key)
+ snapshots = project.events.select { |event| !event.snapshot_id.nil? && event.category==EventCategory::KEY_VERSION }[0..5].reverse.map {|e| e.snapshot}
+ else
+ # the request comes from the comparison page: let's compare the given snapshots
+ sids = get_params_as_array(:sids)
+ unless sids.empty?
+ selected_snapshots = Snapshot.find(:all, :conditions => ['id in (?)', sids])
+ # next loop is required to keep the order that was decided by the user and which comes from the "sids" parameter
+ sids.each do |id|
+ selected_snapshots.each do |s|
+ snapshots << s if id==s.id.to_s
+ end
+ end
+ end
+ end
+ @snapshots = select_authorized(:user, snapshots)
+
+ metrics = get_params_as_array(:metrics)
+ if metrics.empty?
+ metrics = [
+ 'ncloc',
+ 'complexity',
+ 'comment_lines_density',
+ 'duplicated_lines_density',
+ 'violations',
+ 'coverage'
+ ]
+ end
+ @metrics = Metric.by_keys(metrics)
+
+ @metric_to_choose = Metric.all.select {|m| m.display? && !@metrics.include?(m)}.sort_by(&:short_name)
+
+ end
+
+ def versions
+ key = params[:resource]
+ sids = get_params_as_array(:sids)
+
+ unless key.blank?
+ resource = Project.by_key(params[:resource])
+ # we look for the events that are versions and that are not linked to snapshots already displayed on the page
+ @versions = resource.events.select { |event| !event.snapshot_id.nil? && event.category==EventCategory::KEY_VERSION && !sids.include?(event.snapshot_id.to_s) }
+ end
+
+ render :partial => 'versions'
+ end
+
+
+ private
+
+ def get_params_as_array(name)
+ list = params[name]
+ if list.blank?
+ []
+ else
+ list.split(',')
+ end
+ end
+
+end \ No newline at end of file
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 76bc0264591..e44991b4189 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
@@ -718,6 +718,7 @@ module ApplicationHelper
# add a select <option> with empty value
select_tag_prompt=''
end
+ js_options.merge!(options[:select2_options]) if options[:select2_options]
extra_values = options[:extra_values]
metrics_by_domain={}
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/comparison/_versions.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/comparison/_versions.html.erb
new file mode 100644
index 00000000000..813d8d4ba12
--- /dev/null
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/comparison/_versions.html.erb
@@ -0,0 +1,8 @@
+<% if @versions %>
+
+ <option value=""></option>
+ <% @versions.each do |version| %>
+ <option value="<%= version.snapshot_id -%>"><%= version.name -%></option>
+ <% end %>
+
+<% end %> \ No newline at end of file
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/comparison/index.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/comparison/index.html.erb
new file mode 100644
index 00000000000..938441aaff4
--- /dev/null
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/comparison/index.html.erb
@@ -0,0 +1,216 @@
+<style type="text/css">
+ #comparison-page td {
+ vertical-align: middle;
+ }
+
+ .move-actions img {
+ visibility: hidden;
+ }
+
+ .move-actions:hover img {
+ visibility: visible;
+ }
+</style>
+
+<div id="comparison-page">
+ <form method="GET" id="compare-form" action="<%= ApplicationController.root_context -%>/comparison/index">
+ <input type="hidden" name="sids" id="sids" value="<%= @snapshots.map {|s| s.id.to_s}.join(',') -%>">
+ <input type="hidden" name="metrics" id="metrics" value="<%= @metrics.map {|m| m.key}.join(',') -%>">
+
+ <table class="data">
+
+ <thead>
+ <th class="thin" style="vertical-align: bottom">
+ <%= metric_select_tag 'new_metric', @metric_to_choose, {
+ :allow_empty => true,
+ :select2_options => {'placeholder' => "'" + message('comparison.add_a_new_metric') + "'"}
+ } -%>
+ <script>
+ // we don't want this parameter to be submitted, so we set its name to ''
+ $j('#new_metric').attr('name', '');
+ $j('#new_metric').on("change", function(e) {
+ if (e.val!=null) {
+ var currentMetrics = $j('#metrics').val();
+ if (currentMetrics.length > 0) {
+ currentMetrics += ',';
+ }
+ currentMetrics += e.val;
+ $j('#metrics').val(currentMetrics);
+ $j('#compare-form').submit();
+ }
+ });
+ </script>
+ </th>
+
+ <%
+ last_index = @snapshots.size-1
+ @snapshots.each_with_index do |s, index|
+ %>
+ <th class="thin move-actions" style="padding: 5px !important;">
+ <table>
+ <tr>
+ <td style="vertical-align: top;">
+ <% if index > 0 %>
+ <a href="#" onclick="moveLeft(<%= index -%>)"><img src="<%= ApplicationController.root_context -%>/images/controls/move_left.png" title="<%= message('comparison.move_left') -%>"/></a>
+ <% else %>
+ <img src="<%= ApplicationController.root_context -%>/images/transparent_16.gif"/>
+ <% end %>
+ </td>
+ <td class="nowrap" style="text-align: center;">
+ <div style="width: 100%; text-align: center;"><a href="#" onclick="removeFromList(<%= index -%>, $j('#sids'))"><img src="<%= ApplicationController.root_context -%>/images/controls/cross.png" title="<%= message('comparison.remove_resource') -%>"/></a></div>
+ <a href="<%= ApplicationController.root_context -%>/dashboard/index/<%= s.resource.key -%>"><%= s.resource.name(true) -%></a>
+ <br/>
+ <span class="note"><b><%= s.event(EventCategory::KEY_VERSION).name -%></b></span>
+ <br/>
+ <span class="note"><%= human_short_date s.created_at -%></span>
+ </td>
+ <td class="thin" style="vertical-align: top;">
+ <% if index < last_index %>
+ <a href="#" onclick="moveRight(<%= index -%>)"><img src="<%= ApplicationController.root_context -%>/images/controls/move_right.png" title="<%= message('comparison.move_right') -%>"/></a>
+ <% else %>
+ <img src="<%= ApplicationController.root_context -%>/images/transparent_16.gif"/>
+ <% end %>
+ </td>
+ </tr>
+ </table>
+ </th>
+ <% end %>
+
+ <th style="padding-left: 20px; vertical-align: bottom">
+ <%= resource_select_tag 'new_resource', {
+ :resource_type_property => 'comparable',
+ :width => '250px',
+ :select2_options => {'placeholder' => "'" + message('comparison.select_resource_to_compare') + "'"}
+ } -%>
+ <script>
+ // we don't want this parameter to be submitted, so we set its name to ''
+ $j('#new_resource').attr('name', '');
+ $j('#new_resource').on("change", function(e) {
+ if (e.val!=null) {
+ $j.ajax({
+ type: 'GET',
+ url: '<%= ApplicationController.root_context -%>/comparison/versions?resource='
+ + e.val + '&sids='
+ + $j('#sids').val(),
+ success: function(data){
+ $j('#new_version').html(data);
+ $j('#new_version').select2({placeholder: '<%= message('comparison.select_version') -%>'});
+ $j('#version_div').show();
+ $j('#new_version').select2("focus");
+ }
+ });
+ }
+ });
+ $j('#new_resource').select2("focus");
+ </script>
+
+ <span id="version_div" style="display: none">
+ <select id="new_version">
+ </select>
+ <script>
+ $j('#new_version').on("change", function(e) {
+ if (e.val!=null) {
+ var currentSnapshotIds = $j('#sids').val();
+ if (currentSnapshotIds.length > 0) {
+ currentSnapshotIds += ',';
+ }
+ currentSnapshotIds += e.val;
+ $j('#sids').val(currentSnapshotIds);
+ $j('#compare-form').submit();
+ }
+ });
+ </script>
+ </span>
+ </th>
+
+ <th class="thin" style="vertical-align: top">
+ <a href="<%= url_for :controller => 'comparison', :action => 'index', :sids => @snapshots.map {|s| s.id.to_s}.join(','), :metrics => @metrics.map {|m| m.key}.join(',') -%>">
+ <img src="<%= ApplicationController.root_context -%>/images/permalink.png" title="<%= message('permalink') -%>"/>
+ </a>
+ </th>
+ </thead>
+
+ <tbody>
+ <%
+ last_index = @metrics.size-1
+ @metrics.each_with_index do |m, index|
+ %>
+ <tr class="<%= cycle 'even', 'odd' -%> move-actions">
+ <td>
+ <div style="float: left; vertical-align: bottom;"><%= m.short_name -%></div>
+ <div style="float: right">
+ <% if index > 0 %>
+ <a href="#" onclick="moveUp(<%= index -%>)"><img src="<%= ApplicationController.root_context -%>/images/controls/move_up.png" title="<%= message('comparison.move_up') -%>"/></a>
+ <% end %>
+ <% if index < last_index %>
+ <a href="#" onclick="moveDown(<%= index -%>)"><img src="<%= ApplicationController.root_context -%>/images/controls/move_down.png" title="<%= message('comparison.move_down') -%>"/></a>
+ <% end %>
+ <a href="#" onclick="removeFromList(<%= index -%>, $j('#metrics'))"><img src="<%= ApplicationController.root_context -%>/images/controls/cross.png" title="<%= message('comparison.remove_metric') -%>"/></a>
+ </div>
+ </td>
+
+ <% @snapshots.each do |s| %>
+ <td style="text-align: center">
+ <%= format_measure s.measure(m) -%>
+ </td>
+ <% end %>
+
+ <td></td>
+ <td></td>
+ </tr>
+ <% end %>
+ </tbody>
+
+ </table>
+
+ <form>
+</div>
+
+<script>
+ function submitForm() {
+ $j('#compare-form').submit();
+ }
+
+ function moveLeft(index) {
+ sids = $j('#sids').val().split(',');
+ idToLeft = sids[index];
+ idToRight = sids[index-1];
+ sids.splice(index-1, 2, [idToLeft, idToRight]);
+ $j('#sids').val(sids.join(','));
+ submitForm();
+ }
+
+ function moveRight(index) {
+ sids = $j('#sids').val().split(',');
+ idToRight = sids[index];
+ idToLeft = sids[index+1];
+ sids.splice(index, 2, [idToLeft, idToRight]);
+ $j('#sids').val(sids.join(','));
+ submitForm();
+ }
+
+ function moveUp(index) {
+ metrics = $j('#metrics').val().split(',');
+ idToUp = metrics[index];
+ idToBottom = metrics[index-1];
+ metrics.splice(index-1, 2, [idToUp, idToBottom]);
+ $j('#metrics').val(metrics.join(','));
+ submitForm();
+ }
+
+ function moveDown(index) {
+ metrics = $j('#metrics').val().split(',');
+ idToBottom = metrics[index];
+ idToUp = metrics[index+1];
+ metrics.splice(index, 2, [idToUp, idToBottom]);
+ $j('#metrics').val(metrics.join(','));
+ submitForm();
+ }
+
+ function removeFromList(index, inputField) {
+ value = inputField.val().split(',');
+ value.splice(index, 1);
+ inputField.val(value.join(','));
+ submitForm();
+ }
+</script>
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb
index 9fb3b7bb9cb..ef763e40276 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb
@@ -80,6 +80,10 @@
<a href="<%= ApplicationController.root_context -%>/components/index/<%= @project.id -%>"><%= message('components.page') -%></a></li>
<li class="<%= 'selected' if request.request_uri.include?('/drilldown/violations') -%>">
<a href="<%= ApplicationController.root_context -%>/drilldown/violations/<%= @project.id -%><%= "?"+period_param if period_param -%>"><%= message('violations_drilldown.page') -%></a>
+ <% if controller.java_facade.getResourceTypeBooleanProperty(@project.qualifier, 'comparable') %>
+ <li class="<%= 'selected' if request.request_uri.include?('/comparison/index') -%>">
+ <a href="<%= ApplicationController.root_context -%>/comparison/index?resource=<%= @project.key -%>"><%= message('comparison.page') -%></a></li>
+ <% end %>
<li class="<%= 'selected' if request.request_uri.include?('/cloud/index') -%>">
<a href="<%= ApplicationController.root_context -%>/cloud/index/<%= @project.id -%>"><%= message('clouds.page') -%></a></li>
<% controller.java_facade.getPages(Navigation::SECTION_RESOURCE, @project.scope, @project.qualifier, @project.language, @project.last_snapshot.metric_keys.to_java(:string)).each do |page|
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_tools.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_tools.html.erb
index b1bca085e65..a5c93c4ecf0 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_tools.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_tools.html.erb
@@ -4,6 +4,7 @@
<div id="tools-menu" class="dropdown-menu" style="display: none">
<ul>
<li><a href="<%= ApplicationController.root_context -%>/dependencies/index"><%= message('dependencies.page') -%></a></li>
+ <li><a href="<%= ApplicationController.root_context -%>/comparison/index"><%= message('comparison.page') -%></a></li>
<li><a href="<%= ApplicationController.root_context -%>/reviews/index"><%= message('reviews.page') -%></a></li>
</ul>
</div>
diff --git a/sonar-server/src/main/webapp/images/controls/move_down.png b/sonar-server/src/main/webapp/images/controls/move_down.png
new file mode 100644
index 00000000000..2ae468a07e2
--- /dev/null
+++ b/sonar-server/src/main/webapp/images/controls/move_down.png
Binary files differ
diff --git a/sonar-server/src/main/webapp/images/controls/move_left.png b/sonar-server/src/main/webapp/images/controls/move_left.png
new file mode 100644
index 00000000000..8c166e875cf
--- /dev/null
+++ b/sonar-server/src/main/webapp/images/controls/move_left.png
Binary files differ
diff --git a/sonar-server/src/main/webapp/images/controls/move_right.png b/sonar-server/src/main/webapp/images/controls/move_right.png
new file mode 100644
index 00000000000..5c9a1df1d6a
--- /dev/null
+++ b/sonar-server/src/main/webapp/images/controls/move_right.png
Binary files differ
diff --git a/sonar-server/src/main/webapp/images/controls/move_up.png b/sonar-server/src/main/webapp/images/controls/move_up.png
new file mode 100644
index 00000000000..83c005763c2
--- /dev/null
+++ b/sonar-server/src/main/webapp/images/controls/move_up.png
Binary files differ