##// END OF EJS Templates
Allows project to be changed from the regular issue update action (#4769, #9803)....
Jean-Philippe Lang -
r8411:81cf6b234397
parent child
Show More
@@ -1,351 +1,361
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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, :create]
19 menu_item :new_issue, :only => [:new, :create]
20 default_search_scope :issues
20 default_search_scope :issues
21
21
22 before_filter :find_issue, :only => [:show, :edit, :update]
22 before_filter :find_issue, :only => [:show, :edit, :update]
23 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :move, :perform_move, :destroy]
23 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :move, :perform_move, :destroy]
24 before_filter :check_project_uniqueness, :only => [:move, :perform_move]
24 before_filter :check_project_uniqueness, :only => [:move, :perform_move]
25 before_filter :find_project, :only => [:new, :create]
25 before_filter :find_project, :only => [:new, :create]
26 before_filter :authorize, :except => [:index]
26 before_filter :authorize, :except => [:index]
27 before_filter :find_optional_project, :only => [:index]
27 before_filter :find_optional_project, :only => [:index]
28 before_filter :check_for_default_issue_status, :only => [:new, :create]
28 before_filter :check_for_default_issue_status, :only => [:new, :create]
29 before_filter :build_new_issue_from_params, :only => [:new, :create]
29 before_filter :build_new_issue_from_params, :only => [:new, :create]
30 accept_rss_auth :index, :show
30 accept_rss_auth :index, :show
31 accept_api_auth :index, :show, :create, :update, :destroy
31 accept_api_auth :index, :show, :create, :update, :destroy
32
32
33 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
33 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
34
34
35 helper :journals
35 helper :journals
36 helper :projects
36 helper :projects
37 include ProjectsHelper
37 include ProjectsHelper
38 helper :custom_fields
38 helper :custom_fields
39 include CustomFieldsHelper
39 include CustomFieldsHelper
40 helper :issue_relations
40 helper :issue_relations
41 include IssueRelationsHelper
41 include IssueRelationsHelper
42 helper :watchers
42 helper :watchers
43 include WatchersHelper
43 include WatchersHelper
44 helper :attachments
44 helper :attachments
45 include AttachmentsHelper
45 include AttachmentsHelper
46 helper :queries
46 helper :queries
47 include QueriesHelper
47 include QueriesHelper
48 helper :repositories
48 helper :repositories
49 include RepositoriesHelper
49 include RepositoriesHelper
50 helper :sort
50 helper :sort
51 include SortHelper
51 include SortHelper
52 include IssuesHelper
52 include IssuesHelper
53 helper :timelog
53 helper :timelog
54 helper :gantt
54 helper :gantt
55 include Redmine::Export::PDF
55 include Redmine::Export::PDF
56
56
57 verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
57 verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed }
58 verify :method => :post, :only => :bulk_update, :render => {:nothing => true, :status => :method_not_allowed }
58 verify :method => :post, :only => :bulk_update, :render => {:nothing => true, :status => :method_not_allowed }
59 verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
59 verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
60
60
61 def index
61 def index
62 retrieve_query
62 retrieve_query
63 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
63 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
64 sort_update(@query.sortable_columns)
64 sort_update(@query.sortable_columns)
65
65
66 if @query.valid?
66 if @query.valid?
67 case params[:format]
67 case params[:format]
68 when 'csv', 'pdf'
68 when 'csv', 'pdf'
69 @limit = Setting.issues_export_limit.to_i
69 @limit = Setting.issues_export_limit.to_i
70 when 'atom'
70 when 'atom'
71 @limit = Setting.feeds_limit.to_i
71 @limit = Setting.feeds_limit.to_i
72 when 'xml', 'json'
72 when 'xml', 'json'
73 @offset, @limit = api_offset_and_limit
73 @offset, @limit = api_offset_and_limit
74 else
74 else
75 @limit = per_page_option
75 @limit = per_page_option
76 end
76 end
77
77
78 @issue_count = @query.issue_count
78 @issue_count = @query.issue_count
79 @issue_pages = Paginator.new self, @issue_count, @limit, params['page']
79 @issue_pages = Paginator.new self, @issue_count, @limit, params['page']
80 @offset ||= @issue_pages.current.offset
80 @offset ||= @issue_pages.current.offset
81 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
81 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
82 :order => sort_clause,
82 :order => sort_clause,
83 :offset => @offset,
83 :offset => @offset,
84 :limit => @limit)
84 :limit => @limit)
85 @issue_count_by_group = @query.issue_count_by_group
85 @issue_count_by_group = @query.issue_count_by_group
86
86
87 respond_to do |format|
87 respond_to do |format|
88 format.html { render :template => 'issues/index', :layout => !request.xhr? }
88 format.html { render :template => 'issues/index', :layout => !request.xhr? }
89 format.api {
89 format.api {
90 Issue.load_relations(@issues) if include_in_api_response?('relations')
90 Issue.load_relations(@issues) if include_in_api_response?('relations')
91 }
91 }
92 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
92 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
93 format.csv { send_data(issues_to_csv(@issues, @project, @query, params), :type => 'text/csv; header=present', :filename => 'export.csv') }
93 format.csv { send_data(issues_to_csv(@issues, @project, @query, params), :type => 'text/csv; header=present', :filename => 'export.csv') }
94 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
94 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
95 end
95 end
96 else
96 else
97 respond_to do |format|
97 respond_to do |format|
98 format.html { render(:template => 'issues/index', :layout => !request.xhr?) }
98 format.html { render(:template => 'issues/index', :layout => !request.xhr?) }
99 format.any(:atom, :csv, :pdf) { render(:nothing => true) }
99 format.any(:atom, :csv, :pdf) { render(:nothing => true) }
100 format.api { render_validation_errors(@query) }
100 format.api { render_validation_errors(@query) }
101 end
101 end
102 end
102 end
103 rescue ActiveRecord::RecordNotFound
103 rescue ActiveRecord::RecordNotFound
104 render_404
104 render_404
105 end
105 end
106
106
107 def show
107 def show
108 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
108 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
109 @journals.each_with_index {|j,i| j.indice = i+1}
109 @journals.each_with_index {|j,i| j.indice = i+1}
110 @journals.reverse! if User.current.wants_comments_in_reverse_order?
110 @journals.reverse! if User.current.wants_comments_in_reverse_order?
111
111
112 if User.current.allowed_to?(:view_changesets, @project)
112 if User.current.allowed_to?(:view_changesets, @project)
113 @changesets = @issue.changesets.visible.all
113 @changesets = @issue.changesets.visible.all
114 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
114 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
115 end
115 end
116
116
117 @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
117 @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
118 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
118 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
119 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
119 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
120 @priorities = IssuePriority.active
120 @priorities = IssuePriority.active
121 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
121 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
122 respond_to do |format|
122 respond_to do |format|
123 format.html {
123 format.html {
124 retrieve_previous_and_next_issue_ids
124 retrieve_previous_and_next_issue_ids
125 render :template => 'issues/show'
125 render :template => 'issues/show'
126 }
126 }
127 format.api
127 format.api
128 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
128 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
129 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
129 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
130 end
130 end
131 end
131 end
132
132
133 # Add a new issue
133 # Add a new issue
134 # The new issue will be created from an existing one if copy_from parameter is given
134 # The new issue will be created from an existing one if copy_from parameter is given
135 def new
135 def new
136 respond_to do |format|
136 respond_to do |format|
137 format.html { render :action => 'new', :layout => !request.xhr? }
137 format.html { render :action => 'new', :layout => !request.xhr? }
138 format.js { render :partial => 'attributes' }
138 format.js {
139 render(:update) { |page|
140 if params[:project_change]
141 page.replace_html 'all_attributes', :partial => 'form'
142 else
143 page.replace_html 'attributes', :partial => 'attributes'
144 end
145 m = User.current.allowed_to?(:log_time, @issue.project) ? 'show' : 'hide'
146 page << "if ($('log_time')) {Element.#{m}('log_time');}"
147 }
148 }
139 end
149 end
140 end
150 end
141
151
142 def create
152 def create
143 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
153 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
144 if @issue.save
154 if @issue.save
145 attachments = Attachment.attach_files(@issue, params[:attachments])
155 attachments = Attachment.attach_files(@issue, params[:attachments])
146 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
156 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
147 respond_to do |format|
157 respond_to do |format|
148 format.html {
158 format.html {
149 render_attachment_warning_if_needed(@issue)
159 render_attachment_warning_if_needed(@issue)
150 flash[:notice] = l(:notice_issue_successful_create, :id => "<a href='#{issue_path(@issue)}'>##{@issue.id}</a>")
160 flash[:notice] = l(:notice_issue_successful_create, :id => "<a href='#{issue_path(@issue)}'>##{@issue.id}</a>")
151 redirect_to(params[:continue] ? { :action => 'new', :project_id => @project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } :
161 redirect_to(params[:continue] ? { :action => 'new', :project_id => @project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } :
152 { :action => 'show', :id => @issue })
162 { :action => 'show', :id => @issue })
153 }
163 }
154 format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
164 format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
155 end
165 end
156 return
166 return
157 else
167 else
158 respond_to do |format|
168 respond_to do |format|
159 format.html { render :action => 'new' }
169 format.html { render :action => 'new' }
160 format.api { render_validation_errors(@issue) }
170 format.api { render_validation_errors(@issue) }
161 end
171 end
162 end
172 end
163 end
173 end
164
174
165 def edit
175 def edit
166 update_issue_from_params
176 update_issue_from_params
167
177
168 @journal = @issue.current_journal
178 @journal = @issue.current_journal
169
179
170 respond_to do |format|
180 respond_to do |format|
171 format.html { }
181 format.html { }
172 format.xml { }
182 format.xml { }
173 end
183 end
174 end
184 end
175
185
176 def update
186 def update
177 update_issue_from_params
187 update_issue_from_params
178
188
179 if @issue.save_issue_with_child_records(params, @time_entry)
189 if @issue.save_issue_with_child_records(params, @time_entry)
180 render_attachment_warning_if_needed(@issue)
190 render_attachment_warning_if_needed(@issue)
181 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
191 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
182
192
183 respond_to do |format|
193 respond_to do |format|
184 format.html { redirect_back_or_default({:action => 'show', :id => @issue}) }
194 format.html { redirect_back_or_default({:action => 'show', :id => @issue}) }
185 format.api { head :ok }
195 format.api { head :ok }
186 end
196 end
187 else
197 else
188 render_attachment_warning_if_needed(@issue)
198 render_attachment_warning_if_needed(@issue)
189 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
199 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
190 @journal = @issue.current_journal
200 @journal = @issue.current_journal
191
201
192 respond_to do |format|
202 respond_to do |format|
193 format.html { render :action => 'edit' }
203 format.html { render :action => 'edit' }
194 format.api { render_validation_errors(@issue) }
204 format.api { render_validation_errors(@issue) }
195 end
205 end
196 end
206 end
197 end
207 end
198
208
199 # Bulk edit a set of issues
209 # Bulk edit a set of issues
200 def bulk_edit
210 def bulk_edit
201 @issues.sort!
211 @issues.sort!
202 @available_statuses = @projects.map{|p|Workflow.available_statuses(p)}.inject{|memo,w|memo & w}
212 @available_statuses = @projects.map{|p|Workflow.available_statuses(p)}.inject{|memo,w|memo & w}
203 @custom_fields = @projects.map{|p|p.all_issue_custom_fields}.inject{|memo,c|memo & c}
213 @custom_fields = @projects.map{|p|p.all_issue_custom_fields}.inject{|memo,c|memo & c}
204 @assignables = @projects.map(&:assignable_users).inject{|memo,a| memo & a}
214 @assignables = @projects.map(&:assignable_users).inject{|memo,a| memo & a}
205 @trackers = @projects.map(&:trackers).inject{|memo,t| memo & t}
215 @trackers = @projects.map(&:trackers).inject{|memo,t| memo & t}
206 end
216 end
207
217
208 def bulk_update
218 def bulk_update
209 @issues.sort!
219 @issues.sort!
210 attributes = parse_params_for_bulk_issue_attributes(params)
220 attributes = parse_params_for_bulk_issue_attributes(params)
211
221
212 unsaved_issue_ids = []
222 unsaved_issue_ids = []
213 @issues.each do |issue|
223 @issues.each do |issue|
214 issue.reload
224 issue.reload
215 journal = issue.init_journal(User.current, params[:notes])
225 journal = issue.init_journal(User.current, params[:notes])
216 issue.safe_attributes = attributes
226 issue.safe_attributes = attributes
217 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
227 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
218 unless issue.save
228 unless issue.save
219 # Keep unsaved issue ids to display them in flash error
229 # Keep unsaved issue ids to display them in flash error
220 unsaved_issue_ids << issue.id
230 unsaved_issue_ids << issue.id
221 end
231 end
222 end
232 end
223 set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
233 set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
224 redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
234 redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
225 end
235 end
226
236
227 verify :method => :delete, :only => :destroy, :render => { :nothing => true, :status => :method_not_allowed }
237 verify :method => :delete, :only => :destroy, :render => { :nothing => true, :status => :method_not_allowed }
228 def destroy
238 def destroy
229 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
239 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
230 if @hours > 0
240 if @hours > 0
231 case params[:todo]
241 case params[:todo]
232 when 'destroy'
242 when 'destroy'
233 # nothing to do
243 # nothing to do
234 when 'nullify'
244 when 'nullify'
235 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
245 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
236 when 'reassign'
246 when 'reassign'
237 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
247 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
238 if reassign_to.nil?
248 if reassign_to.nil?
239 flash.now[:error] = l(:error_issue_not_found_in_project)
249 flash.now[:error] = l(:error_issue_not_found_in_project)
240 return
250 return
241 else
251 else
242 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
252 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
243 end
253 end
244 else
254 else
245 # display the destroy form if it's a user request
255 # display the destroy form if it's a user request
246 return unless api_request?
256 return unless api_request?
247 end
257 end
248 end
258 end
249 @issues.each do |issue|
259 @issues.each do |issue|
250 begin
260 begin
251 issue.reload.destroy
261 issue.reload.destroy
252 rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists
262 rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists
253 # nothing to do, issue was already deleted (eg. by a parent)
263 # nothing to do, issue was already deleted (eg. by a parent)
254 end
264 end
255 end
265 end
256 respond_to do |format|
266 respond_to do |format|
257 format.html { redirect_back_or_default(:action => 'index', :project_id => @project) }
267 format.html { redirect_back_or_default(:action => 'index', :project_id => @project) }
258 format.api { head :ok }
268 format.api { head :ok }
259 end
269 end
260 end
270 end
261
271
262 private
272 private
263 def find_issue
273 def find_issue
264 # Issue.visible.find(...) can not be used to redirect user to the login form
274 # Issue.visible.find(...) can not be used to redirect user to the login form
265 # if the issue actually exists but requires authentication
275 # if the issue actually exists but requires authentication
266 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
276 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
267 unless @issue.visible?
277 unless @issue.visible?
268 deny_access
278 deny_access
269 return
279 return
270 end
280 end
271 @project = @issue.project
281 @project = @issue.project
272 rescue ActiveRecord::RecordNotFound
282 rescue ActiveRecord::RecordNotFound
273 render_404
283 render_404
274 end
284 end
275
285
276 def find_project
286 def find_project
277 project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id]
287 project_id = params[:project_id] || (params[:issue] && params[:issue][:project_id])
278 @project = Project.find(project_id)
288 @project = Project.find(project_id)
279 rescue ActiveRecord::RecordNotFound
289 rescue ActiveRecord::RecordNotFound
280 render_404
290 render_404
281 end
291 end
282
292
283 def retrieve_previous_and_next_issue_ids
293 def retrieve_previous_and_next_issue_ids
284 retrieve_query_from_session
294 retrieve_query_from_session
285 if @query
295 if @query
286 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
296 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
287 sort_update(@query.sortable_columns, 'issues_index_sort')
297 sort_update(@query.sortable_columns, 'issues_index_sort')
288 limit = 500
298 limit = 500
289 issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1))
299 issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1))
290 if (idx = issue_ids.index(@issue.id)) && idx < limit
300 if (idx = issue_ids.index(@issue.id)) && idx < limit
291 @prev_issue_id = issue_ids[idx - 1] if idx > 0
301 @prev_issue_id = issue_ids[idx - 1] if idx > 0
292 @next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
302 @next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
293 end
303 end
294 end
304 end
295 end
305 end
296
306
297 # Used by #edit and #update to set some common instance variables
307 # Used by #edit and #update to set some common instance variables
298 # from the params
308 # from the params
299 # TODO: Refactor, not everything in here is needed by #edit
309 # TODO: Refactor, not everything in here is needed by #edit
300 def update_issue_from_params
310 def update_issue_from_params
301 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
311 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
302 @priorities = IssuePriority.active
312 @priorities = IssuePriority.active
303 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
313 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
304 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
314 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
305 @time_entry.attributes = params[:time_entry]
315 @time_entry.attributes = params[:time_entry]
306
316
307 @notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
317 @notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
308 @issue.init_journal(User.current, @notes)
318 @issue.init_journal(User.current, @notes)
309 @issue.safe_attributes = params[:issue]
319 @issue.safe_attributes = params[:issue]
310 end
320 end
311
321
312 # TODO: Refactor, lots of extra code in here
322 # TODO: Refactor, lots of extra code in here
313 # TODO: Changing tracker on an existing issue should not trigger this
323 # TODO: Changing tracker on an existing issue should not trigger this
314 def build_new_issue_from_params
324 def build_new_issue_from_params
315 if params[:id].blank?
325 if params[:id].blank?
316 @issue = Issue.new
326 @issue = Issue.new
317 @issue.copy_from(params[:copy_from]) if params[:copy_from]
327 @issue.copy_from(params[:copy_from]) if params[:copy_from]
318 @issue.project = @project
328 @issue.project = @project
319 else
329 else
320 @issue = @project.issues.visible.find(params[:id])
330 @issue = @project.issues.visible.find(params[:id])
321 end
331 end
322
332
323 @issue.project = @project
333 @issue.project = @project
324 @issue.author = User.current
334 @issue.author = User.current
325 # Tracker must be set before custom field values
335 # Tracker must be set before custom field values
326 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
336 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
327 if @issue.tracker.nil?
337 if @issue.tracker.nil?
328 render_error l(:error_no_tracker_in_project)
338 render_error l(:error_no_tracker_in_project)
329 return false
339 return false
330 end
340 end
331 @issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
341 @issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
332 @issue.safe_attributes = params[:issue]
342 @issue.safe_attributes = params[:issue]
333
343
334 @priorities = IssuePriority.active
344 @priorities = IssuePriority.active
335 @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
345 @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
336 end
346 end
337
347
338 def check_for_default_issue_status
348 def check_for_default_issue_status
339 if IssueStatus.default.nil?
349 if IssueStatus.default.nil?
340 render_error l(:error_no_default_issue_status)
350 render_error l(:error_no_default_issue_status)
341 return false
351 return false
342 end
352 end
343 end
353 end
344
354
345 def parse_params_for_bulk_issue_attributes(params)
355 def parse_params_for_bulk_issue_attributes(params)
346 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
356 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
347 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
357 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
348 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
358 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
349 attributes
359 attributes
350 end
360 end
351 end
361 end
@@ -1,982 +1,1008
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 Issue < ActiveRecord::Base
18 class Issue < ActiveRecord::Base
19 include Redmine::SafeAttributes
19 include Redmine::SafeAttributes
20
20
21 belongs_to :project
21 belongs_to :project
22 belongs_to :tracker
22 belongs_to :tracker
23 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
23 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
24 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
24 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
25 belongs_to :assigned_to, :class_name => 'Principal', :foreign_key => 'assigned_to_id'
25 belongs_to :assigned_to, :class_name => 'Principal', :foreign_key => 'assigned_to_id'
26 belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
26 belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
27 belongs_to :priority, :class_name => 'IssuePriority', :foreign_key => 'priority_id'
27 belongs_to :priority, :class_name => 'IssuePriority', :foreign_key => 'priority_id'
28 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
28 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
29
29
30 has_many :journals, :as => :journalized, :dependent => :destroy
30 has_many :journals, :as => :journalized, :dependent => :destroy
31 has_many :time_entries, :dependent => :delete_all
31 has_many :time_entries, :dependent => :delete_all
32 has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
32 has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
33
33
34 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
34 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
35 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
35 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
36
36
37 acts_as_nested_set :scope => 'root_id', :dependent => :destroy
37 acts_as_nested_set :scope => 'root_id', :dependent => :destroy
38 acts_as_attachable :after_add => :attachment_added, :after_remove => :attachment_removed
38 acts_as_attachable :after_add => :attachment_added, :after_remove => :attachment_removed
39 acts_as_customizable
39 acts_as_customizable
40 acts_as_watchable
40 acts_as_watchable
41 acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"],
41 acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"],
42 :include => [:project, :journals],
42 :include => [:project, :journals],
43 # sort by id so that limited eager loading doesn't break with postgresql
43 # sort by id so that limited eager loading doesn't break with postgresql
44 :order_column => "#{table_name}.id"
44 :order_column => "#{table_name}.id"
45 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"},
45 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"},
46 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}},
46 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}},
47 :type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') }
47 :type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') }
48
48
49 acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
49 acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
50 :author_key => :author_id
50 :author_key => :author_id
51
51
52 DONE_RATIO_OPTIONS = %w(issue_field issue_status)
52 DONE_RATIO_OPTIONS = %w(issue_field issue_status)
53
53
54 attr_reader :current_journal
54 attr_reader :current_journal
55
55
56 validates_presence_of :subject, :priority, :project, :tracker, :author, :status
56 validates_presence_of :subject, :priority, :project, :tracker, :author, :status
57
57
58 validates_length_of :subject, :maximum => 255
58 validates_length_of :subject, :maximum => 255
59 validates_inclusion_of :done_ratio, :in => 0..100
59 validates_inclusion_of :done_ratio, :in => 0..100
60 validates_numericality_of :estimated_hours, :allow_nil => true
60 validates_numericality_of :estimated_hours, :allow_nil => true
61 validate :validate_issue
61 validate :validate_issue
62
62
63 named_scope :visible, lambda {|*args| { :include => :project,
63 named_scope :visible, lambda {|*args| { :include => :project,
64 :conditions => Issue.visible_condition(args.shift || User.current, *args) } }
64 :conditions => Issue.visible_condition(args.shift || User.current, *args) } }
65
65
66 named_scope :open, lambda {|*args|
66 named_scope :open, lambda {|*args|
67 is_closed = args.size > 0 ? !args.first : false
67 is_closed = args.size > 0 ? !args.first : false
68 {:conditions => ["#{IssueStatus.table_name}.is_closed = ?", is_closed], :include => :status}
68 {:conditions => ["#{IssueStatus.table_name}.is_closed = ?", is_closed], :include => :status}
69 }
69 }
70
70
71 named_scope :recently_updated, :order => "#{Issue.table_name}.updated_on DESC"
71 named_scope :recently_updated, :order => "#{Issue.table_name}.updated_on DESC"
72 named_scope :with_limit, lambda { |limit| { :limit => limit} }
72 named_scope :with_limit, lambda { |limit| { :limit => limit} }
73 named_scope :on_active_project, :include => [:status, :project, :tracker],
73 named_scope :on_active_project, :include => [:status, :project, :tracker],
74 :conditions => ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"]
74 :conditions => ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"]
75
75
76 before_create :default_assign
76 before_create :default_assign
77 before_save :close_duplicates, :update_done_ratio_from_issue_status
77 before_save :close_duplicates, :update_done_ratio_from_issue_status
78 after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?}
78 after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?}
79 after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal
79 after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal
80 after_destroy :update_parent_attributes
80 after_destroy :update_parent_attributes
81
81
82 # Returns a SQL conditions string used to find all issues visible by the specified user
82 # Returns a SQL conditions string used to find all issues visible by the specified user
83 def self.visible_condition(user, options={})
83 def self.visible_condition(user, options={})
84 Project.allowed_to_condition(user, :view_issues, options) do |role, user|
84 Project.allowed_to_condition(user, :view_issues, options) do |role, user|
85 case role.issues_visibility
85 case role.issues_visibility
86 when 'all'
86 when 'all'
87 nil
87 nil
88 when 'default'
88 when 'default'
89 user_ids = [user.id] + user.groups.map(&:id)
89 user_ids = [user.id] + user.groups.map(&:id)
90 "(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
90 "(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
91 when 'own'
91 when 'own'
92 user_ids = [user.id] + user.groups.map(&:id)
92 user_ids = [user.id] + user.groups.map(&:id)
93 "(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
93 "(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
94 else
94 else
95 '1=0'
95 '1=0'
96 end
96 end
97 end
97 end
98 end
98 end
99
99
100 # Returns true if usr or current user is allowed to view the issue
100 # Returns true if usr or current user is allowed to view the issue
101 def visible?(usr=nil)
101 def visible?(usr=nil)
102 (usr || User.current).allowed_to?(:view_issues, self.project) do |role, user|
102 (usr || User.current).allowed_to?(:view_issues, self.project) do |role, user|
103 case role.issues_visibility
103 case role.issues_visibility
104 when 'all'
104 when 'all'
105 true
105 true
106 when 'default'
106 when 'default'
107 !self.is_private? || self.author == user || user.is_or_belongs_to?(assigned_to)
107 !self.is_private? || self.author == user || user.is_or_belongs_to?(assigned_to)
108 when 'own'
108 when 'own'
109 self.author == user || user.is_or_belongs_to?(assigned_to)
109 self.author == user || user.is_or_belongs_to?(assigned_to)
110 else
110 else
111 false
111 false
112 end
112 end
113 end
113 end
114 end
114 end
115
115
116 def initialize(attributes=nil, *args)
116 def initialize(attributes=nil, *args)
117 super
117 super
118 if new_record?
118 if new_record?
119 # set default values for new records only
119 # set default values for new records only
120 self.status ||= IssueStatus.default
120 self.status ||= IssueStatus.default
121 self.priority ||= IssuePriority.default
121 self.priority ||= IssuePriority.default
122 end
122 end
123 end
123 end
124
124
125 # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
125 # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
126 def available_custom_fields
126 def available_custom_fields
127 (project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields.all) : []
127 (project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields.all) : []
128 end
128 end
129
129
130 def copy_from(arg)
130 def copy_from(arg)
131 issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg)
131 issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg)
132 self.attributes = issue.attributes.dup.except("id", "root_id", "parent_id", "lft", "rgt", "created_on", "updated_on")
132 self.attributes = issue.attributes.dup.except("id", "root_id", "parent_id", "lft", "rgt", "created_on", "updated_on")
133 self.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
133 self.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
134 self.status = issue.status
134 self.status = issue.status
135 self.author = User.current
135 self.author = User.current
136 self
136 self
137 end
137 end
138
138
139 # Moves/copies an issue to a new project and tracker
139 # Moves/copies an issue to a new project and tracker
140 # Returns the moved/copied issue on success, false on failure
140 # Returns the moved/copied issue on success, false on failure
141 def move_to_project(new_project, new_tracker=nil, options={})
141 def move_to_project(new_project, new_tracker=nil, options={})
142 if options[:copy]
142 if options[:copy]
143 issue = self.class.new.copy_from(self)
143 issue = self.class.new.copy_from(self)
144 else
144 else
145 issue = self
145 issue = self
146 end
146 end
147
147
148 issue.init_journal(User.current, options[:notes])
148 issue.init_journal(User.current, options[:notes])
149
149
150 issue.project = new_project
150 # Preserve previous behaviour
151 # #move_to_project doesn't change tracker automatically
152 issue.send :project=, new_project, true
151 if new_tracker
153 if new_tracker
152 issue.tracker = new_tracker
154 issue.tracker = new_tracker
153 end
155 end
154 # Allow bulk setting of attributes on the issue
156 # Allow bulk setting of attributes on the issue
155 if options[:attributes]
157 if options[:attributes]
156 issue.attributes = options[:attributes]
158 issue.attributes = options[:attributes]
157 end
159 end
158
160
159 issue.save ? issue : false
161 issue.save ? issue : false
160 end
162 end
161
163
162 def status_id=(sid)
164 def status_id=(sid)
163 self.status = nil
165 self.status = nil
164 write_attribute(:status_id, sid)
166 write_attribute(:status_id, sid)
165 end
167 end
166
168
167 def priority_id=(pid)
169 def priority_id=(pid)
168 self.priority = nil
170 self.priority = nil
169 write_attribute(:priority_id, pid)
171 write_attribute(:priority_id, pid)
170 end
172 end
171
173
174 def category_id=(cid)
175 self.category = nil
176 write_attribute(:category_id, cid)
177 end
178
179 def fixed_version_id=(vid)
180 self.fixed_version = nil
181 write_attribute(:fixed_version_id, vid)
182 end
183
172 def tracker_id=(tid)
184 def tracker_id=(tid)
173 self.tracker = nil
185 self.tracker = nil
174 result = write_attribute(:tracker_id, tid)
186 result = write_attribute(:tracker_id, tid)
175 @custom_field_values = nil
187 @custom_field_values = nil
176 result
188 result
177 end
189 end
178
190
179 def project_id=(project_id)
191 def project_id=(project_id)
180 if project_id.to_s != self.project_id.to_s
192 if project_id.to_s != self.project_id.to_s
181 self.project = (project_id.present? ? Project.find_by_id(project_id) : nil)
193 self.project = (project_id.present? ? Project.find_by_id(project_id) : nil)
182 end
194 end
183 end
195 end
184
196
185 def project=(project)
197 def project=(project, keep_tracker=false)
186 project_was = self.project
198 project_was = self.project
187 write_attribute(:project_id, project ? project.id : nil)
199 write_attribute(:project_id, project ? project.id : nil)
188 association_instance_set('project', project)
200 association_instance_set('project', project)
189 if project_was && project && project_was != project
201 if project_was && project && project_was != project
202 unless keep_tracker || project.trackers.include?(tracker)
203 self.tracker = project.trackers.first
204 end
190 # Reassign to the category with same name if any
205 # Reassign to the category with same name if any
191 if category
206 if category
192 self.category = project.issue_categories.find_by_name(category.name)
207 self.category = project.issue_categories.find_by_name(category.name)
193 end
208 end
194 # Keep the fixed_version if it's still valid in the new_project
209 # Keep the fixed_version if it's still valid in the new_project
195 if fixed_version && fixed_version.project != project && !project.shared_versions.include?(fixed_version)
210 if fixed_version && fixed_version.project != project && !project.shared_versions.include?(fixed_version)
196 self.fixed_version = nil
211 self.fixed_version = nil
197 end
212 end
198 if parent && parent.project_id != project_id
213 if parent && parent.project_id != project_id
199 self.parent_issue_id = nil
214 self.parent_issue_id = nil
200 end
215 end
201 @custom_field_values = nil
216 @custom_field_values = nil
202 end
217 end
203 end
218 end
204
219
205 def description=(arg)
220 def description=(arg)
206 if arg.is_a?(String)
221 if arg.is_a?(String)
207 arg = arg.gsub(/(\r\n|\n|\r)/, "\r\n")
222 arg = arg.gsub(/(\r\n|\n|\r)/, "\r\n")
208 end
223 end
209 write_attribute(:description, arg)
224 write_attribute(:description, arg)
210 end
225 end
211
226
212 # Overrides attributes= so that project and tracker get assigned first
227 # Overrides attributes= so that project and tracker get assigned first
213 def attributes_with_project_and_tracker_first=(new_attributes, *args)
228 def attributes_with_project_and_tracker_first=(new_attributes, *args)
214 return if new_attributes.nil?
229 return if new_attributes.nil?
215 attrs = new_attributes.dup
230 attrs = new_attributes.dup
216 attrs.stringify_keys!
231 attrs.stringify_keys!
217
232
218 %w(project project_id tracker tracker_id).each do |attr|
233 %w(project project_id tracker tracker_id).each do |attr|
219 if attrs.has_key?(attr)
234 if attrs.has_key?(attr)
220 send "#{attr}=", attrs.delete(attr)
235 send "#{attr}=", attrs.delete(attr)
221 end
236 end
222 end
237 end
223 send :attributes_without_project_and_tracker_first=, attrs, *args
238 send :attributes_without_project_and_tracker_first=, attrs, *args
224 end
239 end
225 # Do not redefine alias chain on reload (see #4838)
240 # Do not redefine alias chain on reload (see #4838)
226 alias_method_chain(:attributes=, :project_and_tracker_first) unless method_defined?(:attributes_without_project_and_tracker_first=)
241 alias_method_chain(:attributes=, :project_and_tracker_first) unless method_defined?(:attributes_without_project_and_tracker_first=)
227
242
228 def estimated_hours=(h)
243 def estimated_hours=(h)
229 write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
244 write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
230 end
245 end
231
246
247 safe_attributes 'project_id',
248 :if => lambda {|issue, user|
249 projects = Issue.allowed_target_projects_on_move(user)
250 projects.include?(issue.project) && projects.size > 1
251 }
252
232 safe_attributes 'tracker_id',
253 safe_attributes 'tracker_id',
233 'status_id',
254 'status_id',
234 'category_id',
255 'category_id',
235 'assigned_to_id',
256 'assigned_to_id',
236 'priority_id',
257 'priority_id',
237 'fixed_version_id',
258 'fixed_version_id',
238 'subject',
259 'subject',
239 'description',
260 'description',
240 'start_date',
261 'start_date',
241 'due_date',
262 'due_date',
242 'done_ratio',
263 'done_ratio',
243 'estimated_hours',
264 'estimated_hours',
244 'custom_field_values',
265 'custom_field_values',
245 'custom_fields',
266 'custom_fields',
246 'lock_version',
267 'lock_version',
247 :if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) }
268 :if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) }
248
269
249 safe_attributes 'status_id',
270 safe_attributes 'status_id',
250 'assigned_to_id',
271 'assigned_to_id',
251 'fixed_version_id',
272 'fixed_version_id',
252 'done_ratio',
273 'done_ratio',
253 'lock_version',
274 'lock_version',
254 :if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? }
275 :if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? }
255
276
256 safe_attributes 'watcher_user_ids',
277 safe_attributes 'watcher_user_ids',
257 :if => lambda {|issue, user| issue.new_record? && user.allowed_to?(:add_issue_watchers, issue.project)}
278 :if => lambda {|issue, user| issue.new_record? && user.allowed_to?(:add_issue_watchers, issue.project)}
258
279
259 safe_attributes 'is_private',
280 safe_attributes 'is_private',
260 :if => lambda {|issue, user|
281 :if => lambda {|issue, user|
261 user.allowed_to?(:set_issues_private, issue.project) ||
282 user.allowed_to?(:set_issues_private, issue.project) ||
262 (issue.author == user && user.allowed_to?(:set_own_issues_private, issue.project))
283 (issue.author == user && user.allowed_to?(:set_own_issues_private, issue.project))
263 }
284 }
264
285
265 safe_attributes 'parent_issue_id',
286 safe_attributes 'parent_issue_id',
266 :if => lambda {|issue, user| (issue.new_record? || user.allowed_to?(:edit_issues, issue.project)) &&
287 :if => lambda {|issue, user| (issue.new_record? || user.allowed_to?(:edit_issues, issue.project)) &&
267 user.allowed_to?(:manage_subtasks, issue.project)}
288 user.allowed_to?(:manage_subtasks, issue.project)}
268
289
269 # Safely sets attributes
290 # Safely sets attributes
270 # Should be called from controllers instead of #attributes=
291 # Should be called from controllers instead of #attributes=
271 # attr_accessible is too rough because we still want things like
292 # attr_accessible is too rough because we still want things like
272 # Issue.new(:project => foo) to work
293 # Issue.new(:project => foo) to work
273 # TODO: move workflow/permission checks from controllers to here
294 # TODO: move workflow/permission checks from controllers to here
274 def safe_attributes=(attrs, user=User.current)
295 def safe_attributes=(attrs, user=User.current)
275 return unless attrs.is_a?(Hash)
296 return unless attrs.is_a?(Hash)
276
297
277 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
298 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
278 attrs = delete_unsafe_attributes(attrs, user)
299 attrs = delete_unsafe_attributes(attrs, user)
279 return if attrs.empty?
300 return if attrs.empty?
280
301
281 # Tracker must be set before since new_statuses_allowed_to depends on it.
302 # Project and Tracker must be set before since new_statuses_allowed_to depends on it.
303 if p = attrs.delete('project_id')
304 self.project_id = p
305 end
306
282 if t = attrs.delete('tracker_id')
307 if t = attrs.delete('tracker_id')
283 self.tracker_id = t
308 self.tracker_id = t
284 end
309 end
285
310
286 if attrs['status_id']
311 if attrs['status_id']
287 unless new_statuses_allowed_to(user).collect(&:id).include?(attrs['status_id'].to_i)
312 unless new_statuses_allowed_to(user).collect(&:id).include?(attrs['status_id'].to_i)
288 attrs.delete('status_id')
313 attrs.delete('status_id')
289 end
314 end
290 end
315 end
291
316
292 unless leaf?
317 unless leaf?
293 attrs.reject! {|k,v| %w(priority_id done_ratio start_date due_date estimated_hours).include?(k)}
318 attrs.reject! {|k,v| %w(priority_id done_ratio start_date due_date estimated_hours).include?(k)}
294 end
319 end
295
320
296 if attrs['parent_issue_id'].present?
321 if attrs['parent_issue_id'].present?
297 attrs.delete('parent_issue_id') unless Issue.visible(user).exists?(attrs['parent_issue_id'].to_i)
322 attrs.delete('parent_issue_id') unless Issue.visible(user).exists?(attrs['parent_issue_id'].to_i)
298 end
323 end
299
324
300 # mass-assignment security bypass
325 # mass-assignment security bypass
301 self.send :attributes=, attrs, false
326 self.send :attributes=, attrs, false
302 end
327 end
303
328
304 def done_ratio
329 def done_ratio
305 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
330 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
306 status.default_done_ratio
331 status.default_done_ratio
307 else
332 else
308 read_attribute(:done_ratio)
333 read_attribute(:done_ratio)
309 end
334 end
310 end
335 end
311
336
312 def self.use_status_for_done_ratio?
337 def self.use_status_for_done_ratio?
313 Setting.issue_done_ratio == 'issue_status'
338 Setting.issue_done_ratio == 'issue_status'
314 end
339 end
315
340
316 def self.use_field_for_done_ratio?
341 def self.use_field_for_done_ratio?
317 Setting.issue_done_ratio == 'issue_field'
342 Setting.issue_done_ratio == 'issue_field'
318 end
343 end
319
344
320 def validate_issue
345 def validate_issue
321 if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
346 if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
322 errors.add :due_date, :not_a_date
347 errors.add :due_date, :not_a_date
323 end
348 end
324
349
325 if self.due_date and self.start_date and self.due_date < self.start_date
350 if self.due_date and self.start_date and self.due_date < self.start_date
326 errors.add :due_date, :greater_than_start_date
351 errors.add :due_date, :greater_than_start_date
327 end
352 end
328
353
329 if start_date && soonest_start && start_date < soonest_start
354 if start_date && soonest_start && start_date < soonest_start
330 errors.add :start_date, :invalid
355 errors.add :start_date, :invalid
331 end
356 end
332
357
333 if fixed_version
358 if fixed_version
334 if !assignable_versions.include?(fixed_version)
359 if !assignable_versions.include?(fixed_version)
335 errors.add :fixed_version_id, :inclusion
360 errors.add :fixed_version_id, :inclusion
336 elsif reopened? && fixed_version.closed?
361 elsif reopened? && fixed_version.closed?
337 errors.add :base, I18n.t(:error_can_not_reopen_issue_on_closed_version)
362 errors.add :base, I18n.t(:error_can_not_reopen_issue_on_closed_version)
338 end
363 end
339 end
364 end
340
365
341 # Checks that the issue can not be added/moved to a disabled tracker
366 # Checks that the issue can not be added/moved to a disabled tracker
342 if project && (tracker_id_changed? || project_id_changed?)
367 if project && (tracker_id_changed? || project_id_changed?)
343 unless project.trackers.include?(tracker)
368 unless project.trackers.include?(tracker)
344 errors.add :tracker_id, :inclusion
369 errors.add :tracker_id, :inclusion
345 end
370 end
346 end
371 end
347
372
348 # Checks parent issue assignment
373 # Checks parent issue assignment
349 if @parent_issue
374 if @parent_issue
350 if @parent_issue.project_id != project_id
375 if @parent_issue.project_id != project_id
351 errors.add :parent_issue_id, :not_same_project
376 errors.add :parent_issue_id, :not_same_project
352 elsif !new_record?
377 elsif !new_record?
353 # moving an existing issue
378 # moving an existing issue
354 if @parent_issue.root_id != root_id
379 if @parent_issue.root_id != root_id
355 # we can always move to another tree
380 # we can always move to another tree
356 elsif move_possible?(@parent_issue)
381 elsif move_possible?(@parent_issue)
357 # move accepted inside tree
382 # move accepted inside tree
358 else
383 else
359 errors.add :parent_issue_id, :not_a_valid_parent
384 errors.add :parent_issue_id, :not_a_valid_parent
360 end
385 end
361 end
386 end
362 end
387 end
363 end
388 end
364
389
365 # Set the done_ratio using the status if that setting is set. This will keep the done_ratios
390 # Set the done_ratio using the status if that setting is set. This will keep the done_ratios
366 # even if the user turns off the setting later
391 # even if the user turns off the setting later
367 def update_done_ratio_from_issue_status
392 def update_done_ratio_from_issue_status
368 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
393 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
369 self.done_ratio = status.default_done_ratio
394 self.done_ratio = status.default_done_ratio
370 end
395 end
371 end
396 end
372
397
373 def init_journal(user, notes = "")
398 def init_journal(user, notes = "")
374 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
399 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
375 if new_record?
400 if new_record?
376 @current_journal.notify = false
401 @current_journal.notify = false
377 else
402 else
378 @attributes_before_change = attributes.dup
403 @attributes_before_change = attributes.dup
379 @custom_values_before_change = {}
404 @custom_values_before_change = {}
380 self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
405 self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
381 end
406 end
382 # Make sure updated_on is updated when adding a note.
407 # Make sure updated_on is updated when adding a note.
383 updated_on_will_change!
408 updated_on_will_change!
384 @current_journal
409 @current_journal
385 end
410 end
386
411
387 # Return true if the issue is closed, otherwise false
412 # Return true if the issue is closed, otherwise false
388 def closed?
413 def closed?
389 self.status.is_closed?
414 self.status.is_closed?
390 end
415 end
391
416
392 # Return true if the issue is being reopened
417 # Return true if the issue is being reopened
393 def reopened?
418 def reopened?
394 if !new_record? && status_id_changed?
419 if !new_record? && status_id_changed?
395 status_was = IssueStatus.find_by_id(status_id_was)
420 status_was = IssueStatus.find_by_id(status_id_was)
396 status_new = IssueStatus.find_by_id(status_id)
421 status_new = IssueStatus.find_by_id(status_id)
397 if status_was && status_new && status_was.is_closed? && !status_new.is_closed?
422 if status_was && status_new && status_was.is_closed? && !status_new.is_closed?
398 return true
423 return true
399 end
424 end
400 end
425 end
401 false
426 false
402 end
427 end
403
428
404 # Return true if the issue is being closed
429 # Return true if the issue is being closed
405 def closing?
430 def closing?
406 if !new_record? && status_id_changed?
431 if !new_record? && status_id_changed?
407 status_was = IssueStatus.find_by_id(status_id_was)
432 status_was = IssueStatus.find_by_id(status_id_was)
408 status_new = IssueStatus.find_by_id(status_id)
433 status_new = IssueStatus.find_by_id(status_id)
409 if status_was && status_new && !status_was.is_closed? && status_new.is_closed?
434 if status_was && status_new && !status_was.is_closed? && status_new.is_closed?
410 return true
435 return true
411 end
436 end
412 end
437 end
413 false
438 false
414 end
439 end
415
440
416 # Returns true if the issue is overdue
441 # Returns true if the issue is overdue
417 def overdue?
442 def overdue?
418 !due_date.nil? && (due_date < Date.today) && !status.is_closed?
443 !due_date.nil? && (due_date < Date.today) && !status.is_closed?
419 end
444 end
420
445
421 # Is the amount of work done less than it should for the due date
446 # Is the amount of work done less than it should for the due date
422 def behind_schedule?
447 def behind_schedule?
423 return false if start_date.nil? || due_date.nil?
448 return false if start_date.nil? || due_date.nil?
424 done_date = start_date + ((due_date - start_date+1)* done_ratio/100).floor
449 done_date = start_date + ((due_date - start_date+1)* done_ratio/100).floor
425 return done_date <= Date.today
450 return done_date <= Date.today
426 end
451 end
427
452
428 # Does this issue have children?
453 # Does this issue have children?
429 def children?
454 def children?
430 !leaf?
455 !leaf?
431 end
456 end
432
457
433 # Users the issue can be assigned to
458 # Users the issue can be assigned to
434 def assignable_users
459 def assignable_users
435 users = project.assignable_users
460 users = project.assignable_users
436 users << author if author
461 users << author if author
437 users << assigned_to if assigned_to
462 users << assigned_to if assigned_to
438 users.uniq.sort
463 users.uniq.sort
439 end
464 end
440
465
441 # Versions that the issue can be assigned to
466 # Versions that the issue can be assigned to
442 def assignable_versions
467 def assignable_versions
443 @assignable_versions ||= (project.shared_versions.open + [Version.find_by_id(fixed_version_id_was)]).compact.uniq.sort
468 @assignable_versions ||= (project.shared_versions.open + [Version.find_by_id(fixed_version_id_was)]).compact.uniq.sort
444 end
469 end
445
470
446 # Returns true if this issue is blocked by another issue that is still open
471 # Returns true if this issue is blocked by another issue that is still open
447 def blocked?
472 def blocked?
448 !relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil?
473 !relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil?
449 end
474 end
450
475
451 # Returns an array of status that user is able to apply
476 # Returns an array of status that user is able to apply
452 def new_statuses_allowed_to(user, include_default=false)
477 def new_statuses_allowed_to(user, include_default=false)
453 statuses = status.find_new_statuses_allowed_to(
478 statuses = status.find_new_statuses_allowed_to(
454 user.roles_for_project(project),
479 user.roles_for_project(project),
455 tracker,
480 tracker,
456 author == user,
481 author == user,
457 assigned_to_id_changed? ? assigned_to_id_was == user.id : assigned_to_id == user.id
482 assigned_to_id_changed? ? assigned_to_id_was == user.id : assigned_to_id == user.id
458 )
483 )
459 statuses << status unless statuses.empty?
484 statuses << status unless statuses.empty?
460 statuses << IssueStatus.default if include_default
485 statuses << IssueStatus.default if include_default
461 statuses = statuses.uniq.sort
486 statuses = statuses.uniq.sort
462 blocked? ? statuses.reject {|s| s.is_closed?} : statuses
487 blocked? ? statuses.reject {|s| s.is_closed?} : statuses
463 end
488 end
464
489
465 # Returns the mail adresses of users that should be notified
490 # Returns the mail adresses of users that should be notified
466 def recipients
491 def recipients
467 notified = project.notified_users
492 notified = project.notified_users
468 # Author and assignee are always notified unless they have been
493 # Author and assignee are always notified unless they have been
469 # locked or don't want to be notified
494 # locked or don't want to be notified
470 notified << author if author && author.active? && author.notify_about?(self)
495 notified << author if author && author.active? && author.notify_about?(self)
471 if assigned_to
496 if assigned_to
472 if assigned_to.is_a?(Group)
497 if assigned_to.is_a?(Group)
473 notified += assigned_to.users.select {|u| u.active? && u.notify_about?(self)}
498 notified += assigned_to.users.select {|u| u.active? && u.notify_about?(self)}
474 else
499 else
475 notified << assigned_to if assigned_to.active? && assigned_to.notify_about?(self)
500 notified << assigned_to if assigned_to.active? && assigned_to.notify_about?(self)
476 end
501 end
477 end
502 end
478 notified.uniq!
503 notified.uniq!
479 # Remove users that can not view the issue
504 # Remove users that can not view the issue
480 notified.reject! {|user| !visible?(user)}
505 notified.reject! {|user| !visible?(user)}
481 notified.collect(&:mail)
506 notified.collect(&:mail)
482 end
507 end
483
508
484 # Returns the number of hours spent on this issue
509 # Returns the number of hours spent on this issue
485 def spent_hours
510 def spent_hours
486 @spent_hours ||= time_entries.sum(:hours) || 0
511 @spent_hours ||= time_entries.sum(:hours) || 0
487 end
512 end
488
513
489 # Returns the total number of hours spent on this issue and its descendants
514 # Returns the total number of hours spent on this issue and its descendants
490 #
515 #
491 # Example:
516 # Example:
492 # spent_hours => 0.0
517 # spent_hours => 0.0
493 # spent_hours => 50.2
518 # spent_hours => 50.2
494 def total_spent_hours
519 def total_spent_hours
495 @total_spent_hours ||= self_and_descendants.sum("#{TimeEntry.table_name}.hours",
520 @total_spent_hours ||= self_and_descendants.sum("#{TimeEntry.table_name}.hours",
496 :joins => "LEFT JOIN #{TimeEntry.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id").to_f || 0.0
521 :joins => "LEFT JOIN #{TimeEntry.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id").to_f || 0.0
497 end
522 end
498
523
499 def relations
524 def relations
500 @relations ||= (relations_from + relations_to).sort
525 @relations ||= (relations_from + relations_to).sort
501 end
526 end
502
527
503 # Preloads relations for a collection of issues
528 # Preloads relations for a collection of issues
504 def self.load_relations(issues)
529 def self.load_relations(issues)
505 if issues.any?
530 if issues.any?
506 relations = IssueRelation.all(:conditions => ["issue_from_id IN (:ids) OR issue_to_id IN (:ids)", {:ids => issues.map(&:id)}])
531 relations = IssueRelation.all(:conditions => ["issue_from_id IN (:ids) OR issue_to_id IN (:ids)", {:ids => issues.map(&:id)}])
507 issues.each do |issue|
532 issues.each do |issue|
508 issue.instance_variable_set "@relations", relations.select {|r| r.issue_from_id == issue.id || r.issue_to_id == issue.id}
533 issue.instance_variable_set "@relations", relations.select {|r| r.issue_from_id == issue.id || r.issue_to_id == issue.id}
509 end
534 end
510 end
535 end
511 end
536 end
512
537
513 # Preloads visible spent time for a collection of issues
538 # Preloads visible spent time for a collection of issues
514 def self.load_visible_spent_hours(issues, user=User.current)
539 def self.load_visible_spent_hours(issues, user=User.current)
515 if issues.any?
540 if issues.any?
516 hours_by_issue_id = TimeEntry.visible(user).sum(:hours, :group => :issue_id)
541 hours_by_issue_id = TimeEntry.visible(user).sum(:hours, :group => :issue_id)
517 issues.each do |issue|
542 issues.each do |issue|
518 issue.instance_variable_set "@spent_hours", (hours_by_issue_id[issue.id] || 0)
543 issue.instance_variable_set "@spent_hours", (hours_by_issue_id[issue.id] || 0)
519 end
544 end
520 end
545 end
521 end
546 end
522
547
523 # Finds an issue relation given its id.
548 # Finds an issue relation given its id.
524 def find_relation(relation_id)
549 def find_relation(relation_id)
525 IssueRelation.find(relation_id, :conditions => ["issue_to_id = ? OR issue_from_id = ?", id, id])
550 IssueRelation.find(relation_id, :conditions => ["issue_to_id = ? OR issue_from_id = ?", id, id])
526 end
551 end
527
552
528 def all_dependent_issues(except=[])
553 def all_dependent_issues(except=[])
529 except << self
554 except << self
530 dependencies = []
555 dependencies = []
531 relations_from.each do |relation|
556 relations_from.each do |relation|
532 if relation.issue_to && !except.include?(relation.issue_to)
557 if relation.issue_to && !except.include?(relation.issue_to)
533 dependencies << relation.issue_to
558 dependencies << relation.issue_to
534 dependencies += relation.issue_to.all_dependent_issues(except)
559 dependencies += relation.issue_to.all_dependent_issues(except)
535 end
560 end
536 end
561 end
537 dependencies
562 dependencies
538 end
563 end
539
564
540 # Returns an array of issues that duplicate this one
565 # Returns an array of issues that duplicate this one
541 def duplicates
566 def duplicates
542 relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from}
567 relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from}
543 end
568 end
544
569
545 # Returns the due date or the target due date if any
570 # Returns the due date or the target due date if any
546 # Used on gantt chart
571 # Used on gantt chart
547 def due_before
572 def due_before
548 due_date || (fixed_version ? fixed_version.effective_date : nil)
573 due_date || (fixed_version ? fixed_version.effective_date : nil)
549 end
574 end
550
575
551 # Returns the time scheduled for this issue.
576 # Returns the time scheduled for this issue.
552 #
577 #
553 # Example:
578 # Example:
554 # Start Date: 2/26/09, End Date: 3/04/09
579 # Start Date: 2/26/09, End Date: 3/04/09
555 # duration => 6
580 # duration => 6
556 def duration
581 def duration
557 (start_date && due_date) ? due_date - start_date : 0
582 (start_date && due_date) ? due_date - start_date : 0
558 end
583 end
559
584
560 def soonest_start
585 def soonest_start
561 @soonest_start ||= (
586 @soonest_start ||= (
562 relations_to.collect{|relation| relation.successor_soonest_start} +
587 relations_to.collect{|relation| relation.successor_soonest_start} +
563 ancestors.collect(&:soonest_start)
588 ancestors.collect(&:soonest_start)
564 ).compact.max
589 ).compact.max
565 end
590 end
566
591
567 def reschedule_after(date)
592 def reschedule_after(date)
568 return if date.nil?
593 return if date.nil?
569 if leaf?
594 if leaf?
570 if start_date.nil? || start_date < date
595 if start_date.nil? || start_date < date
571 self.start_date, self.due_date = date, date + duration
596 self.start_date, self.due_date = date, date + duration
572 save
597 save
573 end
598 end
574 else
599 else
575 leaves.each do |leaf|
600 leaves.each do |leaf|
576 leaf.reschedule_after(date)
601 leaf.reschedule_after(date)
577 end
602 end
578 end
603 end
579 end
604 end
580
605
581 def <=>(issue)
606 def <=>(issue)
582 if issue.nil?
607 if issue.nil?
583 -1
608 -1
584 elsif root_id != issue.root_id
609 elsif root_id != issue.root_id
585 (root_id || 0) <=> (issue.root_id || 0)
610 (root_id || 0) <=> (issue.root_id || 0)
586 else
611 else
587 (lft || 0) <=> (issue.lft || 0)
612 (lft || 0) <=> (issue.lft || 0)
588 end
613 end
589 end
614 end
590
615
591 def to_s
616 def to_s
592 "#{tracker} ##{id}: #{subject}"
617 "#{tracker} ##{id}: #{subject}"
593 end
618 end
594
619
595 # Returns a string of css classes that apply to the issue
620 # Returns a string of css classes that apply to the issue
596 def css_classes
621 def css_classes
597 s = "issue status-#{status.position} priority-#{priority.position}"
622 s = "issue status-#{status.position} priority-#{priority.position}"
598 s << ' closed' if closed?
623 s << ' closed' if closed?
599 s << ' overdue' if overdue?
624 s << ' overdue' if overdue?
600 s << ' child' if child?
625 s << ' child' if child?
601 s << ' parent' unless leaf?
626 s << ' parent' unless leaf?
602 s << ' private' if is_private?
627 s << ' private' if is_private?
603 s << ' created-by-me' if User.current.logged? && author_id == User.current.id
628 s << ' created-by-me' if User.current.logged? && author_id == User.current.id
604 s << ' assigned-to-me' if User.current.logged? && assigned_to_id == User.current.id
629 s << ' assigned-to-me' if User.current.logged? && assigned_to_id == User.current.id
605 s
630 s
606 end
631 end
607
632
608 # Saves an issue, time_entry, attachments, and a journal from the parameters
633 # Saves an issue, time_entry, attachments, and a journal from the parameters
609 # Returns false if save fails
634 # Returns false if save fails
610 def save_issue_with_child_records(params, existing_time_entry=nil)
635 def save_issue_with_child_records(params, existing_time_entry=nil)
611 Issue.transaction do
636 Issue.transaction do
612 if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, project)
637 if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, project)
613 @time_entry = existing_time_entry || TimeEntry.new
638 @time_entry = existing_time_entry || TimeEntry.new
614 @time_entry.project = project
639 @time_entry.project = project
615 @time_entry.issue = self
640 @time_entry.issue = self
616 @time_entry.user = User.current
641 @time_entry.user = User.current
617 @time_entry.spent_on = User.current.today
642 @time_entry.spent_on = User.current.today
618 @time_entry.attributes = params[:time_entry]
643 @time_entry.attributes = params[:time_entry]
619 self.time_entries << @time_entry
644 self.time_entries << @time_entry
620 end
645 end
621
646
622 if valid?
647 if valid?
623 attachments = Attachment.attach_files(self, params[:attachments])
648 attachments = Attachment.attach_files(self, params[:attachments])
624 # TODO: Rename hook
649 # TODO: Rename hook
625 Redmine::Hook.call_hook(:controller_issues_edit_before_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
650 Redmine::Hook.call_hook(:controller_issues_edit_before_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
626 begin
651 begin
627 if save
652 if save
628 # TODO: Rename hook
653 # TODO: Rename hook
629 Redmine::Hook.call_hook(:controller_issues_edit_after_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
654 Redmine::Hook.call_hook(:controller_issues_edit_after_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
630 else
655 else
631 raise ActiveRecord::Rollback
656 raise ActiveRecord::Rollback
632 end
657 end
633 rescue ActiveRecord::StaleObjectError
658 rescue ActiveRecord::StaleObjectError
634 attachments[:files].each(&:destroy)
659 attachments[:files].each(&:destroy)
635 errors.add :base, l(:notice_locking_conflict)
660 errors.add :base, l(:notice_locking_conflict)
636 raise ActiveRecord::Rollback
661 raise ActiveRecord::Rollback
637 end
662 end
638 end
663 end
639 end
664 end
640 end
665 end
641
666
642 # Unassigns issues from +version+ if it's no longer shared with issue's project
667 # Unassigns issues from +version+ if it's no longer shared with issue's project
643 def self.update_versions_from_sharing_change(version)
668 def self.update_versions_from_sharing_change(version)
644 # Update issues assigned to the version
669 # Update issues assigned to the version
645 update_versions(["#{Issue.table_name}.fixed_version_id = ?", version.id])
670 update_versions(["#{Issue.table_name}.fixed_version_id = ?", version.id])
646 end
671 end
647
672
648 # Unassigns issues from versions that are no longer shared
673 # Unassigns issues from versions that are no longer shared
649 # after +project+ was moved
674 # after +project+ was moved
650 def self.update_versions_from_hierarchy_change(project)
675 def self.update_versions_from_hierarchy_change(project)
651 moved_project_ids = project.self_and_descendants.reload.collect(&:id)
676 moved_project_ids = project.self_and_descendants.reload.collect(&:id)
652 # Update issues of the moved projects and issues assigned to a version of a moved project
677 # Update issues of the moved projects and issues assigned to a version of a moved project
653 Issue.update_versions(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids])
678 Issue.update_versions(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids])
654 end
679 end
655
680
656 def parent_issue_id=(arg)
681 def parent_issue_id=(arg)
657 parent_issue_id = arg.blank? ? nil : arg.to_i
682 parent_issue_id = arg.blank? ? nil : arg.to_i
658 if parent_issue_id && @parent_issue = Issue.find_by_id(parent_issue_id)
683 if parent_issue_id && @parent_issue = Issue.find_by_id(parent_issue_id)
659 @parent_issue.id
684 @parent_issue.id
660 else
685 else
661 @parent_issue = nil
686 @parent_issue = nil
662 nil
687 nil
663 end
688 end
664 end
689 end
665
690
666 def parent_issue_id
691 def parent_issue_id
667 if instance_variable_defined? :@parent_issue
692 if instance_variable_defined? :@parent_issue
668 @parent_issue.nil? ? nil : @parent_issue.id
693 @parent_issue.nil? ? nil : @parent_issue.id
669 else
694 else
670 parent_id
695 parent_id
671 end
696 end
672 end
697 end
673
698
674 # Extracted from the ReportsController.
699 # Extracted from the ReportsController.
675 def self.by_tracker(project)
700 def self.by_tracker(project)
676 count_and_group_by(:project => project,
701 count_and_group_by(:project => project,
677 :field => 'tracker_id',
702 :field => 'tracker_id',
678 :joins => Tracker.table_name)
703 :joins => Tracker.table_name)
679 end
704 end
680
705
681 def self.by_version(project)
706 def self.by_version(project)
682 count_and_group_by(:project => project,
707 count_and_group_by(:project => project,
683 :field => 'fixed_version_id',
708 :field => 'fixed_version_id',
684 :joins => Version.table_name)
709 :joins => Version.table_name)
685 end
710 end
686
711
687 def self.by_priority(project)
712 def self.by_priority(project)
688 count_and_group_by(:project => project,
713 count_and_group_by(:project => project,
689 :field => 'priority_id',
714 :field => 'priority_id',
690 :joins => IssuePriority.table_name)
715 :joins => IssuePriority.table_name)
691 end
716 end
692
717
693 def self.by_category(project)
718 def self.by_category(project)
694 count_and_group_by(:project => project,
719 count_and_group_by(:project => project,
695 :field => 'category_id',
720 :field => 'category_id',
696 :joins => IssueCategory.table_name)
721 :joins => IssueCategory.table_name)
697 end
722 end
698
723
699 def self.by_assigned_to(project)
724 def self.by_assigned_to(project)
700 count_and_group_by(:project => project,
725 count_and_group_by(:project => project,
701 :field => 'assigned_to_id',
726 :field => 'assigned_to_id',
702 :joins => User.table_name)
727 :joins => User.table_name)
703 end
728 end
704
729
705 def self.by_author(project)
730 def self.by_author(project)
706 count_and_group_by(:project => project,
731 count_and_group_by(:project => project,
707 :field => 'author_id',
732 :field => 'author_id',
708 :joins => User.table_name)
733 :joins => User.table_name)
709 end
734 end
710
735
711 def self.by_subproject(project)
736 def self.by_subproject(project)
712 ActiveRecord::Base.connection.select_all("select s.id as status_id,
737 ActiveRecord::Base.connection.select_all("select s.id as status_id,
713 s.is_closed as closed,
738 s.is_closed as closed,
714 #{Issue.table_name}.project_id as project_id,
739 #{Issue.table_name}.project_id as project_id,
715 count(#{Issue.table_name}.id) as total
740 count(#{Issue.table_name}.id) as total
716 from
741 from
717 #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s
742 #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s
718 where
743 where
719 #{Issue.table_name}.status_id=s.id
744 #{Issue.table_name}.status_id=s.id
720 and #{Issue.table_name}.project_id = #{Project.table_name}.id
745 and #{Issue.table_name}.project_id = #{Project.table_name}.id
721 and #{visible_condition(User.current, :project => project, :with_subprojects => true)}
746 and #{visible_condition(User.current, :project => project, :with_subprojects => true)}
722 and #{Issue.table_name}.project_id <> #{project.id}
747 and #{Issue.table_name}.project_id <> #{project.id}
723 group by s.id, s.is_closed, #{Issue.table_name}.project_id") if project.descendants.active.any?
748 group by s.id, s.is_closed, #{Issue.table_name}.project_id") if project.descendants.active.any?
724 end
749 end
725 # End ReportsController extraction
750 # End ReportsController extraction
726
751
727 # Returns an array of projects that current user can move issues to
752 # Returns an array of projects that current user can move issues to
728 def self.allowed_target_projects_on_move
753 def self.allowed_target_projects_on_move(user=User.current)
729 projects = []
754 projects = []
730 if User.current.admin?
755 if user.admin?
731 # admin is allowed to move issues to any active (visible) project
756 # admin is allowed to move issues to any active (visible) project
732 projects = Project.visible.all
757 projects = Project.visible(user).all
733 elsif User.current.logged?
758 elsif user.logged?
734 if Role.non_member.allowed_to?(:move_issues)
759 if Role.non_member.allowed_to?(:move_issues)
735 projects = Project.visible.all
760 projects = Project.visible(user).all
736 else
761 else
737 User.current.memberships.each {|m| projects << m.project if m.roles.detect {|r| r.allowed_to?(:move_issues)}}
762 user.memberships.each {|m| projects << m.project if m.roles.detect {|r| r.allowed_to?(:move_issues)}}
738 end
763 end
739 end
764 end
740 projects
765 projects
741 end
766 end
742
767
743 private
768 private
744
769
745 def after_project_change
770 def after_project_change
746 # Update project_id on related time entries
771 # Update project_id on related time entries
747 TimeEntry.update_all(["project_id = ?", project_id], {:issue_id => id})
772 TimeEntry.update_all(["project_id = ?", project_id], {:issue_id => id})
748
773
749 # Delete issue relations
774 # Delete issue relations
750 unless Setting.cross_project_issue_relations?
775 unless Setting.cross_project_issue_relations?
751 relations_from.clear
776 relations_from.clear
752 relations_to.clear
777 relations_to.clear
753 end
778 end
754
779
755 # Move subtasks
780 # Move subtasks
756 children.each do |child|
781 children.each do |child|
757 child.project = project
782 # Change project and keep project
783 child.send :project=, project, true
758 unless child.save
784 unless child.save
759 raise ActiveRecord::Rollback
785 raise ActiveRecord::Rollback
760 end
786 end
761 end
787 end
762 end
788 end
763
789
764 def update_nested_set_attributes
790 def update_nested_set_attributes
765 if root_id.nil?
791 if root_id.nil?
766 # issue was just created
792 # issue was just created
767 self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id)
793 self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id)
768 set_default_left_and_right
794 set_default_left_and_right
769 Issue.update_all("root_id = #{root_id}, lft = #{lft}, rgt = #{rgt}", ["id = ?", id])
795 Issue.update_all("root_id = #{root_id}, lft = #{lft}, rgt = #{rgt}", ["id = ?", id])
770 if @parent_issue
796 if @parent_issue
771 move_to_child_of(@parent_issue)
797 move_to_child_of(@parent_issue)
772 end
798 end
773 reload
799 reload
774 elsif parent_issue_id != parent_id
800 elsif parent_issue_id != parent_id
775 former_parent_id = parent_id
801 former_parent_id = parent_id
776 # moving an existing issue
802 # moving an existing issue
777 if @parent_issue && @parent_issue.root_id == root_id
803 if @parent_issue && @parent_issue.root_id == root_id
778 # inside the same tree
804 # inside the same tree
779 move_to_child_of(@parent_issue)
805 move_to_child_of(@parent_issue)
780 else
806 else
781 # to another tree
807 # to another tree
782 unless root?
808 unless root?
783 move_to_right_of(root)
809 move_to_right_of(root)
784 reload
810 reload
785 end
811 end
786 old_root_id = root_id
812 old_root_id = root_id
787 self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id )
813 self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id )
788 target_maxright = nested_set_scope.maximum(right_column_name) || 0
814 target_maxright = nested_set_scope.maximum(right_column_name) || 0
789 offset = target_maxright + 1 - lft
815 offset = target_maxright + 1 - lft
790 Issue.update_all("root_id = #{root_id}, lft = lft + #{offset}, rgt = rgt + #{offset}",
816 Issue.update_all("root_id = #{root_id}, lft = lft + #{offset}, rgt = rgt + #{offset}",
791 ["root_id = ? AND lft >= ? AND rgt <= ? ", old_root_id, lft, rgt])
817 ["root_id = ? AND lft >= ? AND rgt <= ? ", old_root_id, lft, rgt])
792 self[left_column_name] = lft + offset
818 self[left_column_name] = lft + offset
793 self[right_column_name] = rgt + offset
819 self[right_column_name] = rgt + offset
794 if @parent_issue
820 if @parent_issue
795 move_to_child_of(@parent_issue)
821 move_to_child_of(@parent_issue)
796 end
822 end
797 end
823 end
798 reload
824 reload
799 # delete invalid relations of all descendants
825 # delete invalid relations of all descendants
800 self_and_descendants.each do |issue|
826 self_and_descendants.each do |issue|
801 issue.relations.each do |relation|
827 issue.relations.each do |relation|
802 relation.destroy unless relation.valid?
828 relation.destroy unless relation.valid?
803 end
829 end
804 end
830 end
805 # update former parent
831 # update former parent
806 recalculate_attributes_for(former_parent_id) if former_parent_id
832 recalculate_attributes_for(former_parent_id) if former_parent_id
807 end
833 end
808 remove_instance_variable(:@parent_issue) if instance_variable_defined?(:@parent_issue)
834 remove_instance_variable(:@parent_issue) if instance_variable_defined?(:@parent_issue)
809 end
835 end
810
836
811 def update_parent_attributes
837 def update_parent_attributes
812 recalculate_attributes_for(parent_id) if parent_id
838 recalculate_attributes_for(parent_id) if parent_id
813 end
839 end
814
840
815 def recalculate_attributes_for(issue_id)
841 def recalculate_attributes_for(issue_id)
816 if issue_id && p = Issue.find_by_id(issue_id)
842 if issue_id && p = Issue.find_by_id(issue_id)
817 # priority = highest priority of children
843 # priority = highest priority of children
818 if priority_position = p.children.maximum("#{IssuePriority.table_name}.position", :joins => :priority)
844 if priority_position = p.children.maximum("#{IssuePriority.table_name}.position", :joins => :priority)
819 p.priority = IssuePriority.find_by_position(priority_position)
845 p.priority = IssuePriority.find_by_position(priority_position)
820 end
846 end
821
847
822 # start/due dates = lowest/highest dates of children
848 # start/due dates = lowest/highest dates of children
823 p.start_date = p.children.minimum(:start_date)
849 p.start_date = p.children.minimum(:start_date)
824 p.due_date = p.children.maximum(:due_date)
850 p.due_date = p.children.maximum(:due_date)
825 if p.start_date && p.due_date && p.due_date < p.start_date
851 if p.start_date && p.due_date && p.due_date < p.start_date
826 p.start_date, p.due_date = p.due_date, p.start_date
852 p.start_date, p.due_date = p.due_date, p.start_date
827 end
853 end
828
854
829 # done ratio = weighted average ratio of leaves
855 # done ratio = weighted average ratio of leaves
830 unless Issue.use_status_for_done_ratio? && p.status && p.status.default_done_ratio
856 unless Issue.use_status_for_done_ratio? && p.status && p.status.default_done_ratio
831 leaves_count = p.leaves.count
857 leaves_count = p.leaves.count
832 if leaves_count > 0
858 if leaves_count > 0
833 average = p.leaves.average(:estimated_hours).to_f
859 average = p.leaves.average(:estimated_hours).to_f
834 if average == 0
860 if average == 0
835 average = 1
861 average = 1
836 end
862 end
837 done = p.leaves.sum("COALESCE(estimated_hours, #{average}) * (CASE WHEN is_closed = #{connection.quoted_true} THEN 100 ELSE COALESCE(done_ratio, 0) END)", :joins => :status).to_f
863 done = p.leaves.sum("COALESCE(estimated_hours, #{average}) * (CASE WHEN is_closed = #{connection.quoted_true} THEN 100 ELSE COALESCE(done_ratio, 0) END)", :joins => :status).to_f
838 progress = done / (average * leaves_count)
864 progress = done / (average * leaves_count)
839 p.done_ratio = progress.round
865 p.done_ratio = progress.round
840 end
866 end
841 end
867 end
842
868
843 # estimate = sum of leaves estimates
869 # estimate = sum of leaves estimates
844 p.estimated_hours = p.leaves.sum(:estimated_hours).to_f
870 p.estimated_hours = p.leaves.sum(:estimated_hours).to_f
845 p.estimated_hours = nil if p.estimated_hours == 0.0
871 p.estimated_hours = nil if p.estimated_hours == 0.0
846
872
847 # ancestors will be recursively updated
873 # ancestors will be recursively updated
848 p.save(false)
874 p.save(false)
849 end
875 end
850 end
876 end
851
877
852 # Update issues so their versions are not pointing to a
878 # Update issues so their versions are not pointing to a
853 # fixed_version that is not shared with the issue's project
879 # fixed_version that is not shared with the issue's project
854 def self.update_versions(conditions=nil)
880 def self.update_versions(conditions=nil)
855 # Only need to update issues with a fixed_version from
881 # Only need to update issues with a fixed_version from
856 # a different project and that is not systemwide shared
882 # a different project and that is not systemwide shared
857 Issue.scoped(:conditions => conditions).all(
883 Issue.scoped(:conditions => conditions).all(
858 :conditions => "#{Issue.table_name}.fixed_version_id IS NOT NULL" +
884 :conditions => "#{Issue.table_name}.fixed_version_id IS NOT NULL" +
859 " AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" +
885 " AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" +
860 " AND #{Version.table_name}.sharing <> 'system'",
886 " AND #{Version.table_name}.sharing <> 'system'",
861 :include => [:project, :fixed_version]
887 :include => [:project, :fixed_version]
862 ).each do |issue|
888 ).each do |issue|
863 next if issue.project.nil? || issue.fixed_version.nil?
889 next if issue.project.nil? || issue.fixed_version.nil?
864 unless issue.project.shared_versions.include?(issue.fixed_version)
890 unless issue.project.shared_versions.include?(issue.fixed_version)
865 issue.init_journal(User.current)
891 issue.init_journal(User.current)
866 issue.fixed_version = nil
892 issue.fixed_version = nil
867 issue.save
893 issue.save
868 end
894 end
869 end
895 end
870 end
896 end
871
897
872 # Callback on attachment deletion
898 # Callback on attachment deletion
873 def attachment_added(obj)
899 def attachment_added(obj)
874 if @current_journal && !obj.new_record?
900 if @current_journal && !obj.new_record?
875 @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => obj.id, :value => obj.filename)
901 @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => obj.id, :value => obj.filename)
876 end
902 end
877 end
903 end
878
904
879 # Callback on attachment deletion
905 # Callback on attachment deletion
880 def attachment_removed(obj)
906 def attachment_removed(obj)
881 journal = init_journal(User.current)
907 journal = init_journal(User.current)
882 journal.details << JournalDetail.new(:property => 'attachment',
908 journal.details << JournalDetail.new(:property => 'attachment',
883 :prop_key => obj.id,
909 :prop_key => obj.id,
884 :old_value => obj.filename)
910 :old_value => obj.filename)
885 journal.save
911 journal.save
886 end
912 end
887
913
888 # Default assignment based on category
914 # Default assignment based on category
889 def default_assign
915 def default_assign
890 if assigned_to.nil? && category && category.assigned_to
916 if assigned_to.nil? && category && category.assigned_to
891 self.assigned_to = category.assigned_to
917 self.assigned_to = category.assigned_to
892 end
918 end
893 end
919 end
894
920
895 # Updates start/due dates of following issues
921 # Updates start/due dates of following issues
896 def reschedule_following_issues
922 def reschedule_following_issues
897 if start_date_changed? || due_date_changed?
923 if start_date_changed? || due_date_changed?
898 relations_from.each do |relation|
924 relations_from.each do |relation|
899 relation.set_issue_to_dates
925 relation.set_issue_to_dates
900 end
926 end
901 end
927 end
902 end
928 end
903
929
904 # Closes duplicates if the issue is being closed
930 # Closes duplicates if the issue is being closed
905 def close_duplicates
931 def close_duplicates
906 if closing?
932 if closing?
907 duplicates.each do |duplicate|
933 duplicates.each do |duplicate|
908 # Reload is need in case the duplicate was updated by a previous duplicate
934 # Reload is need in case the duplicate was updated by a previous duplicate
909 duplicate.reload
935 duplicate.reload
910 # Don't re-close it if it's already closed
936 # Don't re-close it if it's already closed
911 next if duplicate.closed?
937 next if duplicate.closed?
912 # Same user and notes
938 # Same user and notes
913 if @current_journal
939 if @current_journal
914 duplicate.init_journal(@current_journal.user, @current_journal.notes)
940 duplicate.init_journal(@current_journal.user, @current_journal.notes)
915 end
941 end
916 duplicate.update_attribute :status, self.status
942 duplicate.update_attribute :status, self.status
917 end
943 end
918 end
944 end
919 end
945 end
920
946
921 # Saves the changes in a Journal
947 # Saves the changes in a Journal
922 # Called after_save
948 # Called after_save
923 def create_journal
949 def create_journal
924 if @current_journal
950 if @current_journal
925 # attributes changes
951 # attributes changes
926 if @attributes_before_change
952 if @attributes_before_change
927 (Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on)).each {|c|
953 (Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on)).each {|c|
928 before = @attributes_before_change[c]
954 before = @attributes_before_change[c]
929 after = send(c)
955 after = send(c)
930 next if before == after || (before.blank? && after.blank?)
956 next if before == after || (before.blank? && after.blank?)
931 @current_journal.details << JournalDetail.new(:property => 'attr',
957 @current_journal.details << JournalDetail.new(:property => 'attr',
932 :prop_key => c,
958 :prop_key => c,
933 :old_value => before,
959 :old_value => before,
934 :value => after)
960 :value => after)
935 }
961 }
936 end
962 end
937 if @custom_values_before_change
963 if @custom_values_before_change
938 # custom fields changes
964 # custom fields changes
939 custom_values.each {|c|
965 custom_values.each {|c|
940 before = @custom_values_before_change[c.custom_field_id]
966 before = @custom_values_before_change[c.custom_field_id]
941 after = c.value
967 after = c.value
942 next if before == after || (before.blank? && after.blank?)
968 next if before == after || (before.blank? && after.blank?)
943 @current_journal.details << JournalDetail.new(:property => 'cf',
969 @current_journal.details << JournalDetail.new(:property => 'cf',
944 :prop_key => c.custom_field_id,
970 :prop_key => c.custom_field_id,
945 :old_value => before,
971 :old_value => before,
946 :value => after)
972 :value => after)
947 }
973 }
948 end
974 end
949 @current_journal.save
975 @current_journal.save
950 # reset current journal
976 # reset current journal
951 init_journal @current_journal.user, @current_journal.notes
977 init_journal @current_journal.user, @current_journal.notes
952 end
978 end
953 end
979 end
954
980
955 # Query generator for selecting groups of issue counts for a project
981 # Query generator for selecting groups of issue counts for a project
956 # based on specific criteria
982 # based on specific criteria
957 #
983 #
958 # Options
984 # Options
959 # * project - Project to search in.
985 # * project - Project to search in.
960 # * field - String. Issue field to key off of in the grouping.
986 # * field - String. Issue field to key off of in the grouping.
961 # * joins - String. The table name to join against.
987 # * joins - String. The table name to join against.
962 def self.count_and_group_by(options)
988 def self.count_and_group_by(options)
963 project = options.delete(:project)
989 project = options.delete(:project)
964 select_field = options.delete(:field)
990 select_field = options.delete(:field)
965 joins = options.delete(:joins)
991 joins = options.delete(:joins)
966
992
967 where = "#{Issue.table_name}.#{select_field}=j.id"
993 where = "#{Issue.table_name}.#{select_field}=j.id"
968
994
969 ActiveRecord::Base.connection.select_all("select s.id as status_id,
995 ActiveRecord::Base.connection.select_all("select s.id as status_id,
970 s.is_closed as closed,
996 s.is_closed as closed,
971 j.id as #{select_field},
997 j.id as #{select_field},
972 count(#{Issue.table_name}.id) as total
998 count(#{Issue.table_name}.id) as total
973 from
999 from
974 #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s, #{joins} j
1000 #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s, #{joins} j
975 where
1001 where
976 #{Issue.table_name}.status_id=s.id
1002 #{Issue.table_name}.status_id=s.id
977 and #{where}
1003 and #{where}
978 and #{Issue.table_name}.project_id=#{Project.table_name}.id
1004 and #{Issue.table_name}.project_id=#{Project.table_name}.id
979 and #{visible_condition(User.current, :project => project)}
1005 and #{visible_condition(User.current, :project => project)}
980 group by s.id, s.is_closed, j.id")
1006 group by s.id, s.is_closed, j.id")
981 end
1007 end
982 end
1008 end
@@ -1,69 +1,69
1 <% labelled_fields_for :issue, @issue do |f| %>
1 <% labelled_fields_for :issue, @issue do |f| %>
2
2
3 <div class="splitcontentleft">
3 <div class="splitcontentleft">
4 <% if @issue.safe_attribute? 'status_id' %>
4 <% if @issue.safe_attribute? 'status_id' %>
5 <p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), :required => true %></p>
5 <p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), :required => true %></p>
6 <% else %>
6 <% else %>
7 <p><label><%= l(:field_status) %></label> <%= h(@issue.status.name) %></p>
7 <p><label><%= l(:field_status) %></label> <%= h(@issue.status.name) %></p>
8 <% end %>
8 <% end %>
9
9
10 <% if @issue.safe_attribute? 'priority_id' %>
10 <% if @issue.safe_attribute? 'priority_id' %>
11 <p><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), {:required => true}, :disabled => !@issue.leaf? %></p>
11 <p><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), {:required => true}, :disabled => !@issue.leaf? %></p>
12 <% end %>
12 <% end %>
13
13
14 <% if @issue.safe_attribute? 'assigned_to_id' %>
14 <% if @issue.safe_attribute? 'assigned_to_id' %>
15 <p><%= f.select :assigned_to_id, principals_options_for_select(@issue.assignable_users, @issue.assigned_to), :include_blank => true %></p>
15 <p><%= f.select :assigned_to_id, principals_options_for_select(@issue.assignable_users, @issue.assigned_to), :include_blank => true %></p>
16 <% end %>
16 <% end %>
17
17
18 <% if @issue.safe_attribute?('category_id') && @project.issue_categories.any? %>
18 <% if @issue.safe_attribute?('category_id') && @issue.project.issue_categories.any? %>
19 <p><%= f.select :category_id, (@project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
19 <p><%= f.select :category_id, (@issue.project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
20 <%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
20 <%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
21 l(:label_issue_category_new),
21 l(:label_issue_category_new),
22 'issue_category[name]',
22 'issue_category[name]',
23 {:controller => 'issue_categories', :action => 'create', :project_id => @project},
23 {:controller => 'issue_categories', :action => 'create', :project_id => @issue.project},
24 :title => l(:label_issue_category_new),
24 :title => l(:label_issue_category_new),
25 :tabindex => 199) if authorize_for('issue_categories', 'new') %></p>
25 :tabindex => 199) if User.current.allowed_to?(:manage_categories, @issue.project) %></p>
26 <% end %>
26 <% end %>
27
27
28 <% if @issue.safe_attribute?('fixed_version_id') && @issue.assignable_versions.any? %>
28 <% if @issue.safe_attribute?('fixed_version_id') && @issue.assignable_versions.any? %>
29 <p><%= f.select :fixed_version_id, version_options_for_select(@issue.assignable_versions, @issue.fixed_version), :include_blank => true %>
29 <p><%= f.select :fixed_version_id, version_options_for_select(@issue.assignable_versions, @issue.fixed_version), :include_blank => true %>
30 <%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
30 <%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
31 l(:label_version_new),
31 l(:label_version_new),
32 'version[name]',
32 'version[name]',
33 {:controller => 'versions', :action => 'create', :project_id => @project},
33 {:controller => 'versions', :action => 'create', :project_id => @issue.project},
34 :title => l(:label_version_new),
34 :title => l(:label_version_new),
35 :tabindex => 200) if authorize_for('versions', 'new') %>
35 :tabindex => 200) if User.current.allowed_to?(:manage_versions, @issue.project) %>
36 </p>
36 </p>
37 <% end %>
37 <% end %>
38 </div>
38 </div>
39
39
40 <div class="splitcontentright">
40 <div class="splitcontentright">
41 <% if @issue.safe_attribute? 'parent_issue_id' %>
41 <% if @issue.safe_attribute? 'parent_issue_id' %>
42 <p id="parent_issue"><%= f.text_field :parent_issue_id, :size => 10 %></p>
42 <p id="parent_issue"><%= f.text_field :parent_issue_id, :size => 10 %></p>
43 <div id="parent_issue_candidates" class="autocomplete"></div>
43 <div id="parent_issue_candidates" class="autocomplete"></div>
44 <%= javascript_tag "observeParentIssueField('#{auto_complete_issues_path(:id => @issue, :project_id => @project) }')" %>
44 <%= javascript_tag "observeParentIssueField('#{auto_complete_issues_path(:id => @issue, :project_id => @issue.project) }')" %>
45 <% end %>
45 <% end %>
46
46
47 <% if @issue.safe_attribute? 'start_date' %>
47 <% if @issue.safe_attribute? 'start_date' %>
48 <p><%= f.text_field :start_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_start_date') if @issue.leaf? %></p>
48 <p><%= f.text_field :start_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_start_date') if @issue.leaf? %></p>
49 <% end %>
49 <% end %>
50
50
51 <% if @issue.safe_attribute? 'due_date' %>
51 <% if @issue.safe_attribute? 'due_date' %>
52 <p><%= f.text_field :due_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_due_date') if @issue.leaf? %></p>
52 <p><%= f.text_field :due_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_due_date') if @issue.leaf? %></p>
53 <% end %>
53 <% end %>
54
54
55 <% if @issue.safe_attribute? 'estimated_hours' %>
55 <% if @issue.safe_attribute? 'estimated_hours' %>
56 <p><%= f.text_field :estimated_hours, :size => 3, :disabled => !@issue.leaf? %> <%= l(:field_hours) %></p>
56 <p><%= f.text_field :estimated_hours, :size => 3, :disabled => !@issue.leaf? %> <%= l(:field_hours) %></p>
57 <% end %>
57 <% end %>
58
58
59 <% if @issue.safe_attribute?('done_ratio') && @issue.leaf? && Issue.use_field_for_done_ratio? %>
59 <% if @issue.safe_attribute?('done_ratio') && @issue.leaf? && Issue.use_field_for_done_ratio? %>
60 <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
60 <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
61 <% end %>
61 <% end %>
62 </div>
62 </div>
63
63
64 <div style="clear:both;"> </div>
64 <div style="clear:both;"> </div>
65 <% if @issue.safe_attribute? 'custom_field_values' %>
65 <% if @issue.safe_attribute? 'custom_field_values' %>
66 <%= render :partial => 'issues/form_custom_fields' %>
66 <%= render :partial => 'issues/form_custom_fields' %>
67 <% end %>
67 <% end %>
68
68
69 <% end %>
69 <% end %>
@@ -1,46 +1,48
1 <% labelled_form_for @issue, :html => {:id => 'issue-form', :multipart => true} do |f| %>
1 <% labelled_form_for @issue, :html => {:id => 'issue-form', :multipart => true} do |f| %>
2 <%= error_messages_for 'issue', 'time_entry' %>
2 <%= error_messages_for 'issue', 'time_entry' %>
3 <div class="box">
3 <div class="box">
4 <% if @edit_allowed || !@allowed_statuses.empty? %>
4 <% if @edit_allowed || !@allowed_statuses.empty? %>
5 <fieldset class="tabular"><legend><%= l(:label_change_properties) %></legend>
5 <fieldset class="tabular"><legend><%= l(:label_change_properties) %></legend>
6 <div id="all_attributes">
6 <%= render :partial => 'form', :locals => {:f => f} %>
7 <%= render :partial => 'form', :locals => {:f => f} %>
8 </div>
7 </fieldset>
9 </fieldset>
8 <% end %>
10 <% end %>
9 <% if User.current.allowed_to?(:log_time, @project) %>
11 <% if User.current.allowed_to?(:log_time, @project) %>
10 <fieldset class="tabular"><legend><%= l(:button_log_time) %></legend>
12 <fieldset class="tabular"><legend><%= l(:button_log_time) %></legend>
11 <% labelled_fields_for :time_entry, @time_entry do |time_entry| %>
13 <% labelled_fields_for :time_entry, @time_entry do |time_entry| %>
12 <div class="splitcontentleft">
14 <div class="splitcontentleft">
13 <p><%= time_entry.text_field :hours, :size => 6, :label => :label_spent_time %> <%= l(:field_hours) %></p>
15 <p><%= time_entry.text_field :hours, :size => 6, :label => :label_spent_time %> <%= l(:field_hours) %></p>
14 </div>
16 </div>
15 <div class="splitcontentright">
17 <div class="splitcontentright">
16 <p><%= time_entry.select :activity_id, activity_collection_for_select_options %></p>
18 <p><%= time_entry.select :activity_id, activity_collection_for_select_options %></p>
17 </div>
19 </div>
18 <p><%= time_entry.text_field :comments, :size => 60 %></p>
20 <p><%= time_entry.text_field :comments, :size => 60 %></p>
19 <% @time_entry.custom_field_values.each do |value| %>
21 <% @time_entry.custom_field_values.each do |value| %>
20 <p><%= custom_field_tag_with_label :time_entry, value %></p>
22 <p><%= custom_field_tag_with_label :time_entry, value %></p>
21 <% end %>
23 <% end %>
22 <% end %>
24 <% end %>
23 </fieldset>
25 </fieldset>
24 <% end %>
26 <% end %>
25
27
26 <fieldset><legend><%= l(:field_notes) %></legend>
28 <fieldset><legend><%= l(:field_notes) %></legend>
27 <%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %>
29 <%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %>
28 <%= wikitoolbar_for 'notes' %>
30 <%= wikitoolbar_for 'notes' %>
29 <%= call_hook(:view_issues_edit_notes_bottom, { :issue => @issue, :notes => @notes, :form => f }) %>
31 <%= call_hook(:view_issues_edit_notes_bottom, { :issue => @issue, :notes => @notes, :form => f }) %>
30
32
31 <p><%=l(:label_attachment_plural)%><br /><%= render :partial => 'attachments/form' %></p>
33 <p><%=l(:label_attachment_plural)%><br /><%= render :partial => 'attachments/form' %></p>
32 </fieldset>
34 </fieldset>
33 </div>
35 </div>
34
36
35 <%= f.hidden_field :lock_version %>
37 <%= f.hidden_field :lock_version %>
36 <%= submit_tag l(:button_submit) %>
38 <%= submit_tag l(:button_submit) %>
37 <%= link_to_remote l(:label_preview),
39 <%= link_to_remote l(:label_preview),
38 { :url => preview_issue_path(:project_id => @project, :id => @issue),
40 { :url => preview_issue_path(:project_id => @project, :id => @issue),
39 :method => 'post',
41 :method => 'post',
40 :update => 'preview',
42 :update => 'preview',
41 :with => 'Form.serialize("issue-form")',
43 :with => 'Form.serialize("issue-form")',
42 :complete => "Element.scrollTo('preview')"
44 :complete => "Element.scrollTo('preview')"
43 }, :accesskey => accesskey(:preview) %>
45 }, :accesskey => accesskey(:preview) %>
44 <% end %>
46 <% end %>
45
47
46 <div id="preview" class="wiki"></div>
48 <div id="preview" class="wiki"></div>
@@ -1,41 +1,48
1 <% labelled_fields_for :issue, @issue do |f| %>
1 <%= call_hook(:view_issues_form_details_top, { :issue => @issue, :form => f }) %>
2 <%= call_hook(:view_issues_form_details_top, { :issue => @issue, :form => f }) %>
2
3
3 <% if @issue.safe_attribute? 'is_private' %>
4 <% if @issue.safe_attribute? 'is_private' %>
4 <p style="float:right; margin-right:1em;">
5 <p style="float:right; margin-right:1em;">
5 <label class="inline" for="issue_is_private" id="issue_is_private_label"><%= f.check_box :is_private, :no_label => true %> <%= l(:field_is_private) %></label>
6 <label class="inline" for="issue_is_private" id="issue_is_private_label"><%= f.check_box :is_private, :no_label => true %> <%= l(:field_is_private) %></label>
6 </p>
7 </p>
7 <% end %>
8 <% end %>
8
9
10 <% if !@issue.new_record? && @issue.safe_attribute?('project_id') %>
11 <p><%= f.select :project_id, Issue.allowed_target_projects_on_move.collect {|t| [t.name, t.id]}, :required => true %></p>
12 <%= observe_field :issue_project_id, :url => project_issue_form_path(@project, :id => @issue, :project_change => '1'),
13 :with => "Form.serialize('issue-form')" %>
14 <% end %>
15
9 <% if @issue.safe_attribute? 'tracker_id' %>
16 <% if @issue.safe_attribute? 'tracker_id' %>
10 <p><%= f.select :tracker_id, @project.trackers.collect {|t| [t.name, t.id]}, :required => true %></p>
17 <p><%= f.select :tracker_id, @issue.project.trackers.collect {|t| [t.name, t.id]}, :required => true %></p>
11 <%= observe_field :issue_tracker_id, :url => project_issue_form_path(@project, :id => @issue),
18 <%= observe_field :issue_tracker_id, :url => project_issue_form_path(@project, :id => @issue),
12 :update => :attributes,
13 :with => "Form.serialize('issue-form')" %>
19 :with => "Form.serialize('issue-form')" %>
14 <% end %>
20 <% end %>
15
21
16 <% if @issue.safe_attribute? 'subject' %>
22 <% if @issue.safe_attribute? 'subject' %>
17 <p><%= f.text_field :subject, :size => 80, :required => true %></p>
23 <p><%= f.text_field :subject, :size => 80, :required => true %></p>
18 <% end %>
24 <% end %>
19
25
20 <% if @issue.safe_attribute? 'description' %>
26 <% if @issue.safe_attribute? 'description' %>
21 <p>
27 <p>
22 <label><%= l(:field_description) %></label>
28 <label><%= l(:field_description) %></label>
23 <%= link_to_function image_tag('edit.png'),
29 <%= link_to_function image_tag('edit.png'),
24 'Element.hide(this); Effect.toggle("issue_description_and_toolbar", "appear", {duration:0.3})' unless @issue.new_record? %>
30 'Element.hide(this); Effect.toggle("issue_description_and_toolbar", "appear", {duration:0.3})' unless @issue.new_record? %>
25 <% content_tag 'span', :id => "issue_description_and_toolbar", :style => (@issue.new_record? ? nil : 'display:none') do %>
31 <% content_tag 'span', :id => "issue_description_and_toolbar", :style => (@issue.new_record? ? nil : 'display:none') do %>
26 <%= f.text_area :description,
32 <%= f.text_area :description,
27 :cols => 60,
33 :cols => 60,
28 :rows => (@issue.description.blank? ? 10 : [[10, @issue.description.length / 50].max, 100].min),
34 :rows => (@issue.description.blank? ? 10 : [[10, @issue.description.length / 50].max, 100].min),
29 :accesskey => accesskey(:edit),
35 :accesskey => accesskey(:edit),
30 :class => 'wiki-edit',
36 :class => 'wiki-edit',
31 :no_label => true %>
37 :no_label => true %>
32 <% end %>
38 <% end %>
33 </p>
39 </p>
34 <%= wikitoolbar_for 'issue_description' %>
40 <%= wikitoolbar_for 'issue_description' %>
35 <% end %>
41 <% end %>
36
42
37 <div id="attributes" class="attributes">
43 <div id="attributes" class="attributes">
38 <%= render :partial => 'issues/attributes' %>
44 <%= render :partial => 'issues/attributes' %>
39 </div>
45 </div>
40
46
41 <%= call_hook(:view_issues_form_details_bottom, { :issue => @issue, :form => f }) %>
47 <%= call_hook(:view_issues_form_details_bottom, { :issue => @issue, :form => f }) %>
48 <% end %>
@@ -1,194 +1,193
1 ---
1 ---
2 roles_001:
2 roles_001:
3 name: Manager
3 name: Manager
4 id: 1
4 id: 1
5 builtin: 0
5 builtin: 0
6 issues_visibility: all
6 issues_visibility: all
7 permissions: |
7 permissions: |
8 ---
8 ---
9 - :add_project
9 - :add_project
10 - :edit_project
10 - :edit_project
11 - :select_project_modules
11 - :select_project_modules
12 - :manage_members
12 - :manage_members
13 - :manage_versions
13 - :manage_versions
14 - :manage_categories
14 - :manage_categories
15 - :view_issues
15 - :view_issues
16 - :add_issues
16 - :add_issues
17 - :edit_issues
17 - :edit_issues
18 - :manage_issue_relations
18 - :manage_issue_relations
19 - :manage_subtasks
19 - :manage_subtasks
20 - :add_issue_notes
20 - :add_issue_notes
21 - :move_issues
21 - :move_issues
22 - :delete_issues
22 - :delete_issues
23 - :view_issue_watchers
23 - :view_issue_watchers
24 - :add_issue_watchers
24 - :add_issue_watchers
25 - :set_issues_private
25 - :set_issues_private
26 - :delete_issue_watchers
26 - :delete_issue_watchers
27 - :manage_public_queries
27 - :manage_public_queries
28 - :save_queries
28 - :save_queries
29 - :view_gantt
29 - :view_gantt
30 - :view_calendar
30 - :view_calendar
31 - :log_time
31 - :log_time
32 - :view_time_entries
32 - :view_time_entries
33 - :edit_time_entries
33 - :edit_time_entries
34 - :delete_time_entries
34 - :delete_time_entries
35 - :manage_news
35 - :manage_news
36 - :comment_news
36 - :comment_news
37 - :view_documents
37 - :view_documents
38 - :manage_documents
38 - :manage_documents
39 - :view_wiki_pages
39 - :view_wiki_pages
40 - :export_wiki_pages
40 - :export_wiki_pages
41 - :view_wiki_edits
41 - :view_wiki_edits
42 - :edit_wiki_pages
42 - :edit_wiki_pages
43 - :delete_wiki_pages_attachments
43 - :delete_wiki_pages_attachments
44 - :protect_wiki_pages
44 - :protect_wiki_pages
45 - :delete_wiki_pages
45 - :delete_wiki_pages
46 - :rename_wiki_pages
46 - :rename_wiki_pages
47 - :add_messages
47 - :add_messages
48 - :edit_messages
48 - :edit_messages
49 - :delete_messages
49 - :delete_messages
50 - :manage_boards
50 - :manage_boards
51 - :view_files
51 - :view_files
52 - :manage_files
52 - :manage_files
53 - :browse_repository
53 - :browse_repository
54 - :manage_repository
54 - :manage_repository
55 - :view_changesets
55 - :view_changesets
56 - :manage_project_activities
56 - :manage_project_activities
57
57
58 position: 1
58 position: 1
59 roles_002:
59 roles_002:
60 name: Developer
60 name: Developer
61 id: 2
61 id: 2
62 builtin: 0
62 builtin: 0
63 issues_visibility: default
63 issues_visibility: default
64 permissions: |
64 permissions: |
65 ---
65 ---
66 - :edit_project
66 - :edit_project
67 - :manage_members
67 - :manage_members
68 - :manage_versions
68 - :manage_versions
69 - :manage_categories
69 - :manage_categories
70 - :view_issues
70 - :view_issues
71 - :add_issues
71 - :add_issues
72 - :edit_issues
72 - :edit_issues
73 - :manage_issue_relations
73 - :manage_issue_relations
74 - :manage_subtasks
74 - :manage_subtasks
75 - :add_issue_notes
75 - :add_issue_notes
76 - :move_issues
76 - :move_issues
77 - :delete_issues
77 - :delete_issues
78 - :view_issue_watchers
78 - :view_issue_watchers
79 - :save_queries
79 - :save_queries
80 - :view_gantt
80 - :view_gantt
81 - :view_calendar
81 - :view_calendar
82 - :log_time
82 - :log_time
83 - :view_time_entries
83 - :view_time_entries
84 - :edit_own_time_entries
84 - :edit_own_time_entries
85 - :manage_news
85 - :manage_news
86 - :comment_news
86 - :comment_news
87 - :view_documents
87 - :view_documents
88 - :manage_documents
88 - :manage_documents
89 - :view_wiki_pages
89 - :view_wiki_pages
90 - :view_wiki_edits
90 - :view_wiki_edits
91 - :edit_wiki_pages
91 - :edit_wiki_pages
92 - :protect_wiki_pages
92 - :protect_wiki_pages
93 - :delete_wiki_pages
93 - :delete_wiki_pages
94 - :add_messages
94 - :add_messages
95 - :edit_own_messages
95 - :edit_own_messages
96 - :delete_own_messages
96 - :delete_own_messages
97 - :manage_boards
97 - :manage_boards
98 - :view_files
98 - :view_files
99 - :manage_files
99 - :manage_files
100 - :browse_repository
100 - :browse_repository
101 - :view_changesets
101 - :view_changesets
102
102
103 position: 2
103 position: 2
104 roles_003:
104 roles_003:
105 name: Reporter
105 name: Reporter
106 id: 3
106 id: 3
107 builtin: 0
107 builtin: 0
108 issues_visibility: default
108 issues_visibility: default
109 permissions: |
109 permissions: |
110 ---
110 ---
111 - :edit_project
111 - :edit_project
112 - :manage_members
112 - :manage_members
113 - :manage_versions
113 - :manage_versions
114 - :manage_categories
114 - :manage_categories
115 - :view_issues
115 - :view_issues
116 - :add_issues
116 - :add_issues
117 - :edit_issues
117 - :edit_issues
118 - :manage_issue_relations
118 - :manage_issue_relations
119 - :add_issue_notes
119 - :add_issue_notes
120 - :move_issues
120 - :move_issues
121 - :view_issue_watchers
121 - :view_issue_watchers
122 - :save_queries
122 - :save_queries
123 - :view_gantt
123 - :view_gantt
124 - :view_calendar
124 - :view_calendar
125 - :log_time
125 - :log_time
126 - :view_time_entries
126 - :view_time_entries
127 - :manage_news
127 - :manage_news
128 - :comment_news
128 - :comment_news
129 - :view_documents
129 - :view_documents
130 - :manage_documents
130 - :manage_documents
131 - :view_wiki_pages
131 - :view_wiki_pages
132 - :view_wiki_edits
132 - :view_wiki_edits
133 - :edit_wiki_pages
133 - :edit_wiki_pages
134 - :delete_wiki_pages
134 - :delete_wiki_pages
135 - :add_messages
135 - :add_messages
136 - :manage_boards
136 - :manage_boards
137 - :view_files
137 - :view_files
138 - :manage_files
138 - :manage_files
139 - :browse_repository
139 - :browse_repository
140 - :view_changesets
140 - :view_changesets
141
141
142 position: 3
142 position: 3
143 roles_004:
143 roles_004:
144 name: Non member
144 name: Non member
145 id: 4
145 id: 4
146 builtin: 1
146 builtin: 1
147 issues_visibility: default
147 issues_visibility: default
148 permissions: |
148 permissions: |
149 ---
149 ---
150 - :view_issues
150 - :view_issues
151 - :add_issues
151 - :add_issues
152 - :edit_issues
152 - :edit_issues
153 - :manage_issue_relations
153 - :manage_issue_relations
154 - :add_issue_notes
154 - :add_issue_notes
155 - :move_issues
156 - :save_queries
155 - :save_queries
157 - :view_gantt
156 - :view_gantt
158 - :view_calendar
157 - :view_calendar
159 - :log_time
158 - :log_time
160 - :view_time_entries
159 - :view_time_entries
161 - :comment_news
160 - :comment_news
162 - :view_documents
161 - :view_documents
163 - :manage_documents
162 - :manage_documents
164 - :view_wiki_pages
163 - :view_wiki_pages
165 - :view_wiki_edits
164 - :view_wiki_edits
166 - :edit_wiki_pages
165 - :edit_wiki_pages
167 - :add_messages
166 - :add_messages
168 - :view_files
167 - :view_files
169 - :manage_files
168 - :manage_files
170 - :browse_repository
169 - :browse_repository
171 - :view_changesets
170 - :view_changesets
172
171
173 position: 4
172 position: 4
174 roles_005:
173 roles_005:
175 name: Anonymous
174 name: Anonymous
176 id: 5
175 id: 5
177 builtin: 2
176 builtin: 2
178 issues_visibility: default
177 issues_visibility: default
179 permissions: |
178 permissions: |
180 ---
179 ---
181 - :view_issues
180 - :view_issues
182 - :add_issue_notes
181 - :add_issue_notes
183 - :view_gantt
182 - :view_gantt
184 - :view_calendar
183 - :view_calendar
185 - :view_time_entries
184 - :view_time_entries
186 - :view_documents
185 - :view_documents
187 - :view_wiki_pages
186 - :view_wiki_pages
188 - :view_wiki_edits
187 - :view_wiki_edits
189 - :view_files
188 - :view_files
190 - :browse_repository
189 - :browse_repository
191 - :view_changesets
190 - :view_changesets
192
191
193 position: 5
192 position: 5
194
193
@@ -1,2262 +1,2339
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19 require 'issues_controller'
19 require 'issues_controller'
20
20
21 class IssuesControllerTest < ActionController::TestCase
21 class IssuesControllerTest < ActionController::TestCase
22 fixtures :projects,
22 fixtures :projects,
23 :users,
23 :users,
24 :roles,
24 :roles,
25 :members,
25 :members,
26 :member_roles,
26 :member_roles,
27 :issues,
27 :issues,
28 :issue_statuses,
28 :issue_statuses,
29 :versions,
29 :versions,
30 :trackers,
30 :trackers,
31 :projects_trackers,
31 :projects_trackers,
32 :issue_categories,
32 :issue_categories,
33 :enabled_modules,
33 :enabled_modules,
34 :enumerations,
34 :enumerations,
35 :attachments,
35 :attachments,
36 :workflows,
36 :workflows,
37 :custom_fields,
37 :custom_fields,
38 :custom_values,
38 :custom_values,
39 :custom_fields_projects,
39 :custom_fields_projects,
40 :custom_fields_trackers,
40 :custom_fields_trackers,
41 :time_entries,
41 :time_entries,
42 :journals,
42 :journals,
43 :journal_details,
43 :journal_details,
44 :queries
44 :queries
45
45
46 include Redmine::I18n
46 include Redmine::I18n
47
47
48 def setup
48 def setup
49 @controller = IssuesController.new
49 @controller = IssuesController.new
50 @request = ActionController::TestRequest.new
50 @request = ActionController::TestRequest.new
51 @response = ActionController::TestResponse.new
51 @response = ActionController::TestResponse.new
52 User.current = nil
52 User.current = nil
53 end
53 end
54
54
55 def test_index
55 def test_index
56 Setting.default_language = 'en'
56 Setting.default_language = 'en'
57
57
58 get :index
58 get :index
59 assert_response :success
59 assert_response :success
60 assert_template 'index'
60 assert_template 'index'
61 assert_not_nil assigns(:issues)
61 assert_not_nil assigns(:issues)
62 assert_nil assigns(:project)
62 assert_nil assigns(:project)
63 assert_tag :tag => 'a', :content => /Can't print recipes/
63 assert_tag :tag => 'a', :content => /Can't print recipes/
64 assert_tag :tag => 'a', :content => /Subproject issue/
64 assert_tag :tag => 'a', :content => /Subproject issue/
65 # private projects hidden
65 # private projects hidden
66 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
66 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
67 assert_no_tag :tag => 'a', :content => /Issue on project 2/
67 assert_no_tag :tag => 'a', :content => /Issue on project 2/
68 # project column
68 # project column
69 assert_tag :tag => 'th', :content => /Project/
69 assert_tag :tag => 'th', :content => /Project/
70 end
70 end
71
71
72 def test_index_should_not_list_issues_when_module_disabled
72 def test_index_should_not_list_issues_when_module_disabled
73 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
73 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
74 get :index
74 get :index
75 assert_response :success
75 assert_response :success
76 assert_template 'index'
76 assert_template 'index'
77 assert_not_nil assigns(:issues)
77 assert_not_nil assigns(:issues)
78 assert_nil assigns(:project)
78 assert_nil assigns(:project)
79 assert_no_tag :tag => 'a', :content => /Can't print recipes/
79 assert_no_tag :tag => 'a', :content => /Can't print recipes/
80 assert_tag :tag => 'a', :content => /Subproject issue/
80 assert_tag :tag => 'a', :content => /Subproject issue/
81 end
81 end
82
82
83 def test_index_should_list_visible_issues_only
83 def test_index_should_list_visible_issues_only
84 get :index, :per_page => 100
84 get :index, :per_page => 100
85 assert_response :success
85 assert_response :success
86 assert_not_nil assigns(:issues)
86 assert_not_nil assigns(:issues)
87 assert_nil assigns(:issues).detect {|issue| !issue.visible?}
87 assert_nil assigns(:issues).detect {|issue| !issue.visible?}
88 end
88 end
89
89
90 def test_index_with_project
90 def test_index_with_project
91 Setting.display_subprojects_issues = 0
91 Setting.display_subprojects_issues = 0
92 get :index, :project_id => 1
92 get :index, :project_id => 1
93 assert_response :success
93 assert_response :success
94 assert_template 'index'
94 assert_template 'index'
95 assert_not_nil assigns(:issues)
95 assert_not_nil assigns(:issues)
96 assert_tag :tag => 'a', :content => /Can't print recipes/
96 assert_tag :tag => 'a', :content => /Can't print recipes/
97 assert_no_tag :tag => 'a', :content => /Subproject issue/
97 assert_no_tag :tag => 'a', :content => /Subproject issue/
98 end
98 end
99
99
100 def test_index_with_project_and_subprojects
100 def test_index_with_project_and_subprojects
101 Setting.display_subprojects_issues = 1
101 Setting.display_subprojects_issues = 1
102 get :index, :project_id => 1
102 get :index, :project_id => 1
103 assert_response :success
103 assert_response :success
104 assert_template 'index'
104 assert_template 'index'
105 assert_not_nil assigns(:issues)
105 assert_not_nil assigns(:issues)
106 assert_tag :tag => 'a', :content => /Can't print recipes/
106 assert_tag :tag => 'a', :content => /Can't print recipes/
107 assert_tag :tag => 'a', :content => /Subproject issue/
107 assert_tag :tag => 'a', :content => /Subproject issue/
108 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
108 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
109 end
109 end
110
110
111 def test_index_with_project_and_subprojects_should_show_private_subprojects
111 def test_index_with_project_and_subprojects_should_show_private_subprojects
112 @request.session[:user_id] = 2
112 @request.session[:user_id] = 2
113 Setting.display_subprojects_issues = 1
113 Setting.display_subprojects_issues = 1
114 get :index, :project_id => 1
114 get :index, :project_id => 1
115 assert_response :success
115 assert_response :success
116 assert_template 'index'
116 assert_template 'index'
117 assert_not_nil assigns(:issues)
117 assert_not_nil assigns(:issues)
118 assert_tag :tag => 'a', :content => /Can't print recipes/
118 assert_tag :tag => 'a', :content => /Can't print recipes/
119 assert_tag :tag => 'a', :content => /Subproject issue/
119 assert_tag :tag => 'a', :content => /Subproject issue/
120 assert_tag :tag => 'a', :content => /Issue of a private subproject/
120 assert_tag :tag => 'a', :content => /Issue of a private subproject/
121 end
121 end
122
122
123 def test_index_with_project_and_default_filter
123 def test_index_with_project_and_default_filter
124 get :index, :project_id => 1, :set_filter => 1
124 get :index, :project_id => 1, :set_filter => 1
125 assert_response :success
125 assert_response :success
126 assert_template 'index'
126 assert_template 'index'
127 assert_not_nil assigns(:issues)
127 assert_not_nil assigns(:issues)
128
128
129 query = assigns(:query)
129 query = assigns(:query)
130 assert_not_nil query
130 assert_not_nil query
131 # default filter
131 # default filter
132 assert_equal({'status_id' => {:operator => 'o', :values => ['']}}, query.filters)
132 assert_equal({'status_id' => {:operator => 'o', :values => ['']}}, query.filters)
133 end
133 end
134
134
135 def test_index_with_project_and_filter
135 def test_index_with_project_and_filter
136 get :index, :project_id => 1, :set_filter => 1,
136 get :index, :project_id => 1, :set_filter => 1,
137 :f => ['tracker_id'],
137 :f => ['tracker_id'],
138 :op => {'tracker_id' => '='},
138 :op => {'tracker_id' => '='},
139 :v => {'tracker_id' => ['1']}
139 :v => {'tracker_id' => ['1']}
140 assert_response :success
140 assert_response :success
141 assert_template 'index'
141 assert_template 'index'
142 assert_not_nil assigns(:issues)
142 assert_not_nil assigns(:issues)
143
143
144 query = assigns(:query)
144 query = assigns(:query)
145 assert_not_nil query
145 assert_not_nil query
146 assert_equal({'tracker_id' => {:operator => '=', :values => ['1']}}, query.filters)
146 assert_equal({'tracker_id' => {:operator => '=', :values => ['1']}}, query.filters)
147 end
147 end
148
148
149 def test_index_with_short_filters
149 def test_index_with_short_filters
150
150
151 to_test = {
151 to_test = {
152 'status_id' => {
152 'status_id' => {
153 'o' => { :op => 'o', :values => [''] },
153 'o' => { :op => 'o', :values => [''] },
154 'c' => { :op => 'c', :values => [''] },
154 'c' => { :op => 'c', :values => [''] },
155 '7' => { :op => '=', :values => ['7'] },
155 '7' => { :op => '=', :values => ['7'] },
156 '7|3|4' => { :op => '=', :values => ['7', '3', '4'] },
156 '7|3|4' => { :op => '=', :values => ['7', '3', '4'] },
157 '=7' => { :op => '=', :values => ['7'] },
157 '=7' => { :op => '=', :values => ['7'] },
158 '!3' => { :op => '!', :values => ['3'] },
158 '!3' => { :op => '!', :values => ['3'] },
159 '!7|3|4' => { :op => '!', :values => ['7', '3', '4'] }},
159 '!7|3|4' => { :op => '!', :values => ['7', '3', '4'] }},
160 'subject' => {
160 'subject' => {
161 'This is a subject' => { :op => '=', :values => ['This is a subject'] },
161 'This is a subject' => { :op => '=', :values => ['This is a subject'] },
162 'o' => { :op => '=', :values => ['o'] },
162 'o' => { :op => '=', :values => ['o'] },
163 '~This is part of a subject' => { :op => '~', :values => ['This is part of a subject'] },
163 '~This is part of a subject' => { :op => '~', :values => ['This is part of a subject'] },
164 '!~This is part of a subject' => { :op => '!~', :values => ['This is part of a subject'] }},
164 '!~This is part of a subject' => { :op => '!~', :values => ['This is part of a subject'] }},
165 'tracker_id' => {
165 'tracker_id' => {
166 '3' => { :op => '=', :values => ['3'] },
166 '3' => { :op => '=', :values => ['3'] },
167 '=3' => { :op => '=', :values => ['3'] }},
167 '=3' => { :op => '=', :values => ['3'] }},
168 'start_date' => {
168 'start_date' => {
169 '2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
169 '2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
170 '=2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
170 '=2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
171 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
171 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
172 '<=2011-10-12' => { :op => '<=', :values => ['2011-10-12'] },
172 '<=2011-10-12' => { :op => '<=', :values => ['2011-10-12'] },
173 '><2011-10-01|2011-10-30' => { :op => '><', :values => ['2011-10-01', '2011-10-30'] },
173 '><2011-10-01|2011-10-30' => { :op => '><', :values => ['2011-10-01', '2011-10-30'] },
174 '<t+2' => { :op => '<t+', :values => ['2'] },
174 '<t+2' => { :op => '<t+', :values => ['2'] },
175 '>t+2' => { :op => '>t+', :values => ['2'] },
175 '>t+2' => { :op => '>t+', :values => ['2'] },
176 't+2' => { :op => 't+', :values => ['2'] },
176 't+2' => { :op => 't+', :values => ['2'] },
177 't' => { :op => 't', :values => [''] },
177 't' => { :op => 't', :values => [''] },
178 'w' => { :op => 'w', :values => [''] },
178 'w' => { :op => 'w', :values => [''] },
179 '>t-2' => { :op => '>t-', :values => ['2'] },
179 '>t-2' => { :op => '>t-', :values => ['2'] },
180 '<t-2' => { :op => '<t-', :values => ['2'] },
180 '<t-2' => { :op => '<t-', :values => ['2'] },
181 't-2' => { :op => 't-', :values => ['2'] }},
181 't-2' => { :op => 't-', :values => ['2'] }},
182 'created_on' => {
182 'created_on' => {
183 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
183 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
184 '<t+2' => { :op => '=', :values => ['<t+2'] },
184 '<t+2' => { :op => '=', :values => ['<t+2'] },
185 '>t+2' => { :op => '=', :values => ['>t+2'] },
185 '>t+2' => { :op => '=', :values => ['>t+2'] },
186 't+2' => { :op => 't', :values => ['+2'] }},
186 't+2' => { :op => 't', :values => ['+2'] }},
187 'cf_1' => {
187 'cf_1' => {
188 'c' => { :op => '=', :values => ['c'] },
188 'c' => { :op => '=', :values => ['c'] },
189 '!c' => { :op => '!', :values => ['c'] },
189 '!c' => { :op => '!', :values => ['c'] },
190 '!*' => { :op => '!*', :values => [''] },
190 '!*' => { :op => '!*', :values => [''] },
191 '*' => { :op => '*', :values => [''] }},
191 '*' => { :op => '*', :values => [''] }},
192 'estimated_hours' => {
192 'estimated_hours' => {
193 '=13.4' => { :op => '=', :values => ['13.4'] },
193 '=13.4' => { :op => '=', :values => ['13.4'] },
194 '>=45' => { :op => '>=', :values => ['45'] },
194 '>=45' => { :op => '>=', :values => ['45'] },
195 '<=125' => { :op => '<=', :values => ['125'] },
195 '<=125' => { :op => '<=', :values => ['125'] },
196 '><10.5|20.5' => { :op => '><', :values => ['10.5', '20.5'] },
196 '><10.5|20.5' => { :op => '><', :values => ['10.5', '20.5'] },
197 '!*' => { :op => '!*', :values => [''] },
197 '!*' => { :op => '!*', :values => [''] },
198 '*' => { :op => '*', :values => [''] }}
198 '*' => { :op => '*', :values => [''] }}
199 }
199 }
200
200
201 default_filter = { 'status_id' => {:operator => 'o', :values => [''] }}
201 default_filter = { 'status_id' => {:operator => 'o', :values => [''] }}
202
202
203 to_test.each do |field, expression_and_expected|
203 to_test.each do |field, expression_and_expected|
204 expression_and_expected.each do |filter_expression, expected|
204 expression_and_expected.each do |filter_expression, expected|
205
205
206 get :index, :set_filter => 1, field => filter_expression
206 get :index, :set_filter => 1, field => filter_expression
207
207
208 assert_response :success
208 assert_response :success
209 assert_template 'index'
209 assert_template 'index'
210 assert_not_nil assigns(:issues)
210 assert_not_nil assigns(:issues)
211
211
212 query = assigns(:query)
212 query = assigns(:query)
213 assert_not_nil query
213 assert_not_nil query
214 assert query.has_filter?(field)
214 assert query.has_filter?(field)
215 assert_equal(default_filter.merge({field => {:operator => expected[:op], :values => expected[:values]}}), query.filters)
215 assert_equal(default_filter.merge({field => {:operator => expected[:op], :values => expected[:values]}}), query.filters)
216 end
216 end
217 end
217 end
218
218
219 end
219 end
220
220
221 def test_index_with_project_and_empty_filters
221 def test_index_with_project_and_empty_filters
222 get :index, :project_id => 1, :set_filter => 1, :fields => ['']
222 get :index, :project_id => 1, :set_filter => 1, :fields => ['']
223 assert_response :success
223 assert_response :success
224 assert_template 'index'
224 assert_template 'index'
225 assert_not_nil assigns(:issues)
225 assert_not_nil assigns(:issues)
226
226
227 query = assigns(:query)
227 query = assigns(:query)
228 assert_not_nil query
228 assert_not_nil query
229 # no filter
229 # no filter
230 assert_equal({}, query.filters)
230 assert_equal({}, query.filters)
231 end
231 end
232
232
233 def test_index_with_query
233 def test_index_with_query
234 get :index, :project_id => 1, :query_id => 5
234 get :index, :project_id => 1, :query_id => 5
235 assert_response :success
235 assert_response :success
236 assert_template 'index'
236 assert_template 'index'
237 assert_not_nil assigns(:issues)
237 assert_not_nil assigns(:issues)
238 assert_nil assigns(:issue_count_by_group)
238 assert_nil assigns(:issue_count_by_group)
239 end
239 end
240
240
241 def test_index_with_query_grouped_by_tracker
241 def test_index_with_query_grouped_by_tracker
242 get :index, :project_id => 1, :query_id => 6
242 get :index, :project_id => 1, :query_id => 6
243 assert_response :success
243 assert_response :success
244 assert_template 'index'
244 assert_template 'index'
245 assert_not_nil assigns(:issues)
245 assert_not_nil assigns(:issues)
246 assert_not_nil assigns(:issue_count_by_group)
246 assert_not_nil assigns(:issue_count_by_group)
247 end
247 end
248
248
249 def test_index_with_query_grouped_by_list_custom_field
249 def test_index_with_query_grouped_by_list_custom_field
250 get :index, :project_id => 1, :query_id => 9
250 get :index, :project_id => 1, :query_id => 9
251 assert_response :success
251 assert_response :success
252 assert_template 'index'
252 assert_template 'index'
253 assert_not_nil assigns(:issues)
253 assert_not_nil assigns(:issues)
254 assert_not_nil assigns(:issue_count_by_group)
254 assert_not_nil assigns(:issue_count_by_group)
255 end
255 end
256
256
257 def test_index_with_query_id_and_project_id_should_set_session_query
257 def test_index_with_query_id_and_project_id_should_set_session_query
258 get :index, :project_id => 1, :query_id => 4
258 get :index, :project_id => 1, :query_id => 4
259 assert_response :success
259 assert_response :success
260 assert_kind_of Hash, session[:query]
260 assert_kind_of Hash, session[:query]
261 assert_equal 4, session[:query][:id]
261 assert_equal 4, session[:query][:id]
262 assert_equal 1, session[:query][:project_id]
262 assert_equal 1, session[:query][:project_id]
263 end
263 end
264
264
265 def test_index_with_cross_project_query_in_session_should_show_project_issues
265 def test_index_with_cross_project_query_in_session_should_show_project_issues
266 q = Query.create!(:name => "test", :user_id => 2, :is_public => false, :project => nil)
266 q = Query.create!(:name => "test", :user_id => 2, :is_public => false, :project => nil)
267 @request.session[:query] = {:id => q.id, :project_id => 1}
267 @request.session[:query] = {:id => q.id, :project_id => 1}
268
268
269 with_settings :display_subprojects_issues => '0' do
269 with_settings :display_subprojects_issues => '0' do
270 get :index, :project_id => 1
270 get :index, :project_id => 1
271 end
271 end
272 assert_response :success
272 assert_response :success
273 assert_not_nil assigns(:query)
273 assert_not_nil assigns(:query)
274 assert_equal q.id, assigns(:query).id
274 assert_equal q.id, assigns(:query).id
275 assert_equal 1, assigns(:query).project_id
275 assert_equal 1, assigns(:query).project_id
276 assert_equal [1], assigns(:issues).map(&:project_id).uniq
276 assert_equal [1], assigns(:issues).map(&:project_id).uniq
277 end
277 end
278
278
279 def test_private_query_should_not_be_available_to_other_users
279 def test_private_query_should_not_be_available_to_other_users
280 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
280 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
281 @request.session[:user_id] = 3
281 @request.session[:user_id] = 3
282
282
283 get :index, :query_id => q.id
283 get :index, :query_id => q.id
284 assert_response 403
284 assert_response 403
285 end
285 end
286
286
287 def test_private_query_should_be_available_to_its_user
287 def test_private_query_should_be_available_to_its_user
288 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
288 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
289 @request.session[:user_id] = 2
289 @request.session[:user_id] = 2
290
290
291 get :index, :query_id => q.id
291 get :index, :query_id => q.id
292 assert_response :success
292 assert_response :success
293 end
293 end
294
294
295 def test_public_query_should_be_available_to_other_users
295 def test_public_query_should_be_available_to_other_users
296 q = Query.create!(:name => "private", :user => User.find(2), :is_public => true, :project => nil)
296 q = Query.create!(:name => "private", :user => User.find(2), :is_public => true, :project => nil)
297 @request.session[:user_id] = 3
297 @request.session[:user_id] = 3
298
298
299 get :index, :query_id => q.id
299 get :index, :query_id => q.id
300 assert_response :success
300 assert_response :success
301 end
301 end
302
302
303 def test_index_csv
303 def test_index_csv
304 get :index, :format => 'csv'
304 get :index, :format => 'csv'
305 assert_response :success
305 assert_response :success
306 assert_not_nil assigns(:issues)
306 assert_not_nil assigns(:issues)
307 assert_equal 'text/csv', @response.content_type
307 assert_equal 'text/csv', @response.content_type
308 assert @response.body.starts_with?("#,")
308 assert @response.body.starts_with?("#,")
309 lines = @response.body.chomp.split("\n")
309 lines = @response.body.chomp.split("\n")
310 assert_equal assigns(:query).columns.size + 1, lines[0].split(',').size
310 assert_equal assigns(:query).columns.size + 1, lines[0].split(',').size
311 end
311 end
312
312
313 def test_index_csv_with_project
313 def test_index_csv_with_project
314 get :index, :project_id => 1, :format => 'csv'
314 get :index, :project_id => 1, :format => 'csv'
315 assert_response :success
315 assert_response :success
316 assert_not_nil assigns(:issues)
316 assert_not_nil assigns(:issues)
317 assert_equal 'text/csv', @response.content_type
317 assert_equal 'text/csv', @response.content_type
318 end
318 end
319
319
320 def test_index_csv_with_description
320 def test_index_csv_with_description
321 get :index, :format => 'csv', :description => '1'
321 get :index, :format => 'csv', :description => '1'
322 assert_response :success
322 assert_response :success
323 assert_not_nil assigns(:issues)
323 assert_not_nil assigns(:issues)
324 assert_equal 'text/csv', @response.content_type
324 assert_equal 'text/csv', @response.content_type
325 assert @response.body.starts_with?("#,")
325 assert @response.body.starts_with?("#,")
326 lines = @response.body.chomp.split("\n")
326 lines = @response.body.chomp.split("\n")
327 assert_equal assigns(:query).columns.size + 2, lines[0].split(',').size
327 assert_equal assigns(:query).columns.size + 2, lines[0].split(',').size
328 end
328 end
329
329
330 def test_index_csv_with_all_columns
330 def test_index_csv_with_all_columns
331 get :index, :format => 'csv', :columns => 'all'
331 get :index, :format => 'csv', :columns => 'all'
332 assert_response :success
332 assert_response :success
333 assert_not_nil assigns(:issues)
333 assert_not_nil assigns(:issues)
334 assert_equal 'text/csv', @response.content_type
334 assert_equal 'text/csv', @response.content_type
335 assert @response.body.starts_with?("#,")
335 assert @response.body.starts_with?("#,")
336 lines = @response.body.chomp.split("\n")
336 lines = @response.body.chomp.split("\n")
337 assert_equal assigns(:query).available_columns.size + 1, lines[0].split(',').size
337 assert_equal assigns(:query).available_columns.size + 1, lines[0].split(',').size
338 end
338 end
339
339
340 def test_index_csv_big_5
340 def test_index_csv_big_5
341 with_settings :default_language => "zh-TW" do
341 with_settings :default_language => "zh-TW" do
342 str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88"
342 str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88"
343 str_big5 = "\xa4@\xa4\xeb"
343 str_big5 = "\xa4@\xa4\xeb"
344 if str_utf8.respond_to?(:force_encoding)
344 if str_utf8.respond_to?(:force_encoding)
345 str_utf8.force_encoding('UTF-8')
345 str_utf8.force_encoding('UTF-8')
346 str_big5.force_encoding('Big5')
346 str_big5.force_encoding('Big5')
347 end
347 end
348 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
348 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
349 :status_id => 1, :priority => IssuePriority.all.first,
349 :status_id => 1, :priority => IssuePriority.all.first,
350 :subject => str_utf8)
350 :subject => str_utf8)
351 assert issue.save
351 assert issue.save
352
352
353 get :index, :project_id => 1,
353 get :index, :project_id => 1,
354 :f => ['subject'],
354 :f => ['subject'],
355 :op => '=', :values => [str_utf8],
355 :op => '=', :values => [str_utf8],
356 :format => 'csv'
356 :format => 'csv'
357 assert_equal 'text/csv', @response.content_type
357 assert_equal 'text/csv', @response.content_type
358 lines = @response.body.chomp.split("\n")
358 lines = @response.body.chomp.split("\n")
359 s1 = "\xaa\xac\xbaA"
359 s1 = "\xaa\xac\xbaA"
360 if str_utf8.respond_to?(:force_encoding)
360 if str_utf8.respond_to?(:force_encoding)
361 s1.force_encoding('Big5')
361 s1.force_encoding('Big5')
362 end
362 end
363 assert lines[0].include?(s1)
363 assert lines[0].include?(s1)
364 assert lines[1].include?(str_big5)
364 assert lines[1].include?(str_big5)
365 end
365 end
366 end
366 end
367
367
368 def test_index_csv_cannot_convert_should_be_replaced_big_5
368 def test_index_csv_cannot_convert_should_be_replaced_big_5
369 with_settings :default_language => "zh-TW" do
369 with_settings :default_language => "zh-TW" do
370 str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85"
370 str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85"
371 if str_utf8.respond_to?(:force_encoding)
371 if str_utf8.respond_to?(:force_encoding)
372 str_utf8.force_encoding('UTF-8')
372 str_utf8.force_encoding('UTF-8')
373 end
373 end
374 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
374 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
375 :status_id => 1, :priority => IssuePriority.all.first,
375 :status_id => 1, :priority => IssuePriority.all.first,
376 :subject => str_utf8)
376 :subject => str_utf8)
377 assert issue.save
377 assert issue.save
378
378
379 get :index, :project_id => 1,
379 get :index, :project_id => 1,
380 :f => ['subject'],
380 :f => ['subject'],
381 :op => '=', :values => [str_utf8],
381 :op => '=', :values => [str_utf8],
382 :c => ['status', 'subject'],
382 :c => ['status', 'subject'],
383 :format => 'csv',
383 :format => 'csv',
384 :set_filter => 1
384 :set_filter => 1
385 assert_equal 'text/csv', @response.content_type
385 assert_equal 'text/csv', @response.content_type
386 lines = @response.body.chomp.split("\n")
386 lines = @response.body.chomp.split("\n")
387 s1 = "\xaa\xac\xbaA" # status
387 s1 = "\xaa\xac\xbaA" # status
388 if str_utf8.respond_to?(:force_encoding)
388 if str_utf8.respond_to?(:force_encoding)
389 s1.force_encoding('Big5')
389 s1.force_encoding('Big5')
390 end
390 end
391 assert lines[0].include?(s1)
391 assert lines[0].include?(s1)
392 s2 = lines[1].split(",")[2]
392 s2 = lines[1].split(",")[2]
393 if s1.respond_to?(:force_encoding)
393 if s1.respond_to?(:force_encoding)
394 s3 = "\xa5H?" # subject
394 s3 = "\xa5H?" # subject
395 s3.force_encoding('Big5')
395 s3.force_encoding('Big5')
396 assert_equal s3, s2
396 assert_equal s3, s2
397 elsif RUBY_PLATFORM == 'java'
397 elsif RUBY_PLATFORM == 'java'
398 assert_equal "??", s2
398 assert_equal "??", s2
399 else
399 else
400 assert_equal "\xa5H???", s2
400 assert_equal "\xa5H???", s2
401 end
401 end
402 end
402 end
403 end
403 end
404
404
405 def test_index_csv_tw
405 def test_index_csv_tw
406 with_settings :default_language => "zh-TW" do
406 with_settings :default_language => "zh-TW" do
407 str1 = "test_index_csv_tw"
407 str1 = "test_index_csv_tw"
408 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
408 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
409 :status_id => 1, :priority => IssuePriority.all.first,
409 :status_id => 1, :priority => IssuePriority.all.first,
410 :subject => str1, :estimated_hours => '1234.5')
410 :subject => str1, :estimated_hours => '1234.5')
411 assert issue.save
411 assert issue.save
412 assert_equal 1234.5, issue.estimated_hours
412 assert_equal 1234.5, issue.estimated_hours
413
413
414 get :index, :project_id => 1,
414 get :index, :project_id => 1,
415 :f => ['subject'],
415 :f => ['subject'],
416 :op => '=', :values => [str1],
416 :op => '=', :values => [str1],
417 :c => ['estimated_hours', 'subject'],
417 :c => ['estimated_hours', 'subject'],
418 :format => 'csv',
418 :format => 'csv',
419 :set_filter => 1
419 :set_filter => 1
420 assert_equal 'text/csv', @response.content_type
420 assert_equal 'text/csv', @response.content_type
421 lines = @response.body.chomp.split("\n")
421 lines = @response.body.chomp.split("\n")
422 assert_equal "#{issue.id},1234.5,#{str1}", lines[1]
422 assert_equal "#{issue.id},1234.5,#{str1}", lines[1]
423
423
424 str_tw = "Traditional Chinese (\xe7\xb9\x81\xe9\xab\x94\xe4\xb8\xad\xe6\x96\x87)"
424 str_tw = "Traditional Chinese (\xe7\xb9\x81\xe9\xab\x94\xe4\xb8\xad\xe6\x96\x87)"
425 if str_tw.respond_to?(:force_encoding)
425 if str_tw.respond_to?(:force_encoding)
426 str_tw.force_encoding('UTF-8')
426 str_tw.force_encoding('UTF-8')
427 end
427 end
428 assert_equal str_tw, l(:general_lang_name)
428 assert_equal str_tw, l(:general_lang_name)
429 assert_equal ',', l(:general_csv_separator)
429 assert_equal ',', l(:general_csv_separator)
430 assert_equal '.', l(:general_csv_decimal_separator)
430 assert_equal '.', l(:general_csv_decimal_separator)
431 end
431 end
432 end
432 end
433
433
434 def test_index_csv_fr
434 def test_index_csv_fr
435 with_settings :default_language => "fr" do
435 with_settings :default_language => "fr" do
436 str1 = "test_index_csv_fr"
436 str1 = "test_index_csv_fr"
437 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
437 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
438 :status_id => 1, :priority => IssuePriority.all.first,
438 :status_id => 1, :priority => IssuePriority.all.first,
439 :subject => str1, :estimated_hours => '1234.5')
439 :subject => str1, :estimated_hours => '1234.5')
440 assert issue.save
440 assert issue.save
441 assert_equal 1234.5, issue.estimated_hours
441 assert_equal 1234.5, issue.estimated_hours
442
442
443 get :index, :project_id => 1,
443 get :index, :project_id => 1,
444 :f => ['subject'],
444 :f => ['subject'],
445 :op => '=', :values => [str1],
445 :op => '=', :values => [str1],
446 :c => ['estimated_hours', 'subject'],
446 :c => ['estimated_hours', 'subject'],
447 :format => 'csv',
447 :format => 'csv',
448 :set_filter => 1
448 :set_filter => 1
449 assert_equal 'text/csv', @response.content_type
449 assert_equal 'text/csv', @response.content_type
450 lines = @response.body.chomp.split("\n")
450 lines = @response.body.chomp.split("\n")
451 assert_equal "#{issue.id};1234,5;#{str1}", lines[1]
451 assert_equal "#{issue.id};1234,5;#{str1}", lines[1]
452
452
453 str_fr = "Fran\xc3\xa7ais"
453 str_fr = "Fran\xc3\xa7ais"
454 if str_fr.respond_to?(:force_encoding)
454 if str_fr.respond_to?(:force_encoding)
455 str_fr.force_encoding('UTF-8')
455 str_fr.force_encoding('UTF-8')
456 end
456 end
457 assert_equal str_fr, l(:general_lang_name)
457 assert_equal str_fr, l(:general_lang_name)
458 assert_equal ';', l(:general_csv_separator)
458 assert_equal ';', l(:general_csv_separator)
459 assert_equal ',', l(:general_csv_decimal_separator)
459 assert_equal ',', l(:general_csv_decimal_separator)
460 end
460 end
461 end
461 end
462
462
463 def test_index_pdf
463 def test_index_pdf
464 ["en", "zh", "zh-TW", "ja", "ko"].each do |lang|
464 ["en", "zh", "zh-TW", "ja", "ko"].each do |lang|
465 with_settings :default_language => lang do
465 with_settings :default_language => lang do
466
466
467 get :index
467 get :index
468 assert_response :success
468 assert_response :success
469 assert_template 'index'
469 assert_template 'index'
470
470
471 if lang == "ja"
471 if lang == "ja"
472 if RUBY_PLATFORM != 'java'
472 if RUBY_PLATFORM != 'java'
473 assert_equal "CP932", l(:general_pdf_encoding)
473 assert_equal "CP932", l(:general_pdf_encoding)
474 end
474 end
475 if RUBY_PLATFORM == 'java' && l(:general_pdf_encoding) == "CP932"
475 if RUBY_PLATFORM == 'java' && l(:general_pdf_encoding) == "CP932"
476 next
476 next
477 end
477 end
478 end
478 end
479
479
480 get :index, :format => 'pdf'
480 get :index, :format => 'pdf'
481 assert_response :success
481 assert_response :success
482 assert_not_nil assigns(:issues)
482 assert_not_nil assigns(:issues)
483 assert_equal 'application/pdf', @response.content_type
483 assert_equal 'application/pdf', @response.content_type
484
484
485 get :index, :project_id => 1, :format => 'pdf'
485 get :index, :project_id => 1, :format => 'pdf'
486 assert_response :success
486 assert_response :success
487 assert_not_nil assigns(:issues)
487 assert_not_nil assigns(:issues)
488 assert_equal 'application/pdf', @response.content_type
488 assert_equal 'application/pdf', @response.content_type
489
489
490 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
490 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
491 assert_response :success
491 assert_response :success
492 assert_not_nil assigns(:issues)
492 assert_not_nil assigns(:issues)
493 assert_equal 'application/pdf', @response.content_type
493 assert_equal 'application/pdf', @response.content_type
494 end
494 end
495 end
495 end
496 end
496 end
497
497
498 def test_index_pdf_with_query_grouped_by_list_custom_field
498 def test_index_pdf_with_query_grouped_by_list_custom_field
499 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
499 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
500 assert_response :success
500 assert_response :success
501 assert_not_nil assigns(:issues)
501 assert_not_nil assigns(:issues)
502 assert_not_nil assigns(:issue_count_by_group)
502 assert_not_nil assigns(:issue_count_by_group)
503 assert_equal 'application/pdf', @response.content_type
503 assert_equal 'application/pdf', @response.content_type
504 end
504 end
505
505
506 def test_index_sort
506 def test_index_sort
507 get :index, :sort => 'tracker,id:desc'
507 get :index, :sort => 'tracker,id:desc'
508 assert_response :success
508 assert_response :success
509
509
510 sort_params = @request.session['issues_index_sort']
510 sort_params = @request.session['issues_index_sort']
511 assert sort_params.is_a?(String)
511 assert sort_params.is_a?(String)
512 assert_equal 'tracker,id:desc', sort_params
512 assert_equal 'tracker,id:desc', sort_params
513
513
514 issues = assigns(:issues)
514 issues = assigns(:issues)
515 assert_not_nil issues
515 assert_not_nil issues
516 assert !issues.empty?
516 assert !issues.empty?
517 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
517 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
518 end
518 end
519
519
520 def test_index_sort_by_field_not_included_in_columns
520 def test_index_sort_by_field_not_included_in_columns
521 Setting.issue_list_default_columns = %w(subject author)
521 Setting.issue_list_default_columns = %w(subject author)
522 get :index, :sort => 'tracker'
522 get :index, :sort => 'tracker'
523 end
523 end
524
524
525 def test_index_sort_by_assigned_to
525 def test_index_sort_by_assigned_to
526 get :index, :sort => 'assigned_to'
526 get :index, :sort => 'assigned_to'
527 assert_response :success
527 assert_response :success
528 assignees = assigns(:issues).collect(&:assigned_to).compact
528 assignees = assigns(:issues).collect(&:assigned_to).compact
529 assert_equal assignees.sort, assignees
529 assert_equal assignees.sort, assignees
530 end
530 end
531
531
532 def test_index_sort_by_assigned_to_desc
532 def test_index_sort_by_assigned_to_desc
533 get :index, :sort => 'assigned_to:desc'
533 get :index, :sort => 'assigned_to:desc'
534 assert_response :success
534 assert_response :success
535 assignees = assigns(:issues).collect(&:assigned_to).compact
535 assignees = assigns(:issues).collect(&:assigned_to).compact
536 assert_equal assignees.sort.reverse, assignees
536 assert_equal assignees.sort.reverse, assignees
537 end
537 end
538
538
539 def test_index_group_by_assigned_to
539 def test_index_group_by_assigned_to
540 get :index, :group_by => 'assigned_to', :sort => 'priority'
540 get :index, :group_by => 'assigned_to', :sort => 'priority'
541 assert_response :success
541 assert_response :success
542 end
542 end
543
543
544 def test_index_sort_by_author
544 def test_index_sort_by_author
545 get :index, :sort => 'author'
545 get :index, :sort => 'author'
546 assert_response :success
546 assert_response :success
547 authors = assigns(:issues).collect(&:author)
547 authors = assigns(:issues).collect(&:author)
548 assert_equal authors.sort, authors
548 assert_equal authors.sort, authors
549 end
549 end
550
550
551 def test_index_sort_by_author_desc
551 def test_index_sort_by_author_desc
552 get :index, :sort => 'author:desc'
552 get :index, :sort => 'author:desc'
553 assert_response :success
553 assert_response :success
554 authors = assigns(:issues).collect(&:author)
554 authors = assigns(:issues).collect(&:author)
555 assert_equal authors.sort.reverse, authors
555 assert_equal authors.sort.reverse, authors
556 end
556 end
557
557
558 def test_index_group_by_author
558 def test_index_group_by_author
559 get :index, :group_by => 'author', :sort => 'priority'
559 get :index, :group_by => 'author', :sort => 'priority'
560 assert_response :success
560 assert_response :success
561 end
561 end
562
562
563 def test_index_sort_by_spent_hours
563 def test_index_sort_by_spent_hours
564 get :index, :sort => 'spent_hours:desc'
564 get :index, :sort => 'spent_hours:desc'
565 assert_response :success
565 assert_response :success
566 hours = assigns(:issues).collect(&:spent_hours)
566 hours = assigns(:issues).collect(&:spent_hours)
567 assert_equal hours.sort.reverse, hours
567 assert_equal hours.sort.reverse, hours
568 end
568 end
569
569
570 def test_index_with_columns
570 def test_index_with_columns
571 columns = ['tracker', 'subject', 'assigned_to']
571 columns = ['tracker', 'subject', 'assigned_to']
572 get :index, :set_filter => 1, :c => columns
572 get :index, :set_filter => 1, :c => columns
573 assert_response :success
573 assert_response :success
574
574
575 # query should use specified columns
575 # query should use specified columns
576 query = assigns(:query)
576 query = assigns(:query)
577 assert_kind_of Query, query
577 assert_kind_of Query, query
578 assert_equal columns, query.column_names.map(&:to_s)
578 assert_equal columns, query.column_names.map(&:to_s)
579
579
580 # columns should be stored in session
580 # columns should be stored in session
581 assert_kind_of Hash, session[:query]
581 assert_kind_of Hash, session[:query]
582 assert_kind_of Array, session[:query][:column_names]
582 assert_kind_of Array, session[:query][:column_names]
583 assert_equal columns, session[:query][:column_names].map(&:to_s)
583 assert_equal columns, session[:query][:column_names].map(&:to_s)
584
584
585 # ensure only these columns are kept in the selected columns list
585 # ensure only these columns are kept in the selected columns list
586 assert_tag :tag => 'select', :attributes => { :id => 'selected_columns' },
586 assert_tag :tag => 'select', :attributes => { :id => 'selected_columns' },
587 :children => { :count => 3 }
587 :children => { :count => 3 }
588 assert_no_tag :tag => 'option', :attributes => { :value => 'project' },
588 assert_no_tag :tag => 'option', :attributes => { :value => 'project' },
589 :parent => { :tag => 'select', :attributes => { :id => "selected_columns" } }
589 :parent => { :tag => 'select', :attributes => { :id => "selected_columns" } }
590 end
590 end
591
591
592 def test_index_without_project_should_implicitly_add_project_column_to_default_columns
592 def test_index_without_project_should_implicitly_add_project_column_to_default_columns
593 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
593 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
594 get :index, :set_filter => 1
594 get :index, :set_filter => 1
595
595
596 # query should use specified columns
596 # query should use specified columns
597 query = assigns(:query)
597 query = assigns(:query)
598 assert_kind_of Query, query
598 assert_kind_of Query, query
599 assert_equal [:project, :tracker, :subject, :assigned_to], query.columns.map(&:name)
599 assert_equal [:project, :tracker, :subject, :assigned_to], query.columns.map(&:name)
600 end
600 end
601
601
602 def test_index_without_project_and_explicit_default_columns_should_not_add_project_column
602 def test_index_without_project_and_explicit_default_columns_should_not_add_project_column
603 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
603 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
604 columns = ['tracker', 'subject', 'assigned_to']
604 columns = ['tracker', 'subject', 'assigned_to']
605 get :index, :set_filter => 1, :c => columns
605 get :index, :set_filter => 1, :c => columns
606
606
607 # query should use specified columns
607 # query should use specified columns
608 query = assigns(:query)
608 query = assigns(:query)
609 assert_kind_of Query, query
609 assert_kind_of Query, query
610 assert_equal columns.map(&:to_sym), query.columns.map(&:name)
610 assert_equal columns.map(&:to_sym), query.columns.map(&:name)
611 end
611 end
612
612
613 def test_index_with_custom_field_column
613 def test_index_with_custom_field_column
614 columns = %w(tracker subject cf_2)
614 columns = %w(tracker subject cf_2)
615 get :index, :set_filter => 1, :c => columns
615 get :index, :set_filter => 1, :c => columns
616 assert_response :success
616 assert_response :success
617
617
618 # query should use specified columns
618 # query should use specified columns
619 query = assigns(:query)
619 query = assigns(:query)
620 assert_kind_of Query, query
620 assert_kind_of Query, query
621 assert_equal columns, query.column_names.map(&:to_s)
621 assert_equal columns, query.column_names.map(&:to_s)
622
622
623 assert_tag :td,
623 assert_tag :td,
624 :attributes => {:class => 'cf_2 string'},
624 :attributes => {:class => 'cf_2 string'},
625 :ancestor => {:tag => 'table', :attributes => {:class => /issues/}}
625 :ancestor => {:tag => 'table', :attributes => {:class => /issues/}}
626 end
626 end
627
627
628 def test_index_with_date_column
628 def test_index_with_date_column
629 Issue.find(1).update_attribute :start_date, '1987-08-24'
629 Issue.find(1).update_attribute :start_date, '1987-08-24'
630
630
631 with_settings :date_format => '%d/%m/%Y' do
631 with_settings :date_format => '%d/%m/%Y' do
632 get :index, :set_filter => 1, :c => %w(start_date)
632 get :index, :set_filter => 1, :c => %w(start_date)
633 assert_tag 'td', :attributes => {:class => /start_date/}, :content => '24/08/1987'
633 assert_tag 'td', :attributes => {:class => /start_date/}, :content => '24/08/1987'
634 end
634 end
635 end
635 end
636
636
637 def test_index_with_done_ratio
637 def test_index_with_done_ratio
638 Issue.find(1).update_attribute :done_ratio, 40
638 Issue.find(1).update_attribute :done_ratio, 40
639
639
640 get :index, :set_filter => 1, :c => %w(done_ratio)
640 get :index, :set_filter => 1, :c => %w(done_ratio)
641 assert_tag 'td', :attributes => {:class => /done_ratio/},
641 assert_tag 'td', :attributes => {:class => /done_ratio/},
642 :child => {:tag => 'table', :attributes => {:class => 'progress'},
642 :child => {:tag => 'table', :attributes => {:class => 'progress'},
643 :descendant => {:tag => 'td', :attributes => {:class => 'closed', :style => 'width: 40%;'}}
643 :descendant => {:tag => 'td', :attributes => {:class => 'closed', :style => 'width: 40%;'}}
644 }
644 }
645 end
645 end
646
646
647 def test_index_with_spent_hours_column
647 def test_index_with_spent_hours_column
648 get :index, :set_filter => 1, :c => %w(subject spent_hours)
648 get :index, :set_filter => 1, :c => %w(subject spent_hours)
649
649
650 assert_tag 'tr', :attributes => {:id => 'issue-3'},
650 assert_tag 'tr', :attributes => {:id => 'issue-3'},
651 :child => {
651 :child => {
652 :tag => 'td', :attributes => {:class => /spent_hours/}, :content => '1.00'
652 :tag => 'td', :attributes => {:class => /spent_hours/}, :content => '1.00'
653 }
653 }
654 end
654 end
655
655
656 def test_index_should_not_show_spent_hours_column_without_permission
656 def test_index_should_not_show_spent_hours_column_without_permission
657 Role.anonymous.remove_permission! :view_time_entries
657 Role.anonymous.remove_permission! :view_time_entries
658 get :index, :set_filter => 1, :c => %w(subject spent_hours)
658 get :index, :set_filter => 1, :c => %w(subject spent_hours)
659
659
660 assert_no_tag 'td', :attributes => {:class => /spent_hours/}
660 assert_no_tag 'td', :attributes => {:class => /spent_hours/}
661 end
661 end
662
662
663 def test_index_with_fixed_version
663 def test_index_with_fixed_version
664 get :index, :set_filter => 1, :c => %w(fixed_version)
664 get :index, :set_filter => 1, :c => %w(fixed_version)
665 assert_tag 'td', :attributes => {:class => /fixed_version/},
665 assert_tag 'td', :attributes => {:class => /fixed_version/},
666 :child => {:tag => 'a', :content => '1.0', :attributes => {:href => '/versions/2'}}
666 :child => {:tag => 'a', :content => '1.0', :attributes => {:href => '/versions/2'}}
667 end
667 end
668
668
669 def test_index_send_html_if_query_is_invalid
669 def test_index_send_html_if_query_is_invalid
670 get :index, :f => ['start_date'], :op => {:start_date => '='}
670 get :index, :f => ['start_date'], :op => {:start_date => '='}
671 assert_equal 'text/html', @response.content_type
671 assert_equal 'text/html', @response.content_type
672 assert_template 'index'
672 assert_template 'index'
673 end
673 end
674
674
675 def test_index_send_nothing_if_query_is_invalid
675 def test_index_send_nothing_if_query_is_invalid
676 get :index, :f => ['start_date'], :op => {:start_date => '='}, :format => 'csv'
676 get :index, :f => ['start_date'], :op => {:start_date => '='}, :format => 'csv'
677 assert_equal 'text/csv', @response.content_type
677 assert_equal 'text/csv', @response.content_type
678 assert @response.body.blank?
678 assert @response.body.blank?
679 end
679 end
680
680
681 def test_show_by_anonymous
681 def test_show_by_anonymous
682 get :show, :id => 1
682 get :show, :id => 1
683 assert_response :success
683 assert_response :success
684 assert_template 'show'
684 assert_template 'show'
685 assert_not_nil assigns(:issue)
685 assert_not_nil assigns(:issue)
686 assert_equal Issue.find(1), assigns(:issue)
686 assert_equal Issue.find(1), assigns(:issue)
687
687
688 # anonymous role is allowed to add a note
688 # anonymous role is allowed to add a note
689 assert_tag :tag => 'form',
689 assert_tag :tag => 'form',
690 :descendant => { :tag => 'fieldset',
690 :descendant => { :tag => 'fieldset',
691 :child => { :tag => 'legend',
691 :child => { :tag => 'legend',
692 :content => /Notes/ } }
692 :content => /Notes/ } }
693 assert_tag :tag => 'title',
693 assert_tag :tag => 'title',
694 :content => "Bug #1: Can't print recipes - eCookbook - Redmine"
694 :content => "Bug #1: Can't print recipes - eCookbook - Redmine"
695 end
695 end
696
696
697 def test_show_by_manager
697 def test_show_by_manager
698 @request.session[:user_id] = 2
698 @request.session[:user_id] = 2
699 get :show, :id => 1
699 get :show, :id => 1
700 assert_response :success
700 assert_response :success
701
701
702 assert_tag :tag => 'a',
702 assert_tag :tag => 'a',
703 :content => /Quote/
703 :content => /Quote/
704
704
705 assert_tag :tag => 'form',
705 assert_tag :tag => 'form',
706 :descendant => { :tag => 'fieldset',
706 :descendant => { :tag => 'fieldset',
707 :child => { :tag => 'legend',
707 :child => { :tag => 'legend',
708 :content => /Change properties/ } },
708 :content => /Change properties/ } },
709 :descendant => { :tag => 'fieldset',
709 :descendant => { :tag => 'fieldset',
710 :child => { :tag => 'legend',
710 :child => { :tag => 'legend',
711 :content => /Log time/ } },
711 :content => /Log time/ } },
712 :descendant => { :tag => 'fieldset',
712 :descendant => { :tag => 'fieldset',
713 :child => { :tag => 'legend',
713 :child => { :tag => 'legend',
714 :content => /Notes/ } }
714 :content => /Notes/ } }
715 end
715 end
716
716
717 def test_show_should_display_update_form
717 def test_show_should_display_update_form
718 @request.session[:user_id] = 2
718 @request.session[:user_id] = 2
719 get :show, :id => 1
719 get :show, :id => 1
720 assert_response :success
720 assert_response :success
721
721
722 assert_tag 'form', :attributes => {:id => 'issue-form'}
722 assert_tag 'form', :attributes => {:id => 'issue-form'}
723 assert_tag 'input', :attributes => {:name => 'issue[is_private]'}
723 assert_tag 'input', :attributes => {:name => 'issue[is_private]'}
724 assert_tag 'select', :attributes => {:name => 'issue[project_id]'}
724 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
725 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
725 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
726 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
726 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
727 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
727 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
728 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
728 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
729 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
729 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
730 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
730 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
731 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
731 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
732 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
732 assert_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
733 assert_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
733 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
734 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
734 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
735 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
735 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
736 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
736 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
737 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
737 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
738 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
738 assert_tag 'textarea', :attributes => {:name => 'notes'}
739 assert_tag 'textarea', :attributes => {:name => 'notes'}
739 end
740 end
740
741
741 def test_show_should_display_update_form_with_minimal_permissions
742 def test_show_should_display_update_form_with_minimal_permissions
742 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
743 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
743 Workflow.delete_all :role_id => 1
744 Workflow.delete_all :role_id => 1
744
745
745 @request.session[:user_id] = 2
746 @request.session[:user_id] = 2
746 get :show, :id => 1
747 get :show, :id => 1
747 assert_response :success
748 assert_response :success
748
749
749 assert_tag 'form', :attributes => {:id => 'issue-form'}
750 assert_tag 'form', :attributes => {:id => 'issue-form'}
750 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
751 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
752 assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
751 assert_no_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
753 assert_no_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
752 assert_no_tag 'input', :attributes => {:name => 'issue[subject]'}
754 assert_no_tag 'input', :attributes => {:name => 'issue[subject]'}
753 assert_no_tag 'textarea', :attributes => {:name => 'issue[description]'}
755 assert_no_tag 'textarea', :attributes => {:name => 'issue[description]'}
754 assert_no_tag 'select', :attributes => {:name => 'issue[status_id]'}
756 assert_no_tag 'select', :attributes => {:name => 'issue[status_id]'}
755 assert_no_tag 'select', :attributes => {:name => 'issue[priority_id]'}
757 assert_no_tag 'select', :attributes => {:name => 'issue[priority_id]'}
756 assert_no_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
758 assert_no_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
757 assert_no_tag 'select', :attributes => {:name => 'issue[category_id]'}
759 assert_no_tag 'select', :attributes => {:name => 'issue[category_id]'}
758 assert_no_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
760 assert_no_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
759 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
761 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
760 assert_no_tag 'input', :attributes => {:name => 'issue[start_date]'}
762 assert_no_tag 'input', :attributes => {:name => 'issue[start_date]'}
761 assert_no_tag 'input', :attributes => {:name => 'issue[due_date]'}
763 assert_no_tag 'input', :attributes => {:name => 'issue[due_date]'}
762 assert_no_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
764 assert_no_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
763 assert_no_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
765 assert_no_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
764 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
766 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
765 assert_tag 'textarea', :attributes => {:name => 'notes'}
767 assert_tag 'textarea', :attributes => {:name => 'notes'}
766 end
768 end
767
769
768 def test_show_should_display_update_form_with_workflow_permissions
770 def test_show_should_display_update_form_with_workflow_permissions
769 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
771 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
770
772
771 @request.session[:user_id] = 2
773 @request.session[:user_id] = 2
772 get :show, :id => 1
774 get :show, :id => 1
773 assert_response :success
775 assert_response :success
774
776
775 assert_tag 'form', :attributes => {:id => 'issue-form'}
777 assert_tag 'form', :attributes => {:id => 'issue-form'}
776 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
778 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
779 assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
777 assert_no_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
780 assert_no_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
778 assert_no_tag 'input', :attributes => {:name => 'issue[subject]'}
781 assert_no_tag 'input', :attributes => {:name => 'issue[subject]'}
779 assert_no_tag 'textarea', :attributes => {:name => 'issue[description]'}
782 assert_no_tag 'textarea', :attributes => {:name => 'issue[description]'}
780 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
783 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
781 assert_no_tag 'select', :attributes => {:name => 'issue[priority_id]'}
784 assert_no_tag 'select', :attributes => {:name => 'issue[priority_id]'}
782 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
785 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
783 assert_no_tag 'select', :attributes => {:name => 'issue[category_id]'}
786 assert_no_tag 'select', :attributes => {:name => 'issue[category_id]'}
784 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
787 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
785 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
788 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
786 assert_no_tag 'input', :attributes => {:name => 'issue[start_date]'}
789 assert_no_tag 'input', :attributes => {:name => 'issue[start_date]'}
787 assert_no_tag 'input', :attributes => {:name => 'issue[due_date]'}
790 assert_no_tag 'input', :attributes => {:name => 'issue[due_date]'}
788 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
791 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
789 assert_no_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
792 assert_no_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
790 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
793 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
791 assert_tag 'textarea', :attributes => {:name => 'notes'}
794 assert_tag 'textarea', :attributes => {:name => 'notes'}
792 end
795 end
793
796
794 def test_show_should_not_display_update_form_without_permissions
797 def test_show_should_not_display_update_form_without_permissions
795 Role.find(1).update_attribute :permissions, [:view_issues]
798 Role.find(1).update_attribute :permissions, [:view_issues]
796
799
797 @request.session[:user_id] = 2
800 @request.session[:user_id] = 2
798 get :show, :id => 1
801 get :show, :id => 1
799 assert_response :success
802 assert_response :success
800
803
801 assert_no_tag 'form', :attributes => {:id => 'issue-form'}
804 assert_no_tag 'form', :attributes => {:id => 'issue-form'}
802 end
805 end
803
806
804 def test_update_form_should_not_display_inactive_enumerations
807 def test_update_form_should_not_display_inactive_enumerations
805 @request.session[:user_id] = 2
808 @request.session[:user_id] = 2
806 get :show, :id => 1
809 get :show, :id => 1
807 assert_response :success
810 assert_response :success
808
811
809 assert ! IssuePriority.find(15).active?
812 assert ! IssuePriority.find(15).active?
810 assert_no_tag :option, :attributes => {:value => '15'},
813 assert_no_tag :option, :attributes => {:value => '15'},
811 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
814 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
812 end
815 end
813
816
814 def test_update_form_should_allow_attachment_upload
817 def test_update_form_should_allow_attachment_upload
815 @request.session[:user_id] = 2
818 @request.session[:user_id] = 2
816 get :show, :id => 1
819 get :show, :id => 1
817
820
818 assert_tag :tag => 'form',
821 assert_tag :tag => 'form',
819 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
822 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
820 :descendant => {
823 :descendant => {
821 :tag => 'input',
824 :tag => 'input',
822 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
825 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
823 }
826 }
824 end
827 end
825
828
826 def test_show_should_deny_anonymous_access_without_permission
829 def test_show_should_deny_anonymous_access_without_permission
827 Role.anonymous.remove_permission!(:view_issues)
830 Role.anonymous.remove_permission!(:view_issues)
828 get :show, :id => 1
831 get :show, :id => 1
829 assert_response :redirect
832 assert_response :redirect
830 end
833 end
831
834
832 def test_show_should_deny_anonymous_access_to_private_issue
835 def test_show_should_deny_anonymous_access_to_private_issue
833 Issue.update_all(["is_private = ?", true], "id = 1")
836 Issue.update_all(["is_private = ?", true], "id = 1")
834 get :show, :id => 1
837 get :show, :id => 1
835 assert_response :redirect
838 assert_response :redirect
836 end
839 end
837
840
838 def test_show_should_deny_non_member_access_without_permission
841 def test_show_should_deny_non_member_access_without_permission
839 Role.non_member.remove_permission!(:view_issues)
842 Role.non_member.remove_permission!(:view_issues)
840 @request.session[:user_id] = 9
843 @request.session[:user_id] = 9
841 get :show, :id => 1
844 get :show, :id => 1
842 assert_response 403
845 assert_response 403
843 end
846 end
844
847
845 def test_show_should_deny_non_member_access_to_private_issue
848 def test_show_should_deny_non_member_access_to_private_issue
846 Issue.update_all(["is_private = ?", true], "id = 1")
849 Issue.update_all(["is_private = ?", true], "id = 1")
847 @request.session[:user_id] = 9
850 @request.session[:user_id] = 9
848 get :show, :id => 1
851 get :show, :id => 1
849 assert_response 403
852 assert_response 403
850 end
853 end
851
854
852 def test_show_should_deny_member_access_without_permission
855 def test_show_should_deny_member_access_without_permission
853 Role.find(1).remove_permission!(:view_issues)
856 Role.find(1).remove_permission!(:view_issues)
854 @request.session[:user_id] = 2
857 @request.session[:user_id] = 2
855 get :show, :id => 1
858 get :show, :id => 1
856 assert_response 403
859 assert_response 403
857 end
860 end
858
861
859 def test_show_should_deny_member_access_to_private_issue_without_permission
862 def test_show_should_deny_member_access_to_private_issue_without_permission
860 Issue.update_all(["is_private = ?", true], "id = 1")
863 Issue.update_all(["is_private = ?", true], "id = 1")
861 @request.session[:user_id] = 3
864 @request.session[:user_id] = 3
862 get :show, :id => 1
865 get :show, :id => 1
863 assert_response 403
866 assert_response 403
864 end
867 end
865
868
866 def test_show_should_allow_author_access_to_private_issue
869 def test_show_should_allow_author_access_to_private_issue
867 Issue.update_all(["is_private = ?, author_id = 3", true], "id = 1")
870 Issue.update_all(["is_private = ?, author_id = 3", true], "id = 1")
868 @request.session[:user_id] = 3
871 @request.session[:user_id] = 3
869 get :show, :id => 1
872 get :show, :id => 1
870 assert_response :success
873 assert_response :success
871 end
874 end
872
875
873 def test_show_should_allow_assignee_access_to_private_issue
876 def test_show_should_allow_assignee_access_to_private_issue
874 Issue.update_all(["is_private = ?, assigned_to_id = 3", true], "id = 1")
877 Issue.update_all(["is_private = ?, assigned_to_id = 3", true], "id = 1")
875 @request.session[:user_id] = 3
878 @request.session[:user_id] = 3
876 get :show, :id => 1
879 get :show, :id => 1
877 assert_response :success
880 assert_response :success
878 end
881 end
879
882
880 def test_show_should_allow_member_access_to_private_issue_with_permission
883 def test_show_should_allow_member_access_to_private_issue_with_permission
881 Issue.update_all(["is_private = ?", true], "id = 1")
884 Issue.update_all(["is_private = ?", true], "id = 1")
882 User.find(3).roles_for_project(Project.find(1)).first.update_attribute :issues_visibility, 'all'
885 User.find(3).roles_for_project(Project.find(1)).first.update_attribute :issues_visibility, 'all'
883 @request.session[:user_id] = 3
886 @request.session[:user_id] = 3
884 get :show, :id => 1
887 get :show, :id => 1
885 assert_response :success
888 assert_response :success
886 end
889 end
887
890
888 def test_show_should_not_disclose_relations_to_invisible_issues
891 def test_show_should_not_disclose_relations_to_invisible_issues
889 Setting.cross_project_issue_relations = '1'
892 Setting.cross_project_issue_relations = '1'
890 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
893 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
891 # Relation to a private project issue
894 # Relation to a private project issue
892 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
895 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
893
896
894 get :show, :id => 1
897 get :show, :id => 1
895 assert_response :success
898 assert_response :success
896
899
897 assert_tag :div, :attributes => { :id => 'relations' },
900 assert_tag :div, :attributes => { :id => 'relations' },
898 :descendant => { :tag => 'a', :content => /#2$/ }
901 :descendant => { :tag => 'a', :content => /#2$/ }
899 assert_no_tag :div, :attributes => { :id => 'relations' },
902 assert_no_tag :div, :attributes => { :id => 'relations' },
900 :descendant => { :tag => 'a', :content => /#4$/ }
903 :descendant => { :tag => 'a', :content => /#4$/ }
901 end
904 end
902
905
903 def test_show_should_list_subtasks
906 def test_show_should_list_subtasks
904 Issue.generate!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
907 Issue.generate!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
905
908
906 get :show, :id => 1
909 get :show, :id => 1
907 assert_response :success
910 assert_response :success
908 assert_tag 'div', :attributes => {:id => 'issue_tree'},
911 assert_tag 'div', :attributes => {:id => 'issue_tree'},
909 :descendant => {:tag => 'td', :content => /Child Issue/, :attributes => {:class => /subject/}}
912 :descendant => {:tag => 'td', :content => /Child Issue/, :attributes => {:class => /subject/}}
910 end
913 end
911
914
912 def test_show_should_list_parents
915 def test_show_should_list_parents
913 issue = Issue.generate!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
916 issue = Issue.generate!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
914
917
915 get :show, :id => issue.id
918 get :show, :id => issue.id
916 assert_response :success
919 assert_response :success
917 assert_tag 'div', :attributes => {:class => 'subject'},
920 assert_tag 'div', :attributes => {:class => 'subject'},
918 :descendant => {:tag => 'h3', :content => 'Child Issue'}
921 :descendant => {:tag => 'h3', :content => 'Child Issue'}
919 assert_tag 'div', :attributes => {:class => 'subject'},
922 assert_tag 'div', :attributes => {:class => 'subject'},
920 :descendant => {:tag => 'a', :attributes => {:href => '/issues/1'}}
923 :descendant => {:tag => 'a', :attributes => {:href => '/issues/1'}}
921 end
924 end
922
925
923 def test_show_should_not_display_prev_next_links_without_query_in_session
926 def test_show_should_not_display_prev_next_links_without_query_in_session
924 get :show, :id => 1
927 get :show, :id => 1
925 assert_response :success
928 assert_response :success
926 assert_nil assigns(:prev_issue_id)
929 assert_nil assigns(:prev_issue_id)
927 assert_nil assigns(:next_issue_id)
930 assert_nil assigns(:next_issue_id)
928 end
931 end
929
932
930 def test_show_should_display_prev_next_links_with_query_in_session
933 def test_show_should_display_prev_next_links_with_query_in_session
931 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
934 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
932 @request.session['issues_index_sort'] = 'id'
935 @request.session['issues_index_sort'] = 'id'
933
936
934 with_settings :display_subprojects_issues => '0' do
937 with_settings :display_subprojects_issues => '0' do
935 get :show, :id => 3
938 get :show, :id => 3
936 end
939 end
937
940
938 assert_response :success
941 assert_response :success
939 # Previous and next issues for all projects
942 # Previous and next issues for all projects
940 assert_equal 2, assigns(:prev_issue_id)
943 assert_equal 2, assigns(:prev_issue_id)
941 assert_equal 5, assigns(:next_issue_id)
944 assert_equal 5, assigns(:next_issue_id)
942
945
943 assert_tag 'a', :attributes => {:href => '/issues/2'}, :content => /Previous/
946 assert_tag 'a', :attributes => {:href => '/issues/2'}, :content => /Previous/
944 assert_tag 'a', :attributes => {:href => '/issues/5'}, :content => /Next/
947 assert_tag 'a', :attributes => {:href => '/issues/5'}, :content => /Next/
945 end
948 end
946
949
947 def test_show_should_display_prev_next_links_with_project_query_in_session
950 def test_show_should_display_prev_next_links_with_project_query_in_session
948 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
951 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
949 @request.session['issues_index_sort'] = 'id'
952 @request.session['issues_index_sort'] = 'id'
950
953
951 with_settings :display_subprojects_issues => '0' do
954 with_settings :display_subprojects_issues => '0' do
952 get :show, :id => 3
955 get :show, :id => 3
953 end
956 end
954
957
955 assert_response :success
958 assert_response :success
956 # Previous and next issues inside project
959 # Previous and next issues inside project
957 assert_equal 2, assigns(:prev_issue_id)
960 assert_equal 2, assigns(:prev_issue_id)
958 assert_equal 7, assigns(:next_issue_id)
961 assert_equal 7, assigns(:next_issue_id)
959
962
960 assert_tag 'a', :attributes => {:href => '/issues/2'}, :content => /Previous/
963 assert_tag 'a', :attributes => {:href => '/issues/2'}, :content => /Previous/
961 assert_tag 'a', :attributes => {:href => '/issues/7'}, :content => /Next/
964 assert_tag 'a', :attributes => {:href => '/issues/7'}, :content => /Next/
962 end
965 end
963
966
964 def test_show_should_not_display_prev_link_for_first_issue
967 def test_show_should_not_display_prev_link_for_first_issue
965 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
968 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
966 @request.session['issues_index_sort'] = 'id'
969 @request.session['issues_index_sort'] = 'id'
967
970
968 with_settings :display_subprojects_issues => '0' do
971 with_settings :display_subprojects_issues => '0' do
969 get :show, :id => 1
972 get :show, :id => 1
970 end
973 end
971
974
972 assert_response :success
975 assert_response :success
973 assert_nil assigns(:prev_issue_id)
976 assert_nil assigns(:prev_issue_id)
974 assert_equal 2, assigns(:next_issue_id)
977 assert_equal 2, assigns(:next_issue_id)
975
978
976 assert_no_tag 'a', :content => /Previous/
979 assert_no_tag 'a', :content => /Previous/
977 assert_tag 'a', :attributes => {:href => '/issues/2'}, :content => /Next/
980 assert_tag 'a', :attributes => {:href => '/issues/2'}, :content => /Next/
978 end
981 end
979
982
980 def test_show_should_not_display_prev_next_links_for_issue_not_in_query_results
983 def test_show_should_not_display_prev_next_links_for_issue_not_in_query_results
981 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'c'}}, :project_id => 1}
984 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'c'}}, :project_id => 1}
982 @request.session['issues_index_sort'] = 'id'
985 @request.session['issues_index_sort'] = 'id'
983
986
984 get :show, :id => 1
987 get :show, :id => 1
985
988
986 assert_response :success
989 assert_response :success
987 assert_nil assigns(:prev_issue_id)
990 assert_nil assigns(:prev_issue_id)
988 assert_nil assigns(:next_issue_id)
991 assert_nil assigns(:next_issue_id)
989
992
990 assert_no_tag 'a', :content => /Previous/
993 assert_no_tag 'a', :content => /Previous/
991 assert_no_tag 'a', :content => /Next/
994 assert_no_tag 'a', :content => /Next/
992 end
995 end
993
996
994 def test_show_atom
997 def test_show_atom
995 get :show, :id => 2, :format => 'atom'
998 get :show, :id => 2, :format => 'atom'
996 assert_response :success
999 assert_response :success
997 assert_template 'journals/index'
1000 assert_template 'journals/index'
998 # Inline image
1001 # Inline image
999 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
1002 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
1000 end
1003 end
1001
1004
1002 def test_show_export_to_pdf
1005 def test_show_export_to_pdf
1003 get :show, :id => 3, :format => 'pdf'
1006 get :show, :id => 3, :format => 'pdf'
1004 assert_response :success
1007 assert_response :success
1005 assert_equal 'application/pdf', @response.content_type
1008 assert_equal 'application/pdf', @response.content_type
1006 assert @response.body.starts_with?('%PDF')
1009 assert @response.body.starts_with?('%PDF')
1007 assert_not_nil assigns(:issue)
1010 assert_not_nil assigns(:issue)
1008 end
1011 end
1009
1012
1010 def test_get_new
1013 def test_get_new
1011 @request.session[:user_id] = 2
1014 @request.session[:user_id] = 2
1012 get :new, :project_id => 1, :tracker_id => 1
1015 get :new, :project_id => 1, :tracker_id => 1
1013 assert_response :success
1016 assert_response :success
1014 assert_template 'new'
1017 assert_template 'new'
1015
1018
1016 assert_tag 'input', :attributes => {:name => 'issue[is_private]'}
1019 assert_tag 'input', :attributes => {:name => 'issue[is_private]'}
1020 assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
1017 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
1021 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
1018 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
1022 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
1019 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
1023 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
1020 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
1024 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
1021 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
1025 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
1022 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
1026 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
1023 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
1027 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
1024 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
1028 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
1025 assert_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
1029 assert_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
1026 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
1030 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
1027 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
1031 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
1028 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
1032 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
1029 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]', :value => 'Default string' }
1033 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]', :value => 'Default string' }
1030 assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
1034 assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
1031
1035
1032 # Be sure we don't display inactive IssuePriorities
1036 # Be sure we don't display inactive IssuePriorities
1033 assert ! IssuePriority.find(15).active?
1037 assert ! IssuePriority.find(15).active?
1034 assert_no_tag :option, :attributes => {:value => '15'},
1038 assert_no_tag :option, :attributes => {:value => '15'},
1035 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1039 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1036 end
1040 end
1037
1041
1038 def test_get_new_with_minimal_permissions
1042 def test_get_new_with_minimal_permissions
1039 Role.find(1).update_attribute :permissions, [:add_issues]
1043 Role.find(1).update_attribute :permissions, [:add_issues]
1040 Workflow.delete_all :role_id => 1
1044 Workflow.delete_all :role_id => 1
1041
1045
1042 @request.session[:user_id] = 2
1046 @request.session[:user_id] = 2
1043 get :new, :project_id => 1, :tracker_id => 1
1047 get :new, :project_id => 1, :tracker_id => 1
1044 assert_response :success
1048 assert_response :success
1045 assert_template 'new'
1049 assert_template 'new'
1046
1050
1047 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
1051 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
1052 assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
1048 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
1053 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
1049 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
1054 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
1050 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
1055 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
1051 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
1056 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
1052 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
1057 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
1053 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
1058 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
1054 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
1059 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
1055 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
1060 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
1056 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
1061 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
1057 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
1062 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
1058 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
1063 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
1059 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
1064 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
1060 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]', :value => 'Default string' }
1065 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]', :value => 'Default string' }
1061 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
1066 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
1062 end
1067 end
1063
1068
1064 def test_get_new_without_default_start_date_is_creation_date
1069 def test_get_new_without_default_start_date_is_creation_date
1065 Setting.default_issue_start_date_to_creation_date = 0
1070 Setting.default_issue_start_date_to_creation_date = 0
1066
1071
1067 @request.session[:user_id] = 2
1072 @request.session[:user_id] = 2
1068 get :new, :project_id => 1, :tracker_id => 1
1073 get :new, :project_id => 1, :tracker_id => 1
1069 assert_response :success
1074 assert_response :success
1070 assert_template 'new'
1075 assert_template 'new'
1071
1076
1072 assert_tag :tag => 'input', :attributes => { :name => 'issue[start_date]',
1077 assert_tag :tag => 'input', :attributes => { :name => 'issue[start_date]',
1073 :value => nil }
1078 :value => nil }
1074 end
1079 end
1075
1080
1076 def test_get_new_with_default_start_date_is_creation_date
1081 def test_get_new_with_default_start_date_is_creation_date
1077 Setting.default_issue_start_date_to_creation_date = 1
1082 Setting.default_issue_start_date_to_creation_date = 1
1078
1083
1079 @request.session[:user_id] = 2
1084 @request.session[:user_id] = 2
1080 get :new, :project_id => 1, :tracker_id => 1
1085 get :new, :project_id => 1, :tracker_id => 1
1081 assert_response :success
1086 assert_response :success
1082 assert_template 'new'
1087 assert_template 'new'
1083
1088
1084 assert_tag :tag => 'input', :attributes => { :name => 'issue[start_date]',
1089 assert_tag :tag => 'input', :attributes => { :name => 'issue[start_date]',
1085 :value => Date.today.to_s }
1090 :value => Date.today.to_s }
1086 end
1091 end
1087
1092
1088 def test_get_new_form_should_allow_attachment_upload
1093 def test_get_new_form_should_allow_attachment_upload
1089 @request.session[:user_id] = 2
1094 @request.session[:user_id] = 2
1090 get :new, :project_id => 1, :tracker_id => 1
1095 get :new, :project_id => 1, :tracker_id => 1
1091
1096
1092 assert_tag :tag => 'form',
1097 assert_tag :tag => 'form',
1093 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
1098 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
1094 :descendant => {
1099 :descendant => {
1095 :tag => 'input',
1100 :tag => 'input',
1096 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
1101 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
1097 }
1102 }
1098 end
1103 end
1099
1104
1100 def test_get_new_without_tracker_id
1105 def test_get_new_without_tracker_id
1101 @request.session[:user_id] = 2
1106 @request.session[:user_id] = 2
1102 get :new, :project_id => 1
1107 get :new, :project_id => 1
1103 assert_response :success
1108 assert_response :success
1104 assert_template 'new'
1109 assert_template 'new'
1105
1110
1106 issue = assigns(:issue)
1111 issue = assigns(:issue)
1107 assert_not_nil issue
1112 assert_not_nil issue
1108 assert_equal Project.find(1).trackers.first, issue.tracker
1113 assert_equal Project.find(1).trackers.first, issue.tracker
1109 end
1114 end
1110
1115
1111 def test_get_new_with_no_default_status_should_display_an_error
1116 def test_get_new_with_no_default_status_should_display_an_error
1112 @request.session[:user_id] = 2
1117 @request.session[:user_id] = 2
1113 IssueStatus.delete_all
1118 IssueStatus.delete_all
1114
1119
1115 get :new, :project_id => 1
1120 get :new, :project_id => 1
1116 assert_response 500
1121 assert_response 500
1117 assert_error_tag :content => /No default issue/
1122 assert_error_tag :content => /No default issue/
1118 end
1123 end
1119
1124
1120 def test_get_new_with_no_tracker_should_display_an_error
1125 def test_get_new_with_no_tracker_should_display_an_error
1121 @request.session[:user_id] = 2
1126 @request.session[:user_id] = 2
1122 Tracker.delete_all
1127 Tracker.delete_all
1123
1128
1124 get :new, :project_id => 1
1129 get :new, :project_id => 1
1125 assert_response 500
1130 assert_response 500
1126 assert_error_tag :content => /No tracker/
1131 assert_error_tag :content => /No tracker/
1127 end
1132 end
1128
1133
1129 def test_update_new_form
1134 def test_update_new_form
1130 @request.session[:user_id] = 2
1135 @request.session[:user_id] = 2
1131 xhr :post, :new, :project_id => 1,
1136 xhr :post, :new, :project_id => 1,
1132 :issue => {:tracker_id => 2,
1137 :issue => {:tracker_id => 2,
1133 :subject => 'This is the test_new issue',
1138 :subject => 'This is the test_new issue',
1134 :description => 'This is the description',
1139 :description => 'This is the description',
1135 :priority_id => 5}
1140 :priority_id => 5}
1136 assert_response :success
1141 assert_response :success
1137 assert_template 'attributes'
1142 assert_template 'attributes'
1138
1143
1139 issue = assigns(:issue)
1144 issue = assigns(:issue)
1140 assert_kind_of Issue, issue
1145 assert_kind_of Issue, issue
1141 assert_equal 1, issue.project_id
1146 assert_equal 1, issue.project_id
1142 assert_equal 2, issue.tracker_id
1147 assert_equal 2, issue.tracker_id
1143 assert_equal 'This is the test_new issue', issue.subject
1148 assert_equal 'This is the test_new issue', issue.subject
1144 end
1149 end
1145
1150
1146 def test_post_create
1151 def test_post_create
1147 @request.session[:user_id] = 2
1152 @request.session[:user_id] = 2
1148 assert_difference 'Issue.count' do
1153 assert_difference 'Issue.count' do
1149 post :create, :project_id => 1,
1154 post :create, :project_id => 1,
1150 :issue => {:tracker_id => 3,
1155 :issue => {:tracker_id => 3,
1151 :status_id => 2,
1156 :status_id => 2,
1152 :subject => 'This is the test_new issue',
1157 :subject => 'This is the test_new issue',
1153 :description => 'This is the description',
1158 :description => 'This is the description',
1154 :priority_id => 5,
1159 :priority_id => 5,
1155 :start_date => '2010-11-07',
1160 :start_date => '2010-11-07',
1156 :estimated_hours => '',
1161 :estimated_hours => '',
1157 :custom_field_values => {'2' => 'Value for field 2'}}
1162 :custom_field_values => {'2' => 'Value for field 2'}}
1158 end
1163 end
1159 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1164 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1160
1165
1161 issue = Issue.find_by_subject('This is the test_new issue')
1166 issue = Issue.find_by_subject('This is the test_new issue')
1162 assert_not_nil issue
1167 assert_not_nil issue
1163 assert_equal 2, issue.author_id
1168 assert_equal 2, issue.author_id
1164 assert_equal 3, issue.tracker_id
1169 assert_equal 3, issue.tracker_id
1165 assert_equal 2, issue.status_id
1170 assert_equal 2, issue.status_id
1166 assert_equal Date.parse('2010-11-07'), issue.start_date
1171 assert_equal Date.parse('2010-11-07'), issue.start_date
1167 assert_nil issue.estimated_hours
1172 assert_nil issue.estimated_hours
1168 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
1173 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
1169 assert_not_nil v
1174 assert_not_nil v
1170 assert_equal 'Value for field 2', v.value
1175 assert_equal 'Value for field 2', v.value
1171 end
1176 end
1172
1177
1173 def test_post_new_with_group_assignment
1178 def test_post_new_with_group_assignment
1174 group = Group.find(11)
1179 group = Group.find(11)
1175 project = Project.find(1)
1180 project = Project.find(1)
1176 project.members << Member.new(:principal => group, :roles => [Role.first])
1181 project.members << Member.new(:principal => group, :roles => [Role.first])
1177
1182
1178 with_settings :issue_group_assignment => '1' do
1183 with_settings :issue_group_assignment => '1' do
1179 @request.session[:user_id] = 2
1184 @request.session[:user_id] = 2
1180 assert_difference 'Issue.count' do
1185 assert_difference 'Issue.count' do
1181 post :create, :project_id => project.id,
1186 post :create, :project_id => project.id,
1182 :issue => {:tracker_id => 3,
1187 :issue => {:tracker_id => 3,
1183 :status_id => 1,
1188 :status_id => 1,
1184 :subject => 'This is the test_new_with_group_assignment issue',
1189 :subject => 'This is the test_new_with_group_assignment issue',
1185 :assigned_to_id => group.id}
1190 :assigned_to_id => group.id}
1186 end
1191 end
1187 end
1192 end
1188 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1193 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1189
1194
1190 issue = Issue.find_by_subject('This is the test_new_with_group_assignment issue')
1195 issue = Issue.find_by_subject('This is the test_new_with_group_assignment issue')
1191 assert_not_nil issue
1196 assert_not_nil issue
1192 assert_equal group, issue.assigned_to
1197 assert_equal group, issue.assigned_to
1193 end
1198 end
1194
1199
1195 def test_post_create_without_start_date_and_default_start_date_is_not_creation_date
1200 def test_post_create_without_start_date_and_default_start_date_is_not_creation_date
1196 Setting.default_issue_start_date_to_creation_date = 0
1201 Setting.default_issue_start_date_to_creation_date = 0
1197
1202
1198 @request.session[:user_id] = 2
1203 @request.session[:user_id] = 2
1199 assert_difference 'Issue.count' do
1204 assert_difference 'Issue.count' do
1200 post :create, :project_id => 1,
1205 post :create, :project_id => 1,
1201 :issue => {:tracker_id => 3,
1206 :issue => {:tracker_id => 3,
1202 :status_id => 2,
1207 :status_id => 2,
1203 :subject => 'This is the test_new issue',
1208 :subject => 'This is the test_new issue',
1204 :description => 'This is the description',
1209 :description => 'This is the description',
1205 :priority_id => 5,
1210 :priority_id => 5,
1206 :estimated_hours => '',
1211 :estimated_hours => '',
1207 :custom_field_values => {'2' => 'Value for field 2'}}
1212 :custom_field_values => {'2' => 'Value for field 2'}}
1208 end
1213 end
1209 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1214 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1210
1215
1211 issue = Issue.find_by_subject('This is the test_new issue')
1216 issue = Issue.find_by_subject('This is the test_new issue')
1212 assert_not_nil issue
1217 assert_not_nil issue
1213 assert_nil issue.start_date
1218 assert_nil issue.start_date
1214 end
1219 end
1215
1220
1216 def test_post_create_without_start_date_and_default_start_date_is_creation_date
1221 def test_post_create_without_start_date_and_default_start_date_is_creation_date
1217 Setting.default_issue_start_date_to_creation_date = 1
1222 Setting.default_issue_start_date_to_creation_date = 1
1218
1223
1219 @request.session[:user_id] = 2
1224 @request.session[:user_id] = 2
1220 assert_difference 'Issue.count' do
1225 assert_difference 'Issue.count' do
1221 post :create, :project_id => 1,
1226 post :create, :project_id => 1,
1222 :issue => {:tracker_id => 3,
1227 :issue => {:tracker_id => 3,
1223 :status_id => 2,
1228 :status_id => 2,
1224 :subject => 'This is the test_new issue',
1229 :subject => 'This is the test_new issue',
1225 :description => 'This is the description',
1230 :description => 'This is the description',
1226 :priority_id => 5,
1231 :priority_id => 5,
1227 :estimated_hours => '',
1232 :estimated_hours => '',
1228 :custom_field_values => {'2' => 'Value for field 2'}}
1233 :custom_field_values => {'2' => 'Value for field 2'}}
1229 end
1234 end
1230 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1235 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1231
1236
1232 issue = Issue.find_by_subject('This is the test_new issue')
1237 issue = Issue.find_by_subject('This is the test_new issue')
1233 assert_not_nil issue
1238 assert_not_nil issue
1234 assert_equal Date.today, issue.start_date
1239 assert_equal Date.today, issue.start_date
1235 end
1240 end
1236
1241
1237 def test_post_create_and_continue
1242 def test_post_create_and_continue
1238 @request.session[:user_id] = 2
1243 @request.session[:user_id] = 2
1239 assert_difference 'Issue.count' do
1244 assert_difference 'Issue.count' do
1240 post :create, :project_id => 1,
1245 post :create, :project_id => 1,
1241 :issue => {:tracker_id => 3, :subject => 'This is first issue', :priority_id => 5},
1246 :issue => {:tracker_id => 3, :subject => 'This is first issue', :priority_id => 5},
1242 :continue => ''
1247 :continue => ''
1243 end
1248 end
1244
1249
1245 issue = Issue.first(:order => 'id DESC')
1250 issue = Issue.first(:order => 'id DESC')
1246 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook', :issue => {:tracker_id => 3}
1251 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook', :issue => {:tracker_id => 3}
1247 assert_not_nil flash[:notice], "flash was not set"
1252 assert_not_nil flash[:notice], "flash was not set"
1248 assert flash[:notice].include?("<a href='/issues/#{issue.id}'>##{issue.id}</a>"), "issue link not found in flash: #{flash[:notice]}"
1253 assert flash[:notice].include?("<a href='/issues/#{issue.id}'>##{issue.id}</a>"), "issue link not found in flash: #{flash[:notice]}"
1249 end
1254 end
1250
1255
1251 def test_post_create_without_custom_fields_param
1256 def test_post_create_without_custom_fields_param
1252 @request.session[:user_id] = 2
1257 @request.session[:user_id] = 2
1253 assert_difference 'Issue.count' do
1258 assert_difference 'Issue.count' do
1254 post :create, :project_id => 1,
1259 post :create, :project_id => 1,
1255 :issue => {:tracker_id => 1,
1260 :issue => {:tracker_id => 1,
1256 :subject => 'This is the test_new issue',
1261 :subject => 'This is the test_new issue',
1257 :description => 'This is the description',
1262 :description => 'This is the description',
1258 :priority_id => 5}
1263 :priority_id => 5}
1259 end
1264 end
1260 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1265 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1261 end
1266 end
1262
1267
1263 def test_post_create_with_required_custom_field_and_without_custom_fields_param
1268 def test_post_create_with_required_custom_field_and_without_custom_fields_param
1264 field = IssueCustomField.find_by_name('Database')
1269 field = IssueCustomField.find_by_name('Database')
1265 field.update_attribute(:is_required, true)
1270 field.update_attribute(:is_required, true)
1266
1271
1267 @request.session[:user_id] = 2
1272 @request.session[:user_id] = 2
1268 post :create, :project_id => 1,
1273 post :create, :project_id => 1,
1269 :issue => {:tracker_id => 1,
1274 :issue => {:tracker_id => 1,
1270 :subject => 'This is the test_new issue',
1275 :subject => 'This is the test_new issue',
1271 :description => 'This is the description',
1276 :description => 'This is the description',
1272 :priority_id => 5}
1277 :priority_id => 5}
1273 assert_response :success
1278 assert_response :success
1274 assert_template 'new'
1279 assert_template 'new'
1275 issue = assigns(:issue)
1280 issue = assigns(:issue)
1276 assert_not_nil issue
1281 assert_not_nil issue
1277 assert_equal I18n.translate('activerecord.errors.messages.invalid'),
1282 assert_equal I18n.translate('activerecord.errors.messages.invalid'),
1278 issue.errors[:custom_values].to_s
1283 issue.errors[:custom_values].to_s
1279 end
1284 end
1280
1285
1281 def test_post_create_with_watchers
1286 def test_post_create_with_watchers
1282 @request.session[:user_id] = 2
1287 @request.session[:user_id] = 2
1283 ActionMailer::Base.deliveries.clear
1288 ActionMailer::Base.deliveries.clear
1284
1289
1285 assert_difference 'Watcher.count', 2 do
1290 assert_difference 'Watcher.count', 2 do
1286 post :create, :project_id => 1,
1291 post :create, :project_id => 1,
1287 :issue => {:tracker_id => 1,
1292 :issue => {:tracker_id => 1,
1288 :subject => 'This is a new issue with watchers',
1293 :subject => 'This is a new issue with watchers',
1289 :description => 'This is the description',
1294 :description => 'This is the description',
1290 :priority_id => 5,
1295 :priority_id => 5,
1291 :watcher_user_ids => ['2', '3']}
1296 :watcher_user_ids => ['2', '3']}
1292 end
1297 end
1293 issue = Issue.find_by_subject('This is a new issue with watchers')
1298 issue = Issue.find_by_subject('This is a new issue with watchers')
1294 assert_not_nil issue
1299 assert_not_nil issue
1295 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
1300 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
1296
1301
1297 # Watchers added
1302 # Watchers added
1298 assert_equal [2, 3], issue.watcher_user_ids.sort
1303 assert_equal [2, 3], issue.watcher_user_ids.sort
1299 assert issue.watched_by?(User.find(3))
1304 assert issue.watched_by?(User.find(3))
1300 # Watchers notified
1305 # Watchers notified
1301 mail = ActionMailer::Base.deliveries.last
1306 mail = ActionMailer::Base.deliveries.last
1302 assert_kind_of TMail::Mail, mail
1307 assert_kind_of TMail::Mail, mail
1303 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
1308 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
1304 end
1309 end
1305
1310
1306 def test_post_create_subissue
1311 def test_post_create_subissue
1307 @request.session[:user_id] = 2
1312 @request.session[:user_id] = 2
1308
1313
1309 assert_difference 'Issue.count' do
1314 assert_difference 'Issue.count' do
1310 post :create, :project_id => 1,
1315 post :create, :project_id => 1,
1311 :issue => {:tracker_id => 1,
1316 :issue => {:tracker_id => 1,
1312 :subject => 'This is a child issue',
1317 :subject => 'This is a child issue',
1313 :parent_issue_id => 2}
1318 :parent_issue_id => 2}
1314 end
1319 end
1315 issue = Issue.find_by_subject('This is a child issue')
1320 issue = Issue.find_by_subject('This is a child issue')
1316 assert_not_nil issue
1321 assert_not_nil issue
1317 assert_equal Issue.find(2), issue.parent
1322 assert_equal Issue.find(2), issue.parent
1318 end
1323 end
1319
1324
1320 def test_post_create_subissue_with_non_numeric_parent_id
1325 def test_post_create_subissue_with_non_numeric_parent_id
1321 @request.session[:user_id] = 2
1326 @request.session[:user_id] = 2
1322
1327
1323 assert_difference 'Issue.count' do
1328 assert_difference 'Issue.count' do
1324 post :create, :project_id => 1,
1329 post :create, :project_id => 1,
1325 :issue => {:tracker_id => 1,
1330 :issue => {:tracker_id => 1,
1326 :subject => 'This is a child issue',
1331 :subject => 'This is a child issue',
1327 :parent_issue_id => 'ABC'}
1332 :parent_issue_id => 'ABC'}
1328 end
1333 end
1329 issue = Issue.find_by_subject('This is a child issue')
1334 issue = Issue.find_by_subject('This is a child issue')
1330 assert_not_nil issue
1335 assert_not_nil issue
1331 assert_nil issue.parent
1336 assert_nil issue.parent
1332 end
1337 end
1333
1338
1334 def test_post_create_private
1339 def test_post_create_private
1335 @request.session[:user_id] = 2
1340 @request.session[:user_id] = 2
1336
1341
1337 assert_difference 'Issue.count' do
1342 assert_difference 'Issue.count' do
1338 post :create, :project_id => 1,
1343 post :create, :project_id => 1,
1339 :issue => {:tracker_id => 1,
1344 :issue => {:tracker_id => 1,
1340 :subject => 'This is a private issue',
1345 :subject => 'This is a private issue',
1341 :is_private => '1'}
1346 :is_private => '1'}
1342 end
1347 end
1343 issue = Issue.first(:order => 'id DESC')
1348 issue = Issue.first(:order => 'id DESC')
1344 assert issue.is_private?
1349 assert issue.is_private?
1345 end
1350 end
1346
1351
1347 def test_post_create_private_with_set_own_issues_private_permission
1352 def test_post_create_private_with_set_own_issues_private_permission
1348 role = Role.find(1)
1353 role = Role.find(1)
1349 role.remove_permission! :set_issues_private
1354 role.remove_permission! :set_issues_private
1350 role.add_permission! :set_own_issues_private
1355 role.add_permission! :set_own_issues_private
1351
1356
1352 @request.session[:user_id] = 2
1357 @request.session[:user_id] = 2
1353
1358
1354 assert_difference 'Issue.count' do
1359 assert_difference 'Issue.count' do
1355 post :create, :project_id => 1,
1360 post :create, :project_id => 1,
1356 :issue => {:tracker_id => 1,
1361 :issue => {:tracker_id => 1,
1357 :subject => 'This is a private issue',
1362 :subject => 'This is a private issue',
1358 :is_private => '1'}
1363 :is_private => '1'}
1359 end
1364 end
1360 issue = Issue.first(:order => 'id DESC')
1365 issue = Issue.first(:order => 'id DESC')
1361 assert issue.is_private?
1366 assert issue.is_private?
1362 end
1367 end
1363
1368
1364 def test_post_create_should_send_a_notification
1369 def test_post_create_should_send_a_notification
1365 ActionMailer::Base.deliveries.clear
1370 ActionMailer::Base.deliveries.clear
1366 @request.session[:user_id] = 2
1371 @request.session[:user_id] = 2
1367 assert_difference 'Issue.count' do
1372 assert_difference 'Issue.count' do
1368 post :create, :project_id => 1,
1373 post :create, :project_id => 1,
1369 :issue => {:tracker_id => 3,
1374 :issue => {:tracker_id => 3,
1370 :subject => 'This is the test_new issue',
1375 :subject => 'This is the test_new issue',
1371 :description => 'This is the description',
1376 :description => 'This is the description',
1372 :priority_id => 5,
1377 :priority_id => 5,
1373 :estimated_hours => '',
1378 :estimated_hours => '',
1374 :custom_field_values => {'2' => 'Value for field 2'}}
1379 :custom_field_values => {'2' => 'Value for field 2'}}
1375 end
1380 end
1376 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1381 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1377
1382
1378 assert_equal 1, ActionMailer::Base.deliveries.size
1383 assert_equal 1, ActionMailer::Base.deliveries.size
1379 end
1384 end
1380
1385
1381 def test_post_create_should_preserve_fields_values_on_validation_failure
1386 def test_post_create_should_preserve_fields_values_on_validation_failure
1382 @request.session[:user_id] = 2
1387 @request.session[:user_id] = 2
1383 post :create, :project_id => 1,
1388 post :create, :project_id => 1,
1384 :issue => {:tracker_id => 1,
1389 :issue => {:tracker_id => 1,
1385 # empty subject
1390 # empty subject
1386 :subject => '',
1391 :subject => '',
1387 :description => 'This is a description',
1392 :description => 'This is a description',
1388 :priority_id => 6,
1393 :priority_id => 6,
1389 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
1394 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
1390 assert_response :success
1395 assert_response :success
1391 assert_template 'new'
1396 assert_template 'new'
1392
1397
1393 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
1398 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
1394 :content => 'This is a description'
1399 :content => 'This is a description'
1395 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
1400 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
1396 :child => { :tag => 'option', :attributes => { :selected => 'selected',
1401 :child => { :tag => 'option', :attributes => { :selected => 'selected',
1397 :value => '6' },
1402 :value => '6' },
1398 :content => 'High' }
1403 :content => 'High' }
1399 # Custom fields
1404 # Custom fields
1400 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
1405 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
1401 :child => { :tag => 'option', :attributes => { :selected => 'selected',
1406 :child => { :tag => 'option', :attributes => { :selected => 'selected',
1402 :value => 'Oracle' },
1407 :value => 'Oracle' },
1403 :content => 'Oracle' }
1408 :content => 'Oracle' }
1404 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
1409 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
1405 :value => 'Value for field 2'}
1410 :value => 'Value for field 2'}
1406 end
1411 end
1407
1412
1408 def test_post_create_should_ignore_non_safe_attributes
1413 def test_post_create_should_ignore_non_safe_attributes
1409 @request.session[:user_id] = 2
1414 @request.session[:user_id] = 2
1410 assert_nothing_raised do
1415 assert_nothing_raised do
1411 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
1416 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
1412 end
1417 end
1413 end
1418 end
1414
1419
1415 def test_post_create_with_attachment
1420 def test_post_create_with_attachment
1416 set_tmp_attachments_directory
1421 set_tmp_attachments_directory
1417 @request.session[:user_id] = 2
1422 @request.session[:user_id] = 2
1418
1423
1419 assert_difference 'Issue.count' do
1424 assert_difference 'Issue.count' do
1420 assert_difference 'Attachment.count' do
1425 assert_difference 'Attachment.count' do
1421 post :create, :project_id => 1,
1426 post :create, :project_id => 1,
1422 :issue => { :tracker_id => '1', :subject => 'With attachment' },
1427 :issue => { :tracker_id => '1', :subject => 'With attachment' },
1423 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
1428 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
1424 end
1429 end
1425 end
1430 end
1426
1431
1427 issue = Issue.first(:order => 'id DESC')
1432 issue = Issue.first(:order => 'id DESC')
1428 attachment = Attachment.first(:order => 'id DESC')
1433 attachment = Attachment.first(:order => 'id DESC')
1429
1434
1430 assert_equal issue, attachment.container
1435 assert_equal issue, attachment.container
1431 assert_equal 2, attachment.author_id
1436 assert_equal 2, attachment.author_id
1432 assert_equal 'testfile.txt', attachment.filename
1437 assert_equal 'testfile.txt', attachment.filename
1433 assert_equal 'text/plain', attachment.content_type
1438 assert_equal 'text/plain', attachment.content_type
1434 assert_equal 'test file', attachment.description
1439 assert_equal 'test file', attachment.description
1435 assert_equal 59, attachment.filesize
1440 assert_equal 59, attachment.filesize
1436 assert File.exists?(attachment.diskfile)
1441 assert File.exists?(attachment.diskfile)
1437 assert_equal 59, File.size(attachment.diskfile)
1442 assert_equal 59, File.size(attachment.diskfile)
1438 end
1443 end
1439
1444
1440 context "without workflow privilege" do
1445 context "without workflow privilege" do
1441 setup do
1446 setup do
1442 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
1447 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
1443 Role.anonymous.add_permission! :add_issues, :add_issue_notes
1448 Role.anonymous.add_permission! :add_issues, :add_issue_notes
1444 end
1449 end
1445
1450
1446 context "#new" do
1451 context "#new" do
1447 should "propose default status only" do
1452 should "propose default status only" do
1448 get :new, :project_id => 1
1453 get :new, :project_id => 1
1449 assert_response :success
1454 assert_response :success
1450 assert_template 'new'
1455 assert_template 'new'
1451 assert_tag :tag => 'select',
1456 assert_tag :tag => 'select',
1452 :attributes => {:name => 'issue[status_id]'},
1457 :attributes => {:name => 'issue[status_id]'},
1453 :children => {:count => 1},
1458 :children => {:count => 1},
1454 :child => {:tag => 'option', :attributes => {:value => IssueStatus.default.id.to_s}}
1459 :child => {:tag => 'option', :attributes => {:value => IssueStatus.default.id.to_s}}
1455 end
1460 end
1456
1461
1457 should "accept default status" do
1462 should "accept default status" do
1458 assert_difference 'Issue.count' do
1463 assert_difference 'Issue.count' do
1459 post :create, :project_id => 1,
1464 post :create, :project_id => 1,
1460 :issue => {:tracker_id => 1,
1465 :issue => {:tracker_id => 1,
1461 :subject => 'This is an issue',
1466 :subject => 'This is an issue',
1462 :status_id => 1}
1467 :status_id => 1}
1463 end
1468 end
1464 issue = Issue.last(:order => 'id')
1469 issue = Issue.last(:order => 'id')
1465 assert_equal IssueStatus.default, issue.status
1470 assert_equal IssueStatus.default, issue.status
1466 end
1471 end
1467
1472
1468 should "ignore unauthorized status" do
1473 should "ignore unauthorized status" do
1469 assert_difference 'Issue.count' do
1474 assert_difference 'Issue.count' do
1470 post :create, :project_id => 1,
1475 post :create, :project_id => 1,
1471 :issue => {:tracker_id => 1,
1476 :issue => {:tracker_id => 1,
1472 :subject => 'This is an issue',
1477 :subject => 'This is an issue',
1473 :status_id => 3}
1478 :status_id => 3}
1474 end
1479 end
1475 issue = Issue.last(:order => 'id')
1480 issue = Issue.last(:order => 'id')
1476 assert_equal IssueStatus.default, issue.status
1481 assert_equal IssueStatus.default, issue.status
1477 end
1482 end
1478 end
1483 end
1479
1484
1480 context "#update" do
1485 context "#update" do
1481 should "ignore status change" do
1486 should "ignore status change" do
1482 assert_difference 'Journal.count' do
1487 assert_difference 'Journal.count' do
1483 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1488 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1484 end
1489 end
1485 assert_equal 1, Issue.find(1).status_id
1490 assert_equal 1, Issue.find(1).status_id
1486 end
1491 end
1487
1492
1488 should "ignore attributes changes" do
1493 should "ignore attributes changes" do
1489 assert_difference 'Journal.count' do
1494 assert_difference 'Journal.count' do
1490 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
1495 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
1491 end
1496 end
1492 issue = Issue.find(1)
1497 issue = Issue.find(1)
1493 assert_equal "Can't print recipes", issue.subject
1498 assert_equal "Can't print recipes", issue.subject
1494 assert_nil issue.assigned_to
1499 assert_nil issue.assigned_to
1495 end
1500 end
1496 end
1501 end
1497 end
1502 end
1498
1503
1499 context "with workflow privilege" do
1504 context "with workflow privilege" do
1500 setup do
1505 setup do
1501 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
1506 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
1502 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
1507 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
1503 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
1508 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
1504 Role.anonymous.add_permission! :add_issues, :add_issue_notes
1509 Role.anonymous.add_permission! :add_issues, :add_issue_notes
1505 end
1510 end
1506
1511
1507 context "#update" do
1512 context "#update" do
1508 should "accept authorized status" do
1513 should "accept authorized status" do
1509 assert_difference 'Journal.count' do
1514 assert_difference 'Journal.count' do
1510 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1515 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1511 end
1516 end
1512 assert_equal 3, Issue.find(1).status_id
1517 assert_equal 3, Issue.find(1).status_id
1513 end
1518 end
1514
1519
1515 should "ignore unauthorized status" do
1520 should "ignore unauthorized status" do
1516 assert_difference 'Journal.count' do
1521 assert_difference 'Journal.count' do
1517 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
1522 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
1518 end
1523 end
1519 assert_equal 1, Issue.find(1).status_id
1524 assert_equal 1, Issue.find(1).status_id
1520 end
1525 end
1521
1526
1522 should "accept authorized attributes changes" do
1527 should "accept authorized attributes changes" do
1523 assert_difference 'Journal.count' do
1528 assert_difference 'Journal.count' do
1524 put :update, :id => 1, :notes => 'just trying', :issue => {:assigned_to_id => 2}
1529 put :update, :id => 1, :notes => 'just trying', :issue => {:assigned_to_id => 2}
1525 end
1530 end
1526 issue = Issue.find(1)
1531 issue = Issue.find(1)
1527 assert_equal 2, issue.assigned_to_id
1532 assert_equal 2, issue.assigned_to_id
1528 end
1533 end
1529
1534
1530 should "ignore unauthorized attributes changes" do
1535 should "ignore unauthorized attributes changes" do
1531 assert_difference 'Journal.count' do
1536 assert_difference 'Journal.count' do
1532 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed'}
1537 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed'}
1533 end
1538 end
1534 issue = Issue.find(1)
1539 issue = Issue.find(1)
1535 assert_equal "Can't print recipes", issue.subject
1540 assert_equal "Can't print recipes", issue.subject
1536 end
1541 end
1537 end
1542 end
1538
1543
1539 context "and :edit_issues permission" do
1544 context "and :edit_issues permission" do
1540 setup do
1545 setup do
1541 Role.anonymous.add_permission! :add_issues, :edit_issues
1546 Role.anonymous.add_permission! :add_issues, :edit_issues
1542 end
1547 end
1543
1548
1544 should "accept authorized status" do
1549 should "accept authorized status" do
1545 assert_difference 'Journal.count' do
1550 assert_difference 'Journal.count' do
1546 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1551 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1547 end
1552 end
1548 assert_equal 3, Issue.find(1).status_id
1553 assert_equal 3, Issue.find(1).status_id
1549 end
1554 end
1550
1555
1551 should "ignore unauthorized status" do
1556 should "ignore unauthorized status" do
1552 assert_difference 'Journal.count' do
1557 assert_difference 'Journal.count' do
1553 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
1558 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
1554 end
1559 end
1555 assert_equal 1, Issue.find(1).status_id
1560 assert_equal 1, Issue.find(1).status_id
1556 end
1561 end
1557
1562
1558 should "accept authorized attributes changes" do
1563 should "accept authorized attributes changes" do
1559 assert_difference 'Journal.count' do
1564 assert_difference 'Journal.count' do
1560 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
1565 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
1561 end
1566 end
1562 issue = Issue.find(1)
1567 issue = Issue.find(1)
1563 assert_equal "changed", issue.subject
1568 assert_equal "changed", issue.subject
1564 assert_equal 2, issue.assigned_to_id
1569 assert_equal 2, issue.assigned_to_id
1565 end
1570 end
1566 end
1571 end
1567 end
1572 end
1568
1573
1569 def test_copy_issue
1574 def test_copy_issue
1570 @request.session[:user_id] = 2
1575 @request.session[:user_id] = 2
1571 get :new, :project_id => 1, :copy_from => 1
1576 get :new, :project_id => 1, :copy_from => 1
1572 assert_template 'new'
1577 assert_template 'new'
1573 assert_not_nil assigns(:issue)
1578 assert_not_nil assigns(:issue)
1574 orig = Issue.find(1)
1579 orig = Issue.find(1)
1575 assert_equal orig.subject, assigns(:issue).subject
1580 assert_equal orig.subject, assigns(:issue).subject
1576 end
1581 end
1577
1582
1578 def test_get_edit
1583 def test_get_edit
1579 @request.session[:user_id] = 2
1584 @request.session[:user_id] = 2
1580 get :edit, :id => 1
1585 get :edit, :id => 1
1581 assert_response :success
1586 assert_response :success
1582 assert_template 'edit'
1587 assert_template 'edit'
1583 assert_not_nil assigns(:issue)
1588 assert_not_nil assigns(:issue)
1584 assert_equal Issue.find(1), assigns(:issue)
1589 assert_equal Issue.find(1), assigns(:issue)
1585
1590
1586 # Be sure we don't display inactive IssuePriorities
1591 # Be sure we don't display inactive IssuePriorities
1587 assert ! IssuePriority.find(15).active?
1592 assert ! IssuePriority.find(15).active?
1588 assert_no_tag :option, :attributes => {:value => '15'},
1593 assert_no_tag :option, :attributes => {:value => '15'},
1589 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1594 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1590 end
1595 end
1591
1596
1592 def test_get_edit_should_display_the_time_entry_form_with_log_time_permission
1597 def test_get_edit_should_display_the_time_entry_form_with_log_time_permission
1593 @request.session[:user_id] = 2
1598 @request.session[:user_id] = 2
1594 Role.find_by_name('Manager').update_attribute :permissions, [:view_issues, :edit_issues, :log_time]
1599 Role.find_by_name('Manager').update_attribute :permissions, [:view_issues, :edit_issues, :log_time]
1595
1600
1596 get :edit, :id => 1
1601 get :edit, :id => 1
1597 assert_tag 'input', :attributes => {:name => 'time_entry[hours]'}
1602 assert_tag 'input', :attributes => {:name => 'time_entry[hours]'}
1598 end
1603 end
1599
1604
1600 def test_get_edit_should_not_display_the_time_entry_form_without_log_time_permission
1605 def test_get_edit_should_not_display_the_time_entry_form_without_log_time_permission
1601 @request.session[:user_id] = 2
1606 @request.session[:user_id] = 2
1602 Role.find_by_name('Manager').remove_permission! :log_time
1607 Role.find_by_name('Manager').remove_permission! :log_time
1603
1608
1604 get :edit, :id => 1
1609 get :edit, :id => 1
1605 assert_no_tag 'input', :attributes => {:name => 'time_entry[hours]'}
1610 assert_no_tag 'input', :attributes => {:name => 'time_entry[hours]'}
1606 end
1611 end
1607
1612
1608 def test_get_edit_with_params
1613 def test_get_edit_with_params
1609 @request.session[:user_id] = 2
1614 @request.session[:user_id] = 2
1610 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 },
1615 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 },
1611 :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => TimeEntryActivity.first.id }
1616 :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => TimeEntryActivity.first.id }
1612 assert_response :success
1617 assert_response :success
1613 assert_template 'edit'
1618 assert_template 'edit'
1614
1619
1615 issue = assigns(:issue)
1620 issue = assigns(:issue)
1616 assert_not_nil issue
1621 assert_not_nil issue
1617
1622
1618 assert_equal 5, issue.status_id
1623 assert_equal 5, issue.status_id
1619 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
1624 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
1620 :child => { :tag => 'option',
1625 :child => { :tag => 'option',
1621 :content => 'Closed',
1626 :content => 'Closed',
1622 :attributes => { :selected => 'selected' } }
1627 :attributes => { :selected => 'selected' } }
1623
1628
1624 assert_equal 7, issue.priority_id
1629 assert_equal 7, issue.priority_id
1625 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
1630 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
1626 :child => { :tag => 'option',
1631 :child => { :tag => 'option',
1627 :content => 'Urgent',
1632 :content => 'Urgent',
1628 :attributes => { :selected => 'selected' } }
1633 :attributes => { :selected => 'selected' } }
1629
1634
1630 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => '2.5' }
1635 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => '2.5' }
1631 assert_tag :select, :attributes => { :name => 'time_entry[activity_id]' },
1636 assert_tag :select, :attributes => { :name => 'time_entry[activity_id]' },
1632 :child => { :tag => 'option',
1637 :child => { :tag => 'option',
1633 :attributes => { :selected => 'selected', :value => TimeEntryActivity.first.id } }
1638 :attributes => { :selected => 'selected', :value => TimeEntryActivity.first.id } }
1634 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => 'test_get_edit_with_params' }
1639 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => 'test_get_edit_with_params' }
1635 end
1640 end
1636
1641
1637 def test_update_edit_form
1642 def test_update_edit_form
1638 @request.session[:user_id] = 2
1643 @request.session[:user_id] = 2
1639 xhr :post, :new, :project_id => 1,
1644 xhr :put, :new, :project_id => 1,
1640 :id => 1,
1645 :id => 1,
1641 :issue => {:tracker_id => 2,
1646 :issue => {:tracker_id => 2,
1642 :subject => 'This is the test_new issue',
1647 :subject => 'This is the test_new issue',
1643 :description => 'This is the description',
1648 :description => 'This is the description',
1644 :priority_id => 5}
1649 :priority_id => 5}
1645 assert_response :success
1650 assert_response :success
1646 assert_template 'attributes'
1651 assert_template 'attributes'
1647
1652
1648 issue = assigns(:issue)
1653 issue = assigns(:issue)
1649 assert_kind_of Issue, issue
1654 assert_kind_of Issue, issue
1650 assert_equal 1, issue.id
1655 assert_equal 1, issue.id
1651 assert_equal 1, issue.project_id
1656 assert_equal 1, issue.project_id
1652 assert_equal 2, issue.tracker_id
1657 assert_equal 2, issue.tracker_id
1653 assert_equal 'This is the test_new issue', issue.subject
1658 assert_equal 'This is the test_new issue', issue.subject
1654 end
1659 end
1655
1660
1661 def test_update_edit_form_with_project_change
1662 @request.session[:user_id] = 2
1663 xhr :put, :new, :project_id => 1,
1664 :id => 1,
1665 :project_change => '1',
1666 :issue => {:project_id => 2,
1667 :tracker_id => 2,
1668 :subject => 'This is the test_new issue',
1669 :description => 'This is the description',
1670 :priority_id => 5}
1671 assert_response :success
1672 assert_template 'form'
1673
1674 issue = assigns(:issue)
1675 assert_kind_of Issue, issue
1676 assert_equal 1, issue.id
1677 assert_equal 2, issue.project_id
1678 assert_equal 2, issue.tracker_id
1679 assert_equal 'This is the test_new issue', issue.subject
1680 end
1681
1656 def test_update_using_invalid_http_verbs
1682 def test_update_using_invalid_http_verbs
1657 @request.session[:user_id] = 2
1683 @request.session[:user_id] = 2
1658 subject = 'Updated by an invalid http verb'
1684 subject = 'Updated by an invalid http verb'
1659
1685
1660 get :update, :id => 1, :issue => {:subject => subject}
1686 get :update, :id => 1, :issue => {:subject => subject}
1661 assert_not_equal subject, Issue.find(1).subject
1687 assert_not_equal subject, Issue.find(1).subject
1662
1688
1663 post :update, :id => 1, :issue => {:subject => subject}
1689 post :update, :id => 1, :issue => {:subject => subject}
1664 assert_not_equal subject, Issue.find(1).subject
1690 assert_not_equal subject, Issue.find(1).subject
1665
1691
1666 delete :update, :id => 1, :issue => {:subject => subject}
1692 delete :update, :id => 1, :issue => {:subject => subject}
1667 assert_not_equal subject, Issue.find(1).subject
1693 assert_not_equal subject, Issue.find(1).subject
1668 end
1694 end
1669
1695
1670 def test_put_update_without_custom_fields_param
1696 def test_put_update_without_custom_fields_param
1671 @request.session[:user_id] = 2
1697 @request.session[:user_id] = 2
1672 ActionMailer::Base.deliveries.clear
1698 ActionMailer::Base.deliveries.clear
1673
1699
1674 issue = Issue.find(1)
1700 issue = Issue.find(1)
1675 assert_equal '125', issue.custom_value_for(2).value
1701 assert_equal '125', issue.custom_value_for(2).value
1676 old_subject = issue.subject
1702 old_subject = issue.subject
1677 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
1703 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
1678
1704
1679 assert_difference('Journal.count') do
1705 assert_difference('Journal.count') do
1680 assert_difference('JournalDetail.count', 2) do
1706 assert_difference('JournalDetail.count', 2) do
1681 put :update, :id => 1, :issue => {:subject => new_subject,
1707 put :update, :id => 1, :issue => {:subject => new_subject,
1682 :priority_id => '6',
1708 :priority_id => '6',
1683 :category_id => '1' # no change
1709 :category_id => '1' # no change
1684 }
1710 }
1685 end
1711 end
1686 end
1712 end
1687 assert_redirected_to :action => 'show', :id => '1'
1713 assert_redirected_to :action => 'show', :id => '1'
1688 issue.reload
1714 issue.reload
1689 assert_equal new_subject, issue.subject
1715 assert_equal new_subject, issue.subject
1690 # Make sure custom fields were not cleared
1716 # Make sure custom fields were not cleared
1691 assert_equal '125', issue.custom_value_for(2).value
1717 assert_equal '125', issue.custom_value_for(2).value
1692
1718
1693 mail = ActionMailer::Base.deliveries.last
1719 mail = ActionMailer::Base.deliveries.last
1694 assert_kind_of TMail::Mail, mail
1720 assert_kind_of TMail::Mail, mail
1695 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
1721 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
1696 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
1722 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
1697 end
1723 end
1698
1724
1725 def test_put_update_with_project_change
1726 @request.session[:user_id] = 2
1727 ActionMailer::Base.deliveries.clear
1728
1729 assert_difference('Journal.count') do
1730 assert_difference('JournalDetail.count', 3) do
1731 put :update, :id => 1, :issue => {:project_id => '2',
1732 :tracker_id => '1', # no change
1733 :priority_id => '6',
1734 :category_id => '3'
1735 }
1736 end
1737 end
1738 assert_redirected_to :action => 'show', :id => '1'
1739 issue = Issue.find(1)
1740 assert_equal 2, issue.project_id
1741 assert_equal 1, issue.tracker_id
1742 assert_equal 6, issue.priority_id
1743 assert_equal 3, issue.category_id
1744
1745 mail = ActionMailer::Base.deliveries.last
1746 assert_not_nil mail
1747 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
1748 assert mail.body.include?("Project changed from eCookbook to OnlineStore")
1749 end
1750
1751 def test_put_update_with_tracker_change
1752 @request.session[:user_id] = 2
1753 ActionMailer::Base.deliveries.clear
1754
1755 assert_difference('Journal.count') do
1756 assert_difference('JournalDetail.count', 2) do
1757 put :update, :id => 1, :issue => {:project_id => '1',
1758 :tracker_id => '2',
1759 :priority_id => '6'
1760 }
1761 end
1762 end
1763 assert_redirected_to :action => 'show', :id => '1'
1764 issue = Issue.find(1)
1765 assert_equal 1, issue.project_id
1766 assert_equal 2, issue.tracker_id
1767 assert_equal 6, issue.priority_id
1768 assert_equal 1, issue.category_id
1769
1770 mail = ActionMailer::Base.deliveries.last
1771 assert_not_nil mail
1772 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
1773 assert mail.body.include?("Tracker changed from Bug to Feature request")
1774 end
1775
1699 def test_put_update_with_custom_field_change
1776 def test_put_update_with_custom_field_change
1700 @request.session[:user_id] = 2
1777 @request.session[:user_id] = 2
1701 issue = Issue.find(1)
1778 issue = Issue.find(1)
1702 assert_equal '125', issue.custom_value_for(2).value
1779 assert_equal '125', issue.custom_value_for(2).value
1703
1780
1704 assert_difference('Journal.count') do
1781 assert_difference('Journal.count') do
1705 assert_difference('JournalDetail.count', 3) do
1782 assert_difference('JournalDetail.count', 3) do
1706 put :update, :id => 1, :issue => {:subject => 'Custom field change',
1783 put :update, :id => 1, :issue => {:subject => 'Custom field change',
1707 :priority_id => '6',
1784 :priority_id => '6',
1708 :category_id => '1', # no change
1785 :category_id => '1', # no change
1709 :custom_field_values => { '2' => 'New custom value' }
1786 :custom_field_values => { '2' => 'New custom value' }
1710 }
1787 }
1711 end
1788 end
1712 end
1789 end
1713 assert_redirected_to :action => 'show', :id => '1'
1790 assert_redirected_to :action => 'show', :id => '1'
1714 issue.reload
1791 issue.reload
1715 assert_equal 'New custom value', issue.custom_value_for(2).value
1792 assert_equal 'New custom value', issue.custom_value_for(2).value
1716
1793
1717 mail = ActionMailer::Base.deliveries.last
1794 mail = ActionMailer::Base.deliveries.last
1718 assert_kind_of TMail::Mail, mail
1795 assert_kind_of TMail::Mail, mail
1719 assert mail.body.include?("Searchable field changed from 125 to New custom value")
1796 assert mail.body.include?("Searchable field changed from 125 to New custom value")
1720 end
1797 end
1721
1798
1722 def test_put_update_with_status_and_assignee_change
1799 def test_put_update_with_status_and_assignee_change
1723 issue = Issue.find(1)
1800 issue = Issue.find(1)
1724 assert_equal 1, issue.status_id
1801 assert_equal 1, issue.status_id
1725 @request.session[:user_id] = 2
1802 @request.session[:user_id] = 2
1726 assert_difference('TimeEntry.count', 0) do
1803 assert_difference('TimeEntry.count', 0) do
1727 put :update,
1804 put :update,
1728 :id => 1,
1805 :id => 1,
1729 :issue => { :status_id => 2, :assigned_to_id => 3 },
1806 :issue => { :status_id => 2, :assigned_to_id => 3 },
1730 :notes => 'Assigned to dlopper',
1807 :notes => 'Assigned to dlopper',
1731 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
1808 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
1732 end
1809 end
1733 assert_redirected_to :action => 'show', :id => '1'
1810 assert_redirected_to :action => 'show', :id => '1'
1734 issue.reload
1811 issue.reload
1735 assert_equal 2, issue.status_id
1812 assert_equal 2, issue.status_id
1736 j = Journal.find(:first, :order => 'id DESC')
1813 j = Journal.find(:first, :order => 'id DESC')
1737 assert_equal 'Assigned to dlopper', j.notes
1814 assert_equal 'Assigned to dlopper', j.notes
1738 assert_equal 2, j.details.size
1815 assert_equal 2, j.details.size
1739
1816
1740 mail = ActionMailer::Base.deliveries.last
1817 mail = ActionMailer::Base.deliveries.last
1741 assert mail.body.include?("Status changed from New to Assigned")
1818 assert mail.body.include?("Status changed from New to Assigned")
1742 # subject should contain the new status
1819 # subject should contain the new status
1743 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
1820 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
1744 end
1821 end
1745
1822
1746 def test_put_update_with_note_only
1823 def test_put_update_with_note_only
1747 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
1824 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
1748 # anonymous user
1825 # anonymous user
1749 put :update,
1826 put :update,
1750 :id => 1,
1827 :id => 1,
1751 :notes => notes
1828 :notes => notes
1752 assert_redirected_to :action => 'show', :id => '1'
1829 assert_redirected_to :action => 'show', :id => '1'
1753 j = Journal.find(:first, :order => 'id DESC')
1830 j = Journal.find(:first, :order => 'id DESC')
1754 assert_equal notes, j.notes
1831 assert_equal notes, j.notes
1755 assert_equal 0, j.details.size
1832 assert_equal 0, j.details.size
1756 assert_equal User.anonymous, j.user
1833 assert_equal User.anonymous, j.user
1757
1834
1758 mail = ActionMailer::Base.deliveries.last
1835 mail = ActionMailer::Base.deliveries.last
1759 assert mail.body.include?(notes)
1836 assert mail.body.include?(notes)
1760 end
1837 end
1761
1838
1762 def test_put_update_with_note_and_spent_time
1839 def test_put_update_with_note_and_spent_time
1763 @request.session[:user_id] = 2
1840 @request.session[:user_id] = 2
1764 spent_hours_before = Issue.find(1).spent_hours
1841 spent_hours_before = Issue.find(1).spent_hours
1765 assert_difference('TimeEntry.count') do
1842 assert_difference('TimeEntry.count') do
1766 put :update,
1843 put :update,
1767 :id => 1,
1844 :id => 1,
1768 :notes => '2.5 hours added',
1845 :notes => '2.5 hours added',
1769 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
1846 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
1770 end
1847 end
1771 assert_redirected_to :action => 'show', :id => '1'
1848 assert_redirected_to :action => 'show', :id => '1'
1772
1849
1773 issue = Issue.find(1)
1850 issue = Issue.find(1)
1774
1851
1775 j = Journal.find(:first, :order => 'id DESC')
1852 j = Journal.find(:first, :order => 'id DESC')
1776 assert_equal '2.5 hours added', j.notes
1853 assert_equal '2.5 hours added', j.notes
1777 assert_equal 0, j.details.size
1854 assert_equal 0, j.details.size
1778
1855
1779 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
1856 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
1780 assert_not_nil t
1857 assert_not_nil t
1781 assert_equal 2.5, t.hours
1858 assert_equal 2.5, t.hours
1782 assert_equal spent_hours_before + 2.5, issue.spent_hours
1859 assert_equal spent_hours_before + 2.5, issue.spent_hours
1783 end
1860 end
1784
1861
1785 def test_put_update_with_attachment_only
1862 def test_put_update_with_attachment_only
1786 set_tmp_attachments_directory
1863 set_tmp_attachments_directory
1787
1864
1788 # Delete all fixtured journals, a race condition can occur causing the wrong
1865 # Delete all fixtured journals, a race condition can occur causing the wrong
1789 # journal to get fetched in the next find.
1866 # journal to get fetched in the next find.
1790 Journal.delete_all
1867 Journal.delete_all
1791
1868
1792 # anonymous user
1869 # anonymous user
1793 assert_difference 'Attachment.count' do
1870 assert_difference 'Attachment.count' do
1794 put :update, :id => 1,
1871 put :update, :id => 1,
1795 :notes => '',
1872 :notes => '',
1796 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
1873 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
1797 end
1874 end
1798
1875
1799 assert_redirected_to :action => 'show', :id => '1'
1876 assert_redirected_to :action => 'show', :id => '1'
1800 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
1877 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
1801 assert j.notes.blank?
1878 assert j.notes.blank?
1802 assert_equal 1, j.details.size
1879 assert_equal 1, j.details.size
1803 assert_equal 'testfile.txt', j.details.first.value
1880 assert_equal 'testfile.txt', j.details.first.value
1804 assert_equal User.anonymous, j.user
1881 assert_equal User.anonymous, j.user
1805
1882
1806 attachment = Attachment.first(:order => 'id DESC')
1883 attachment = Attachment.first(:order => 'id DESC')
1807 assert_equal Issue.find(1), attachment.container
1884 assert_equal Issue.find(1), attachment.container
1808 assert_equal User.anonymous, attachment.author
1885 assert_equal User.anonymous, attachment.author
1809 assert_equal 'testfile.txt', attachment.filename
1886 assert_equal 'testfile.txt', attachment.filename
1810 assert_equal 'text/plain', attachment.content_type
1887 assert_equal 'text/plain', attachment.content_type
1811 assert_equal 'test file', attachment.description
1888 assert_equal 'test file', attachment.description
1812 assert_equal 59, attachment.filesize
1889 assert_equal 59, attachment.filesize
1813 assert File.exists?(attachment.diskfile)
1890 assert File.exists?(attachment.diskfile)
1814 assert_equal 59, File.size(attachment.diskfile)
1891 assert_equal 59, File.size(attachment.diskfile)
1815
1892
1816 mail = ActionMailer::Base.deliveries.last
1893 mail = ActionMailer::Base.deliveries.last
1817 assert mail.body.include?('testfile.txt')
1894 assert mail.body.include?('testfile.txt')
1818 end
1895 end
1819
1896
1820 def test_put_update_with_attachment_that_fails_to_save
1897 def test_put_update_with_attachment_that_fails_to_save
1821 set_tmp_attachments_directory
1898 set_tmp_attachments_directory
1822
1899
1823 # Delete all fixtured journals, a race condition can occur causing the wrong
1900 # Delete all fixtured journals, a race condition can occur causing the wrong
1824 # journal to get fetched in the next find.
1901 # journal to get fetched in the next find.
1825 Journal.delete_all
1902 Journal.delete_all
1826
1903
1827 # Mock out the unsaved attachment
1904 # Mock out the unsaved attachment
1828 Attachment.any_instance.stubs(:create).returns(Attachment.new)
1905 Attachment.any_instance.stubs(:create).returns(Attachment.new)
1829
1906
1830 # anonymous user
1907 # anonymous user
1831 put :update,
1908 put :update,
1832 :id => 1,
1909 :id => 1,
1833 :notes => '',
1910 :notes => '',
1834 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
1911 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
1835 assert_redirected_to :action => 'show', :id => '1'
1912 assert_redirected_to :action => 'show', :id => '1'
1836 assert_equal '1 file(s) could not be saved.', flash[:warning]
1913 assert_equal '1 file(s) could not be saved.', flash[:warning]
1837
1914
1838 end if Object.const_defined?(:Mocha)
1915 end if Object.const_defined?(:Mocha)
1839
1916
1840 def test_put_update_with_no_change
1917 def test_put_update_with_no_change
1841 issue = Issue.find(1)
1918 issue = Issue.find(1)
1842 issue.journals.clear
1919 issue.journals.clear
1843 ActionMailer::Base.deliveries.clear
1920 ActionMailer::Base.deliveries.clear
1844
1921
1845 put :update,
1922 put :update,
1846 :id => 1,
1923 :id => 1,
1847 :notes => ''
1924 :notes => ''
1848 assert_redirected_to :action => 'show', :id => '1'
1925 assert_redirected_to :action => 'show', :id => '1'
1849
1926
1850 issue.reload
1927 issue.reload
1851 assert issue.journals.empty?
1928 assert issue.journals.empty?
1852 # No email should be sent
1929 # No email should be sent
1853 assert ActionMailer::Base.deliveries.empty?
1930 assert ActionMailer::Base.deliveries.empty?
1854 end
1931 end
1855
1932
1856 def test_put_update_should_send_a_notification
1933 def test_put_update_should_send_a_notification
1857 @request.session[:user_id] = 2
1934 @request.session[:user_id] = 2
1858 ActionMailer::Base.deliveries.clear
1935 ActionMailer::Base.deliveries.clear
1859 issue = Issue.find(1)
1936 issue = Issue.find(1)
1860 old_subject = issue.subject
1937 old_subject = issue.subject
1861 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
1938 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
1862
1939
1863 put :update, :id => 1, :issue => {:subject => new_subject,
1940 put :update, :id => 1, :issue => {:subject => new_subject,
1864 :priority_id => '6',
1941 :priority_id => '6',
1865 :category_id => '1' # no change
1942 :category_id => '1' # no change
1866 }
1943 }
1867 assert_equal 1, ActionMailer::Base.deliveries.size
1944 assert_equal 1, ActionMailer::Base.deliveries.size
1868 end
1945 end
1869
1946
1870 def test_put_update_with_invalid_spent_time_hours_only
1947 def test_put_update_with_invalid_spent_time_hours_only
1871 @request.session[:user_id] = 2
1948 @request.session[:user_id] = 2
1872 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
1949 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
1873
1950
1874 assert_no_difference('Journal.count') do
1951 assert_no_difference('Journal.count') do
1875 put :update,
1952 put :update,
1876 :id => 1,
1953 :id => 1,
1877 :notes => notes,
1954 :notes => notes,
1878 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
1955 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
1879 end
1956 end
1880 assert_response :success
1957 assert_response :success
1881 assert_template 'edit'
1958 assert_template 'edit'
1882
1959
1883 assert_error_tag :descendant => {:content => /Activity can't be blank/}
1960 assert_error_tag :descendant => {:content => /Activity can't be blank/}
1884 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
1961 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
1885 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
1962 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
1886 end
1963 end
1887
1964
1888 def test_put_update_with_invalid_spent_time_comments_only
1965 def test_put_update_with_invalid_spent_time_comments_only
1889 @request.session[:user_id] = 2
1966 @request.session[:user_id] = 2
1890 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
1967 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
1891
1968
1892 assert_no_difference('Journal.count') do
1969 assert_no_difference('Journal.count') do
1893 put :update,
1970 put :update,
1894 :id => 1,
1971 :id => 1,
1895 :notes => notes,
1972 :notes => notes,
1896 :time_entry => {"comments"=>"this is my comment", "activity_id"=>"", "hours"=>""}
1973 :time_entry => {"comments"=>"this is my comment", "activity_id"=>"", "hours"=>""}
1897 end
1974 end
1898 assert_response :success
1975 assert_response :success
1899 assert_template 'edit'
1976 assert_template 'edit'
1900
1977
1901 assert_error_tag :descendant => {:content => /Activity can't be blank/}
1978 assert_error_tag :descendant => {:content => /Activity can't be blank/}
1902 assert_error_tag :descendant => {:content => /Hours can't be blank/}
1979 assert_error_tag :descendant => {:content => /Hours can't be blank/}
1903 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
1980 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
1904 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => "this is my comment" }
1981 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => "this is my comment" }
1905 end
1982 end
1906
1983
1907 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
1984 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
1908 issue = Issue.find(2)
1985 issue = Issue.find(2)
1909 @request.session[:user_id] = 2
1986 @request.session[:user_id] = 2
1910
1987
1911 put :update,
1988 put :update,
1912 :id => issue.id,
1989 :id => issue.id,
1913 :issue => {
1990 :issue => {
1914 :fixed_version_id => 4
1991 :fixed_version_id => 4
1915 }
1992 }
1916
1993
1917 assert_response :redirect
1994 assert_response :redirect
1918 issue.reload
1995 issue.reload
1919 assert_equal 4, issue.fixed_version_id
1996 assert_equal 4, issue.fixed_version_id
1920 assert_not_equal issue.project_id, issue.fixed_version.project_id
1997 assert_not_equal issue.project_id, issue.fixed_version.project_id
1921 end
1998 end
1922
1999
1923 def test_put_update_should_redirect_back_using_the_back_url_parameter
2000 def test_put_update_should_redirect_back_using_the_back_url_parameter
1924 issue = Issue.find(2)
2001 issue = Issue.find(2)
1925 @request.session[:user_id] = 2
2002 @request.session[:user_id] = 2
1926
2003
1927 put :update,
2004 put :update,
1928 :id => issue.id,
2005 :id => issue.id,
1929 :issue => {
2006 :issue => {
1930 :fixed_version_id => 4
2007 :fixed_version_id => 4
1931 },
2008 },
1932 :back_url => '/issues'
2009 :back_url => '/issues'
1933
2010
1934 assert_response :redirect
2011 assert_response :redirect
1935 assert_redirected_to '/issues'
2012 assert_redirected_to '/issues'
1936 end
2013 end
1937
2014
1938 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
2015 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
1939 issue = Issue.find(2)
2016 issue = Issue.find(2)
1940 @request.session[:user_id] = 2
2017 @request.session[:user_id] = 2
1941
2018
1942 put :update,
2019 put :update,
1943 :id => issue.id,
2020 :id => issue.id,
1944 :issue => {
2021 :issue => {
1945 :fixed_version_id => 4
2022 :fixed_version_id => 4
1946 },
2023 },
1947 :back_url => 'http://google.com'
2024 :back_url => 'http://google.com'
1948
2025
1949 assert_response :redirect
2026 assert_response :redirect
1950 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
2027 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
1951 end
2028 end
1952
2029
1953 def test_get_bulk_edit
2030 def test_get_bulk_edit
1954 @request.session[:user_id] = 2
2031 @request.session[:user_id] = 2
1955 get :bulk_edit, :ids => [1, 2]
2032 get :bulk_edit, :ids => [1, 2]
1956 assert_response :success
2033 assert_response :success
1957 assert_template 'bulk_edit'
2034 assert_template 'bulk_edit'
1958
2035
1959 assert_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
2036 assert_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
1960
2037
1961 # Project specific custom field, date type
2038 # Project specific custom field, date type
1962 field = CustomField.find(9)
2039 field = CustomField.find(9)
1963 assert !field.is_for_all?
2040 assert !field.is_for_all?
1964 assert_equal 'date', field.field_format
2041 assert_equal 'date', field.field_format
1965 assert_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
2042 assert_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
1966
2043
1967 # System wide custom field
2044 # System wide custom field
1968 assert CustomField.find(1).is_for_all?
2045 assert CustomField.find(1).is_for_all?
1969 assert_tag :select, :attributes => {:name => 'issue[custom_field_values][1]'}
2046 assert_tag :select, :attributes => {:name => 'issue[custom_field_values][1]'}
1970
2047
1971 # Be sure we don't display inactive IssuePriorities
2048 # Be sure we don't display inactive IssuePriorities
1972 assert ! IssuePriority.find(15).active?
2049 assert ! IssuePriority.find(15).active?
1973 assert_no_tag :option, :attributes => {:value => '15'},
2050 assert_no_tag :option, :attributes => {:value => '15'},
1974 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
2051 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1975 end
2052 end
1976
2053
1977 def test_get_bulk_edit_on_different_projects
2054 def test_get_bulk_edit_on_different_projects
1978 @request.session[:user_id] = 2
2055 @request.session[:user_id] = 2
1979 get :bulk_edit, :ids => [1, 2, 6]
2056 get :bulk_edit, :ids => [1, 2, 6]
1980 assert_response :success
2057 assert_response :success
1981 assert_template 'bulk_edit'
2058 assert_template 'bulk_edit'
1982
2059
1983 # Can not set issues from different projects as children of an issue
2060 # Can not set issues from different projects as children of an issue
1984 assert_no_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
2061 assert_no_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
1985
2062
1986 # Project specific custom field, date type
2063 # Project specific custom field, date type
1987 field = CustomField.find(9)
2064 field = CustomField.find(9)
1988 assert !field.is_for_all?
2065 assert !field.is_for_all?
1989 assert !field.project_ids.include?(Issue.find(6).project_id)
2066 assert !field.project_ids.include?(Issue.find(6).project_id)
1990 assert_no_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
2067 assert_no_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
1991 end
2068 end
1992
2069
1993 def test_get_bulk_edit_with_user_custom_field
2070 def test_get_bulk_edit_with_user_custom_field
1994 field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true)
2071 field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true)
1995
2072
1996 @request.session[:user_id] = 2
2073 @request.session[:user_id] = 2
1997 get :bulk_edit, :ids => [1, 2]
2074 get :bulk_edit, :ids => [1, 2]
1998 assert_response :success
2075 assert_response :success
1999 assert_template 'bulk_edit'
2076 assert_template 'bulk_edit'
2000
2077
2001 assert_tag :select,
2078 assert_tag :select,
2002 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
2079 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
2003 :children => {
2080 :children => {
2004 :only => {:tag => 'option'},
2081 :only => {:tag => 'option'},
2005 :count => Project.find(1).users.count + 1
2082 :count => Project.find(1).users.count + 1
2006 }
2083 }
2007 end
2084 end
2008
2085
2009 def test_get_bulk_edit_with_version_custom_field
2086 def test_get_bulk_edit_with_version_custom_field
2010 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true)
2087 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true)
2011
2088
2012 @request.session[:user_id] = 2
2089 @request.session[:user_id] = 2
2013 get :bulk_edit, :ids => [1, 2]
2090 get :bulk_edit, :ids => [1, 2]
2014 assert_response :success
2091 assert_response :success
2015 assert_template 'bulk_edit'
2092 assert_template 'bulk_edit'
2016
2093
2017 assert_tag :select,
2094 assert_tag :select,
2018 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
2095 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
2019 :children => {
2096 :children => {
2020 :only => {:tag => 'option'},
2097 :only => {:tag => 'option'},
2021 :count => Project.find(1).shared_versions.count + 1
2098 :count => Project.find(1).shared_versions.count + 1
2022 }
2099 }
2023 end
2100 end
2024
2101
2025 def test_bulk_update
2102 def test_bulk_update
2026 @request.session[:user_id] = 2
2103 @request.session[:user_id] = 2
2027 # update issues priority
2104 # update issues priority
2028 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
2105 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
2029 :issue => {:priority_id => 7,
2106 :issue => {:priority_id => 7,
2030 :assigned_to_id => '',
2107 :assigned_to_id => '',
2031 :custom_field_values => {'2' => ''}}
2108 :custom_field_values => {'2' => ''}}
2032
2109
2033 assert_response 302
2110 assert_response 302
2034 # check that the issues were updated
2111 # check that the issues were updated
2035 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
2112 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
2036
2113
2037 issue = Issue.find(1)
2114 issue = Issue.find(1)
2038 journal = issue.journals.find(:first, :order => 'created_on DESC')
2115 journal = issue.journals.find(:first, :order => 'created_on DESC')
2039 assert_equal '125', issue.custom_value_for(2).value
2116 assert_equal '125', issue.custom_value_for(2).value
2040 assert_equal 'Bulk editing', journal.notes
2117 assert_equal 'Bulk editing', journal.notes
2041 assert_equal 1, journal.details.size
2118 assert_equal 1, journal.details.size
2042 end
2119 end
2043
2120
2044 def test_bulk_update_with_group_assignee
2121 def test_bulk_update_with_group_assignee
2045 group = Group.find(11)
2122 group = Group.find(11)
2046 project = Project.find(1)
2123 project = Project.find(1)
2047 project.members << Member.new(:principal => group, :roles => [Role.first])
2124 project.members << Member.new(:principal => group, :roles => [Role.first])
2048
2125
2049 @request.session[:user_id] = 2
2126 @request.session[:user_id] = 2
2050 # update issues assignee
2127 # update issues assignee
2051 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
2128 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
2052 :issue => {:priority_id => '',
2129 :issue => {:priority_id => '',
2053 :assigned_to_id => group.id,
2130 :assigned_to_id => group.id,
2054 :custom_field_values => {'2' => ''}}
2131 :custom_field_values => {'2' => ''}}
2055
2132
2056 assert_response 302
2133 assert_response 302
2057 assert_equal [group, group], Issue.find_all_by_id([1, 2]).collect {|i| i.assigned_to}
2134 assert_equal [group, group], Issue.find_all_by_id([1, 2]).collect {|i| i.assigned_to}
2058 end
2135 end
2059
2136
2060 def test_bulk_update_on_different_projects
2137 def test_bulk_update_on_different_projects
2061 @request.session[:user_id] = 2
2138 @request.session[:user_id] = 2
2062 # update issues priority
2139 # update issues priority
2063 post :bulk_update, :ids => [1, 2, 6], :notes => 'Bulk editing',
2140 post :bulk_update, :ids => [1, 2, 6], :notes => 'Bulk editing',
2064 :issue => {:priority_id => 7,
2141 :issue => {:priority_id => 7,
2065 :assigned_to_id => '',
2142 :assigned_to_id => '',
2066 :custom_field_values => {'2' => ''}}
2143 :custom_field_values => {'2' => ''}}
2067
2144
2068 assert_response 302
2145 assert_response 302
2069 # check that the issues were updated
2146 # check that the issues were updated
2070 assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
2147 assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
2071
2148
2072 issue = Issue.find(1)
2149 issue = Issue.find(1)
2073 journal = issue.journals.find(:first, :order => 'created_on DESC')
2150 journal = issue.journals.find(:first, :order => 'created_on DESC')
2074 assert_equal '125', issue.custom_value_for(2).value
2151 assert_equal '125', issue.custom_value_for(2).value
2075 assert_equal 'Bulk editing', journal.notes
2152 assert_equal 'Bulk editing', journal.notes
2076 assert_equal 1, journal.details.size
2153 assert_equal 1, journal.details.size
2077 end
2154 end
2078
2155
2079 def test_bulk_update_on_different_projects_without_rights
2156 def test_bulk_update_on_different_projects_without_rights
2080 @request.session[:user_id] = 3
2157 @request.session[:user_id] = 3
2081 user = User.find(3)
2158 user = User.find(3)
2082 action = { :controller => "issues", :action => "bulk_update" }
2159 action = { :controller => "issues", :action => "bulk_update" }
2083 assert user.allowed_to?(action, Issue.find(1).project)
2160 assert user.allowed_to?(action, Issue.find(1).project)
2084 assert ! user.allowed_to?(action, Issue.find(6).project)
2161 assert ! user.allowed_to?(action, Issue.find(6).project)
2085 post :bulk_update, :ids => [1, 6], :notes => 'Bulk should fail',
2162 post :bulk_update, :ids => [1, 6], :notes => 'Bulk should fail',
2086 :issue => {:priority_id => 7,
2163 :issue => {:priority_id => 7,
2087 :assigned_to_id => '',
2164 :assigned_to_id => '',
2088 :custom_field_values => {'2' => ''}}
2165 :custom_field_values => {'2' => ''}}
2089 assert_response 403
2166 assert_response 403
2090 assert_not_equal "Bulk should fail", Journal.last.notes
2167 assert_not_equal "Bulk should fail", Journal.last.notes
2091 end
2168 end
2092
2169
2093 def test_bullk_update_should_send_a_notification
2170 def test_bullk_update_should_send_a_notification
2094 @request.session[:user_id] = 2
2171 @request.session[:user_id] = 2
2095 ActionMailer::Base.deliveries.clear
2172 ActionMailer::Base.deliveries.clear
2096 post(:bulk_update,
2173 post(:bulk_update,
2097 {
2174 {
2098 :ids => [1, 2],
2175 :ids => [1, 2],
2099 :notes => 'Bulk editing',
2176 :notes => 'Bulk editing',
2100 :issue => {
2177 :issue => {
2101 :priority_id => 7,
2178 :priority_id => 7,
2102 :assigned_to_id => '',
2179 :assigned_to_id => '',
2103 :custom_field_values => {'2' => ''}
2180 :custom_field_values => {'2' => ''}
2104 }
2181 }
2105 })
2182 })
2106
2183
2107 assert_response 302
2184 assert_response 302
2108 assert_equal 2, ActionMailer::Base.deliveries.size
2185 assert_equal 2, ActionMailer::Base.deliveries.size
2109 end
2186 end
2110
2187
2111 def test_bulk_update_status
2188 def test_bulk_update_status
2112 @request.session[:user_id] = 2
2189 @request.session[:user_id] = 2
2113 # update issues priority
2190 # update issues priority
2114 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
2191 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
2115 :issue => {:priority_id => '',
2192 :issue => {:priority_id => '',
2116 :assigned_to_id => '',
2193 :assigned_to_id => '',
2117 :status_id => '5'}
2194 :status_id => '5'}
2118
2195
2119 assert_response 302
2196 assert_response 302
2120 issue = Issue.find(1)
2197 issue = Issue.find(1)
2121 assert issue.closed?
2198 assert issue.closed?
2122 end
2199 end
2123
2200
2124 def test_bulk_update_parent_id
2201 def test_bulk_update_parent_id
2125 @request.session[:user_id] = 2
2202 @request.session[:user_id] = 2
2126 post :bulk_update, :ids => [1, 3],
2203 post :bulk_update, :ids => [1, 3],
2127 :notes => 'Bulk editing parent',
2204 :notes => 'Bulk editing parent',
2128 :issue => {:priority_id => '', :assigned_to_id => '', :status_id => '', :parent_issue_id => '2'}
2205 :issue => {:priority_id => '', :assigned_to_id => '', :status_id => '', :parent_issue_id => '2'}
2129
2206
2130 assert_response 302
2207 assert_response 302
2131 parent = Issue.find(2)
2208 parent = Issue.find(2)
2132 assert_equal parent.id, Issue.find(1).parent_id
2209 assert_equal parent.id, Issue.find(1).parent_id
2133 assert_equal parent.id, Issue.find(3).parent_id
2210 assert_equal parent.id, Issue.find(3).parent_id
2134 assert_equal [1, 3], parent.children.collect(&:id).sort
2211 assert_equal [1, 3], parent.children.collect(&:id).sort
2135 end
2212 end
2136
2213
2137 def test_bulk_update_custom_field
2214 def test_bulk_update_custom_field
2138 @request.session[:user_id] = 2
2215 @request.session[:user_id] = 2
2139 # update issues priority
2216 # update issues priority
2140 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
2217 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
2141 :issue => {:priority_id => '',
2218 :issue => {:priority_id => '',
2142 :assigned_to_id => '',
2219 :assigned_to_id => '',
2143 :custom_field_values => {'2' => '777'}}
2220 :custom_field_values => {'2' => '777'}}
2144
2221
2145 assert_response 302
2222 assert_response 302
2146
2223
2147 issue = Issue.find(1)
2224 issue = Issue.find(1)
2148 journal = issue.journals.find(:first, :order => 'created_on DESC')
2225 journal = issue.journals.find(:first, :order => 'created_on DESC')
2149 assert_equal '777', issue.custom_value_for(2).value
2226 assert_equal '777', issue.custom_value_for(2).value
2150 assert_equal 1, journal.details.size
2227 assert_equal 1, journal.details.size
2151 assert_equal '125', journal.details.first.old_value
2228 assert_equal '125', journal.details.first.old_value
2152 assert_equal '777', journal.details.first.value
2229 assert_equal '777', journal.details.first.value
2153 end
2230 end
2154
2231
2155 def test_bulk_update_unassign
2232 def test_bulk_update_unassign
2156 assert_not_nil Issue.find(2).assigned_to
2233 assert_not_nil Issue.find(2).assigned_to
2157 @request.session[:user_id] = 2
2234 @request.session[:user_id] = 2
2158 # unassign issues
2235 # unassign issues
2159 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
2236 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
2160 assert_response 302
2237 assert_response 302
2161 # check that the issues were updated
2238 # check that the issues were updated
2162 assert_nil Issue.find(2).assigned_to
2239 assert_nil Issue.find(2).assigned_to
2163 end
2240 end
2164
2241
2165 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
2242 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
2166 @request.session[:user_id] = 2
2243 @request.session[:user_id] = 2
2167
2244
2168 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
2245 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
2169
2246
2170 assert_response :redirect
2247 assert_response :redirect
2171 issues = Issue.find([1,2])
2248 issues = Issue.find([1,2])
2172 issues.each do |issue|
2249 issues.each do |issue|
2173 assert_equal 4, issue.fixed_version_id
2250 assert_equal 4, issue.fixed_version_id
2174 assert_not_equal issue.project_id, issue.fixed_version.project_id
2251 assert_not_equal issue.project_id, issue.fixed_version.project_id
2175 end
2252 end
2176 end
2253 end
2177
2254
2178 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
2255 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
2179 @request.session[:user_id] = 2
2256 @request.session[:user_id] = 2
2180 post :bulk_update, :ids => [1,2], :back_url => '/issues'
2257 post :bulk_update, :ids => [1,2], :back_url => '/issues'
2181
2258
2182 assert_response :redirect
2259 assert_response :redirect
2183 assert_redirected_to '/issues'
2260 assert_redirected_to '/issues'
2184 end
2261 end
2185
2262
2186 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
2263 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
2187 @request.session[:user_id] = 2
2264 @request.session[:user_id] = 2
2188 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
2265 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
2189
2266
2190 assert_response :redirect
2267 assert_response :redirect
2191 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
2268 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
2192 end
2269 end
2193
2270
2194 def test_destroy_issue_with_no_time_entries
2271 def test_destroy_issue_with_no_time_entries
2195 assert_nil TimeEntry.find_by_issue_id(2)
2272 assert_nil TimeEntry.find_by_issue_id(2)
2196 @request.session[:user_id] = 2
2273 @request.session[:user_id] = 2
2197 delete :destroy, :id => 2
2274 delete :destroy, :id => 2
2198 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
2275 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
2199 assert_nil Issue.find_by_id(2)
2276 assert_nil Issue.find_by_id(2)
2200 end
2277 end
2201
2278
2202 def test_destroy_issues_with_time_entries
2279 def test_destroy_issues_with_time_entries
2203 @request.session[:user_id] = 2
2280 @request.session[:user_id] = 2
2204 delete :destroy, :ids => [1, 3]
2281 delete :destroy, :ids => [1, 3]
2205 assert_response :success
2282 assert_response :success
2206 assert_template 'destroy'
2283 assert_template 'destroy'
2207 assert_not_nil assigns(:hours)
2284 assert_not_nil assigns(:hours)
2208 assert Issue.find_by_id(1) && Issue.find_by_id(3)
2285 assert Issue.find_by_id(1) && Issue.find_by_id(3)
2209 end
2286 end
2210
2287
2211 def test_destroy_issues_and_destroy_time_entries
2288 def test_destroy_issues_and_destroy_time_entries
2212 @request.session[:user_id] = 2
2289 @request.session[:user_id] = 2
2213 delete :destroy, :ids => [1, 3], :todo => 'destroy'
2290 delete :destroy, :ids => [1, 3], :todo => 'destroy'
2214 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
2291 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
2215 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
2292 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
2216 assert_nil TimeEntry.find_by_id([1, 2])
2293 assert_nil TimeEntry.find_by_id([1, 2])
2217 end
2294 end
2218
2295
2219 def test_destroy_issues_and_assign_time_entries_to_project
2296 def test_destroy_issues_and_assign_time_entries_to_project
2220 @request.session[:user_id] = 2
2297 @request.session[:user_id] = 2
2221 delete :destroy, :ids => [1, 3], :todo => 'nullify'
2298 delete :destroy, :ids => [1, 3], :todo => 'nullify'
2222 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
2299 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
2223 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
2300 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
2224 assert_nil TimeEntry.find(1).issue_id
2301 assert_nil TimeEntry.find(1).issue_id
2225 assert_nil TimeEntry.find(2).issue_id
2302 assert_nil TimeEntry.find(2).issue_id
2226 end
2303 end
2227
2304
2228 def test_destroy_issues_and_reassign_time_entries_to_another_issue
2305 def test_destroy_issues_and_reassign_time_entries_to_another_issue
2229 @request.session[:user_id] = 2
2306 @request.session[:user_id] = 2
2230 delete :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
2307 delete :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
2231 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
2308 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
2232 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
2309 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
2233 assert_equal 2, TimeEntry.find(1).issue_id
2310 assert_equal 2, TimeEntry.find(1).issue_id
2234 assert_equal 2, TimeEntry.find(2).issue_id
2311 assert_equal 2, TimeEntry.find(2).issue_id
2235 end
2312 end
2236
2313
2237 def test_destroy_issues_from_different_projects
2314 def test_destroy_issues_from_different_projects
2238 @request.session[:user_id] = 2
2315 @request.session[:user_id] = 2
2239 delete :destroy, :ids => [1, 2, 6], :todo => 'destroy'
2316 delete :destroy, :ids => [1, 2, 6], :todo => 'destroy'
2240 assert_redirected_to :controller => 'issues', :action => 'index'
2317 assert_redirected_to :controller => 'issues', :action => 'index'
2241 assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6))
2318 assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6))
2242 end
2319 end
2243
2320
2244 def test_destroy_parent_and_child_issues
2321 def test_destroy_parent_and_child_issues
2245 parent = Issue.generate!(:project_id => 1, :tracker_id => 1)
2322 parent = Issue.generate!(:project_id => 1, :tracker_id => 1)
2246 child = Issue.generate!(:project_id => 1, :tracker_id => 1, :parent_issue_id => parent.id)
2323 child = Issue.generate!(:project_id => 1, :tracker_id => 1, :parent_issue_id => parent.id)
2247 assert child.is_descendant_of?(parent.reload)
2324 assert child.is_descendant_of?(parent.reload)
2248
2325
2249 @request.session[:user_id] = 2
2326 @request.session[:user_id] = 2
2250 assert_difference 'Issue.count', -2 do
2327 assert_difference 'Issue.count', -2 do
2251 delete :destroy, :ids => [parent.id, child.id], :todo => 'destroy'
2328 delete :destroy, :ids => [parent.id, child.id], :todo => 'destroy'
2252 end
2329 end
2253 assert_response 302
2330 assert_response 302
2254 end
2331 end
2255
2332
2256 def test_default_search_scope
2333 def test_default_search_scope
2257 get :index
2334 get :index
2258 assert_tag :div, :attributes => {:id => 'quick-search'},
2335 assert_tag :div, :attributes => {:id => 'quick-search'},
2259 :child => {:tag => 'form',
2336 :child => {:tag => 'form',
2260 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
2337 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
2261 end
2338 end
2262 end
2339 end
@@ -1,574 +1,590
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class ApiTest::IssuesTest < ActionController::IntegrationTest
20 class ApiTest::IssuesTest < ActionController::IntegrationTest
21 fixtures :projects,
21 fixtures :projects,
22 :users,
22 :users,
23 :roles,
23 :roles,
24 :members,
24 :members,
25 :member_roles,
25 :member_roles,
26 :issues,
26 :issues,
27 :issue_statuses,
27 :issue_statuses,
28 :versions,
28 :versions,
29 :trackers,
29 :trackers,
30 :projects_trackers,
30 :projects_trackers,
31 :issue_categories,
31 :issue_categories,
32 :enabled_modules,
32 :enabled_modules,
33 :enumerations,
33 :enumerations,
34 :attachments,
34 :attachments,
35 :workflows,
35 :workflows,
36 :custom_fields,
36 :custom_fields,
37 :custom_values,
37 :custom_values,
38 :custom_fields_projects,
38 :custom_fields_projects,
39 :custom_fields_trackers,
39 :custom_fields_trackers,
40 :time_entries,
40 :time_entries,
41 :journals,
41 :journals,
42 :journal_details,
42 :journal_details,
43 :queries,
43 :queries,
44 :attachments
44 :attachments
45
45
46 def setup
46 def setup
47 Setting.rest_api_enabled = '1'
47 Setting.rest_api_enabled = '1'
48 end
48 end
49
49
50 context "/issues" do
50 context "/issues" do
51 # Use a private project to make sure auth is really working and not just
51 # Use a private project to make sure auth is really working and not just
52 # only showing public issues.
52 # only showing public issues.
53 should_allow_api_authentication(:get, "/projects/private-child/issues.xml")
53 should_allow_api_authentication(:get, "/projects/private-child/issues.xml")
54
54
55 should "contain metadata" do
55 should "contain metadata" do
56 get '/issues.xml'
56 get '/issues.xml'
57
57
58 assert_tag :tag => 'issues',
58 assert_tag :tag => 'issues',
59 :attributes => {
59 :attributes => {
60 :type => 'array',
60 :type => 'array',
61 :total_count => assigns(:issue_count),
61 :total_count => assigns(:issue_count),
62 :limit => 25,
62 :limit => 25,
63 :offset => 0
63 :offset => 0
64 }
64 }
65 end
65 end
66
66
67 context "with offset and limit" do
67 context "with offset and limit" do
68 should "use the params" do
68 should "use the params" do
69 get '/issues.xml?offset=2&limit=3'
69 get '/issues.xml?offset=2&limit=3'
70
70
71 assert_equal 3, assigns(:limit)
71 assert_equal 3, assigns(:limit)
72 assert_equal 2, assigns(:offset)
72 assert_equal 2, assigns(:offset)
73 assert_tag :tag => 'issues', :children => {:count => 3, :only => {:tag => 'issue'}}
73 assert_tag :tag => 'issues', :children => {:count => 3, :only => {:tag => 'issue'}}
74 end
74 end
75 end
75 end
76
76
77 context "with nometa param" do
77 context "with nometa param" do
78 should "not contain metadata" do
78 should "not contain metadata" do
79 get '/issues.xml?nometa=1'
79 get '/issues.xml?nometa=1'
80
80
81 assert_tag :tag => 'issues',
81 assert_tag :tag => 'issues',
82 :attributes => {
82 :attributes => {
83 :type => 'array',
83 :type => 'array',
84 :total_count => nil,
84 :total_count => nil,
85 :limit => nil,
85 :limit => nil,
86 :offset => nil
86 :offset => nil
87 }
87 }
88 end
88 end
89 end
89 end
90
90
91 context "with nometa header" do
91 context "with nometa header" do
92 should "not contain metadata" do
92 should "not contain metadata" do
93 get '/issues.xml', {}, {'X-Redmine-Nometa' => '1'}
93 get '/issues.xml', {}, {'X-Redmine-Nometa' => '1'}
94
94
95 assert_tag :tag => 'issues',
95 assert_tag :tag => 'issues',
96 :attributes => {
96 :attributes => {
97 :type => 'array',
97 :type => 'array',
98 :total_count => nil,
98 :total_count => nil,
99 :limit => nil,
99 :limit => nil,
100 :offset => nil
100 :offset => nil
101 }
101 }
102 end
102 end
103 end
103 end
104
104
105 context "with relations" do
105 context "with relations" do
106 should "display relations" do
106 should "display relations" do
107 get '/issues.xml?include=relations'
107 get '/issues.xml?include=relations'
108
108
109 assert_response :success
109 assert_response :success
110 assert_equal 'application/xml', @response.content_type
110 assert_equal 'application/xml', @response.content_type
111 assert_tag 'relations',
111 assert_tag 'relations',
112 :parent => {:tag => 'issue', :child => {:tag => 'id', :content => '3'}},
112 :parent => {:tag => 'issue', :child => {:tag => 'id', :content => '3'}},
113 :children => {:count => 1},
113 :children => {:count => 1},
114 :child => {
114 :child => {
115 :tag => 'relation',
115 :tag => 'relation',
116 :attributes => {:id => '2', :issue_id => '2', :issue_to_id => '3', :relation_type => 'relates'}
116 :attributes => {:id => '2', :issue_id => '2', :issue_to_id => '3', :relation_type => 'relates'}
117 }
117 }
118 assert_tag 'relations',
118 assert_tag 'relations',
119 :parent => {:tag => 'issue', :child => {:tag => 'id', :content => '1'}},
119 :parent => {:tag => 'issue', :child => {:tag => 'id', :content => '1'}},
120 :children => {:count => 0}
120 :children => {:count => 0}
121 end
121 end
122 end
122 end
123
123
124 context "with invalid query params" do
124 context "with invalid query params" do
125 should "return errors" do
125 should "return errors" do
126 get '/issues.xml', {:f => ['start_date'], :op => {:start_date => '='}}
126 get '/issues.xml', {:f => ['start_date'], :op => {:start_date => '='}}
127
127
128 assert_response :unprocessable_entity
128 assert_response :unprocessable_entity
129 assert_equal 'application/xml', @response.content_type
129 assert_equal 'application/xml', @response.content_type
130 assert_tag 'errors', :child => {:tag => 'error', :content => "Start date can't be blank"}
130 assert_tag 'errors', :child => {:tag => 'error', :content => "Start date can't be blank"}
131 end
131 end
132 end
132 end
133
133
134 context "with custom field filter" do
134 context "with custom field filter" do
135 should "show only issues with the custom field value" do
135 should "show only issues with the custom field value" do
136 get '/issues.xml', { :set_filter => 1, :f => ['cf_1'], :op => {:cf_1 => '='}, :v => {:cf_1 => ['MySQL']}}
136 get '/issues.xml', { :set_filter => 1, :f => ['cf_1'], :op => {:cf_1 => '='}, :v => {:cf_1 => ['MySQL']}}
137
137
138 expected_ids = Issue.visible.all(
138 expected_ids = Issue.visible.all(
139 :include => :custom_values,
139 :include => :custom_values,
140 :conditions => {:custom_values => {:custom_field_id => 1, :value => 'MySQL'}}).map(&:id)
140 :conditions => {:custom_values => {:custom_field_id => 1, :value => 'MySQL'}}).map(&:id)
141
141
142 assert_select 'issues > issue > id', :count => expected_ids.count do |ids|
142 assert_select 'issues > issue > id', :count => expected_ids.count do |ids|
143 ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) }
143 ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) }
144 end
144 end
145 end
145 end
146 end
146 end
147
147
148 context "with custom field filter (shorthand method)" do
148 context "with custom field filter (shorthand method)" do
149 should "show only issues with the custom field value" do
149 should "show only issues with the custom field value" do
150 get '/issues.xml', { :cf_1 => 'MySQL' }
150 get '/issues.xml', { :cf_1 => 'MySQL' }
151
151
152 expected_ids = Issue.visible.all(
152 expected_ids = Issue.visible.all(
153 :include => :custom_values,
153 :include => :custom_values,
154 :conditions => {:custom_values => {:custom_field_id => 1, :value => 'MySQL'}}).map(&:id)
154 :conditions => {:custom_values => {:custom_field_id => 1, :value => 'MySQL'}}).map(&:id)
155
155
156 assert_select 'issues > issue > id', :count => expected_ids.count do |ids|
156 assert_select 'issues > issue > id', :count => expected_ids.count do |ids|
157 ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) }
157 ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) }
158 end
158 end
159 end
159 end
160 end
160 end
161 end
161 end
162
162
163 context "/index.json" do
163 context "/index.json" do
164 should_allow_api_authentication(:get, "/projects/private-child/issues.json")
164 should_allow_api_authentication(:get, "/projects/private-child/issues.json")
165 end
165 end
166
166
167 context "/index.xml with filter" do
167 context "/index.xml with filter" do
168 should "show only issues with the status_id" do
168 should "show only issues with the status_id" do
169 get '/issues.xml?status_id=5'
169 get '/issues.xml?status_id=5'
170
170
171 expected_ids = Issue.visible.all(:conditions => {:status_id => 5}).map(&:id)
171 expected_ids = Issue.visible.all(:conditions => {:status_id => 5}).map(&:id)
172
172
173 assert_select 'issues > issue > id', :count => expected_ids.count do |ids|
173 assert_select 'issues > issue > id', :count => expected_ids.count do |ids|
174 ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) }
174 ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) }
175 end
175 end
176 end
176 end
177 end
177 end
178
178
179 context "/index.json with filter" do
179 context "/index.json with filter" do
180 should "show only issues with the status_id" do
180 should "show only issues with the status_id" do
181 get '/issues.json?status_id=5'
181 get '/issues.json?status_id=5'
182
182
183 json = ActiveSupport::JSON.decode(response.body)
183 json = ActiveSupport::JSON.decode(response.body)
184 status_ids_used = json['issues'].collect {|j| j['status']['id'] }
184 status_ids_used = json['issues'].collect {|j| j['status']['id'] }
185 assert_equal 3, status_ids_used.length
185 assert_equal 3, status_ids_used.length
186 assert status_ids_used.all? {|id| id == 5 }
186 assert status_ids_used.all? {|id| id == 5 }
187 end
187 end
188
188
189 end
189 end
190
190
191 # Issue 6 is on a private project
191 # Issue 6 is on a private project
192 context "/issues/6.xml" do
192 context "/issues/6.xml" do
193 should_allow_api_authentication(:get, "/issues/6.xml")
193 should_allow_api_authentication(:get, "/issues/6.xml")
194 end
194 end
195
195
196 context "/issues/6.json" do
196 context "/issues/6.json" do
197 should_allow_api_authentication(:get, "/issues/6.json")
197 should_allow_api_authentication(:get, "/issues/6.json")
198 end
198 end
199
199
200 context "GET /issues/:id" do
200 context "GET /issues/:id" do
201 context "with journals" do
201 context "with journals" do
202 context ".xml" do
202 context ".xml" do
203 should "display journals" do
203 should "display journals" do
204 get '/issues/1.xml?include=journals'
204 get '/issues/1.xml?include=journals'
205
205
206 assert_tag :tag => 'issue',
206 assert_tag :tag => 'issue',
207 :child => {
207 :child => {
208 :tag => 'journals',
208 :tag => 'journals',
209 :attributes => { :type => 'array' },
209 :attributes => { :type => 'array' },
210 :child => {
210 :child => {
211 :tag => 'journal',
211 :tag => 'journal',
212 :attributes => { :id => '1'},
212 :attributes => { :id => '1'},
213 :child => {
213 :child => {
214 :tag => 'details',
214 :tag => 'details',
215 :attributes => { :type => 'array' },
215 :attributes => { :type => 'array' },
216 :child => {
216 :child => {
217 :tag => 'detail',
217 :tag => 'detail',
218 :attributes => { :name => 'status_id' },
218 :attributes => { :name => 'status_id' },
219 :child => {
219 :child => {
220 :tag => 'old_value',
220 :tag => 'old_value',
221 :content => '1',
221 :content => '1',
222 :sibling => {
222 :sibling => {
223 :tag => 'new_value',
223 :tag => 'new_value',
224 :content => '2'
224 :content => '2'
225 }
225 }
226 }
226 }
227 }
227 }
228 }
228 }
229 }
229 }
230 }
230 }
231 end
231 end
232 end
232 end
233 end
233 end
234
234
235 context "with custom fields" do
235 context "with custom fields" do
236 context ".xml" do
236 context ".xml" do
237 should "display custom fields" do
237 should "display custom fields" do
238 get '/issues/3.xml'
238 get '/issues/3.xml'
239
239
240 assert_tag :tag => 'issue',
240 assert_tag :tag => 'issue',
241 :child => {
241 :child => {
242 :tag => 'custom_fields',
242 :tag => 'custom_fields',
243 :attributes => { :type => 'array' },
243 :attributes => { :type => 'array' },
244 :child => {
244 :child => {
245 :tag => 'custom_field',
245 :tag => 'custom_field',
246 :attributes => { :id => '1'},
246 :attributes => { :id => '1'},
247 :child => {
247 :child => {
248 :tag => 'value',
248 :tag => 'value',
249 :content => 'MySQL'
249 :content => 'MySQL'
250 }
250 }
251 }
251 }
252 }
252 }
253
253
254 assert_nothing_raised do
254 assert_nothing_raised do
255 Hash.from_xml(response.body).to_xml
255 Hash.from_xml(response.body).to_xml
256 end
256 end
257 end
257 end
258 end
258 end
259 end
259 end
260
260
261 context "with attachments" do
261 context "with attachments" do
262 context ".xml" do
262 context ".xml" do
263 should "display attachments" do
263 should "display attachments" do
264 get '/issues/3.xml?include=attachments'
264 get '/issues/3.xml?include=attachments'
265
265
266 assert_tag :tag => 'issue',
266 assert_tag :tag => 'issue',
267 :child => {
267 :child => {
268 :tag => 'attachments',
268 :tag => 'attachments',
269 :children => {:count => 5},
269 :children => {:count => 5},
270 :child => {
270 :child => {
271 :tag => 'attachment',
271 :tag => 'attachment',
272 :child => {
272 :child => {
273 :tag => 'filename',
273 :tag => 'filename',
274 :content => 'source.rb',
274 :content => 'source.rb',
275 :sibling => {
275 :sibling => {
276 :tag => 'content_url',
276 :tag => 'content_url',
277 :content => 'http://www.example.com/attachments/download/4/source.rb'
277 :content => 'http://www.example.com/attachments/download/4/source.rb'
278 }
278 }
279 }
279 }
280 }
280 }
281 }
281 }
282 end
282 end
283 end
283 end
284 end
284 end
285
285
286 context "with subtasks" do
286 context "with subtasks" do
287 setup do
287 setup do
288 @c1 = Issue.generate!(:status_id => 1, :subject => "child c1", :tracker_id => 1, :project_id => 1, :parent_issue_id => 1)
288 @c1 = Issue.generate!(:status_id => 1, :subject => "child c1", :tracker_id => 1, :project_id => 1, :parent_issue_id => 1)
289 @c2 = Issue.generate!(:status_id => 1, :subject => "child c2", :tracker_id => 1, :project_id => 1, :parent_issue_id => 1)
289 @c2 = Issue.generate!(:status_id => 1, :subject => "child c2", :tracker_id => 1, :project_id => 1, :parent_issue_id => 1)
290 @c3 = Issue.generate!(:status_id => 1, :subject => "child c3", :tracker_id => 1, :project_id => 1, :parent_issue_id => @c1.id)
290 @c3 = Issue.generate!(:status_id => 1, :subject => "child c3", :tracker_id => 1, :project_id => 1, :parent_issue_id => @c1.id)
291 end
291 end
292
292
293 context ".xml" do
293 context ".xml" do
294 should "display children" do
294 should "display children" do
295 get '/issues/1.xml?include=children'
295 get '/issues/1.xml?include=children'
296
296
297 assert_tag :tag => 'issue',
297 assert_tag :tag => 'issue',
298 :child => {
298 :child => {
299 :tag => 'children',
299 :tag => 'children',
300 :children => {:count => 2},
300 :children => {:count => 2},
301 :child => {
301 :child => {
302 :tag => 'issue',
302 :tag => 'issue',
303 :attributes => {:id => @c1.id.to_s},
303 :attributes => {:id => @c1.id.to_s},
304 :child => {
304 :child => {
305 :tag => 'subject',
305 :tag => 'subject',
306 :content => 'child c1',
306 :content => 'child c1',
307 :sibling => {
307 :sibling => {
308 :tag => 'children',
308 :tag => 'children',
309 :children => {:count => 1},
309 :children => {:count => 1},
310 :child => {
310 :child => {
311 :tag => 'issue',
311 :tag => 'issue',
312 :attributes => {:id => @c3.id.to_s}
312 :attributes => {:id => @c3.id.to_s}
313 }
313 }
314 }
314 }
315 }
315 }
316 }
316 }
317 }
317 }
318 end
318 end
319
319
320 context ".json" do
320 context ".json" do
321 should "display children" do
321 should "display children" do
322 get '/issues/1.json?include=children'
322 get '/issues/1.json?include=children'
323
323
324 json = ActiveSupport::JSON.decode(response.body)
324 json = ActiveSupport::JSON.decode(response.body)
325 assert_equal([
325 assert_equal([
326 {
326 {
327 'id' => @c1.id, 'subject' => 'child c1', 'tracker' => {'id' => 1, 'name' => 'Bug'},
327 'id' => @c1.id, 'subject' => 'child c1', 'tracker' => {'id' => 1, 'name' => 'Bug'},
328 'children' => [{ 'id' => @c3.id, 'subject' => 'child c3', 'tracker' => {'id' => 1, 'name' => 'Bug'} }]
328 'children' => [{ 'id' => @c3.id, 'subject' => 'child c3', 'tracker' => {'id' => 1, 'name' => 'Bug'} }]
329 },
329 },
330 { 'id' => @c2.id, 'subject' => 'child c2', 'tracker' => {'id' => 1, 'name' => 'Bug'} }
330 { 'id' => @c2.id, 'subject' => 'child c2', 'tracker' => {'id' => 1, 'name' => 'Bug'} }
331 ],
331 ],
332 json['issue']['children'])
332 json['issue']['children'])
333 end
333 end
334 end
334 end
335 end
335 end
336 end
336 end
337 end
337 end
338
338
339 context "POST /issues.xml" do
339 context "POST /issues.xml" do
340 should_allow_api_authentication(:post,
340 should_allow_api_authentication(:post,
341 '/issues.xml',
341 '/issues.xml',
342 {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}},
342 {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}},
343 {:success_code => :created})
343 {:success_code => :created})
344
344
345 should "create an issue with the attributes" do
345 should "create an issue with the attributes" do
346 assert_difference('Issue.count') do
346 assert_difference('Issue.count') do
347 post '/issues.xml', {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, credentials('jsmith')
347 post '/issues.xml', {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, credentials('jsmith')
348 end
348 end
349
349
350 issue = Issue.first(:order => 'id DESC')
350 issue = Issue.first(:order => 'id DESC')
351 assert_equal 1, issue.project_id
351 assert_equal 1, issue.project_id
352 assert_equal 2, issue.tracker_id
352 assert_equal 2, issue.tracker_id
353 assert_equal 3, issue.status_id
353 assert_equal 3, issue.status_id
354 assert_equal 'API test', issue.subject
354 assert_equal 'API test', issue.subject
355
355
356 assert_response :created
356 assert_response :created
357 assert_equal 'application/xml', @response.content_type
357 assert_equal 'application/xml', @response.content_type
358 assert_tag 'issue', :child => {:tag => 'id', :content => issue.id.to_s}
358 assert_tag 'issue', :child => {:tag => 'id', :content => issue.id.to_s}
359 end
359 end
360 end
360 end
361
361
362 context "POST /issues.xml with failure" do
362 context "POST /issues.xml with failure" do
363 should "have an errors tag" do
363 should "have an errors tag" do
364 assert_no_difference('Issue.count') do
364 assert_no_difference('Issue.count') do
365 post '/issues.xml', {:issue => {:project_id => 1}}, credentials('jsmith')
365 post '/issues.xml', {:issue => {:project_id => 1}}, credentials('jsmith')
366 end
366 end
367
367
368 assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"}
368 assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"}
369 end
369 end
370 end
370 end
371
371
372 context "POST /issues.json" do
372 context "POST /issues.json" do
373 should_allow_api_authentication(:post,
373 should_allow_api_authentication(:post,
374 '/issues.json',
374 '/issues.json',
375 {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}},
375 {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}},
376 {:success_code => :created})
376 {:success_code => :created})
377
377
378 should "create an issue with the attributes" do
378 should "create an issue with the attributes" do
379 assert_difference('Issue.count') do
379 assert_difference('Issue.count') do
380 post '/issues.json', {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, credentials('jsmith')
380 post '/issues.json', {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, credentials('jsmith')
381 end
381 end
382
382
383 issue = Issue.first(:order => 'id DESC')
383 issue = Issue.first(:order => 'id DESC')
384 assert_equal 1, issue.project_id
384 assert_equal 1, issue.project_id
385 assert_equal 2, issue.tracker_id
385 assert_equal 2, issue.tracker_id
386 assert_equal 3, issue.status_id
386 assert_equal 3, issue.status_id
387 assert_equal 'API test', issue.subject
387 assert_equal 'API test', issue.subject
388 end
388 end
389
389
390 end
390 end
391
391
392 context "POST /issues.json with failure" do
392 context "POST /issues.json with failure" do
393 should "have an errors element" do
393 should "have an errors element" do
394 assert_no_difference('Issue.count') do
394 assert_no_difference('Issue.count') do
395 post '/issues.json', {:issue => {:project_id => 1}}, credentials('jsmith')
395 post '/issues.json', {:issue => {:project_id => 1}}, credentials('jsmith')
396 end
396 end
397
397
398 json = ActiveSupport::JSON.decode(response.body)
398 json = ActiveSupport::JSON.decode(response.body)
399 assert json['errors'].include?(['subject', "can't be blank"])
399 assert json['errors'].include?(['subject', "can't be blank"])
400 end
400 end
401 end
401 end
402
402
403 # Issue 6 is on a private project
403 # Issue 6 is on a private project
404 context "PUT /issues/6.xml" do
404 context "PUT /issues/6.xml" do
405 setup do
405 setup do
406 @parameters = {:issue => {:subject => 'API update', :notes => 'A new note'}}
406 @parameters = {:issue => {:subject => 'API update', :notes => 'A new note'}}
407 end
407 end
408
408
409 should_allow_api_authentication(:put,
409 should_allow_api_authentication(:put,
410 '/issues/6.xml',
410 '/issues/6.xml',
411 {:issue => {:subject => 'API update', :notes => 'A new note'}},
411 {:issue => {:subject => 'API update', :notes => 'A new note'}},
412 {:success_code => :ok})
412 {:success_code => :ok})
413
413
414 should "not create a new issue" do
414 should "not create a new issue" do
415 assert_no_difference('Issue.count') do
415 assert_no_difference('Issue.count') do
416 put '/issues/6.xml', @parameters, credentials('jsmith')
416 put '/issues/6.xml', @parameters, credentials('jsmith')
417 end
417 end
418 end
418 end
419
419
420 should "create a new journal" do
420 should "create a new journal" do
421 assert_difference('Journal.count') do
421 assert_difference('Journal.count') do
422 put '/issues/6.xml', @parameters, credentials('jsmith')
422 put '/issues/6.xml', @parameters, credentials('jsmith')
423 end
423 end
424 end
424 end
425
425
426 should "add the note to the journal" do
426 should "add the note to the journal" do
427 put '/issues/6.xml', @parameters, credentials('jsmith')
427 put '/issues/6.xml', @parameters, credentials('jsmith')
428
428
429 journal = Journal.last
429 journal = Journal.last
430 assert_equal "A new note", journal.notes
430 assert_equal "A new note", journal.notes
431 end
431 end
432
432
433 should "update the issue" do
433 should "update the issue" do
434 put '/issues/6.xml', @parameters, credentials('jsmith')
434 put '/issues/6.xml', @parameters, credentials('jsmith')
435
435
436 issue = Issue.find(6)
436 issue = Issue.find(6)
437 assert_equal "API update", issue.subject
437 assert_equal "API update", issue.subject
438 end
438 end
439
439
440 end
440 end
441
441
442 context "PUT /issues/3.xml with custom fields" do
442 context "PUT /issues/3.xml with custom fields" do
443 setup do
443 setup do
444 @parameters = {:issue => {:custom_fields => [{'id' => '1', 'value' => 'PostgreSQL' }, {'id' => '2', 'value' => '150'}]}}
444 @parameters = {:issue => {:custom_fields => [{'id' => '1', 'value' => 'PostgreSQL' }, {'id' => '2', 'value' => '150'}]}}
445 end
445 end
446
446
447 should "update custom fields" do
447 should "update custom fields" do
448 assert_no_difference('Issue.count') do
448 assert_no_difference('Issue.count') do
449 put '/issues/3.xml', @parameters, credentials('jsmith')
449 put '/issues/3.xml', @parameters, credentials('jsmith')
450 end
450 end
451
451
452 issue = Issue.find(3)
452 issue = Issue.find(3)
453 assert_equal '150', issue.custom_value_for(2).value
453 assert_equal '150', issue.custom_value_for(2).value
454 assert_equal 'PostgreSQL', issue.custom_value_for(1).value
454 assert_equal 'PostgreSQL', issue.custom_value_for(1).value
455 end
455 end
456 end
456 end
457
457
458 context "PUT /issues/3.xml with project change" do
459 setup do
460 @parameters = {:issue => {:project_id => 2, :subject => 'Project changed'}}
461 end
462
463 should "update project" do
464 assert_no_difference('Issue.count') do
465 put '/issues/3.xml', @parameters, credentials('jsmith')
466 end
467
468 issue = Issue.find(3)
469 assert_equal 2, issue.project_id
470 assert_equal 'Project changed', issue.subject
471 end
472 end
473
458 context "PUT /issues/6.xml with failed update" do
474 context "PUT /issues/6.xml with failed update" do
459 setup do
475 setup do
460 @parameters = {:issue => {:subject => ''}}
476 @parameters = {:issue => {:subject => ''}}
461 end
477 end
462
478
463 should "not create a new issue" do
479 should "not create a new issue" do
464 assert_no_difference('Issue.count') do
480 assert_no_difference('Issue.count') do
465 put '/issues/6.xml', @parameters, credentials('jsmith')
481 put '/issues/6.xml', @parameters, credentials('jsmith')
466 end
482 end
467 end
483 end
468
484
469 should "not create a new journal" do
485 should "not create a new journal" do
470 assert_no_difference('Journal.count') do
486 assert_no_difference('Journal.count') do
471 put '/issues/6.xml', @parameters, credentials('jsmith')
487 put '/issues/6.xml', @parameters, credentials('jsmith')
472 end
488 end
473 end
489 end
474
490
475 should "have an errors tag" do
491 should "have an errors tag" do
476 put '/issues/6.xml', @parameters, credentials('jsmith')
492 put '/issues/6.xml', @parameters, credentials('jsmith')
477
493
478 assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"}
494 assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"}
479 end
495 end
480 end
496 end
481
497
482 context "PUT /issues/6.json" do
498 context "PUT /issues/6.json" do
483 setup do
499 setup do
484 @parameters = {:issue => {:subject => 'API update', :notes => 'A new note'}}
500 @parameters = {:issue => {:subject => 'API update', :notes => 'A new note'}}
485 end
501 end
486
502
487 should_allow_api_authentication(:put,
503 should_allow_api_authentication(:put,
488 '/issues/6.json',
504 '/issues/6.json',
489 {:issue => {:subject => 'API update', :notes => 'A new note'}},
505 {:issue => {:subject => 'API update', :notes => 'A new note'}},
490 {:success_code => :ok})
506 {:success_code => :ok})
491
507
492 should "not create a new issue" do
508 should "not create a new issue" do
493 assert_no_difference('Issue.count') do
509 assert_no_difference('Issue.count') do
494 put '/issues/6.json', @parameters, credentials('jsmith')
510 put '/issues/6.json', @parameters, credentials('jsmith')
495 end
511 end
496 end
512 end
497
513
498 should "create a new journal" do
514 should "create a new journal" do
499 assert_difference('Journal.count') do
515 assert_difference('Journal.count') do
500 put '/issues/6.json', @parameters, credentials('jsmith')
516 put '/issues/6.json', @parameters, credentials('jsmith')
501 end
517 end
502 end
518 end
503
519
504 should "add the note to the journal" do
520 should "add the note to the journal" do
505 put '/issues/6.json', @parameters, credentials('jsmith')
521 put '/issues/6.json', @parameters, credentials('jsmith')
506
522
507 journal = Journal.last
523 journal = Journal.last
508 assert_equal "A new note", journal.notes
524 assert_equal "A new note", journal.notes
509 end
525 end
510
526
511 should "update the issue" do
527 should "update the issue" do
512 put '/issues/6.json', @parameters, credentials('jsmith')
528 put '/issues/6.json', @parameters, credentials('jsmith')
513
529
514 issue = Issue.find(6)
530 issue = Issue.find(6)
515 assert_equal "API update", issue.subject
531 assert_equal "API update", issue.subject
516 end
532 end
517
533
518 end
534 end
519
535
520 context "PUT /issues/6.json with failed update" do
536 context "PUT /issues/6.json with failed update" do
521 setup do
537 setup do
522 @parameters = {:issue => {:subject => ''}}
538 @parameters = {:issue => {:subject => ''}}
523 end
539 end
524
540
525 should "not create a new issue" do
541 should "not create a new issue" do
526 assert_no_difference('Issue.count') do
542 assert_no_difference('Issue.count') do
527 put '/issues/6.json', @parameters, credentials('jsmith')
543 put '/issues/6.json', @parameters, credentials('jsmith')
528 end
544 end
529 end
545 end
530
546
531 should "not create a new journal" do
547 should "not create a new journal" do
532 assert_no_difference('Journal.count') do
548 assert_no_difference('Journal.count') do
533 put '/issues/6.json', @parameters, credentials('jsmith')
549 put '/issues/6.json', @parameters, credentials('jsmith')
534 end
550 end
535 end
551 end
536
552
537 should "have an errors attribute" do
553 should "have an errors attribute" do
538 put '/issues/6.json', @parameters, credentials('jsmith')
554 put '/issues/6.json', @parameters, credentials('jsmith')
539
555
540 json = ActiveSupport::JSON.decode(response.body)
556 json = ActiveSupport::JSON.decode(response.body)
541 assert json['errors'].include?(['subject', "can't be blank"])
557 assert json['errors'].include?(['subject', "can't be blank"])
542 end
558 end
543 end
559 end
544
560
545 context "DELETE /issues/1.xml" do
561 context "DELETE /issues/1.xml" do
546 should_allow_api_authentication(:delete,
562 should_allow_api_authentication(:delete,
547 '/issues/6.xml',
563 '/issues/6.xml',
548 {},
564 {},
549 {:success_code => :ok})
565 {:success_code => :ok})
550
566
551 should "delete the issue" do
567 should "delete the issue" do
552 assert_difference('Issue.count',-1) do
568 assert_difference('Issue.count',-1) do
553 delete '/issues/6.xml', {}, credentials('jsmith')
569 delete '/issues/6.xml', {}, credentials('jsmith')
554 end
570 end
555
571
556 assert_nil Issue.find_by_id(6)
572 assert_nil Issue.find_by_id(6)
557 end
573 end
558 end
574 end
559
575
560 context "DELETE /issues/1.json" do
576 context "DELETE /issues/1.json" do
561 should_allow_api_authentication(:delete,
577 should_allow_api_authentication(:delete,
562 '/issues/6.json',
578 '/issues/6.json',
563 {},
579 {},
564 {:success_code => :ok})
580 {:success_code => :ok})
565
581
566 should "delete the issue" do
582 should "delete the issue" do
567 assert_difference('Issue.count',-1) do
583 assert_difference('Issue.count',-1) do
568 delete '/issues/6.json', {}, credentials('jsmith')
584 delete '/issues/6.json', {}, credentials('jsmith')
569 end
585 end
570
586
571 assert_nil Issue.find_by_id(6)
587 assert_nil Issue.find_by_id(6)
572 end
588 end
573 end
589 end
574 end
590 end
General Comments 0
You need to be logged in to leave comments. Login now