##// END OF EJS Templates
Renamed #_issues_path to #_project_issues_path....
Jean-Philippe Lang -
r10846:3ef7f5855edc
parent child
Show More
@@ -1,435 +1,435
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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, :destroy]
23 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy]
24 before_filter :find_project, :only => [:new, :create]
24 before_filter :find_project, :only => [:new, :create]
25 before_filter :authorize, :except => [:index]
25 before_filter :authorize, :except => [:index]
26 before_filter :find_optional_project, :only => [:index]
26 before_filter :find_optional_project, :only => [:index]
27 before_filter :check_for_default_issue_status, :only => [:new, :create]
27 before_filter :check_for_default_issue_status, :only => [:new, :create]
28 before_filter :build_new_issue_from_params, :only => [:new, :create]
28 before_filter :build_new_issue_from_params, :only => [:new, :create]
29 accept_rss_auth :index, :show
29 accept_rss_auth :index, :show
30 accept_api_auth :index, :show, :create, :update, :destroy
30 accept_api_auth :index, :show, :create, :update, :destroy
31
31
32 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
32 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
33
33
34 helper :journals
34 helper :journals
35 helper :projects
35 helper :projects
36 include ProjectsHelper
36 include ProjectsHelper
37 helper :custom_fields
37 helper :custom_fields
38 include CustomFieldsHelper
38 include CustomFieldsHelper
39 helper :issue_relations
39 helper :issue_relations
40 include IssueRelationsHelper
40 include IssueRelationsHelper
41 helper :watchers
41 helper :watchers
42 include WatchersHelper
42 include WatchersHelper
43 helper :attachments
43 helper :attachments
44 include AttachmentsHelper
44 include AttachmentsHelper
45 helper :queries
45 helper :queries
46 include QueriesHelper
46 include QueriesHelper
47 helper :repositories
47 helper :repositories
48 include RepositoriesHelper
48 include RepositoriesHelper
49 helper :sort
49 helper :sort
50 include SortHelper
50 include SortHelper
51 include IssuesHelper
51 include IssuesHelper
52 helper :timelog
52 helper :timelog
53 include Redmine::Export::PDF
53 include Redmine::Export::PDF
54
54
55 def index
55 def index
56 retrieve_query
56 retrieve_query
57 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
57 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
58 sort_update(@query.sortable_columns)
58 sort_update(@query.sortable_columns)
59 @query.sort_criteria = sort_criteria.to_a
59 @query.sort_criteria = sort_criteria.to_a
60
60
61 if @query.valid?
61 if @query.valid?
62 case params[:format]
62 case params[:format]
63 when 'csv', 'pdf'
63 when 'csv', 'pdf'
64 @limit = Setting.issues_export_limit.to_i
64 @limit = Setting.issues_export_limit.to_i
65 when 'atom'
65 when 'atom'
66 @limit = Setting.feeds_limit.to_i
66 @limit = Setting.feeds_limit.to_i
67 when 'xml', 'json'
67 when 'xml', 'json'
68 @offset, @limit = api_offset_and_limit
68 @offset, @limit = api_offset_and_limit
69 else
69 else
70 @limit = per_page_option
70 @limit = per_page_option
71 end
71 end
72
72
73 @issue_count = @query.issue_count
73 @issue_count = @query.issue_count
74 @issue_pages = Paginator.new self, @issue_count, @limit, params['page']
74 @issue_pages = Paginator.new self, @issue_count, @limit, params['page']
75 @offset ||= @issue_pages.current.offset
75 @offset ||= @issue_pages.current.offset
76 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
76 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
77 :order => sort_clause,
77 :order => sort_clause,
78 :offset => @offset,
78 :offset => @offset,
79 :limit => @limit)
79 :limit => @limit)
80 @issue_count_by_group = @query.issue_count_by_group
80 @issue_count_by_group = @query.issue_count_by_group
81
81
82 respond_to do |format|
82 respond_to do |format|
83 format.html { render :template => 'issues/index', :layout => !request.xhr? }
83 format.html { render :template => 'issues/index', :layout => !request.xhr? }
84 format.api {
84 format.api {
85 Issue.load_visible_relations(@issues) if include_in_api_response?('relations')
85 Issue.load_visible_relations(@issues) if include_in_api_response?('relations')
86 }
86 }
87 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
87 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
88 format.csv { send_data(issues_to_csv(@issues, @project, @query, params), :type => 'text/csv; header=present', :filename => 'export.csv') }
88 format.csv { send_data(issues_to_csv(@issues, @project, @query, params), :type => 'text/csv; header=present', :filename => 'export.csv') }
89 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
89 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
90 end
90 end
91 else
91 else
92 respond_to do |format|
92 respond_to do |format|
93 format.html { render(:template => 'issues/index', :layout => !request.xhr?) }
93 format.html { render(:template => 'issues/index', :layout => !request.xhr?) }
94 format.any(:atom, :csv, :pdf) { render(:nothing => true) }
94 format.any(:atom, :csv, :pdf) { render(:nothing => true) }
95 format.api { render_validation_errors(@query) }
95 format.api { render_validation_errors(@query) }
96 end
96 end
97 end
97 end
98 rescue ActiveRecord::RecordNotFound
98 rescue ActiveRecord::RecordNotFound
99 render_404
99 render_404
100 end
100 end
101
101
102 def show
102 def show
103 @journals = @issue.journals.includes(:user, :details).reorder("#{Journal.table_name}.id ASC").all
103 @journals = @issue.journals.includes(:user, :details).reorder("#{Journal.table_name}.id ASC").all
104 @journals.each_with_index {|j,i| j.indice = i+1}
104 @journals.each_with_index {|j,i| j.indice = i+1}
105 @journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
105 @journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
106 @journals.reverse! if User.current.wants_comments_in_reverse_order?
106 @journals.reverse! if User.current.wants_comments_in_reverse_order?
107
107
108 @changesets = @issue.changesets.visible.all
108 @changesets = @issue.changesets.visible.all
109 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
109 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
110
110
111 @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
111 @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
112 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
112 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
113 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
113 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
114 @priorities = IssuePriority.active
114 @priorities = IssuePriority.active
115 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
115 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
116 respond_to do |format|
116 respond_to do |format|
117 format.html {
117 format.html {
118 retrieve_previous_and_next_issue_ids
118 retrieve_previous_and_next_issue_ids
119 render :template => 'issues/show'
119 render :template => 'issues/show'
120 }
120 }
121 format.api
121 format.api
122 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
122 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
123 format.pdf {
123 format.pdf {
124 pdf = issue_to_pdf(@issue, :journals => @journals)
124 pdf = issue_to_pdf(@issue, :journals => @journals)
125 send_data(pdf, :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf")
125 send_data(pdf, :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf")
126 }
126 }
127 end
127 end
128 end
128 end
129
129
130 # Add a new issue
130 # Add a new issue
131 # The new issue will be created from an existing one if copy_from parameter is given
131 # The new issue will be created from an existing one if copy_from parameter is given
132 def new
132 def new
133 respond_to do |format|
133 respond_to do |format|
134 format.html { render :action => 'new', :layout => !request.xhr? }
134 format.html { render :action => 'new', :layout => !request.xhr? }
135 format.js { render :partial => 'update_form' }
135 format.js { render :partial => 'update_form' }
136 end
136 end
137 end
137 end
138
138
139 def create
139 def create
140 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
140 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
141 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
141 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
142 if @issue.save
142 if @issue.save
143 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
143 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
144 respond_to do |format|
144 respond_to do |format|
145 format.html {
145 format.html {
146 render_attachment_warning_if_needed(@issue)
146 render_attachment_warning_if_needed(@issue)
147 flash[:notice] = l(:notice_issue_successful_create, :id => view_context.link_to("##{@issue.id}", issue_path(@issue), :title => @issue.subject))
147 flash[:notice] = l(:notice_issue_successful_create, :id => view_context.link_to("##{@issue.id}", issue_path(@issue), :title => @issue.subject))
148 if params[:continue]
148 if params[:continue]
149 attrs = {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?}
149 attrs = {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?}
150 redirect_to new_project_issue_path(@issue.project, :issue => attrs)
150 redirect_to new_project_issue_path(@issue.project, :issue => attrs)
151 else
151 else
152 redirect_to issue_path(@issue)
152 redirect_to issue_path(@issue)
153 end
153 end
154 }
154 }
155 format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
155 format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
156 end
156 end
157 return
157 return
158 else
158 else
159 respond_to do |format|
159 respond_to do |format|
160 format.html { render :action => 'new' }
160 format.html { render :action => 'new' }
161 format.api { render_validation_errors(@issue) }
161 format.api { render_validation_errors(@issue) }
162 end
162 end
163 end
163 end
164 end
164 end
165
165
166 def edit
166 def edit
167 return unless update_issue_from_params
167 return unless update_issue_from_params
168
168
169 respond_to do |format|
169 respond_to do |format|
170 format.html { }
170 format.html { }
171 format.xml { }
171 format.xml { }
172 end
172 end
173 end
173 end
174
174
175 def update
175 def update
176 return unless update_issue_from_params
176 return unless update_issue_from_params
177 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
177 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
178 saved = false
178 saved = false
179 begin
179 begin
180 saved = @issue.save_issue_with_child_records(params, @time_entry)
180 saved = @issue.save_issue_with_child_records(params, @time_entry)
181 rescue ActiveRecord::StaleObjectError
181 rescue ActiveRecord::StaleObjectError
182 @conflict = true
182 @conflict = true
183 if params[:last_journal_id]
183 if params[:last_journal_id]
184 @conflict_journals = @issue.journals_after(params[:last_journal_id]).all
184 @conflict_journals = @issue.journals_after(params[:last_journal_id]).all
185 @conflict_journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
185 @conflict_journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
186 end
186 end
187 end
187 end
188
188
189 if saved
189 if saved
190 render_attachment_warning_if_needed(@issue)
190 render_attachment_warning_if_needed(@issue)
191 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?
192
192
193 respond_to do |format|
193 respond_to do |format|
194 format.html { redirect_back_or_default issue_path(@issue) }
194 format.html { redirect_back_or_default issue_path(@issue) }
195 format.api { render_api_ok }
195 format.api { render_api_ok }
196 end
196 end
197 else
197 else
198 respond_to do |format|
198 respond_to do |format|
199 format.html { render :action => 'edit' }
199 format.html { render :action => 'edit' }
200 format.api { render_validation_errors(@issue) }
200 format.api { render_validation_errors(@issue) }
201 end
201 end
202 end
202 end
203 end
203 end
204
204
205 # Bulk edit/copy a set of issues
205 # Bulk edit/copy a set of issues
206 def bulk_edit
206 def bulk_edit
207 @issues.sort!
207 @issues.sort!
208 @copy = params[:copy].present?
208 @copy = params[:copy].present?
209 @notes = params[:notes]
209 @notes = params[:notes]
210
210
211 if User.current.allowed_to?(:move_issues, @projects)
211 if User.current.allowed_to?(:move_issues, @projects)
212 @allowed_projects = Issue.allowed_target_projects_on_move
212 @allowed_projects = Issue.allowed_target_projects_on_move
213 if params[:issue]
213 if params[:issue]
214 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:issue][:project_id].to_s}
214 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:issue][:project_id].to_s}
215 if @target_project
215 if @target_project
216 target_projects = [@target_project]
216 target_projects = [@target_project]
217 end
217 end
218 end
218 end
219 end
219 end
220 target_projects ||= @projects
220 target_projects ||= @projects
221
221
222 if @copy
222 if @copy
223 @available_statuses = [IssueStatus.default]
223 @available_statuses = [IssueStatus.default]
224 else
224 else
225 @available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
225 @available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
226 end
226 end
227 @custom_fields = target_projects.map{|p|p.all_issue_custom_fields}.reduce(:&)
227 @custom_fields = target_projects.map{|p|p.all_issue_custom_fields}.reduce(:&)
228 @assignables = target_projects.map(&:assignable_users).reduce(:&)
228 @assignables = target_projects.map(&:assignable_users).reduce(:&)
229 @trackers = target_projects.map(&:trackers).reduce(:&)
229 @trackers = target_projects.map(&:trackers).reduce(:&)
230 @versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
230 @versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
231 @categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
231 @categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
232 if @copy
232 if @copy
233 @attachments_present = @issues.detect {|i| i.attachments.any?}.present?
233 @attachments_present = @issues.detect {|i| i.attachments.any?}.present?
234 @subtasks_present = @issues.detect {|i| !i.leaf?}.present?
234 @subtasks_present = @issues.detect {|i| !i.leaf?}.present?
235 end
235 end
236
236
237 @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
237 @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
238 render :layout => false if request.xhr?
238 render :layout => false if request.xhr?
239 end
239 end
240
240
241 def bulk_update
241 def bulk_update
242 @issues.sort!
242 @issues.sort!
243 @copy = params[:copy].present?
243 @copy = params[:copy].present?
244 attributes = parse_params_for_bulk_issue_attributes(params)
244 attributes = parse_params_for_bulk_issue_attributes(params)
245
245
246 unsaved_issue_ids = []
246 unsaved_issue_ids = []
247 moved_issues = []
247 moved_issues = []
248
248
249 if @copy && params[:copy_subtasks].present?
249 if @copy && params[:copy_subtasks].present?
250 # Descendant issues will be copied with the parent task
250 # Descendant issues will be copied with the parent task
251 # Don't copy them twice
251 # Don't copy them twice
252 @issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}}
252 @issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}}
253 end
253 end
254
254
255 @issues.each do |issue|
255 @issues.each do |issue|
256 issue.reload
256 issue.reload
257 if @copy
257 if @copy
258 issue = issue.copy({},
258 issue = issue.copy({},
259 :attachments => params[:copy_attachments].present?,
259 :attachments => params[:copy_attachments].present?,
260 :subtasks => params[:copy_subtasks].present?
260 :subtasks => params[:copy_subtasks].present?
261 )
261 )
262 end
262 end
263 journal = issue.init_journal(User.current, params[:notes])
263 journal = issue.init_journal(User.current, params[:notes])
264 issue.safe_attributes = attributes
264 issue.safe_attributes = attributes
265 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
265 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
266 if issue.save
266 if issue.save
267 moved_issues << issue
267 moved_issues << issue
268 else
268 else
269 # Keep unsaved issue ids to display them in flash error
269 # Keep unsaved issue ids to display them in flash error
270 unsaved_issue_ids << issue.id
270 unsaved_issue_ids << issue.id
271 end
271 end
272 end
272 end
273 set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
273 set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
274
274
275 if params[:follow]
275 if params[:follow]
276 if @issues.size == 1 && moved_issues.size == 1
276 if @issues.size == 1 && moved_issues.size == 1
277 redirect_to issue_path(moved_issues.first)
277 redirect_to issue_path(moved_issues.first)
278 elsif moved_issues.map(&:project).uniq.size == 1
278 elsif moved_issues.map(&:project).uniq.size == 1
279 redirect_to project_issues_path(moved_issues.map(&:project).first)
279 redirect_to project_issues_path(moved_issues.map(&:project).first)
280 end
280 end
281 else
281 else
282 redirect_back_or_default _issues_path(@project)
282 redirect_back_or_default _project_issues_path(@project)
283 end
283 end
284 end
284 end
285
285
286 def destroy
286 def destroy
287 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
287 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
288 if @hours > 0
288 if @hours > 0
289 case params[:todo]
289 case params[:todo]
290 when 'destroy'
290 when 'destroy'
291 # nothing to do
291 # nothing to do
292 when 'nullify'
292 when 'nullify'
293 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
293 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
294 when 'reassign'
294 when 'reassign'
295 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
295 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
296 if reassign_to.nil?
296 if reassign_to.nil?
297 flash.now[:error] = l(:error_issue_not_found_in_project)
297 flash.now[:error] = l(:error_issue_not_found_in_project)
298 return
298 return
299 else
299 else
300 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
300 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
301 end
301 end
302 else
302 else
303 # display the destroy form if it's a user request
303 # display the destroy form if it's a user request
304 return unless api_request?
304 return unless api_request?
305 end
305 end
306 end
306 end
307 @issues.each do |issue|
307 @issues.each do |issue|
308 begin
308 begin
309 issue.reload.destroy
309 issue.reload.destroy
310 rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists
310 rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists
311 # nothing to do, issue was already deleted (eg. by a parent)
311 # nothing to do, issue was already deleted (eg. by a parent)
312 end
312 end
313 end
313 end
314 respond_to do |format|
314 respond_to do |format|
315 format.html { redirect_back_or_default _issues_path(@project) }
315 format.html { redirect_back_or_default _project_issues_path(@project) }
316 format.api { render_api_ok }
316 format.api { render_api_ok }
317 end
317 end
318 end
318 end
319
319
320 private
320 private
321
321
322 def find_project
322 def find_project
323 project_id = params[:project_id] || (params[:issue] && params[:issue][:project_id])
323 project_id = params[:project_id] || (params[:issue] && params[:issue][:project_id])
324 @project = Project.find(project_id)
324 @project = Project.find(project_id)
325 rescue ActiveRecord::RecordNotFound
325 rescue ActiveRecord::RecordNotFound
326 render_404
326 render_404
327 end
327 end
328
328
329 def retrieve_previous_and_next_issue_ids
329 def retrieve_previous_and_next_issue_ids
330 retrieve_query_from_session
330 retrieve_query_from_session
331 if @query
331 if @query
332 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
332 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
333 sort_update(@query.sortable_columns, 'issues_index_sort')
333 sort_update(@query.sortable_columns, 'issues_index_sort')
334 limit = 500
334 limit = 500
335 issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1), :include => [:assigned_to, :tracker, :priority, :category, :fixed_version])
335 issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1), :include => [:assigned_to, :tracker, :priority, :category, :fixed_version])
336 if (idx = issue_ids.index(@issue.id)) && idx < limit
336 if (idx = issue_ids.index(@issue.id)) && idx < limit
337 if issue_ids.size < 500
337 if issue_ids.size < 500
338 @issue_position = idx + 1
338 @issue_position = idx + 1
339 @issue_count = issue_ids.size
339 @issue_count = issue_ids.size
340 end
340 end
341 @prev_issue_id = issue_ids[idx - 1] if idx > 0
341 @prev_issue_id = issue_ids[idx - 1] if idx > 0
342 @next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
342 @next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
343 end
343 end
344 end
344 end
345 end
345 end
346
346
347 # Used by #edit and #update to set some common instance variables
347 # Used by #edit and #update to set some common instance variables
348 # from the params
348 # from the params
349 # TODO: Refactor, not everything in here is needed by #edit
349 # TODO: Refactor, not everything in here is needed by #edit
350 def update_issue_from_params
350 def update_issue_from_params
351 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
351 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
352 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
352 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
353 @time_entry.attributes = params[:time_entry]
353 @time_entry.attributes = params[:time_entry]
354
354
355 @issue.init_journal(User.current)
355 @issue.init_journal(User.current)
356
356
357 issue_attributes = params[:issue]
357 issue_attributes = params[:issue]
358 if issue_attributes && params[:conflict_resolution]
358 if issue_attributes && params[:conflict_resolution]
359 case params[:conflict_resolution]
359 case params[:conflict_resolution]
360 when 'overwrite'
360 when 'overwrite'
361 issue_attributes = issue_attributes.dup
361 issue_attributes = issue_attributes.dup
362 issue_attributes.delete(:lock_version)
362 issue_attributes.delete(:lock_version)
363 when 'add_notes'
363 when 'add_notes'
364 issue_attributes = issue_attributes.slice(:notes)
364 issue_attributes = issue_attributes.slice(:notes)
365 when 'cancel'
365 when 'cancel'
366 redirect_to issue_path(@issue)
366 redirect_to issue_path(@issue)
367 return false
367 return false
368 end
368 end
369 end
369 end
370 @issue.safe_attributes = issue_attributes
370 @issue.safe_attributes = issue_attributes
371 @priorities = IssuePriority.active
371 @priorities = IssuePriority.active
372 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
372 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
373 true
373 true
374 end
374 end
375
375
376 # TODO: Refactor, lots of extra code in here
376 # TODO: Refactor, lots of extra code in here
377 # TODO: Changing tracker on an existing issue should not trigger this
377 # TODO: Changing tracker on an existing issue should not trigger this
378 def build_new_issue_from_params
378 def build_new_issue_from_params
379 if params[:id].blank?
379 if params[:id].blank?
380 @issue = Issue.new
380 @issue = Issue.new
381 if params[:copy_from]
381 if params[:copy_from]
382 begin
382 begin
383 @copy_from = Issue.visible.find(params[:copy_from])
383 @copy_from = Issue.visible.find(params[:copy_from])
384 @copy_attachments = params[:copy_attachments].present? || request.get?
384 @copy_attachments = params[:copy_attachments].present? || request.get?
385 @copy_subtasks = params[:copy_subtasks].present? || request.get?
385 @copy_subtasks = params[:copy_subtasks].present? || request.get?
386 @issue.copy_from(@copy_from, :attachments => @copy_attachments, :subtasks => @copy_subtasks)
386 @issue.copy_from(@copy_from, :attachments => @copy_attachments, :subtasks => @copy_subtasks)
387 rescue ActiveRecord::RecordNotFound
387 rescue ActiveRecord::RecordNotFound
388 render_404
388 render_404
389 return
389 return
390 end
390 end
391 end
391 end
392 @issue.project = @project
392 @issue.project = @project
393 else
393 else
394 @issue = @project.issues.visible.find(params[:id])
394 @issue = @project.issues.visible.find(params[:id])
395 end
395 end
396
396
397 @issue.project = @project
397 @issue.project = @project
398 @issue.author ||= User.current
398 @issue.author ||= User.current
399 # Tracker must be set before custom field values
399 # Tracker must be set before custom field values
400 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
400 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
401 if @issue.tracker.nil?
401 if @issue.tracker.nil?
402 render_error l(:error_no_tracker_in_project)
402 render_error l(:error_no_tracker_in_project)
403 return false
403 return false
404 end
404 end
405 @issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
405 @issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
406 @issue.safe_attributes = params[:issue]
406 @issue.safe_attributes = params[:issue]
407
407
408 @priorities = IssuePriority.active
408 @priorities = IssuePriority.active
409 @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
409 @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
410 @available_watchers = (@issue.project.users.sort + @issue.watcher_users).uniq
410 @available_watchers = (@issue.project.users.sort + @issue.watcher_users).uniq
411 end
411 end
412
412
413 def check_for_default_issue_status
413 def check_for_default_issue_status
414 if IssueStatus.default.nil?
414 if IssueStatus.default.nil?
415 render_error l(:error_no_default_issue_status)
415 render_error l(:error_no_default_issue_status)
416 return false
416 return false
417 end
417 end
418 end
418 end
419
419
420 def parse_params_for_bulk_issue_attributes(params)
420 def parse_params_for_bulk_issue_attributes(params)
421 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
421 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
422 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
422 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
423 if custom = attributes[:custom_field_values]
423 if custom = attributes[:custom_field_values]
424 custom.reject! {|k,v| v.blank?}
424 custom.reject! {|k,v| v.blank?}
425 custom.keys.each do |k|
425 custom.keys.each do |k|
426 if custom[k].is_a?(Array)
426 if custom[k].is_a?(Array)
427 custom[k] << '' if custom[k].delete('__none__')
427 custom[k] << '' if custom[k].delete('__none__')
428 else
428 else
429 custom[k] = '' if custom[k] == '__none__'
429 custom[k] = '' if custom[k] == '__none__'
430 end
430 end
431 end
431 end
432 end
432 end
433 attributes
433 attributes
434 end
434 end
435 end
435 end
@@ -1,106 +1,106
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 QueriesController < ApplicationController
18 class QueriesController < ApplicationController
19 menu_item :issues
19 menu_item :issues
20 before_filter :find_query, :except => [:new, :create, :index]
20 before_filter :find_query, :except => [:new, :create, :index]
21 before_filter :find_optional_project, :only => [:new, :create]
21 before_filter :find_optional_project, :only => [:new, :create]
22
22
23 accept_api_auth :index
23 accept_api_auth :index
24
24
25 include QueriesHelper
25 include QueriesHelper
26
26
27 def index
27 def index
28 case params[:format]
28 case params[:format]
29 when 'xml', 'json'
29 when 'xml', 'json'
30 @offset, @limit = api_offset_and_limit
30 @offset, @limit = api_offset_and_limit
31 else
31 else
32 @limit = per_page_option
32 @limit = per_page_option
33 end
33 end
34
34
35 @query_count = IssueQuery.visible.count
35 @query_count = IssueQuery.visible.count
36 @query_pages = Paginator.new self, @query_count, @limit, params['page']
36 @query_pages = Paginator.new self, @query_count, @limit, params['page']
37 @queries = IssueQuery.visible.all(:limit => @limit, :offset => @offset, :order => "#{Query.table_name}.name")
37 @queries = IssueQuery.visible.all(:limit => @limit, :offset => @offset, :order => "#{Query.table_name}.name")
38
38
39 respond_to do |format|
39 respond_to do |format|
40 format.api
40 format.api
41 end
41 end
42 end
42 end
43
43
44 def new
44 def new
45 @query = IssueQuery.new
45 @query = IssueQuery.new
46 @query.user = User.current
46 @query.user = User.current
47 @query.project = @project
47 @query.project = @project
48 @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
48 @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
49 @query.build_from_params(params)
49 @query.build_from_params(params)
50 end
50 end
51
51
52 def create
52 def create
53 @query = IssueQuery.new(params[:query])
53 @query = IssueQuery.new(params[:query])
54 @query.user = User.current
54 @query.user = User.current
55 @query.project = params[:query_is_for_all] ? nil : @project
55 @query.project = params[:query_is_for_all] ? nil : @project
56 @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
56 @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
57 @query.build_from_params(params)
57 @query.build_from_params(params)
58 @query.column_names = nil if params[:default_columns]
58 @query.column_names = nil if params[:default_columns]
59
59
60 if @query.save
60 if @query.save
61 flash[:notice] = l(:notice_successful_create)
61 flash[:notice] = l(:notice_successful_create)
62 redirect_to _issues_path(@project, :query_id => @query)
62 redirect_to _project_issues_path(@project, :query_id => @query)
63 else
63 else
64 render :action => 'new', :layout => !request.xhr?
64 render :action => 'new', :layout => !request.xhr?
65 end
65 end
66 end
66 end
67
67
68 def edit
68 def edit
69 end
69 end
70
70
71 def update
71 def update
72 @query.attributes = params[:query]
72 @query.attributes = params[:query]
73 @query.project = nil if params[:query_is_for_all]
73 @query.project = nil if params[:query_is_for_all]
74 @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
74 @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
75 @query.build_from_params(params)
75 @query.build_from_params(params)
76 @query.column_names = nil if params[:default_columns]
76 @query.column_names = nil if params[:default_columns]
77
77
78 if @query.save
78 if @query.save
79 flash[:notice] = l(:notice_successful_update)
79 flash[:notice] = l(:notice_successful_update)
80 redirect_to _issues_path(@project, :query_id => @query)
80 redirect_to _project_issues_path(@project, :query_id => @query)
81 else
81 else
82 render :action => 'edit'
82 render :action => 'edit'
83 end
83 end
84 end
84 end
85
85
86 def destroy
86 def destroy
87 @query.destroy
87 @query.destroy
88 redirect_to _issues_path(@project, :set_filter => 1)
88 redirect_to _project_issues_path(@project, :set_filter => 1)
89 end
89 end
90
90
91 private
91 private
92 def find_query
92 def find_query
93 @query = IssueQuery.find(params[:id])
93 @query = IssueQuery.find(params[:id])
94 @project = @query.project
94 @project = @query.project
95 render_403 unless @query.editable_by?(User.current)
95 render_403 unless @query.editable_by?(User.current)
96 rescue ActiveRecord::RecordNotFound
96 rescue ActiveRecord::RecordNotFound
97 render_404
97 render_404
98 end
98 end
99
99
100 def find_optional_project
100 def find_optional_project
101 @project = Project.find(params[:project_id]) if params[:project_id]
101 @project = Project.find(params[:project_id]) if params[:project_id]
102 render_403 unless User.current.allowed_to?(:save_queries, @project, :global => true)
102 render_403 unless User.current.allowed_to?(:save_queries, @project, :global => true)
103 rescue ActiveRecord::RecordNotFound
103 rescue ActiveRecord::RecordNotFound
104 render_404
104 render_404
105 end
105 end
106 end
106 end
@@ -1,31 +1,31
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2012 Jean-Philippe Lang
4 # Copyright (C) 2006-2012 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 module RoutesHelper
20 module RoutesHelper
21
21
22 # Returns the path to project issues or to the cross-project
22 # Returns the path to project issues or to the cross-project
23 # issue list if project is nil
23 # issue list if project is nil
24 def _issues_path(project, *args)
24 def _project_issues_path(project, *args)
25 if project
25 if project
26 project_issues_path(project, *args)
26 project_issues_path(project, *args)
27 else
27 else
28 issues_path(*args)
28 issues_path(*args)
29 end
29 end
30 end
30 end
31 end
31 end
General Comments 0
You need to be logged in to leave comments. Login now