]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-2662 Create action plans which are a set of manual reviews
authorFabrice Bellingard <bellingard@gmail.com>
Sun, 18 Dec 2011 16:47:40 +0000 (17:47 +0100)
committerFabrice Bellingard <bellingard@gmail.com>
Mon, 19 Dec 2011 07:40:38 +0000 (08:40 +0100)
- Improve progress bar
- Display the list of reviews in a separate page which is still in
  the context of the project

plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties
sonar-server/src/main/webapp/WEB-INF/app/controllers/project_reviews_controller.rb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/models/action_plan.rb
sonar-server/src/main/webapp/WEB-INF/app/models/review.rb
sonar-server/src/main/webapp/WEB-INF/app/views/action_plans/_progress.html.erb
sonar-server/src/main/webapp/WEB-INF/app/views/action_plans/index.html.erb
sonar-server/src/main/webapp/WEB-INF/app/views/project_reviews/index.html.erb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/views/reviews/_list.html.erb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/views/reviews/index.html.erb
sonar-server/src/main/webapp/stylesheets/style.css

index b07942374378662758fd22dc495c46043085ec54..02ca77ea58199de5403af0e3be44345172104ce9 100644 (file)
@@ -424,6 +424,8 @@ action_plans.date_format_help=The date should be entered using the following pat
 action_plans.date_not_valid=Date not valid
 action_plans.date_cant_be_in_past=The dead-line can't be in the past
 action_plans.x_out_of_x_reviews_solved={0} of {1} reviews solved
+action_plans.resolved_reviews_x_percent=Resolved reviews - {0}% ({1} reviews)
+action_plans.open_reviews_x_percent=Open reviews - {0}% ({1} reviews)
 
 
 #------------------------------------------------------------------------------
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/project_reviews_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/project_reviews_controller.rb
new file mode 100644 (file)
index 0000000..e20078c
--- /dev/null
@@ -0,0 +1,51 @@
+#
+# Sonar, entreprise quality control 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
+#
+
+class ProjectReviewsController < ApplicationController
+
+  SECTION=Navigation::SECTION_RESOURCE
+
+  def index
+    @project=Project.by_key(params[:projects])
+    not_found("Project not found") unless @project
+    access_denied unless is_admin?(@project)
+    
+    found_reviews = Review.search(params)
+    @reviews = select_authorized(:user, found_reviews, :project)
+    if found_reviews.size != @reviews.size
+      @security_exclusions = true
+    end
+
+    # table pagination
+    @page_size = 20
+    @page_size = params[:page_size].to_i if Api::Utils.is_number?(params[:page_size]) && params[:page_size].to_i > 5
+    @total_number = @reviews.size
+    if @reviews.size > @page_size
+      @page_id = (params[:page_id] ? params[:page_id].to_i : 1)
+      @page_count = @reviews.size / @page_size
+      @page_count += 1 if (@reviews.size % @page_size > 0)
+      from = (@page_id-1) * @page_size
+      to = (@page_id*@page_size)-1
+      to = @reviews.size-1 if to >= @reviews.size
+      @reviews = @reviews[from..to]
+    end
+  end
+
+end
index 34e20850ec71df31b6f4ec44a166d9f77a729282..720462165397dd8d4bda564b1d8c30aa3e6a6e4a 100644 (file)
@@ -51,7 +51,7 @@ class ActionPlan < ActiveRecord::Base
   def progress
     total_reviews = reviews.size
     open_reviews = reviews.select{|r| r.open? || r.reopened?}.size
-    {:total => total_reviews, :open => open_reviews}
+    {:total => total_reviews, :open => open_reviews, :resolved => total_reviews-open_reviews}
   end
 
   private
index 1d3740b9405120ab94db56188b3bc6610ab1e77b..e7dcdae8766fe4f3088930c013467a06ffa8e08d 100644 (file)
@@ -227,7 +227,6 @@ class Review < ActiveRecord::Base
     conditions=[]
     values={}
 
-
     if options['id'].present?
       conditions << 'id=:id'
       values[:id]=options['id'].to_i
@@ -236,8 +235,7 @@ class Review < ActiveRecord::Base
       conditions << 'id in (:ids)'
       values[:ids]=ids.map { |id| id.to_i }
     else
-
-
+      
       # --- 'review_type' is deprecated since 2.9 ---
       # Following code just for backward compatibility
       review_type = options['review_type']
@@ -321,6 +319,15 @@ class Review < ActiveRecord::Base
           values[:assignees]=assignees.map { |user_id| user_id.to_i }
         end
       end
+      
+      action_plan_id = options['action_plan_id']
+      if action_plan_id
+        action_plan = ActionPlan.find action_plan_id.to_i, :include => 'reviews'
+        if action_plan
+          conditions << 'id in (:ids)'
+          values[:ids]=action_plan.reviews.map { |r| r.id }
+        end
+      end
 
       from=options['from']
       if from
index 9ba9f66dc4a8254cc753a4a40a823dfee901f198..23bdfa2e52ba7fa185eda5d50a43705bb7dc38b6 100644 (file)
@@ -1,15 +1,34 @@
 <%
-  open_reviews_url = link_to action_plan.progress[:open].to_s, 
-                             :controller => 'reviews', :action => 'index', 
-                             :action_plans => [action_plan.id], :statuses => [Review::STATUS_OPEN, Review::STATUS_REOPENED], :assignee_login => ''
+  unless action_plan.progress[:total]==0
+    options = {:controller => 'project_reviews', :action => 'index', :action_plan_id => action_plan.id, :projects => action_plan.project_id}
   
-  total_reviews_url = link_to action_plan.progress[:total].to_s, 
-                              :controller => 'reviews', :action => 'index', 
-                              :action_plans => [action_plan.id], :statuses => [''], :assignee_login => ''
+    resolved_reviews_link = link_to action_plan.progress[:resolved].to_s, options.merge({:statuses => "#{Review::STATUS_RESOLVED},#{Review::STATUS_CLOSED}"})
+    total_reviews_link = link_to action_plan.progress[:total].to_s, options
+
+    resolved_reviews_url = url_for options.merge({:statuses => "#{Review::STATUS_RESOLVED},#{Review::STATUS_CLOSED}"})
+    open_reviews_url = url_for options.merge({:statuses => "#{Review::STATUS_OPEN},#{Review::STATUS_REOPENED}"})
+  
+    percent_resolved = (action_plan.progress[:resolved]*100/action_plan.progress[:total]).to_i
+    percent_open = (action_plan.progress[:open]*100/action_plan.progress[:total]).to_i
+    
+    tooltip_resolved = message('action_plans.resolved_reviews_x_percent', :params => [percent_resolved.to_s, action_plan.progress[:resolved].to_s])
+    tooltip_open = message('action_plans.open_reviews_x_percent', :params => [percent_open.to_s, action_plan.progress[:open].to_s])
 %>
+
 <div class="progress">
-  <div class="bar">
-    <div style="width:<%= action_plan.progress[:total]==0 ? '0' : action_plan.progress[:open]*100/action_plan.progress[:total] -%>%;"> </div>
-  </div>
-  <div class="note"><%= message('action_plans.x_out_of_x_reviews_solved', :params => [open_reviews_url, total_reviews_url]) -%></div>
-</div>
\ No newline at end of file
+  <table>
+    <tbody>
+      <tr>
+        <td class="resolved" style="width:<%= percent_resolved -%>%;">
+          <a href="<%= resolved_reviews_url -%>" title="<%= tooltip_resolved -%>" alt="<%= tooltip_resolved -%>"></a>
+        </td>
+        <td class="open" style="width:<%= percent_open -%>%;">
+          <a href="<%= open_reviews_url -%>" title="<%= tooltip_open -%>" alt="<%= tooltip_open -%>"></a>
+        </td>
+      </tr>
+    </tbody>
+  </table>
+  <div class="note"><%= message('action_plans.x_out_of_x_reviews_solved', :params => [resolved_reviews_link, total_reviews_link]) -%></div>
+</div>
+
+<% end %>
\ No newline at end of file
index d5d21cafc1f27860163fe8e90660b5bc9216e35f..16425cfdf1bbcbaf581b50b8e5b27bcee866d23a 100644 (file)
@@ -3,6 +3,7 @@
     vertical-align: top;
   }
   #actionPlans td.progress {
+    width: 300px;
     padding: 0px 40px;
   }    
 </style>
@@ -39,7 +40,7 @@
     <tr>
       <td class="thin nowrap center"><img src="<%= ApplicationController.root_context -%>/images/status/<%= plan.status -%>.png" title="<%= message(plan.status.downcase).capitalize -%>"/></td>
       <td class="thin nowrap"><%= h(plan.name) -%></td>
-      <td class="thin nowrap" align="right"><%= plan.dead_line ? plan.dead_line.strftime('%Y/%m/%d') : ' '  -%></td>
+      <td class="thin nowrap" align="right" x="<%= plan.dead_line ? plan.dead_line.tv_sec : '' -%>"><%= plan.dead_line ? l(plan.dead_line) : ' '  -%></td>
       <td class="progress thin">
         <%= render :partial => 'progress', :locals => {:action_plan => plan} -%>
       </td>
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/project_reviews/index.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/project_reviews/index.html.erb
new file mode 100644 (file)
index 0000000..de1a581
--- /dev/null
@@ -0,0 +1,6 @@
+<div id="reviews-search">
+  <h1><%= message('reviews') -%></h1>
+
+  <%= render :partial => "reviews/list" -%>
+
+</div>
\ No newline at end of file
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/reviews/_list.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/reviews/_list.html.erb
new file mode 100644 (file)
index 0000000..945b680
--- /dev/null
@@ -0,0 +1,106 @@
+  <%
+     if @reviews && !@reviews.empty?
+  %>
+    <% if @false_positives=='only' %>
+      <span class="falsePositive"><%= message('reviews.showing_false_positives_only') -%></span>
+    <% end %>
+    <%
+       if params[:from] && params[:to]
+         from = Time.parse(params[:from])
+         to = Time.parse(params[:to])
+    %>
+      <div style="color:#777777; font-size:93%; padding: 4px 0px 4px 10px;">
+        <span style="background-color: #FFF6BF; padding-left: 5px; padding-right: 5px;">
+          <%= message('reviews.reviews_filtered_by_date_x_to_y', :params => [l(from, :format => '%d %B %Y'), l(to, :format => '%d %B %Y')]) -%>
+        </span>
+      </div>
+    <% end %>
+
+    <table id="reviews-list" class="data width100">
+      <thead>
+      <tr>
+        <th width="1%" nowrap>
+          <a href="#" onClick="launchSearch('status', this)"><%= message('status_abbreviated') -%></a>
+          <%= image_tag(@asc ? "asc12.png" : "desc12.png") if @sort == 'status' -%>
+        </th>
+        <th width="1%" nowrap>
+          <a href="#" onClick="launchSearch('id', this)"><%= message('identifier_abbreviated') -%></a>
+          <%= image_tag(@asc ? "asc12.png" : "desc12.png") if @sort == 'id' -%>
+        </th>
+        <th width="1%" nowrap>
+          <a href="#" onClick="launchSearch('severity', this)"><%= message('severity_abbreviated') -%></a>
+          <%= image_tag(@asc ? "asc12.png" : "desc12.png") if @sort == 'severity' -%>
+        </th>
+        <th>
+          <a href="#" onClick="launchSearch('title', this)"><%= message('title') -%></a>
+          <%= image_tag(@asc ? "asc12.png" : "desc12.png") if @sort == 'title' -%>
+        </th>
+        <th width="1%"><%= message('project') -%></th>
+        <th><%= message('assignee') -%></th>
+        <th>
+          <a href="#" onClick="launchSearch('updated_at', this)"><%= message('age') -%></a>
+          <%= image_tag(@asc ? "asc12.png" : "desc12.png") if @sort == 'updated_at' -%>
+        </th>
+      </tr>
+      </thead>
+      <tfoot>
+      <tr>
+        <td colspan="6">
+          <%= @total_number -%> <%= message('results').downcase -%>
+          <% if @page_count %>
+            |
+            <%= link_to_if @page_id>1, message('paging_previous'), {:overwrite_params => {:page_id => @page_id-1}} %>
+            <% for index in 1..@page_count %>
+              <%= link_to_unless index==@page_id, index.to_s, {:overwrite_params => {:page_id => index}} %>
+            <% end %>
+            <%= link_to_if @page_id<@page_count, message('paging_next'), {:overwrite_params => {:page_id => 1+@page_id}} %>
+          <% end %>
+        </td>
+      </tr>
+      </tfoot>
+      <tbody>
+      <%
+         @reviews.each do |review|
+           comment = review.comments.last
+      %>
+        <tr class="<%= cycle('even', 'odd') -%>">
+          <td><img src="<%= ApplicationController.root_context -%>/images/status/<%= review.status -%>.png" title="<%= message(review.status.downcase).capitalize -%>"/></td>
+          <td>
+            <%= link_to h(review.id), :controller => "reviews", :action => "view", :id => review.id -%>
+          </td>
+          <td><img src="<%= ApplicationController.root_context -%>/images/priority/<%= review.severity -%>.png" title="<%= message(review.severity.downcase).capitalize -%>"/></td>
+          <td>
+            <%= link_to h(review.title), :controller => "reviews", :action => "view", :id => review.id -%>
+            <% if comment %>
+              <div class="comment-excerpt">
+                <img src="<%= ApplicationController.root_context -%>/images/reviews/comment.png"/>
+                &nbsp;<b><%= comment.user.name -%> :</b>
+                <%= comment.excerpt -%>
+            <% end %>
+            </div>
+          </td>
+          <td>
+            <span class="nowrap"><%= review.project.name -%></span>
+            <br/>
+            <span class="note"><%= review.resource.name -%></span></td>
+          <td><%= review.assignee ? h(review.assignee.name) : '-' -%></td>
+          <td><%= distance_of_time_in_words_to_now(review.updated_at) -%></td>
+        </tr>
+      <%
+         end
+      %>
+      </tbody>
+    </table>
+  <%
+     elsif @reviews
+  %>
+    <p><%= message('no_results') -%></p>
+  <%
+     end
+  %>
+
+  <% if @security_exclusions %>
+    <br/>
+
+    <p class="notes"><%= message('results_not_display_due_to_security') -%></p>
+  <% end %>
\ No newline at end of file
index 31648dc70e27cc2678736a9161d8a03c48cd33b6..9c7783e4e0a19ab30fb685d3edc2e82edaf8d56e 100644 (file)
@@ -1,26 +1,3 @@
-<script>
-  function reviewIdFieldModified(field) {
-    if (field.value != '') {
-      $('statuses').value = ''
-      $('severities').value = ''
-      $('projects').value = ''
-      $('author_login').value = ''
-      $('autocompleteText-author_login').value = ''
-      $('assignee_login').value = ''
-      $('autocompleteText-assignee_login').value = ''
-      $('false_positives').value = 'with'
-    }
-  }
-  function launchSearch(columnName, link) {
-    $('sort').value = columnName
-    if ($('asc').value == "true") {
-      $('asc').value = "false";
-    } else {
-      $('asc').value = "true";
-    }
-    document.forms[0].submit()
-  }
-</script>
 <div id="reviews-search">
   <h1><%= message('reviews') -%></h1>
   <% form_tag({:action => 'index'}, {:method => 'get'}) do %>
   <% end %>
 
 
-  <%
-     if @reviews && !@reviews.empty?
-  %>
-    <% if @false_positives=='only' %>
-      <span class="falsePositive"><%= message('reviews.showing_false_positives_only') -%></span>
-    <% end %>
-    <%
-       if params[:from] && params[:to]
-         from = Time.parse(params[:from])
-         to = Time.parse(params[:to])
-    %>
-      <div style="color:#777777; font-size:93%; padding: 4px 0px 4px 10px;">
-        <span style="background-color: #FFF6BF; padding-left: 5px; padding-right: 5px;">
-          <%= message('reviews.reviews_filtered_by_date_x_to_y', :params => [l(from, :format => '%d %B %Y'), l(to, :format => '%d %B %Y')]) -%>
-        </span>
-      </div>
-    <% end %>
-
-    <table id="reviews-list" class="data width100">
-      <thead>
-      <tr>
-        <th width="1%" nowrap>
-          <a href="#" onClick="launchSearch('status', this)"><%= message('status_abbreviated') -%></a>
-          <%= image_tag(@asc ? "asc12.png" : "desc12.png") if @sort == 'status' -%>
-        </th>
-        <th width="1%" nowrap>
-          <a href="#" onClick="launchSearch('id', this)"><%= message('identifier_abbreviated') -%></a>
-          <%= image_tag(@asc ? "asc12.png" : "desc12.png") if @sort == 'id' -%>
-        </th>
-        <th width="1%" nowrap>
-          <a href="#" onClick="launchSearch('severity', this)"><%= message('severity_abbreviated') -%></a>
-          <%= image_tag(@asc ? "asc12.png" : "desc12.png") if @sort == 'severity' -%>
-        </th>
-        <th>
-          <a href="#" onClick="launchSearch('title', this)"><%= message('title') -%></a>
-          <%= image_tag(@asc ? "asc12.png" : "desc12.png") if @sort == 'title' -%>
-        </th>
-        <th width="1%"><%= message('project') -%></th>
-        <th><%= message('assignee') -%></th>
-        <th>
-          <a href="#" onClick="launchSearch('updated_at', this)"><%= message('age') -%></a>
-          <%= image_tag(@asc ? "asc12.png" : "desc12.png") if @sort == 'updated_at' -%>
-        </th>
-      </tr>
-      </thead>
-      <tfoot>
-      <tr>
-        <td colspan="6">
-          <%= @total_number -%> <%= message('results').downcase -%>
-          <% if @page_count %>
-            |
-            <%= link_to_if @page_id>1, message('paging_previous'), {:overwrite_params => {:page_id => @page_id-1}} %>
-            <% for index in 1..@page_count %>
-              <%= link_to_unless index==@page_id, index.to_s, {:overwrite_params => {:page_id => index}} %>
-            <% end %>
-            <%= link_to_if @page_id<@page_count, message('paging_next'), {:overwrite_params => {:page_id => 1+@page_id}} %>
-          <% end %>
-        </td>
-      </tr>
-      </tfoot>
-      <tbody>
-      <%
-         @reviews.each do |review|
-           comment = review.comments.last
-      %>
-        <tr class="<%= cycle('even', 'odd') -%>">
-          <td><img src="<%= ApplicationController.root_context -%>/images/status/<%= review.status -%>.png" title="<%= message(review.status.downcase).capitalize -%>"/></td>
-          <td>
-            <%= link_to h(review.id), :controller => "reviews", :action => "view", :id => review.id -%>
-          </td>
-          <td><img src="<%= ApplicationController.root_context -%>/images/priority/<%= review.severity -%>.png" title="<%= message(review.severity.downcase).capitalize -%>"/></td>
-          <td>
-            <%= link_to h(review.title), :controller => "reviews", :action => "view", :id => review.id -%>
-            <% if comment %>
-              <div class="comment-excerpt">
-                <img src="<%= ApplicationController.root_context -%>/images/reviews/comment.png"/>
-                &nbsp;<b><%= comment.user.name -%> :</b>
-                <%= comment.excerpt -%>
-            <% end %>
-            </div>
-          </td>
-          <td>
-            <span class="nowrap"><%= review.project.name -%></span>
-            <br/>
-            <span class="note"><%= review.resource.name -%></span></td>
-          <td><%= review.assignee ? h(review.assignee.name) : '-' -%></td>
-          <td><%= distance_of_time_in_words_to_now(review.updated_at) -%></td>
-        </tr>
-      <%
-         end
-      %>
-      </tbody>
-    </table>
-  <%
-     elsif @reviews
-  %>
-    <p><%= message('no_results') -%></p>
-  <%
-     end
-  %>
-
-  <% if @security_exclusions %>
-    <br/>
-
-    <p class="notes"><%= message('results_not_display_due_to_security') -%></p>
-  <% end %>
+  <%= render :partial => "list" -%>
 
 </div>
 
 <script>
+  function reviewIdFieldModified(field) {
+    if (field.value != '') {
+      $('statuses').value = ''
+      $('severities').value = ''
+      $('projects').value = ''
+      $('author_login').value = ''
+      $('autocompleteText-author_login').value = ''
+      $('assignee_login').value = ''
+      $('autocompleteText-assignee_login').value = ''
+      $('false_positives').value = 'with'
+    }
+  }
+  function launchSearch(columnName, link) {
+    $('sort').value = columnName
+    if ($('asc').value == "true") {
+      $('asc').value = "false";
+    } else {
+      $('asc').value = "true";
+    }
+    document.forms[0].submit()
+  }
+
   $('review_id').focus();
 </script>
\ No newline at end of file
index 7b44c075b92fd8a83276b36da8133f21dd0cc742..9fb6b224d750f8d087fb7cf11b7bd9e0ee1873b4 100644 (file)
@@ -1226,22 +1226,30 @@ div.comment-excerpt {
 /* ACTION PLANS */
 
 div.progress {
-  width: 400px;
+  width: 100%;
   margin: 4px;
 }
 
-div.progress div.bar {
+div.progress table {
   width: 100%;
-  background-color: #CC0000;
-  border: none;
-  margin-bottom: 4px;
 }
 
-div.progress > div > div {
+div.progress td {
   height: 10px;
+}
+
+div.progress td a {
+  display: block; width: 100%; height: 100%;
+}
+
+div.progress td.resolved {
   background-color: #078C00;
 }
 
+div.progress td.open {
+  background-color: #CC0000;
+}
+
 div.progress div.note {
   color: #777777;
   font-size: 93%;