]> source.dussan.org Git - redmine.git/commitdiff
Makes spent time column available on the issue list (#971).
authorJean-Philippe Lang <jp_lang@yahoo.fr>
Sun, 4 Dec 2011 16:43:32 +0000 (16:43 +0000)
committerJean-Philippe Lang <jp_lang@yahoo.fr>
Sun, 4 Dec 2011 16:43:32 +0000 (16:43 +0000)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@8073 e93f8b46-1217-0410-a6f0-8f06a7374b81

app/helpers/queries_helper.rb
app/models/issue.rb
app/models/query.rb
app/views/issues/show.html.erb
test/functional/issues_controller_test.rb
test/unit/query_test.rb

index 862bc71b68b5472b86da29402c4bf171ac130453..7c43be12c21fd33ad77d52add5aead684d2ddd52 100644 (file)
@@ -44,6 +44,8 @@ module QueriesHelper
     when 'Fixnum', 'Float'
       if column.name == :done_ratio
         progress_bar(value, :width => '80px')
+      elsif  column.name == :spent_hours
+        sprintf "%.2f", value
       else
         h(value.to_s)
       end
index cbe3dd65949005ee63dc6645420a2137370b7fed..9eb567c9e67829867b6241979bbfe51d67b7b3a0 100644 (file)
@@ -499,13 +499,18 @@ class Issue < ActiveRecord::Base
     notified.collect(&:mail)
   end
 
+  # Returns the number of hours spent on this issue
+  def spent_hours
+    @spent_hours ||= time_entries.sum(:hours) || 0
+  end
+
   # Returns the total number of hours spent on this issue and its descendants
   #
   # Example:
   #   spent_hours => 0.0
   #   spent_hours => 50.2
-  def spent_hours
-    @spent_hours ||= self_and_descendants.sum("#{TimeEntry.table_name}.hours", :include => :time_entries).to_f || 0.0
+  def total_spent_hours
+    @total_spent_hours ||= self_and_descendants.sum("#{TimeEntry.table_name}.hours", :include => :time_entries).to_f || 0.0
   end
 
   def relations
@@ -522,6 +527,16 @@ class Issue < ActiveRecord::Base
     end
   end
 
+  # Preloads visible spent time for a collection of issues
+  def self.load_visible_spent_hours(issues, user=User.current)
+    if issues.any?
+      hours_by_issue_id = TimeEntry.visible(user).sum(:hours, :group => :issue_id)
+      issues.each do |issue|
+        issue.instance_variable_set "@spent_hours", (hours_by_issue_id[issue.id] || 0)
+      end
+    end
+  end
+
   # Finds an issue relation given its id.
   def find_relation(relation_id)
     IssueRelation.find(relation_id, :conditions => ["issue_to_id = ? OR issue_from_id = ?", id, id])
index f3b83d0f4680977bd023335c5b1f693d7c698d92..f41c1cdaa11f24576b5f0c51c4a97a91da54f1bd 100644 (file)
@@ -356,11 +356,23 @@ class Query < ActiveRecord::Base
 
   def available_columns
     return @available_columns if @available_columns
-    @available_columns = ::Query.available_columns
+    @available_columns = ::Query.available_columns.dup
     @available_columns += (project ?
                             project.all_issue_custom_fields :
                             IssueCustomField.find(:all)
                            ).collect {|cf| QueryCustomFieldColumn.new(cf) }
+
+    if User.current.allowed_to?(:view_time_entries, project, :global => true)
+      index = @available_columns.index {|column| column.name == :estimated_hours}
+      index = (index ? index + 1 : -1)
+      # insert the column after estimated_hours or at the end
+      @available_columns.insert index, QueryColumn.new(:spent_hours,
+        :sortable => "(SELECT SUM(hours) FROM #{TimeEntry.table_name} WHERE #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id)",
+        :default_order => 'desc',
+        :caption => :label_spent_time
+      )
+    end
+    @available_columns
   end
 
   def self.available_columns=(v)
@@ -412,7 +424,7 @@ class Query < ActiveRecord::Base
   end
 
   def has_column?(column)
-    column_names && column_names.include?(column.name)
+    column_names && column_names.include?(column.is_a?(QueryColumn) ? column.name : column)
   end
 
   def has_default_columns?
@@ -561,12 +573,17 @@ class Query < ActiveRecord::Base
     
     joins = (order_option && order_option.include?('authors')) ? "LEFT OUTER JOIN users authors ON authors.id = #{Issue.table_name}.author_id" : nil
 
-    Issue.visible.scoped(:conditions => options[:conditions]).find :all, :include => ([:status, :project] + (options[:include] || [])).uniq,
+    issues = Issue.visible.scoped(:conditions => options[:conditions]).find :all, :include => ([:status, :project] + (options[:include] || [])).uniq,
                      :conditions => statement,
                      :order => order_option,
                      :joins => joins,
                      :limit  => options[:limit],
                      :offset => options[:offset]
+
+    if has_column?(:spent_hours)
+      Issue.load_visible_spent_hours(issues)
+    end
+    issues
   rescue ::ActiveRecord::StatementInvalid => e
     raise StatementInvalid.new(e.message)
   end
index 491966b2224df578b44d3d8410f7edb5f4cbe5e0..3302bbb7a246e04b4d1669356f8f5878b6a84ea3 100644 (file)
@@ -32,7 +32,7 @@
     <th class="category"><%=l(:field_category)%>:</th><td class="category"><%=h(@issue.category ? @issue.category.name : "-") %></td>
     <% if User.current.allowed_to?(:view_time_entries, @project) %>
     <th class="spent-time"><%=l(:label_spent_time)%>:</th>
-    <td class="spent-time"><%= @issue.spent_hours > 0 ? (link_to l_hours(@issue.spent_hours), {:controller => 'timelog', :action => 'index', :project_id => @project, :issue_id => @issue}) : "-" %></td>
+    <td class="spent-time"><%= @issue.total_spent_hours > 0 ? (link_to l_hours(@issue.total_spent_hours), {:controller => 'timelog', :action => 'index', :project_id => @project, :issue_id => @issue}) : "-" %></td>
     <% end %>
 </tr>
 <tr>
index f6821284d8e3b0bc6a4765899e0e069a40dec604..24e16e78816119ad8d6ac6ef09000f9bc6290dd6 100644 (file)
@@ -537,6 +537,13 @@ class IssuesControllerTest < ActionController::TestCase
     get :index, :group_by => 'author', :sort => 'priority'
     assert_response :success
   end
+  
+  def test_index_group_by_spent_hours
+    get :index, :group_by => 'author', :sort => 'spent_hours:desc'
+    assert_response :success
+    hours = assigns(:issues).collect(&:spent_hours)
+    assert_equal hours.sort.reverse, hours
+  end
 
   def test_index_with_columns
     columns = ['tracker', 'subject', 'assigned_to']
@@ -615,6 +622,22 @@ class IssuesControllerTest < ActionController::TestCase
       }
   end
 
+  def test_index_with_spent_hours_column
+    get :index, :set_filter => 1, :c => %w(subject spent_hours)
+
+    assert_tag 'tr', :attributes => {:id => 'issue-3'},
+      :child => {
+        :tag => 'td', :attributes => {:class => /spent_hours/}, :content => '1.00'
+      }
+  end
+
+  def test_index_should_not_show_spent_hours_column_without_permission
+    Role.anonymous.remove_permission! :view_time_entries
+    get :index, :set_filter => 1, :c => %w(subject spent_hours)
+
+    assert_no_tag 'td', :attributes => {:class => /spent_hours/}
+  end
+
   def test_index_with_fixed_version
     get :index, :set_filter => 1, :c => %w(fixed_version)
     assert_tag 'td', :attributes => {:class => /fixed_version/},
index e3f3c9249fcacbe956fe787011d666576c20912f..f298dd59f3946db2774489d09aac88afb906ba92 100644 (file)
@@ -423,6 +423,13 @@ class QueryTest < ActiveSupport::TestCase
     assert q.has_column?(c)
   end
 
+  def test_query_should_preload_spent_hours
+    q = Query.new(:name => '_', :column_names => [:subject, :spent_hours])
+    assert q.has_column?(:spent_hours)
+    issues = q.issues
+    assert_not_nil issues.first.instance_variable_get("@spent_hours")
+  end
+
   def test_groupable_columns_should_include_custom_fields
     q = Query.new
     assert q.groupable_columns.detect {|c| c.is_a? QueryCustomFieldColumn}