Feature #24277 » 01_add_remaining_hours_field_5.1.2.patch
app/helpers/issues_helper.rb | ||
---|---|---|
265 | 265 |
end |
266 | 266 |
end |
267 | 267 | |
268 |
def issue_remaining_hours_details(issue) |
|
269 |
if issue.total_remaining_hours.present? |
|
270 |
if issue.total_remaining_hours == issue.remaining_hours |
|
271 |
l_hours_short(issue.remaining_hours) |
|
272 |
else |
|
273 |
s = issue.remaining_hours.present? ? l_hours_short(issue.remaining_hours) : "" |
|
274 |
s << " (#{l(:label_total)}: #{l_hours_short(issue.total_remaining_hours)})" |
|
275 |
s.html_safe |
|
276 |
end |
|
277 |
end |
|
278 |
end |
|
279 | ||
268 | 280 |
def issue_spent_hours_details(issue) |
269 | 281 |
if issue.total_spent_hours > 0 |
270 | 282 |
path = project_time_entries_path(issue.project, :issue_id => "~#{issue.id}") |
... | ... | |
540 | 552 |
value = find_name_by_reflection(field, detail.value) |
541 | 553 |
old_value = find_name_by_reflection(field, detail.old_value) |
542 | 554 | |
543 |
when 'estimated_hours' |
|
555 |
when 'estimated_hours', 'remaining_hours'
|
|
544 | 556 |
value = l_hours_short(detail.value.to_f) unless detail.value.blank? |
545 | 557 |
old_value = l_hours_short(detail.old_value.to_f) unless detail.old_value.blank? |
546 | 558 |
app/models/issue.rb | ||
---|---|---|
70 | 70 |
validates_length_of :subject, :maximum => 255 |
71 | 71 |
validates_inclusion_of :done_ratio, :in => 0..100 |
72 | 72 |
validates :estimated_hours, :numericality => {:greater_than_or_equal_to => 0, :allow_nil => true, :message => :invalid} |
73 |
validates :remaining_hours, :numericality => {:greater_than_or_equal_to => 0, :allow_nil => true, :message => :invalid} |
|
73 | 74 |
validates :start_date, :date => true |
74 | 75 |
validates :due_date, :date => true |
75 | 76 |
validate :validate_issue, :validate_required_fields, :validate_permissions |
... | ... | |
109 | 110 | |
110 | 111 |
before_validation :default_assign, on: :create |
111 | 112 |
before_validation :clear_disabled_fields |
113 |
before_validation :update_remaining_hours_from_estimated_hours |
|
112 | 114 |
before_save :close_duplicates, :update_done_ratio_from_issue_status, |
113 |
:force_updated_on_change, :update_closed_on |
|
115 |
:force_updated_on_change, :update_closed_on, :update_remaining_hours
|
|
114 | 116 |
after_save do |issue| |
115 | 117 |
if !issue.saved_change_to_id? && issue.saved_change_to_project_id? |
116 | 118 |
issue.send :after_project_change |
... | ... | |
270 | 272 |
@spent_hours = nil |
271 | 273 |
@total_spent_hours = nil |
272 | 274 |
@total_estimated_hours = nil |
275 |
@total_remaining_hours = nil |
|
273 | 276 |
@last_updated_by = nil |
274 | 277 |
@last_notes = nil |
275 | 278 |
base_reload(*args) |
... | ... | |
486 | 489 |
write_attribute :estimated_hours, (h.is_a?(String) ? (h.to_hours || h) : h) |
487 | 490 |
end |
488 | 491 | |
492 |
def remaining_hours=(h) |
|
493 |
h = h.is_a?(String) ? h.to_hours : h |
|
494 |
# remaining time cannot be less than zero |
|
495 |
h = 0 if !h.nil? && h < 0 |
|
496 |
write_attribute :remaining_hours, h |
|
497 |
end |
|
498 | ||
489 | 499 |
safe_attributes( |
490 | 500 |
'project_id', |
491 | 501 |
'tracker_id', |
... | ... | |
500 | 510 |
'due_date', |
501 | 511 |
'done_ratio', |
502 | 512 |
'estimated_hours', |
513 |
'remaining_hours', |
|
503 | 514 |
'custom_field_values', |
504 | 515 |
'custom_fields', |
505 | 516 |
'lock_version', |
... | ... | |
1165 | 1176 |
end |
1166 | 1177 |
end |
1167 | 1178 | |
1179 |
def total_remaining_hours |
|
1180 |
if leaf? |
|
1181 |
remaining_hours |
|
1182 |
else |
|
1183 |
@total_remaining_hours ||= self_and_descendants.sum(:remaining_hours) |
|
1184 |
end |
|
1185 |
end |
|
1186 | ||
1168 | 1187 |
def relations |
1169 | 1188 |
@relations ||= IssueRelation::Relations.new(self, (relations_from + relations_to).sort) |
1170 | 1189 |
end |
... | ... | |
2006 | 2025 |
end |
2007 | 2026 |
end |
2008 | 2027 | |
2028 |
# Callback for setting remaining time to zero when the issue is closed. |
|
2029 |
def update_remaining_hours |
|
2030 |
if closing? && safe_attribute?('remaining_hours') && self.remaining_hours.to_f > 0 |
|
2031 |
self.remaining_hours = 0 |
|
2032 |
end |
|
2033 |
end |
|
2034 | ||
2009 | 2035 |
# Saves the changes in a Journal |
2010 | 2036 |
# Called after_save |
2011 | 2037 |
def create_journal |
... | ... | |
2095 | 2121 |
roles = user.admin ? Role.all.to_a : user.roles_for_project(project) |
2096 | 2122 |
roles.select(&:consider_workflow?) |
2097 | 2123 |
end |
2124 | ||
2125 |
def update_remaining_hours_from_estimated_hours |
|
2126 |
if self.remaining_hours.blank? && self.estimated_hours |
|
2127 |
self.remaining_hours = self.estimated_hours |
|
2128 |
end |
|
2129 |
end |
|
2098 | 2130 |
end |
app/models/issue_import.rb | ||
---|---|---|
213 | 213 |
if estimated_hours = row_value(row, 'estimated_hours') |
214 | 214 |
attributes['estimated_hours'] = estimated_hours |
215 | 215 |
end |
216 |
if remaining_hours = row_value(row, 'remaining_hours') |
|
217 |
attributes['remaining_hours'] = remaining_hours |
|
218 |
end |
|
216 | 219 |
if done_ratio = row_value(row, 'done_ratio') |
217 | 220 |
attributes['done_ratio'] = done_ratio |
218 | 221 |
end |
app/models/issue_query.rb | ||
---|---|---|
65 | 65 |
QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date", :groupable => true), |
66 | 66 |
QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours", |
67 | 67 |
:totalable => true), |
68 |
QueryColumn.new(:remaining_hours, :sortable => "#{Issue.table_name}.remaining_hours", |
|
69 |
:totalable => true), |
|
68 | 70 |
EstimatedRemainingHoursColumn.new, |
69 | 71 |
QueryColumn.new( |
70 | 72 |
:total_estimated_hours, |
... | ... | |
77 | 79 |
" AND subtasks.rgt <= #{Issue.table_name}.rgt), 0)" |
78 | 80 |
end, |
79 | 81 |
:default_order => 'desc'), |
82 |
QueryColumn.new(:total_remaining_hours, |
|
83 |
:sortable => "COALESCE((SELECT SUM(remaining_hours) FROM #{Issue.table_name} subtasks" + |
|
84 |
" WHERE subtasks.root_id = #{Issue.table_name}.root_id AND subtasks.lft >= #{Issue.table_name}.lft AND subtasks.rgt <= #{Issue.table_name}.rgt), 0)", |
|
85 |
:default_order => 'desc'), |
|
80 | 86 |
QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true), |
81 | 87 |
TimestampQueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", |
82 | 88 |
:default_order => 'desc', :groupable => true), |
... | ... | |
223 | 229 |
add_available_filter "start_date", :type => :date |
224 | 230 |
add_available_filter "due_date", :type => :date |
225 | 231 |
add_available_filter "estimated_hours", :type => :float |
232 |
add_available_filter "remaining_hours", :type => :float |
|
226 | 233 | |
227 | 234 |
if User.current.allowed_to?(:view_time_entries, project, :global => true) |
228 | 235 |
add_available_filter "spent_time", :type => :float, :label => :label_spent_time |
... | ... | |
389 | 396 |
map_total(scope.sum(EstimatedRemainingHoursColumn::COLUMN_SQL)) {|t| t.to_f.round(2)} |
390 | 397 |
end |
391 | 398 | |
399 |
# Returns sum of all the issue's remaining_hours |
|
400 |
def total_for_remaining_hours(scope) |
|
401 |
map_total(scope.sum(:remaining_hours)) {|t| t.to_f.round(2)} |
|
402 |
end |
|
403 | ||
392 | 404 |
# Returns sum of all the issue's time entries hours |
393 | 405 |
def total_for_spent_hours(scope) |
394 | 406 |
total = scope.joins(:time_entries). |
app/models/mail_handler.rb | ||
---|---|---|
484 | 484 |
'start_date' => get_keyword(:start_date, :format => '\d{4}-\d{2}-\d{2}'), |
485 | 485 |
'due_date' => get_keyword(:due_date, :format => '\d{4}-\d{2}-\d{2}'), |
486 | 486 |
'estimated_hours' => get_keyword(:estimated_hours), |
487 |
'remaining_hours' => get_keyword(:remaining_hours), |
|
487 | 488 |
'done_ratio' => get_keyword(:done_ratio, :format => '(\d|10)?0'), |
488 | 489 |
'is_private' => get_keyword_bool(:is_private), |
489 | 490 |
'parent_issue_id' => get_keyword(:parent_issue) |
app/models/tracker.rb | ||
---|---|---|
23 | 23 |
CORE_FIELDS_UNDISABLABLE = %w(project_id tracker_id subject is_private).freeze |
24 | 24 |
# Fields that can be disabled |
25 | 25 |
# Other (future) fields should be appended, not inserted! |
26 |
CORE_FIELDS = |
|
27 |
%w(assigned_to_id category_id fixed_version_id parent_issue_id |
|
28 |
start_date due_date estimated_hours done_ratio description priority_id).freeze |
|
26 |
CORE_FIELDS = %w(assigned_to_id category_id fixed_version_id parent_issue_id start_date due_date estimated_hours remaining_hours done_ratio description priority_id).freeze |
|
29 | 27 |
CORE_FIELDS_ALL = (CORE_FIELDS_UNDISABLABLE + CORE_FIELDS).freeze |
30 | 28 | |
31 | 29 |
before_destroy :check_integrity |
app/views/imports/_issues_fields_mapping.html.erb | ||
---|---|---|
71 | 71 |
<label for="import_mapping_estimated_hours"><%= l(:field_estimated_hours) %></label> |
72 | 72 |
<%= mapping_select_tag @import, 'estimated_hours' %> |
73 | 73 |
</p> |
74 |
<p> |
|
75 |
<label><%= l(:field_remaining_hours) %></label> |
|
76 |
<%= mapping_select_tag @import, 'remaining_hours' %> |
|
77 |
</p> |
|
74 | 78 |
<p> |
75 | 79 |
<label for="import_mapping_done_ratio"><%= l(:field_done_ratio) %></label> |
76 | 80 |
<%= mapping_select_tag @import, 'done_ratio' %> |
app/views/issues/_attributes.html.erb | ||
---|---|---|
83 | 83 |
</p> |
84 | 84 |
<% end %> |
85 | 85 | |
86 |
<% if @issue.safe_attribute?('done_ratio') && Issue.use_field_for_done_ratio? %> |
|
87 |
<p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }), :required => @issue.required_attribute?('done_ratio') %></p> |
|
88 |
<% end %> |
|
89 | ||
86 | 90 |
<% if @issue.safe_attribute? 'estimated_hours' %> |
87 | 91 |
<p><%= f.hours_field :estimated_hours, :size => 3, :required => @issue.required_attribute?('estimated_hours') %> <%= l(:field_hours) %></p> |
88 | 92 |
<% end %> |
89 | 93 | |
90 |
<% if @issue.safe_attribute?('done_ratio') && Issue.use_field_for_done_ratio? %>
|
|
91 |
<p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }), :required => @issue.required_attribute?('done_ratio') %></p>
|
|
94 |
<% if @issue.safe_attribute? 'remaining_hours' %>
|
|
95 |
<p><%= f.text_field :remaining_hours, :size => 3, :required => @issue.required_attribute?('remaining_hours') %> <%= l(:field_hours) %></p>
|
|
92 | 96 |
<% end %> |
93 | 97 |
</div> |
94 | 98 |
</div> |
app/views/issues/bulk_edit.html.erb | ||
---|---|---|
175 | 175 |
</p> |
176 | 176 |
<% end %> |
177 | 177 | |
178 |
<% if @safe_attributes.include?('remaining_hours') %> |
|
179 |
<p> |
|
180 |
<label for='issue_remaining_hours'><%= l(:field_remaining_hours) %></label> |
|
181 |
<%= text_field_tag 'issue[remaining_hours]', '', :value => @issue_params[:remaining_hours], :size => 10 %> |
|
182 |
<label class="inline"><%= check_box_tag 'issue[remaining_hours]', 'none', (@issue_params[:remaining_hours] == 'none'), :id => nil, :data => {:disables => '#issue_remaining_hours'} %><%= l(:button_clear) %></label> |
|
183 |
</p> |
|
184 |
<% end %> |
|
185 | ||
178 | 186 |
<% if @safe_attributes.include?('done_ratio') && Issue.use_field_for_done_ratio? %> |
179 | 187 |
<p> |
180 | 188 |
<label for='issue_done_ratio'><%= l(:field_done_ratio) %></label> |
app/views/issues/index.api.rsb | ||
---|---|---|
19 | 19 |
api.done_ratio issue.done_ratio |
20 | 20 |
api.is_private issue.is_private |
21 | 21 |
api.estimated_hours issue.estimated_hours |
22 |
api.remaining_hours issue.remaining_hours |
|
22 | 23 |
api.total_estimated_hours issue.total_estimated_hours |
23 | 24 |
if User.current.allowed_to?(:view_time_entries, issue.project) |
24 | 25 |
api.spent_hours(issue.spent_hours) |
app/views/issues/show.api.rsb | ||
---|---|---|
17 | 17 |
api.done_ratio @issue.done_ratio |
18 | 18 |
api.is_private @issue.is_private |
19 | 19 |
api.estimated_hours @issue.estimated_hours |
20 |
api.remaining_hours @issue.remaining_hours |
|
20 | 21 |
api.total_estimated_hours @issue.total_estimated_hours |
22 |
api.total_remaining_hours @issue.total_remaining_hours |
|
21 | 23 |
if User.current.allowed_to?(:view_time_entries, @project) |
22 | 24 |
api.spent_hours(@issue.spent_hours) |
23 | 25 |
api.total_spent_hours(@issue.total_spent_hours) |
app/views/issues/show.html.erb | ||
---|---|---|
73 | 73 |
unless @issue.disabled_core_fields.include?('estimated_hours') |
74 | 74 |
rows.right l(:field_estimated_hours), issue_estimated_hours_details(@issue), :class => 'estimated-hours' |
75 | 75 |
end |
76 |
unless @issue.disabled_core_fields.include?('remaining_hours') |
|
77 |
rows.right l(:field_remaining_hours), issue_remaining_hours_details(@issue), :class => 'remaining-hours' |
|
78 |
end |
|
76 | 79 |
if User.current.allowed_to?(:view_time_entries, @project) && @issue.total_spent_hours > 0 |
77 | 80 |
rows.right l(:label_spent_time), issue_spent_hours_details(@issue), :class => 'spent-time' |
78 | 81 |
end |
config/locales/en.yml | ||
---|---|---|
409 | 409 |
field_full_width_layout: Full width layout |
410 | 410 |
field_digest: Checksum |
411 | 411 |
field_default_assigned_to: Default assignee |
412 |
field_remaining_hours: Remaining time |
|
413 |
field_total_remaining_hours: Total remaining time |
|
412 | 414 |
field_recently_used_projects: Number of recently used projects in jump box |
413 | 415 |
field_history_default_tab: Issue's history default tab |
414 | 416 |
field_unique_id: Unique ID |
db/migrate/20160920184857_add_remaining_hours_to_issues.rb | ||
---|---|---|
1 |
class AddRemainingHoursToIssues < ActiveRecord::Migration[4.2] |
|
2 |
def self.up |
|
3 |
add_column :issues, :remaining_hours, :float |
|
4 |
Issue.where("estimated_hours > ?", 0).update_all("remaining_hours = estimated_hours") |
|
5 |
end |
|
6 | ||
7 |
def self.down |
|
8 |
remove_column :issues, :remaining_hours |
|
9 |
end |
|
10 |
end |
test/fixtures/mail_handler/ticket_on_given_project.eml | ||
---|---|---|
18 | 18 |
X-Mailer: Microsoft Outlook Express 6.00.2900.2869 |
19 | 19 |
X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869 |
20 | 20 | |
21 |
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet
|
|
22 |
turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus
|
|
23 |
blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti
|
|
24 |
sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In
|
|
25 |
in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras
|
|
26 |
sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum
|
|
27 |
id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus
|
|
28 |
eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique
|
|
29 |
sed, mauris --- Pellentesque habitant morbi tristique senectus et netus et
|
|
30 |
malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse
|
|
21 |
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet |
|
22 |
turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus |
|
23 |
blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti |
|
24 |
sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In |
|
25 |
in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras |
|
26 |
sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum |
|
27 |
id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus |
|
28 |
eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique |
|
29 |
sed, mauris --- Pellentesque habitant morbi tristique senectus et netus et |
|
30 |
malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse |
|
31 | 31 |
platea dictumst. |
32 | 32 | |
33 | 33 |
Project: onlinestore |
... | ... | |
37 | 37 |
Assigned to: John Smith |
38 | 38 |
fixed version: alpha |
39 | 39 |
estimated hours: 2.5 |
40 |
remaining hours: 1 |
|
40 | 41 |
done ratio: 30 |
41 | 42 |
parent issue: 4 |
42 | 43 | |
... | ... | |
52 | 53 | |
53 | 54 |
This paragraph is after the delimiter so it shouldn't appear. |
54 | 55 | |
55 |
Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque
|
|
56 |
sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem.
|
|
57 |
Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et,
|
|
58 |
dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed,
|
|
59 |
massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo
|
|
56 |
Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque |
|
57 |
sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem. |
|
58 |
Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et, |
|
59 |
dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed, |
|
60 |
massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo |
|
60 | 61 |
pulvinar dui, a gravida orci mi eget odio. Nunc a lacus. |
61 | 62 |
test/functional/issues_controller_test.rb | ||
---|---|---|
1400 | 1400 |
assert_equal hours.sort.reverse, hours |
1401 | 1401 |
end |
1402 | 1402 | |
1403 |
def test_index_sort_by_total_remaining_hours |
|
1404 |
get :index, :sort => 'total_remaining_hours:desc' |
|
1405 |
assert_response :success |
|
1406 |
hours = assigns(:issues).collect(&:total_remaining_hours) |
|
1407 |
assert_equal hours.sort.reverse, hours |
|
1408 |
end |
|
1409 | ||
1403 | 1410 |
def test_index_sort_by_user_custom_field |
1404 | 1411 |
cf = IssueCustomField. |
1405 | 1412 |
create!( |
... | ... | |
1630 | 1637 |
assert_select 'table.issues td.total_estimated_hours' |
1631 | 1638 |
end |
1632 | 1639 | |
1640 |
def test_index_with_total_remaining_hours_column |
|
1641 |
get :index, :set_filter => 1, :c => %w(subject total_remaining_hours) |
|
1642 |
assert_select 'table.issues td.total_remaining_hours' |
|
1643 |
end |
|
1644 | ||
1633 | 1645 |
def test_index_should_not_show_spent_hours_column_without_permission |
1634 | 1646 |
Role.anonymous.remove_permission! :view_time_entries |
1635 | 1647 |
get( |
... | ... | |
1934 | 1946 |
assert_select 'input[type=checkbox][name=?][value=estimated_hours][checked=checked]', 't[]' |
1935 | 1947 |
end |
1936 | 1948 | |
1949 |
def test_index_with_remaining_hours_total |
|
1950 |
Issue.delete_all |
|
1951 |
Issue.generate!(:remaining_hours => 5.4) |
|
1952 |
Issue.generate!(:remaining_hours => 1.1) |
|
1953 | ||
1954 |
get :index, :t => %w(remaining_hours) |
|
1955 |
assert_response :success |
|
1956 |
assert_select '.query-totals' |
|
1957 |
assert_select '.total-for-remaining-hours span.value', :text => '6.50' |
|
1958 |
assert_select 'input[type=checkbox][name=?][value=remaining_hours][checked=checked]', 't[]' |
|
1959 |
end |
|
1960 | ||
1937 | 1961 |
def test_index_with_grouped_query_and_estimated_hours_total |
1938 | 1962 |
Issue.delete_all |
1939 | 1963 |
Issue.generate!(:estimated_hours => '5:30', :category_id => 1) |
... | ... | |
4075 | 4099 |
:priority_id => 5, |
4076 | 4100 |
:start_date => '2010-11-07', |
4077 | 4101 |
:estimated_hours => '', |
4102 |
:remaining_hours => '', |
|
4078 | 4103 |
:custom_field_values => { |
4079 | 4104 |
'2' => 'Value for field 2' |
4080 | 4105 |
} |
... | ... | |
4092 | 4117 |
assert_equal 2, issue.status_id |
4093 | 4118 |
assert_equal Date.parse('2010-11-07'), issue.start_date |
4094 | 4119 |
assert_nil issue.estimated_hours |
4120 |
assert_nil issue.remaining_hours |
|
4095 | 4121 |
v = issue.custom_values.where(:custom_field_id => 2).first |
4096 | 4122 |
assert_not_nil v |
4097 | 4123 |
assert_equal 'Value for field 2', v.value |
... | ... | |
7480 | 7506 |
assert_equal 4.25, Issue.find(2).estimated_hours |
7481 | 7507 |
end |
7482 | 7508 | |
7509 |
def test_bulk_update_remaining_hours |
|
7510 |
@request.session[:user_id] = 2 |
|
7511 |
post :bulk_update, :ids => [1, 2], :issue => {:remaining_hours => 4.15} |
|
7512 | ||
7513 |
assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook' |
|
7514 |
assert_equal 4.15, Issue.find(1).remaining_hours |
|
7515 |
assert_equal 4.15, Issue.find(2).remaining_hours |
|
7516 |
end |
|
7517 | ||
7483 | 7518 |
def test_bulk_update_custom_field |
7484 | 7519 |
@request.session[:user_id] = 2 |
7485 | 7520 |
# update issues priority |
test/helpers/issues_helper_test.rb | ||
---|---|---|
208 | 208 |
assert_match '6:18', show_detail(detail, true) |
209 | 209 |
end |
210 | 210 | |
211 |
test 'show_detail should show old and new values with a remaining hours attribute' do |
|
212 |
detail = JournalDetail.new(:property => 'attr', :prop_key => 'remaining_hours', |
|
213 |
:old_value => '6.3', :value => '5') |
|
214 |
assert_match '5.00', show_detail(detail, true) |
|
215 |
assert_match '6.30', show_detail(detail, true) |
|
216 |
end |
|
217 | ||
211 | 218 |
test 'show_detail should not show values with a description attribute' do |
212 | 219 |
detail = JournalDetail.new(:property => 'attr', :prop_key => 'description', |
213 | 220 |
:old_value => 'Foo', :value => 'Bar') |
test/integration/api_test/issues_test.rb | ||
---|---|---|
456 | 456 |
end |
457 | 457 |
end |
458 | 458 | |
459 |
test "GET /issues/:id.xml should contains total_estimated_hours and total_spent_hours" do |
|
459 |
test "GET /issues/:id.xml should contains total_estimated_hours, total_remaining_hours and total_spent_hours" do
|
|
460 | 460 |
parent = Issue.find(3) |
461 | 461 |
parent.update_columns :estimated_hours => 2.0 |
462 |
child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0) |
|
462 |
child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0, :remaining_hours => 1.0)
|
|
463 | 463 |
TimeEntry.create!(:project => child.project, :issue => child, :user => child.author, :spent_on => child.author.today, |
464 | 464 |
:hours => '2.5', :comments => '', :activity_id => TimeEntryActivity.first.id) |
465 | 465 |
get '/issues/3.xml' |
... | ... | |
468 | 468 |
assert_select 'issue' do |
469 | 469 |
assert_select 'estimated_hours', parent.estimated_hours.to_s |
470 | 470 |
assert_select 'total_estimated_hours', (parent.estimated_hours.to_f + 3.0).to_s |
471 |
assert_select 'remaining_hours', parent.remaining_hours.to_s |
|
472 |
assert_select 'total_remaining_hours', (parent.remaining_hours.to_f + 1.0).to_s |
|
471 | 473 |
assert_select 'spent_hours', parent.spent_hours.to_s |
472 | 474 |
assert_select 'total_spent_hours', (parent.spent_hours.to_f + 2.5).to_s |
473 | 475 |
end |
474 | 476 |
end |
475 | 477 | |
476 |
test "GET /issues/:id.xml should contains total_estimated_hours, and should not contains spent_hours and total_spent_hours when permission does not exists" do |
|
478 |
test "GET /issues/:id.xml should contains total_estimated_hours and total_remaining_hours, and should not contains spent_hours and total_spent_hours when permission does not exists" do
|
|
477 | 479 |
parent = Issue.find(3) |
478 | 480 |
parent.update_columns :estimated_hours => 2.0 |
479 |
child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0) |
|
481 |
child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0, :remaining_hours => 1.0)
|
|
480 | 482 |
Role.anonymous.remove_permission! :view_time_entries |
481 | 483 |
get '/issues/3.xml' |
482 | 484 | |
... | ... | |
484 | 486 |
assert_select 'issue' do |
485 | 487 |
assert_select 'estimated_hours', parent.estimated_hours.to_s |
486 | 488 |
assert_select 'total_estimated_hours', (parent.estimated_hours.to_f + 3.0).to_s |
489 |
assert_select 'remaining_hours', parent.remaining_hours.to_s |
|
490 |
assert_select 'total_remaining_hours', (parent.remaining_hours.to_f + 1.0).to_s |
|
487 | 491 |
assert_select 'spent_hours', false |
488 | 492 |
assert_select 'total_spent_hours', false |
489 | 493 |
end |
... | ... | |
506 | 510 |
end |
507 | 511 |
end |
508 | 512 | |
509 |
test "GET /issues/:id.json should contains total_estimated_hours and total_spent_hours" do |
|
513 |
test "GET /issues/:id.json should contains total_estimated_hours, total_remaining_hours and total_spent_hours" do
|
|
510 | 514 |
parent = Issue.find(3) |
511 | 515 |
parent.update_columns :estimated_hours => 2.0 |
512 |
child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0) |
|
516 |
child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0, :remaining_hours => 1.0)
|
|
513 | 517 |
TimeEntry.create!(:project => child.project, :issue => child, :user => child.author, :spent_on => child.author.today, |
514 | 518 |
:hours => '2.5', :comments => '', :activity_id => TimeEntryActivity.first.id) |
515 | 519 |
get '/issues/3.json' |
... | ... | |
518 | 522 |
json = ActiveSupport::JSON.decode(response.body) |
519 | 523 |
assert_equal parent.estimated_hours, json['issue']['estimated_hours'] |
520 | 524 |
assert_equal (parent.estimated_hours.to_f + 3.0), json['issue']['total_estimated_hours'] |
525 |
assert_equal parent.remaining_hours, json['issue']['remaining_hours'] |
|
526 |
assert_equal (parent.remaining_hours.to_f + 1.0), json['issue']['total_remaining_hours'] |
|
521 | 527 |
assert_equal parent.spent_hours, json['issue']['spent_hours'] |
522 | 528 |
assert_equal (parent.spent_hours.to_f + 2.5), json['issue']['total_spent_hours'] |
523 | 529 |
end |
524 | 530 | |
525 |
test "GET /issues/:id.json should contains total_estimated_hours, and should not contains spent_hours and total_spent_hours when permission does not exists" do |
|
531 |
test "GET /issues/:id.json should contains total_estimated_hours and total_remaining_hours, and should not contains spent_hours and total_spent_hours when permission does not exists" do
|
|
526 | 532 |
parent = Issue.find(3) |
527 | 533 |
parent.update_columns :estimated_hours => 2.0 |
528 |
child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0) |
|
534 |
child = Issue.generate!(:parent_issue_id => parent.id, :estimated_hours => 3.0, :remaining_hours => 1.0)
|
|
529 | 535 |
Role.anonymous.remove_permission! :view_time_entries |
530 | 536 |
get '/issues/3.json' |
531 | 537 | |
... | ... | |
533 | 539 |
json = ActiveSupport::JSON.decode(response.body) |
534 | 540 |
assert_equal parent.estimated_hours, json['issue']['estimated_hours'] |
535 | 541 |
assert_equal (parent.estimated_hours.to_f + 3.0), json['issue']['total_estimated_hours'] |
542 |
assert_equal parent.remaining_hours, json['issue']['remaining_hours'] |
|
543 |
assert_equal (parent.remaining_hours.to_f + 1.0), json['issue']['total_remaining_hours'] |
|
536 | 544 |
assert_nil json['issue']['spent_hours'] |
537 | 545 |
assert_nil json['issue']['total_spent_hours'] |
538 | 546 |
end |
test/unit/issue_subtasking_test.rb | ||
---|---|---|
34 | 34 |
def test_leaf_planning_fields_should_be_editable |
35 | 35 |
issue = Issue.generate! |
36 | 36 |
user = User.find(1) |
37 |
%w(priority_id done_ratio start_date due_date estimated_hours).each do |attribute| |
|
37 |
%w(priority_id done_ratio start_date due_date estimated_hours remaining_hours).each do |attribute|
|
|
38 | 38 |
assert issue.safe_attribute?(attribute, user) |
39 | 39 |
end |
40 | 40 |
end |
... | ... | |
377 | 377 |
assert !child.save |
378 | 378 |
assert_include I18n.t("activerecord.errors.messages.open_issue_with_closed_parent"), child.errors.full_messages |
379 | 379 |
end |
380 | ||
381 |
def test_parent_total_remaining_hours_should_be_sum_of_descendants |
|
382 |
parent = Issue.generate! |
|
383 |
parent.generate_child!(:remaining_hours => nil) |
|
384 |
assert_equal 0, parent.reload.total_remaining_hours |
|
385 |
parent.generate_child!(:remaining_hours => 5) |
|
386 |
assert_equal 5, parent.reload.total_remaining_hours |
|
387 |
parent.generate_child!(:remaining_hours => 7) |
|
388 |
assert_equal 12, parent.reload.total_remaining_hours |
|
389 |
end |
|
380 | 390 |
end |
test/unit/issue_test.rb | ||
---|---|---|
60 | 60 |
issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, |
61 | 61 |
:status_id => 1, :priority => IssuePriority.first, |
62 | 62 |
:subject => 'test_create', |
63 |
:description => 'IssueTest#test_create', :estimated_hours => '1:30') |
|
63 |
:description => 'IssueTest#test_create', :estimated_hours => '1:30', :remaining_hours => '1')
|
|
64 | 64 |
assert issue.save |
65 | 65 |
issue.reload |
66 | 66 |
assert_equal 1.5, issue.estimated_hours |
67 |
assert_equal 1, issue.remaining_hours |
|
67 | 68 |
end |
68 | 69 | |
69 | 70 |
def test_create_minimal |
... | ... | |
72 | 73 |
assert_equal issue.tracker.default_status, issue.status |
73 | 74 |
assert issue.description.nil? |
74 | 75 |
assert_nil issue.estimated_hours |
76 |
assert_nil issue.remaining_hours |
|
75 | 77 |
end |
76 | 78 | |
77 | 79 |
def test_create_with_all_fields_disabled |
... | ... | |
148 | 150 |
end |
149 | 151 |
end |
150 | 152 | |
153 |
def test_remaining_hours_update_with_negative_value_should_set_to_zero |
|
154 |
set_language_if_valid 'en' |
|
155 |
['-4'].each do |invalid| |
|
156 |
issue = Issue.new(:remaining_hours => invalid) |
|
157 |
assert_equal 0, issue.remaining_hours |
|
158 |
end |
|
159 |
end |
|
160 | ||
161 |
def test_remaining_hours_should_be_set_from_estimated_hours_when_is_empty |
|
162 |
issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, |
|
163 |
:status_id => 1, :priority => IssuePriority.all.first, |
|
164 |
:subject => 'test_create', |
|
165 |
:description => 'IssueTest#test_create', :estimated_hours => '1:30') |
|
166 |
assert issue.save |
|
167 |
assert_equal 1.5, issue.remaining_hours |
|
168 |
end |
|
169 | ||
151 | 170 |
def test_create_with_required_custom_field |
152 | 171 |
set_language_if_valid 'en' |
153 | 172 |
field = IssueCustomField.find_by_name('Database') |
... | ... | |
2727 | 2746 |
user = User.find(3) |
2728 | 2747 |
user.members.update_all ["mail_notification = ?", false] |
2729 | 2748 |
user.update! :mail_notification => 'only_assigned' |
2749 |
puts user.inspect |
|
2730 | 2750 | |
2731 | 2751 |
with_settings :notified_events => %w(issue_updated) do |
2732 | 2752 |
issue = Issue.find(2) |
... | ... | |
3284 | 3304 |
assert_equal false, issue.closing? |
3285 | 3305 |
end |
3286 | 3306 | |
3307 |
def test_closing_should_set_remaining_hours_to_zero |
|
3308 |
issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, |
|
3309 |
:status_id => 1, :priority => IssuePriority.all.first, |
|
3310 |
:subject => 'test_create', |
|
3311 |
:description => 'IssueTest#test_create', :estimated_hours => '1:30', :remaining_hours => '1') |
|
3312 |
assert_equal 1, issue.remaining_hours |
|
3313 |
issue.status_id = 5 |
|
3314 |
issue.save! |
|
3315 |
assert_equal 0, issue.remaining_hours |
|
3316 |
end |
|
3317 | ||
3287 | 3318 |
def test_reopening_should_return_true_when_reopening_an_issue |
3288 | 3319 |
issue = Issue.find(8) |
3289 | 3320 |
issue.status = IssueStatus.find(6) |
test/unit/mail_handler_test.rb | ||
---|---|---|
48 | 48 |
'ticket_on_given_project.eml', |
49 | 49 |
:allow_override => |
50 | 50 |
['status', 'start_date', 'due_date', 'assigned_to', |
51 |
'fixed_version', 'estimated_hours', 'done_ratio', |
|
51 |
'fixed_version', 'estimated_hours', 'remaining_hours', 'done_ratio',
|
|
52 | 52 |
'parent_issue'] |
53 | 53 |
) |
54 | 54 |
assert issue.is_a?(Issue) |
... | ... | |
65 | 65 |
assert_equal User.find_by_login('jsmith'), issue.assigned_to |
66 | 66 |
assert_equal Version.find_by_name('Alpha'), issue.fixed_version |
67 | 67 |
assert_equal 2.5, issue.estimated_hours |
68 |
assert_equal 1, issue.remaining_hours |
|
68 | 69 |
assert_equal 30, issue.done_ratio |
69 | 70 |
assert_equal Issue.find(4), issue.parent |
70 | 71 |
# keywords should be removed from the email body |
71 | 72 |
assert !issue.description.match(/^Project:/i) |
72 | 73 |
assert !issue.description.match(/^Status:/i) |
73 | 74 |
assert !issue.description.match(/^Start Date:/i) |
75 |
assert !issue.description.match(/^remaining hours:/i) |
|
74 | 76 |
end |
75 | 77 | |
76 | 78 |
def test_add_issue_with_all_overrides |
... | ... | |
87 | 89 |
assert_equal User.find_by_login('jsmith'), issue.assigned_to |
88 | 90 |
assert_equal Version.find_by_name('Alpha'), issue.fixed_version |
89 | 91 |
assert_equal 2.5, issue.estimated_hours |
92 |
assert_equal 1, issue.remaining_hours |
|
90 | 93 |
assert_equal 30, issue.done_ratio |
91 | 94 |
assert_equal Issue.find(4), issue.parent |
92 | 95 |
end |
... | ... | |
109 | 112 |
assert_nil issue.assigned_to |
110 | 113 |
assert_nil issue.fixed_version |
111 | 114 |
assert_nil issue.estimated_hours |
115 |
assert_nil issue.remaining_hours |
|
112 | 116 |
assert_equal 0, issue.done_ratio |
113 | 117 |
assert_nil issue.parent |
114 | 118 |
end |
test/unit/query_test.rb | ||
---|---|---|
2347 | 2347 | |
2348 | 2348 |
def test_set_totalable_names |
2349 | 2349 |
q = IssueQuery.new |
2350 |
q.totalable_names = ['estimated_hours', :spent_hours, ''] |
|
2351 |
assert_equal [:estimated_hours, :spent_hours], q.totalable_columns.map(&:name) |
|
2350 |
q.totalable_names = ['estimated_hours', 'remaining_hours', :spent_hours, '']
|
|
2351 |
assert_equal [:estimated_hours, :remaining_hours, :spent_hours], q.totalable_columns.map(&:name)
|
|
2352 | 2352 |
end |
2353 | 2353 | |
2354 | 2354 |
def test_totalable_columns_should_default_to_settings |
... | ... | |
2368 | 2368 |
assert_include :estimated_remaining_hours, q.available_totalable_columns.map(&:name) |
2369 | 2369 |
end |
2370 | 2370 | |
2371 |
def test_available_totalable_columns_should_include_remaining_hours |
|
2372 |
q = IssueQuery.new |
|
2373 |
assert_include :remaining_hours, q.available_totalable_columns.map(&:name) |
|
2374 |
end |
|
2375 | ||
2371 | 2376 |
def test_available_totalable_columns_should_include_spent_hours |
2372 | 2377 |
User.current = User.find(1) |
2373 | 2378 | |
... | ... | |
2464 | 2469 |
) |
2465 | 2470 |
end |
2466 | 2471 | |
2472 |
def test_total_for_remaining_hours |
|
2473 |
Issue.delete_all |
|
2474 |
Issue.generate!(:remaining_hours => 5.5) |
|
2475 |
Issue.generate!(:remaining_hours => 1.1) |
|
2476 |
Issue.generate! |
|
2477 | ||
2478 |
q = IssueQuery.new |
|
2479 |
assert_equal 6.6, q.total_for(:remaining_hours) |
|
2480 |
end |
|
2481 | ||
2482 |
def test_total_by_group_for_remaining_hours |
|
2483 |
Issue.delete_all |
|
2484 |
Issue.generate!(:remaining_hours => 5.5, :assigned_to_id => 2) |
|
2485 |
Issue.generate!(:remaining_hours => 1.1, :assigned_to_id => 3) |
|
2486 |
Issue.generate!(:remaining_hours => 3.5) |
|
2487 | ||
2488 |
q = IssueQuery.new(:group_by => 'assigned_to') |
|
2489 |
assert_equal( |
|
2490 |
{nil => 3.5, User.find(2) => 5.5, User.find(3) => 1.1}, |
|
2491 |
q.total_by_group_for(:remaining_hours) |
|
2492 |
) |
|
2493 |
end |
|
2494 | ||
2467 | 2495 |
def test_total_for_spent_hours |
2468 | 2496 |
TimeEntry.delete_all |
2469 | 2497 |
TimeEntry.generate!(:hours => 5.5) |