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) |