##// END OF EJS Templates
Adds a link to automatically close completed versions in project settings (#1245)....
Jean-Philippe Lang -
r2909:8f40750ad7f7
parent child
Show More
@@ -1,54 +1,69
1 1 # redMine - project management software
2 2 # Copyright (C) 2006 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class VersionsController < ApplicationController
19 19 menu_item :roadmap
20 before_filter :find_project, :authorize
20 before_filter :find_version, :except => :close_completed
21 before_filter :find_project, :only => :close_completed
22 before_filter :authorize
21 23
22 24 def show
23 25 end
24 26
25 27 def edit
26 28 if request.post? and @version.update_attributes(params[:version])
27 29 flash[:notice] = l(:notice_successful_update)
28 30 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
29 31 end
30 32 end
33
34 def close_completed
35 if request.post?
36 @project.close_completed_versions
37 end
38 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
39 end
31 40
32 41 def destroy
33 42 @version.destroy
34 43 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
35 44 rescue
36 45 flash[:error] = l(:notice_unable_delete_version)
37 46 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
38 47 end
39 48
40 49 def status_by
41 50 respond_to do |format|
42 51 format.html { render :action => 'show' }
43 52 format.js { render(:update) {|page| page.replace_html 'status_by', render_issue_status_by(@version, params[:status_by])} }
44 53 end
45 54 end
46 55
47 56 private
48 def find_project
57 def find_version
49 58 @version = Version.find(params[:id])
50 59 @project = @version.project
51 60 rescue ActiveRecord::RecordNotFound
52 61 render_404
53 end
62 end
63
64 def find_project
65 @project = Project.find(params[:project_id])
66 rescue ActiveRecord::RecordNotFound
67 render_404
68 end
54 69 end
@@ -1,583 +1,594
1 1 # redMine - project management software
2 2 # Copyright (C) 2006 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Project < ActiveRecord::Base
19 19 # Project statuses
20 20 STATUS_ACTIVE = 1
21 21 STATUS_ARCHIVED = 9
22 22
23 23 # Specific overidden Activities
24 24 has_many :time_entry_activities do
25 25 def active
26 26 find(:all, :conditions => {:active => true})
27 27 end
28 28 end
29 29 has_many :members, :include => :user, :conditions => "#{User.table_name}.type='User' AND #{User.table_name}.status=#{User::STATUS_ACTIVE}"
30 30 has_many :member_principals, :class_name => 'Member',
31 31 :include => :principal,
32 32 :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{User::STATUS_ACTIVE})"
33 33 has_many :users, :through => :members
34 34 has_many :principals, :through => :member_principals, :source => :principal
35 35
36 36 has_many :enabled_modules, :dependent => :delete_all
37 37 has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
38 38 has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
39 39 has_many :issue_changes, :through => :issues, :source => :journals
40 40 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
41 41 has_many :time_entries, :dependent => :delete_all
42 42 has_many :queries, :dependent => :delete_all
43 43 has_many :documents, :dependent => :destroy
44 44 has_many :news, :dependent => :delete_all, :include => :author
45 45 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
46 46 has_many :boards, :dependent => :destroy, :order => "position ASC"
47 47 has_one :repository, :dependent => :destroy
48 48 has_many :changesets, :through => :repository
49 49 has_one :wiki, :dependent => :destroy
50 50 # Custom field for the project issues
51 51 has_and_belongs_to_many :issue_custom_fields,
52 52 :class_name => 'IssueCustomField',
53 53 :order => "#{CustomField.table_name}.position",
54 54 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
55 55 :association_foreign_key => 'custom_field_id'
56 56
57 57 acts_as_nested_set :order => 'name', :dependent => :destroy
58 58 acts_as_attachable :view_permission => :view_files,
59 59 :delete_permission => :manage_files
60 60
61 61 acts_as_customizable
62 62 acts_as_searchable :columns => ['name', 'description'], :project_key => 'id', :permission => nil
63 63 acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
64 64 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o.id}},
65 65 :author => nil
66 66
67 67 attr_protected :status, :enabled_module_names
68 68
69 69 validates_presence_of :name, :identifier
70 70 validates_uniqueness_of :name, :identifier
71 71 validates_associated :repository, :wiki
72 72 validates_length_of :name, :maximum => 30
73 73 validates_length_of :homepage, :maximum => 255
74 74 validates_length_of :identifier, :in => 1..20
75 75 # donwcase letters, digits, dashes but not digits only
76 76 validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-]*$/, :if => Proc.new { |p| p.identifier_changed? }
77 77 # reserved words
78 78 validates_exclusion_of :identifier, :in => %w( new )
79 79
80 80 before_destroy :delete_all_members
81 81
82 82 named_scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } }
83 83 named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
84 84 named_scope :all_public, { :conditions => { :is_public => true } }
85 85 named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } }
86 86
87 87 def identifier=(identifier)
88 88 super unless identifier_frozen?
89 89 end
90 90
91 91 def identifier_frozen?
92 92 errors[:identifier].nil? && !(new_record? || identifier.blank?)
93 93 end
94 94
95 95 def issues_with_subprojects(include_subprojects=false)
96 96 conditions = nil
97 97 if include_subprojects
98 98 ids = [id] + descendants.collect(&:id)
99 99 conditions = ["#{Project.table_name}.id IN (#{ids.join(',')}) AND #{Project.visible_by}"]
100 100 end
101 101 conditions ||= ["#{Project.table_name}.id = ?", id]
102 102 # Quick and dirty fix for Rails 2 compatibility
103 103 Issue.send(:with_scope, :find => { :conditions => conditions }) do
104 104 Version.send(:with_scope, :find => { :conditions => conditions }) do
105 105 yield
106 106 end
107 107 end
108 108 end
109 109
110 110 # returns latest created projects
111 111 # non public projects will be returned only if user is a member of those
112 112 def self.latest(user=nil, count=5)
113 113 find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
114 114 end
115 115
116 116 # Returns a SQL :conditions string used to find all active projects for the specified user.
117 117 #
118 118 # Examples:
119 119 # Projects.visible_by(admin) => "projects.status = 1"
120 120 # Projects.visible_by(normal_user) => "projects.status = 1 AND projects.is_public = 1"
121 121 def self.visible_by(user=nil)
122 122 user ||= User.current
123 123 if user && user.admin?
124 124 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
125 125 elsif user && user.memberships.any?
126 126 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND (#{Project.table_name}.is_public = #{connection.quoted_true} or #{Project.table_name}.id IN (#{user.memberships.collect{|m| m.project_id}.join(',')}))"
127 127 else
128 128 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}"
129 129 end
130 130 end
131 131
132 132 def self.allowed_to_condition(user, permission, options={})
133 133 statements = []
134 134 base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
135 135 if perm = Redmine::AccessControl.permission(permission)
136 136 unless perm.project_module.nil?
137 137 # If the permission belongs to a project module, make sure the module is enabled
138 138 base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')"
139 139 end
140 140 end
141 141 if options[:project]
142 142 project_statement = "#{Project.table_name}.id = #{options[:project].id}"
143 143 project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects]
144 144 base_statement = "(#{project_statement}) AND (#{base_statement})"
145 145 end
146 146 if user.admin?
147 147 # no restriction
148 148 else
149 149 statements << "1=0"
150 150 if user.logged?
151 151 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}" if Role.non_member.allowed_to?(permission)
152 152 allowed_project_ids = user.memberships.select {|m| m.roles.detect {|role| role.allowed_to?(permission)}}.collect {|m| m.project_id}
153 153 statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any?
154 154 elsif Role.anonymous.allowed_to?(permission)
155 155 # anonymous user allowed on public project
156 156 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
157 157 else
158 158 # anonymous user is not authorized
159 159 end
160 160 end
161 161 statements.empty? ? base_statement : "((#{base_statement}) AND (#{statements.join(' OR ')}))"
162 162 end
163 163
164 164 # Returns the Systemwide and project specific activities
165 165 def activities(include_inactive=false)
166 166 if include_inactive
167 167 return all_activities
168 168 else
169 169 return active_activities
170 170 end
171 171 end
172 172
173 173 # Will create a new Project specific Activity or update an existing one
174 174 #
175 175 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
176 176 # does not successfully save.
177 177 def update_or_create_time_entry_activity(id, activity_hash)
178 178 if activity_hash.respond_to?(:has_key?) && activity_hash.has_key?('parent_id')
179 179 self.create_time_entry_activity_if_needed(activity_hash)
180 180 else
181 181 activity = project.time_entry_activities.find_by_id(id.to_i)
182 182 activity.update_attributes(activity_hash) if activity
183 183 end
184 184 end
185 185
186 186 # Create a new TimeEntryActivity if it overrides a system TimeEntryActivity
187 187 #
188 188 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
189 189 # does not successfully save.
190 190 def create_time_entry_activity_if_needed(activity)
191 191 if activity['parent_id']
192 192
193 193 parent_activity = TimeEntryActivity.find(activity['parent_id'])
194 194 activity['name'] = parent_activity.name
195 195 activity['position'] = parent_activity.position
196 196
197 197 if Enumeration.overridding_change?(activity, parent_activity)
198 198 project_activity = self.time_entry_activities.create(activity)
199 199
200 200 if project_activity.new_record?
201 201 raise ActiveRecord::Rollback, "Overridding TimeEntryActivity was not successfully saved"
202 202 else
203 203 self.time_entries.update_all("activity_id = #{project_activity.id}", ["activity_id = ?", parent_activity.id])
204 204 end
205 205 end
206 206 end
207 207 end
208 208
209 209 # Returns a :conditions SQL string that can be used to find the issues associated with this project.
210 210 #
211 211 # Examples:
212 212 # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))"
213 213 # project.project_condition(false) => "projects.id = 1"
214 214 def project_condition(with_subprojects)
215 215 cond = "#{Project.table_name}.id = #{id}"
216 216 cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
217 217 cond
218 218 end
219 219
220 220 def self.find(*args)
221 221 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
222 222 project = find_by_identifier(*args)
223 223 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
224 224 project
225 225 else
226 226 super
227 227 end
228 228 end
229 229
230 230 def to_param
231 231 # id is used for projects with a numeric identifier (compatibility)
232 232 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id : identifier)
233 233 end
234 234
235 235 def active?
236 236 self.status == STATUS_ACTIVE
237 237 end
238 238
239 239 # Archives the project and its descendants recursively
240 240 def archive
241 241 # Archive subprojects if any
242 242 children.each do |subproject|
243 243 subproject.archive
244 244 end
245 245 update_attribute :status, STATUS_ARCHIVED
246 246 end
247 247
248 248 # Unarchives the project
249 249 # All its ancestors must be active
250 250 def unarchive
251 251 return false if ancestors.detect {|a| !a.active?}
252 252 update_attribute :status, STATUS_ACTIVE
253 253 end
254 254
255 255 # Returns an array of projects the project can be moved to
256 256 def possible_parents
257 257 @possible_parents ||= (Project.active.find(:all) - self_and_descendants)
258 258 end
259 259
260 260 # Sets the parent of the project
261 261 # Argument can be either a Project, a String, a Fixnum or nil
262 262 def set_parent!(p)
263 263 unless p.nil? || p.is_a?(Project)
264 264 if p.to_s.blank?
265 265 p = nil
266 266 else
267 267 p = Project.find_by_id(p)
268 268 return false unless p
269 269 end
270 270 end
271 271 if p == parent && !p.nil?
272 272 # Nothing to do
273 273 true
274 274 elsif p.nil? || (p.active? && move_possible?(p))
275 275 # Insert the project so that target's children or root projects stay alphabetically sorted
276 276 sibs = (p.nil? ? self.class.roots : p.children)
277 277 to_be_inserted_before = sibs.detect {|c| c.name.to_s.downcase > name.to_s.downcase }
278 278 if to_be_inserted_before
279 279 move_to_left_of(to_be_inserted_before)
280 280 elsif p.nil?
281 281 if sibs.empty?
282 282 # move_to_root adds the project in first (ie. left) position
283 283 move_to_root
284 284 else
285 285 move_to_right_of(sibs.last) unless self == sibs.last
286 286 end
287 287 else
288 288 # move_to_child_of adds the project in last (ie.right) position
289 289 move_to_child_of(p)
290 290 end
291 291 true
292 292 else
293 293 # Can not move to the given target
294 294 false
295 295 end
296 296 end
297 297
298 298 # Returns an array of the trackers used by the project and its active sub projects
299 299 def rolled_up_trackers
300 300 @rolled_up_trackers ||=
301 301 Tracker.find(:all, :include => :projects,
302 302 :select => "DISTINCT #{Tracker.table_name}.*",
303 303 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt],
304 304 :order => "#{Tracker.table_name}.position")
305 305 end
306 306
307 # Closes open and locked project versions that are completed
308 def close_completed_versions
309 Version.transaction do
310 versions.find(:all, :conditions => {:status => %w(open locked)}).each do |version|
311 if version.completed?
312 version.update_attribute(:status, 'closed')
313 end
314 end
315 end
316 end
317
307 318 # Returns a hash of project users grouped by role
308 319 def users_by_role
309 320 members.find(:all, :include => [:user, :roles]).inject({}) do |h, m|
310 321 m.roles.each do |r|
311 322 h[r] ||= []
312 323 h[r] << m.user
313 324 end
314 325 h
315 326 end
316 327 end
317 328
318 329 # Deletes all project's members
319 330 def delete_all_members
320 331 me, mr = Member.table_name, MemberRole.table_name
321 332 connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})")
322 333 Member.delete_all(['project_id = ?', id])
323 334 end
324 335
325 336 # Users issues can be assigned to
326 337 def assignable_users
327 338 members.select {|m| m.roles.detect {|role| role.assignable?}}.collect {|m| m.user}.sort
328 339 end
329 340
330 341 # Returns the mail adresses of users that should be always notified on project events
331 342 def recipients
332 343 members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user.mail}
333 344 end
334 345
335 346 # Returns an array of all custom fields enabled for project issues
336 347 # (explictly associated custom fields and custom fields enabled for all projects)
337 348 def all_issue_custom_fields
338 349 @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort
339 350 end
340 351
341 352 def project
342 353 self
343 354 end
344 355
345 356 def <=>(project)
346 357 name.downcase <=> project.name.downcase
347 358 end
348 359
349 360 def to_s
350 361 name
351 362 end
352 363
353 364 # Returns a short description of the projects (first lines)
354 365 def short_description(length = 255)
355 366 description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
356 367 end
357 368
358 369 # Return true if this project is allowed to do the specified action.
359 370 # action can be:
360 371 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
361 372 # * a permission Symbol (eg. :edit_project)
362 373 def allows_to?(action)
363 374 if action.is_a? Hash
364 375 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
365 376 else
366 377 allowed_permissions.include? action
367 378 end
368 379 end
369 380
370 381 def module_enabled?(module_name)
371 382 module_name = module_name.to_s
372 383 enabled_modules.detect {|m| m.name == module_name}
373 384 end
374 385
375 386 def enabled_module_names=(module_names)
376 387 if module_names && module_names.is_a?(Array)
377 388 module_names = module_names.collect(&:to_s)
378 389 # remove disabled modules
379 390 enabled_modules.each {|mod| mod.destroy unless module_names.include?(mod.name)}
380 391 # add new modules
381 392 module_names.each {|name| enabled_modules << EnabledModule.new(:name => name)}
382 393 else
383 394 enabled_modules.clear
384 395 end
385 396 end
386 397
387 398 # Returns an auto-generated project identifier based on the last identifier used
388 399 def self.next_identifier
389 400 p = Project.find(:first, :order => 'created_on DESC')
390 401 p.nil? ? nil : p.identifier.to_s.succ
391 402 end
392 403
393 404 # Copies and saves the Project instance based on the +project+.
394 405 # Duplicates the source project's:
395 406 # * Wiki
396 407 # * Versions
397 408 # * Categories
398 409 # * Issues
399 410 # * Members
400 411 # * Queries
401 412 #
402 413 # Accepts an +options+ argument to specify what to copy
403 414 #
404 415 # Examples:
405 416 # project.copy(1) # => copies everything
406 417 # project.copy(1, :only => 'members') # => copies members only
407 418 # project.copy(1, :only => ['members', 'versions']) # => copies members and versions
408 419 def copy(project, options={})
409 420 project = project.is_a?(Project) ? project : Project.find(project)
410 421
411 422 to_be_copied = %w(wiki versions issue_categories issues members queries boards)
412 423 to_be_copied = to_be_copied & options[:only].to_a unless options[:only].nil?
413 424
414 425 Project.transaction do
415 426 if save
416 427 reload
417 428 to_be_copied.each do |name|
418 429 send "copy_#{name}", project
419 430 end
420 431 Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
421 432 save
422 433 end
423 434 end
424 435 end
425 436
426 437
427 438 # Copies +project+ and returns the new instance. This will not save
428 439 # the copy
429 440 def self.copy_from(project)
430 441 begin
431 442 project = project.is_a?(Project) ? project : Project.find(project)
432 443 if project
433 444 # clear unique attributes
434 445 attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt')
435 446 copy = Project.new(attributes)
436 447 copy.enabled_modules = project.enabled_modules
437 448 copy.trackers = project.trackers
438 449 copy.custom_values = project.custom_values.collect {|v| v.clone}
439 450 copy.issue_custom_fields = project.issue_custom_fields
440 451 return copy
441 452 else
442 453 return nil
443 454 end
444 455 rescue ActiveRecord::RecordNotFound
445 456 return nil
446 457 end
447 458 end
448 459
449 460 private
450 461
451 462 # Copies wiki from +project+
452 463 def copy_wiki(project)
453 464 # Check that the source project has a wiki first
454 465 unless project.wiki.nil?
455 466 self.wiki ||= Wiki.new
456 467 wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
457 468 project.wiki.pages.each do |page|
458 469 new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
459 470 new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
460 471 new_wiki_page.content = new_wiki_content
461 472 wiki.pages << new_wiki_page
462 473 end
463 474 end
464 475 end
465 476
466 477 # Copies versions from +project+
467 478 def copy_versions(project)
468 479 project.versions.each do |version|
469 480 new_version = Version.new
470 481 new_version.attributes = version.attributes.dup.except("id", "project_id", "created_on", "updated_on")
471 482 self.versions << new_version
472 483 end
473 484 end
474 485
475 486 # Copies issue categories from +project+
476 487 def copy_issue_categories(project)
477 488 project.issue_categories.each do |issue_category|
478 489 new_issue_category = IssueCategory.new
479 490 new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id")
480 491 self.issue_categories << new_issue_category
481 492 end
482 493 end
483 494
484 495 # Copies issues from +project+
485 496 def copy_issues(project)
486 497 project.issues.each do |issue|
487 498 new_issue = Issue.new
488 499 new_issue.copy_from(issue)
489 500 # Reassign fixed_versions by name, since names are unique per
490 501 # project and the versions for self are not yet saved
491 502 if issue.fixed_version
492 503 new_issue.fixed_version = self.versions.select {|v| v.name == issue.fixed_version.name}.first
493 504 end
494 505 # Reassign the category by name, since names are unique per
495 506 # project and the categories for self are not yet saved
496 507 if issue.category
497 508 new_issue.category = self.issue_categories.select {|c| c.name == issue.category.name}.first
498 509 end
499 510 self.issues << new_issue
500 511 end
501 512 end
502 513
503 514 # Copies members from +project+
504 515 def copy_members(project)
505 516 project.members.each do |member|
506 517 new_member = Member.new
507 518 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
508 519 new_member.role_ids = member.role_ids.dup
509 520 new_member.project = self
510 521 self.members << new_member
511 522 end
512 523 end
513 524
514 525 # Copies queries from +project+
515 526 def copy_queries(project)
516 527 project.queries.each do |query|
517 528 new_query = Query.new
518 529 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria")
519 530 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
520 531 new_query.project = self
521 532 self.queries << new_query
522 533 end
523 534 end
524 535
525 536 # Copies boards from +project+
526 537 def copy_boards(project)
527 538 project.boards.each do |board|
528 539 new_board = Board.new
529 540 new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id")
530 541 new_board.project = self
531 542 self.boards << new_board
532 543 end
533 544 end
534 545
535 546 def allowed_permissions
536 547 @allowed_permissions ||= begin
537 548 module_names = enabled_modules.collect {|m| m.name}
538 549 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
539 550 end
540 551 end
541 552
542 553 def allowed_actions
543 554 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
544 555 end
545 556
546 557 # Returns all the active Systemwide and project specific activities
547 558 def active_activities
548 559 overridden_activity_ids = self.time_entry_activities.active.collect(&:parent_id)
549 560
550 561 if overridden_activity_ids.empty?
551 562 return TimeEntryActivity.active
552 563 else
553 564 return system_activities_and_project_overrides
554 565 end
555 566 end
556 567
557 568 # Returns all the Systemwide and project specific activities
558 569 # (inactive and active)
559 570 def all_activities
560 571 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
561 572
562 573 if overridden_activity_ids.empty?
563 574 return TimeEntryActivity.all
564 575 else
565 576 return system_activities_and_project_overrides(true)
566 577 end
567 578 end
568 579
569 580 # Returns the systemwide active activities merged with the project specific overrides
570 581 def system_activities_and_project_overrides(include_inactive=false)
571 582 if include_inactive
572 583 return TimeEntryActivity.all.
573 584 find(:all,
574 585 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
575 586 self.time_entry_activities
576 587 else
577 588 return TimeEntryActivity.active.
578 589 find(:all,
579 590 :conditions => ["id NOT IN (?)", self.time_entry_activities.active.collect(&:parent_id)]) +
580 591 self.time_entry_activities.active
581 592 end
582 593 end
583 594 end
@@ -1,31 +1,37
1 1 <% if @project.versions.any? %>
2 2 <table class="list versions">
3 3 <thead>
4 4 <th><%= l(:label_version) %></th>
5 5 <th><%= l(:field_effective_date) %></th>
6 6 <th><%= l(:field_description) %></th>
7 7 <th><%= l(:field_status) %></th>
8 8 <th><%= l(:label_wiki_page) unless @project.wiki.nil? %></th>
9 9 <th style="width:15%"></th>
10 10 </thead>
11 11 <tbody>
12 12 <% for version in @project.versions.sort %>
13 13 <tr class="version <%= cycle 'odd', 'even' %> <%=h version.status %>">
14 14 <td><%= link_to h(version.name), :controller => 'versions', :action => 'show', :id => version %></td>
15 15 <td align="center"><%= format_date(version.effective_date) %></td>
16 16 <td><%=h version.description %></td>
17 17 <td><%= l("version_status_#{version.status}") %></td>
18 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>
19 19 <td class="buttons">
20 20 <%= link_to_if_authorized l(:button_edit), {:controller => 'versions', :action => 'edit', :id => version }, :class => 'icon icon-edit' %>
21 21 <%= link_to_if_authorized l(:button_delete), {:controller => 'versions', :action => 'destroy', :id => version}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
22 22 </td>
23 23 </tr>
24 24 <% end; reset_cycle %>
25 25 </tbody>
26 26 </table>
27 27 <% else %>
28 28 <p class="nodata"><%= l(:label_no_data) %></p>
29 29 <% end %>
30 30
31 <div class="contextual">
32 <% if @project.versions.any? %>
33 <%= link_to 'Close completed versions', {:controller => 'versions', :action => 'close_completed', :project_id => @project}, :method => :post %>
34 <% end %>
35 </div>
36
31 37 <p><%= link_to_if_authorized l(:label_version_new), :controller => 'projects', :action => 'add_version', :id => @project %></p>
@@ -1,262 +1,268
1 1 ActionController::Routing::Routes.draw do |map|
2 2 # Add your own custom routes here.
3 3 # The priority is based upon order of creation: first created -> highest priority.
4 4
5 5 # Here's a sample route:
6 6 # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
7 7 # Keep in mind you can assign values other than :controller and :action
8 8
9 9 map.home '', :controller => 'welcome'
10 10
11 11 map.signin 'login', :controller => 'account', :action => 'login'
12 12 map.signout 'logout', :controller => 'account', :action => 'logout'
13 13
14 14 map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow'
15 15 map.connect 'help/:ctrl/:page', :controller => 'help'
16 16
17 17 map.connect 'time_entries/:id/edit', :action => 'edit', :controller => 'timelog'
18 18 map.connect 'projects/:project_id/time_entries/new', :action => 'edit', :controller => 'timelog'
19 19 map.connect 'projects/:project_id/issues/:issue_id/time_entries/new', :action => 'edit', :controller => 'timelog'
20 20
21 21 map.with_options :controller => 'timelog' do |timelog|
22 22 timelog.connect 'projects/:project_id/time_entries', :action => 'details'
23 23
24 24 timelog.with_options :action => 'details', :conditions => {:method => :get} do |time_details|
25 25 time_details.connect 'time_entries'
26 26 time_details.connect 'time_entries.:format'
27 27 time_details.connect 'issues/:issue_id/time_entries'
28 28 time_details.connect 'issues/:issue_id/time_entries.:format'
29 29 time_details.connect 'projects/:project_id/time_entries.:format'
30 30 time_details.connect 'projects/:project_id/issues/:issue_id/time_entries'
31 31 time_details.connect 'projects/:project_id/issues/:issue_id/time_entries.:format'
32 32 end
33 33 timelog.connect 'projects/:project_id/time_entries/report', :action => 'report'
34 34 timelog.with_options :action => 'report',:conditions => {:method => :get} do |time_report|
35 35 time_report.connect 'time_entries/report'
36 36 time_report.connect 'time_entries/report.:format'
37 37 time_report.connect 'projects/:project_id/time_entries/report.:format'
38 38 end
39 39
40 40 timelog.with_options :action => 'edit', :conditions => {:method => :get} do |time_edit|
41 41 time_edit.connect 'issues/:issue_id/time_entries/new'
42 42 end
43 43
44 44 timelog.connect 'time_entries/:id/destroy', :action => 'destroy', :conditions => {:method => :post}
45 45 end
46 46
47 47 map.connect 'projects/:id/wiki', :controller => 'wikis', :action => 'edit', :conditions => {:method => :post}
48 48 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :get}
49 49 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :post}
50 50 map.with_options :controller => 'wiki' do |wiki_routes|
51 51 wiki_routes.with_options :conditions => {:method => :get} do |wiki_views|
52 52 wiki_views.connect 'projects/:id/wiki/:page', :action => 'special', :page => /page_index|date_index|export/i
53 53 wiki_views.connect 'projects/:id/wiki/:page', :action => 'index', :page => nil
54 54 wiki_views.connect 'projects/:id/wiki/:page/edit', :action => 'edit'
55 55 wiki_views.connect 'projects/:id/wiki/:page/rename', :action => 'rename'
56 56 wiki_views.connect 'projects/:id/wiki/:page/history', :action => 'history'
57 57 wiki_views.connect 'projects/:id/wiki/:page/diff/:version/vs/:version_from', :action => 'diff'
58 58 wiki_views.connect 'projects/:id/wiki/:page/annotate/:version', :action => 'annotate'
59 59 end
60 60
61 61 wiki_routes.connect 'projects/:id/wiki/:page/:action',
62 62 :action => /edit|rename|destroy|preview|protect/,
63 63 :conditions => {:method => :post}
64 64 end
65 65
66 66 map.with_options :controller => 'messages' do |messages_routes|
67 67 messages_routes.with_options :conditions => {:method => :get} do |messages_views|
68 68 messages_views.connect 'boards/:board_id/topics/new', :action => 'new'
69 69 messages_views.connect 'boards/:board_id/topics/:id', :action => 'show'
70 70 messages_views.connect 'boards/:board_id/topics/:id/edit', :action => 'edit'
71 71 end
72 72 messages_routes.with_options :conditions => {:method => :post} do |messages_actions|
73 73 messages_actions.connect 'boards/:board_id/topics/new', :action => 'new'
74 74 messages_actions.connect 'boards/:board_id/topics/:id/replies', :action => 'reply'
75 75 messages_actions.connect 'boards/:board_id/topics/:id/:action', :action => /edit|destroy/
76 76 end
77 77 end
78 78
79 79 map.with_options :controller => 'boards' do |board_routes|
80 80 board_routes.with_options :conditions => {:method => :get} do |board_views|
81 81 board_views.connect 'projects/:project_id/boards', :action => 'index'
82 82 board_views.connect 'projects/:project_id/boards/new', :action => 'new'
83 83 board_views.connect 'projects/:project_id/boards/:id', :action => 'show'
84 84 board_views.connect 'projects/:project_id/boards/:id.:format', :action => 'show'
85 85 board_views.connect 'projects/:project_id/boards/:id/edit', :action => 'edit'
86 86 end
87 87 board_routes.with_options :conditions => {:method => :post} do |board_actions|
88 88 board_actions.connect 'projects/:project_id/boards', :action => 'new'
89 89 board_actions.connect 'projects/:project_id/boards/:id/:action', :action => /edit|destroy/
90 90 end
91 91 end
92 92
93 93 map.with_options :controller => 'documents' do |document_routes|
94 94 document_routes.with_options :conditions => {:method => :get} do |document_views|
95 95 document_views.connect 'projects/:project_id/documents', :action => 'index'
96 96 document_views.connect 'projects/:project_id/documents/new', :action => 'new'
97 97 document_views.connect 'documents/:id', :action => 'show'
98 98 document_views.connect 'documents/:id/edit', :action => 'edit'
99 99 end
100 100 document_routes.with_options :conditions => {:method => :post} do |document_actions|
101 101 document_actions.connect 'projects/:project_id/documents', :action => 'new'
102 102 document_actions.connect 'documents/:id/:action', :action => /destroy|edit/
103 103 end
104 104 end
105 105
106 106 map.with_options :controller => 'issues' do |issues_routes|
107 107 issues_routes.with_options :conditions => {:method => :get} do |issues_views|
108 108 issues_views.connect 'issues', :action => 'index'
109 109 issues_views.connect 'issues.:format', :action => 'index'
110 110 issues_views.connect 'projects/:project_id/issues', :action => 'index'
111 111 issues_views.connect 'projects/:project_id/issues.:format', :action => 'index'
112 112 issues_views.connect 'projects/:project_id/issues/new', :action => 'new'
113 113 issues_views.connect 'projects/:project_id/issues/gantt', :action => 'gantt'
114 114 issues_views.connect 'projects/:project_id/issues/calendar', :action => 'calendar'
115 115 issues_views.connect 'projects/:project_id/issues/:copy_from/copy', :action => 'new'
116 116 issues_views.connect 'issues/:id', :action => 'show', :id => /\d+/
117 117 issues_views.connect 'issues/:id.:format', :action => 'show', :id => /\d+/
118 118 issues_views.connect 'issues/:id/edit', :action => 'edit', :id => /\d+/
119 119 issues_views.connect 'issues/:id/move', :action => 'move', :id => /\d+/
120 120 end
121 121 issues_routes.with_options :conditions => {:method => :post} do |issues_actions|
122 122 issues_actions.connect 'projects/:project_id/issues', :action => 'new'
123 123 issues_actions.connect 'issues/:id/quoted', :action => 'reply', :id => /\d+/
124 124 issues_actions.connect 'issues/:id/:action', :action => /edit|move|destroy/, :id => /\d+/
125 125 end
126 126 issues_routes.connect 'issues/:action'
127 127 end
128 128
129 129 map.with_options :controller => 'issue_relations', :conditions => {:method => :post} do |relations|
130 130 relations.connect 'issues/:issue_id/relations/:id', :action => 'new'
131 131 relations.connect 'issues/:issue_id/relations/:id/destroy', :action => 'destroy'
132 132 end
133 133
134 134 map.with_options :controller => 'reports', :action => 'issue_report', :conditions => {:method => :get} do |reports|
135 135 reports.connect 'projects/:id/issues/report'
136 136 reports.connect 'projects/:id/issues/report/:detail'
137 137 end
138 138
139 139 map.with_options :controller => 'news' do |news_routes|
140 140 news_routes.with_options :conditions => {:method => :get} do |news_views|
141 141 news_views.connect 'news', :action => 'index'
142 142 news_views.connect 'projects/:project_id/news', :action => 'index'
143 143 news_views.connect 'projects/:project_id/news.:format', :action => 'index'
144 144 news_views.connect 'news.:format', :action => 'index'
145 145 news_views.connect 'projects/:project_id/news/new', :action => 'new'
146 146 news_views.connect 'news/:id', :action => 'show'
147 147 news_views.connect 'news/:id/edit', :action => 'edit'
148 148 end
149 149 news_routes.with_options do |news_actions|
150 150 news_actions.connect 'projects/:project_id/news', :action => 'new'
151 151 news_actions.connect 'news/:id/edit', :action => 'edit'
152 152 news_actions.connect 'news/:id/destroy', :action => 'destroy'
153 153 end
154 154 end
155 155
156 156 map.connect 'projects/:id/members/new', :controller => 'members', :action => 'new'
157 157
158 158 map.with_options :controller => 'users' do |users|
159 159 users.with_options :conditions => {:method => :get} do |user_views|
160 160 user_views.connect 'users', :action => 'index'
161 161 user_views.connect 'users/:id', :action => 'show', :id => /\d+/
162 162 user_views.connect 'users/new', :action => 'add'
163 163 user_views.connect 'users/:id/edit/:tab', :action => 'edit', :tab => nil
164 164 end
165 165 users.with_options :conditions => {:method => :post} do |user_actions|
166 166 user_actions.connect 'users', :action => 'add'
167 167 user_actions.connect 'users/new', :action => 'add'
168 168 user_actions.connect 'users/:id/edit', :action => 'edit'
169 169 user_actions.connect 'users/:id/memberships', :action => 'edit_membership'
170 170 user_actions.connect 'users/:id/memberships/:membership_id', :action => 'edit_membership'
171 171 user_actions.connect 'users/:id/memberships/:membership_id/destroy', :action => 'destroy_membership'
172 172 end
173 173 end
174 174
175 175 map.with_options :controller => 'projects' do |projects|
176 176 projects.with_options :conditions => {:method => :get} do |project_views|
177 177 project_views.connect 'projects', :action => 'index'
178 178 project_views.connect 'projects.:format', :action => 'index'
179 179 project_views.connect 'projects/new', :action => 'add'
180 180 project_views.connect 'projects/:id', :action => 'show'
181 181 project_views.connect 'projects/:id/:action', :action => /roadmap|changelog|destroy|settings/
182 182 project_views.connect 'projects/:id/files', :action => 'list_files'
183 183 project_views.connect 'projects/:id/files/new', :action => 'add_file'
184 184 project_views.connect 'projects/:id/versions/new', :action => 'add_version'
185 185 project_views.connect 'projects/:id/categories/new', :action => 'add_issue_category'
186 186 project_views.connect 'projects/:id/settings/:tab', :action => 'settings'
187 187 end
188 188
189 189 projects.with_options :action => 'activity', :conditions => {:method => :get} do |activity|
190 190 activity.connect 'projects/:id/activity'
191 191 activity.connect 'projects/:id/activity.:format'
192 192 activity.connect 'activity', :id => nil
193 193 activity.connect 'activity.:format', :id => nil
194 194 end
195 195
196 196 projects.with_options :conditions => {:method => :post} do |project_actions|
197 197 project_actions.connect 'projects/new', :action => 'add'
198 198 project_actions.connect 'projects', :action => 'add'
199 199 project_actions.connect 'projects/:id/:action', :action => /destroy|archive|unarchive/
200 200 project_actions.connect 'projects/:id/files/new', :action => 'add_file'
201 201 project_actions.connect 'projects/:id/versions/new', :action => 'add_version'
202 202 project_actions.connect 'projects/:id/categories/new', :action => 'add_issue_category'
203 203 project_actions.connect 'projects/:id/activities/save', :action => 'save_activities'
204 204 end
205 205
206 206 projects.with_options :conditions => {:method => :delete} do |project_actions|
207 207 project_actions.conditions 'projects/:id/reset_activities', :action => 'reset_activities'
208 208 end
209 209 end
210 210
211 map.with_options :controller => 'versions' do |versions|
212 versions.with_options :conditions => {:method => :post} do |version_actions|
213 version_actions.connect 'projects/:project_id/versions/close_completed', :action => 'close_completed'
214 end
215 end
216
211 217 map.with_options :controller => 'repositories' do |repositories|
212 218 repositories.with_options :conditions => {:method => :get} do |repository_views|
213 219 repository_views.connect 'projects/:id/repository', :action => 'show'
214 220 repository_views.connect 'projects/:id/repository/edit', :action => 'edit'
215 221 repository_views.connect 'projects/:id/repository/statistics', :action => 'stats'
216 222 repository_views.connect 'projects/:id/repository/revisions', :action => 'revisions'
217 223 repository_views.connect 'projects/:id/repository/revisions.:format', :action => 'revisions'
218 224 repository_views.connect 'projects/:id/repository/revisions/:rev', :action => 'revision'
219 225 repository_views.connect 'projects/:id/repository/revisions/:rev/diff', :action => 'diff'
220 226 repository_views.connect 'projects/:id/repository/revisions/:rev/diff.:format', :action => 'diff'
221 227 repository_views.connect 'projects/:id/repository/revisions/:rev/:action/*path', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
222 228 repository_views.connect 'projects/:id/repository/:action/*path'
223 229 end
224 230
225 231 repositories.connect 'projects/:id/repository/:action', :conditions => {:method => :post}
226 232 end
227 233
228 234 map.connect 'attachments/:id', :controller => 'attachments', :action => 'show', :id => /\d+/
229 235 map.connect 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/
230 236 map.connect 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/
231 237
232 238 map.resources :groups
233 239
234 240 #left old routes at the bottom for backwards compat
235 241 map.connect 'projects/:project_id/issues/:action', :controller => 'issues'
236 242 map.connect 'projects/:project_id/documents/:action', :controller => 'documents'
237 243 map.connect 'projects/:project_id/boards/:action/:id', :controller => 'boards'
238 244 map.connect 'boards/:board_id/topics/:action/:id', :controller => 'messages'
239 245 map.connect 'wiki/:id/:page/:action', :page => nil, :controller => 'wiki'
240 246 map.connect 'issues/:issue_id/relations/:action/:id', :controller => 'issue_relations'
241 247 map.connect 'projects/:project_id/news/:action', :controller => 'news'
242 248 map.connect 'projects/:project_id/timelog/:action/:id', :controller => 'timelog', :project_id => /.+/
243 249 map.with_options :controller => 'repositories' do |omap|
244 250 omap.repositories_show 'repositories/browse/:id/*path', :action => 'browse'
245 251 omap.repositories_changes 'repositories/changes/:id/*path', :action => 'changes'
246 252 omap.repositories_diff 'repositories/diff/:id/*path', :action => 'diff'
247 253 omap.repositories_entry 'repositories/entry/:id/*path', :action => 'entry'
248 254 omap.repositories_entry 'repositories/annotate/:id/*path', :action => 'annotate'
249 255 omap.connect 'repositories/revision/:id/:rev', :action => 'revision'
250 256 end
251 257
252 258 map.with_options :controller => 'sys' do |sys|
253 259 sys.connect 'sys/projects.:format', :action => 'projects', :conditions => {:method => :get}
254 260 sys.connect 'sys/projects/:id/repository.:format', :action => 'create_project_repository', :conditions => {:method => :post}
255 261 end
256 262
257 263 # Install the default route as the lowest priority.
258 264 map.connect ':controller/:action/:id'
259 265 map.connect 'robots.txt', :controller => 'welcome', :action => 'robots'
260 266 # Used for OpenID
261 267 map.root :controller => 'account', :action => 'login'
262 268 end
@@ -1,174 +1,174
1 1 require 'redmine/access_control'
2 2 require 'redmine/menu_manager'
3 3 require 'redmine/activity'
4 4 require 'redmine/mime_type'
5 5 require 'redmine/core_ext'
6 6 require 'redmine/themes'
7 7 require 'redmine/hook'
8 8 require 'redmine/plugin'
9 9 require 'redmine/wiki_formatting'
10 10
11 11 begin
12 12 require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
13 13 rescue LoadError
14 14 # RMagick is not available
15 15 end
16 16
17 17 if RUBY_VERSION < '1.9'
18 18 require 'faster_csv'
19 19 else
20 20 require 'csv'
21 21 FCSV = CSV
22 22 end
23 23
24 24 REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar Git Filesystem )
25 25
26 26 # Permissions
27 27 Redmine::AccessControl.map do |map|
28 28 map.permission :view_project, {:projects => [:show, :activity]}, :public => true
29 29 map.permission :search_project, {:search => :index}, :public => true
30 30 map.permission :add_project, {:projects => :add}, :require => :loggedin
31 31 map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member
32 32 map.permission :select_project_modules, {:projects => :modules}, :require => :member
33 33 map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy, :autocomplete_for_member]}, :require => :member
34 map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :destroy]}, :require => :member
34 map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :close_completed, :destroy]}, :require => :member
35 35
36 36 map.project_module :issue_tracking do |map|
37 37 # Issue categories
38 38 map.permission :manage_categories, {:projects => [:settings, :add_issue_category], :issue_categories => [:edit, :destroy]}, :require => :member
39 39 # Issues
40 40 map.permission :view_issues, {:projects => [:changelog, :roadmap],
41 41 :issues => [:index, :changes, :show, :context_menu],
42 42 :versions => [:show, :status_by],
43 43 :queries => :index,
44 44 :reports => :issue_report}, :public => true
45 45 map.permission :add_issues, {:issues => :new}
46 46 map.permission :edit_issues, {:issues => [:edit, :reply, :bulk_edit]}
47 47 map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}
48 48 map.permission :add_issue_notes, {:issues => [:edit, :reply]}
49 49 map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin
50 50 map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin
51 51 map.permission :move_issues, {:issues => :move}, :require => :loggedin
52 52 map.permission :delete_issues, {:issues => :destroy}, :require => :member
53 53 # Queries
54 54 map.permission :manage_public_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
55 55 map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
56 56 # Gantt & calendar
57 57 map.permission :view_gantt, :issues => :gantt
58 58 map.permission :view_calendar, :issues => :calendar
59 59 # Watchers
60 60 map.permission :view_issue_watchers, {}
61 61 map.permission :add_issue_watchers, {:watchers => :new}
62 62 map.permission :delete_issue_watchers, {:watchers => :destroy}
63 63 end
64 64
65 65 map.project_module :time_tracking do |map|
66 66 map.permission :log_time, {:timelog => :edit}, :require => :loggedin
67 67 map.permission :view_time_entries, :timelog => [:details, :report]
68 68 map.permission :edit_time_entries, {:timelog => [:edit, :destroy]}, :require => :member
69 69 map.permission :edit_own_time_entries, {:timelog => [:edit, :destroy]}, :require => :loggedin
70 70 map.permission :manage_project_activities, {:projects => [:save_activities, :reset_activities]}, :require => :member
71 71 end
72 72
73 73 map.project_module :news do |map|
74 74 map.permission :manage_news, {:news => [:new, :edit, :destroy, :destroy_comment]}, :require => :member
75 75 map.permission :view_news, {:news => [:index, :show]}, :public => true
76 76 map.permission :comment_news, {:news => :add_comment}
77 77 end
78 78
79 79 map.project_module :documents do |map|
80 80 map.permission :manage_documents, {:documents => [:new, :edit, :destroy, :add_attachment]}, :require => :loggedin
81 81 map.permission :view_documents, :documents => [:index, :show, :download]
82 82 end
83 83
84 84 map.project_module :files do |map|
85 85 map.permission :manage_files, {:projects => :add_file}, :require => :loggedin
86 86 map.permission :view_files, :projects => :list_files, :versions => :download
87 87 end
88 88
89 89 map.project_module :wiki do |map|
90 90 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
91 91 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
92 92 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
93 93 map.permission :view_wiki_pages, :wiki => [:index, :special]
94 94 map.permission :view_wiki_edits, :wiki => [:history, :diff, :annotate]
95 95 map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment]
96 96 map.permission :delete_wiki_pages_attachments, {}
97 97 map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member
98 98 end
99 99
100 100 map.project_module :repository do |map|
101 101 map.permission :manage_repository, {:repositories => [:edit, :committers, :destroy]}, :require => :member
102 102 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]
103 103 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
104 104 map.permission :commit_access, {}
105 105 end
106 106
107 107 map.project_module :boards do |map|
108 108 map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
109 109 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
110 110 map.permission :add_messages, {:messages => [:new, :reply, :quote]}
111 111 map.permission :edit_messages, {:messages => :edit}, :require => :member
112 112 map.permission :edit_own_messages, {:messages => :edit}, :require => :loggedin
113 113 map.permission :delete_messages, {:messages => :destroy}, :require => :member
114 114 map.permission :delete_own_messages, {:messages => :destroy}, :require => :loggedin
115 115 end
116 116 end
117 117
118 118 Redmine::MenuManager.map :top_menu do |menu|
119 119 menu.push :home, :home_path
120 120 menu.push :my_page, { :controller => 'my', :action => 'page' }, :if => Proc.new { User.current.logged? }
121 121 menu.push :projects, { :controller => 'projects', :action => 'index' }, :caption => :label_project_plural
122 122 menu.push :administration, { :controller => 'admin', :action => 'index' }, :if => Proc.new { User.current.admin? }, :last => true
123 123 menu.push :help, Redmine::Info.help_url, :last => true
124 124 end
125 125
126 126 Redmine::MenuManager.map :account_menu do |menu|
127 127 menu.push :login, :signin_path, :if => Proc.new { !User.current.logged? }
128 128 menu.push :register, { :controller => 'account', :action => 'register' }, :if => Proc.new { !User.current.logged? && Setting.self_registration? }
129 129 menu.push :my_account, { :controller => 'my', :action => 'account' }, :if => Proc.new { User.current.logged? }
130 130 menu.push :logout, :signout_path, :if => Proc.new { User.current.logged? }
131 131 end
132 132
133 133 Redmine::MenuManager.map :application_menu do |menu|
134 134 # Empty
135 135 end
136 136
137 137 Redmine::MenuManager.map :admin_menu do |menu|
138 138 # Empty
139 139 end
140 140
141 141 Redmine::MenuManager.map :project_menu do |menu|
142 142 menu.push :overview, { :controller => 'projects', :action => 'show' }
143 143 menu.push :activity, { :controller => 'projects', :action => 'activity' }
144 144 menu.push :roadmap, { :controller => 'projects', :action => 'roadmap' },
145 145 :if => Proc.new { |p| p.versions.any? }
146 146 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
147 147 menu.push :new_issue, { :controller => 'issues', :action => 'new' }, :param => :project_id, :caption => :label_issue_new,
148 148 :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) }
149 149 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
150 150 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
151 151 menu.push :wiki, { :controller => 'wiki', :action => 'index', :page => nil },
152 152 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
153 153 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
154 154 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
155 155 menu.push :files, { :controller => 'projects', :action => 'list_files' }, :caption => :label_attachment_plural
156 156 menu.push :repository, { :controller => 'repositories', :action => 'show' },
157 157 :if => Proc.new { |p| p.repository && !p.repository.new_record? }
158 158 menu.push :settings, { :controller => 'projects', :action => 'settings' }, :last => true
159 159 end
160 160
161 161 Redmine::Activity.map do |activity|
162 162 activity.register :issues, :class_name => %w(Issue Journal)
163 163 activity.register :changesets
164 164 activity.register :news
165 165 activity.register :documents, :class_name => %w(Document Attachment)
166 166 activity.register :files, :class_name => 'Attachment'
167 167 activity.register :wiki_edits, :class_name => 'WikiContent::Version', :default => false
168 168 activity.register :messages, :default => false
169 169 activity.register :time_entries, :default => false
170 170 end
171 171
172 172 Redmine::WikiFormatting.map do |format|
173 173 format.register :textile, Redmine::WikiFormatting::Textile::Formatter, Redmine::WikiFormatting::Textile::Helper
174 174 end
@@ -1,73 +1,81
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.dirname(__FILE__) + '/../test_helper'
19 19 require 'versions_controller'
20 20
21 21 # Re-raise errors caught by the controller.
22 22 class VersionsController; def rescue_action(e) raise e end; end
23 23
24 24 class VersionsControllerTest < ActionController::TestCase
25 25 fixtures :projects, :versions, :issues, :users, :roles, :members, :member_roles, :enabled_modules
26 26
27 27 def setup
28 28 @controller = VersionsController.new
29 29 @request = ActionController::TestRequest.new
30 30 @response = ActionController::TestResponse.new
31 31 User.current = nil
32 32 end
33 33
34 34 def test_show
35 35 get :show, :id => 2
36 36 assert_response :success
37 37 assert_template 'show'
38 38 assert_not_nil assigns(:version)
39 39
40 40 assert_tag :tag => 'h2', :content => /1.0/
41 41 end
42 42
43 43 def test_get_edit
44 44 @request.session[:user_id] = 2
45 45 get :edit, :id => 2
46 46 assert_response :success
47 47 assert_template 'edit'
48 48 end
49 49
50 def test_close_completed
51 Version.update_all("status = 'open'")
52 @request.session[:user_id] = 2
53 post :close_completed, :project_id => 'ecookbook'
54 assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook'
55 assert_not_nil Version.find_by_status('closed')
56 end
57
50 58 def test_post_edit
51 59 @request.session[:user_id] = 2
52 60 post :edit, :id => 2,
53 61 :version => { :name => 'New version name',
54 62 :effective_date => Date.today.strftime("%Y-%m-%d")}
55 63 assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook'
56 64 version = Version.find(2)
57 65 assert_equal 'New version name', version.name
58 66 assert_equal Date.today, version.effective_date
59 67 end
60 68
61 69 def test_destroy
62 70 @request.session[:user_id] = 2
63 71 post :destroy, :id => 3
64 72 assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook'
65 73 assert_nil Version.find_by_id(3)
66 74 end
67 75
68 76 def test_issue_status_by
69 77 xhr :get, :status_by, :id => 2
70 78 assert_response :success
71 79 assert_template '_issue_counts'
72 80 end
73 81 end
@@ -1,530 +1,541
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.dirname(__FILE__) + '/../test_helper'
19 19
20 20 class ProjectTest < ActiveSupport::TestCase
21 21 fixtures :projects, :enabled_modules,
22 22 :issues, :issue_statuses, :journals, :journal_details,
23 23 :users, :members, :member_roles, :roles, :projects_trackers, :trackers, :boards,
24 24 :queries
25 25
26 26 def setup
27 27 @ecookbook = Project.find(1)
28 28 @ecookbook_sub1 = Project.find(3)
29 29 end
30 30
31 31 should_validate_presence_of :name
32 32 should_validate_presence_of :identifier
33 33
34 34 should_validate_uniqueness_of :name
35 35 should_validate_uniqueness_of :identifier
36 36
37 37 context "associations" do
38 38 should_have_many :members
39 39 should_have_many :users, :through => :members
40 40 should_have_many :member_principals
41 41 should_have_many :principals, :through => :member_principals
42 42 should_have_many :enabled_modules
43 43 should_have_many :issues
44 44 should_have_many :issue_changes, :through => :issues
45 45 should_have_many :versions
46 46 should_have_many :time_entries
47 47 should_have_many :queries
48 48 should_have_many :documents
49 49 should_have_many :news
50 50 should_have_many :issue_categories
51 51 should_have_many :boards
52 52 should_have_many :changesets, :through => :repository
53 53
54 54 should_have_one :repository
55 55 should_have_one :wiki
56 56
57 57 should_have_and_belong_to_many :trackers
58 58 should_have_and_belong_to_many :issue_custom_fields
59 59 end
60 60
61 61 def test_truth
62 62 assert_kind_of Project, @ecookbook
63 63 assert_equal "eCookbook", @ecookbook.name
64 64 end
65 65
66 66 def test_update
67 67 assert_equal "eCookbook", @ecookbook.name
68 68 @ecookbook.name = "eCook"
69 69 assert @ecookbook.save, @ecookbook.errors.full_messages.join("; ")
70 70 @ecookbook.reload
71 71 assert_equal "eCook", @ecookbook.name
72 72 end
73 73
74 74 def test_validate_identifier
75 75 to_test = {"abc" => true,
76 76 "ab12" => true,
77 77 "ab-12" => true,
78 78 "12" => false,
79 79 "new" => false}
80 80
81 81 to_test.each do |identifier, valid|
82 82 p = Project.new
83 83 p.identifier = identifier
84 84 p.valid?
85 85 assert_equal valid, p.errors.on('identifier').nil?
86 86 end
87 87 end
88 88
89 89 def test_members_should_be_active_users
90 90 Project.all.each do |project|
91 91 assert_nil project.members.detect {|m| !(m.user.is_a?(User) && m.user.active?) }
92 92 end
93 93 end
94 94
95 95 def test_users_should_be_active_users
96 96 Project.all.each do |project|
97 97 assert_nil project.users.detect {|u| !(u.is_a?(User) && u.active?) }
98 98 end
99 99 end
100 100
101 101 def test_archive
102 102 user = @ecookbook.members.first.user
103 103 @ecookbook.archive
104 104 @ecookbook.reload
105 105
106 106 assert !@ecookbook.active?
107 107 assert !user.projects.include?(@ecookbook)
108 108 # Subproject are also archived
109 109 assert !@ecookbook.children.empty?
110 110 assert @ecookbook.descendants.active.empty?
111 111 end
112 112
113 113 def test_unarchive
114 114 user = @ecookbook.members.first.user
115 115 @ecookbook.archive
116 116 # A subproject of an archived project can not be unarchived
117 117 assert !@ecookbook_sub1.unarchive
118 118
119 119 # Unarchive project
120 120 assert @ecookbook.unarchive
121 121 @ecookbook.reload
122 122 assert @ecookbook.active?
123 123 assert user.projects.include?(@ecookbook)
124 124 # Subproject can now be unarchived
125 125 @ecookbook_sub1.reload
126 126 assert @ecookbook_sub1.unarchive
127 127 end
128 128
129 129 def test_destroy
130 130 # 2 active members
131 131 assert_equal 2, @ecookbook.members.size
132 132 # and 1 is locked
133 133 assert_equal 3, Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).size
134 134 # some boards
135 135 assert @ecookbook.boards.any?
136 136
137 137 @ecookbook.destroy
138 138 # make sure that the project non longer exists
139 139 assert_raise(ActiveRecord::RecordNotFound) { Project.find(@ecookbook.id) }
140 140 # make sure related data was removed
141 141 assert Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).empty?
142 142 assert Board.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).empty?
143 143 end
144 144
145 145 def test_move_an_orphan_project_to_a_root_project
146 146 sub = Project.find(2)
147 147 sub.set_parent! @ecookbook
148 148 assert_equal @ecookbook.id, sub.parent.id
149 149 @ecookbook.reload
150 150 assert_equal 4, @ecookbook.children.size
151 151 end
152 152
153 153 def test_move_an_orphan_project_to_a_subproject
154 154 sub = Project.find(2)
155 155 assert sub.set_parent!(@ecookbook_sub1)
156 156 end
157 157
158 158 def test_move_a_root_project_to_a_project
159 159 sub = @ecookbook
160 160 assert sub.set_parent!(Project.find(2))
161 161 end
162 162
163 163 def test_should_not_move_a_project_to_its_children
164 164 sub = @ecookbook
165 165 assert !(sub.set_parent!(Project.find(3)))
166 166 end
167 167
168 168 def test_set_parent_should_add_roots_in_alphabetical_order
169 169 ProjectCustomField.delete_all
170 170 Project.delete_all
171 171 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(nil)
172 172 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(nil)
173 173 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(nil)
174 174 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(nil)
175 175
176 176 assert_equal 4, Project.count
177 177 assert_equal Project.all.sort_by(&:name), Project.all.sort_by(&:lft)
178 178 end
179 179
180 180 def test_set_parent_should_add_children_in_alphabetical_order
181 181 ProjectCustomField.delete_all
182 182 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
183 183 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(parent)
184 184 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(parent)
185 185 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(parent)
186 186 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(parent)
187 187
188 188 parent.reload
189 189 assert_equal 4, parent.children.size
190 190 assert_equal parent.children.sort_by(&:name), parent.children
191 191 end
192 192
193 193 def test_rebuild_should_sort_children_alphabetically
194 194 ProjectCustomField.delete_all
195 195 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
196 196 Project.create!(:name => 'Project C', :identifier => 'project-c').move_to_child_of(parent)
197 197 Project.create!(:name => 'Project B', :identifier => 'project-b').move_to_child_of(parent)
198 198 Project.create!(:name => 'Project D', :identifier => 'project-d').move_to_child_of(parent)
199 199 Project.create!(:name => 'Project A', :identifier => 'project-a').move_to_child_of(parent)
200 200
201 201 Project.update_all("lft = NULL, rgt = NULL")
202 202 Project.rebuild!
203 203
204 204 parent.reload
205 205 assert_equal 4, parent.children.size
206 206 assert_equal parent.children.sort_by(&:name), parent.children
207 207 end
208 208
209 209 def test_parent
210 210 p = Project.find(6).parent
211 211 assert p.is_a?(Project)
212 212 assert_equal 5, p.id
213 213 end
214 214
215 215 def test_ancestors
216 216 a = Project.find(6).ancestors
217 217 assert a.first.is_a?(Project)
218 218 assert_equal [1, 5], a.collect(&:id)
219 219 end
220 220
221 221 def test_root
222 222 r = Project.find(6).root
223 223 assert r.is_a?(Project)
224 224 assert_equal 1, r.id
225 225 end
226 226
227 227 def test_children
228 228 c = Project.find(1).children
229 229 assert c.first.is_a?(Project)
230 230 assert_equal [5, 3, 4], c.collect(&:id)
231 231 end
232 232
233 233 def test_descendants
234 234 d = Project.find(1).descendants
235 235 assert d.first.is_a?(Project)
236 236 assert_equal [5, 6, 3, 4], d.collect(&:id)
237 237 end
238 238
239 239 def test_users_by_role
240 240 users_by_role = Project.find(1).users_by_role
241 241 assert_kind_of Hash, users_by_role
242 242 role = Role.find(1)
243 243 assert_kind_of Array, users_by_role[role]
244 244 assert users_by_role[role].include?(User.find(2))
245 245 end
246 246
247 247 def test_rolled_up_trackers
248 248 parent = Project.find(1)
249 249 parent.trackers = Tracker.find([1,2])
250 250 child = parent.children.find(3)
251 251
252 252 assert_equal [1, 2], parent.tracker_ids
253 253 assert_equal [2, 3], child.trackers.collect(&:id)
254 254
255 255 assert_kind_of Tracker, parent.rolled_up_trackers.first
256 256 assert_equal Tracker.find(1), parent.rolled_up_trackers.first
257 257
258 258 assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id)
259 259 assert_equal [2, 3], child.rolled_up_trackers.collect(&:id)
260 260 end
261 261
262 262 def test_rolled_up_trackers_should_ignore_archived_subprojects
263 263 parent = Project.find(1)
264 264 parent.trackers = Tracker.find([1,2])
265 265 child = parent.children.find(3)
266 266 child.trackers = Tracker.find([1,3])
267 267 parent.children.each(&:archive)
268 268
269 269 assert_equal [1,2], parent.rolled_up_trackers.collect(&:id)
270 270 end
271 271
272 272 def test_next_identifier
273 273 ProjectCustomField.delete_all
274 274 Project.create!(:name => 'last', :identifier => 'p2008040')
275 275 assert_equal 'p2008041', Project.next_identifier
276 276 end
277 277
278 278 def test_next_identifier_first_project
279 279 Project.delete_all
280 280 assert_nil Project.next_identifier
281 281 end
282 282
283 283
284 284 def test_enabled_module_names_should_not_recreate_enabled_modules
285 285 project = Project.find(1)
286 286 # Remove one module
287 287 modules = project.enabled_modules.slice(0..-2)
288 288 assert modules.any?
289 289 assert_difference 'EnabledModule.count', -1 do
290 290 project.enabled_module_names = modules.collect(&:name)
291 291 end
292 292 project.reload
293 293 # Ids should be preserved
294 294 assert_equal project.enabled_module_ids.sort, modules.collect(&:id).sort
295 295 end
296 296
297 297 def test_copy_from_existing_project
298 298 source_project = Project.find(1)
299 299 copied_project = Project.copy_from(1)
300 300
301 301 assert copied_project
302 302 # Cleared attributes
303 303 assert copied_project.id.blank?
304 304 assert copied_project.name.blank?
305 305 assert copied_project.identifier.blank?
306 306
307 307 # Duplicated attributes
308 308 assert_equal source_project.description, copied_project.description
309 309 assert_equal source_project.enabled_modules, copied_project.enabled_modules
310 310 assert_equal source_project.trackers, copied_project.trackers
311 311
312 312 # Default attributes
313 313 assert_equal 1, copied_project.status
314 314 end
315 315
316 316 def test_activities_should_use_the_system_activities
317 317 project = Project.find(1)
318 318 assert_equal project.activities, TimeEntryActivity.find(:all, :conditions => {:active => true} )
319 319 end
320 320
321 321
322 322 def test_activities_should_use_the_project_specific_activities
323 323 project = Project.find(1)
324 324 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project})
325 325 assert overridden_activity.save!
326 326
327 327 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
328 328 end
329 329
330 330 def test_activities_should_not_include_the_inactive_project_specific_activities
331 331 project = Project.find(1)
332 332 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
333 333 assert overridden_activity.save!
334 334
335 335 assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity found"
336 336 end
337 337
338 338 def test_activities_should_not_include_project_specific_activities_from_other_projects
339 339 project = Project.find(1)
340 340 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(2)})
341 341 assert overridden_activity.save!
342 342
343 343 assert !project.activities.include?(overridden_activity), "Project specific Activity found on a different project"
344 344 end
345 345
346 346 def test_activities_should_handle_nils
347 347 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(1), :parent => TimeEntryActivity.find(:first)})
348 348 TimeEntryActivity.delete_all
349 349
350 350 # No activities
351 351 project = Project.find(1)
352 352 assert project.activities.empty?
353 353
354 354 # No system, one overridden
355 355 assert overridden_activity.save!
356 356 project.reload
357 357 assert_equal [overridden_activity], project.activities
358 358 end
359 359
360 360 def test_activities_should_override_system_activities_with_project_activities
361 361 project = Project.find(1)
362 362 parent_activity = TimeEntryActivity.find(:first)
363 363 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => parent_activity})
364 364 assert overridden_activity.save!
365 365
366 366 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
367 367 assert !project.activities.include?(parent_activity), "System Activity found when it should have been overridden"
368 368 end
369 369
370 370 def test_activities_should_include_inactive_activities_if_specified
371 371 project = Project.find(1)
372 372 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
373 373 assert overridden_activity.save!
374 374
375 375 assert project.activities(true).include?(overridden_activity), "Inactive Project specific Activity not found"
376 376 end
377
378 def test_close_completed_versions
379 Version.update_all("status = 'open'")
380 project = Project.find(1)
381 assert_not_nil project.versions.detect {|v| v.completed? && v.status == 'open'}
382 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
383 project.close_completed_versions
384 project.reload
385 assert_nil project.versions.detect {|v| v.completed? && v.status != 'closed'}
386 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
387 end
377 388
378 389 context "Project#copy" do
379 390 setup do
380 391 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
381 392 Project.destroy_all :identifier => "copy-test"
382 393 @source_project = Project.find(2)
383 394 @project = Project.new(:name => 'Copy Test', :identifier => 'copy-test')
384 395 @project.trackers = @source_project.trackers
385 396 @project.enabled_module_names = @source_project.enabled_modules.collect(&:name)
386 397 end
387 398
388 399 should "copy issues" do
389 400 assert @project.valid?
390 401 assert @project.issues.empty?
391 402 assert @project.copy(@source_project)
392 403
393 404 assert_equal @source_project.issues.size, @project.issues.size
394 405 @project.issues.each do |issue|
395 406 assert issue.valid?
396 407 assert ! issue.assigned_to.blank?
397 408 assert_equal @project, issue.project
398 409 end
399 410 end
400 411
401 412 should "change the new issues to use the copied version" do
402 413 assigned_version = Version.generate!(:name => "Assigned Issues")
403 414 @source_project.versions << assigned_version
404 415 assert_equal 1, @source_project.versions.size
405 416 @source_project.issues << Issue.generate!(:fixed_version_id => assigned_version.id,
406 417 :subject => "change the new issues to use the copied version",
407 418 :tracker_id => 1,
408 419 :project_id => @source_project.id)
409 420
410 421 assert @project.copy(@source_project)
411 422 @project.reload
412 423 copied_issue = @project.issues.first(:conditions => {:subject => "change the new issues to use the copied version"})
413 424
414 425 assert copied_issue
415 426 assert copied_issue.fixed_version
416 427 assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name
417 428 assert_not_equal assigned_version.id, copied_issue.fixed_version.id # Different record
418 429 end
419 430
420 431 should "copy members" do
421 432 assert @project.valid?
422 433 assert @project.members.empty?
423 434 assert @project.copy(@source_project)
424 435
425 436 assert_equal @source_project.members.size, @project.members.size
426 437 @project.members.each do |member|
427 438 assert member
428 439 assert_equal @project, member.project
429 440 end
430 441 end
431 442
432 443 should "copy project specific queries" do
433 444 assert @project.valid?
434 445 assert @project.queries.empty?
435 446 assert @project.copy(@source_project)
436 447
437 448 assert_equal @source_project.queries.size, @project.queries.size
438 449 @project.queries.each do |query|
439 450 assert query
440 451 assert_equal @project, query.project
441 452 end
442 453 end
443 454
444 455 should "copy versions" do
445 456 @source_project.versions << Version.generate!
446 457 @source_project.versions << Version.generate!
447 458
448 459 assert @project.versions.empty?
449 460 assert @project.copy(@source_project)
450 461
451 462 assert_equal @source_project.versions.size, @project.versions.size
452 463 @project.versions.each do |version|
453 464 assert version
454 465 assert_equal @project, version.project
455 466 end
456 467 end
457 468
458 469 should "copy wiki" do
459 470 assert_difference 'Wiki.count' do
460 471 assert @project.copy(@source_project)
461 472 end
462 473
463 474 assert @project.wiki
464 475 assert_not_equal @source_project.wiki, @project.wiki
465 476 assert_equal "Start page", @project.wiki.start_page
466 477 end
467 478
468 479 should "copy wiki pages and content" do
469 480 assert @project.copy(@source_project)
470 481
471 482 assert @project.wiki
472 483 assert_equal 1, @project.wiki.pages.length
473 484
474 485 @project.wiki.pages.each do |wiki_page|
475 486 assert wiki_page.content
476 487 assert !@source_project.wiki.pages.include?(wiki_page)
477 488 end
478 489 end
479 490
480 491 should "copy custom fields"
481 492
482 493 should "copy issue categories" do
483 494 assert @project.copy(@source_project)
484 495
485 496 assert_equal 2, @project.issue_categories.size
486 497 @project.issue_categories.each do |issue_category|
487 498 assert !@source_project.issue_categories.include?(issue_category)
488 499 end
489 500 end
490 501
491 502 should "copy boards" do
492 503 assert @project.copy(@source_project)
493 504
494 505 assert_equal 1, @project.boards.size
495 506 @project.boards.each do |board|
496 507 assert !@source_project.boards.include?(board)
497 508 end
498 509 end
499 510
500 511 should "change the new issues to use the copied issue categories" do
501 512 issue = Issue.find(4)
502 513 issue.update_attribute(:category_id, 3)
503 514
504 515 assert @project.copy(@source_project)
505 516
506 517 @project.issues.each do |issue|
507 518 assert issue.category
508 519 assert_equal "Stock management", issue.category.name # Same name
509 520 assert_not_equal IssueCategory.find(3), issue.category # Different record
510 521 end
511 522 end
512 523
513 524 should "limit copy with :only option" do
514 525 assert @project.members.empty?
515 526 assert @project.issue_categories.empty?
516 527 assert @source_project.issues.any?
517 528
518 529 assert @project.copy(@source_project, :only => ['members', 'issue_categories'])
519 530
520 531 assert @project.members.any?
521 532 assert @project.issue_categories.any?
522 533 assert @project.issues.empty?
523 534 end
524 535
525 536 should "copy issue relations"
526 537 should "link issue relations if cross project issue relations are valid"
527 538
528 539 end
529 540
530 541 end
General Comments 0
You need to be logged in to leave comments. Login now