Feature #1675 » affected-version.patch
redmine/app/controllers/reports_controller.rb 2011-01-05 15:24:18.339643700 -0700 | ||
---|---|---|
29 | 29 |
@subprojects = @project.descendants.visible |
30 | 30 |
|
31 | 31 |
@issues_by_tracker = Issue.by_tracker(@project) |
32 |
@issues_by_version = Issue.by_version(@project) |
|
32 |
@issues_by_affected_version = Issue.by_affected_version(@project) |
|
33 |
@issues_by_fixed_version = Issue.by_fixed_version(@project) |
|
33 | 34 |
@issues_by_priority = Issue.by_priority(@project) |
34 | 35 |
@issues_by_category = Issue.by_category(@project) |
35 | 36 |
@issues_by_assigned_to = Issue.by_assigned_to(@project) |
... | ... | |
46 | 47 |
@rows = @project.trackers |
47 | 48 |
@data = Issue.by_tracker(@project) |
48 | 49 |
@report_title = l(:field_tracker) |
49 |
when "version" |
|
50 |
when "affected_version" |
|
51 |
@field = "affected_version_id" |
|
52 |
@rows = @project.shared_versions.sort |
|
53 |
@data = Issue.by_affected_version(@project) |
|
54 |
@report_title = l(:field_affected_version) |
|
55 |
when "fixed_version" |
|
50 | 56 |
@field = "fixed_version_id" |
51 | 57 |
@rows = @project.shared_versions.sort |
52 |
@data = Issue.by_version(@project) |
|
53 |
@report_title = l(:field_version) |
|
58 |
@data = Issue.by_fixed_version(@project)
|
|
59 |
@report_title = l(:field_fixed_version)
|
|
54 | 60 |
when "priority" |
55 | 61 |
@field = "priority_id" |
56 | 62 |
@rows = IssuePriority.all |
redmine/app/helpers/issues_helper.rb 2011-01-05 15:24:58.871153100 -0700 | ||
---|---|---|
121 | 121 |
value = format_date(detail.value.to_date) if detail.value |
122 | 122 |
old_value = format_date(detail.old_value.to_date) if detail.old_value |
123 | 123 |
|
124 |
when ['project_id', 'status_id', 'tracker_id', 'assigned_to_id', 'priority_id', 'category_id', 'fixed_version_id'].include?(detail.prop_key) |
|
124 |
when ['project_id', 'status_id', 'tracker_id', 'assigned_to_id', 'priority_id', 'category_id', 'affected_version_id', 'fixed_version_id'].include?(detail.prop_key)
|
|
125 | 125 |
value = find_name_by_reflection(field, detail.value) |
126 | 126 |
old_value = find_name_by_reflection(field, detail.old_value) |
127 | 127 |
|
... | ... | |
200 | 200 |
l(:field_subject), |
201 | 201 |
l(:field_assigned_to), |
202 | 202 |
l(:field_category), |
203 |
l(:field_affected_version), |
|
203 | 204 |
l(:field_fixed_version), |
204 | 205 |
l(:field_author), |
205 | 206 |
l(:field_start_date), |
... | ... | |
227 | 228 |
issue.subject, |
228 | 229 |
issue.assigned_to, |
229 | 230 |
issue.category, |
231 |
issue.affected_version, |
|
230 | 232 |
issue.fixed_version, |
231 | 233 |
issue.author.name, |
232 | 234 |
format_date(issue.start_date), |
redmine/app/models/issue.rb 2011-01-05 16:49:11.669115800 -0700 | ||
---|---|---|
21 | 21 |
belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id' |
22 | 22 |
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' |
23 | 23 |
belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id' |
24 |
belongs_to :affected_version, :class_name => 'Version', :foreign_key => 'affected_version_id' |
|
24 | 25 |
belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id' |
25 | 26 |
belongs_to :priority, :class_name => 'IssuePriority', :foreign_key => 'priority_id' |
26 | 27 |
belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id' |
... | ... | |
121 | 122 |
# reassign to the category with same name if any |
122 | 123 |
new_category = issue.category.nil? ? nil : new_project.issue_categories.find_by_name(issue.category.name) |
123 | 124 |
issue.category = new_category |
124 |
# Keep the fixed_version if it's still valid in the new_project
|
|
125 |
# Keep the versions if they're still valid in the new_project
|
|
125 | 126 |
unless new_project.shared_versions.include?(issue.fixed_version) |
127 |
issue.affected_version = nil |
|
126 | 128 |
issue.fixed_version = nil |
127 | 129 |
end |
128 | 130 |
issue.project = new_project |
... | ... | |
204 | 206 |
category_id |
205 | 207 |
assigned_to_id |
206 | 208 |
priority_id |
209 |
affected_version_id |
|
207 | 210 |
fixed_version_id |
208 | 211 |
subject |
209 | 212 |
description |
... | ... | |
273 | 276 |
errors.add :start_date, :invalid |
274 | 277 |
end |
275 | 278 |
|
279 |
if affected_version |
|
280 |
if !affected_assignable_versions.include?(affected_version) |
|
281 |
errors.add :affected_version_id, :inclusion |
|
282 |
end |
|
283 |
end |
|
284 |
|
|
276 | 285 |
if fixed_version |
277 |
if !assignable_versions.include?(fixed_version) |
|
286 |
if !fixed_assignable_versions.include?(fixed_version)
|
|
278 | 287 |
errors.add :fixed_version_id, :inclusion |
279 | 288 |
elsif reopened? && fixed_version.closed? |
280 | 289 |
errors.add_to_base I18n.t(:error_can_not_reopen_issue_on_closed_version) |
... | ... | |
370 | 379 |
users.uniq.sort |
371 | 380 |
end |
372 | 381 |
|
373 |
# Versions that the issue can be assigned to |
|
374 |
def assignable_versions |
|
375 |
@assignable_versions ||= (project.shared_versions.open + [Version.find_by_id(fixed_version_id_was)]).compact.uniq.sort |
|
382 |
# Versions that the issue can be assigned as fixed to |
|
383 |
def affected_assignable_versions |
|
384 |
@affected_assignable_versions ||= (project.shared_versions.locked + [Version.find_by_id(affected_version_id_was)]).compact.uniq.sort |
|
385 |
end |
|
386 |
|
|
387 |
# Versions that the issue can be assigned as fixed to |
|
388 |
def fixed_assignable_versions |
|
389 |
@fixed_assignable_versions ||= (project.shared_versions.open + [Version.find_by_id(fixed_version_id_was)]).compact.uniq.sort |
|
376 | 390 |
end |
377 | 391 |
|
378 | 392 |
# Returns true if this issue is blocked by another issue that is still open |
... | ... | |
527 | 541 |
# Unassigns issues from +version+ if it's no longer shared with issue's project |
528 | 542 |
def self.update_versions_from_sharing_change(version) |
529 | 543 |
# Update issues assigned to the version |
544 |
update_versions(["#{Issue.table_name}.affected_version_id = ?", version.id]) |
|
530 | 545 |
update_versions(["#{Issue.table_name}.fixed_version_id = ?", version.id]) |
531 | 546 |
end |
532 | 547 |
|
... | ... | |
563 | 578 |
:joins => Tracker.table_name) |
564 | 579 |
end |
565 | 580 |
|
566 |
def self.by_version(project) |
|
581 |
def self.by_affected_version(project) |
|
582 |
count_and_group_by(:project => project, |
|
583 |
:field => 'affected_version_id', |
|
584 |
:joins => Version.table_name) |
|
585 |
end |
|
586 |
|
|
587 |
def self.by_fixed_version(project) |
|
567 | 588 |
count_and_group_by(:project => project, |
568 | 589 |
:field => 'fixed_version_id', |
569 | 590 |
:joins => Version.table_name) |
... | ... | |
739 | 760 |
issue.save |
740 | 761 |
end |
741 | 762 |
end |
763 |
# Only need to update issues with a affected_version from |
|
764 |
# a different project and that is not systemwide shared |
|
765 |
Issue.all(:conditions => merge_conditions("#{Issue.table_name}.affected_version_id IS NOT NULL" + |
|
766 |
" AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" + |
|
767 |
" AND #{Version.table_name}.sharing <> 'system'", |
|
768 |
conditions), |
|
769 |
:include => [:project, :affected_version] |
|
770 |
).each do |issue| |
|
771 |
next if issue.project.nil? || issue.affected_version.nil? |
|
772 |
unless issue.project.shared_versions.include?(issue.affected_version) |
|
773 |
issue.init_journal(User.current) |
|
774 |
issue.affected_version = nil |
|
775 |
issue.save |
|
776 |
end |
|
777 |
end |
|
742 | 778 |
end |
743 | 779 |
|
744 | 780 |
# Callback on attachment deletion |
redmine/app/models/query.rb 2011-01-05 15:28:01.372321100 -0700 | ||
---|---|---|
129 | 129 |
QueryColumn.new(:assigned_to, :sortable => ["#{User.table_name}.lastname", "#{User.table_name}.firstname", "#{User.table_name}.id"], :groupable => true), |
130 | 130 |
QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'), |
131 | 131 |
QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true), |
132 |
QueryColumn.new(:affected_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc', :groupable => true), |
|
132 | 133 |
QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc', :groupable => true), |
133 | 134 |
QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"), |
134 | 135 |
QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"), |
... | ... | |
174 | 175 |
@available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } }, |
175 | 176 |
"tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } }, |
176 | 177 |
"priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } }, |
177 |
"subject" => { :type => :text, :order => 8 },
|
|
178 |
"created_on" => { :type => :date_past, :order => 9 },
|
|
179 |
"updated_on" => { :type => :date_past, :order => 10 },
|
|
180 |
"start_date" => { :type => :date, :order => 11 },
|
|
181 |
"due_date" => { :type => :date, :order => 12 },
|
|
182 |
"estimated_hours" => { :type => :integer, :order => 13 },
|
|
183 |
"done_ratio" => { :type => :integer, :order => 14 }}
|
|
178 |
"subject" => { :type => :text, :order => 9 },
|
|
179 |
"created_on" => { :type => :date_past, :order => 10 },
|
|
180 |
"updated_on" => { :type => :date_past, :order => 11 },
|
|
181 |
"start_date" => { :type => :date, :order => 12 },
|
|
182 |
"due_date" => { :type => :date, :order => 13 },
|
|
183 |
"estimated_hours" => { :type => :integer, :order => 14 },
|
|
184 |
"done_ratio" => { :type => :integer, :order => 15 }}
|
|
184 | 185 |
|
185 | 186 |
user_values = [] |
186 | 187 |
user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? |
... | ... | |
197 | 198 |
@available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty? |
198 | 199 |
|
199 | 200 |
if User.current.logged? |
200 |
@available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
|
|
201 |
@available_filters["watcher_id"] = { :type => :list, :order => 16, :values => [["<< #{l(:label_me)} >>", "me"]] }
|
|
201 | 202 |
end |
202 | 203 |
|
203 | 204 |
if project |
... | ... | |
206 | 207 |
@available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } } |
207 | 208 |
end |
208 | 209 |
unless @project.shared_versions.empty? |
209 |
@available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } } |
|
210 |
@available_filters["affected_version_id"] = { :type => :list_optional, :order => 7, :values => @project.shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } } |
|
211 |
@available_filters["fixed_version_id"] = { :type => :list_optional, :order => 8, :values => @project.shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } } |
|
210 | 212 |
end |
211 | 213 |
unless @project.descendants.active.empty? |
212 |
@available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.descendants.visible.collect{|s| [s.name, s.id.to_s] } }
|
|
214 |
@available_filters["subproject_id"] = { :type => :list_subprojects, :order => 14, :values => @project.descendants.visible.collect{|s| [s.name, s.id.to_s] } }
|
|
213 | 215 |
end |
214 | 216 |
add_custom_fields_filters(@project.all_issue_custom_fields) |
215 | 217 |
else |
216 | 218 |
# global filters for cross project issue list |
217 | 219 |
system_shared_versions = Version.visible.find_all_by_sharing('system') |
218 | 220 |
unless system_shared_versions.empty? |
219 |
@available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => system_shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } } |
|
221 |
@available_filters["affected_version_id"] = { :type => :list_optional, :order => 7, :values => system_shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } } |
|
222 |
@available_filters["fixed_version_id"] = { :type => :list_optional, :order => 8, :values => system_shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } } |
|
220 | 223 |
end |
221 | 224 |
add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true})) |
222 | 225 |
# project filter |
redmine/app/models/version.rb 2011-01-05 15:52:31.147352600 -0700 | ||
---|---|---|
18 | 18 |
class Version < ActiveRecord::Base |
19 | 19 |
after_update :update_issues_from_sharing_change |
20 | 20 |
belongs_to :project |
21 |
has_many :affected_issues, :class_name => 'Issue', :foreign_key => 'affected_version_id', :dependent => :nullify |
|
21 | 22 |
has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id', :dependent => :nullify |
22 | 23 |
acts_as_customizable |
23 | 24 |
acts_as_attachable :view_permission => :view_files, |
... | ... | |
34 | 35 |
validates_inclusion_of :sharing, :in => VERSION_SHARINGS |
35 | 36 |
|
36 | 37 |
named_scope :open, :conditions => {:status => 'open'} |
38 |
named_scope :locked, :conditions => {:status => 'locked'} |
|
39 |
named_scope :closed, :conditions => {:status => 'closed'} |
|
37 | 40 |
named_scope :visible, lambda {|*args| { :include => :project, |
38 | 41 |
:conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } } |
39 | 42 |
|
redmine/app/views/issues/_attributes.rhtml 2011-01-05 16:01:29.807050000 -0700 | ||
---|---|---|
18 | 18 |
:title => l(:label_issue_category_new), |
19 | 19 |
:tabindex => 199) if authorize_for('issue_categories', 'new') %></p> |
20 | 20 |
<% end %> |
21 |
<% unless @issue.assignable_versions.empty? %> |
|
22 |
<p><%= f.select :fixed_version_id, version_options_for_select(@issue.assignable_versions, @issue.fixed_version), :include_blank => true %> |
|
21 |
<% unless @issue.affected_assignable_versions.empty? %> |
|
22 |
<p><%= f.select :affected_version_id, version_options_for_select(@issue.affected_assignable_versions, @issue.affected_version), :include_blank => true %></p> |
|
23 |
<% end %> |
|
24 |
<% unless @issue.fixed_assignable_versions.empty? %> |
|
25 |
<p><%= f.select :fixed_version_id, version_options_for_select(@issue.fixed_assignable_versions, @issue.fixed_version), :include_blank => true %> |
|
23 | 26 |
<%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'), |
24 | 27 |
l(:label_version_new), |
25 | 28 |
'version[name]', |
redmine/app/views/issues/show.rhtml 2011-01-05 13:21:19.754816600 -0700 | ||
---|---|---|
36 | 36 |
<% end %> |
37 | 37 |
</tr> |
38 | 38 |
<tr> |
39 |
<th class="fixed-version"><%=l(:field_fixed_version)%>:</th><td class="fixed-version"><%= @issue.fixed_version ? link_to_version(@issue.fixed_version) : "-" %></td>
|
|
39 |
<th class="affected-version"><%=l(:field_affected_version)%>:</th><td class="affected-version"><%= @issue.affected_version ? link_to_version(@issue.affected_version) : "-" %></td>
|
|
40 | 40 |
<% if @issue.estimated_hours %> |
41 | 41 |
<th class="estimated-hours"><%=l(:field_estimated_hours)%>:</th><td class="estimated-hours"><%= l_hours(@issue.estimated_hours) %></td> |
42 | 42 |
<% end %> |
43 | 43 |
</tr> |
44 |
<tr> |
|
45 |
<th class="fixed-version"><%=l(:field_fixed_version)%>:</th><td class="fixed-version"><%= @issue.fixed_version ? link_to_version(@issue.fixed_version) : "-" %></td> |
|
46 |
</tr> |
|
44 | 47 |
<%= render_custom_fields_rows(@issue) %> |
45 | 48 |
<%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %> |
46 | 49 |
</table> |
redmine/app/views/reports/issue_report.rhtml 2011-01-05 13:00:16.262355300 -0700 | ||
---|---|---|
17 | 17 |
</div> |
18 | 18 |
|
19 | 19 |
<div class="splitcontentright"> |
20 |
<h3><%=l(:field_version)%> <%= link_to image_tag('zoom_in.png'), :action => 'issue_report_details', :detail => 'version' %></h3> |
|
21 |
<%= render :partial => 'simple', :locals => { :data => @issues_by_version, :field_name => "fixed_version_id", :rows => @versions } %> |
|
20 |
<h3><%=l(:field_affected_version)%> <%= link_to image_tag('zoom_in.png'), :action => 'issue_report_details', :detail => 'affected_version' %></h3> |
|
21 |
<%= render :partial => 'simple', :locals => { :data => @issues_by_affected_version, :field_name => "affected_version_id", :rows => @versions } %> |
|
22 |
<br /> |
|
23 |
<h3><%=l(:field_fixed_version)%> <%= link_to image_tag('zoom_in.png'), :action => 'issue_report_details', :detail => 'fixed_version' %></h3> |
|
24 |
<%= render :partial => 'simple', :locals => { :data => @issues_by_fixed_version, :field_name => "fixed_version_id", :rows => @versions } %> |
|
22 | 25 |
<br /> |
23 | 26 |
<% if @project.children.any? %> |
24 | 27 |
<h3><%=l(:field_subproject)%> <%= link_to image_tag('zoom_in.png'), :action => 'issue_report_details', :detail => 'subproject' %></h3> |
redmine/config/locales/en.yml 2011-01-05 16:11:43.717229000 -0700 | ||
---|---|---|
233 | 233 |
field_due_date: Due date |
234 | 234 |
field_assigned_to: Assigned to |
235 | 235 |
field_priority: Priority |
236 |
field_affected_version: Affected version |
|
236 | 237 |
field_fixed_version: Target version |
237 | 238 |
field_user: User |
238 | 239 |
field_principal: Principal |