Patch #5510 » versions.patch
app/controllers/gantts_controller.rb (working copy) | ||
---|---|---|
23 | 23 |
:conditions => ["(((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to] |
24 | 24 |
) |
25 | 25 |
# Issues that don't have a due date but that are assigned to a version with a date |
26 |
events += @query.issues(:include => [:tracker, :assigned_to, :priority, :fixed_version],
|
|
26 |
events += @query.issues(:include => [:tracker, :assigned_to, :priority, :versions],
|
|
27 | 27 |
:order => "start_date, effective_date", |
28 | 28 |
:conditions => ["(((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date<? and effective_date>?)) and start_date is not null and due_date is null and effective_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to] |
29 | 29 |
) |
app/controllers/issue_versions_controller.rb (revision 0) | ||
---|---|---|
1 |
# redMine - project management software |
|
2 |
# Copyright (C) 2006-2007 Jean-Philippe Lang |
|
3 |
# |
|
4 |
# This program is free software; you can redistribute it and/or |
|
5 |
# modify it under the terms of the GNU General Public License |
|
6 |
# as published by the Free Software Foundation; either version 2 |
|
7 |
# of the License, or (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU General Public License |
|
15 |
# along with this program; if not, write to the Free Software |
|
16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
17 |
|
|
18 |
class IssueVersionsController < ApplicationController |
|
19 |
before_filter :find_issue, :find_project_from_association, :authorize |
|
20 |
helper :projects |
|
21 |
|
|
22 |
def new |
|
23 |
@version = Version.find(params[:id]) |
|
24 |
@issue.versions << @version if request.post? |
|
25 |
respond_to do |format| |
|
26 |
format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue } |
|
27 |
format.js do |
|
28 |
render :update do |page| |
|
29 |
page.replace_html "versions", :partial => 'issues/versions' |
|
30 |
end |
|
31 |
end |
|
32 |
end |
|
33 |
end |
|
34 |
|
|
35 |
def destroy |
|
36 |
@version = Version.find(params[:id]) |
|
37 |
if request.post? && @issue.versions.include?(@version) |
|
38 |
@issue.versions.delete(@version) |
|
39 |
@issue.reload |
|
40 |
end |
|
41 |
respond_to do |format| |
|
42 |
format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue } |
|
43 |
format.js { render(:update) {|page| page.replace_html "versions", :partial => 'issues/versions'} } |
|
44 |
end |
|
45 |
end |
|
46 |
|
|
47 |
private |
|
48 |
def find_issue |
|
49 |
@issue = @object = Issue.find(params[:issue_id]) |
|
50 |
rescue ActiveRecord::RecordNotFound |
|
51 |
render_404 |
|
52 |
end |
|
53 |
end |
app/controllers/issues_controller.rb (working copy) | ||
---|---|---|
73 | 73 |
|
74 | 74 |
@issue_count = @query.issue_count |
75 | 75 |
@issue_pages = Paginator.new self, @issue_count, limit, params['page'] |
76 |
@issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
|
|
76 |
@issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :versions],
|
|
77 | 77 |
:order => sort_clause, |
78 | 78 |
:offset => @issue_pages.current.offset, |
79 | 79 |
:limit => limit) |
... | ... | |
158 | 158 |
|
159 | 159 |
# Attributes that can be updated on workflow transition (without :edit permission) |
160 | 160 |
# TODO: make it configurable (at least per role) |
161 |
UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
|
|
161 |
UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION) |
|
162 | 162 |
|
163 | 163 |
def edit |
164 | 164 |
update_issue_from_params |
... | ... | |
229 | 229 |
@issues.each do |issue| |
230 | 230 |
issue.reload |
231 | 231 |
journal = issue.init_journal(User.current, params[:notes]) |
232 |
if attributes.include?(:version_id) |
|
233 |
if attributes[:version_id].blank? |
|
234 |
issue.versions.clear |
|
235 |
else |
|
236 |
issue.versions << Version.find(attributes[:version_id]) |
|
237 |
end |
|
238 |
end |
|
232 | 239 |
issue.safe_attributes = attributes |
233 | 240 |
call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue }) |
234 | 241 |
unless issue.save |
app/controllers/projects_controller.rb (working copy) | ||
---|---|---|
302 | 302 |
unless @selected_tracker_ids.empty? |
303 | 303 |
@versions.each do |version| |
304 | 304 |
issues = version.fixed_issues.visible.find(:all, |
305 |
:include => [:project, :status, :tracker, :priority],
|
|
306 |
:conditions => {:tracker_id => @selected_tracker_ids, :project_id => project_ids},
|
|
307 |
:order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id")
|
|
305 |
:include => [:project, :status, :tracker, :priority], |
|
306 |
:conditions => {:tracker_id => @selected_tracker_ids, :project_id => project_ids}, |
|
307 |
:order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id") |
|
308 | 308 |
@issues_by_version[version] = issues |
309 | 309 |
end |
310 | 310 |
end |
app/controllers/reports_controller.rb (working copy) | ||
---|---|---|
47 | 47 |
@data = Issue.by_tracker(@project) |
48 | 48 |
@report_title = l(:field_tracker) |
49 | 49 |
when "version" |
50 |
@field = "fixed_version_id"
|
|
50 |
@field = "version_id" |
|
51 | 51 |
@rows = @project.shared_versions.sort |
52 | 52 |
@data = Issue.by_version(@project) |
53 | 53 |
@report_title = l(:field_version) |
app/controllers/timelog_controller.rb (working copy) | ||
---|---|---|
33 | 33 |
@available_criterias = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id", |
34 | 34 |
:klass => Project, |
35 | 35 |
:label => :label_project}, |
36 |
'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
|
|
36 |
'version' => {:sql => "#{Version.table_name}.id",
|
|
37 | 37 |
:klass => Version, |
38 | 38 |
:label => :label_version}, |
39 | 39 |
'category' => {:sql => "#{Issue.table_name}.category_id", |
... | ... | |
101 | 101 |
sql << " FROM #{TimeEntry.table_name}" |
102 | 102 |
sql << " LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id" |
103 | 103 |
sql << " LEFT JOIN #{Project.table_name} ON #{TimeEntry.table_name}.project_id = #{Project.table_name}.id" |
104 |
# Note - If an issue is in multiple versions and if the user asks to group times |
|
105 |
# by version, then that issue's time will be included for each version. So if the |
|
106 |
# issue is in 3 versions, then its time will be counted 3 times over, once per |
|
107 |
# version. That is intentional for the breakdown by versions, but means the |
|
108 |
# total time will be too high. |
|
109 |
if @criterias.include?('version') |
|
110 |
sql << " LEFT JOIN issues_versions ON #{Issue.table_name}.id = issues_versions.issue_id" |
|
111 |
sql << " LEFT JOIN #{Version.table_name} ON issues_versions.version_id = #{Version.table_name}.id" |
|
112 |
end |
|
104 | 113 |
sql << " WHERE" |
105 | 114 |
sql << " (%s) AND" % sql_condition |
106 | 115 |
sql << " (spent_on BETWEEN '%s' AND '%s')" % [ActiveRecord::Base.connection.quoted_date(@from), ActiveRecord::Base.connection.quoted_date(@to)] |
app/controllers/versions_controller.rb (working copy) | ||
---|---|---|
43 | 43 |
flash[:notice] = l(:notice_successful_create) |
44 | 44 |
redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project |
45 | 45 |
end |
46 |
format.js do |
|
47 |
# IE doesn't support the replace_html rjs method for select box options |
|
48 |
render(:update) {|page| page.replace "issue_fixed_version_id", |
|
49 |
content_tag('select', '<option></option>' + version_options_for_select(@project.shared_versions.open, @version), :id => 'issue_fixed_version_id', :name => 'issue[fixed_version_id]') |
|
50 |
} |
|
51 |
end |
|
52 | 46 |
end |
53 | 47 |
else |
54 | 48 |
respond_to do |format| |
app/helpers/issues_helper.rb (working copy) | ||
---|---|---|
110 | 110 |
value = format_date(detail.value.to_date) if detail.value |
111 | 111 |
old_value = format_date(detail.old_value.to_date) if detail.old_value |
112 | 112 | |
113 |
when ['project_id', 'status_id', 'tracker_id', 'assigned_to_id', 'priority_id', 'category_id', 'fixed_version_id'].include?(detail.prop_key)
|
|
113 |
when ['project_id', 'status_id', 'tracker_id', 'assigned_to_id', 'priority_id', 'category_id', 'version_id'].include?(detail.prop_key) |
|
114 | 114 |
value = find_name_by_reflection(field, detail.value) |
115 | 115 |
old_value = find_name_by_reflection(field, detail.old_value) |
116 | 116 | |
... | ... | |
189 | 189 |
l(:field_subject), |
190 | 190 |
l(:field_assigned_to), |
191 | 191 |
l(:field_category), |
192 |
l(:field_fixed_version),
|
|
192 |
l(:field_version), |
|
193 | 193 |
l(:field_author), |
194 | 194 |
l(:field_start_date), |
195 | 195 |
l(:field_due_date), |
... | ... | |
216 | 216 |
issue.subject, |
217 | 217 |
issue.assigned_to, |
218 | 218 |
issue.category, |
219 |
issue.fixed_version,
|
|
219 |
issue.versions.map {|v| v.id}.join("|"),
|
|
220 | 220 |
issue.author.name, |
221 | 221 |
format_date(issue.start_date), |
222 | 222 |
format_date(issue.due_date), |
app/helpers/versions_helper.rb (working copy) | ||
---|---|---|
27 | 27 |
begin |
28 | 28 |
# Total issue count |
29 | 29 |
Issue.count(:group => criteria, |
30 |
:conditions => ["#{Issue.table_name}.fixed_version_id = ?", version.id]).each {|c,s| h[c][0] = s} |
|
30 |
:include => :versions, |
|
31 |
:conditions => ["#{Version.table_name}.id = ?", version.id]).each {|c,s| h[c][0] = s} |
|
31 | 32 |
# Open issues count |
32 | 33 |
Issue.count(:group => criteria, |
33 | 34 |
:include => :status, |
app/models/issue.rb (working copy) | ||
---|---|---|
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 :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
|
|
24 |
has_and_belongs_to_many :versions
|
|
25 | 25 |
belongs_to :priority, :class_name => 'IssuePriority', :foreign_key => 'priority_id' |
26 | 26 |
belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id' |
27 | 27 | |
... | ... | |
121 | 121 |
# reassign to the category with same name if any |
122 | 122 |
new_category = issue.category.nil? ? nil : new_project.issue_categories.find_by_name(issue.category.name) |
123 | 123 |
issue.category = new_category |
124 |
# Keep the fixed_version if it's still valid in the new_project |
|
125 |
unless new_project.shared_versions.include?(issue.fixed_version) |
|
126 |
issue.fixed_version = nil |
|
124 |
# Keep the versions that are still valid in the new_project |
|
125 |
issue.versions.each do |version| |
|
126 |
unless new_project.shared_versions.include?(version) |
|
127 |
issue.versions.delete(version) |
|
128 |
end |
|
127 | 129 |
end |
128 | 130 |
issue.project = new_project |
129 | 131 |
if issue.parent && issue.parent.project_id != issue.project_id |
... | ... | |
204 | 206 |
category_id |
205 | 207 |
assigned_to_id |
206 | 208 |
priority_id |
207 |
fixed_version_id |
|
208 | 209 |
subject |
209 | 210 |
description |
210 | 211 |
start_date |
... | ... | |
273 | 274 |
errors.add :start_date, :invalid |
274 | 275 |
end |
275 | 276 |
|
276 |
if fixed_version |
|
277 |
if !assignable_versions.include?(fixed_version) |
|
278 |
errors.add :fixed_version_id, :inclusion |
|
279 |
elsif reopened? && fixed_version.closed? |
|
280 |
errors.add_to_base I18n.t(:error_can_not_reopen_issue_on_closed_version) |
|
281 |
end |
|
282 |
end |
|
283 |
|
|
284 | 277 |
# Checks that the issue can not be added/moved to a disabled tracker |
285 | 278 |
if project && (tracker_id_changed? || project_id_changed?) |
286 | 279 |
unless project.trackers.include?(tracker) |
... | ... | |
365 | 358 |
|
366 | 359 |
# Versions that the issue can be assigned to |
367 | 360 |
def assignable_versions |
368 |
@assignable_versions ||= (project.shared_versions.open + [Version.find_by_id(fixed_version_id_was)]).compact.uniq.sort
|
|
361 |
@assignable_versions ||= (project.shared_versions.open).compact.uniq.sort |
|
369 | 362 |
end |
370 | 363 |
|
371 | 364 |
# Returns true if this issue is blocked by another issue that is still open |
... | ... | |
424 | 417 |
# Returns the due date or the target due date if any |
425 | 418 |
# Used on gantt chart |
426 | 419 |
def due_before |
427 |
due_date || (fixed_version ? fixed_version.effective_date : nil)
|
|
420 |
due_date || (versions.empty? ? nil : versions.map {|version| version.effective_date}.min)
|
|
428 | 421 |
end |
429 | 422 |
|
430 | 423 |
# Returns the time scheduled for this issue. |
... | ... | |
520 | 513 |
# Unassigns issues from +version+ if it's no longer shared with issue's project |
521 | 514 |
def self.update_versions_from_sharing_change(version) |
522 | 515 |
# Update issues assigned to the version |
523 |
update_versions(["#{Issue.table_name}.fixed_version_id = ?", version.id])
|
|
516 |
update_versions(["#{Version.table_name}.id = ?", version.id])
|
|
524 | 517 |
end |
525 | 518 |
|
526 | 519 |
# Unassigns issues from versions that are no longer shared |
... | ... | |
557 | 550 |
end |
558 | 551 | |
559 | 552 |
def self.by_version(project) |
560 |
count_and_group_by(:project => project, |
|
561 |
:field => 'fixed_version_id', |
|
562 |
:joins => Version.table_name) |
|
553 |
joins = "issues_versions, #{Version.table_name}" |
|
554 |
where = "i.id = issues_versions.issue_id and issues_versions.version_id = j.id" |
|
555 |
ActiveRecord::Base.connection.select_all("select |
|
556 |
s.id as status_id, |
|
557 |
s.is_closed as closed, |
|
558 |
j.id as version_id, |
|
559 |
count(i.id) as total |
|
560 |
from |
|
561 |
#{Issue.table_name} i, #{IssueStatus.table_name} s, #{joins} as j |
|
562 |
where |
|
563 |
i.status_id=s.id |
|
564 |
and #{where} |
|
565 |
and i.project_id=#{project.id} |
|
566 |
group by s.id, s.is_closed, j.id") |
|
563 | 567 |
end |
564 | ||
568 |
|
|
565 | 569 |
def self.by_priority(project) |
566 | 570 |
count_and_group_by(:project => project, |
567 | 571 |
:field => 'priority_id', |
... | ... | |
708 | 712 |
end |
709 | 713 |
|
710 | 714 |
# Update issues so their versions are not pointing to a |
711 |
# fixed_version that is not shared with the issue's project
|
|
715 |
# version that is not shared with the issue's project |
|
712 | 716 |
def self.update_versions(conditions=nil) |
713 |
# Only need to update issues with a fixed_version from
|
|
717 |
# Only need to update issues with a fversion from |
|
714 | 718 |
# a different project and that is not systemwide shared |
715 |
Issue.all(:conditions => merge_conditions("#{Issue.table_name}.fixed_version_id IS NOT NULL" + |
|
716 |
" AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" + |
|
717 |
" AND #{Version.table_name}.sharing <> 'system'", |
|
719 |
Issue.all(:conditions => merge_conditions("#{Issue.table_name}.project_id <> #{Version.table_name}.project_id" + |
|
720 |
" AND #{Version.table_name}.sharing <> 'system'", |
|
718 | 721 |
conditions), |
719 |
:include => [:project, :fixed_version]
|
|
722 |
:include => [:project, :versions]
|
|
720 | 723 |
).each do |issue| |
721 |
next if issue.project.nil? || issue.fixed_version.nil? |
|
722 |
unless issue.project.shared_versions.include?(issue.fixed_version) |
|
723 |
issue.init_journal(User.current) |
|
724 |
issue.fixed_version = nil |
|
725 |
issue.save |
|
724 |
next if issue.project.nil? |
|
725 |
issue.versions.each do |version| |
|
726 |
unless issue.project.shared_versions.include?(version) |
|
727 |
issue.init_journal(User.current) |
|
728 |
issue.versions.delete(version) |
|
729 |
issue.save |
|
730 |
end |
|
726 | 731 |
end |
727 | 732 |
end |
728 | 733 |
end |
app/models/project.rb (working copy) | ||
---|---|---|
225 | 225 |
# Check that there is no issue of a non descendant project that is assigned |
226 | 226 |
# to one of the project or descendant versions |
227 | 227 |
v_ids = self_and_descendants.collect {|p| p.version_ids}.flatten |
228 |
if v_ids.any? && Issue.find(:first, :include => :project,
|
|
228 |
if v_ids.any? && Issue.find(:first, :include => [:project, :versions],
|
|
229 | 229 |
:conditions => ["(#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?)" + |
230 |
" AND #{Issue.table_name}.fixed_version_id IN (?)", lft, rgt, v_ids])
|
|
230 |
" AND #{Version.table_name}.id IN (?)", lft, rgt, v_ids])
|
|
231 | 231 |
return false |
232 | 232 |
end |
233 | 233 |
Project.transaction do |
... | ... | |
563 | 563 |
new_issue = Issue.new |
564 | 564 |
new_issue.copy_from(issue) |
565 | 565 |
new_issue.project = self |
566 |
# Reassign fixed_versions by name, since names are unique per
|
|
566 |
# Reassign versions by name, since names are unique per |
|
567 | 567 |
# project and the versions for self are not yet saved |
568 |
if issue.fixed_version |
|
569 |
new_issue.fixed_version = self.versions.select {|v| v.name == issue.fixed_version.name}.first |
|
568 |
issue.versions.each do |issue_version| |
|
569 |
self.versions.select {|v| v.name == issue_version.name}.each do |version| |
|
570 |
new_issue.versions << version |
|
571 |
end |
|
570 | 572 |
end |
571 | 573 |
# Reassign the category by name, since names are unique per |
572 | 574 |
# project and the categories for self are not yet saved |
app/models/query.rb (working copy) | ||
---|---|---|
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(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc', :groupable => true),
|
|
132 |
QueryColumn.new(:versions, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc', :groupable => true),
|
|
133 | 133 |
QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"), |
134 | 134 |
QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"), |
135 | 135 |
QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"), |
... | ... | |
206 | 206 |
@available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } } |
207 | 207 |
end |
208 | 208 |
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] } }
|
|
209 |
@available_filters["version_id"] = { :type => :list_optional, :order => 7, :values => @project.shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } } |
|
210 | 210 |
end |
211 | 211 |
unless @project.descendants.active.empty? |
212 | 212 |
@available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.descendants.visible.collect{|s| [s.name, s.id.to_s] } } |
... | ... | |
216 | 216 |
# global filters for cross project issue list |
217 | 217 |
system_shared_versions = Version.visible.find_all_by_sharing('system') |
218 | 218 |
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] } }
|
|
219 |
@available_filters["version_id"] = { :type => :list_optional, :order => 7, :values => system_shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } } |
|
220 | 220 |
end |
221 | 221 |
add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true})) |
222 | 222 |
end |
... | ... | |
426 | 426 |
db_field = 'user_id' |
427 | 427 |
sql << "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " |
428 | 428 |
sql << sql_for_field(field, '=', v, db_table, db_field) + ')' |
429 |
elsif field == 'version_id' |
|
430 |
db_table = 'issues_versions' |
|
431 |
db_field = 'version_id' |
|
432 |
sql << "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.issue_id FROM #{db_table} WHERE " |
|
433 |
sql << sql_for_field(field, '=', v, db_table, db_field) + ')' |
|
429 | 434 |
else |
430 | 435 |
# regular field |
431 | 436 |
db_table = Issue.table_name |
app/models/version.rb (working copy) | ||
---|---|---|
18 | 18 |
class Version < ActiveRecord::Base |
19 | 19 |
after_update :update_issues_from_sharing_change |
20 | 20 |
belongs_to :project |
21 |
has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id', :dependent => :nullify
|
|
21 |
has_and_belongs_to_many :fixed_issues, :class_name => 'Issue', :join_table => 'issues_versions'
|
|
22 | 22 |
acts_as_customizable |
23 | 23 |
acts_as_attachable :view_permission => :view_files, |
24 | 24 |
:delete_permission => :manage_files |
... | ... | |
58 | 58 |
|
59 | 59 |
# Returns the total reported time for this version |
60 | 60 |
def spent_hours |
61 |
@spent_hours ||= TimeEntry.sum(:hours, :include => :issue, :conditions => ["#{Issue.table_name}.fixed_version_id = ?", id]).to_f |
|
61 |
@spent_hours ||= TimeEntry.sum(:hours, :include => {:issue => :versions}, |
|
62 |
:conditions => ["#{Version.table_name}.id = ?", id]).to_f |
|
62 | 63 |
end |
63 | 64 |
|
64 | 65 |
def closed? |
... | ... | |
107 | 108 |
|
108 | 109 |
# Returns the total amount of open issues for this version. |
109 | 110 |
def open_issues_count |
110 |
@open_issues_count ||= Issue.count(:all, :conditions => ["fixed_version_id = ? AND is_closed = ?", self.id, false], :include => :status) |
|
111 |
@open_issues_count ||= Issue.count(:all, :conditions => ["#{Version.table_name}.id = ? AND is_closed = ?", self.id, false], |
|
112 |
:include => [:status, :versions]) |
|
111 | 113 |
end |
112 | 114 | |
113 | 115 |
# Returns the total amount of closed issues for this version. |
114 | 116 |
def closed_issues_count |
115 |
@closed_issues_count ||= Issue.count(:all, :conditions => ["fixed_version_id = ? AND is_closed = ?", self.id, true], :include => :status) |
|
117 |
@closed_issues_count ||= Issue.count(:all, :conditions => ["#{Version.table_name}.id = ? AND is_closed = ?", self.id, true], |
|
118 |
:include => [:status, :versions]) |
|
116 | 119 |
end |
117 | 120 |
|
118 | 121 |
def wiki_page |
... | ... | |
170 | 173 |
|
171 | 174 |
# Returns the average estimated time of assigned issues |
172 | 175 |
# or 1 if no issue has an estimated time |
173 |
# Used to weigth unestimated issues in progress calculation
|
|
176 |
# Used to weight unestimated issues in progress calculation
|
|
174 | 177 |
def estimated_average |
175 | 178 |
if @estimated_average.nil? |
176 | 179 |
average = fixed_issues.average(:estimated_hours).to_f |
app/views/issue_versions/_form.rhtml (revision 0) | ||
---|---|---|
1 |
<%= error_messages_for 'version' %> |
|
2 |
|
|
3 |
<p> |
|
4 |
<%= select_tag :id, version_options_for_select(@issue.assignable_versions ), :include_blank => false %> |
|
5 |
<%= submit_tag l(:button_add) %> |
|
6 |
<%= toggle_link l(:button_cancel), 'new-version-form'%> |
|
7 |
</p> |
app/views/issues/_attributes.rhtml (working copy) | ||
---|---|---|
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 %> |
|
23 |
<%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'), |
|
24 |
l(:label_version_new), |
|
25 |
'version[name]', |
|
26 |
{:controller => 'versions', :action => 'new', :project_id => @project}, |
|
27 |
:title => l(:label_version_new), |
|
28 |
:tabindex => 200) if authorize_for('versions', 'new') %> |
|
29 |
</p> |
|
30 |
<% end %> |
|
31 | 21 |
</div> |
32 | 22 | |
33 | 23 |
<div class="splitcontentright"> |
app/views/issues/_form_update.rhtml (working copy) | ||
---|---|---|
7 | 7 |
<% if Issue.use_field_for_done_ratio? %> |
8 | 8 |
<p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p> |
9 | 9 |
<% end %> |
10 |
<% unless @issue.assignable_versions.empty? %> |
|
11 |
<p><%= f.select :fixed_version_id, (@issue.assignable_versions.collect {|v| [v.name, v.id]}), :include_blank => true %></p> |
|
12 |
<% end %> |
|
13 | 10 |
</div> |
14 | 11 |
</div> |
app/views/issues/_versions.rhtml (revision 0) | ||
---|---|---|
1 |
<div class="contextual"> |
|
2 |
<% if authorize_for('issue_versions', 'new') %> |
|
3 |
<%= toggle_link l(:button_add), 'new-version-form'%> |
|
4 |
<% end %> |
|
5 |
</div> |
|
6 |
|
|
7 |
<p><strong><%=l(:label_version_plural)%></strong></p> |
|
8 |
|
|
9 |
<% if @issue.versions.any? %> |
|
10 |
<table style="width:100%"> |
|
11 |
<% @issue.versions.each do |version| %> |
|
12 |
<tr> |
|
13 |
<td><%= link_to_version(version, :truncate => 60) %></td> |
|
14 |
<td><%= format_date(version.effective_date) %></td> |
|
15 |
<td><%= link_to_remote(image_tag('delete.png'), { :url => {:controller => 'issue_versions', :action => 'destroy', :issue_id => @issue, :id => version}, |
|
16 |
:method => :post |
|
17 |
}, :title => l(:label_version_delete)) %></td> |
|
18 |
</tr> |
|
19 |
<% end %> |
|
20 |
</table> |
|
21 |
<% end %> |
|
22 |
|
|
23 |
<% remote_form_for(:version, @version, |
|
24 |
:url => {:controller => 'issue_versions', :action => 'new', :issue_id => @issue}, |
|
25 |
:method => :post, |
|
26 |
:html => {:id => 'new-version-form', :style => 'display: none;'}) do |f| %> |
|
27 |
<%= render :partial => 'issue_versions/form', :locals => {:f => f}%> |
|
28 |
<% end %> |
app/views/issues/bulk_edit.rhtml (working copy) | ||
---|---|---|
36 | 36 |
options_from_collection_for_select(@project.issue_categories, :id, :name)) %> |
37 | 37 |
</p> |
38 | 38 |
<p> |
39 |
<label><%= l(:field_fixed_version) %></label>
|
|
40 |
<%= select_tag('issue[fixed_version_id]', content_tag('option', l(:label_no_change_option), :value => '') +
|
|
39 |
<label><%= l(:field_version) %></label> |
|
40 |
<%= select_tag('issue[version_id]', content_tag('option', l(:label_no_change_option), :value => '') + |
|
41 | 41 |
content_tag('option', l(:label_none), :value => 'none') + |
42 | 42 |
version_options_for_select(@project.shared_versions.open)) %> |
43 | 43 |
</p> |
app/views/issues/context_menu.rhtml (working copy) | ||
---|---|---|
40 | 40 |
</li> |
41 | 41 |
<% unless @project.nil? || @project.shared_versions.open.empty? -%> |
42 | 42 |
<li class="folder"> |
43 |
<a href="#" class="submenu"><%= l(:field_fixed_version) %></a>
|
|
43 |
<a href="#" class="submenu"><%= l(:label_version_plural) %></a>
|
|
44 | 44 |
<ul> |
45 | 45 |
<% @project.shared_versions.open.sort.each do |v| -%> |
46 |
<li><%= context_menu_link format_version_name(v), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'fixed_version_id' => v}, :back_url => @back}, :method => :post,
|
|
47 |
:selected => (@issue && v == @issue.fixed_version), :disabled => !@can[:update] %></li>
|
|
46 |
<li><%= context_menu_link format_version_name(v), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'version_id' => v}, :back_url => @back}, :method => :post, |
|
47 |
:selected => (@issue && @issue.versions.include?(v)), :disabled => !@can[:update] %></li>
|
|
48 | 48 |
<% end -%> |
49 |
<li><%= context_menu_link l(:label_none), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'fixed_version_id' => 'none'}, :back_url => @back}, :method => :post,
|
|
50 |
:selected => (@issue && @issue.fixed_version.nil?), :disabled => !@can[:update] %></li>
|
|
49 |
<li><%= context_menu_link l(:label_none), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'version_id' => 'none'}, :back_url => @back}, :method => :post, |
|
50 |
:selected => (@issue && @issue.versions.empty?), :disabled => !@can[:update] %></li>
|
|
51 | 51 |
</ul> |
52 | 52 |
</li> |
53 | 53 |
<% end %> |
app/views/issues/index.xml.builder (working copy) | ||
---|---|---|
10 | 10 |
xml.author(:id => issue.author_id, :name => issue.author.name) unless issue.author.nil? |
11 | 11 |
xml.assigned_to(:id => issue.assigned_to_id, :name => issue.assigned_to.name) unless issue.assigned_to.nil? |
12 | 12 |
xml.category(:id => issue.category_id, :name => issue.category.name) unless issue.category.nil? |
13 |
xml.fixed_version(:id => issue.fixed_version_id, :name => issue.fixed_version.name) unless issue.fixed_version.nil? |
|
13 |
xml.versions do |
|
14 |
issue.versions.each do |version| |
|
15 |
xml.version(:id => version.id, :name => version.name) |
|
16 |
end |
|
17 |
end |
|
14 | 18 |
xml.parent(:id => issue.parent_id) unless issue.parent.nil? |
15 | 19 |
|
16 | 20 |
xml.subject issue.subject |
app/views/issues/show.rhtml (working copy) | ||
---|---|---|
35 | 35 |
<td class="spent-time"><%= @issue.spent_hours > 0 ? (link_to l_hours(@issue.spent_hours), {:controller => 'timelog', :action => 'details', :project_id => @project, :issue_id => @issue}) : "-" %></td> |
36 | 36 |
<% end %> |
37 | 37 |
</tr> |
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> |
|
40 |
<% if @issue.estimated_hours %> |
|
41 |
<th class="estimated-hours"><%=l(:field_estimated_hours)%>:</th><td class="estimated-hours"><%= l_hours(@issue.estimated_hours) %></td> |
|
42 |
<% end %> |
|
43 |
</tr> |
|
44 | 38 |
<%= render_custom_fields_rows(@issue) %> |
45 | 39 |
<%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %> |
46 | 40 |
</table> |
47 | 41 |
<hr /> |
48 | 42 | |
43 |
<div id="versions"> |
|
44 |
<%= render :partial => 'issues/versions' %> |
|
45 |
</div> |
|
46 |
<hr /> |
|
47 | ||
49 | 48 |
<div class="contextual"> |
50 | 49 |
<%= link_to_remote_if_authorized(l(:button_quote), { :url => {:action => 'reply', :id => @issue} }, :class => 'icon icon-comment') unless @issue.description.blank? %> |
51 | 50 |
</div> |
52 |
|
|
51 | ||
53 | 52 |
<p><strong><%=l(:field_description)%></strong></p> |
54 | 53 |
<div class="wiki"> |
55 | 54 |
<%= textilizable @issue, :description, :attachments => @issue.attachments %> |
app/views/issues/show.xml.builder (working copy) | ||
---|---|---|
8 | 8 |
xml.author(:id => @issue.author_id, :name => @issue.author.name) unless @issue.author.nil? |
9 | 9 |
xml.assigned_to(:id => @issue.assigned_to_id, :name => @issue.assigned_to.name) unless @issue.assigned_to.nil? |
10 | 10 |
xml.category(:id => @issue.category_id, :name => @issue.category.name) unless @issue.category.nil? |
11 |
xml.fixed_version(:id => @issue.fixed_version_id, :name => @issue.fixed_version.name) unless @issue.fixed_version.nil? |
|
11 |
xml.versions each do |version| |
|
12 |
xml.version(:id => version.id, :name => version.name) |
|
13 |
end |
|
12 | 14 |
xml.parent(:id => @issue.parent_id) unless @issue.parent.nil? |
13 | 15 |
|
14 | 16 |
xml.subject @issue.subject |
app/views/mailer/_issue_text_html.rhtml (working copy) | ||
---|---|---|
6 | 6 |
<li><%=l(:field_priority)%>: <%=h issue.priority %></li> |
7 | 7 |
<li><%=l(:field_assigned_to)%>: <%=h issue.assigned_to %></li> |
8 | 8 |
<li><%=l(:field_category)%>: <%=h issue.category %></li> |
9 |
<li><%=l(:field_fixed_version)%>: <%=h issue.fixed_version %></li>
|
|
9 |
<li><%=l(:field_version)%>: <%=h issue.versions.map {|version| version.name}.join(", ") %></li>
|
|
10 | 10 |
<% issue.custom_values.each do |c| %> |
11 | 11 |
<li><%=h c.custom_field.name %>: <%=h show_value(c) %></li> |
12 | 12 |
<% end %> |
app/views/mailer/_issue_text_plain.rhtml (working copy) | ||
---|---|---|
6 | 6 |
<%=l(:field_priority)%>: <%= issue.priority %> |
7 | 7 |
<%=l(:field_assigned_to)%>: <%= issue.assigned_to %> |
8 | 8 |
<%=l(:field_category)%>: <%= issue.category %> |
9 |
<%=l(:field_fixed_version)%>: <%= issue.fixed_version %>
|
|
9 |
<%=l(:field_version)%>: <%= issue.versions.map {|version| version.name}.join(", ") %>
|
|
10 | 10 |
<% issue.custom_values.each do |c| %><%= c.custom_field.name %>: <%= show_value(c) %> |
11 | 11 |
<% end %> |
12 | 12 |
app/views/reports/issue_report.rhtml (working copy) | ||
---|---|---|
17 | 17 | |
18 | 18 |
<div class="splitcontentright"> |
19 | 19 |
<h3><%=l(:field_version)%> <%= link_to image_tag('zoom_in.png'), :action => 'issue_report_details', :detail => 'version' %></h3> |
20 |
<%= render :partial => 'simple', :locals => { :data => @issues_by_version, :field_name => "fixed_version_id", :rows => @versions } %>
|
|
20 |
<%= render :partial => 'simple', :locals => { :data => @issues_by_version, :field_name => "version_id", :rows => @versions } %> |
|
21 | 21 |
<br /> |
22 | 22 |
<% if @project.children.any? %> |
23 | 23 |
<h3><%=l(:field_subproject)%> <%= link_to image_tag('zoom_in.png'), :action => 'issue_report_details', :detail => 'subproject' %></h3> |
app/views/versions/_issue_counts.rhtml (working copy) | ||
---|---|---|
19 | 19 |
:action => 'index', |
20 | 20 |
:project_id => version.project, |
21 | 21 |
:set_filter => 1, |
22 |
:fixed_version_id => version,
|
|
22 |
:version_id => version, |
|
23 | 23 |
"#{criteria}_id" => count[:group]} %> |
24 | 24 |
</td> |
25 | 25 |
<td width="240px"> |
app/views/versions/_overview.rhtml (working copy) | ||
---|---|---|
16 | 16 |
<% if version.fixed_issues.count > 0 %> |
17 | 17 |
<%= progress_bar([version.closed_pourcent, version.completed_pourcent], :width => '40em', :legend => ('%0.0f%' % version.completed_pourcent)) %> |
18 | 18 |
<p class="progress-info"> |
19 |
<%= link_to_if(version.closed_issues_count > 0, l(:label_x_closed_issues_abbr, :count => version.closed_issues_count), :controller => 'issues', :action => 'index', :project_id => version.project, :status_id => 'c', :fixed_version_id => version, :set_filter => 1) %>
|
|
19 |
<%= link_to_if(version.closed_issues_count > 0, l(:label_x_closed_issues_abbr, :count => version.closed_issues_count), :controller => 'issues', :action => 'index', :project_id => version.project, :status_id => 'c', :version_id => version, :set_filter => 1) %> |
|
20 | 20 |
(<%= '%0.0f' % (version.closed_issues_count.to_f / version.fixed_issues.count * 100) %>%) |
21 | 21 |
  |
22 |
<%= link_to_if(version.open_issues_count > 0, l(:label_x_open_issues_abbr, :count => version.open_issues_count), :controller => 'issues', :action => 'index', :project_id => version.project, :status_id => 'o', :fixed_version_id => version, :set_filter => 1) %>
|
|
22 |
<%= link_to_if(version.open_issues_count > 0, l(:label_x_open_issues_abbr, :count => version.open_issues_count), :controller => 'issues', :action => 'index', :project_id => version.project, :status_id => 'o', :version_id => version, :set_filter => 1) %> |
|
23 | 23 |
(<%= '%0.0f' % (version.open_issues_count.to_f / version.fixed_issues.count * 100) %>%) |
24 | 24 |
</p> |
25 | 25 |
<% else %> |
config/locales/en.yml (working copy) | ||
---|---|---|
240 | 240 |
field_password: Password |
241 | 241 |
field_new_password: New password |
242 | 242 |
field_password_confirmation: Confirmation |
243 |
field_version: Version |
|
243 |
field_version: Versions
|
|
244 | 244 |
field_type: Type |
245 | 245 |
field_host: Host |
246 | 246 |
field_port: Port |
db/migrate/20100511054420_issue_versions_table.rb (revision 0) | ||
---|---|---|
1 |
class IssueVersionsTable < ActiveRecord::Migration |
|
2 |
def self.up |
|
3 |
execute <<-EOS |
|
4 |
CREATE TABLE issues_versions |
|
5 |
( |
|
6 |
issue_id integer NOT NULL, |
|
7 |
version_id integer NOT NULL, |
|
8 |
PRIMARY KEY (issue_id, version_id) |
|
9 |
); |
|
10 | ||
11 |
CREATE INDEX issues_versions_on_version_id |
|
12 |
ON issues_versions USING btree (version_id); |
|
13 |
EOS |
|
14 | ||
15 |
Version.find(:all).each do |version| |
|
16 |
puts "Migrating version #{version.name}" |
|
17 |
Issue.find(:all, :conditions => {:fixed_version_id => version.id}).each do |issue| |
|
18 |
version.fixed_issues << issue |
|
19 |
end |
|
20 |
end |
|
21 | ||
22 |
# Rename fixed_version_id for now - so we keep the data but make sure |
|
23 |
# that it isn't used |
|
24 |
rename_column :issues, :fixed_version_id, :fixed_version_id_old |
|
25 |
end |
|
26 | ||
27 |
def self.down |
|
28 |
drop_table :version_issues |
|
29 |
rename_column :issues, :fixed_version_id_old, :fixed_version_id |
|
30 |
end |
|
31 |
end |
lib/redmine.rb (working copy) | ||
---|---|---|
81 | 81 |
map.permission :view_issue_watchers, {} |
82 | 82 |
map.permission :add_issue_watchers, {:watchers => :new} |
83 | 83 |
map.permission :delete_issue_watchers, {:watchers => :destroy} |
84 |
# Versions |
|
85 |
map.permission :view_issue_versions, {} |
|
86 |
map.permission :add_issue_versions, {:issue_versions => :new} |
|
87 |
map.permission :delete_issue_versions, {:issue_versions => :destroy} |
|
84 | 88 |
end |
85 | 89 |
|
86 | 90 |
map.project_module :time_tracking do |map| |