##// END OF EJS Templates
Version sharing (#465) + optional inclusion of subprojects in the roadmap view (#2666)....
Jean-Philippe Lang -
r3009:5f8e9d711820
parent child
Show More
@@ -0,0 +1,10
1 class AddVersionsSharing < ActiveRecord::Migration
2 def self.up
3 add_column :versions, :sharing, :string, :default => 'none', :null => false
4 add_index :versions, :sharing
5 end
6
7 def self.down
8 remove_column :versions, :sharing
9 end
10 end
@@ -0,0 +1,63
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 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 require File.dirname(__FILE__) + '/../../test_helper'
19
20 class ProjectsHelperTest < HelperTestCase
21 include ApplicationHelper
22 include ProjectsHelper
23
24 fixtures :all
25
26 def setup
27 super
28 set_language_if_valid('en')
29 User.current = nil
30 end
31
32 def test_link_to_version_within_project
33 @project = Project.find(2)
34 User.current = User.find(1)
35 assert_equal '<a href="/versions/show/5">Alpha</a>', link_to_version(Version.find(5))
36 end
37
38 def test_link_to_version
39 User.current = User.find(1)
40 assert_equal '<a href="/versions/show/5">OnlineStore - Alpha</a>', link_to_version(Version.find(5))
41 end
42
43 def test_link_to_private_version
44 assert_equal 'OnlineStore - Alpha', link_to_version(Version.find(5))
45 end
46
47 def test_link_to_version_invalid_version
48 assert_equal '', link_to_version(Object)
49 end
50
51 def test_format_version_name_within_project
52 @project = Project.find(1)
53 assert_equal "0.1", format_version_name(Version.find(1))
54 end
55
56 def test_format_version_name
57 assert_equal "eCookbook - 0.1", format_version_name(Version.find(1))
58 end
59
60 def test_format_version_name_for_system_version
61 assert_equal "OnlineStore - Systemwide visible version", format_version_name(Version.find(7))
62 end
63 end
@@ -239,7 +239,7 class IssuesController < ApplicationController
239 priority = params[:priority_id].blank? ? nil : IssuePriority.find_by_id(params[:priority_id])
239 priority = params[:priority_id].blank? ? nil : IssuePriority.find_by_id(params[:priority_id])
240 assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id])
240 assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id])
241 category = (params[:category_id].blank? || params[:category_id] == 'none') ? nil : @project.issue_categories.find_by_id(params[:category_id])
241 category = (params[:category_id].blank? || params[:category_id] == 'none') ? nil : @project.issue_categories.find_by_id(params[:category_id])
242 fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.versions.find_by_id(params[:fixed_version_id])
242 fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.shared_versions.find_by_id(params[:fixed_version_id])
243 custom_field_values = params[:custom_field_values] ? params[:custom_field_values].reject {|k,v| v.blank?} : nil
243 custom_field_values = params[:custom_field_values] ? params[:custom_field_values].reject {|k,v| v.blank?} : nil
244
244
245 unsaved_issue_ids = []
245 unsaved_issue_ids = []
@@ -174,7 +174,11 class ProjectsController < ApplicationController
174 end
174 end
175
175
176 def archive
176 def archive
177 @project.archive if request.post? && @project.active?
177 if request.post?
178 unless @project.archive
179 flash[:error] = l(:error_can_not_archive_project)
180 end
181 end
178 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
182 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
179 end
183 end
180
184
@@ -224,7 +228,12 class ProjectsController < ApplicationController
224
228
225 # Add a new version to @project
229 # Add a new version to @project
226 def add_version
230 def add_version
227 @version = @project.versions.build(params[:version])
231 @version = @project.versions.build
232 if params[:version]
233 attributes = params[:version].dup
234 attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
235 @version.attributes = attributes
236 end
228 if request.post? and @version.save
237 if request.post? and @version.save
229 flash[:notice] = l(:notice_successful_create)
238 flash[:notice] = l(:notice_successful_create)
230 redirect_to :action => 'settings', :tab => 'versions', :id => @project
239 redirect_to :action => 'settings', :tab => 'versions', :id => @project
@@ -278,15 +287,53 class ProjectsController < ApplicationController
278 # Show changelog for @project
287 # Show changelog for @project
279 def changelog
288 def changelog
280 @trackers = @project.trackers.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
289 @trackers = @project.trackers.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
281 retrieve_selected_tracker_ids(@trackers)
290 retrieve_selected_tracker_ids(@trackers)
282 @versions = @project.versions.sort
291 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
292 project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id]
293
294 @versions = @project.shared_versions.sort
295
296 @issues_by_version = {}
297 unless @selected_tracker_ids.empty?
298 @versions.each do |version|
299 conditions = {:tracker_id => @selected_tracker_ids, "#{IssueStatus.table_name}.is_closed" => true}
300 if !@project.versions.include?(version)
301 conditions.merge!(:project_id => project_ids)
302 end
303 issues = version.fixed_issues.visible.find(:all,
304 :include => [:status, :tracker, :priority],
305 :conditions => conditions,
306 :order => "#{Tracker.table_name}.position, #{Issue.table_name}.id")
307 @issues_by_version[version] = issues
308 end
309 end
310 @versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].empty?}
283 end
311 end
284
312
285 def roadmap
313 def roadmap
286 @trackers = @project.trackers.find(:all, :conditions => ["is_in_roadmap=?", true])
314 @trackers = @project.trackers.find(:all, :conditions => ["is_in_roadmap=?", true], :order => 'position')
287 retrieve_selected_tracker_ids(@trackers)
315 retrieve_selected_tracker_ids(@trackers)
288 @versions = @project.versions.sort
316 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
289 @versions = @versions.select {|v| !v.completed? } unless params[:completed]
317 project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id]
318
319 @versions = @project.shared_versions.sort
320 @versions.reject! {|version| version.closed? || version.completed? } unless params[:completed]
321
322 @issues_by_version = {}
323 unless @selected_tracker_ids.empty?
324 @versions.each do |version|
325 conditions = {:tracker_id => @selected_tracker_ids}
326 if !@project.versions.include?(version)
327 conditions.merge!(:project_id => project_ids)
328 end
329 issues = version.fixed_issues.visible.find(:all,
330 :include => [:status, :tracker, :priority],
331 :conditions => conditions,
332 :order => "#{Tracker.table_name}.position, #{Issue.table_name}.id")
333 @issues_by_version[version] = issues
334 end
335 end
336 @versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].empty?}
290 end
337 end
291
338
292 def activity
339 def activity
@@ -22,14 +22,19 class VersionsController < ApplicationController
22 before_filter :authorize
22 before_filter :authorize
23
23
24 helper :custom_fields
24 helper :custom_fields
25 helper :projects
25
26
26 def show
27 def show
27 end
28 end
28
29
29 def edit
30 def edit
30 if request.post? and @version.update_attributes(params[:version])
31 if request.post? && params[:version]
31 flash[:notice] = l(:notice_successful_update)
32 attributes = params[:version].dup
32 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
33 attributes.delete('sharing') unless @version.allowed_sharings.include?(attributes['sharing'])
34 if @version.update_attributes(attributes)
35 flash[:notice] = l(:notice_successful_update)
36 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
37 end
33 end
38 end
34 end
39 end
35
40
@@ -126,6 +126,14 module ApplicationHelper
126 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
126 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
127 end
127 end
128
128
129 def format_version_name(version)
130 if version.project == @project
131 h(version)
132 else
133 h("#{version.project} - #{version}")
134 end
135 end
136
129 def due_date_distance_in_words(date)
137 def due_date_distance_in_words(date)
130 if date
138 if date
131 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
139 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
@@ -91,8 +91,8 module IssuesHelper
91 c = IssueCategory.find_by_id(detail.value) and value = c.name if detail.value
91 c = IssueCategory.find_by_id(detail.value) and value = c.name if detail.value
92 c = IssueCategory.find_by_id(detail.old_value) and old_value = c.name if detail.old_value
92 c = IssueCategory.find_by_id(detail.old_value) and old_value = c.name if detail.old_value
93 when 'fixed_version_id'
93 when 'fixed_version_id'
94 v = Version.find_by_id(detail.value) and value = v.name if detail.value
94 v = Version.find_by_id(detail.value) and value = format_version_name(v) if detail.value
95 v = Version.find_by_id(detail.old_value) and old_value = v.name if detail.old_value
95 v = Version.find_by_id(detail.old_value) and old_value = format_version_name(v) if detail.old_value
96 when 'estimated_hours'
96 when 'estimated_hours'
97 value = "%0.02f" % detail.value.to_f unless detail.value.blank?
97 value = "%0.02f" % detail.value.to_f unless detail.value.blank?
98 old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank?
98 old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank?
@@ -18,7 +18,7
18 module ProjectsHelper
18 module ProjectsHelper
19 def link_to_version(version, options = {})
19 def link_to_version(version, options = {})
20 return '' unless version && version.is_a?(Version)
20 return '' unless version && version.is_a?(Version)
21 link_to h(version.name), { :controller => 'versions', :action => 'show', :id => version }, options
21 link_to_if version.visible?, format_version_name(version), { :controller => 'versions', :action => 'show', :id => version }, options
22 end
22 end
23
23
24 def project_settings_tabs
24 def project_settings_tabs
@@ -69,4 +69,27 module ProjectsHelper
69 end
69 end
70 s
70 s
71 end
71 end
72
73 # Returns a set of options for a select field, grouped by project.
74 def version_options_for_select(versions, selected=nil)
75 grouped = Hash.new {|h,k| h[k] = []}
76 versions.each do |version|
77 grouped[version.project.name] << [h(version.name), version.id]
78 end
79 # Add in the selected
80 if selected && !versions.include?(selected)
81 grouped[selected.project.name] << [h(selected.name), selected.id]
82 end
83
84 if grouped.keys.size > 1
85 grouped_options_for_select(grouped, selected && selected.id)
86 else
87 options_for_select(grouped.values.first, selected && selected.id)
88 end
89 end
90
91 def format_version_sharing(sharing)
92 sharing = 'none' unless Version::VERSION_SHARINGS.include?(sharing)
93 l("label_version_sharing_#{sharing}")
94 end
72 end
95 end
@@ -100,7 +100,10 class Issue < ActiveRecord::Base
100 # reassign to the category with same name if any
100 # reassign to the category with same name if any
101 new_category = issue.category.nil? ? nil : new_project.issue_categories.find_by_name(issue.category.name)
101 new_category = issue.category.nil? ? nil : new_project.issue_categories.find_by_name(issue.category.name)
102 issue.category = new_category
102 issue.category = new_category
103 issue.fixed_version = nil
103 # Keep the fixed_version if it's still valid in the new_project
104 unless new_project.shared_versions.include?(issue.fixed_version)
105 issue.fixed_version = nil
106 end
104 issue.project = new_project
107 issue.project = new_project
105 end
108 end
106 if new_tracker
109 if new_tracker
@@ -242,7 +245,7 class Issue < ActiveRecord::Base
242
245
243 # Versions that the issue can be assigned to
246 # Versions that the issue can be assigned to
244 def assignable_versions
247 def assignable_versions
245 @assignable_versions ||= (project.versions.open + [Version.find_by_id(fixed_version_id_was)]).compact.uniq.sort
248 @assignable_versions ||= (project.shared_versions.open + [Version.find_by_id(fixed_version_id_was)]).compact.uniq.sort
246 end
249 end
247
250
248 # Returns true if this issue is blocked by another issue that is still open
251 # Returns true if this issue is blocked by another issue that is still open
@@ -336,6 +339,23 class Issue < ActiveRecord::Base
336 s << ' assigned-to-me' if User.current.logged? && assigned_to_id == User.current.id
339 s << ' assigned-to-me' if User.current.logged? && assigned_to_id == User.current.id
337 s
340 s
338 end
341 end
342
343 # Update all issues so their versions are not pointing to a
344 # fixed_version that is outside of the issue's project hierarchy.
345 #
346 # OPTIMIZE: does a full table scan of Issues with a fixed_version.
347 def self.update_fixed_versions_from_project_hierarchy_change
348 Issue.all(:conditions => ['fixed_version_id IS NOT NULL'],
349 :include => [:project, :fixed_version]
350 ).each do |issue|
351 next if issue.project.nil? || issue.fixed_version.nil?
352 unless issue.project.shared_versions.include?(issue.fixed_version)
353 issue.init_journal(User.current)
354 issue.fixed_version = nil
355 issue.save
356 end
357 end
358 end
339
359
340 private
360 private
341
361
@@ -219,13 +219,20 class Project < ActiveRecord::Base
219 self.status == STATUS_ACTIVE
219 self.status == STATUS_ACTIVE
220 end
220 end
221
221
222 # Archives the project and its descendants recursively
222 # Archives the project and its descendants
223 def archive
223 def archive
224 # Archive subprojects if any
224 # Check that there is no issue of a non descendant project that is assigned
225 children.each do |subproject|
225 # to one of the project or descendant versions
226 subproject.archive
226 v_ids = self_and_descendants.collect {|p| p.version_ids}.flatten
227 if v_ids.any? && Issue.find(:first, :include => :project,
228 :conditions => ["(#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?)" +
229 " AND #{Issue.table_name}.fixed_version_id IN (?)", lft, rgt, v_ids])
230 return false
227 end
231 end
228 update_attribute :status, STATUS_ARCHIVED
232 Project.transaction do
233 archive!
234 end
235 true
229 end
236 end
230
237
231 # Unarchives the project
238 # Unarchives the project
@@ -297,6 +304,7 class Project < ActiveRecord::Base
297 # move_to_child_of adds the project in last (ie.right) position
304 # move_to_child_of adds the project in last (ie.right) position
298 move_to_child_of(p)
305 move_to_child_of(p)
299 end
306 end
307 Issue.update_fixed_versions_from_project_hierarchy_change
300 true
308 true
301 else
309 else
302 # Can not move to the given target
310 # Can not move to the given target
@@ -324,6 +332,19 class Project < ActiveRecord::Base
324 end
332 end
325 end
333 end
326
334
335 # Returns a scope of the Versions used by the project
336 def shared_versions
337 @shared_versions ||=
338 Version.scoped(:include => :project,
339 :conditions => "#{Project.table_name}.id = #{id}" +
340 " OR (#{Project.table_name}.status = #{Project::STATUS_ACTIVE} AND (" +
341 " #{Version.table_name}.sharing = 'system'" +
342 " OR (#{Project.table_name}.lft >= #{root.lft} AND #{Project.table_name}.rgt <= #{root.rgt} AND #{Version.table_name}.sharing = 'tree')" +
343 " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" +
344 " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" +
345 "))")
346 end
347
327 # Returns a hash of project users grouped by role
348 # Returns a hash of project users grouped by role
328 def users_by_role
349 def users_by_role
329 members.find(:all, :include => [:user, :roles]).inject({}) do |h, m|
350 members.find(:all, :include => [:user, :roles]).inject({}) do |h, m|
@@ -605,4 +626,12 class Project < ActiveRecord::Base
605 self.time_entry_activities.active
626 self.time_entry_activities.active
606 end
627 end
607 end
628 end
629
630 # Archives subprojects recursively
631 def archive!
632 children.each do |subproject|
633 subproject.send :archive!
634 end
635 update_attribute :status, STATUS_ARCHIVED
636 end
608 end
637 end
@@ -200,8 +200,8 class Query < ActiveRecord::Base
200 unless @project.issue_categories.empty?
200 unless @project.issue_categories.empty?
201 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
201 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
202 end
202 end
203 unless @project.versions.empty?
203 unless @project.shared_versions.empty?
204 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } }
204 @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] } }
205 end
205 end
206 unless @project.descendants.active.empty?
206 unless @project.descendants.active.empty?
207 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.descendants.visible.collect{|s| [s.name, s.id.to_s] } }
207 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.descendants.visible.collect{|s| [s.name, s.id.to_s] } }
@@ -17,6 +17,7
17
17
18 class Version < ActiveRecord::Base
18 class Version < ActiveRecord::Base
19 before_destroy :check_integrity
19 before_destroy :check_integrity
20 after_update :update_issue_versions
20 belongs_to :project
21 belongs_to :project
21 has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id'
22 has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id'
22 acts_as_customizable
23 acts_as_customizable
@@ -24,15 +25,24 class Version < ActiveRecord::Base
24 :delete_permission => :manage_files
25 :delete_permission => :manage_files
25
26
26 VERSION_STATUSES = %w(open locked closed)
27 VERSION_STATUSES = %w(open locked closed)
28 VERSION_SHARINGS = %w(none descendants hierarchy tree system)
27
29
28 validates_presence_of :name
30 validates_presence_of :name
29 validates_uniqueness_of :name, :scope => [:project_id]
31 validates_uniqueness_of :name, :scope => [:project_id]
30 validates_length_of :name, :maximum => 60
32 validates_length_of :name, :maximum => 60
31 validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :not_a_date, :allow_nil => true
33 validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :not_a_date, :allow_nil => true
32 validates_inclusion_of :status, :in => VERSION_STATUSES
34 validates_inclusion_of :status, :in => VERSION_STATUSES
35 validates_inclusion_of :sharing, :in => VERSION_SHARINGS
33
36
34 named_scope :open, :conditions => {:status => 'open'}
37 named_scope :open, :conditions => {:status => 'open'}
35
38 named_scope :visible, lambda {|*args| { :include => :project,
39 :conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } }
40
41 # Returns true if +user+ or current user is allowed to view the version
42 def visible?(user=User.current)
43 user.allowed_to?(:view_issues, self.project)
44 end
45
36 def start_date
46 def start_date
37 effective_date
47 effective_date
38 end
48 end
@@ -54,6 +64,10 class Version < ActiveRecord::Base
54 def closed?
64 def closed?
55 status == 'closed'
65 status == 'closed'
56 end
66 end
67
68 def open?
69 status == 'open'
70 end
57
71
58 # Returns true if the version is completed: due date reached and no open issues
72 # Returns true if the version is completed: due date reached and no open issues
59 def completed?
73 def completed?
@@ -120,10 +134,38 class Version < ActiveRecord::Base
120 end
134 end
121 end
135 end
122
136
137 # Returns the sharings that +user+ can set the version to
138 def allowed_sharings(user = User.current)
139 VERSION_SHARINGS.select do |s|
140 if sharing == s
141 true
142 else
143 case s
144 when 'system'
145 # Only admin users can set a systemwide sharing
146 user.admin?
147 when 'hierarchy', 'tree'
148 # Only users allowed to manage versions of the root project can
149 # set sharing to hierarchy or tree
150 project.nil? || user.allowed_to?(:manage_versions, project.root)
151 else
152 true
153 end
154 end
155 end
156 end
157
123 private
158 private
124 def check_integrity
159 def check_integrity
125 raise "Can't delete version" if self.fixed_issues.find(:first)
160 raise "Can't delete version" if self.fixed_issues.find(:first)
126 end
161 end
162
163 # Update the issue's fixed versions. Used if a version's sharing changes.
164 def update_issue_versions
165 if sharing_changed?
166 Issue.update_fixed_versions_from_project_hierarchy_change
167 end
168 end
127
169
128 # Returns the average estimated time of assigned issues
170 # Returns the average estimated time of assigned issues
129 # or 1 if no issue has an estimated time
171 # or 1 if no issue has an estimated time
@@ -19,7 +19,7
19 :tabindex => 199) if authorize_for('projects', 'add_issue_category') %></p>
19 :tabindex => 199) if authorize_for('projects', 'add_issue_category') %></p>
20 <% end %>
20 <% end %>
21 <% unless @issue.assignable_versions.empty? %>
21 <% unless @issue.assignable_versions.empty? %>
22 <p><%= f.select :fixed_version_id, (@issue.assignable_versions.collect {|v| [v.name, v.id]}), :include_blank => true %></p>
22 <p><%= f.select :fixed_version_id, version_options_for_select(@issue.assignable_versions, @issue.fixed_version), :include_blank => true %></p>
23 <% end %>
23 <% end %>
24 </div>
24 </div>
25
25
@@ -31,7 +31,7
31 <label><%= l(:field_fixed_version) %>:
31 <label><%= l(:field_fixed_version) %>:
32 <%= select_tag('fixed_version_id', content_tag('option', l(:label_no_change_option), :value => '') +
32 <%= select_tag('fixed_version_id', content_tag('option', l(:label_no_change_option), :value => '') +
33 content_tag('option', l(:label_none), :value => 'none') +
33 content_tag('option', l(:label_none), :value => 'none') +
34 options_from_collection_for_select(@project.versions.open.sort, :id, :name)) %></label>
34 version_options_for_select(@project.shared_versions.open)) %></label>
35 </p>
35 </p>
36
36
37 <p>
37 <p>
@@ -38,12 +38,12
38 <% end -%>
38 <% end -%>
39 </ul>
39 </ul>
40 </li>
40 </li>
41 <% unless @project.nil? || @project.versions.open.empty? -%>
41 <% unless @project.nil? || @project.shared_versions.open.empty? -%>
42 <li class="folder">
42 <li class="folder">
43 <a href="#" class="submenu"><%= l(:field_fixed_version) %></a>
43 <a href="#" class="submenu"><%= l(:field_fixed_version) %></a>
44 <ul>
44 <ul>
45 <% @project.versions.open.sort.each do |v| -%>
45 <% @project.shared_versions.open.sort.each do |v| -%>
46 <li><%= context_menu_link v.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), 'fixed_version_id' => v, :back_to => @back}, :method => :post,
46 <li><%= context_menu_link format_version_name(v), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), 'fixed_version_id' => v, :back_to => @back}, :method => :post,
47 :selected => (@issue && v == @issue.fixed_version), :disabled => !@can[:update] %></li>
47 :selected => (@issue && v == @issue.fixed_version), :disabled => !@can[:update] %></li>
48 <% end -%>
48 <% end -%>
49 <li><%= context_menu_link l(:label_none), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), 'fixed_version_id' => 'none', :back_to => @back}, :method => :post,
49 <li><%= context_menu_link l(:label_none), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), 'fixed_version_id' => 'none', :back_to => @back}, :method => :post,
@@ -50,8 +50,8
50 <br />
50 <br />
51 <% end %></p>
51 <% end %></p>
52 <% if @project && @project.descendants.active.any? %>
52 <% if @project && @project.descendants.active.any? %>
53 <%= hidden_field_tag 'with_subprojects', 0 %>
53 <p><label><%= check_box_tag 'with_subprojects', 1, @with_subprojects %> <%=l(:label_subproject_plural)%></label></p>
54 <p><label><%= check_box_tag 'with_subprojects', 1, @with_subprojects %> <%=l(:label_subproject_plural)%></label></p>
54 <%= hidden_field_tag 'with_subprojects', 0 %>
55 <% end %>
55 <% end %>
56 <%= hidden_field_tag('user_id', params[:user_id]) unless params[:user_id].blank? %>
56 <%= hidden_field_tag('user_id', params[:user_id]) unless params[:user_id].blank? %>
57 <p><%= submit_tag l(:button_apply), :class => 'button-small', :name => nil %></p>
57 <p><%= submit_tag l(:button_apply), :class => 'button-small', :name => nil %></p>
@@ -6,15 +6,15
6
6
7 <% @versions.each do |version| %>
7 <% @versions.each do |version| %>
8 <%= tag 'a', :name => version.name %>
8 <%= tag 'a', :name => version.name %>
9 <h3 class="icon22 icon22-package"><%= link_to h(version.name), :controller => 'versions', :action => 'show', :id => version %></h3>
9 <h3 class="icon22 icon22-package"><%= link_to_version version %></h3>
10 <% if version.effective_date %>
10 <% if version.effective_date %>
11 <p><%= format_date(version.effective_date) %></p>
11 <p><%= format_date(version.effective_date) %></p>
12 <% end %>
12 <% end %>
13 <p><%=h version.description %></p>
13 <p><%=h version.description %></p>
14 <% issues = version.fixed_issues.find(:all,
14 <% issues = version.fixed_issues.visible.find(:all,
15 :include => [:status, :tracker, :priority],
15 :include => [:status, :tracker, :priority],
16 :conditions => ["#{IssueStatus.table_name}.is_closed=? AND #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')})", true],
16 :conditions => ["#{Issue.table_name}.project_id = ? AND #{IssueStatus.table_name}.is_closed=? AND #{Issue.table_name}.tracker_id in (?)", @project.id, true, @selected_tracker_ids],
17 :order => "#{Tracker.table_name}.position") unless @selected_tracker_ids.empty?
17 :order => "#{Tracker.table_name}.position") unless @selected_tracker_ids.empty?
18 issues ||= []
18 issues ||= []
19 %>
19 %>
20 <% if !issues.empty? %>
20 <% if !issues.empty? %>
@@ -33,11 +33,15
33 <label><%= check_box_tag "tracker_ids[]", tracker.id, (@selected_tracker_ids.include? tracker.id.to_s) %>
33 <label><%= check_box_tag "tracker_ids[]", tracker.id, (@selected_tracker_ids.include? tracker.id.to_s) %>
34 <%= tracker.name %></label><br />
34 <%= tracker.name %></label><br />
35 <% end %>
35 <% end %>
36 <% if @project.descendants.active.any? %>
37 <%= hidden_field_tag 'with_subprojects', 0 %>
38 <br /><label><%= check_box_tag 'with_subprojects', 1, @with_subprojects %> <%=l(:label_subproject_plural)%></label>
39 <% end %>
36 <p><%= submit_tag l(:button_apply), :class => 'button-small' %></p>
40 <p><%= submit_tag l(:button_apply), :class => 'button-small' %></p>
37 <% end %>
41 <% end %>
38
42
39 <h3><%= l(:label_version_plural) %></h3>
43 <h3><%= l(:label_version_plural) %></h3>
40 <% @versions.each do |version| %>
44 <% @versions.each do |version| %>
41 <%= link_to version.name, :anchor => version.name %><br />
45 <%= link_to format_version_name(version), :anchor => version.name %><br />
42 <% end %>
46 <% end %>
43 <% end %>
47 <% end %>
@@ -6,17 +6,11
6 <div id="roadmap">
6 <div id="roadmap">
7 <% @versions.each do |version| %>
7 <% @versions.each do |version| %>
8 <%= tag 'a', :name => version.name %>
8 <%= tag 'a', :name => version.name %>
9 <h3 class="icon22 icon22-package"><%= link_to h(version.name), :controller => 'versions', :action => 'show', :id => version %></h3>
9 <h3 class="icon22 icon22-package"><%= link_to_version version %></h3>
10 <%= render :partial => 'versions/overview', :locals => {:version => version} %>
10 <%= render :partial => 'versions/overview', :locals => {:version => version} %>
11 <%= render(:partial => "wiki/content", :locals => {:content => version.wiki_page.content}) if version.wiki_page %>
11 <%= render(:partial => "wiki/content", :locals => {:content => version.wiki_page.content}) if version.wiki_page %>
12
12
13 <% issues = version.fixed_issues.find(:all,
13 <% if (issues = @issues_by_version[version]) && issues.size > 0 %>
14 :include => [:status, :tracker, :priority],
15 :conditions => ["tracker_id in (#{@selected_tracker_ids.join(',')})"],
16 :order => "#{Tracker.table_name}.position, #{Issue.table_name}.id") unless @selected_tracker_ids.empty?
17 issues ||= []
18 %>
19 <% if issues.size > 0 %>
20 <fieldset class="related-issues"><legend><%= l(:label_related_issues) %></legend>
14 <fieldset class="related-issues"><legend><%= l(:label_related_issues) %></legend>
21 <ul>
15 <ul>
22 <%- issues.each do |issue| -%>
16 <%- issues.each do |issue| -%>
@@ -39,12 +33,16
39 <% end %>
33 <% end %>
40 <br />
34 <br />
41 <label for="completed"><%= check_box_tag "completed", 1, params[:completed] %> <%= l(:label_show_completed_versions) %></label>
35 <label for="completed"><%= check_box_tag "completed", 1, params[:completed] %> <%= l(:label_show_completed_versions) %></label>
36 <% if @project.descendants.active.any? %>
37 <%= hidden_field_tag 'with_subprojects', 0 %>
38 <br /><label><%= check_box_tag 'with_subprojects', 1, @with_subprojects %> <%=l(:label_subproject_plural)%></label>
39 <% end %>
42 <p><%= submit_tag l(:button_apply), :class => 'button-small', :name => nil %></p>
40 <p><%= submit_tag l(:button_apply), :class => 'button-small', :name => nil %></p>
43 <% end %>
41 <% end %>
44
42
45 <h3><%= l(:label_version_plural) %></h3>
43 <h3><%= l(:label_version_plural) %></h3>
46 <% @versions.each do |version| %>
44 <% @versions.each do |version| %>
47 <%= link_to version.name, "##{version.name}" %><br />
45 <%= link_to format_version_name(version), "##{version.name}" %><br />
48 <% end %>
46 <% end %>
49 <% end %>
47 <% end %>
50
48
@@ -5,6 +5,7
5 <th><%= l(:field_effective_date) %></th>
5 <th><%= l(:field_effective_date) %></th>
6 <th><%= l(:field_description) %></th>
6 <th><%= l(:field_description) %></th>
7 <th><%= l(:field_status) %></th>
7 <th><%= l(:field_status) %></th>
8 <th><%= l(:field_sharing) %></th>
8 <th><%= l(:label_wiki_page) unless @project.wiki.nil? %></th>
9 <th><%= l(:label_wiki_page) unless @project.wiki.nil? %></th>
9 <th style="width:15%"></th>
10 <th style="width:15%"></th>
10 </thead>
11 </thead>
@@ -15,6 +16,7
15 <td align="center"><%= format_date(version.effective_date) %></td>
16 <td align="center"><%= format_date(version.effective_date) %></td>
16 <td><%=h version.description %></td>
17 <td><%=h version.description %></td>
17 <td><%= l("version_status_#{version.status}") %></td>
18 <td><%= l("version_status_#{version.status}") %></td>
19 <td><%=h format_version_sharing(version.sharing) %></td>
18 <td><%= link_to(h(version.wiki_page_title), :controller => 'wiki', :page => Wiki.titleize(version.wiki_page_title)) unless version.wiki_page_title.blank? || @project.wiki.nil? %></td>
20 <td><%= link_to(h(version.wiki_page_title), :controller => 'wiki', :page => Wiki.titleize(version.wiki_page_title)) unless version.wiki_page_title.blank? || @project.wiki.nil? %></td>
19 <td class="buttons">
21 <td class="buttons">
20 <%= link_to_if_authorized l(:button_edit), {:controller => 'versions', :action => 'edit', :id => version }, :class => 'icon icon-edit' %>
22 <%= link_to_if_authorized l(:button_edit), {:controller => 'versions', :action => 'edit', :id => version }, :class => 'icon icon-edit' %>
@@ -6,8 +6,10
6 <p><%= f.select :status, Version::VERSION_STATUSES.collect {|s| [l("version_status_#{s}"), s]} %></p>
6 <p><%= f.select :status, Version::VERSION_STATUSES.collect {|s| [l("version_status_#{s}"), s]} %></p>
7 <p><%= f.text_field :wiki_page_title, :label => :label_wiki_page, :size => 60, :disabled => @project.wiki.nil? %></p>
7 <p><%= f.text_field :wiki_page_title, :label => :label_wiki_page, :size => 60, :disabled => @project.wiki.nil? %></p>
8 <p><%= f.text_field :effective_date, :size => 10 %><%= calendar_for('version_effective_date') %></p>
8 <p><%= f.text_field :effective_date, :size => 10 %><%= calendar_for('version_effective_date') %></p>
9 <p><%= f.select :sharing, @version.allowed_sharings.collect {|v| [format_version_sharing(v), v]} %></p>
9
10
10 <% @version.custom_field_values.each do |value| %>
11 <% @version.custom_field_values.each do |value| %>
11 <p><%= custom_field_tag_with_label :version, value %></p>
12 <p><%= custom_field_tag_with_label :version, value %></p>
12 <% end %>
13 <% end %>
14
13 </div>
15 </div>
@@ -156,6 +156,7 en:
156 error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.'
156 error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.'
157 error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
157 error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
158 error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version can not be reopened'
158 error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version can not be reopened'
159 error_can_not_archive_project: This project can not be archived
159
160
160 warning_attachments_not_saved: "{{count}} file(s) could not be saved."
161 warning_attachments_not_saved: "{{count}} file(s) could not be saved."
161
162
@@ -267,6 +268,7 en:
267 field_identity_url: OpenID URL
268 field_identity_url: OpenID URL
268 field_content: Content
269 field_content: Content
269 field_group_by: Group results by
270 field_group_by: Group results by
271 field_sharing: Sharing
270
272
271 setting_app_title: Application title
273 setting_app_title: Application title
272 setting_app_subtitle: Application subtitle
274 setting_app_subtitle: Application subtitle
@@ -709,6 +711,11 en:
709 label_group_plural: Groups
711 label_group_plural: Groups
710 label_group_new: New group
712 label_group_new: New group
711 label_time_entry_plural: Spent time
713 label_time_entry_plural: Spent time
714 label_version_sharing_none: Not shared
715 label_version_sharing_descendants: With subprojects
716 label_version_sharing_hierarchy: With project hierarchy
717 label_version_sharing_tree: With project tree
718 label_version_sharing_system: With all projects
712
719
713 button_login: Login
720 button_login: Login
714 button_submit: Submit
721 button_submit: Submit
@@ -178,6 +178,7 fr:
178 error_scm_annotate: "L'entrée n'existe pas ou ne peut pas être annotée."
178 error_scm_annotate: "L'entrée n'existe pas ou ne peut pas être annotée."
179 error_issue_not_found_in_project: "La demande n'existe pas ou n'appartient pas à ce projet"
179 error_issue_not_found_in_project: "La demande n'existe pas ou n'appartient pas à ce projet"
180 error_can_not_reopen_issue_on_closed_version: 'Une demande assignée à une version fermée ne peut pas être réouverte'
180 error_can_not_reopen_issue_on_closed_version: 'Une demande assignée à une version fermée ne peut pas être réouverte'
181 error_can_not_archive_project: "Ce projet ne peut pas être archivé"
181
182
182 warning_attachments_not_saved: "{{count}} fichier(s) n'ont pas pu être sauvegardés."
183 warning_attachments_not_saved: "{{count}} fichier(s) n'ont pas pu être sauvegardés."
183
184
@@ -289,6 +290,7 fr:
289 field_identity_url: URL OpenID
290 field_identity_url: URL OpenID
290 field_content: Contenu
291 field_content: Contenu
291 field_group_by: Grouper par
292 field_group_by: Grouper par
293 field_sharing: Partage
292
294
293 setting_app_title: Titre de l'application
295 setting_app_title: Titre de l'application
294 setting_app_subtitle: Sous-titre de l'application
296 setting_app_subtitle: Sous-titre de l'application
@@ -725,6 +727,11 fr:
725 label_group: Groupe
727 label_group: Groupe
726 label_group_new: Nouveau groupe
728 label_group_new: Nouveau groupe
727 label_time_entry_plural: Temps passé
729 label_time_entry_plural: Temps passé
730 label_version_sharing_none: Non partagé
731 label_version_sharing_descendants: Avec les sous-projets
732 label_version_sharing_hierarchy: Avec toute la hiérarchie
733 label_version_sharing_tree: Avec tout l'arbre
734 label_version_sharing_system: Avec tous les projets
728
735
729 button_login: Connexion
736 button_login: Connexion
730 button_submit: Soumettre
737 button_submit: Soumettre
@@ -142,7 +142,7 Redmine::MenuManager.map :project_menu do |menu|
142 menu.push :overview, { :controller => 'projects', :action => 'show' }
142 menu.push :overview, { :controller => 'projects', :action => 'show' }
143 menu.push :activity, { :controller => 'projects', :action => 'activity' }
143 menu.push :activity, { :controller => 'projects', :action => 'activity' }
144 menu.push :roadmap, { :controller => 'projects', :action => 'roadmap' },
144 menu.push :roadmap, { :controller => 'projects', :action => 'roadmap' },
145 :if => Proc.new { |p| p.versions.any? }
145 :if => Proc.new { |p| p.shared_versions.any? }
146 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
146 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
147 menu.push :new_issue, { :controller => 'issues', :action => 'new' }, :param => :project_id, :caption => :label_issue_new,
147 menu.push :new_issue, { :controller => 'issues', :action => 'new' }, :param => :project_id, :caption => :label_issue_new,
148 :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) }
148 :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) }
@@ -1,10 +1,11
1 class Version < ActiveRecord::Base
1 class Version < ActiveRecord::Base
2 generator_for :name, :method => :next_name
2 generator_for :name, :method => :next_name
3
3 generator_for :status => 'open'
4 def self.next_name
4
5 @last_name ||= 'Version 1.0.0'
5 def self.next_name
6 @last_name.succ!
6 @last_name ||= 'Version 1.0.0'
7 @last_name
7 @last_name.succ!
8 end
8 @last_name
9
9 end
10 end
10
11 end
@@ -133,4 +133,15 attachments_011:
133 filename: picture.jpg
133 filename: picture.jpg
134 author_id: 2
134 author_id: 2
135 content_type: image/jpeg
135 content_type: image/jpeg
136 No newline at end of file
136 attachments_012:
137 created_on: 2006-07-19 21:07:27 +02:00
138 container_type: Version
139 container_id: 1
140 downloads: 0
141 disk_filename: 060719210727_version_file.zip
142 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
143 id: 12
144 filesize: 452
145 filename: version_file.zip
146 author_id: 2
147 content_type: application/octet-stream
@@ -189,3 +189,17 issues_012:
189 status_id: 5
189 status_id: 5
190 start_date: <%= 1.day.ago.to_date.to_s(:db) %>
190 start_date: <%= 1.day.ago.to_date.to_s(:db) %>
191 due_date:
191 due_date:
192 issues_013:
193 created_on: <%= 5.days.ago.to_date.to_s(:db) %>
194 project_id: 3
195 updated_on: <%= 2.days.ago.to_date.to_s(:db) %>
196 priority_id: 4
197 subject: Subproject issue two
198 id: 13
199 fixed_version_id:
200 category_id:
201 description: This is a second issue on a cookbook subproject
202 tracker_id: 1
203 assigned_to_id:
204 author_id: 2
205 status_id: 1
@@ -13,3 +13,10 journal_details_002:
13 value: "30"
13 value: "30"
14 prop_key: done_ratio
14 prop_key: done_ratio
15 journal_id: 1
15 journal_id: 1
16 journal_details_003:
17 old_value: nil
18 property: attr
19 id: 3
20 value: "6"
21 prop_key: fixed_version_id
22 journal_id: 4
@@ -20,4 +20,10 journals_003:
20 journalized_type: Issue
20 journalized_type: Issue
21 user_id: 2
21 user_id: 2
22 journalized_id: 2
22 journalized_id: 2
23 No newline at end of file
23 journals_004:
24 created_on: <%= 1.days.ago.to_date.to_s(:db) %>
25 notes: "A comment with a private version."
26 id: 4
27 journalized_type: Issue
28 user_id: 1
29 journalized_id: 6
@@ -42,4 +42,9 members_007:
42 project_id: 5
42 project_id: 5
43 user_id: 8
43 user_id: 8
44 mail_notification: false
44 mail_notification: false
45 No newline at end of file
45 members_008:
46 created_on: 2006-07-19 19:35:33 +02:00
47 project_id: 5
48 id: 8
49 user_id: 1
50 mail_notification: true
@@ -8,6 +8,7 versions_001:
8 description: Beta
8 description: Beta
9 effective_date: 2006-07-01
9 effective_date: 2006-07-01
10 status: closed
10 status: closed
11 sharing: 'none'
11 versions_002:
12 versions_002:
12 created_on: 2006-07-19 21:00:33 +02:00
13 created_on: 2006-07-19 21:00:33 +02:00
13 name: "1.0"
14 name: "1.0"
@@ -17,6 +18,7 versions_002:
17 description: Stable release
18 description: Stable release
18 effective_date: <%= 20.day.from_now.to_date.to_s(:db) %>
19 effective_date: <%= 20.day.from_now.to_date.to_s(:db) %>
19 status: locked
20 status: locked
21 sharing: 'none'
20 versions_003:
22 versions_003:
21 created_on: 2006-07-19 21:00:33 +02:00
23 created_on: 2006-07-19 21:00:33 +02:00
22 name: "2.0"
24 name: "2.0"
@@ -26,4 +28,44 versions_003:
26 description: Future version
28 description: Future version
27 effective_date:
29 effective_date:
28 status: open
30 status: open
29 No newline at end of file
31 sharing: 'none'
32 versions_004:
33 created_on: 2006-07-19 21:00:33 +02:00
34 name: "2.0"
35 project_id: 3
36 updated_on: 2006-07-19 21:00:33 +02:00
37 id: 4
38 description: Future version on subproject
39 effective_date:
40 status: open
41 sharing: 'tree'
42 versions_005:
43 created_on: 2006-07-19 21:00:07 +02:00
44 name: "Alpha"
45 project_id: 2
46 updated_on: 2006-07-19 21:00:07 +02:00
47 id: 5
48 description: Private Alpha
49 effective_date: 2006-07-01
50 status: open
51 sharing: 'none'
52 versions_006:
53 created_on: 2006-07-19 21:00:07 +02:00
54 name: "Private Version of public subproject"
55 project_id: 5
56 updated_on: 2006-07-19 21:00:07 +02:00
57 id: 6
58 description: "Should be done any day now..."
59 effective_date:
60 status: open
61 sharing: 'tree'
62 versions_007:
63 created_on: 2006-07-19 21:00:07 +02:00
64 name: "Systemwide visible version"
65 project_id: 2
66 updated_on: 2006-07-19 21:00:07 +02:00
67 id: 7
68 description:
69 effective_date:
70 status: open
71 sharing: 'system'
@@ -925,6 +925,22 class IssuesControllerTest < ActionController::TestCase
925 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
925 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
926 end
926 end
927
927
928 def test_post_edit_should_allow_fixed_version_to_be_set_to_a_subproject
929 issue = Issue.find(2)
930 @request.session[:user_id] = 2
931
932 post :edit,
933 :id => issue.id,
934 :issue => {
935 :fixed_version_id => 4
936 }
937
938 assert_response :redirect
939 issue.reload
940 assert_equal 4, issue.fixed_version_id
941 assert_not_equal issue.project_id, issue.fixed_version.project_id
942 end
943
928 def test_get_bulk_edit
944 def test_get_bulk_edit
929 @request.session[:user_id] = 2
945 @request.session[:user_id] = 2
930 get :bulk_edit, :ids => [1, 2]
946 get :bulk_edit, :ids => [1, 2]
@@ -1005,6 +1021,21 class IssuesControllerTest < ActionController::TestCase
1005 assert_nil Issue.find(2).assigned_to
1021 assert_nil Issue.find(2).assigned_to
1006 end
1022 end
1007
1023
1024 def test_post_bulk_edit_should_allow_fixed_version_to_be_set_to_a_subproject
1025 @request.session[:user_id] = 2
1026
1027 post :bulk_edit,
1028 :ids => [1,2],
1029 :fixed_version_id => 4
1030
1031 assert_response :redirect
1032 issues = Issue.find([1,2])
1033 issues.each do |issue|
1034 assert_equal 4, issue.fixed_version_id
1035 assert_not_equal issue.project_id, issue.fixed_version.project_id
1036 end
1037 end
1038
1008 def test_move_routing
1039 def test_move_routing
1009 assert_routing(
1040 assert_routing(
1010 {:method => :get, :path => '/issues/1/move'},
1041 {:method => :get, :path => '/issues/1/move'},
@@ -1101,6 +1132,14 class IssuesControllerTest < ActionController::TestCase
1101 assert_tag :tag => 'a', :content => 'Immediate',
1132 assert_tag :tag => 'a', :content => 'Immediate',
1102 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;priority_id=8',
1133 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;priority_id=8',
1103 :class => '' }
1134 :class => '' }
1135 # Versions
1136 assert_tag :tag => 'a', :content => '2.0',
1137 :attributes => { :href => '/issues/bulk_edit?fixed_version_id=3&amp;ids%5B%5D=1',
1138 :class => '' }
1139 assert_tag :tag => 'a', :content => 'eCookbook Subproject 1 - 2.0',
1140 :attributes => { :href => '/issues/bulk_edit?fixed_version_id=4&amp;ids%5B%5D=1',
1141 :class => '' }
1142
1104 assert_tag :tag => 'a', :content => 'Dave Lopper',
1143 assert_tag :tag => 'a', :content => 'Dave Lopper',
1105 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1',
1144 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1',
1106 :class => '' }
1145 :class => '' }
@@ -364,6 +364,15 class ProjectsControllerTest < ActionController::TestCase
364 assert_not_nil assigns(:versions)
364 assert_not_nil assigns(:versions)
365 end
365 end
366
366
367 def test_changelog_showing_subprojects_versions
368 get :changelog, :id => 1, :with_subprojects => 1
369 assert_response :success
370 assert_template 'changelog'
371 assert_not_nil assigns(:versions)
372 # Version on subproject appears
373 assert assigns(:versions).include?(Version.find(4))
374 end
375
367 def test_roadmap_routing
376 def test_roadmap_routing
368 assert_routing(
377 assert_routing(
369 {:method => :get, :path => 'projects/33/roadmap'},
378 {:method => :get, :path => 'projects/33/roadmap'},
@@ -392,6 +401,15 class ProjectsControllerTest < ActionController::TestCase
392 # Completed version appears
401 # Completed version appears
393 assert assigns(:versions).include?(Version.find(1))
402 assert assigns(:versions).include?(Version.find(1))
394 end
403 end
404
405 def test_roadmap_showing_subprojects_versions
406 get :roadmap, :id => 1, :with_subprojects => 1
407 assert_response :success
408 assert_template 'roadmap'
409 assert_not_nil assigns(:versions)
410 # Version on subproject appears
411 assert assigns(:versions).include?(Version.find(4))
412 end
395
413
396 def test_project_activity_routing
414 def test_project_activity_routing
397 assert_routing(
415 assert_routing(
@@ -24,7 +24,7 rescue
24 end
24 end
25
25
26 class AccountTest < ActionController::IntegrationTest
26 class AccountTest < ActionController::IntegrationTest
27 fixtures :users
27 fixtures :users, :roles
28
28
29 # Replace this with your real tests.
29 # Replace this with your real tests.
30 def test_login
30 def test_login
@@ -49,6 +49,7 class ActiveSupport::TestCase
49 # Add more helper methods to be used by all tests here...
49 # Add more helper methods to be used by all tests here...
50
50
51 def log_user(login, password)
51 def log_user(login, password)
52 User.anonymous
52 get "/login"
53 get "/login"
53 assert_equal nil, session[:user_id]
54 assert_equal nil, session[:user_id]
54 assert_response :success
55 assert_response :success
@@ -25,7 +25,7 class EnumerationTest < ActiveSupport::TestCase
25
25
26 def test_objects_count
26 def test_objects_count
27 # low priority
27 # low priority
28 assert_equal 5, Enumeration.find(4).objects_count
28 assert_equal 6, Enumeration.find(4).objects_count
29 # urgent
29 # urgent
30 assert_equal 0, Enumeration.find(7).objects_count
30 assert_equal 0, Enumeration.find(7).objects_count
31 end
31 end
@@ -79,7 +79,7 class EnumerationTest < ActiveSupport::TestCase
79 def test_destroy_with_reassign
79 def test_destroy_with_reassign
80 Enumeration.find(4).destroy(Enumeration.find(6))
80 Enumeration.find(4).destroy(Enumeration.find(6))
81 assert_nil Issue.find(:first, :conditions => {:priority_id => 4})
81 assert_nil Issue.find(:first, :conditions => {:priority_id => 4})
82 assert_equal 5, Enumeration.find(6).objects_count
82 assert_equal 6, Enumeration.find(6).objects_count
83 end
83 end
84
84
85 def test_should_be_customizable
85 def test_should_be_customizable
@@ -26,7 +26,7 class IssuePriorityTest < ActiveSupport::TestCase
26
26
27 def test_objects_count
27 def test_objects_count
28 # low priority
28 # low priority
29 assert_equal 5, IssuePriority.find(4).objects_count
29 assert_equal 6, IssuePriority.find(4).objects_count
30 # urgent
30 # urgent
31 assert_equal 0, IssuePriority.find(7).objects_count
31 assert_equal 0, IssuePriority.find(7).objects_count
32 end
32 end
@@ -329,6 +329,46 class IssueTest < ActiveSupport::TestCase
329 assert_nil issue.category_id
329 assert_nil issue.category_id
330 end
330 end
331
331
332 def test_move_to_another_project_should_clear_fixed_version_when_not_shared
333 issue = Issue.find(1)
334 issue.update_attribute(:fixed_version_id, 1)
335 assert issue.move_to(Project.find(2))
336 issue.reload
337 assert_equal 2, issue.project_id
338 # Cleared fixed_version
339 assert_equal nil, issue.fixed_version
340 end
341
342 def test_move_to_another_project_should_keep_fixed_version_when_shared_with_the_target_project
343 issue = Issue.find(1)
344 issue.update_attribute(:fixed_version_id, 4)
345 assert issue.move_to(Project.find(5))
346 issue.reload
347 assert_equal 5, issue.project_id
348 # Keep fixed_version
349 assert_equal 4, issue.fixed_version_id
350 end
351
352 def test_move_to_another_project_should_clear_fixed_version_when_not_shared_with_the_target_project
353 issue = Issue.find(1)
354 issue.update_attribute(:fixed_version_id, 1)
355 assert issue.move_to(Project.find(5))
356 issue.reload
357 assert_equal 5, issue.project_id
358 # Cleared fixed_version
359 assert_equal nil, issue.fixed_version
360 end
361
362 def test_move_to_another_project_should_keep_fixed_version_when_shared_systemwide
363 issue = Issue.find(1)
364 issue.update_attribute(:fixed_version_id, 7)
365 assert issue.move_to(Project.find(2))
366 issue.reload
367 assert_equal 2, issue.project_id
368 # Keep fixed_version
369 assert_equal 7, issue.fixed_version_id
370 end
371
332 def test_copy_to_the_same_project
372 def test_copy_to_the_same_project
333 issue = Issue.find(1)
373 issue = Issue.find(1)
334 copy = nil
374 copy = nil
@@ -18,10 +18,7
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class ProjectTest < ActiveSupport::TestCase
20 class ProjectTest < ActiveSupport::TestCase
21 fixtures :projects, :enabled_modules,
21 fixtures :all
22 :issues, :issue_statuses, :journals, :journal_details,
23 :users, :members, :member_roles, :roles, :projects_trackers, :trackers, :boards,
24 :queries
25
22
26 def setup
23 def setup
27 @ecookbook = Project.find(1)
24 @ecookbook = Project.find(1)
@@ -111,6 +108,17 class ProjectTest < ActiveSupport::TestCase
111 assert @ecookbook.descendants.active.empty?
108 assert @ecookbook.descendants.active.empty?
112 end
109 end
113
110
111 def test_archive_should_fail_if_versions_are_used_by_non_descendant_projects
112 # Assign an issue of a project to a version of a child project
113 Issue.find(4).update_attribute :fixed_version_id, 4
114
115 assert_no_difference "Project.count(:all, :conditions => 'status = #{Project::STATUS_ARCHIVED}')" do
116 assert_equal false, @ecookbook.archive
117 end
118 @ecookbook.reload
119 assert @ecookbook.active?
120 end
121
114 def test_unarchive
122 def test_unarchive
115 user = @ecookbook.members.first.user
123 user = @ecookbook.members.first.user
116 @ecookbook.archive
124 @ecookbook.archive
@@ -206,6 +214,38 class ProjectTest < ActiveSupport::TestCase
206 assert_equal 4, parent.children.size
214 assert_equal 4, parent.children.size
207 assert_equal parent.children.sort_by(&:name), parent.children
215 assert_equal parent.children.sort_by(&:name), parent.children
208 end
216 end
217
218
219 def test_set_parent_should_update_issue_fixed_version_associations_when_a_fixed_version_is_moved_out_of_the_hierarchy
220 # Parent issue with a hierarchy project's fixed version
221 parent_issue = Issue.find(1)
222 parent_issue.update_attribute(:fixed_version_id, 4)
223 parent_issue.reload
224 assert_equal 4, parent_issue.fixed_version_id
225
226 # Should keep fixed versions for the issues
227 issue_with_local_fixed_version = Issue.find(5)
228 issue_with_local_fixed_version.update_attribute(:fixed_version_id, 4)
229 issue_with_local_fixed_version.reload
230 assert_equal 4, issue_with_local_fixed_version.fixed_version_id
231
232 # Local issue with hierarchy fixed_version
233 issue_with_hierarchy_fixed_version = Issue.find(13)
234 issue_with_hierarchy_fixed_version.update_attribute(:fixed_version_id, 6)
235 issue_with_hierarchy_fixed_version.reload
236 assert_equal 6, issue_with_hierarchy_fixed_version.fixed_version_id
237
238 # Move project out of the issue's hierarchy
239 moved_project = Project.find(3)
240 moved_project.set_parent!(Project.find(2))
241 parent_issue.reload
242 issue_with_local_fixed_version.reload
243 issue_with_hierarchy_fixed_version.reload
244
245 assert_equal 4, issue_with_local_fixed_version.fixed_version_id, "Fixed version was not keep on an issue local to the moved project"
246 assert_equal nil, issue_with_hierarchy_fixed_version.fixed_version_id, "Fixed version is still set after moving the Project out of the hierarchy where the version is defined in"
247 assert_equal nil, parent_issue.fixed_version_id, "Fixed version is still set after moving the Version out of the hierarchy for the issue."
248 end
209
249
210 def test_parent
250 def test_parent
211 p = Project.find(6).parent
251 p = Project.find(6).parent
@@ -277,13 +317,61 class ProjectTest < ActiveSupport::TestCase
277
317
278 assert_equal [1,2], parent.rolled_up_trackers.collect(&:id)
318 assert_equal [1,2], parent.rolled_up_trackers.collect(&:id)
279 end
319 end
320
321 def test_shared_versions
322 parent = Project.find(1)
323 child = parent.children.find(3)
324 private_child = parent.children.find(5)
325
326 assert_equal [1,2,3], parent.version_ids.sort
327 assert_equal [4], child.version_ids
328 assert_equal [6], private_child.version_ids
329 assert_equal [7], Version.find_all_by_sharing('system').collect(&:id)
330
331 assert_equal 6, parent.shared_versions.size
332 parent.shared_versions.each do |version|
333 assert_kind_of Version, version
334 end
335
336 assert_equal [1,2,3,4,6,7], parent.shared_versions.collect(&:id).sort
337 end
338
339 def test_shared_versions_should_ignore_archived_subprojects
340 parent = Project.find(1)
341 child = parent.children.find(3)
342 child.archive
343 parent.reload
344
345 assert_equal [1,2,3], parent.version_ids.sort
346 assert_equal [4], child.version_ids
347 assert !parent.shared_versions.collect(&:id).include?(4)
348 end
349
350 def test_shared_versions_visible_to_user
351 user = User.find(3)
352 parent = Project.find(1)
353 child = parent.children.find(5)
354
355 assert_equal [1,2,3], parent.version_ids.sort
356 assert_equal [6], child.version_ids
357
358 versions = parent.shared_versions.visible(user)
359
360 assert_equal 4, versions.size
361 versions.each do |version|
362 assert_kind_of Version, version
363 end
364
365 assert !versions.collect(&:id).include?(6)
366 end
367
280
368
281 def test_next_identifier
369 def test_next_identifier
282 ProjectCustomField.delete_all
370 ProjectCustomField.delete_all
283 Project.create!(:name => 'last', :identifier => 'p2008040')
371 Project.create!(:name => 'last', :identifier => 'p2008040')
284 assert_equal 'p2008041', Project.next_identifier
372 assert_equal 'p2008041', Project.next_identifier
285 end
373 end
286
374
287 def test_next_identifier_first_project
375 def test_next_identifier_first_project
288 Project.delete_all
376 Project.delete_all
289 assert_nil Project.next_identifier
377 assert_nil Project.next_identifier
@@ -429,13 +517,15 class ProjectTest < ActiveSupport::TestCase
429 end
517 end
430
518
431 should "change the new issues to use the copied version" do
519 should "change the new issues to use the copied version" do
432 assigned_version = Version.generate!(:name => "Assigned Issues")
520 User.current = User.find(1)
521 assigned_version = Version.generate!(:name => "Assigned Issues", :status => 'open')
433 @source_project.versions << assigned_version
522 @source_project.versions << assigned_version
434 assert_equal 1, @source_project.versions.size
523 assert_equal 3, @source_project.versions.size
435 @source_project.issues << Issue.generate!(:fixed_version_id => assigned_version.id,
524 Issue.generate_for_project!(@source_project,
436 :subject => "change the new issues to use the copied version",
525 :fixed_version_id => assigned_version.id,
437 :tracker_id => 1,
526 :subject => "change the new issues to use the copied version",
438 :project_id => @source_project.id)
527 :tracker_id => 1,
528 :project_id => @source_project.id)
439
529
440 assert @project.copy(@source_project)
530 assert @project.copy(@source_project)
441 @project.reload
531 @project.reload
@@ -31,6 +31,14 class QueryTest < ActiveSupport::TestCase
31 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
31 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
32 :conditions => query.statement
32 :conditions => query.statement
33 end
33 end
34
35 def test_query_should_allow_shared_versions_for_a_project_query
36 subproject_version = Version.find(4)
37 query = Query.new(:project => Project.find(1), :name => '_')
38 query.add_filter('fixed_version_id', '=', [subproject_version.id.to_s])
39
40 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IN ('4')")
41 end
34
42
35 def test_query_with_multiple_custom_fields
43 def test_query_with_multiple_custom_fields
36 query = Query.find(1)
44 query = Query.find(1)
@@ -104,6 +104,41 class VersionTest < ActiveSupport::TestCase
104 assert_progress_equal (25.0*0.2 + 25.0*1 + 10.0*0.3 + 40.0*0.1)/100.0*100, v.completed_pourcent
104 assert_progress_equal (25.0*0.2 + 25.0*1 + 10.0*0.3 + 40.0*0.1)/100.0*100, v.completed_pourcent
105 assert_progress_equal 25.0/100.0*100, v.closed_pourcent
105 assert_progress_equal 25.0/100.0*100, v.closed_pourcent
106 end
106 end
107
108 test "should update all issue's fixed_version associations in case the hierarchy changed XXX" do
109 User.current = User.find(1) # Need the admin's permissions
110
111 @version = Version.find(7)
112 # Separate hierarchy
113 project_1_issue = Issue.find(1)
114 project_1_issue.fixed_version = @version
115 assert project_1_issue.save, project_1_issue.errors.full_messages
116
117 project_5_issue = Issue.find(6)
118 project_5_issue.fixed_version = @version
119 assert project_5_issue.save
120
121 # Project
122 project_2_issue = Issue.find(4)
123 project_2_issue.fixed_version = @version
124 assert project_2_issue.save
125
126 # Update the sharing
127 @version.sharing = 'none'
128 assert @version.save
129
130 # Project 1 now out of the shared scope
131 project_1_issue.reload
132 assert_equal nil, project_1_issue.fixed_version, "Fixed version is still set after changing the Version's sharing"
133
134 # Project 5 now out of the shared scope
135 project_5_issue.reload
136 assert_equal nil, project_5_issue.fixed_version, "Fixed version is still set after changing the Version's sharing"
137
138 # Project 2 issue remains
139 project_2_issue.reload
140 assert_equal @version, project_2_issue.fixed_version
141 end
107
142
108 private
143 private
109
144
General Comments 0
You need to be logged in to leave comments. Login now