]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-1332 profile comparison tool
authorsimonbrandhof <simon.brandhof@gmail.com>
Wed, 30 Mar 2011 14:02:04 +0000 (16:02 +0200)
committersimonbrandhof <simon.brandhof@gmail.com>
Wed, 30 Mar 2011 14:02:04 +0000 (16:02 +0200)
sonar-server/src/main/webapp/WEB-INF/app/controllers/profiles_controller.rb
sonar-server/src/main/webapp/WEB-INF/app/helpers/profiles_helper.rb
sonar-server/src/main/webapp/WEB-INF/app/views/profiles/_diff_rule.html.erb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/views/profiles/compare.html.erb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/views/profiles/index.html.erb
sonar-server/src/main/webapp/images/compare.png [new file with mode: 0644]
sonar-server/src/main/webapp/images/switch.png [new file with mode: 0644]
sonar-server/src/main/webapp/stylesheets/style.css

index 1f068161adfc4fbc14a9ceca31360bc8b009b4ad..24339483e4a282d5602345d74f5e9ab5bcf8c797 100644 (file)
@@ -197,7 +197,8 @@ class ProfilesController < ApplicationController
     @select_parent = [['None', nil]] + profiles.collect{ |profile| [profile.name, profile.name] }
   end
 
-
+  
+  
   #
   #
   # POST /profiles/change_parent?id=<profile id>&parent_name=<parent profile name>
@@ -282,8 +283,165 @@ class ProfilesController < ApplicationController
   end
 
 
+  #
+  #
+  # GET /profiles/compare?id1=<profile1 id>&id2=<profile2 id>
+  #
+  #
+  def compare
+    @profiles = Profile.find(:all, :conditions => ['enabled=?', true], :order => 'language asc, name')
+    if params[:id1].present? && params[:id2].present?
+      @profile1 = Profile.find(params[:id1])
+      @profile2 = Profile.find(params[:id2])
+      
+      arules1 = ActiveRule.find(:all, :include => [{:active_rule_parameters => :rules_parameter}, :rule],
+        :conditions => ['active_rules.profile_id=?', @profile1.id])
+      arules2 = ActiveRule.find(:all, :order => 'rules.plugin_name, rules.plugin_rule_key', :include => [{:active_rule_parameters => :rules_parameter}, :rule],
+        :conditions => ['active_rules.profile_id=?', @profile2.id])
+
+      diffs_by_rule={}
+      arules1.each do |arule1|
+        diffs_by_rule[arule1.rule]||=RuleDiff.new(arule1.rule)
+        diffs_by_rule[arule1.rule].arule1=arule1
+      end
+      arules2.each do |arule2|
+        diffs_by_rule[arule2.rule]||=RuleDiff.new(arule2.rule)
+        diffs_by_rule[arule2.rule].arule2=arule2
+      end
+      @in1=[]
+      @in2=[]
+      @modified=[]
+      @sames=[]
+      diffs_by_rule.values.sort.each do |diff|
+        case diff.status
+        when DIFF_IN1: @in1<<diff
+        when DIFF_IN2: @in2<<diff
+        when DIFF_MODIFIED: @modified<<diff
+        when DIFF_SAME: @sames<<diff
+        end
+      end
+    end
+  end
+
+  DIFF_IN1=1
+  DIFF_IN2=2
+  DIFF_MODIFIED=3
+  DIFF_SAME=4
+  
   private
 
+  class RuleDiff
+    attr_reader :rule, :removed_params, :added_params
+    attr_accessor :arule1, :arule2
+
+    def initialize(rule)
+      @rule=rule
+    end
+
+    def status
+      @status ||=
+          begin
+            if @arule1.nil?
+              @status=(@arule2 ? DIFF_IN2 : nil)
+            else
+              if @arule2
+                # compare severity and parameters
+                @removed_params=[]
+                @added_params=[]
+                @rule.parameters.each do |param|
+                  v1=@arule1.value(param.id)
+                  v2=@arule2.value(param.id)
+                  if v1
+                    if v2
+                      if v1!=v2
+                        @removed_params<<@arule1.parameter(param.name)
+                        @added_params<<@arule2.parameter(param.name)
+                      end
+                    else
+                      @removed_params<<@arule1.parameter(param.name)
+                    end          
+                  elsif v2
+                    @added_params<<@arule2.parameter(param.name)
+                  end
+                end
+                diff=(@arule1.priority!=@arule2.priority) || !@removed_params.empty? || !@added_params.empty?
+                @status=(diff ? DIFF_MODIFIED : DIFF_SAME)
+              else
+                @status=DIFF_IN1
+              end
+            end
+          end
+    end
+
+    def <=>(other)
+      rule.name()<=>other.rule.name
+    end
+  end
+  
+  #
+  # Remove active rules that are identical in both collections (same severity and same parameters)
+  # and return a map with results {:added => X, :removed => Y, :modified => Z, 
+  # :rules => {rule1 => [activeruleleft1, activeruleright1], rule2 => [activeruleleft2, nil], ...]}
+  # Assume both collections are ordered by rule key
+  #
+  def compute_diff(arules1, arules2)
+    rules = {}
+    removed = 0
+    added = 0
+    modified = 0
+    same = 0
+    begin
+      diff = false
+      #take first item of each collection
+      active_rule1 = arules1.first
+      active_rule2 = arules2.first
+      if active_rule1 != nil and active_rule2 != nil
+        order = active_rule1.rule.key <=> active_rule2.rule.key
+        if order < 0
+          active_rule2 = nil
+          rule = active_rule1.rule
+          diff = true
+          removed = removed +1
+        elsif order > 0
+          active_rule1 = nil
+          rule = active_rule2.rule
+          diff = true
+          added = added +1
+        else
+          rule = active_rule1.rule # = active_rule2.rule
+          #compare severity
+          diff = true if active_rule1.priority != active_rule2.priority
+          #compare parameters
+          rule.parameters.each do |param|
+            diff = true if active_rule1.value(param.id) != active_rule2.value(param.id)
+          end
+          if diff
+            modified = modified + 1
+          else
+            same = same +1
+          end
+        end
+      elsif active_rule1 != nil
+        #no more rule in right collection
+        diff = true
+        removed = removed +1
+        rule = active_rule1.rule
+      elsif active_rule2 != nil
+        #no more rule in left collection
+        diff = true
+        added = added +1
+        rule = active_rule2.rule
+      end
+      # remove processed rule(s)
+      arules1 = arules1.drop(1) if active_rule1 != nil
+      arules2 = arules2.drop(1) if active_rule2 != nil
+      if diff
+        rules[rule] = [active_rule1, active_rule2]
+      end
+    end while !arules1.empty? || !arules2.empty?
+    return {:same => same, :added => added, :removed => removed, :modified => modified,  :rules => rules}
+  end
+
   def read_file_param(configuration_file)
     # configuration file is a StringIO
     if configuration_file.respond_to?(:read)
index d9e3d3a5db59b5fe13810050e155c2839f1c764b..3c2146fb835c6a3951f4857314473dae944ecd03 100644 (file)
@@ -32,4 +32,16 @@ module ProfilesHelper
     end
     label
   end
+
+  def options_for_profiles(profiles, selected_id=nil)
+    html=""
+    profiles.group_by(&:language).each do |language, profiles|
+      html += "<optgroup label=\"#{html_escape(language)}\">"
+      profiles.each do |profile|
+        html += "<option value='#{profile.id}' #{'selected' if profile.id==selected_id}>#{html_escape(profile.name)}</option>"
+      end
+      html += "</optgroup>"
+    end
+    html
+  end
 end
\ No newline at end of file
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/profiles/_diff_rule.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/profiles/_diff_rule.html.erb
new file mode 100644 (file)
index 0000000..9b7acae
--- /dev/null
@@ -0,0 +1,3 @@
+<%= image_tag "priority/#{arule.priority}.png" %>
+
+<span class="rulename"><a onclick="window.open(this.href,'rule','height=700,width=500,scrollbars=1,resizable=1');return false;" href="<%= url_for :controller => 'rules', :action => 'show', :id => arule.rule.key, :layout => 'false' -%>"><%= h(arule.rule.name) -%></a></span> <em><%= h(arule.rule.plugin_name) -%></em>
\ No newline at end of file
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/profiles/compare.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/profiles/compare.html.erb
new file mode 100644 (file)
index 0000000..7023545
--- /dev/null
@@ -0,0 +1,136 @@
+<h1 class="marginbottom10"><%= link_to 'Quality profiles', :controller => 'profiles', :action => 'index' -%> / Compare</h1>
+
+<form method="GET" class="marginbottom10">
+  <select name="id1" class="small">
+    <option value=""></option>
+    <%= options_for_profiles(@profiles, params[:id1].to_i) %>
+  </select>
+
+  <select name="id2" class="small">
+    <option value=""></option>
+    <%= options_for_profiles(@profiles, params[:id2].to_i) %>
+  </select>
+  <input type="submit" value="Compare" class="small"/>
+  <% if @profile1 && @profile2 %>
+    <a href="<%= url_for :action => 'compare', :id1 => @profile2.id, :id2 => @profile1.id -%>" id="switch" title="Switch left and right view"><%= image_tag 'switch.png'-%></a>
+  <% end %>
+</form>
+
+<% if @profile1 && @profile2 %>
+
+<table class="width100 marginbottom10" id="profile_diff_table">
+<tbody>
+
+<% unless @in1.empty? %>
+  <tr>
+  <td  width="49%">
+    <table class="data width100 marginbottom10">
+      <thead>
+        <tr>
+          <th><%= @in1.size -%> rules only in <a href="<%= url_for :controller => 'rules_configuration', :action => 'index', :id => @profile1.id -%>"><%= h @profile1.name %></a></th>
+        </tr>
+      </thead>
+      <% @in1.each do |diff| %>
+        <tr id="rule_<%= u(diff.rule.key) -%>">
+          <td class="<%= cycle('even','odd', :name => 'in1')-%>">
+            <%= render :partial => 'diff_rule', :locals => {:arule => diff.arule1} %>
+          </td>
+        </tr>
+      <% end %>
+    </table>
+  </td>
+  <td width="2%"> </td>
+  <td width="49%"> </td>
+  </tr>
+<% end %>
+
+<% unless @in2.empty? %>
+  <tr>
+    <td width="49%"></td>
+    <td width="2%"> </td>
+    <td width="49%">
+      <table class="data width100 marginbottom10">
+        <thead>
+          <tr>
+            <th><%= @in2.size -%> rules only in <a href="<%= url_for :controller => 'rules_configuration', :action => 'index', :id => @profile2.id -%>"><%= h @profile2.name %></a></th>
+          </tr>
+        </thead>
+        <% @in2.each do |diff| %>
+          <tr id="rule_<%= u(diff.rule.key) -%>">
+            <td class="<%= cycle('even','odd', :name => 'in2')-%>">
+              <%= render :partial => 'diff_rule', :locals => {:arule => diff.arule2} %>
+            </td>
+          </tr>
+        <% end %>
+      </table>
+    </td>
+  </tr>
+<% end %>
+
+
+<% unless @modified.empty? %>
+<tr>
+    <td colspan="3">
+    <table class="data width100 marginbottom10">
+      <thead>
+        <tr>
+          <th width="49%"><%= @modified.size -%> rules have a different configuration<br/>
+            <a href="<%= url_for :controller => 'rules_configuration', :action => 'index', :id => @profile1.id -%>"><%= h @profile1.name %></a>
+          </th>
+          <th width="2%"></th>
+          <th width="49%"><br/><a href="<%= url_for :controller => 'rules_configuration', :action => 'index', :id => @profile2.id -%>"><%= h @profile2.name %></a></th>
+        </tr>
+      </thead>
+      <% @modified.each do |diff|
+           td_css=cycle('even','odd', :name => 'modified')
+      %>
+        <tr id="rule_<%= u(diff.rule.key) -%>">
+          <td class="<%= td_css -%>" width="49%">
+            <%= render :partial => 'diff_rule', :locals => {:arule => diff.arule1} %>
+            <% if diff.removed_params && !diff.removed_params.empty? %>
+                       <ul>
+                               <% diff.removed_params.each do |parameter| %>
+                             <li><%= h(parameter.name) -%>: <span class="diffParam"><%= parameter.value.gsub(',', ', ') -%></span></li>
+                         <% end %>
+                       </ul>
+                       <% end %>
+          </td>
+          <td width="2%" class="<%= td_css -%>"> </td>
+          <td class="<%= td_css -%>" width="49%">
+            <%= render :partial => 'diff_rule', :locals => {:arule => diff.arule2} %>
+            <% if diff.added_params && !diff.added_params.empty? %>
+                       <ul>
+                               <% diff.added_params.each do |parameter| %>
+                             <li><%= h(parameter.name) -%>: <span class="diffParam"><%= parameter.value.gsub(',', ', ') -%></span></li>
+                         <% end %>
+                       </ul>
+                       <% end %>
+          </td>
+        </tr>
+      <% end %>
+    </table>
+    </td>
+  </tr>
+<% end %>
+
+<% unless @sames.empty? %>
+<tr>
+    <td colspan="3">
+    <table class="data width100 marginbottom10">
+      <thead>
+        <tr>
+          <th><%= @sames.size -%> rules have the same configuration</th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr>
+          <td class="even">Not displayed</td>
+        </tr>
+      </tbody>
+    </table>
+    </td>
+  </tr>
+<% end %>
+</tbody>
+</table>
+<% end %>
\ No newline at end of file
index 48cb7947eb3b8f60f7bc4c7c237bd42aa0082c2b..c3d2cf5104ee0d5f8b9289cd0fb29e621f661f8e 100644 (file)
@@ -1,7 +1,12 @@
 <% if administrator? %>
 <div class="line-block marginbottom10">
-  <ul style="float: right" class="horizontal">
-    <li class="marginleft10 restore">
+  <ul class="operations">
+    <li>
+      <%= image_tag 'compare.png' -%>
+      <a href="<%= ApplicationController.root_context-%>/profiles/compare" id="compare-link">Compare profiles</a>
+    </li>
+    <li class="last">
+      <%= image_tag 'restore.gif' -%>
       <a href="#" onclick="$('restore-form').show();return false;" id="restore-link">Restore profile</a>
     </li>
   </ul>
@@ -15,7 +20,7 @@
 
       <tr>
         <td colspan="2">
-          <input type="submit" value="Restore profile"></input>
+          <input type="submit" value="Restore profile"/>
           <a href="#" onclick="$('restore-form').reset();$('restore-form').hide();return false;">Cancel</a>
         </td>
       </tr>
diff --git a/sonar-server/src/main/webapp/images/compare.png b/sonar-server/src/main/webapp/images/compare.png
new file mode 100644 (file)
index 0000000..a61ec13
Binary files /dev/null and b/sonar-server/src/main/webapp/images/compare.png differ
diff --git a/sonar-server/src/main/webapp/images/switch.png b/sonar-server/src/main/webapp/images/switch.png
new file mode 100644 (file)
index 0000000..4a72a34
Binary files /dev/null and b/sonar-server/src/main/webapp/images/switch.png differ
index e4e37fd9e39612f834462751aa3a388091dbc530..435d6dd9fc44ac720d752e6db28d659407b7c151 100644 (file)
@@ -255,6 +255,11 @@ a {
 .left {
   text-align: left;
 }
+
+.center {
+  text-align: center;
+}
+
 code {
   font-size: 93%;
 }
@@ -284,6 +289,12 @@ code {
 .small {
   font-size: 85%;
 }
+em {
+  color: #AAA;
+  font-size: 85%;
+  font-style: normal;
+}
+
 a.external {
        background: url('../images/links/external.png') no-repeat 100% 0;
        padding: 0 16px 0px 0;
@@ -603,6 +614,10 @@ ul.operations li.last {
 ul.operations li a {
   color: #555;
 }
+ul.operations li img {
+  vertical-align: middle;
+  margin-right: 5px;
+}
 
 /* RESOURCE VIEWER */
 .resourceName h1 {
@@ -1582,4 +1597,14 @@ table.nowrap td, td.nowrap {
   display: block;
   background: url("../images/restore.gif") no-repeat scroll left 50% transparent;
   padding: 2px 0 2px 20px;
-}
\ No newline at end of file
+}
+.compare {
+  display: block;
+  background: url("../images/compare.png") no-repeat scroll left 50% transparent;
+  padding: 2px 0 2px 20px;
+}
+
+/* Profile diff */
+.diffParam {
+  font-family: 'Bitstream Vera Sans Mono','Courier',monospace;
+}