##// END OF EJS Templates
Adds a Setting to control how an Issue's done_ratio is calculated:...
Eric Davis -
r3037:4fe14e71c2d7
parent child
Show More
@@ -0,0 +1,9
1 class AddDefaultDoneRatioToIssueStatus < ActiveRecord::Migration
2 def self.up
3 add_column :issue_statuses, :default_done_ratio, :integer
4 end
5
6 def self.down
7 remove_column :issue_statuses, :default_done_ratio
8 end
9 end
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
@@ -18,7 +18,7
18 class IssueStatusesController < ApplicationController
18 class IssueStatusesController < ApplicationController
19 before_filter :require_admin
19 before_filter :require_admin
20
20
21 verify :method => :post, :only => [ :destroy, :create, :update, :move ],
21 verify :method => :post, :only => [ :destroy, :create, :update, :move, :update_issue_done_ratio ],
22 :redirect_to => { :action => :list }
22 :redirect_to => { :action => :list }
23
23
24 def index
24 def index
@@ -66,4 +66,13 class IssueStatusesController < ApplicationController
66 flash[:error] = "Unable to delete issue status"
66 flash[:error] = "Unable to delete issue status"
67 redirect_to :action => 'list'
67 redirect_to :action => 'list'
68 end
68 end
69
70 def update_issue_done_ratio
71 if IssueStatus.update_issue_done_ratios
72 flash[:notice] = l(:notice_issue_done_ratios_updated)
73 else
74 flash[:error] = l(:error_issue_done_ratios_not_updated)
75 end
76 redirect_to :action => 'list'
77 end
69 end
78 end
@@ -45,6 +45,8 class Issue < ActiveRecord::Base
45
45
46 acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
46 acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
47 :author_key => :author_id
47 :author_key => :author_id
48
49 DONE_RATIO_OPTIONS = %w(issue_field issue_status)
48
50
49 validates_presence_of :subject, :priority, :project, :tracker, :author, :status
51 validates_presence_of :subject, :priority, :project, :tracker, :author, :status
50 validates_length_of :subject, :maximum => 255
52 validates_length_of :subject, :maximum => 255
@@ -55,7 +57,8 class Issue < ActiveRecord::Base
55 :conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } }
57 :conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } }
56
58
57 named_scope :open, :conditions => ["#{IssueStatus.table_name}.is_closed = ?", false], :include => :status
59 named_scope :open, :conditions => ["#{IssueStatus.table_name}.is_closed = ?", false], :include => :status
58
60
61 before_save :update_done_ratio_from_issue_status
59 after_save :create_journal
62 after_save :create_journal
60
63
61 # Returns true if usr or current user is allowed to view the issue
64 # Returns true if usr or current user is allowed to view the issue
@@ -162,6 +165,22 class Issue < ActiveRecord::Base
162 write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
165 write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
163 end
166 end
164
167
168 def done_ratio
169 if Issue.use_status_for_done_ratio? && !self.status.default_done_ratio.blank?
170 self.status.default_done_ratio
171 else
172 read_attribute(:done_ratio)
173 end
174 end
175
176 def self.use_status_for_done_ratio?
177 Setting.issue_done_ratio == 'issue_status'
178 end
179
180 def self.use_field_for_done_ratio?
181 Setting.issue_done_ratio == 'issue_field'
182 end
183
165 def validate
184 def validate
166 if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
185 if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
167 errors.add :due_date, :not_a_date
186 errors.add :due_date, :not_a_date
@@ -198,6 +217,14 class Issue < ActiveRecord::Base
198 end
217 end
199 end
218 end
200
219
220 # Set the done_ratio using the status if that setting is set. This will keep the done_ratios
221 # even if the user turns off the setting later
222 def update_done_ratio_from_issue_status
223 if Issue.use_status_for_done_ratio? && !self.status.default_done_ratio.blank?
224 self.done_ratio = self.status.default_done_ratio
225 end
226 end
227
201 def after_save
228 def after_save
202 # Reload is needed in order to get the right status
229 # Reload is needed in order to get the right status
203 reload
230 reload
@@ -33,6 +33,18 class IssueStatus < ActiveRecord::Base
33 def self.default
33 def self.default
34 find(:first, :conditions =>["is_default=?", true])
34 find(:first, :conditions =>["is_default=?", true])
35 end
35 end
36
37 # Update all the +Issues+ setting their done_ratio to the value of their +IssueStatus+
38 def self.update_issue_done_ratios
39 if Issue.use_status_for_done_ratio?
40 IssueStatus.find(:all, :conditions => ["default_done_ratio >= 0"]).each do |status|
41 Issue.update_all(["done_ratio = ?", status.default_done_ratio],
42 ["status_id = ?", status.id])
43 end
44 end
45
46 return Issue.use_status_for_done_ratio?
47 end
36
48
37 # Returns an array of all statuses the given role can switch to
49 # Returns an array of all statuses the given role can switch to
38 # Uses association cache when called more than one time
50 # Uses association cache when called more than one time
@@ -5,6 +5,11
5 <p><label for="issue_status_name"><%=l(:field_name)%><span class="required"> *</span></label>
5 <p><label for="issue_status_name"><%=l(:field_name)%><span class="required"> *</span></label>
6 <%= text_field 'issue_status', 'name' %></p>
6 <%= text_field 'issue_status', 'name' %></p>
7
7
8 <% if Issue.use_status_for_done_ratio? %>
9 <p><label for="issue_done_ratio"><%=l(:field_done_ratio)%></label>
10 <%= select 'issue_status', :default_done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
11 <% end %>
12
8 <p><label for="issue_status_is_closed"><%=l(:field_is_closed)%></label>
13 <p><label for="issue_status_is_closed"><%=l(:field_is_closed)%></label>
9 <%= check_box 'issue_status', 'is_closed' %></p>
14 <%= check_box 'issue_status', 'is_closed' %></p>
10
15
@@ -1,5 +1,6
1 <div class="contextual">
1 <div class="contextual">
2 <%= link_to l(:label_issue_status_new), {:action => 'new'}, :class => 'icon icon-add' %>
2 <%= link_to l(:label_issue_status_new), {:action => 'new'}, :class => 'icon icon-add' %>
3 <%= 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? %>
3 </div>
4 </div>
4
5
5 <h2><%=l(:label_issue_status_plural)%></h2>
6 <h2><%=l(:label_issue_status_plural)%></h2>
@@ -7,6 +8,9
7 <table class="list">
8 <table class="list">
8 <thead><tr>
9 <thead><tr>
9 <th><%=l(:field_status)%></th>
10 <th><%=l(:field_status)%></th>
11 <% if Issue.use_status_for_done_ratio? %>
12 <th><%=l(:field_done_ratio)%></th>
13 <% end %>
10 <th><%=l(:field_is_default)%></th>
14 <th><%=l(:field_is_default)%></th>
11 <th><%=l(:field_is_closed)%></th>
15 <th><%=l(:field_is_closed)%></th>
12 <th><%=l(:button_sort)%></th>
16 <th><%=l(:button_sort)%></th>
@@ -16,6 +20,9
16 <% for status in @issue_statuses %>
20 <% for status in @issue_statuses %>
17 <tr class="<%= cycle("odd", "even") %>">
21 <tr class="<%= cycle("odd", "even") %>">
18 <td><%= link_to status.name, :action => 'edit', :id => status %></td>
22 <td><%= link_to status.name, :action => 'edit', :id => status %></td>
23 <% if Issue.use_status_for_done_ratio? %>
24 <td align="center"><%= h status.default_done_ratio %></td>
25 <% end %>
19 <td align="center"><%= image_tag 'true.png' if status.is_default? %></td>
26 <td align="center"><%= image_tag 'true.png' if status.is_default? %></td>
20 <td align="center"><%= image_tag 'true.png' if status.is_closed? %></td>
27 <td align="center"><%= image_tag 'true.png' if status.is_closed? %></td>
21 <td align="center" style="width:15%;"><%= reorder_links('issue_status', {:action => 'update', :id => status}) %></td>
28 <td align="center" style="width:15%;"><%= reorder_links('issue_status', {:action => 'update', :id => status}) %></td>
@@ -34,7 +34,9
34 <p><%= f.text_field :start_date, :size => 10 %><%= calendar_for('issue_start_date') %></p>
34 <p><%= f.text_field :start_date, :size => 10 %><%= calendar_for('issue_start_date') %></p>
35 <p><%= f.text_field :due_date, :size => 10 %><%= calendar_for('issue_due_date') %></p>
35 <p><%= f.text_field :due_date, :size => 10 %><%= calendar_for('issue_due_date') %></p>
36 <p><%= f.text_field :estimated_hours, :size => 3 %> <%= l(:field_hours) %></p>
36 <p><%= f.text_field :estimated_hours, :size => 3 %> <%= l(:field_hours) %></p>
37 <% if Issue.use_field_for_done_ratio? %>
37 <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
38 <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
39 <% end %>
38 </div>
40 </div>
39
41
40 <div style="clear:both;"> </div>
42 <div style="clear:both;"> </div>
@@ -4,7 +4,9
4 <p><%= f.select :assigned_to_id, (@issue.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true %></p>
4 <p><%= f.select :assigned_to_id, (@issue.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true %></p>
5 </div>
5 </div>
6 <div class="splitcontentright">
6 <div class="splitcontentright">
7 <% if Issue.use_field_for_done_ratio? %>
7 <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
8 <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
9 <% end %>
8 <% unless @issue.assignable_versions.empty? %>
10 <% unless @issue.assignable_versions.empty? %>
9 <p><%= f.select :fixed_version_id, (@issue.assignable_versions.collect {|v| [v.name, v.id]}), :include_blank => true %></p>
11 <p><%= f.select :fixed_version_id, (@issue.assignable_versions.collect {|v| [v.name, v.id]}), :include_blank => true %></p>
10 <% end %>
12 <% end %>
@@ -39,8 +39,10
39 <%= text_field_tag 'start_date', '', :size => 10 %><%= calendar_for('start_date') %></label>
39 <%= text_field_tag 'start_date', '', :size => 10 %><%= calendar_for('start_date') %></label>
40 <label><%= l(:field_due_date) %>:
40 <label><%= l(:field_due_date) %>:
41 <%= text_field_tag 'due_date', '', :size => 10 %><%= calendar_for('due_date') %></label>
41 <%= text_field_tag 'due_date', '', :size => 10 %><%= calendar_for('due_date') %></label>
42 <% if Issue.use_field_for_done_ratio? %>
42 <label><%= l(:field_done_ratio) %>:
43 <label><%= l(:field_done_ratio) %>:
43 <%= select_tag 'done_ratio', options_for_select([[l(:label_no_change_option), '']] + (0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></label>
44 <%= select_tag 'done_ratio', options_for_select([[l(:label_no_change_option), '']] + (0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></label>
45 <% end %>
44 </p>
46 </p>
45
47
46 <% @custom_fields.each do |custom_field| %>
48 <% @custom_fields.each do |custom_field| %>
@@ -77,6 +77,7
77 </ul>
77 </ul>
78 </li>
78 </li>
79 <% end -%>
79 <% end -%>
80 <% if Issue.use_field_for_done_ratio? %>
80 <li class="folder">
81 <li class="folder">
81 <a href="#" class="submenu"><%= l(:field_done_ratio) %></a>
82 <a href="#" class="submenu"><%= l(:field_done_ratio) %></a>
82 <ul>
83 <ul>
@@ -86,7 +87,7
86 <% end -%>
87 <% end -%>
87 </ul>
88 </ul>
88 </li>
89 </li>
89
90 <% end %>
90 <% if !@issue.nil? %>
91 <% if !@issue.nil? %>
91 <% if @can[:log_time] -%>
92 <% if @can[:log_time] -%>
92 <li><%= context_menu_link l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue},
93 <li><%= context_menu_link l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue},
@@ -11,6 +11,9
11 <%= check_box_tag 'settings[display_subprojects_issues]', 1, Setting.display_subprojects_issues? %>
11 <%= check_box_tag 'settings[display_subprojects_issues]', 1, Setting.display_subprojects_issues? %>
12 </p>
12 </p>
13
13
14 <p><label><%= l(:setting_issue_done_ratio) %></label>
15 <%= 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>
16
14 <p><label><%= l(:setting_issues_export_limit) %></label>
17 <p><label><%= l(:setting_issues_export_limit) %></label>
15 <%= text_field_tag 'settings[issues_export_limit]', Setting.issues_export_limit, :size => 6 %></p>
18 <%= text_field_tag 'settings[issues_export_limit]', Setting.issues_export_limit, :size => 6 %></p>
16 </div>
19 </div>
@@ -147,6 +147,7 en:
147 notice_account_pending: "Your account was created and is now pending administrator approval."
147 notice_account_pending: "Your account was created and is now pending administrator approval."
148 notice_default_data_loaded: Default configuration successfully loaded.
148 notice_default_data_loaded: Default configuration successfully loaded.
149 notice_unable_delete_version: Unable to delete version.
149 notice_unable_delete_version: Unable to delete version.
150 notice_issue_done_ratios_updated: Issue done ratios updated.
150
151
151 error_can_t_load_default_data: "Default configuration could not be loaded: {{value}}"
152 error_can_t_load_default_data: "Default configuration could not be loaded: {{value}}"
152 error_scm_not_found: "The entry or revision was not found in the repository."
153 error_scm_not_found: "The entry or revision was not found in the repository."
@@ -157,7 +158,8 en:
157 error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
158 error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
158 error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version can not be reopened'
159 error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version can not be reopened'
159 error_can_not_archive_project: This project can not be archived
160 error_can_not_archive_project: This project can not be archived
160
161 error_issue_done_ratios_not_updated: "Issue done ratios not updated."
162
161 warning_attachments_not_saved: "{{count}} file(s) could not be saved."
163 warning_attachments_not_saved: "{{count}} file(s) could not be saved."
162
164
163 mail_subject_lost_password: "Your {{value}} password"
165 mail_subject_lost_password: "Your {{value}} password"
@@ -309,6 +311,7 en:
309 setting_sequential_project_identifiers: Generate sequential project identifiers
311 setting_sequential_project_identifiers: Generate sequential project identifiers
310 setting_gravatar_enabled: Use Gravatar user icons
312 setting_gravatar_enabled: Use Gravatar user icons
311 setting_gravatar_default: Default Gravatar image
313 setting_gravatar_default: Default Gravatar image
314 setting_issue_done_ratio: Calculate the issue done ratio with
312 setting_diff_max_lines_displayed: Max number of diff lines displayed
315 setting_diff_max_lines_displayed: Max number of diff lines displayed
313 setting_file_max_size_displayed: Max size of text files displayed inline
316 setting_file_max_size_displayed: Max size of text files displayed inline
314 setting_repository_log_display_limit: Maximum number of revisions displayed on file log
317 setting_repository_log_display_limit: Maximum number of revisions displayed on file log
@@ -716,6 +719,7 en:
716 label_version_sharing_hierarchy: With project hierarchy
719 label_version_sharing_hierarchy: With project hierarchy
717 label_version_sharing_tree: With project tree
720 label_version_sharing_tree: With project tree
718 label_version_sharing_system: With all projects
721 label_version_sharing_system: With all projects
722 label_update_issue_done_ratios: Update issue done ratios
719
723
720 button_login: Login
724 button_login: Login
721 button_submit: Submit
725 button_submit: Submit
@@ -850,3 +854,6 en:
850 enumeration_doc_categories: Document categories
854 enumeration_doc_categories: Document categories
851 enumeration_activities: Activities (time tracking)
855 enumeration_activities: Activities (time tracking)
852 enumeration_system_activity: System Activity
856 enumeration_system_activity: System Activity
857
858 issue_field: Use the issue field
859 issue_status: Use the issue status
@@ -129,6 +129,8 issue_list_default_columns:
129 - updated_on
129 - updated_on
130 display_subprojects_issues:
130 display_subprojects_issues:
131 default: 1
131 default: 1
132 issue_done_ratio:
133 default: 'issue_field'
132 default_projects_public:
134 default_projects_public:
133 default: 1
135 default: 1
134 default_projects_modules:
136 default_projects_modules:
@@ -702,6 +702,7 vertical-align: middle;
702 .icon-move { background-image: url(../images/move.png); }
702 .icon-move { background-image: url(../images/move.png); }
703 .icon-save { background-image: url(../images/save.png); }
703 .icon-save { background-image: url(../images/save.png); }
704 .icon-cancel { background-image: url(../images/cancel.png); }
704 .icon-cancel { background-image: url(../images/cancel.png); }
705 .icon-multiple { background-image: url(../images/table_multiple.png); }
705 .icon-folder { background-image: url(../images/folder.png); }
706 .icon-folder { background-image: url(../images/folder.png); }
706 .open .icon-folder { background-image: url(../images/folder_open.png); }
707 .open .icon-folder { background-image: url(../images/folder_open.png); }
707 .icon-package { background-image: url(../images/package.png); }
708 .icon-package { background-image: url(../images/package.png); }
@@ -70,4 +70,27 class IssueStatusesControllerTest < ActionController::TestCase
70 assert_redirected_to 'issue_statuses/list'
70 assert_redirected_to 'issue_statuses/list'
71 assert_not_nil IssueStatus.find_by_id(1)
71 assert_not_nil IssueStatus.find_by_id(1)
72 end
72 end
73
74 context "on POST to :update_issue_done_ratio" do
75 context "with Setting.issue_done_ratio using the issue_field" do
76 setup do
77 Setting.issue_done_ratio = 'issue_field'
78 post :update_issue_done_ratio
79 end
80
81 should_set_the_flash_to /not updated/
82 should_redirect_to('the list') { '/issue_statuses/list' }
83 end
84
85 context "with Setting.issue_done_ratio using the issue_status" do
86 setup do
87 Setting.issue_done_ratio = 'issue_status'
88 post :update_issue_done_ratio
89 end
90
91 should_set_the_flash_to /Issue done ratios updated/
92 should_redirect_to('the list') { '/issue_statuses/list' }
93 end
94 end
95
73 end
96 end
@@ -66,4 +66,40 class IssueStatusTest < ActiveSupport::TestCase
66 status.reload
66 status.reload
67 assert status.is_default?
67 assert status.is_default?
68 end
68 end
69
70 context "#update_done_ratios" do
71 setup do
72 @issue = Issue.find(1)
73 @issue_status = IssueStatus.find(1)
74 @issue_status.update_attribute(:default_done_ratio, 50)
75 end
76
77 context "with Setting.issue_done_ratio using the issue_field" do
78 setup do
79 Setting.issue_done_ratio = 'issue_field'
80 end
81
82 should "change nothing" do
83 IssueStatus.update_issue_done_ratios
84
85 assert_equal 0, Issue.count(:conditions => {:done_ratio => 50})
86 end
87 end
88
89 context "with Setting.issue_done_ratio using the issue_status" do
90 setup do
91 Setting.issue_done_ratio = 'issue_status'
92 end
93
94 should "update all of the issue's done_ratios to match their Issue Status" do
95 IssueStatus.update_issue_done_ratios
96
97 issues = Issue.find([1,3,4,5,6,7,9,10])
98 issues.each do |issue|
99 assert_equal @issue_status, issue.status
100 assert_equal 50, issue.read_attribute(:done_ratio)
101 end
102 end
103 end
104 end
69 end
105 end
@@ -529,4 +529,64 class IssueTest < ActiveSupport::TestCase
529 end
529 end
530 assert ActionMailer::Base.deliveries.empty?
530 assert ActionMailer::Base.deliveries.empty?
531 end
531 end
532
533 context "#done_ratio" do
534 setup do
535 @issue = Issue.find(1)
536 @issue_status = IssueStatus.find(1)
537 @issue_status.update_attribute(:default_done_ratio, 50)
538 end
539
540 context "with Setting.issue_done_ratio using the issue_field" do
541 setup do
542 Setting.issue_done_ratio = 'issue_field'
543 end
544
545 should "read the issue's field" do
546 assert_equal 0, @issue.done_ratio
547 end
548 end
549
550 context "with Setting.issue_done_ratio using the issue_status" do
551 setup do
552 Setting.issue_done_ratio = 'issue_status'
553 end
554
555 should "read the Issue Status's default done ratio" do
556 assert_equal 50, @issue.done_ratio
557 end
558 end
559 end
560
561 context "#update_done_ratio_from_issue_status" do
562 setup do
563 @issue = Issue.find(1)
564 @issue_status = IssueStatus.find(1)
565 @issue_status.update_attribute(:default_done_ratio, 50)
566 end
567
568 context "with Setting.issue_done_ratio using the issue_field" do
569 setup do
570 Setting.issue_done_ratio = 'issue_field'
571 end
572
573 should "not change the issue" do
574 @issue.update_done_ratio_from_issue_status
575
576 assert_equal 0, @issue.done_ratio
577 end
578 end
579
580 context "with Setting.issue_done_ratio using the issue_status" do
581 setup do
582 Setting.issue_done_ratio = 'issue_status'
583 end
584
585 should "not change the issue's done ratio" do
586 @issue.update_done_ratio_from_issue_status
587
588 assert_equal 50, @issue.done_ratio
589 end
590 end
591 end
532 end
592 end
General Comments 0
You need to be logged in to leave comments. Login now