##// END OF EJS Templates
Cross-project gantt and calendar (#1157)....
Jean-Philippe Lang -
r2086:50794b08a925
parent child
Show More
@@ -1,493 +1,493
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class IssuesController < ApplicationController
18 class IssuesController < ApplicationController
19 menu_item :new_issue, :only => :new
19 menu_item :new_issue, :only => :new
20
20
21 before_filter :find_issue, :only => [:show, :edit, :reply, :destroy_attachment]
21 before_filter :find_issue, :only => [:show, :edit, :reply, :destroy_attachment]
22 before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
22 before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
23 before_filter :find_project, :only => [:new, :update_form, :preview, :gantt, :calendar]
23 before_filter :find_project, :only => [:new, :update_form, :preview]
24 before_filter :authorize, :except => [:index, :changes, :preview, :update_form, :context_menu]
24 before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu]
25 before_filter :find_optional_project, :only => [:index, :changes]
25 before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar]
26 accept_key_auth :index, :changes
26 accept_key_auth :index, :changes
27
27
28 helper :journals
28 helper :journals
29 helper :projects
29 helper :projects
30 include ProjectsHelper
30 include ProjectsHelper
31 helper :custom_fields
31 helper :custom_fields
32 include CustomFieldsHelper
32 include CustomFieldsHelper
33 helper :ifpdf
33 helper :ifpdf
34 include IfpdfHelper
34 include IfpdfHelper
35 helper :issue_relations
35 helper :issue_relations
36 include IssueRelationsHelper
36 include IssueRelationsHelper
37 helper :watchers
37 helper :watchers
38 include WatchersHelper
38 include WatchersHelper
39 helper :attachments
39 helper :attachments
40 include AttachmentsHelper
40 include AttachmentsHelper
41 helper :queries
41 helper :queries
42 helper :sort
42 helper :sort
43 include SortHelper
43 include SortHelper
44 include IssuesHelper
44 include IssuesHelper
45 helper :timelog
45 helper :timelog
46
46
47 def index
47 def index
48 sort_init "#{Issue.table_name}.id", "desc"
48 sort_init "#{Issue.table_name}.id", "desc"
49 sort_update
49 sort_update
50 retrieve_query
50 retrieve_query
51 if @query.valid?
51 if @query.valid?
52 limit = per_page_option
52 limit = per_page_option
53 respond_to do |format|
53 respond_to do |format|
54 format.html { }
54 format.html { }
55 format.atom { }
55 format.atom { }
56 format.csv { limit = Setting.issues_export_limit.to_i }
56 format.csv { limit = Setting.issues_export_limit.to_i }
57 format.pdf { limit = Setting.issues_export_limit.to_i }
57 format.pdf { limit = Setting.issues_export_limit.to_i }
58 end
58 end
59 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
59 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
60 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
60 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
61 @issues = Issue.find :all, :order => sort_clause,
61 @issues = Issue.find :all, :order => sort_clause,
62 :include => [ :assigned_to, :status, :tracker, :project, :priority, :category, :fixed_version ],
62 :include => [ :assigned_to, :status, :tracker, :project, :priority, :category, :fixed_version ],
63 :conditions => @query.statement,
63 :conditions => @query.statement,
64 :limit => limit,
64 :limit => limit,
65 :offset => @issue_pages.current.offset
65 :offset => @issue_pages.current.offset
66 respond_to do |format|
66 respond_to do |format|
67 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
67 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
68 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
68 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
69 format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
69 format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
70 format.pdf { send_data(render(:template => 'issues/index.rfpdf', :layout => false), :type => 'application/pdf', :filename => 'export.pdf') }
70 format.pdf { send_data(render(:template => 'issues/index.rfpdf', :layout => false), :type => 'application/pdf', :filename => 'export.pdf') }
71 end
71 end
72 else
72 else
73 # Send html if the query is not valid
73 # Send html if the query is not valid
74 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
74 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
75 end
75 end
76 rescue ActiveRecord::RecordNotFound
76 rescue ActiveRecord::RecordNotFound
77 render_404
77 render_404
78 end
78 end
79
79
80 def changes
80 def changes
81 sort_init "#{Issue.table_name}.id", "desc"
81 sort_init "#{Issue.table_name}.id", "desc"
82 sort_update
82 sort_update
83 retrieve_query
83 retrieve_query
84 if @query.valid?
84 if @query.valid?
85 @journals = Journal.find :all, :include => [ :details, :user, {:issue => [:project, :author, :tracker, :status]} ],
85 @journals = Journal.find :all, :include => [ :details, :user, {:issue => [:project, :author, :tracker, :status]} ],
86 :conditions => @query.statement,
86 :conditions => @query.statement,
87 :limit => 25,
87 :limit => 25,
88 :order => "#{Journal.table_name}.created_on DESC"
88 :order => "#{Journal.table_name}.created_on DESC"
89 end
89 end
90 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
90 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
91 render :layout => false, :content_type => 'application/atom+xml'
91 render :layout => false, :content_type => 'application/atom+xml'
92 rescue ActiveRecord::RecordNotFound
92 rescue ActiveRecord::RecordNotFound
93 render_404
93 render_404
94 end
94 end
95
95
96 def show
96 def show
97 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
97 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
98 @journals.each_with_index {|j,i| j.indice = i+1}
98 @journals.each_with_index {|j,i| j.indice = i+1}
99 @journals.reverse! if User.current.wants_comments_in_reverse_order?
99 @journals.reverse! if User.current.wants_comments_in_reverse_order?
100 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
100 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
101 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
101 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
102 @priorities = Enumeration::get_values('IPRI')
102 @priorities = Enumeration::get_values('IPRI')
103 @time_entry = TimeEntry.new
103 @time_entry = TimeEntry.new
104 respond_to do |format|
104 respond_to do |format|
105 format.html { render :template => 'issues/show.rhtml' }
105 format.html { render :template => 'issues/show.rhtml' }
106 format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
106 format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
107 format.pdf { send_data(render(:template => 'issues/show.rfpdf', :layout => false), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
107 format.pdf { send_data(render(:template => 'issues/show.rfpdf', :layout => false), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
108 end
108 end
109 end
109 end
110
110
111 # Add a new issue
111 # Add a new issue
112 # The new issue will be created from an existing one if copy_from parameter is given
112 # The new issue will be created from an existing one if copy_from parameter is given
113 def new
113 def new
114 @issue = Issue.new
114 @issue = Issue.new
115 @issue.copy_from(params[:copy_from]) if params[:copy_from]
115 @issue.copy_from(params[:copy_from]) if params[:copy_from]
116 @issue.project = @project
116 @issue.project = @project
117 # Tracker must be set before custom field values
117 # Tracker must be set before custom field values
118 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
118 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
119 if @issue.tracker.nil?
119 if @issue.tracker.nil?
120 flash.now[:error] = 'No tracker is associated to this project. Please check the Project settings.'
120 flash.now[:error] = 'No tracker is associated to this project. Please check the Project settings.'
121 render :nothing => true, :layout => true
121 render :nothing => true, :layout => true
122 return
122 return
123 end
123 end
124 @issue.attributes = params[:issue]
124 @issue.attributes = params[:issue]
125 @issue.author = User.current
125 @issue.author = User.current
126
126
127 default_status = IssueStatus.default
127 default_status = IssueStatus.default
128 unless default_status
128 unless default_status
129 flash.now[:error] = 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
129 flash.now[:error] = 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
130 render :nothing => true, :layout => true
130 render :nothing => true, :layout => true
131 return
131 return
132 end
132 end
133 @issue.status = default_status
133 @issue.status = default_status
134 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)).uniq
134 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)).uniq
135
135
136 if request.get? || request.xhr?
136 if request.get? || request.xhr?
137 @issue.start_date ||= Date.today
137 @issue.start_date ||= Date.today
138 else
138 else
139 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
139 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
140 # Check that the user is allowed to apply the requested status
140 # Check that the user is allowed to apply the requested status
141 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
141 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
142 if @issue.save
142 if @issue.save
143 attach_files(@issue, params[:attachments])
143 attach_files(@issue, params[:attachments])
144 flash[:notice] = l(:notice_successful_create)
144 flash[:notice] = l(:notice_successful_create)
145 Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
145 Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
146 redirect_to :controller => 'issues', :action => 'show', :id => @issue
146 redirect_to :controller => 'issues', :action => 'show', :id => @issue
147 return
147 return
148 end
148 end
149 end
149 end
150 @priorities = Enumeration::get_values('IPRI')
150 @priorities = Enumeration::get_values('IPRI')
151 render :layout => !request.xhr?
151 render :layout => !request.xhr?
152 end
152 end
153
153
154 # Attributes that can be updated on workflow transition (without :edit permission)
154 # Attributes that can be updated on workflow transition (without :edit permission)
155 # TODO: make it configurable (at least per role)
155 # TODO: make it configurable (at least per role)
156 UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
156 UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
157
157
158 def edit
158 def edit
159 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
159 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
160 @priorities = Enumeration::get_values('IPRI')
160 @priorities = Enumeration::get_values('IPRI')
161 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
161 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
162 @time_entry = TimeEntry.new
162 @time_entry = TimeEntry.new
163
163
164 @notes = params[:notes]
164 @notes = params[:notes]
165 journal = @issue.init_journal(User.current, @notes)
165 journal = @issue.init_journal(User.current, @notes)
166 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
166 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
167 if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
167 if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
168 attrs = params[:issue].dup
168 attrs = params[:issue].dup
169 attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
169 attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
170 attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
170 attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
171 @issue.attributes = attrs
171 @issue.attributes = attrs
172 end
172 end
173
173
174 if request.post?
174 if request.post?
175 @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
175 @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
176 @time_entry.attributes = params[:time_entry]
176 @time_entry.attributes = params[:time_entry]
177 attachments = attach_files(@issue, params[:attachments])
177 attachments = attach_files(@issue, params[:attachments])
178 attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
178 attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
179
179
180 call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
180 call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
181
181
182 if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.save
182 if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.save
183 # Log spend time
183 # Log spend time
184 if current_role.allowed_to?(:log_time)
184 if current_role.allowed_to?(:log_time)
185 @time_entry.save
185 @time_entry.save
186 end
186 end
187 if !journal.new_record?
187 if !journal.new_record?
188 # Only send notification if something was actually changed
188 # Only send notification if something was actually changed
189 flash[:notice] = l(:notice_successful_update)
189 flash[:notice] = l(:notice_successful_update)
190 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
190 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
191 end
191 end
192 redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
192 redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
193 end
193 end
194 end
194 end
195 rescue ActiveRecord::StaleObjectError
195 rescue ActiveRecord::StaleObjectError
196 # Optimistic locking exception
196 # Optimistic locking exception
197 flash.now[:error] = l(:notice_locking_conflict)
197 flash.now[:error] = l(:notice_locking_conflict)
198 end
198 end
199
199
200 def reply
200 def reply
201 journal = Journal.find(params[:journal_id]) if params[:journal_id]
201 journal = Journal.find(params[:journal_id]) if params[:journal_id]
202 if journal
202 if journal
203 user = journal.user
203 user = journal.user
204 text = journal.notes
204 text = journal.notes
205 else
205 else
206 user = @issue.author
206 user = @issue.author
207 text = @issue.description
207 text = @issue.description
208 end
208 end
209 content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> "
209 content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> "
210 content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
210 content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
211 render(:update) { |page|
211 render(:update) { |page|
212 page.<< "$('notes').value = \"#{content}\";"
212 page.<< "$('notes').value = \"#{content}\";"
213 page.show 'update'
213 page.show 'update'
214 page << "Form.Element.focus('notes');"
214 page << "Form.Element.focus('notes');"
215 page << "Element.scrollTo('update');"
215 page << "Element.scrollTo('update');"
216 page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
216 page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
217 }
217 }
218 end
218 end
219
219
220 # Bulk edit a set of issues
220 # Bulk edit a set of issues
221 def bulk_edit
221 def bulk_edit
222 if request.post?
222 if request.post?
223 status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
223 status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
224 priority = params[:priority_id].blank? ? nil : Enumeration.find_by_id(params[:priority_id])
224 priority = params[:priority_id].blank? ? nil : Enumeration.find_by_id(params[:priority_id])
225 assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id])
225 assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id])
226 category = (params[:category_id].blank? || params[:category_id] == 'none') ? nil : @project.issue_categories.find_by_id(params[:category_id])
226 category = (params[:category_id].blank? || params[:category_id] == 'none') ? nil : @project.issue_categories.find_by_id(params[:category_id])
227 fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.versions.find_by_id(params[:fixed_version_id])
227 fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.versions.find_by_id(params[:fixed_version_id])
228
228
229 unsaved_issue_ids = []
229 unsaved_issue_ids = []
230 @issues.each do |issue|
230 @issues.each do |issue|
231 journal = issue.init_journal(User.current, params[:notes])
231 journal = issue.init_journal(User.current, params[:notes])
232 issue.priority = priority if priority
232 issue.priority = priority if priority
233 issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
233 issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
234 issue.category = category if category || params[:category_id] == 'none'
234 issue.category = category if category || params[:category_id] == 'none'
235 issue.fixed_version = fixed_version if fixed_version || params[:fixed_version_id] == 'none'
235 issue.fixed_version = fixed_version if fixed_version || params[:fixed_version_id] == 'none'
236 issue.start_date = params[:start_date] unless params[:start_date].blank?
236 issue.start_date = params[:start_date] unless params[:start_date].blank?
237 issue.due_date = params[:due_date] unless params[:due_date].blank?
237 issue.due_date = params[:due_date] unless params[:due_date].blank?
238 issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
238 issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
239 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
239 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
240 # Don't save any change to the issue if the user is not authorized to apply the requested status
240 # Don't save any change to the issue if the user is not authorized to apply the requested status
241 if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
241 if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
242 # Send notification for each issue (if changed)
242 # Send notification for each issue (if changed)
243 Mailer.deliver_issue_edit(journal) if journal.details.any? && Setting.notified_events.include?('issue_updated')
243 Mailer.deliver_issue_edit(journal) if journal.details.any? && Setting.notified_events.include?('issue_updated')
244 else
244 else
245 # Keep unsaved issue ids to display them in flash error
245 # Keep unsaved issue ids to display them in flash error
246 unsaved_issue_ids << issue.id
246 unsaved_issue_ids << issue.id
247 end
247 end
248 end
248 end
249 if unsaved_issue_ids.empty?
249 if unsaved_issue_ids.empty?
250 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
250 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
251 else
251 else
252 flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #'))
252 flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #'))
253 end
253 end
254 redirect_to(params[:back_to] || {:controller => 'issues', :action => 'index', :project_id => @project})
254 redirect_to(params[:back_to] || {:controller => 'issues', :action => 'index', :project_id => @project})
255 return
255 return
256 end
256 end
257 # Find potential statuses the user could be allowed to switch issues to
257 # Find potential statuses the user could be allowed to switch issues to
258 @available_statuses = Workflow.find(:all, :include => :new_status,
258 @available_statuses = Workflow.find(:all, :include => :new_status,
259 :conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq.sort
259 :conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq.sort
260 end
260 end
261
261
262 def move
262 def move
263 @allowed_projects = []
263 @allowed_projects = []
264 # find projects to which the user is allowed to move the issue
264 # find projects to which the user is allowed to move the issue
265 if User.current.admin?
265 if User.current.admin?
266 # admin is allowed to move issues to any active (visible) project
266 # admin is allowed to move issues to any active (visible) project
267 @allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current), :order => 'name')
267 @allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current), :order => 'name')
268 else
268 else
269 User.current.memberships.each {|m| @allowed_projects << m.project if m.role.allowed_to?(:move_issues)}
269 User.current.memberships.each {|m| @allowed_projects << m.project if m.role.allowed_to?(:move_issues)}
270 end
270 end
271 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
271 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
272 @target_project ||= @project
272 @target_project ||= @project
273 @trackers = @target_project.trackers
273 @trackers = @target_project.trackers
274 if request.post?
274 if request.post?
275 new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
275 new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
276 unsaved_issue_ids = []
276 unsaved_issue_ids = []
277 @issues.each do |issue|
277 @issues.each do |issue|
278 issue.init_journal(User.current)
278 issue.init_journal(User.current)
279 unsaved_issue_ids << issue.id unless issue.move_to(@target_project, new_tracker)
279 unsaved_issue_ids << issue.id unless issue.move_to(@target_project, new_tracker)
280 end
280 end
281 if unsaved_issue_ids.empty?
281 if unsaved_issue_ids.empty?
282 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
282 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
283 else
283 else
284 flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #'))
284 flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #'))
285 end
285 end
286 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
286 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
287 return
287 return
288 end
288 end
289 render :layout => false if request.xhr?
289 render :layout => false if request.xhr?
290 end
290 end
291
291
292 def destroy
292 def destroy
293 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
293 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
294 if @hours > 0
294 if @hours > 0
295 case params[:todo]
295 case params[:todo]
296 when 'destroy'
296 when 'destroy'
297 # nothing to do
297 # nothing to do
298 when 'nullify'
298 when 'nullify'
299 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
299 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
300 when 'reassign'
300 when 'reassign'
301 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
301 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
302 if reassign_to.nil?
302 if reassign_to.nil?
303 flash.now[:error] = l(:error_issue_not_found_in_project)
303 flash.now[:error] = l(:error_issue_not_found_in_project)
304 return
304 return
305 else
305 else
306 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
306 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
307 end
307 end
308 else
308 else
309 # display the destroy form
309 # display the destroy form
310 return
310 return
311 end
311 end
312 end
312 end
313 @issues.each(&:destroy)
313 @issues.each(&:destroy)
314 redirect_to :action => 'index', :project_id => @project
314 redirect_to :action => 'index', :project_id => @project
315 end
315 end
316
316
317 def destroy_attachment
317 def destroy_attachment
318 a = @issue.attachments.find(params[:attachment_id])
318 a = @issue.attachments.find(params[:attachment_id])
319 a.destroy
319 a.destroy
320 journal = @issue.init_journal(User.current)
320 journal = @issue.init_journal(User.current)
321 journal.details << JournalDetail.new(:property => 'attachment',
321 journal.details << JournalDetail.new(:property => 'attachment',
322 :prop_key => a.id,
322 :prop_key => a.id,
323 :old_value => a.filename)
323 :old_value => a.filename)
324 journal.save
324 journal.save
325 redirect_to :action => 'show', :id => @issue
325 redirect_to :action => 'show', :id => @issue
326 end
326 end
327
327
328 def gantt
328 def gantt
329 @gantt = Redmine::Helpers::Gantt.new(params)
329 @gantt = Redmine::Helpers::Gantt.new(params)
330 retrieve_query
330 retrieve_query
331 if @query.valid?
331 if @query.valid?
332 events = []
332 events = []
333 # Issues that have start and due dates
333 # Issues that have start and due dates
334 events += Issue.find(:all,
334 events += Issue.find(:all,
335 :order => "start_date, due_date",
335 :order => "start_date, due_date",
336 :include => [:tracker, :status, :assigned_to, :priority, :project],
336 :include => [:tracker, :status, :assigned_to, :priority, :project],
337 :conditions => ["(#{@query.statement}) AND (((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
337 :conditions => ["(#{@query.statement}) AND (((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
338 )
338 )
339 # Issues that don't have a due date but that are assigned to a version with a date
339 # Issues that don't have a due date but that are assigned to a version with a date
340 events += Issue.find(:all,
340 events += Issue.find(:all,
341 :order => "start_date, effective_date",
341 :order => "start_date, effective_date",
342 :include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version],
342 :include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version],
343 :conditions => ["(#{@query.statement}) AND (((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date<? and effective_date>?)) and start_date is not null and due_date is null and effective_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
343 :conditions => ["(#{@query.statement}) AND (((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date<? and effective_date>?)) and start_date is not null and due_date is null and effective_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
344 )
344 )
345 # Versions
345 # Versions
346 events += Version.find(:all, :include => :project,
346 events += Version.find(:all, :include => :project,
347 :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to])
347 :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to])
348
348
349 @gantt.events = events
349 @gantt.events = events
350 end
350 end
351
351
352 respond_to do |format|
352 respond_to do |format|
353 format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? }
353 format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? }
354 format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png") } if @gantt.respond_to?('to_image')
354 format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png") } if @gantt.respond_to?('to_image')
355 format.pdf { send_data(render(:template => "issues/gantt.rfpdf", :layout => false), :type => 'application/pdf', :filename => "#{@project.identifier}-gantt.pdf") }
355 format.pdf { send_data(render(:template => "issues/gantt.rfpdf", :layout => false), :type => 'application/pdf', :filename => "#{@project.nil? ? '' : "#{@project.identifier}-" }gantt.pdf") }
356 end
356 end
357 end
357 end
358
358
359 def calendar
359 def calendar
360 if params[:year] and params[:year].to_i > 1900
360 if params[:year] and params[:year].to_i > 1900
361 @year = params[:year].to_i
361 @year = params[:year].to_i
362 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
362 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
363 @month = params[:month].to_i
363 @month = params[:month].to_i
364 end
364 end
365 end
365 end
366 @year ||= Date.today.year
366 @year ||= Date.today.year
367 @month ||= Date.today.month
367 @month ||= Date.today.month
368
368
369 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
369 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
370 retrieve_query
370 retrieve_query
371 if @query.valid?
371 if @query.valid?
372 events = []
372 events = []
373 events += Issue.find(:all,
373 events += Issue.find(:all,
374 :include => [:tracker, :status, :assigned_to, :priority, :project],
374 :include => [:tracker, :status, :assigned_to, :priority, :project],
375 :conditions => ["(#{@query.statement}) AND ((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
375 :conditions => ["(#{@query.statement}) AND ((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
376 )
376 )
377 events += Version.find(:all, :include => :project,
377 events += Version.find(:all, :include => :project,
378 :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
378 :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
379
379
380 @calendar.events = events
380 @calendar.events = events
381 end
381 end
382
382
383 render :layout => false if request.xhr?
383 render :layout => false if request.xhr?
384 end
384 end
385
385
386 def context_menu
386 def context_menu
387 @issues = Issue.find_all_by_id(params[:ids], :include => :project)
387 @issues = Issue.find_all_by_id(params[:ids], :include => :project)
388 if (@issues.size == 1)
388 if (@issues.size == 1)
389 @issue = @issues.first
389 @issue = @issues.first
390 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
390 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
391 end
391 end
392 projects = @issues.collect(&:project).compact.uniq
392 projects = @issues.collect(&:project).compact.uniq
393 @project = projects.first if projects.size == 1
393 @project = projects.first if projects.size == 1
394
394
395 @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
395 @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
396 :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
396 :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
397 :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))),
397 :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))),
398 :move => (@project && User.current.allowed_to?(:move_issues, @project)),
398 :move => (@project && User.current.allowed_to?(:move_issues, @project)),
399 :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
399 :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
400 :delete => (@project && User.current.allowed_to?(:delete_issues, @project))
400 :delete => (@project && User.current.allowed_to?(:delete_issues, @project))
401 }
401 }
402 if @project
402 if @project
403 @assignables = @project.assignable_users
403 @assignables = @project.assignable_users
404 @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
404 @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
405 end
405 end
406
406
407 @priorities = Enumeration.get_values('IPRI').reverse
407 @priorities = Enumeration.get_values('IPRI').reverse
408 @statuses = IssueStatus.find(:all, :order => 'position')
408 @statuses = IssueStatus.find(:all, :order => 'position')
409 @back = request.env['HTTP_REFERER']
409 @back = request.env['HTTP_REFERER']
410
410
411 render :layout => false
411 render :layout => false
412 end
412 end
413
413
414 def update_form
414 def update_form
415 @issue = Issue.new(params[:issue])
415 @issue = Issue.new(params[:issue])
416 render :action => :new, :layout => false
416 render :action => :new, :layout => false
417 end
417 end
418
418
419 def preview
419 def preview
420 @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
420 @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
421 @attachements = @issue.attachments if @issue
421 @attachements = @issue.attachments if @issue
422 @text = params[:notes] || (params[:issue] ? params[:issue][:description] : nil)
422 @text = params[:notes] || (params[:issue] ? params[:issue][:description] : nil)
423 render :partial => 'common/preview'
423 render :partial => 'common/preview'
424 end
424 end
425
425
426 private
426 private
427 def find_issue
427 def find_issue
428 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
428 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
429 @project = @issue.project
429 @project = @issue.project
430 rescue ActiveRecord::RecordNotFound
430 rescue ActiveRecord::RecordNotFound
431 render_404
431 render_404
432 end
432 end
433
433
434 # Filter for bulk operations
434 # Filter for bulk operations
435 def find_issues
435 def find_issues
436 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
436 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
437 raise ActiveRecord::RecordNotFound if @issues.empty?
437 raise ActiveRecord::RecordNotFound if @issues.empty?
438 projects = @issues.collect(&:project).compact.uniq
438 projects = @issues.collect(&:project).compact.uniq
439 if projects.size == 1
439 if projects.size == 1
440 @project = projects.first
440 @project = projects.first
441 else
441 else
442 # TODO: let users bulk edit/move/destroy issues from different projects
442 # TODO: let users bulk edit/move/destroy issues from different projects
443 render_error 'Can not bulk edit/move/destroy issues from different projects' and return false
443 render_error 'Can not bulk edit/move/destroy issues from different projects' and return false
444 end
444 end
445 rescue ActiveRecord::RecordNotFound
445 rescue ActiveRecord::RecordNotFound
446 render_404
446 render_404
447 end
447 end
448
448
449 def find_project
449 def find_project
450 @project = Project.find(params[:project_id])
450 @project = Project.find(params[:project_id])
451 rescue ActiveRecord::RecordNotFound
451 rescue ActiveRecord::RecordNotFound
452 render_404
452 render_404
453 end
453 end
454
454
455 def find_optional_project
455 def find_optional_project
456 return true unless params[:project_id]
456 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
457 @project = Project.find(params[:project_id])
457 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
458 authorize
458 allowed ? true : deny_access
459 rescue ActiveRecord::RecordNotFound
459 rescue ActiveRecord::RecordNotFound
460 render_404
460 render_404
461 end
461 end
462
462
463 # Retrieve query from session or build a new query
463 # Retrieve query from session or build a new query
464 def retrieve_query
464 def retrieve_query
465 if !params[:query_id].blank?
465 if !params[:query_id].blank?
466 cond = "project_id IS NULL"
466 cond = "project_id IS NULL"
467 cond << " OR project_id = #{@project.id}" if @project
467 cond << " OR project_id = #{@project.id}" if @project
468 @query = Query.find(params[:query_id], :conditions => cond)
468 @query = Query.find(params[:query_id], :conditions => cond)
469 @query.project = @project
469 @query.project = @project
470 session[:query] = {:id => @query.id, :project_id => @query.project_id}
470 session[:query] = {:id => @query.id, :project_id => @query.project_id}
471 else
471 else
472 if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
472 if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
473 # Give it a name, required to be valid
473 # Give it a name, required to be valid
474 @query = Query.new(:name => "_")
474 @query = Query.new(:name => "_")
475 @query.project = @project
475 @query.project = @project
476 if params[:fields] and params[:fields].is_a? Array
476 if params[:fields] and params[:fields].is_a? Array
477 params[:fields].each do |field|
477 params[:fields].each do |field|
478 @query.add_filter(field, params[:operators][field], params[:values][field])
478 @query.add_filter(field, params[:operators][field], params[:values][field])
479 end
479 end
480 else
480 else
481 @query.available_filters.keys.each do |field|
481 @query.available_filters.keys.each do |field|
482 @query.add_short_filter(field, params[field]) if params[field]
482 @query.add_short_filter(field, params[field]) if params[field]
483 end
483 end
484 end
484 end
485 session[:query] = {:project_id => @query.project_id, :filters => @query.filters}
485 session[:query] = {:project_id => @query.project_id, :filters => @query.filters}
486 else
486 else
487 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
487 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
488 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters])
488 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters])
489 @query.project = @project
489 @query.project = @project
490 end
490 end
491 end
491 end
492 end
492 end
493 end
493 end
@@ -1,24 +1,23
1 <h3><%= l(:label_issue_plural) %></h3>
1 <h3><%= l(:label_issue_plural) %></h3>
2 <%= link_to l(:label_issue_view_all), { :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 } %><br />
2 <%= link_to l(:label_issue_view_all), { :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 } %><br />
3 <% if @project %>
3 <% if @project %>
4 <%= link_to l(:field_summary), :controller => 'reports', :action => 'issue_report', :id => @project %><br />
4 <%= link_to l(:field_summary), :controller => 'reports', :action => 'issue_report', :id => @project %><br />
5 <%= link_to l(:label_change_log), :controller => 'projects', :action => 'changelog', :id => @project %>
5 <%= link_to l(:label_change_log), :controller => 'projects', :action => 'changelog', :id => @project %>
6 <% end %>
6
7
7 <% planning_links = []
8 <% planning_links = []
8 planning_links << link_to_if_authorized(l(:label_calendar), :action => 'calendar', :project_id => @project)
9 planning_links << link_to(l(:label_calendar), :action => 'calendar', :project_id => @project) if User.current.allowed_to?(:view_calendar, @project, :global => true)
9 planning_links << link_to_if_authorized(l(:label_gantt), :action => 'gantt', :project_id => @project)
10 planning_links << link_to(l(:label_gantt), :action => 'gantt', :project_id => @project) if User.current.allowed_to?(:view_gantt, @project, :global => true)
10 planning_links.compact!
11 %>
11 unless planning_links.empty? %>
12 <% unless planning_links.empty? %>
12 <h3><%= l(:label_planning) %></h3>
13 <h3><%= l(:label_planning) %></h3>
13 <p><%= planning_links.join(' | ') %></p>
14 <p><%= planning_links.join(' | ') %></p>
14 <% end %>
15 <% end %>
15
16
16 <% end %>
17
18 <% unless sidebar_queries.empty? -%>
17 <% unless sidebar_queries.empty? -%>
19 <h3><%= l(:label_query_plural) %></h3>
18 <h3><%= l(:label_query_plural) %></h3>
20
19
21 <% sidebar_queries.each do |query| -%>
20 <% sidebar_queries.each do |query| -%>
22 <%= link_to query.name, :controller => 'issues', :action => 'index', :project_id => @project, :query_id => query %><br />
21 <%= link_to query.name, :controller => 'issues', :action => 'index', :project_id => @project, :query_id => query %><br />
23 <% end -%>
22 <% end -%>
24 <% end -%>
23 <% end -%>
@@ -1,188 +1,188
1 <%
1 <%
2 pdf=IfpdfHelper::IFPDF.new(current_language)
2 pdf=IfpdfHelper::IFPDF.new(current_language)
3 pdf.SetTitle("#{@project.name} - #{l(:label_gantt)}")
3 pdf.SetTitle("#{l(:label_gantt)} #{@project}")
4 pdf.AliasNbPages
4 pdf.AliasNbPages
5 pdf.footer_date = format_date(Date.today)
5 pdf.footer_date = format_date(Date.today)
6 pdf.AddPage("L")
6 pdf.AddPage("L")
7 pdf.SetFontStyle('B',12)
7 pdf.SetFontStyle('B',12)
8 pdf.SetX(15)
8 pdf.SetX(15)
9 pdf.Cell(70, 20, @project.name)
9 pdf.Cell(70, 20, @project.to_s)
10 pdf.Ln
10 pdf.Ln
11 pdf.SetFontStyle('B',9)
11 pdf.SetFontStyle('B',9)
12
12
13 subject_width = 70
13 subject_width = 70
14 header_heigth = 5
14 header_heigth = 5
15
15
16 headers_heigth = header_heigth
16 headers_heigth = header_heigth
17 show_weeks = false
17 show_weeks = false
18 show_days = false
18 show_days = false
19
19
20 if @gantt.months < 7
20 if @gantt.months < 7
21 show_weeks = true
21 show_weeks = true
22 headers_heigth = 2*header_heigth
22 headers_heigth = 2*header_heigth
23 if @gantt.months < 3
23 if @gantt.months < 3
24 show_days = true
24 show_days = true
25 headers_heigth = 3*header_heigth
25 headers_heigth = 3*header_heigth
26 end
26 end
27 end
27 end
28
28
29 g_width = 210
29 g_width = 210
30 zoom = (g_width) / (@gantt.date_to - @gantt.date_from + 1)
30 zoom = (g_width) / (@gantt.date_to - @gantt.date_from + 1)
31 g_height = 120
31 g_height = 120
32 t_height = g_height + headers_heigth
32 t_height = g_height + headers_heigth
33
33
34 y_start = pdf.GetY
34 y_start = pdf.GetY
35
35
36
36
37 #
37 #
38 # Months headers
38 # Months headers
39 #
39 #
40 month_f = @gantt.date_from
40 month_f = @gantt.date_from
41 left = subject_width
41 left = subject_width
42 height = header_heigth
42 height = header_heigth
43 @gantt.months.times do
43 @gantt.months.times do
44 width = ((month_f >> 1) - month_f) * zoom
44 width = ((month_f >> 1) - month_f) * zoom
45 pdf.SetY(y_start)
45 pdf.SetY(y_start)
46 pdf.SetX(left)
46 pdf.SetX(left)
47 pdf.Cell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C")
47 pdf.Cell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C")
48 left = left + width
48 left = left + width
49 month_f = month_f >> 1
49 month_f = month_f >> 1
50 end
50 end
51
51
52 #
52 #
53 # Weeks headers
53 # Weeks headers
54 #
54 #
55 if show_weeks
55 if show_weeks
56 left = subject_width
56 left = subject_width
57 height = header_heigth
57 height = header_heigth
58 if @gantt.date_from.cwday == 1
58 if @gantt.date_from.cwday == 1
59 # @gantt.date_from is monday
59 # @gantt.date_from is monday
60 week_f = @gantt.date_from
60 week_f = @gantt.date_from
61 else
61 else
62 # find next monday after @gantt.date_from
62 # find next monday after @gantt.date_from
63 week_f = @gantt.date_from + (7 - @gantt.date_from.cwday + 1)
63 week_f = @gantt.date_from + (7 - @gantt.date_from.cwday + 1)
64 width = (7 - @gantt.date_from.cwday + 1) * zoom-1
64 width = (7 - @gantt.date_from.cwday + 1) * zoom-1
65 pdf.SetY(y_start + header_heigth)
65 pdf.SetY(y_start + header_heigth)
66 pdf.SetX(left)
66 pdf.SetX(left)
67 pdf.Cell(width + 1, height, "", "LTR")
67 pdf.Cell(width + 1, height, "", "LTR")
68 left = left + width+1
68 left = left + width+1
69 end
69 end
70 while week_f <= @gantt.date_to
70 while week_f <= @gantt.date_to
71 width = (week_f + 6 <= @gantt.date_to) ? 7 * zoom : (@gantt.date_to - week_f + 1) * zoom
71 width = (week_f + 6 <= @gantt.date_to) ? 7 * zoom : (@gantt.date_to - week_f + 1) * zoom
72 pdf.SetY(y_start + header_heigth)
72 pdf.SetY(y_start + header_heigth)
73 pdf.SetX(left)
73 pdf.SetX(left)
74 pdf.Cell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C")
74 pdf.Cell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C")
75 left = left + width
75 left = left + width
76 week_f = week_f+7
76 week_f = week_f+7
77 end
77 end
78 end
78 end
79
79
80 #
80 #
81 # Days headers
81 # Days headers
82 #
82 #
83 if show_days
83 if show_days
84 left = subject_width
84 left = subject_width
85 height = header_heigth
85 height = header_heigth
86 wday = @gantt.date_from.cwday
86 wday = @gantt.date_from.cwday
87 pdf.SetFontStyle('B',7)
87 pdf.SetFontStyle('B',7)
88 (@gantt.date_to - @gantt.date_from + 1).to_i.times do
88 (@gantt.date_to - @gantt.date_from + 1).to_i.times do
89 width = zoom
89 width = zoom
90 pdf.SetY(y_start + 2 * header_heigth)
90 pdf.SetY(y_start + 2 * header_heigth)
91 pdf.SetX(left)
91 pdf.SetX(left)
92 pdf.Cell(width, height, day_name(wday).first, "LTR", 0, "C")
92 pdf.Cell(width, height, day_name(wday).first, "LTR", 0, "C")
93 left = left + width
93 left = left + width
94 wday = wday + 1
94 wday = wday + 1
95 wday = 1 if wday > 7
95 wday = 1 if wday > 7
96 end
96 end
97 end
97 end
98
98
99 pdf.SetY(y_start)
99 pdf.SetY(y_start)
100 pdf.SetX(15)
100 pdf.SetX(15)
101 pdf.Cell(subject_width+g_width-15, headers_heigth, "", 1)
101 pdf.Cell(subject_width+g_width-15, headers_heigth, "", 1)
102
102
103
103
104 #
104 #
105 # Tasks
105 # Tasks
106 #
106 #
107 top = headers_heigth + y_start
107 top = headers_heigth + y_start
108 pdf.SetFontStyle('B',7)
108 pdf.SetFontStyle('B',7)
109 @gantt.events.each do |i|
109 @gantt.events.each do |i|
110 pdf.SetY(top)
110 pdf.SetY(top)
111 pdf.SetX(15)
111 pdf.SetX(15)
112
112
113 if i.is_a? Issue
113 if i.is_a? Issue
114 pdf.Cell(subject_width-15, 5, "#{i.tracker.name} #{i.id}: #{i.subject}".sub(/^(.{30}[^\s]*\s).*$/, '\1 (...)'), "LR")
114 pdf.Cell(subject_width-15, 5, "#{i.tracker.name} #{i.id}: #{i.subject}".sub(/^(.{30}[^\s]*\s).*$/, '\1 (...)'), "LR")
115 else
115 else
116 pdf.Cell(subject_width-15, 5, "#{l(:label_version)}: #{i.name}", "LR")
116 pdf.Cell(subject_width-15, 5, "#{l(:label_version)}: #{i.name}", "LR")
117 end
117 end
118
118
119 pdf.SetY(top)
119 pdf.SetY(top)
120 pdf.SetX(subject_width)
120 pdf.SetX(subject_width)
121 pdf.Cell(g_width, 5, "", "LR")
121 pdf.Cell(g_width, 5, "", "LR")
122
122
123 pdf.SetY(top+1.5)
123 pdf.SetY(top+1.5)
124
124
125 if i.is_a? Issue
125 if i.is_a? Issue
126 i_start_date = (i.start_date >= @gantt.date_from ? i.start_date : @gantt.date_from )
126 i_start_date = (i.start_date >= @gantt.date_from ? i.start_date : @gantt.date_from )
127 i_end_date = (i.due_before <= @gantt.date_to ? i.due_before : @gantt.date_to )
127 i_end_date = (i.due_before <= @gantt.date_to ? i.due_before : @gantt.date_to )
128
128
129 i_done_date = i.start_date + ((i.due_before - i.start_date+1)*i.done_ratio/100).floor
129 i_done_date = i.start_date + ((i.due_before - i.start_date+1)*i.done_ratio/100).floor
130 i_done_date = (i_done_date <= @gantt.date_from ? @gantt.date_from : i_done_date )
130 i_done_date = (i_done_date <= @gantt.date_from ? @gantt.date_from : i_done_date )
131 i_done_date = (i_done_date >= @gantt.date_to ? @gantt.date_to : i_done_date )
131 i_done_date = (i_done_date >= @gantt.date_to ? @gantt.date_to : i_done_date )
132
132
133 i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
133 i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
134
134
135 i_left = ((i_start_date - @gantt.date_from)*zoom)
135 i_left = ((i_start_date - @gantt.date_from)*zoom)
136 i_width = ((i_end_date - i_start_date + 1)*zoom)
136 i_width = ((i_end_date - i_start_date + 1)*zoom)
137 d_width = ((i_done_date - i_start_date)*zoom)
137 d_width = ((i_done_date - i_start_date)*zoom)
138 l_width = ((i_late_date - i_start_date+1)*zoom) if i_late_date
138 l_width = ((i_late_date - i_start_date+1)*zoom) if i_late_date
139 l_width ||= 0
139 l_width ||= 0
140
140
141 pdf.SetX(subject_width + i_left)
141 pdf.SetX(subject_width + i_left)
142 pdf.SetFillColor(200,200,200)
142 pdf.SetFillColor(200,200,200)
143 pdf.Cell(i_width, 2, "", 0, 0, "", 1)
143 pdf.Cell(i_width, 2, "", 0, 0, "", 1)
144
144
145 if l_width > 0
145 if l_width > 0
146 pdf.SetY(top+1.5)
146 pdf.SetY(top+1.5)
147 pdf.SetX(subject_width + i_left)
147 pdf.SetX(subject_width + i_left)
148 pdf.SetFillColor(255,100,100)
148 pdf.SetFillColor(255,100,100)
149 pdf.Cell(l_width, 2, "", 0, 0, "", 1)
149 pdf.Cell(l_width, 2, "", 0, 0, "", 1)
150 end
150 end
151 if d_width > 0
151 if d_width > 0
152 pdf.SetY(top+1.5)
152 pdf.SetY(top+1.5)
153 pdf.SetX(subject_width + i_left)
153 pdf.SetX(subject_width + i_left)
154 pdf.SetFillColor(100,100,255)
154 pdf.SetFillColor(100,100,255)
155 pdf.Cell(d_width, 2, "", 0, 0, "", 1)
155 pdf.Cell(d_width, 2, "", 0, 0, "", 1)
156 end
156 end
157
157
158 pdf.SetY(top+1.5)
158 pdf.SetY(top+1.5)
159 pdf.SetX(subject_width + i_left + i_width)
159 pdf.SetX(subject_width + i_left + i_width)
160 pdf.Cell(30, 2, "#{i.status.name} #{i.done_ratio}%")
160 pdf.Cell(30, 2, "#{i.status.name} #{i.done_ratio}%")
161 else
161 else
162 i_left = ((i.start_date - @gantt.date_from)*zoom)
162 i_left = ((i.start_date - @gantt.date_from)*zoom)
163
163
164 pdf.SetX(subject_width + i_left)
164 pdf.SetX(subject_width + i_left)
165 pdf.SetFillColor(50,200,50)
165 pdf.SetFillColor(50,200,50)
166 pdf.Cell(2, 2, "", 0, 0, "", 1)
166 pdf.Cell(2, 2, "", 0, 0, "", 1)
167
167
168 pdf.SetY(top+1.5)
168 pdf.SetY(top+1.5)
169 pdf.SetX(subject_width + i_left + 3)
169 pdf.SetX(subject_width + i_left + 3)
170 pdf.Cell(30, 2, "#{i.name}")
170 pdf.Cell(30, 2, "#{i.name}")
171 end
171 end
172
172
173
173
174 top = top + 5
174 top = top + 5
175 pdf.SetDrawColor(200, 200, 200)
175 pdf.SetDrawColor(200, 200, 200)
176 pdf.Line(15, top, subject_width+g_width, top)
176 pdf.Line(15, top, subject_width+g_width, top)
177 if pdf.GetY() > 180
177 if pdf.GetY() > 180
178 pdf.AddPage("L")
178 pdf.AddPage("L")
179 top = 20
179 top = 20
180 pdf.Line(15, top, subject_width+g_width, top)
180 pdf.Line(15, top, subject_width+g_width, top)
181 end
181 end
182 pdf.SetDrawColor(0, 0, 0)
182 pdf.SetDrawColor(0, 0, 0)
183 end
183 end
184
184
185 pdf.Line(15, top, subject_width+g_width, top)
185 pdf.Line(15, top, subject_width+g_width, top)
186
186
187 %>
187 %>
188 <%= pdf.Output %> No newline at end of file
188 <%= pdf.Output %>
@@ -1,705 +1,729
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'issues_controller'
19 require 'issues_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class IssuesController; def rescue_action(e) raise e end; end
22 class IssuesController; def rescue_action(e) raise e end; end
23
23
24 class IssuesControllerTest < Test::Unit::TestCase
24 class IssuesControllerTest < Test::Unit::TestCase
25 fixtures :projects,
25 fixtures :projects,
26 :users,
26 :users,
27 :roles,
27 :roles,
28 :members,
28 :members,
29 :issues,
29 :issues,
30 :issue_statuses,
30 :issue_statuses,
31 :versions,
31 :versions,
32 :trackers,
32 :trackers,
33 :projects_trackers,
33 :projects_trackers,
34 :issue_categories,
34 :issue_categories,
35 :enabled_modules,
35 :enabled_modules,
36 :enumerations,
36 :enumerations,
37 :attachments,
37 :attachments,
38 :workflows,
38 :workflows,
39 :custom_fields,
39 :custom_fields,
40 :custom_values,
40 :custom_values,
41 :custom_fields_trackers,
41 :custom_fields_trackers,
42 :time_entries,
42 :time_entries,
43 :journals,
43 :journals,
44 :journal_details
44 :journal_details
45
45
46 def setup
46 def setup
47 @controller = IssuesController.new
47 @controller = IssuesController.new
48 @request = ActionController::TestRequest.new
48 @request = ActionController::TestRequest.new
49 @response = ActionController::TestResponse.new
49 @response = ActionController::TestResponse.new
50 User.current = nil
50 User.current = nil
51 end
51 end
52
52
53 def test_index
53 def test_index
54 get :index
54 get :index
55 assert_response :success
55 assert_response :success
56 assert_template 'index.rhtml'
56 assert_template 'index.rhtml'
57 assert_not_nil assigns(:issues)
57 assert_not_nil assigns(:issues)
58 assert_nil assigns(:project)
58 assert_nil assigns(:project)
59 assert_tag :tag => 'a', :content => /Can't print recipes/
59 assert_tag :tag => 'a', :content => /Can't print recipes/
60 assert_tag :tag => 'a', :content => /Subproject issue/
60 assert_tag :tag => 'a', :content => /Subproject issue/
61 # private projects hidden
61 # private projects hidden
62 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
62 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
63 assert_no_tag :tag => 'a', :content => /Issue on project 2/
63 assert_no_tag :tag => 'a', :content => /Issue on project 2/
64 end
64 end
65
65
66 def test_index_should_not_list_issues_when_module_disabled
66 def test_index_should_not_list_issues_when_module_disabled
67 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
67 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
68 get :index
68 get :index
69 assert_response :success
69 assert_response :success
70 assert_template 'index.rhtml'
70 assert_template 'index.rhtml'
71 assert_not_nil assigns(:issues)
71 assert_not_nil assigns(:issues)
72 assert_nil assigns(:project)
72 assert_nil assigns(:project)
73 assert_no_tag :tag => 'a', :content => /Can't print recipes/
73 assert_no_tag :tag => 'a', :content => /Can't print recipes/
74 assert_tag :tag => 'a', :content => /Subproject issue/
74 assert_tag :tag => 'a', :content => /Subproject issue/
75 end
75 end
76
76
77 def test_index_with_project
77 def test_index_with_project
78 Setting.display_subprojects_issues = 0
78 Setting.display_subprojects_issues = 0
79 get :index, :project_id => 1
79 get :index, :project_id => 1
80 assert_response :success
80 assert_response :success
81 assert_template 'index.rhtml'
81 assert_template 'index.rhtml'
82 assert_not_nil assigns(:issues)
82 assert_not_nil assigns(:issues)
83 assert_tag :tag => 'a', :content => /Can't print recipes/
83 assert_tag :tag => 'a', :content => /Can't print recipes/
84 assert_no_tag :tag => 'a', :content => /Subproject issue/
84 assert_no_tag :tag => 'a', :content => /Subproject issue/
85 end
85 end
86
86
87 def test_index_with_project_and_subprojects
87 def test_index_with_project_and_subprojects
88 Setting.display_subprojects_issues = 1
88 Setting.display_subprojects_issues = 1
89 get :index, :project_id => 1
89 get :index, :project_id => 1
90 assert_response :success
90 assert_response :success
91 assert_template 'index.rhtml'
91 assert_template 'index.rhtml'
92 assert_not_nil assigns(:issues)
92 assert_not_nil assigns(:issues)
93 assert_tag :tag => 'a', :content => /Can't print recipes/
93 assert_tag :tag => 'a', :content => /Can't print recipes/
94 assert_tag :tag => 'a', :content => /Subproject issue/
94 assert_tag :tag => 'a', :content => /Subproject issue/
95 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
95 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
96 end
96 end
97
97
98 def test_index_with_project_and_subprojects_should_show_private_subprojects
98 def test_index_with_project_and_subprojects_should_show_private_subprojects
99 @request.session[:user_id] = 2
99 @request.session[:user_id] = 2
100 Setting.display_subprojects_issues = 1
100 Setting.display_subprojects_issues = 1
101 get :index, :project_id => 1
101 get :index, :project_id => 1
102 assert_response :success
102 assert_response :success
103 assert_template 'index.rhtml'
103 assert_template 'index.rhtml'
104 assert_not_nil assigns(:issues)
104 assert_not_nil assigns(:issues)
105 assert_tag :tag => 'a', :content => /Can't print recipes/
105 assert_tag :tag => 'a', :content => /Can't print recipes/
106 assert_tag :tag => 'a', :content => /Subproject issue/
106 assert_tag :tag => 'a', :content => /Subproject issue/
107 assert_tag :tag => 'a', :content => /Issue of a private subproject/
107 assert_tag :tag => 'a', :content => /Issue of a private subproject/
108 end
108 end
109
109
110 def test_index_with_project_and_filter
110 def test_index_with_project_and_filter
111 get :index, :project_id => 1, :set_filter => 1
111 get :index, :project_id => 1, :set_filter => 1
112 assert_response :success
112 assert_response :success
113 assert_template 'index.rhtml'
113 assert_template 'index.rhtml'
114 assert_not_nil assigns(:issues)
114 assert_not_nil assigns(:issues)
115 end
115 end
116
116
117 def test_index_csv_with_project
117 def test_index_csv_with_project
118 get :index, :format => 'csv'
118 get :index, :format => 'csv'
119 assert_response :success
119 assert_response :success
120 assert_not_nil assigns(:issues)
120 assert_not_nil assigns(:issues)
121 assert_equal 'text/csv', @response.content_type
121 assert_equal 'text/csv', @response.content_type
122
122
123 get :index, :project_id => 1, :format => 'csv'
123 get :index, :project_id => 1, :format => 'csv'
124 assert_response :success
124 assert_response :success
125 assert_not_nil assigns(:issues)
125 assert_not_nil assigns(:issues)
126 assert_equal 'text/csv', @response.content_type
126 assert_equal 'text/csv', @response.content_type
127 end
127 end
128
128
129 def test_index_pdf
129 def test_index_pdf
130 get :index, :format => 'pdf'
130 get :index, :format => 'pdf'
131 assert_response :success
131 assert_response :success
132 assert_not_nil assigns(:issues)
132 assert_not_nil assigns(:issues)
133 assert_equal 'application/pdf', @response.content_type
133 assert_equal 'application/pdf', @response.content_type
134
134
135 get :index, :project_id => 1, :format => 'pdf'
135 get :index, :project_id => 1, :format => 'pdf'
136 assert_response :success
136 assert_response :success
137 assert_not_nil assigns(:issues)
137 assert_not_nil assigns(:issues)
138 assert_equal 'application/pdf', @response.content_type
138 assert_equal 'application/pdf', @response.content_type
139 end
139 end
140
140
141 def test_gantt
141 def test_gantt
142 get :gantt, :project_id => 1
142 get :gantt, :project_id => 1
143 assert_response :success
143 assert_response :success
144 assert_template 'gantt.rhtml'
144 assert_template 'gantt.rhtml'
145 assert_not_nil assigns(:gantt)
145 assert_not_nil assigns(:gantt)
146 events = assigns(:gantt).events
146 events = assigns(:gantt).events
147 assert_not_nil events
147 assert_not_nil events
148 # Issue with start and due dates
148 # Issue with start and due dates
149 i = Issue.find(1)
149 i = Issue.find(1)
150 assert_not_nil i.due_date
150 assert_not_nil i.due_date
151 assert events.include?(Issue.find(1))
151 assert events.include?(Issue.find(1))
152 # Issue with without due date but targeted to a version with date
152 # Issue with without due date but targeted to a version with date
153 i = Issue.find(2)
153 i = Issue.find(2)
154 assert_nil i.due_date
154 assert_nil i.due_date
155 assert events.include?(i)
155 assert events.include?(i)
156 end
156 end
157
157
158 def test_cross_project_gantt
159 get :gantt
160 assert_response :success
161 assert_template 'gantt.rhtml'
162 assert_not_nil assigns(:gantt)
163 events = assigns(:gantt).events
164 assert_not_nil events
165 end
166
158 def test_gantt_export_to_pdf
167 def test_gantt_export_to_pdf
159 get :gantt, :project_id => 1, :format => 'pdf'
168 get :gantt, :project_id => 1, :format => 'pdf'
160 assert_response :success
169 assert_response :success
161 assert_template 'gantt.rfpdf'
170 assert_template 'gantt.rfpdf'
162 assert_equal 'application/pdf', @response.content_type
171 assert_equal 'application/pdf', @response.content_type
163 assert_not_nil assigns(:gantt)
172 assert_not_nil assigns(:gantt)
164 end
173 end
174
175 def test_cross_project_gantt_export_to_pdf
176 get :gantt, :format => 'pdf'
177 assert_response :success
178 assert_template 'gantt.rfpdf'
179 assert_equal 'application/pdf', @response.content_type
180 assert_not_nil assigns(:gantt)
181 end
165
182
166 if Object.const_defined?(:Magick)
183 if Object.const_defined?(:Magick)
167 def test_gantt_image
184 def test_gantt_image
168 get :gantt, :project_id => 1, :format => 'png'
185 get :gantt, :project_id => 1, :format => 'png'
169 assert_response :success
186 assert_response :success
170 assert_equal 'image/png', @response.content_type
187 assert_equal 'image/png', @response.content_type
171 end
188 end
172 else
189 else
173 puts "RMagick not installed. Skipping tests !!!"
190 puts "RMagick not installed. Skipping tests !!!"
174 end
191 end
175
192
176 def test_calendar
193 def test_calendar
177 get :calendar, :project_id => 1
194 get :calendar, :project_id => 1
178 assert_response :success
195 assert_response :success
179 assert_template 'calendar'
196 assert_template 'calendar'
180 assert_not_nil assigns(:calendar)
197 assert_not_nil assigns(:calendar)
181 end
198 end
182
199
200 def test_cross_project_calendar
201 get :calendar
202 assert_response :success
203 assert_template 'calendar'
204 assert_not_nil assigns(:calendar)
205 end
206
183 def test_changes
207 def test_changes
184 get :changes, :project_id => 1
208 get :changes, :project_id => 1
185 assert_response :success
209 assert_response :success
186 assert_not_nil assigns(:journals)
210 assert_not_nil assigns(:journals)
187 assert_equal 'application/atom+xml', @response.content_type
211 assert_equal 'application/atom+xml', @response.content_type
188 end
212 end
189
213
190 def test_show_by_anonymous
214 def test_show_by_anonymous
191 get :show, :id => 1
215 get :show, :id => 1
192 assert_response :success
216 assert_response :success
193 assert_template 'show.rhtml'
217 assert_template 'show.rhtml'
194 assert_not_nil assigns(:issue)
218 assert_not_nil assigns(:issue)
195 assert_equal Issue.find(1), assigns(:issue)
219 assert_equal Issue.find(1), assigns(:issue)
196
220
197 # anonymous role is allowed to add a note
221 # anonymous role is allowed to add a note
198 assert_tag :tag => 'form',
222 assert_tag :tag => 'form',
199 :descendant => { :tag => 'fieldset',
223 :descendant => { :tag => 'fieldset',
200 :child => { :tag => 'legend',
224 :child => { :tag => 'legend',
201 :content => /Notes/ } }
225 :content => /Notes/ } }
202 end
226 end
203
227
204 def test_show_by_manager
228 def test_show_by_manager
205 @request.session[:user_id] = 2
229 @request.session[:user_id] = 2
206 get :show, :id => 1
230 get :show, :id => 1
207 assert_response :success
231 assert_response :success
208
232
209 assert_tag :tag => 'form',
233 assert_tag :tag => 'form',
210 :descendant => { :tag => 'fieldset',
234 :descendant => { :tag => 'fieldset',
211 :child => { :tag => 'legend',
235 :child => { :tag => 'legend',
212 :content => /Change properties/ } },
236 :content => /Change properties/ } },
213 :descendant => { :tag => 'fieldset',
237 :descendant => { :tag => 'fieldset',
214 :child => { :tag => 'legend',
238 :child => { :tag => 'legend',
215 :content => /Log time/ } },
239 :content => /Log time/ } },
216 :descendant => { :tag => 'fieldset',
240 :descendant => { :tag => 'fieldset',
217 :child => { :tag => 'legend',
241 :child => { :tag => 'legend',
218 :content => /Notes/ } }
242 :content => /Notes/ } }
219 end
243 end
220
244
221 def test_get_new
245 def test_get_new
222 @request.session[:user_id] = 2
246 @request.session[:user_id] = 2
223 get :new, :project_id => 1, :tracker_id => 1
247 get :new, :project_id => 1, :tracker_id => 1
224 assert_response :success
248 assert_response :success
225 assert_template 'new'
249 assert_template 'new'
226
250
227 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
251 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
228 :value => 'Default string' }
252 :value => 'Default string' }
229 end
253 end
230
254
231 def test_get_new_without_tracker_id
255 def test_get_new_without_tracker_id
232 @request.session[:user_id] = 2
256 @request.session[:user_id] = 2
233 get :new, :project_id => 1
257 get :new, :project_id => 1
234 assert_response :success
258 assert_response :success
235 assert_template 'new'
259 assert_template 'new'
236
260
237 issue = assigns(:issue)
261 issue = assigns(:issue)
238 assert_not_nil issue
262 assert_not_nil issue
239 assert_equal Project.find(1).trackers.first, issue.tracker
263 assert_equal Project.find(1).trackers.first, issue.tracker
240 end
264 end
241
265
242 def test_update_new_form
266 def test_update_new_form
243 @request.session[:user_id] = 2
267 @request.session[:user_id] = 2
244 xhr :post, :new, :project_id => 1,
268 xhr :post, :new, :project_id => 1,
245 :issue => {:tracker_id => 2,
269 :issue => {:tracker_id => 2,
246 :subject => 'This is the test_new issue',
270 :subject => 'This is the test_new issue',
247 :description => 'This is the description',
271 :description => 'This is the description',
248 :priority_id => 5}
272 :priority_id => 5}
249 assert_response :success
273 assert_response :success
250 assert_template 'new'
274 assert_template 'new'
251 end
275 end
252
276
253 def test_post_new
277 def test_post_new
254 @request.session[:user_id] = 2
278 @request.session[:user_id] = 2
255 post :new, :project_id => 1,
279 post :new, :project_id => 1,
256 :issue => {:tracker_id => 3,
280 :issue => {:tracker_id => 3,
257 :subject => 'This is the test_new issue',
281 :subject => 'This is the test_new issue',
258 :description => 'This is the description',
282 :description => 'This is the description',
259 :priority_id => 5,
283 :priority_id => 5,
260 :estimated_hours => '',
284 :estimated_hours => '',
261 :custom_field_values => {'2' => 'Value for field 2'}}
285 :custom_field_values => {'2' => 'Value for field 2'}}
262 assert_redirected_to 'issues/show'
286 assert_redirected_to 'issues/show'
263
287
264 issue = Issue.find_by_subject('This is the test_new issue')
288 issue = Issue.find_by_subject('This is the test_new issue')
265 assert_not_nil issue
289 assert_not_nil issue
266 assert_equal 2, issue.author_id
290 assert_equal 2, issue.author_id
267 assert_equal 3, issue.tracker_id
291 assert_equal 3, issue.tracker_id
268 assert_nil issue.estimated_hours
292 assert_nil issue.estimated_hours
269 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
293 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
270 assert_not_nil v
294 assert_not_nil v
271 assert_equal 'Value for field 2', v.value
295 assert_equal 'Value for field 2', v.value
272 end
296 end
273
297
274 def test_post_new_without_custom_fields_param
298 def test_post_new_without_custom_fields_param
275 @request.session[:user_id] = 2
299 @request.session[:user_id] = 2
276 post :new, :project_id => 1,
300 post :new, :project_id => 1,
277 :issue => {:tracker_id => 1,
301 :issue => {:tracker_id => 1,
278 :subject => 'This is the test_new issue',
302 :subject => 'This is the test_new issue',
279 :description => 'This is the description',
303 :description => 'This is the description',
280 :priority_id => 5}
304 :priority_id => 5}
281 assert_redirected_to 'issues/show'
305 assert_redirected_to 'issues/show'
282 end
306 end
283
307
284 def test_post_new_with_required_custom_field_and_without_custom_fields_param
308 def test_post_new_with_required_custom_field_and_without_custom_fields_param
285 field = IssueCustomField.find_by_name('Database')
309 field = IssueCustomField.find_by_name('Database')
286 field.update_attribute(:is_required, true)
310 field.update_attribute(:is_required, true)
287
311
288 @request.session[:user_id] = 2
312 @request.session[:user_id] = 2
289 post :new, :project_id => 1,
313 post :new, :project_id => 1,
290 :issue => {:tracker_id => 1,
314 :issue => {:tracker_id => 1,
291 :subject => 'This is the test_new issue',
315 :subject => 'This is the test_new issue',
292 :description => 'This is the description',
316 :description => 'This is the description',
293 :priority_id => 5}
317 :priority_id => 5}
294 assert_response :success
318 assert_response :success
295 assert_template 'new'
319 assert_template 'new'
296 issue = assigns(:issue)
320 issue = assigns(:issue)
297 assert_not_nil issue
321 assert_not_nil issue
298 assert_equal 'activerecord_error_invalid', issue.errors.on(:custom_values)
322 assert_equal 'activerecord_error_invalid', issue.errors.on(:custom_values)
299 end
323 end
300
324
301 def test_post_should_preserve_fields_values_on_validation_failure
325 def test_post_should_preserve_fields_values_on_validation_failure
302 @request.session[:user_id] = 2
326 @request.session[:user_id] = 2
303 post :new, :project_id => 1,
327 post :new, :project_id => 1,
304 :issue => {:tracker_id => 1,
328 :issue => {:tracker_id => 1,
305 :subject => 'This is the test_new issue',
329 :subject => 'This is the test_new issue',
306 # empty description
330 # empty description
307 :description => '',
331 :description => '',
308 :priority_id => 6,
332 :priority_id => 6,
309 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
333 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
310 assert_response :success
334 assert_response :success
311 assert_template 'new'
335 assert_template 'new'
312
336
313 assert_tag :input, :attributes => { :name => 'issue[subject]',
337 assert_tag :input, :attributes => { :name => 'issue[subject]',
314 :value => 'This is the test_new issue' }
338 :value => 'This is the test_new issue' }
315 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
339 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
316 :child => { :tag => 'option', :attributes => { :selected => 'selected',
340 :child => { :tag => 'option', :attributes => { :selected => 'selected',
317 :value => '6' },
341 :value => '6' },
318 :content => 'High' }
342 :content => 'High' }
319 # Custom fields
343 # Custom fields
320 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
344 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
321 :child => { :tag => 'option', :attributes => { :selected => 'selected',
345 :child => { :tag => 'option', :attributes => { :selected => 'selected',
322 :value => 'Oracle' },
346 :value => 'Oracle' },
323 :content => 'Oracle' }
347 :content => 'Oracle' }
324 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
348 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
325 :value => 'Value for field 2'}
349 :value => 'Value for field 2'}
326 end
350 end
327
351
328 def test_copy_issue
352 def test_copy_issue
329 @request.session[:user_id] = 2
353 @request.session[:user_id] = 2
330 get :new, :project_id => 1, :copy_from => 1
354 get :new, :project_id => 1, :copy_from => 1
331 assert_template 'new'
355 assert_template 'new'
332 assert_not_nil assigns(:issue)
356 assert_not_nil assigns(:issue)
333 orig = Issue.find(1)
357 orig = Issue.find(1)
334 assert_equal orig.subject, assigns(:issue).subject
358 assert_equal orig.subject, assigns(:issue).subject
335 end
359 end
336
360
337 def test_get_edit
361 def test_get_edit
338 @request.session[:user_id] = 2
362 @request.session[:user_id] = 2
339 get :edit, :id => 1
363 get :edit, :id => 1
340 assert_response :success
364 assert_response :success
341 assert_template 'edit'
365 assert_template 'edit'
342 assert_not_nil assigns(:issue)
366 assert_not_nil assigns(:issue)
343 assert_equal Issue.find(1), assigns(:issue)
367 assert_equal Issue.find(1), assigns(:issue)
344 end
368 end
345
369
346 def test_get_edit_with_params
370 def test_get_edit_with_params
347 @request.session[:user_id] = 2
371 @request.session[:user_id] = 2
348 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
372 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
349 assert_response :success
373 assert_response :success
350 assert_template 'edit'
374 assert_template 'edit'
351
375
352 issue = assigns(:issue)
376 issue = assigns(:issue)
353 assert_not_nil issue
377 assert_not_nil issue
354
378
355 assert_equal 5, issue.status_id
379 assert_equal 5, issue.status_id
356 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
380 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
357 :child => { :tag => 'option',
381 :child => { :tag => 'option',
358 :content => 'Closed',
382 :content => 'Closed',
359 :attributes => { :selected => 'selected' } }
383 :attributes => { :selected => 'selected' } }
360
384
361 assert_equal 7, issue.priority_id
385 assert_equal 7, issue.priority_id
362 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
386 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
363 :child => { :tag => 'option',
387 :child => { :tag => 'option',
364 :content => 'Urgent',
388 :content => 'Urgent',
365 :attributes => { :selected => 'selected' } }
389 :attributes => { :selected => 'selected' } }
366 end
390 end
367
391
368 def test_reply_to_issue
392 def test_reply_to_issue
369 @request.session[:user_id] = 2
393 @request.session[:user_id] = 2
370 get :reply, :id => 1
394 get :reply, :id => 1
371 assert_response :success
395 assert_response :success
372 assert_select_rjs :show, "update"
396 assert_select_rjs :show, "update"
373 end
397 end
374
398
375 def test_reply_to_note
399 def test_reply_to_note
376 @request.session[:user_id] = 2
400 @request.session[:user_id] = 2
377 get :reply, :id => 1, :journal_id => 2
401 get :reply, :id => 1, :journal_id => 2
378 assert_response :success
402 assert_response :success
379 assert_select_rjs :show, "update"
403 assert_select_rjs :show, "update"
380 end
404 end
381
405
382 def test_post_edit_without_custom_fields_param
406 def test_post_edit_without_custom_fields_param
383 @request.session[:user_id] = 2
407 @request.session[:user_id] = 2
384 ActionMailer::Base.deliveries.clear
408 ActionMailer::Base.deliveries.clear
385
409
386 issue = Issue.find(1)
410 issue = Issue.find(1)
387 assert_equal '125', issue.custom_value_for(2).value
411 assert_equal '125', issue.custom_value_for(2).value
388 old_subject = issue.subject
412 old_subject = issue.subject
389 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
413 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
390
414
391 assert_difference('Journal.count') do
415 assert_difference('Journal.count') do
392 assert_difference('JournalDetail.count', 2) do
416 assert_difference('JournalDetail.count', 2) do
393 post :edit, :id => 1, :issue => {:subject => new_subject,
417 post :edit, :id => 1, :issue => {:subject => new_subject,
394 :priority_id => '6',
418 :priority_id => '6',
395 :category_id => '1' # no change
419 :category_id => '1' # no change
396 }
420 }
397 end
421 end
398 end
422 end
399 assert_redirected_to 'issues/show/1'
423 assert_redirected_to 'issues/show/1'
400 issue.reload
424 issue.reload
401 assert_equal new_subject, issue.subject
425 assert_equal new_subject, issue.subject
402 # Make sure custom fields were not cleared
426 # Make sure custom fields were not cleared
403 assert_equal '125', issue.custom_value_for(2).value
427 assert_equal '125', issue.custom_value_for(2).value
404
428
405 mail = ActionMailer::Base.deliveries.last
429 mail = ActionMailer::Base.deliveries.last
406 assert_kind_of TMail::Mail, mail
430 assert_kind_of TMail::Mail, mail
407 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
431 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
408 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
432 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
409 end
433 end
410
434
411 def test_post_edit_with_custom_field_change
435 def test_post_edit_with_custom_field_change
412 @request.session[:user_id] = 2
436 @request.session[:user_id] = 2
413 issue = Issue.find(1)
437 issue = Issue.find(1)
414 assert_equal '125', issue.custom_value_for(2).value
438 assert_equal '125', issue.custom_value_for(2).value
415
439
416 assert_difference('Journal.count') do
440 assert_difference('Journal.count') do
417 assert_difference('JournalDetail.count', 3) do
441 assert_difference('JournalDetail.count', 3) do
418 post :edit, :id => 1, :issue => {:subject => 'Custom field change',
442 post :edit, :id => 1, :issue => {:subject => 'Custom field change',
419 :priority_id => '6',
443 :priority_id => '6',
420 :category_id => '1', # no change
444 :category_id => '1', # no change
421 :custom_field_values => { '2' => 'New custom value' }
445 :custom_field_values => { '2' => 'New custom value' }
422 }
446 }
423 end
447 end
424 end
448 end
425 assert_redirected_to 'issues/show/1'
449 assert_redirected_to 'issues/show/1'
426 issue.reload
450 issue.reload
427 assert_equal 'New custom value', issue.custom_value_for(2).value
451 assert_equal 'New custom value', issue.custom_value_for(2).value
428
452
429 mail = ActionMailer::Base.deliveries.last
453 mail = ActionMailer::Base.deliveries.last
430 assert_kind_of TMail::Mail, mail
454 assert_kind_of TMail::Mail, mail
431 assert mail.body.include?("Searchable field changed from 125 to New custom value")
455 assert mail.body.include?("Searchable field changed from 125 to New custom value")
432 end
456 end
433
457
434 def test_post_edit_with_status_and_assignee_change
458 def test_post_edit_with_status_and_assignee_change
435 issue = Issue.find(1)
459 issue = Issue.find(1)
436 assert_equal 1, issue.status_id
460 assert_equal 1, issue.status_id
437 @request.session[:user_id] = 2
461 @request.session[:user_id] = 2
438 assert_difference('TimeEntry.count', 0) do
462 assert_difference('TimeEntry.count', 0) do
439 post :edit,
463 post :edit,
440 :id => 1,
464 :id => 1,
441 :issue => { :status_id => 2, :assigned_to_id => 3 },
465 :issue => { :status_id => 2, :assigned_to_id => 3 },
442 :notes => 'Assigned to dlopper',
466 :notes => 'Assigned to dlopper',
443 :time_entry => { :hours => '', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
467 :time_entry => { :hours => '', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
444 end
468 end
445 assert_redirected_to 'issues/show/1'
469 assert_redirected_to 'issues/show/1'
446 issue.reload
470 issue.reload
447 assert_equal 2, issue.status_id
471 assert_equal 2, issue.status_id
448 j = issue.journals.find(:first, :order => 'id DESC')
472 j = issue.journals.find(:first, :order => 'id DESC')
449 assert_equal 'Assigned to dlopper', j.notes
473 assert_equal 'Assigned to dlopper', j.notes
450 assert_equal 2, j.details.size
474 assert_equal 2, j.details.size
451
475
452 mail = ActionMailer::Base.deliveries.last
476 mail = ActionMailer::Base.deliveries.last
453 assert mail.body.include?("Status changed from New to Assigned")
477 assert mail.body.include?("Status changed from New to Assigned")
454 end
478 end
455
479
456 def test_post_edit_with_note_only
480 def test_post_edit_with_note_only
457 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
481 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
458 # anonymous user
482 # anonymous user
459 post :edit,
483 post :edit,
460 :id => 1,
484 :id => 1,
461 :notes => notes
485 :notes => notes
462 assert_redirected_to 'issues/show/1'
486 assert_redirected_to 'issues/show/1'
463 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
487 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
464 assert_equal notes, j.notes
488 assert_equal notes, j.notes
465 assert_equal 0, j.details.size
489 assert_equal 0, j.details.size
466 assert_equal User.anonymous, j.user
490 assert_equal User.anonymous, j.user
467
491
468 mail = ActionMailer::Base.deliveries.last
492 mail = ActionMailer::Base.deliveries.last
469 assert mail.body.include?(notes)
493 assert mail.body.include?(notes)
470 end
494 end
471
495
472 def test_post_edit_with_note_and_spent_time
496 def test_post_edit_with_note_and_spent_time
473 @request.session[:user_id] = 2
497 @request.session[:user_id] = 2
474 spent_hours_before = Issue.find(1).spent_hours
498 spent_hours_before = Issue.find(1).spent_hours
475 assert_difference('TimeEntry.count') do
499 assert_difference('TimeEntry.count') do
476 post :edit,
500 post :edit,
477 :id => 1,
501 :id => 1,
478 :notes => '2.5 hours added',
502 :notes => '2.5 hours added',
479 :time_entry => { :hours => '2.5', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
503 :time_entry => { :hours => '2.5', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
480 end
504 end
481 assert_redirected_to 'issues/show/1'
505 assert_redirected_to 'issues/show/1'
482
506
483 issue = Issue.find(1)
507 issue = Issue.find(1)
484
508
485 j = issue.journals.find(:first, :order => 'id DESC')
509 j = issue.journals.find(:first, :order => 'id DESC')
486 assert_equal '2.5 hours added', j.notes
510 assert_equal '2.5 hours added', j.notes
487 assert_equal 0, j.details.size
511 assert_equal 0, j.details.size
488
512
489 t = issue.time_entries.find(:first, :order => 'id DESC')
513 t = issue.time_entries.find(:first, :order => 'id DESC')
490 assert_not_nil t
514 assert_not_nil t
491 assert_equal 2.5, t.hours
515 assert_equal 2.5, t.hours
492 assert_equal spent_hours_before + 2.5, issue.spent_hours
516 assert_equal spent_hours_before + 2.5, issue.spent_hours
493 end
517 end
494
518
495 def test_post_edit_with_attachment_only
519 def test_post_edit_with_attachment_only
496 set_tmp_attachments_directory
520 set_tmp_attachments_directory
497
521
498 # Delete all fixtured journals, a race condition can occur causing the wrong
522 # Delete all fixtured journals, a race condition can occur causing the wrong
499 # journal to get fetched in the next find.
523 # journal to get fetched in the next find.
500 Journal.delete_all
524 Journal.delete_all
501
525
502 # anonymous user
526 # anonymous user
503 post :edit,
527 post :edit,
504 :id => 1,
528 :id => 1,
505 :notes => '',
529 :notes => '',
506 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
530 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
507 assert_redirected_to 'issues/show/1'
531 assert_redirected_to 'issues/show/1'
508 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
532 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
509 assert j.notes.blank?
533 assert j.notes.blank?
510 assert_equal 1, j.details.size
534 assert_equal 1, j.details.size
511 assert_equal 'testfile.txt', j.details.first.value
535 assert_equal 'testfile.txt', j.details.first.value
512 assert_equal User.anonymous, j.user
536 assert_equal User.anonymous, j.user
513
537
514 mail = ActionMailer::Base.deliveries.last
538 mail = ActionMailer::Base.deliveries.last
515 assert mail.body.include?('testfile.txt')
539 assert mail.body.include?('testfile.txt')
516 end
540 end
517
541
518 def test_post_edit_with_no_change
542 def test_post_edit_with_no_change
519 issue = Issue.find(1)
543 issue = Issue.find(1)
520 issue.journals.clear
544 issue.journals.clear
521 ActionMailer::Base.deliveries.clear
545 ActionMailer::Base.deliveries.clear
522
546
523 post :edit,
547 post :edit,
524 :id => 1,
548 :id => 1,
525 :notes => ''
549 :notes => ''
526 assert_redirected_to 'issues/show/1'
550 assert_redirected_to 'issues/show/1'
527
551
528 issue.reload
552 issue.reload
529 assert issue.journals.empty?
553 assert issue.journals.empty?
530 # No email should be sent
554 # No email should be sent
531 assert ActionMailer::Base.deliveries.empty?
555 assert ActionMailer::Base.deliveries.empty?
532 end
556 end
533
557
534 def test_bulk_edit
558 def test_bulk_edit
535 @request.session[:user_id] = 2
559 @request.session[:user_id] = 2
536 # update issues priority
560 # update issues priority
537 post :bulk_edit, :ids => [1, 2], :priority_id => 7, :notes => 'Bulk editing', :assigned_to_id => ''
561 post :bulk_edit, :ids => [1, 2], :priority_id => 7, :notes => 'Bulk editing', :assigned_to_id => ''
538 assert_response 302
562 assert_response 302
539 # check that the issues were updated
563 # check that the issues were updated
540 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
564 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
541 assert_equal 'Bulk editing', Issue.find(1).journals.find(:first, :order => 'created_on DESC').notes
565 assert_equal 'Bulk editing', Issue.find(1).journals.find(:first, :order => 'created_on DESC').notes
542 end
566 end
543
567
544 def test_bulk_unassign
568 def test_bulk_unassign
545 assert_not_nil Issue.find(2).assigned_to
569 assert_not_nil Issue.find(2).assigned_to
546 @request.session[:user_id] = 2
570 @request.session[:user_id] = 2
547 # unassign issues
571 # unassign issues
548 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :assigned_to_id => 'none'
572 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :assigned_to_id => 'none'
549 assert_response 302
573 assert_response 302
550 # check that the issues were updated
574 # check that the issues were updated
551 assert_nil Issue.find(2).assigned_to
575 assert_nil Issue.find(2).assigned_to
552 end
576 end
553
577
554 def test_move_one_issue_to_another_project
578 def test_move_one_issue_to_another_project
555 @request.session[:user_id] = 1
579 @request.session[:user_id] = 1
556 post :move, :id => 1, :new_project_id => 2
580 post :move, :id => 1, :new_project_id => 2
557 assert_redirected_to 'projects/ecookbook/issues'
581 assert_redirected_to 'projects/ecookbook/issues'
558 assert_equal 2, Issue.find(1).project_id
582 assert_equal 2, Issue.find(1).project_id
559 end
583 end
560
584
561 def test_bulk_move_to_another_project
585 def test_bulk_move_to_another_project
562 @request.session[:user_id] = 1
586 @request.session[:user_id] = 1
563 post :move, :ids => [1, 2], :new_project_id => 2
587 post :move, :ids => [1, 2], :new_project_id => 2
564 assert_redirected_to 'projects/ecookbook/issues'
588 assert_redirected_to 'projects/ecookbook/issues'
565 # Issues moved to project 2
589 # Issues moved to project 2
566 assert_equal 2, Issue.find(1).project_id
590 assert_equal 2, Issue.find(1).project_id
567 assert_equal 2, Issue.find(2).project_id
591 assert_equal 2, Issue.find(2).project_id
568 # No tracker change
592 # No tracker change
569 assert_equal 1, Issue.find(1).tracker_id
593 assert_equal 1, Issue.find(1).tracker_id
570 assert_equal 2, Issue.find(2).tracker_id
594 assert_equal 2, Issue.find(2).tracker_id
571 end
595 end
572
596
573 def test_bulk_move_to_another_tracker
597 def test_bulk_move_to_another_tracker
574 @request.session[:user_id] = 1
598 @request.session[:user_id] = 1
575 post :move, :ids => [1, 2], :new_tracker_id => 2
599 post :move, :ids => [1, 2], :new_tracker_id => 2
576 assert_redirected_to 'projects/ecookbook/issues'
600 assert_redirected_to 'projects/ecookbook/issues'
577 assert_equal 2, Issue.find(1).tracker_id
601 assert_equal 2, Issue.find(1).tracker_id
578 assert_equal 2, Issue.find(2).tracker_id
602 assert_equal 2, Issue.find(2).tracker_id
579 end
603 end
580
604
581 def test_context_menu_one_issue
605 def test_context_menu_one_issue
582 @request.session[:user_id] = 2
606 @request.session[:user_id] = 2
583 get :context_menu, :ids => [1]
607 get :context_menu, :ids => [1]
584 assert_response :success
608 assert_response :success
585 assert_template 'context_menu'
609 assert_template 'context_menu'
586 assert_tag :tag => 'a', :content => 'Edit',
610 assert_tag :tag => 'a', :content => 'Edit',
587 :attributes => { :href => '/issues/edit/1',
611 :attributes => { :href => '/issues/edit/1',
588 :class => 'icon-edit' }
612 :class => 'icon-edit' }
589 assert_tag :tag => 'a', :content => 'Closed',
613 assert_tag :tag => 'a', :content => 'Closed',
590 :attributes => { :href => '/issues/edit/1?issue%5Bstatus_id%5D=5',
614 :attributes => { :href => '/issues/edit/1?issue%5Bstatus_id%5D=5',
591 :class => '' }
615 :class => '' }
592 assert_tag :tag => 'a', :content => 'Immediate',
616 assert_tag :tag => 'a', :content => 'Immediate',
593 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;priority_id=8',
617 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;priority_id=8',
594 :class => '' }
618 :class => '' }
595 assert_tag :tag => 'a', :content => 'Dave Lopper',
619 assert_tag :tag => 'a', :content => 'Dave Lopper',
596 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1',
620 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1',
597 :class => '' }
621 :class => '' }
598 assert_tag :tag => 'a', :content => 'Copy',
622 assert_tag :tag => 'a', :content => 'Copy',
599 :attributes => { :href => '/projects/ecookbook/issues/new?copy_from=1',
623 :attributes => { :href => '/projects/ecookbook/issues/new?copy_from=1',
600 :class => 'icon-copy' }
624 :class => 'icon-copy' }
601 assert_tag :tag => 'a', :content => 'Move',
625 assert_tag :tag => 'a', :content => 'Move',
602 :attributes => { :href => '/issues/move?ids%5B%5D=1',
626 :attributes => { :href => '/issues/move?ids%5B%5D=1',
603 :class => 'icon-move' }
627 :class => 'icon-move' }
604 assert_tag :tag => 'a', :content => 'Delete',
628 assert_tag :tag => 'a', :content => 'Delete',
605 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
629 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
606 :class => 'icon-del' }
630 :class => 'icon-del' }
607 end
631 end
608
632
609 def test_context_menu_one_issue_by_anonymous
633 def test_context_menu_one_issue_by_anonymous
610 get :context_menu, :ids => [1]
634 get :context_menu, :ids => [1]
611 assert_response :success
635 assert_response :success
612 assert_template 'context_menu'
636 assert_template 'context_menu'
613 assert_tag :tag => 'a', :content => 'Delete',
637 assert_tag :tag => 'a', :content => 'Delete',
614 :attributes => { :href => '#',
638 :attributes => { :href => '#',
615 :class => 'icon-del disabled' }
639 :class => 'icon-del disabled' }
616 end
640 end
617
641
618 def test_context_menu_multiple_issues_of_same_project
642 def test_context_menu_multiple_issues_of_same_project
619 @request.session[:user_id] = 2
643 @request.session[:user_id] = 2
620 get :context_menu, :ids => [1, 2]
644 get :context_menu, :ids => [1, 2]
621 assert_response :success
645 assert_response :success
622 assert_template 'context_menu'
646 assert_template 'context_menu'
623 assert_tag :tag => 'a', :content => 'Edit',
647 assert_tag :tag => 'a', :content => 'Edit',
624 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
648 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
625 :class => 'icon-edit' }
649 :class => 'icon-edit' }
626 assert_tag :tag => 'a', :content => 'Immediate',
650 assert_tag :tag => 'a', :content => 'Immediate',
627 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;priority_id=8',
651 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;priority_id=8',
628 :class => '' }
652 :class => '' }
629 assert_tag :tag => 'a', :content => 'Dave Lopper',
653 assert_tag :tag => 'a', :content => 'Dave Lopper',
630 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
654 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
631 :class => '' }
655 :class => '' }
632 assert_tag :tag => 'a', :content => 'Move',
656 assert_tag :tag => 'a', :content => 'Move',
633 :attributes => { :href => '/issues/move?ids%5B%5D=1&amp;ids%5B%5D=2',
657 :attributes => { :href => '/issues/move?ids%5B%5D=1&amp;ids%5B%5D=2',
634 :class => 'icon-move' }
658 :class => 'icon-move' }
635 assert_tag :tag => 'a', :content => 'Delete',
659 assert_tag :tag => 'a', :content => 'Delete',
636 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
660 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
637 :class => 'icon-del' }
661 :class => 'icon-del' }
638 end
662 end
639
663
640 def test_context_menu_multiple_issues_of_different_project
664 def test_context_menu_multiple_issues_of_different_project
641 @request.session[:user_id] = 2
665 @request.session[:user_id] = 2
642 get :context_menu, :ids => [1, 2, 4]
666 get :context_menu, :ids => [1, 2, 4]
643 assert_response :success
667 assert_response :success
644 assert_template 'context_menu'
668 assert_template 'context_menu'
645 assert_tag :tag => 'a', :content => 'Delete',
669 assert_tag :tag => 'a', :content => 'Delete',
646 :attributes => { :href => '#',
670 :attributes => { :href => '#',
647 :class => 'icon-del disabled' }
671 :class => 'icon-del disabled' }
648 end
672 end
649
673
650 def test_destroy_issue_with_no_time_entries
674 def test_destroy_issue_with_no_time_entries
651 assert_nil TimeEntry.find_by_issue_id(2)
675 assert_nil TimeEntry.find_by_issue_id(2)
652 @request.session[:user_id] = 2
676 @request.session[:user_id] = 2
653 post :destroy, :id => 2
677 post :destroy, :id => 2
654 assert_redirected_to 'projects/ecookbook/issues'
678 assert_redirected_to 'projects/ecookbook/issues'
655 assert_nil Issue.find_by_id(2)
679 assert_nil Issue.find_by_id(2)
656 end
680 end
657
681
658 def test_destroy_issues_with_time_entries
682 def test_destroy_issues_with_time_entries
659 @request.session[:user_id] = 2
683 @request.session[:user_id] = 2
660 post :destroy, :ids => [1, 3]
684 post :destroy, :ids => [1, 3]
661 assert_response :success
685 assert_response :success
662 assert_template 'destroy'
686 assert_template 'destroy'
663 assert_not_nil assigns(:hours)
687 assert_not_nil assigns(:hours)
664 assert Issue.find_by_id(1) && Issue.find_by_id(3)
688 assert Issue.find_by_id(1) && Issue.find_by_id(3)
665 end
689 end
666
690
667 def test_destroy_issues_and_destroy_time_entries
691 def test_destroy_issues_and_destroy_time_entries
668 @request.session[:user_id] = 2
692 @request.session[:user_id] = 2
669 post :destroy, :ids => [1, 3], :todo => 'destroy'
693 post :destroy, :ids => [1, 3], :todo => 'destroy'
670 assert_redirected_to 'projects/ecookbook/issues'
694 assert_redirected_to 'projects/ecookbook/issues'
671 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
695 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
672 assert_nil TimeEntry.find_by_id([1, 2])
696 assert_nil TimeEntry.find_by_id([1, 2])
673 end
697 end
674
698
675 def test_destroy_issues_and_assign_time_entries_to_project
699 def test_destroy_issues_and_assign_time_entries_to_project
676 @request.session[:user_id] = 2
700 @request.session[:user_id] = 2
677 post :destroy, :ids => [1, 3], :todo => 'nullify'
701 post :destroy, :ids => [1, 3], :todo => 'nullify'
678 assert_redirected_to 'projects/ecookbook/issues'
702 assert_redirected_to 'projects/ecookbook/issues'
679 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
703 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
680 assert_nil TimeEntry.find(1).issue_id
704 assert_nil TimeEntry.find(1).issue_id
681 assert_nil TimeEntry.find(2).issue_id
705 assert_nil TimeEntry.find(2).issue_id
682 end
706 end
683
707
684 def test_destroy_issues_and_reassign_time_entries_to_another_issue
708 def test_destroy_issues_and_reassign_time_entries_to_another_issue
685 @request.session[:user_id] = 2
709 @request.session[:user_id] = 2
686 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
710 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
687 assert_redirected_to 'projects/ecookbook/issues'
711 assert_redirected_to 'projects/ecookbook/issues'
688 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
712 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
689 assert_equal 2, TimeEntry.find(1).issue_id
713 assert_equal 2, TimeEntry.find(1).issue_id
690 assert_equal 2, TimeEntry.find(2).issue_id
714 assert_equal 2, TimeEntry.find(2).issue_id
691 end
715 end
692
716
693 def test_destroy_attachment
717 def test_destroy_attachment
694 issue = Issue.find(3)
718 issue = Issue.find(3)
695 a = issue.attachments.size
719 a = issue.attachments.size
696 @request.session[:user_id] = 2
720 @request.session[:user_id] = 2
697 post :destroy_attachment, :id => 3, :attachment_id => 1
721 post :destroy_attachment, :id => 3, :attachment_id => 1
698 assert_redirected_to 'issues/show/3'
722 assert_redirected_to 'issues/show/3'
699 assert_nil Attachment.find_by_id(1)
723 assert_nil Attachment.find_by_id(1)
700 issue.reload
724 issue.reload
701 assert_equal((a-1), issue.attachments.size)
725 assert_equal((a-1), issue.attachments.size)
702 j = issue.journals.find(:first, :order => 'created_on DESC')
726 j = issue.journals.find(:first, :order => 'created_on DESC')
703 assert_equal 'attachment', j.details.first.property
727 assert_equal 'attachment', j.details.first.property
704 end
728 end
705 end
729 end
General Comments 0
You need to be logged in to leave comments. Login now