]> source.dussan.org Git - redmine.git/commitdiff
Adds a Setting to control how an Issue's done_ratio is calculated:
authorEric Davis <edavis@littlestreamsoftware.com>
Fri, 11 Dec 2009 18:48:34 +0000 (18:48 +0000)
committerEric Davis <edavis@littlestreamsoftware.com>
Fri, 11 Dec 2009 18:48:34 +0000 (18:48 +0000)
* Issue field (default) - the done_ratio field for the Issue
* Issue status - uses the Issue Status's value

  #4274

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3151 e93f8b46-1217-0410-a6f0-8f06a7374b81

18 files changed:
app/controllers/issue_statuses_controller.rb
app/models/issue.rb
app/models/issue_status.rb
app/views/issue_statuses/_form.rhtml
app/views/issue_statuses/list.rhtml
app/views/issues/_attributes.rhtml
app/views/issues/_form_update.rhtml
app/views/issues/bulk_edit.rhtml
app/views/issues/context_menu.rhtml
app/views/settings/_issues.rhtml
config/locales/en.yml
config/settings.yml
db/migrate/20091123212029_add_default_done_ratio_to_issue_status.rb [new file with mode: 0644]
public/images/table_multiple.png [new file with mode: 0644]
public/stylesheets/application.css
test/functional/issue_statuses_controller_test.rb
test/unit/issue_status_test.rb
test/unit/issue_test.rb

index bee7f4833b51e17c6646c47f9f19bc0fd78eda3b..3be6abf3f90a258420b470194e5e7018de66c9d5 100644 (file)
@@ -18,7 +18,7 @@
 class IssueStatusesController < ApplicationController
   before_filter :require_admin
 
-  verify :method => :post, :only => [ :destroy, :create, :update, :move ],
+  verify :method => :post, :only => [ :destroy, :create, :update, :move, :update_issue_done_ratio ],
          :redirect_to => { :action => :list }
          
   def index
@@ -66,4 +66,13 @@ class IssueStatusesController < ApplicationController
     flash[:error] = "Unable to delete issue status"
     redirect_to :action => 'list'
   end          
+  
+  def update_issue_done_ratio
+    if IssueStatus.update_issue_done_ratios
+      flash[:notice] = l(:notice_issue_done_ratios_updated)
+    else
+      flash[:error] =  l(:error_issue_done_ratios_not_updated)
+    end
+    redirect_to :action => 'list'
+  end
 end
index a0f47fdb866befce14a23385534950d2095b1bb6..2062e58e8b0c198f34088b8b5e19fbba818611a6 100644 (file)
@@ -45,6 +45,8 @@ class Issue < ActiveRecord::Base
   
   acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
                             :author_key => :author_id
+
+  DONE_RATIO_OPTIONS = %w(issue_field issue_status)
   
   validates_presence_of :subject, :priority, :project, :tracker, :author, :status
   validates_length_of :subject, :maximum => 255
@@ -55,7 +57,8 @@ class Issue < ActiveRecord::Base
                                           :conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } }
   
   named_scope :open, :conditions => ["#{IssueStatus.table_name}.is_closed = ?", false], :include => :status
-  
+
+  before_save :update_done_ratio_from_issue_status
   after_save :create_journal
   
   # Returns true if usr or current user is allowed to view the issue
@@ -162,6 +165,22 @@ class Issue < ActiveRecord::Base
     write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
   end
   
+  def done_ratio
+    if Issue.use_status_for_done_ratio? && !self.status.default_done_ratio.blank?
+      self.status.default_done_ratio
+    else
+      read_attribute(:done_ratio)
+    end
+  end
+
+  def self.use_status_for_done_ratio?
+    Setting.issue_done_ratio == 'issue_status'
+  end
+
+  def self.use_field_for_done_ratio?
+    Setting.issue_done_ratio == 'issue_field'
+  end
+  
   def validate
     if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
       errors.add :due_date, :not_a_date
@@ -198,6 +217,14 @@ class Issue < ActiveRecord::Base
     end
   end
   
+  # Set the done_ratio using the status if that setting is set.  This will keep the done_ratios
+  # even if the user turns off the setting later
+  def update_done_ratio_from_issue_status
+    if Issue.use_status_for_done_ratio? && !self.status.default_done_ratio.blank?
+      self.done_ratio = self.status.default_done_ratio
+    end
+  end
+  
   def after_save
     # Reload is needed in order to get the right status
     reload
index ca33d37d6500e9fe446232299d5c1ca8461ab4b1..a9c1db584c7649719818e92c4af5d0f5ddcf470d 100644 (file)
@@ -33,6 +33,18 @@ class IssueStatus < ActiveRecord::Base
   def self.default
     find(:first, :conditions =>["is_default=?", true])
   end
+  
+  # Update all the +Issues+ setting their done_ratio to the value of their +IssueStatus+
+  def self.update_issue_done_ratios
+    if Issue.use_status_for_done_ratio?
+      IssueStatus.find(:all, :conditions => ["default_done_ratio >= 0"]).each do |status|
+        Issue.update_all(["done_ratio = ?", status.default_done_ratio],
+                         ["status_id = ?", status.id])
+      end
+    end
+
+    return Issue.use_status_for_done_ratio?
+  end
 
   # Returns an array of all statuses the given role can switch to
   # Uses association cache when called more than one time
index b6a5bc19f764af01f16ab47f224a4d983ed1ea47..e36dec8244745f5186ea5642c05d8da2413fbf64 100644 (file)
@@ -5,6 +5,11 @@
 <p><label for="issue_status_name"><%=l(:field_name)%><span class="required"> *</span></label>
 <%= text_field 'issue_status', 'name'  %></p>
 
+<% if Issue.use_status_for_done_ratio? %>
+<p><label for="issue_done_ratio"><%=l(:field_done_ratio)%></label>
+<%= select 'issue_status', :default_done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
+<% end %>
+
 <p><label for="issue_status_is_closed"><%=l(:field_is_closed)%></label>
 <%= check_box 'issue_status', 'is_closed' %></p>
 
index ca973153d4c515972c8b8e4aea437ab9b43b6965..a98ed0cd83575d3a57b1693c18774e5a48aa99af 100644 (file)
@@ -1,5 +1,6 @@
 <div class="contextual">
 <%= link_to l(:label_issue_status_new), {:action => 'new'}, :class => 'icon icon-add' %>
+<%= link_to(l(:label_update_issue_done_ratios), {:action => 'update_issue_done_ratio'}, :class => 'icon icon-multiple', :method => 'post', :confirm => l(:text_are_you_sure)) if Issue.use_status_for_done_ratio? %>
 </div>
 
 <h2><%=l(:label_issue_status_plural)%></h2>
@@ -7,6 +8,9 @@
 <table class="list">
   <thead><tr>
   <th><%=l(:field_status)%></th>
+  <% if Issue.use_status_for_done_ratio? %>
+  <th><%=l(:field_done_ratio)%></th>
+  <% end %>
   <th><%=l(:field_is_default)%></th>
   <th><%=l(:field_is_closed)%></th>
   <th><%=l(:button_sort)%></th>
@@ -16,6 +20,9 @@
 <% for status in @issue_statuses %>
   <tr class="<%= cycle("odd", "even") %>">
   <td><%= link_to status.name, :action => 'edit', :id => status %></td>
+  <% if Issue.use_status_for_done_ratio? %>
+  <td align="center"><%= h status.default_done_ratio %></td>
+  <% end %>
   <td align="center"><%= image_tag 'true.png' if status.is_default? %></td>
   <td align="center"><%= image_tag 'true.png' if status.is_closed? %></td>
   <td align="center" style="width:15%;"><%= reorder_links('issue_status', {:action => 'update', :id => status}) %></td>
index 9adf0f153e5c73354ef62d767cd0d4fd7f574eda..f8fc8d67294b5e12c639fab36e16388cae017897 100644 (file)
@@ -34,7 +34,9 @@
 <p><%= f.text_field :start_date, :size => 10 %><%= calendar_for('issue_start_date') %></p>
 <p><%= f.text_field :due_date, :size => 10 %><%= calendar_for('issue_due_date') %></p>
 <p><%= f.text_field :estimated_hours, :size => 3 %> <%= l(:field_hours) %></p>
+<% if Issue.use_field_for_done_ratio? %>
 <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
+<% end %>
 </div>
 
 <div style="clear:both;"> </div>
index 5304ee23a585319ef080ba09b626f5685a0c22f0..e29c41a7ca9c5a151c8ce84ffef36b5ce0ed5e9b 100644 (file)
@@ -4,7 +4,9 @@
 <p><%= f.select :assigned_to_id, (@issue.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true %></p>
 </div>
 <div class="splitcontentright">
+<% if Issue.use_field_for_done_ratio? %>
 <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
+<% end %>
 <% unless @issue.assignable_versions.empty? %>
 <p><%= f.select :fixed_version_id, (@issue.assignable_versions.collect {|v| [v.name, v.id]}), :include_blank => true %></p>
 <% end %>
index f428566b295a13791ae1a0d3e93af11e7a2ebd6a..b298c3c4663b5275e7b8dbbeb672a399c0e4ee8c 100644 (file)
 <%= text_field_tag 'start_date', '', :size => 10 %><%= calendar_for('start_date') %></label>
 <label><%= l(:field_due_date) %>: 
 <%= text_field_tag 'due_date', '', :size => 10 %><%= calendar_for('due_date') %></label>
+<% if Issue.use_field_for_done_ratio? %>
 <label><%= l(:field_done_ratio) %>: 
 <%= select_tag 'done_ratio', options_for_select([[l(:label_no_change_option), '']] + (0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></label>
+<% end %>
 </p>
 
 <% @custom_fields.each do |custom_field| %>
index 6cb05606abc373013c4899c8eb8510b3e41cff41..4a1d0c310f26e147ea0d752066ce843beedb0f04 100644 (file)
@@ -77,6 +77,7 @@
                </ul>
        </li>
        <% end -%>
+  <% if Issue.use_field_for_done_ratio? %>
        <li class="folder">
                <a href="#" class="submenu"><%= l(:field_done_ratio) %></a>
                <ul>
@@ -86,7 +87,7 @@
                <% end -%>
                </ul>
        </li>
-       
+  <% end %>
 <% if !@issue.nil? %>
        <% if @can[:log_time] -%>
        <li><%= context_menu_link l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue},
index b0277fab3a62d0ddba47bf6419757b8d7b2937e9..725d57da6ecc3cb57693d9238eba21d0f00240d1 100644 (file)
@@ -11,6 +11,9 @@
 <%= check_box_tag 'settings[display_subprojects_issues]', 1, Setting.display_subprojects_issues? %>
 </p>
 
+<p><label><%= l(:setting_issue_done_ratio) %></label>
+<%= select_tag 'settings[issue_done_ratio]', options_for_select(Issue::DONE_RATIO_OPTIONS.collect {|i| [l(i.to_sym), i]}, Setting.issue_done_ratio) %></p>
+
 <p><label><%= l(:setting_issues_export_limit) %></label>
 <%= text_field_tag 'settings[issues_export_limit]', Setting.issues_export_limit, :size => 6 %></p>
 </div>
index 184788eeedfbc8a9a5db3806834a0e14dabf9e83..6a439a33734dc5f533df901771acc6202ae6a29a 100644 (file)
@@ -147,6 +147,7 @@ en:
   notice_account_pending: "Your account was created and is now pending administrator approval."
   notice_default_data_loaded: Default configuration successfully loaded.
   notice_unable_delete_version: Unable to delete version.
+  notice_issue_done_ratios_updated: Issue done ratios updated.
   
   error_can_t_load_default_data: "Default configuration could not be loaded: {{value}}"
   error_scm_not_found: "The entry or revision was not found in the repository."
@@ -157,7 +158,8 @@ en:
   error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
   error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version can not be reopened'
   error_can_not_archive_project: This project can not be archived
-  
+  error_issue_done_ratios_not_updated: "Issue done ratios not updated."
+
   warning_attachments_not_saved: "{{count}} file(s) could not be saved."
   
   mail_subject_lost_password: "Your {{value}} password"
@@ -309,6 +311,7 @@ en:
   setting_sequential_project_identifiers: Generate sequential project identifiers
   setting_gravatar_enabled: Use Gravatar user icons
   setting_gravatar_default: Default Gravatar image
+  setting_issue_done_ratio: Calculate the issue done ratio with
   setting_diff_max_lines_displayed: Max number of diff lines displayed
   setting_file_max_size_displayed: Max size of text files displayed inline
   setting_repository_log_display_limit: Maximum number of revisions displayed on file log
@@ -716,6 +719,7 @@ en:
   label_version_sharing_hierarchy: With project hierarchy
   label_version_sharing_tree: With project tree
   label_version_sharing_system: With all projects
+  label_update_issue_done_ratios: Update issue done ratios
   
   button_login: Login
   button_submit: Submit
@@ -850,3 +854,6 @@ en:
   enumeration_doc_categories: Document categories
   enumeration_activities: Activities (time tracking)
   enumeration_system_activity: System Activity
+
+  issue_field: Use the issue field
+  issue_status: Use the issue status
index ca0cc3d7b339eb1574a1730da413b128e78ed517..754c0248ae277360536f8fa28250a70018b8defd 100644 (file)
@@ -129,6 +129,8 @@ issue_list_default_columns:
   - updated_on
 display_subprojects_issues:
   default: 1
+issue_done_ratio:
+  default: 'issue_field'
 default_projects_public:
   default: 1
 default_projects_modules:
diff --git a/db/migrate/20091123212029_add_default_done_ratio_to_issue_status.rb b/db/migrate/20091123212029_add_default_done_ratio_to_issue_status.rb
new file mode 100644 (file)
index 0000000..0ce6721
--- /dev/null
@@ -0,0 +1,9 @@
+class AddDefaultDoneRatioToIssueStatus < ActiveRecord::Migration
+  def self.up
+    add_column :issue_statuses, :default_done_ratio, :integer
+  end
+
+  def self.down
+    remove_column :issue_statuses, :default_done_ratio
+  end
+end
diff --git a/public/images/table_multiple.png b/public/images/table_multiple.png
new file mode 100644 (file)
index 0000000..d76448e
Binary files /dev/null and b/public/images/table_multiple.png differ
index afbee7638af404836334bdcf6e288bd0678ef37c..3d0c3b028ab7b4485a6d14e55f27e8aa03cf4b46 100644 (file)
@@ -702,6 +702,7 @@ vertical-align: middle;
 .icon-move { background-image: url(../images/move.png); }
 .icon-save { background-image: url(../images/save.png); }
 .icon-cancel { background-image: url(../images/cancel.png); }
+.icon-multiple { background-image: url(../images/table_multiple.png); }
 .icon-folder { background-image: url(../images/folder.png); }
 .open .icon-folder { background-image: url(../images/folder_open.png); }
 .icon-package { background-image: url(../images/package.png); }
index 5e012f7f49d59d840df2839ca94c5a1531cf578a..4f88433bdae0e7d5a2d341964469f76f2eaf4e17 100644 (file)
@@ -70,4 +70,27 @@ class IssueStatusesControllerTest < ActionController::TestCase
     assert_redirected_to 'issue_statuses/list'
     assert_not_nil IssueStatus.find_by_id(1)
   end
+
+  context "on POST to :update_issue_done_ratio" do
+    context "with Setting.issue_done_ratio using the issue_field" do
+      setup do
+        Setting.issue_done_ratio = 'issue_field'
+        post :update_issue_done_ratio
+      end
+
+      should_set_the_flash_to /not updated/
+      should_redirect_to('the list') { '/issue_statuses/list' }
+    end
+
+    context "with Setting.issue_done_ratio using the issue_status" do
+      setup do
+        Setting.issue_done_ratio = 'issue_status'
+        post :update_issue_done_ratio
+      end
+
+      should_set_the_flash_to /Issue done ratios updated/
+      should_redirect_to('the list') { '/issue_statuses/list' }
+    end
+  end
+  
 end
index 042f30e32085f9c09e638bed9102744217f4fc89..2c0685cec1f80ebe65dc74ed57306405e0898b2b 100644 (file)
@@ -66,4 +66,40 @@ class IssueStatusTest < ActiveSupport::TestCase
     status.reload
     assert status.is_default?
   end
+
+  context "#update_done_ratios" do
+    setup do
+      @issue = Issue.find(1)
+      @issue_status = IssueStatus.find(1)
+      @issue_status.update_attribute(:default_done_ratio, 50)
+    end
+    
+    context "with Setting.issue_done_ratio using the issue_field" do
+      setup do
+        Setting.issue_done_ratio = 'issue_field'
+      end
+      
+      should "change nothing" do
+        IssueStatus.update_issue_done_ratios
+
+        assert_equal 0, Issue.count(:conditions => {:done_ratio => 50})
+      end
+    end
+
+    context "with Setting.issue_done_ratio using the issue_status" do
+      setup do
+        Setting.issue_done_ratio = 'issue_status'
+      end
+      
+      should "update all of the issue's done_ratios to match their Issue Status" do
+        IssueStatus.update_issue_done_ratios
+        
+        issues = Issue.find([1,3,4,5,6,7,9,10])
+        issues.each do |issue|
+          assert_equal @issue_status, issue.status
+          assert_equal 50, issue.read_attribute(:done_ratio)
+        end
+      end
+    end
+  end
 end
index e91880c08dc1f0487f91bbee1d0ad7725cf234b9..fa9d56dbd9b69e34242aece0023f559c3dd2100a 100644 (file)
@@ -529,4 +529,64 @@ class IssueTest < ActiveSupport::TestCase
     end
     assert ActionMailer::Base.deliveries.empty?
   end
+
+  context "#done_ratio" do
+    setup do
+      @issue = Issue.find(1)
+      @issue_status = IssueStatus.find(1)
+      @issue_status.update_attribute(:default_done_ratio, 50)
+    end
+    
+    context "with Setting.issue_done_ratio using the issue_field" do
+      setup do
+        Setting.issue_done_ratio = 'issue_field'
+      end
+      
+      should "read the issue's field" do
+        assert_equal 0, @issue.done_ratio
+      end
+    end
+
+    context "with Setting.issue_done_ratio using the issue_status" do
+      setup do
+        Setting.issue_done_ratio = 'issue_status'
+      end
+      
+      should "read the Issue Status's default done ratio" do
+        assert_equal 50, @issue.done_ratio
+      end
+    end
+  end
+
+  context "#update_done_ratio_from_issue_status" do
+    setup do
+      @issue = Issue.find(1)
+      @issue_status = IssueStatus.find(1)
+      @issue_status.update_attribute(:default_done_ratio, 50)
+    end
+    
+    context "with Setting.issue_done_ratio using the issue_field" do
+      setup do
+        Setting.issue_done_ratio = 'issue_field'
+      end
+      
+      should "not change the issue" do
+        @issue.update_done_ratio_from_issue_status
+
+        assert_equal 0, @issue.done_ratio
+      end
+    end
+
+    context "with Setting.issue_done_ratio using the issue_status" do
+      setup do
+        Setting.issue_done_ratio = 'issue_status'
+      end
+      
+      should "not change the issue's done ratio" do
+        @issue.update_done_ratio_from_issue_status
+
+        assert_equal 50, @issue.done_ratio
+      end
+    end
+  end
 end