##// END OF EJS Templates
Isolates csv options for a hash param (#1159)....
Jean-Philippe Lang -
r14292:f97d23b1b7ed
parent child
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,517 +1,517
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 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 :authorize, :except => [:index, :new, :create]
24 before_filter :authorize, :except => [:index, :new, :create]
25 before_filter :find_optional_project, :only => [:index, :new, :create]
25 before_filter :find_optional_project, :only => [:index, :new, :create]
26 before_filter :build_new_issue_from_params, :only => [:new, :create]
26 before_filter :build_new_issue_from_params, :only => [:new, :create]
27 accept_rss_auth :index, :show
27 accept_rss_auth :index, :show
28 accept_api_auth :index, :show, :create, :update, :destroy
28 accept_api_auth :index, :show, :create, :update, :destroy
29
29
30 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
30 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
31
31
32 helper :journals
32 helper :journals
33 helper :projects
33 helper :projects
34 helper :custom_fields
34 helper :custom_fields
35 helper :issue_relations
35 helper :issue_relations
36 helper :watchers
36 helper :watchers
37 helper :attachments
37 helper :attachments
38 helper :queries
38 helper :queries
39 include QueriesHelper
39 include QueriesHelper
40 helper :repositories
40 helper :repositories
41 helper :sort
41 helper :sort
42 include SortHelper
42 include SortHelper
43 helper :timelog
43 helper :timelog
44
44
45 def index
45 def index
46 retrieve_query
46 retrieve_query
47 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
47 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
48 sort_update(@query.sortable_columns)
48 sort_update(@query.sortable_columns)
49 @query.sort_criteria = sort_criteria.to_a
49 @query.sort_criteria = sort_criteria.to_a
50
50
51 if @query.valid?
51 if @query.valid?
52 case params[:format]
52 case params[:format]
53 when 'csv', 'pdf'
53 when 'csv', 'pdf'
54 @limit = Setting.issues_export_limit.to_i
54 @limit = Setting.issues_export_limit.to_i
55 if params[:columns] == 'all'
55 if params[:columns] == 'all'
56 @query.column_names = @query.available_inline_columns.map(&:name)
56 @query.column_names = @query.available_inline_columns.map(&:name)
57 end
57 end
58 when 'atom'
58 when 'atom'
59 @limit = Setting.feeds_limit.to_i
59 @limit = Setting.feeds_limit.to_i
60 when 'xml', 'json'
60 when 'xml', 'json'
61 @offset, @limit = api_offset_and_limit
61 @offset, @limit = api_offset_and_limit
62 @query.column_names = %w(author)
62 @query.column_names = %w(author)
63 else
63 else
64 @limit = per_page_option
64 @limit = per_page_option
65 end
65 end
66
66
67 @issue_count = @query.issue_count
67 @issue_count = @query.issue_count
68 @issue_pages = Paginator.new @issue_count, @limit, params['page']
68 @issue_pages = Paginator.new @issue_count, @limit, params['page']
69 @offset ||= @issue_pages.offset
69 @offset ||= @issue_pages.offset
70 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
70 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
71 :order => sort_clause,
71 :order => sort_clause,
72 :offset => @offset,
72 :offset => @offset,
73 :limit => @limit)
73 :limit => @limit)
74 @issue_count_by_group = @query.issue_count_by_group
74 @issue_count_by_group = @query.issue_count_by_group
75
75
76 respond_to do |format|
76 respond_to do |format|
77 format.html { render :template => 'issues/index', :layout => !request.xhr? }
77 format.html { render :template => 'issues/index', :layout => !request.xhr? }
78 format.api {
78 format.api {
79 Issue.load_visible_relations(@issues) if include_in_api_response?('relations')
79 Issue.load_visible_relations(@issues) if include_in_api_response?('relations')
80 }
80 }
81 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
81 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
82 format.csv { send_data(query_to_csv(@issues, @query, params), :type => 'text/csv; header=present', :filename => 'issues.csv') }
82 format.csv { send_data(query_to_csv(@issues, @query, params[:csv]), :type => 'text/csv; header=present', :filename => 'issues.csv') }
83 format.pdf { send_file_headers! :type => 'application/pdf', :filename => 'issues.pdf' }
83 format.pdf { send_file_headers! :type => 'application/pdf', :filename => 'issues.pdf' }
84 end
84 end
85 else
85 else
86 respond_to do |format|
86 respond_to do |format|
87 format.html { render(:template => 'issues/index', :layout => !request.xhr?) }
87 format.html { render(:template => 'issues/index', :layout => !request.xhr?) }
88 format.any(:atom, :csv, :pdf) { render(:nothing => true) }
88 format.any(:atom, :csv, :pdf) { render(:nothing => true) }
89 format.api { render_validation_errors(@query) }
89 format.api { render_validation_errors(@query) }
90 end
90 end
91 end
91 end
92 rescue ActiveRecord::RecordNotFound
92 rescue ActiveRecord::RecordNotFound
93 render_404
93 render_404
94 end
94 end
95
95
96 def show
96 def show
97 @journals = @issue.journals.includes(:user, :details).
97 @journals = @issue.journals.includes(:user, :details).
98 references(:user, :details).
98 references(:user, :details).
99 reorder(:created_on, :id).to_a
99 reorder(:created_on, :id).to_a
100 @journals.each_with_index {|j,i| j.indice = i+1}
100 @journals.each_with_index {|j,i| j.indice = i+1}
101 @journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
101 @journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
102 Journal.preload_journals_details_custom_fields(@journals)
102 Journal.preload_journals_details_custom_fields(@journals)
103 @journals.select! {|journal| journal.notes? || journal.visible_details.any?}
103 @journals.select! {|journal| journal.notes? || journal.visible_details.any?}
104 @journals.reverse! if User.current.wants_comments_in_reverse_order?
104 @journals.reverse! if User.current.wants_comments_in_reverse_order?
105
105
106 @changesets = @issue.changesets.visible.preload(:repository, :user).to_a
106 @changesets = @issue.changesets.visible.preload(:repository, :user).to_a
107 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
107 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
108
108
109 @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
109 @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
110 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
110 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
111 @priorities = IssuePriority.active
111 @priorities = IssuePriority.active
112 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
112 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
113 @relation = IssueRelation.new
113 @relation = IssueRelation.new
114
114
115 respond_to do |format|
115 respond_to do |format|
116 format.html {
116 format.html {
117 retrieve_previous_and_next_issue_ids
117 retrieve_previous_and_next_issue_ids
118 render :template => 'issues/show'
118 render :template => 'issues/show'
119 }
119 }
120 format.api
120 format.api
121 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
121 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
122 format.pdf {
122 format.pdf {
123 send_file_headers! :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf"
123 send_file_headers! :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf"
124 }
124 }
125 end
125 end
126 end
126 end
127
127
128 def new
128 def new
129 respond_to do |format|
129 respond_to do |format|
130 format.html { render :action => 'new', :layout => !request.xhr? }
130 format.html { render :action => 'new', :layout => !request.xhr? }
131 format.js
131 format.js
132 end
132 end
133 end
133 end
134
134
135 def create
135 def create
136 unless User.current.allowed_to?(:add_issues, @issue.project, :global => true)
136 unless User.current.allowed_to?(:add_issues, @issue.project, :global => true)
137 raise ::Unauthorized
137 raise ::Unauthorized
138 end
138 end
139 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
139 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
140 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
140 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
141 if @issue.save
141 if @issue.save
142 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
142 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
143 respond_to do |format|
143 respond_to do |format|
144 format.html {
144 format.html {
145 render_attachment_warning_if_needed(@issue)
145 render_attachment_warning_if_needed(@issue)
146 flash[:notice] = l(:notice_issue_successful_create, :id => view_context.link_to("##{@issue.id}", issue_path(@issue), :title => @issue.subject))
146 flash[:notice] = l(:notice_issue_successful_create, :id => view_context.link_to("##{@issue.id}", issue_path(@issue), :title => @issue.subject))
147 redirect_after_create
147 redirect_after_create
148 }
148 }
149 format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
149 format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
150 end
150 end
151 return
151 return
152 else
152 else
153 respond_to do |format|
153 respond_to do |format|
154 format.html {
154 format.html {
155 if @issue.project.nil?
155 if @issue.project.nil?
156 render_error :status => 422
156 render_error :status => 422
157 else
157 else
158 render :action => 'new'
158 render :action => 'new'
159 end
159 end
160 }
160 }
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.js
171 format.js
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 = save_issue_with_child_records
180 saved = save_issue_with_child_records
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]).to_a
184 @conflict_journals = @issue.journals_after(params[:last_journal_id]).to_a
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 @copy
211 if @copy
212 unless User.current.allowed_to?(:copy_issues, @projects)
212 unless User.current.allowed_to?(:copy_issues, @projects)
213 raise ::Unauthorized
213 raise ::Unauthorized
214 end
214 end
215 end
215 end
216
216
217 @allowed_projects = Issue.allowed_target_projects
217 @allowed_projects = Issue.allowed_target_projects
218 if params[:issue]
218 if params[:issue]
219 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:issue][:project_id].to_s}
219 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:issue][:project_id].to_s}
220 if @target_project
220 if @target_project
221 target_projects = [@target_project]
221 target_projects = [@target_project]
222 end
222 end
223 end
223 end
224 target_projects ||= @projects
224 target_projects ||= @projects
225
225
226 if @copy
226 if @copy
227 # Copied issues will get their default statuses
227 # Copied issues will get their default statuses
228 @available_statuses = []
228 @available_statuses = []
229 else
229 else
230 @available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
230 @available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
231 end
231 end
232 @custom_fields = @issues.map{|i|i.editable_custom_fields}.reduce(:&)
232 @custom_fields = @issues.map{|i|i.editable_custom_fields}.reduce(:&)
233 @assignables = target_projects.map(&:assignable_users).reduce(:&)
233 @assignables = target_projects.map(&:assignable_users).reduce(:&)
234 @trackers = target_projects.map(&:trackers).reduce(:&)
234 @trackers = target_projects.map(&:trackers).reduce(:&)
235 @versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
235 @versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
236 @categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
236 @categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
237 if @copy
237 if @copy
238 @attachments_present = @issues.detect {|i| i.attachments.any?}.present?
238 @attachments_present = @issues.detect {|i| i.attachments.any?}.present?
239 @subtasks_present = @issues.detect {|i| !i.leaf?}.present?
239 @subtasks_present = @issues.detect {|i| !i.leaf?}.present?
240 end
240 end
241
241
242 @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
242 @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
243
243
244 @issue_params = params[:issue] || {}
244 @issue_params = params[:issue] || {}
245 @issue_params[:custom_field_values] ||= {}
245 @issue_params[:custom_field_values] ||= {}
246 end
246 end
247
247
248 def bulk_update
248 def bulk_update
249 @issues.sort!
249 @issues.sort!
250 @copy = params[:copy].present?
250 @copy = params[:copy].present?
251
251
252 attributes = parse_params_for_bulk_issue_attributes(params)
252 attributes = parse_params_for_bulk_issue_attributes(params)
253 copy_subtasks = (params[:copy_subtasks] == '1')
253 copy_subtasks = (params[:copy_subtasks] == '1')
254 copy_attachments = (params[:copy_attachments] == '1')
254 copy_attachments = (params[:copy_attachments] == '1')
255
255
256 if @copy
256 if @copy
257 unless User.current.allowed_to?(:copy_issues, @projects)
257 unless User.current.allowed_to?(:copy_issues, @projects)
258 raise ::Unauthorized
258 raise ::Unauthorized
259 end
259 end
260 target_projects = @projects
260 target_projects = @projects
261 if attributes['project_id'].present?
261 if attributes['project_id'].present?
262 target_projects = Project.where(:id => attributes['project_id']).to_a
262 target_projects = Project.where(:id => attributes['project_id']).to_a
263 end
263 end
264 unless User.current.allowed_to?(:add_issues, target_projects)
264 unless User.current.allowed_to?(:add_issues, target_projects)
265 raise ::Unauthorized
265 raise ::Unauthorized
266 end
266 end
267 end
267 end
268
268
269 unsaved_issues = []
269 unsaved_issues = []
270 saved_issues = []
270 saved_issues = []
271
271
272 if @copy && copy_subtasks
272 if @copy && copy_subtasks
273 # Descendant issues will be copied with the parent task
273 # Descendant issues will be copied with the parent task
274 # Don't copy them twice
274 # Don't copy them twice
275 @issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}}
275 @issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}}
276 end
276 end
277
277
278 @issues.each do |orig_issue|
278 @issues.each do |orig_issue|
279 orig_issue.reload
279 orig_issue.reload
280 if @copy
280 if @copy
281 issue = orig_issue.copy({},
281 issue = orig_issue.copy({},
282 :attachments => copy_attachments,
282 :attachments => copy_attachments,
283 :subtasks => copy_subtasks,
283 :subtasks => copy_subtasks,
284 :link => link_copy?(params[:link_copy])
284 :link => link_copy?(params[:link_copy])
285 )
285 )
286 else
286 else
287 issue = orig_issue
287 issue = orig_issue
288 end
288 end
289 journal = issue.init_journal(User.current, params[:notes])
289 journal = issue.init_journal(User.current, params[:notes])
290 issue.safe_attributes = attributes
290 issue.safe_attributes = attributes
291 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
291 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
292 if issue.save
292 if issue.save
293 saved_issues << issue
293 saved_issues << issue
294 else
294 else
295 unsaved_issues << orig_issue
295 unsaved_issues << orig_issue
296 end
296 end
297 end
297 end
298
298
299 if unsaved_issues.empty?
299 if unsaved_issues.empty?
300 flash[:notice] = l(:notice_successful_update) unless saved_issues.empty?
300 flash[:notice] = l(:notice_successful_update) unless saved_issues.empty?
301 if params[:follow]
301 if params[:follow]
302 if @issues.size == 1 && saved_issues.size == 1
302 if @issues.size == 1 && saved_issues.size == 1
303 redirect_to issue_path(saved_issues.first)
303 redirect_to issue_path(saved_issues.first)
304 elsif saved_issues.map(&:project).uniq.size == 1
304 elsif saved_issues.map(&:project).uniq.size == 1
305 redirect_to project_issues_path(saved_issues.map(&:project).first)
305 redirect_to project_issues_path(saved_issues.map(&:project).first)
306 end
306 end
307 else
307 else
308 redirect_back_or_default _project_issues_path(@project)
308 redirect_back_or_default _project_issues_path(@project)
309 end
309 end
310 else
310 else
311 @saved_issues = @issues
311 @saved_issues = @issues
312 @unsaved_issues = unsaved_issues
312 @unsaved_issues = unsaved_issues
313 @issues = Issue.visible.where(:id => @unsaved_issues.map(&:id)).to_a
313 @issues = Issue.visible.where(:id => @unsaved_issues.map(&:id)).to_a
314 bulk_edit
314 bulk_edit
315 render :action => 'bulk_edit'
315 render :action => 'bulk_edit'
316 end
316 end
317 end
317 end
318
318
319 def destroy
319 def destroy
320 @hours = TimeEntry.where(:issue_id => @issues.map(&:id)).sum(:hours).to_f
320 @hours = TimeEntry.where(:issue_id => @issues.map(&:id)).sum(:hours).to_f
321 if @hours > 0
321 if @hours > 0
322 case params[:todo]
322 case params[:todo]
323 when 'destroy'
323 when 'destroy'
324 # nothing to do
324 # nothing to do
325 when 'nullify'
325 when 'nullify'
326 TimeEntry.where(['issue_id IN (?)', @issues]).update_all('issue_id = NULL')
326 TimeEntry.where(['issue_id IN (?)', @issues]).update_all('issue_id = NULL')
327 when 'reassign'
327 when 'reassign'
328 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
328 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
329 if reassign_to.nil?
329 if reassign_to.nil?
330 flash.now[:error] = l(:error_issue_not_found_in_project)
330 flash.now[:error] = l(:error_issue_not_found_in_project)
331 return
331 return
332 else
332 else
333 TimeEntry.where(['issue_id IN (?)', @issues]).
333 TimeEntry.where(['issue_id IN (?)', @issues]).
334 update_all("issue_id = #{reassign_to.id}")
334 update_all("issue_id = #{reassign_to.id}")
335 end
335 end
336 else
336 else
337 # display the destroy form if it's a user request
337 # display the destroy form if it's a user request
338 return unless api_request?
338 return unless api_request?
339 end
339 end
340 end
340 end
341 @issues.each do |issue|
341 @issues.each do |issue|
342 begin
342 begin
343 issue.reload.destroy
343 issue.reload.destroy
344 rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists
344 rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists
345 # nothing to do, issue was already deleted (eg. by a parent)
345 # nothing to do, issue was already deleted (eg. by a parent)
346 end
346 end
347 end
347 end
348 respond_to do |format|
348 respond_to do |format|
349 format.html { redirect_back_or_default _project_issues_path(@project) }
349 format.html { redirect_back_or_default _project_issues_path(@project) }
350 format.api { render_api_ok }
350 format.api { render_api_ok }
351 end
351 end
352 end
352 end
353
353
354 private
354 private
355
355
356 def retrieve_previous_and_next_issue_ids
356 def retrieve_previous_and_next_issue_ids
357 retrieve_query_from_session
357 retrieve_query_from_session
358 if @query
358 if @query
359 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
359 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
360 sort_update(@query.sortable_columns, 'issues_index_sort')
360 sort_update(@query.sortable_columns, 'issues_index_sort')
361 limit = 500
361 limit = 500
362 issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1), :include => [:assigned_to, :tracker, :priority, :category, :fixed_version])
362 issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1), :include => [:assigned_to, :tracker, :priority, :category, :fixed_version])
363 if (idx = issue_ids.index(@issue.id)) && idx < limit
363 if (idx = issue_ids.index(@issue.id)) && idx < limit
364 if issue_ids.size < 500
364 if issue_ids.size < 500
365 @issue_position = idx + 1
365 @issue_position = idx + 1
366 @issue_count = issue_ids.size
366 @issue_count = issue_ids.size
367 end
367 end
368 @prev_issue_id = issue_ids[idx - 1] if idx > 0
368 @prev_issue_id = issue_ids[idx - 1] if idx > 0
369 @next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
369 @next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
370 end
370 end
371 end
371 end
372 end
372 end
373
373
374 # Used by #edit and #update to set some common instance variables
374 # Used by #edit and #update to set some common instance variables
375 # from the params
375 # from the params
376 def update_issue_from_params
376 def update_issue_from_params
377 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
377 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
378 if params[:time_entry]
378 if params[:time_entry]
379 @time_entry.attributes = params[:time_entry]
379 @time_entry.attributes = params[:time_entry]
380 end
380 end
381
381
382 @issue.init_journal(User.current)
382 @issue.init_journal(User.current)
383
383
384 issue_attributes = params[:issue]
384 issue_attributes = params[:issue]
385 if issue_attributes && params[:conflict_resolution]
385 if issue_attributes && params[:conflict_resolution]
386 case params[:conflict_resolution]
386 case params[:conflict_resolution]
387 when 'overwrite'
387 when 'overwrite'
388 issue_attributes = issue_attributes.dup
388 issue_attributes = issue_attributes.dup
389 issue_attributes.delete(:lock_version)
389 issue_attributes.delete(:lock_version)
390 when 'add_notes'
390 when 'add_notes'
391 issue_attributes = issue_attributes.slice(:notes)
391 issue_attributes = issue_attributes.slice(:notes)
392 when 'cancel'
392 when 'cancel'
393 redirect_to issue_path(@issue)
393 redirect_to issue_path(@issue)
394 return false
394 return false
395 end
395 end
396 end
396 end
397 @issue.safe_attributes = issue_attributes
397 @issue.safe_attributes = issue_attributes
398 @priorities = IssuePriority.active
398 @priorities = IssuePriority.active
399 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
399 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
400 true
400 true
401 end
401 end
402
402
403 # Used by #new and #create to build a new issue from the params
403 # Used by #new and #create to build a new issue from the params
404 # The new issue will be copied from an existing one if copy_from parameter is given
404 # The new issue will be copied from an existing one if copy_from parameter is given
405 def build_new_issue_from_params
405 def build_new_issue_from_params
406 @issue = Issue.new
406 @issue = Issue.new
407 if params[:copy_from]
407 if params[:copy_from]
408 begin
408 begin
409 @issue.init_journal(User.current)
409 @issue.init_journal(User.current)
410 @copy_from = Issue.visible.find(params[:copy_from])
410 @copy_from = Issue.visible.find(params[:copy_from])
411 unless User.current.allowed_to?(:copy_issues, @copy_from.project)
411 unless User.current.allowed_to?(:copy_issues, @copy_from.project)
412 raise ::Unauthorized
412 raise ::Unauthorized
413 end
413 end
414 @link_copy = link_copy?(params[:link_copy]) || request.get?
414 @link_copy = link_copy?(params[:link_copy]) || request.get?
415 @copy_attachments = params[:copy_attachments].present? || request.get?
415 @copy_attachments = params[:copy_attachments].present? || request.get?
416 @copy_subtasks = params[:copy_subtasks].present? || request.get?
416 @copy_subtasks = params[:copy_subtasks].present? || request.get?
417 @issue.copy_from(@copy_from, :attachments => @copy_attachments, :subtasks => @copy_subtasks, :link => @link_copy)
417 @issue.copy_from(@copy_from, :attachments => @copy_attachments, :subtasks => @copy_subtasks, :link => @link_copy)
418 rescue ActiveRecord::RecordNotFound
418 rescue ActiveRecord::RecordNotFound
419 render_404
419 render_404
420 return
420 return
421 end
421 end
422 end
422 end
423 @issue.project = @project
423 @issue.project = @project
424 if request.get?
424 if request.get?
425 @issue.project ||= @issue.allowed_target_projects.first
425 @issue.project ||= @issue.allowed_target_projects.first
426 end
426 end
427 @issue.author ||= User.current
427 @issue.author ||= User.current
428 @issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
428 @issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
429
429
430 attrs = (params[:issue] || {}).deep_dup
430 attrs = (params[:issue] || {}).deep_dup
431 if action_name == 'new' && params[:was_default_status] == attrs[:status_id]
431 if action_name == 'new' && params[:was_default_status] == attrs[:status_id]
432 attrs.delete(:status_id)
432 attrs.delete(:status_id)
433 end
433 end
434 @issue.safe_attributes = attrs
434 @issue.safe_attributes = attrs
435
435
436 if @issue.project
436 if @issue.project
437 @issue.tracker ||= @issue.project.trackers.first
437 @issue.tracker ||= @issue.project.trackers.first
438 if @issue.tracker.nil?
438 if @issue.tracker.nil?
439 render_error l(:error_no_tracker_in_project)
439 render_error l(:error_no_tracker_in_project)
440 return false
440 return false
441 end
441 end
442 if @issue.status.nil?
442 if @issue.status.nil?
443 render_error l(:error_no_default_issue_status)
443 render_error l(:error_no_default_issue_status)
444 return false
444 return false
445 end
445 end
446 end
446 end
447
447
448 @priorities = IssuePriority.active
448 @priorities = IssuePriority.active
449 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
449 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
450 end
450 end
451
451
452 def parse_params_for_bulk_issue_attributes(params)
452 def parse_params_for_bulk_issue_attributes(params)
453 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
453 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
454 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
454 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
455 if custom = attributes[:custom_field_values]
455 if custom = attributes[:custom_field_values]
456 custom.reject! {|k,v| v.blank?}
456 custom.reject! {|k,v| v.blank?}
457 custom.keys.each do |k|
457 custom.keys.each do |k|
458 if custom[k].is_a?(Array)
458 if custom[k].is_a?(Array)
459 custom[k] << '' if custom[k].delete('__none__')
459 custom[k] << '' if custom[k].delete('__none__')
460 else
460 else
461 custom[k] = '' if custom[k] == '__none__'
461 custom[k] = '' if custom[k] == '__none__'
462 end
462 end
463 end
463 end
464 end
464 end
465 attributes
465 attributes
466 end
466 end
467
467
468 # Saves @issue and a time_entry from the parameters
468 # Saves @issue and a time_entry from the parameters
469 def save_issue_with_child_records
469 def save_issue_with_child_records
470 Issue.transaction do
470 Issue.transaction do
471 if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, @issue.project)
471 if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, @issue.project)
472 time_entry = @time_entry || TimeEntry.new
472 time_entry = @time_entry || TimeEntry.new
473 time_entry.project = @issue.project
473 time_entry.project = @issue.project
474 time_entry.issue = @issue
474 time_entry.issue = @issue
475 time_entry.user = User.current
475 time_entry.user = User.current
476 time_entry.spent_on = User.current.today
476 time_entry.spent_on = User.current.today
477 time_entry.attributes = params[:time_entry]
477 time_entry.attributes = params[:time_entry]
478 @issue.time_entries << time_entry
478 @issue.time_entries << time_entry
479 end
479 end
480
480
481 call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal})
481 call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal})
482 if @issue.save
482 if @issue.save
483 call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal})
483 call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal})
484 else
484 else
485 raise ActiveRecord::Rollback
485 raise ActiveRecord::Rollback
486 end
486 end
487 end
487 end
488 end
488 end
489
489
490 # Returns true if the issue copy should be linked
490 # Returns true if the issue copy should be linked
491 # to the original issue
491 # to the original issue
492 def link_copy?(param)
492 def link_copy?(param)
493 case Setting.link_copied_issue
493 case Setting.link_copied_issue
494 when 'yes'
494 when 'yes'
495 true
495 true
496 when 'no'
496 when 'no'
497 false
497 false
498 when 'ask'
498 when 'ask'
499 param == '1'
499 param == '1'
500 end
500 end
501 end
501 end
502
502
503 # Redirects user after a successful issue creation
503 # Redirects user after a successful issue creation
504 def redirect_after_create
504 def redirect_after_create
505 if params[:continue]
505 if params[:continue]
506 attrs = {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?}
506 attrs = {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?}
507 if params[:project_id]
507 if params[:project_id]
508 redirect_to new_project_issue_path(@issue.project, :issue => attrs)
508 redirect_to new_project_issue_path(@issue.project, :issue => attrs)
509 else
509 else
510 attrs.merge! :project_id => @issue.project_id
510 attrs.merge! :project_id => @issue.project_id
511 redirect_to new_issue_path(:issue => attrs)
511 redirect_to new_issue_path(:issue => attrs)
512 end
512 end
513 else
513 else
514 redirect_to issue_path(@issue)
514 redirect_to issue_path(@issue)
515 end
515 end
516 end
516 end
517 end
517 end
@@ -1,245 +1,246
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2015 Jean-Philippe Lang
4 # Copyright (C) 2006-2015 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 QueriesHelper
20 module QueriesHelper
21 include ApplicationHelper
21 include ApplicationHelper
22
22
23 def filters_options_for_select(query)
23 def filters_options_for_select(query)
24 ungrouped = []
24 ungrouped = []
25 grouped = {}
25 grouped = {}
26 query.available_filters.map do |field, field_options|
26 query.available_filters.map do |field, field_options|
27 if [:tree, :relation].include?(field_options[:type])
27 if [:tree, :relation].include?(field_options[:type])
28 group = :label_related_issues
28 group = :label_related_issues
29 elsif field =~ /^(.+)\./
29 elsif field =~ /^(.+)\./
30 # association filters
30 # association filters
31 group = "field_#{$1}"
31 group = "field_#{$1}"
32 elsif %w(member_of_group assigned_to_role).include?(field)
32 elsif %w(member_of_group assigned_to_role).include?(field)
33 group = :field_assigned_to
33 group = :field_assigned_to
34 elsif field_options[:type] == :date_past || field_options[:type] == :date
34 elsif field_options[:type] == :date_past || field_options[:type] == :date
35 group = :label_date
35 group = :label_date
36 end
36 end
37 if group
37 if group
38 (grouped[group] ||= []) << [field_options[:name], field]
38 (grouped[group] ||= []) << [field_options[:name], field]
39 else
39 else
40 ungrouped << [field_options[:name], field]
40 ungrouped << [field_options[:name], field]
41 end
41 end
42 end
42 end
43 # Don't group dates if there's only one (eg. time entries filters)
43 # Don't group dates if there's only one (eg. time entries filters)
44 if grouped[:label_date].try(:size) == 1
44 if grouped[:label_date].try(:size) == 1
45 ungrouped << grouped.delete(:label_date).first
45 ungrouped << grouped.delete(:label_date).first
46 end
46 end
47 s = options_for_select([[]] + ungrouped)
47 s = options_for_select([[]] + ungrouped)
48 if grouped.present?
48 if grouped.present?
49 localized_grouped = grouped.map {|k,v| [l(k), v]}
49 localized_grouped = grouped.map {|k,v| [l(k), v]}
50 s << grouped_options_for_select(localized_grouped)
50 s << grouped_options_for_select(localized_grouped)
51 end
51 end
52 s
52 s
53 end
53 end
54
54
55 def query_filters_hidden_tags(query)
55 def query_filters_hidden_tags(query)
56 tags = ''.html_safe
56 tags = ''.html_safe
57 query.filters.each do |field, options|
57 query.filters.each do |field, options|
58 tags << hidden_field_tag("f[]", field, :id => nil)
58 tags << hidden_field_tag("f[]", field, :id => nil)
59 tags << hidden_field_tag("op[#{field}]", options[:operator], :id => nil)
59 tags << hidden_field_tag("op[#{field}]", options[:operator], :id => nil)
60 options[:values].each do |value|
60 options[:values].each do |value|
61 tags << hidden_field_tag("v[#{field}][]", value, :id => nil)
61 tags << hidden_field_tag("v[#{field}][]", value, :id => nil)
62 end
62 end
63 end
63 end
64 tags
64 tags
65 end
65 end
66
66
67 def query_columns_hidden_tags(query)
67 def query_columns_hidden_tags(query)
68 tags = ''.html_safe
68 tags = ''.html_safe
69 query.columns.each do |column|
69 query.columns.each do |column|
70 tags << hidden_field_tag("c[]", column.name, :id => nil)
70 tags << hidden_field_tag("c[]", column.name, :id => nil)
71 end
71 end
72 tags
72 tags
73 end
73 end
74
74
75 def query_hidden_tags(query)
75 def query_hidden_tags(query)
76 query_filters_hidden_tags(query) + query_columns_hidden_tags(query)
76 query_filters_hidden_tags(query) + query_columns_hidden_tags(query)
77 end
77 end
78
78
79 def available_block_columns_tags(query)
79 def available_block_columns_tags(query)
80 tags = ''.html_safe
80 tags = ''.html_safe
81 query.available_block_columns.each do |column|
81 query.available_block_columns.each do |column|
82 tags << content_tag('label', check_box_tag('c[]', column.name.to_s, query.has_column?(column), :id => nil) + " #{column.caption}", :class => 'inline')
82 tags << content_tag('label', check_box_tag('c[]', column.name.to_s, query.has_column?(column), :id => nil) + " #{column.caption}", :class => 'inline')
83 end
83 end
84 tags
84 tags
85 end
85 end
86
86
87 def available_totalable_columns_tags(query)
87 def available_totalable_columns_tags(query)
88 tags = ''.html_safe
88 tags = ''.html_safe
89 query.available_totalable_columns.each do |column|
89 query.available_totalable_columns.each do |column|
90 tags << content_tag('label', check_box_tag('t[]', column.name.to_s, query.totalable_columns.include?(column), :id => nil) + " #{column.caption}", :class => 'inline')
90 tags << content_tag('label', check_box_tag('t[]', column.name.to_s, query.totalable_columns.include?(column), :id => nil) + " #{column.caption}", :class => 'inline')
91 end
91 end
92 tags
92 tags
93 end
93 end
94
94
95 def query_available_inline_columns_options(query)
95 def query_available_inline_columns_options(query)
96 (query.available_inline_columns - query.columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
96 (query.available_inline_columns - query.columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
97 end
97 end
98
98
99 def query_selected_inline_columns_options(query)
99 def query_selected_inline_columns_options(query)
100 (query.inline_columns & query.available_inline_columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
100 (query.inline_columns & query.available_inline_columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
101 end
101 end
102
102
103 def render_query_columns_selection(query, options={})
103 def render_query_columns_selection(query, options={})
104 tag_name = (options[:name] || 'c') + '[]'
104 tag_name = (options[:name] || 'c') + '[]'
105 render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name}
105 render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name}
106 end
106 end
107
107
108 def render_query_totals(query)
108 def render_query_totals(query)
109 return unless query.totalable_columns.present?
109 return unless query.totalable_columns.present?
110 totals = query.totalable_columns.map do |column|
110 totals = query.totalable_columns.map do |column|
111 total_tag(column, query.total_for(column))
111 total_tag(column, query.total_for(column))
112 end
112 end
113 content_tag('p', totals.join(" ").html_safe, :class => "query-totals")
113 content_tag('p', totals.join(" ").html_safe, :class => "query-totals")
114 end
114 end
115
115
116 def total_tag(column, value)
116 def total_tag(column, value)
117 label = content_tag('span', "#{column.caption}:")
117 label = content_tag('span', "#{column.caption}:")
118 value = content_tag('span', format_object(value), :class => 'value')
118 value = content_tag('span', format_object(value), :class => 'value')
119 content_tag('span', label + " " + value, :class => "total-for-#{column.name.to_s.dasherize}")
119 content_tag('span', label + " " + value, :class => "total-for-#{column.name.to_s.dasherize}")
120 end
120 end
121
121
122 def column_header(column)
122 def column_header(column)
123 column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption,
123 column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption,
124 :default_order => column.default_order) :
124 :default_order => column.default_order) :
125 content_tag('th', h(column.caption))
125 content_tag('th', h(column.caption))
126 end
126 end
127
127
128 def column_content(column, issue)
128 def column_content(column, issue)
129 value = column.value_object(issue)
129 value = column.value_object(issue)
130 if value.is_a?(Array)
130 if value.is_a?(Array)
131 value.collect {|v| column_value(column, issue, v)}.compact.join(', ').html_safe
131 value.collect {|v| column_value(column, issue, v)}.compact.join(', ').html_safe
132 else
132 else
133 column_value(column, issue, value)
133 column_value(column, issue, value)
134 end
134 end
135 end
135 end
136
136
137 def column_value(column, issue, value)
137 def column_value(column, issue, value)
138 case column.name
138 case column.name
139 when :id
139 when :id
140 link_to value, issue_path(issue)
140 link_to value, issue_path(issue)
141 when :subject
141 when :subject
142 link_to value, issue_path(issue)
142 link_to value, issue_path(issue)
143 when :parent
143 when :parent
144 value ? (value.visible? ? link_to_issue(value, :subject => false) : "##{value.id}") : ''
144 value ? (value.visible? ? link_to_issue(value, :subject => false) : "##{value.id}") : ''
145 when :description
145 when :description
146 issue.description? ? content_tag('div', textilizable(issue, :description), :class => "wiki") : ''
146 issue.description? ? content_tag('div', textilizable(issue, :description), :class => "wiki") : ''
147 when :done_ratio
147 when :done_ratio
148 progress_bar(value, :width => '80px')
148 progress_bar(value, :width => '80px')
149 when :relations
149 when :relations
150 content_tag('span',
150 content_tag('span',
151 value.to_s(issue) {|other| link_to_issue(other, :subject => false, :tracker => false)}.html_safe,
151 value.to_s(issue) {|other| link_to_issue(other, :subject => false, :tracker => false)}.html_safe,
152 :class => value.css_classes_for(issue))
152 :class => value.css_classes_for(issue))
153 else
153 else
154 format_object(value)
154 format_object(value)
155 end
155 end
156 end
156 end
157
157
158 def csv_content(column, issue)
158 def csv_content(column, issue)
159 value = column.value_object(issue)
159 value = column.value_object(issue)
160 if value.is_a?(Array)
160 if value.is_a?(Array)
161 value.collect {|v| csv_value(column, issue, v)}.compact.join(', ')
161 value.collect {|v| csv_value(column, issue, v)}.compact.join(', ')
162 else
162 else
163 csv_value(column, issue, value)
163 csv_value(column, issue, value)
164 end
164 end
165 end
165 end
166
166
167 def csv_value(column, object, value)
167 def csv_value(column, object, value)
168 format_object(value, false) do |value|
168 format_object(value, false) do |value|
169 case value.class.name
169 case value.class.name
170 when 'Float'
170 when 'Float'
171 sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator))
171 sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator))
172 when 'IssueRelation'
172 when 'IssueRelation'
173 value.to_s(object)
173 value.to_s(object)
174 when 'Issue'
174 when 'Issue'
175 if object.is_a?(TimeEntry)
175 if object.is_a?(TimeEntry)
176 "#{value.tracker} ##{value.id}: #{value.subject}"
176 "#{value.tracker} ##{value.id}: #{value.subject}"
177 else
177 else
178 value.id
178 value.id
179 end
179 end
180 else
180 else
181 value
181 value
182 end
182 end
183 end
183 end
184 end
184 end
185
185
186 def query_to_csv(items, query, options={})
186 def query_to_csv(items, query, options={})
187 options ||= {}
187 columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns)
188 columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns)
188 query.available_block_columns.each do |column|
189 query.available_block_columns.each do |column|
189 if options[column.name].present?
190 if options[column.name].present?
190 columns << column
191 columns << column
191 end
192 end
192 end
193 end
193
194
194 Redmine::Export::CSV.generate do |csv|
195 Redmine::Export::CSV.generate do |csv|
195 # csv header fields
196 # csv header fields
196 csv << columns.map {|c| c.caption.to_s}
197 csv << columns.map {|c| c.caption.to_s}
197 # csv lines
198 # csv lines
198 items.each do |item|
199 items.each do |item|
199 csv << columns.map {|c| csv_content(c, item)}
200 csv << columns.map {|c| csv_content(c, item)}
200 end
201 end
201 end
202 end
202 end
203 end
203
204
204 # Retrieve query from session or build a new query
205 # Retrieve query from session or build a new query
205 def retrieve_query
206 def retrieve_query
206 if !params[:query_id].blank?
207 if !params[:query_id].blank?
207 cond = "project_id IS NULL"
208 cond = "project_id IS NULL"
208 cond << " OR project_id = #{@project.id}" if @project
209 cond << " OR project_id = #{@project.id}" if @project
209 @query = IssueQuery.where(cond).find(params[:query_id])
210 @query = IssueQuery.where(cond).find(params[:query_id])
210 raise ::Unauthorized unless @query.visible?
211 raise ::Unauthorized unless @query.visible?
211 @query.project = @project
212 @query.project = @project
212 session[:query] = {:id => @query.id, :project_id => @query.project_id}
213 session[:query] = {:id => @query.id, :project_id => @query.project_id}
213 sort_clear
214 sort_clear
214 elsif api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
215 elsif api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
215 # Give it a name, required to be valid
216 # Give it a name, required to be valid
216 @query = IssueQuery.new(:name => "_")
217 @query = IssueQuery.new(:name => "_")
217 @query.project = @project
218 @query.project = @project
218 @query.build_from_params(params)
219 @query.build_from_params(params)
219 session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names, :totalable_names => @query.totalable_names}
220 session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names, :totalable_names => @query.totalable_names}
220 else
221 else
221 # retrieve from session
222 # retrieve from session
222 @query = nil
223 @query = nil
223 @query = IssueQuery.find_by_id(session[:query][:id]) if session[:query][:id]
224 @query = IssueQuery.find_by_id(session[:query][:id]) if session[:query][:id]
224 @query ||= IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names], :totalable_names => session[:query][:totalable_names])
225 @query ||= IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names], :totalable_names => session[:query][:totalable_names])
225 @query.project = @project
226 @query.project = @project
226 end
227 end
227 end
228 end
228
229
229 def retrieve_query_from_session
230 def retrieve_query_from_session
230 if session[:query]
231 if session[:query]
231 if session[:query][:id]
232 if session[:query][:id]
232 @query = IssueQuery.find_by_id(session[:query][:id])
233 @query = IssueQuery.find_by_id(session[:query][:id])
233 return unless @query
234 return unless @query
234 else
235 else
235 @query = IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names], :totalable_names => session[:query][:totalable_names])
236 @query = IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names], :totalable_names => session[:query][:totalable_names])
236 end
237 end
237 if session[:query].has_key?(:project_id)
238 if session[:query].has_key?(:project_id)
238 @query.project_id = session[:query][:project_id]
239 @query.project_id = session[:query][:project_id]
239 else
240 else
240 @query.project = @project
241 @query.project = @project
241 end
242 end
242 @query
243 @query
243 end
244 end
244 end
245 end
245 end
246 end
@@ -1,119 +1,119
1 <div class="contextual">
1 <div class="contextual">
2 <% if !@query.new_record? && @query.editable_by?(User.current) %>
2 <% if !@query.new_record? && @query.editable_by?(User.current) %>
3 <%= link_to l(:button_edit), edit_query_path(@query), :class => 'icon icon-edit' %>
3 <%= link_to l(:button_edit), edit_query_path(@query), :class => 'icon icon-edit' %>
4 <%= delete_link query_path(@query) %>
4 <%= delete_link query_path(@query) %>
5 <% end %>
5 <% end %>
6 </div>
6 </div>
7
7
8 <h2><%= @query.new_record? ? l(:label_issue_plural) : @query.name %></h2>
8 <h2><%= @query.new_record? ? l(:label_issue_plural) : @query.name %></h2>
9 <% html_title(@query.new_record? ? l(:label_issue_plural) : @query.name) %>
9 <% html_title(@query.new_record? ? l(:label_issue_plural) : @query.name) %>
10
10
11 <%= form_tag({ :controller => 'issues', :action => 'index', :project_id => @project },
11 <%= form_tag({ :controller => 'issues', :action => 'index', :project_id => @project },
12 :method => :get, :id => 'query_form') do %>
12 :method => :get, :id => 'query_form') do %>
13 <div id="query_form_with_buttons" class="hide-when-print">
13 <div id="query_form_with_buttons" class="hide-when-print">
14 <%= hidden_field_tag 'set_filter', '1' %>
14 <%= hidden_field_tag 'set_filter', '1' %>
15 <div id="query_form_content">
15 <div id="query_form_content">
16 <fieldset id="filters" class="collapsible <%= @query.new_record? ? "" : "collapsed" %>">
16 <fieldset id="filters" class="collapsible <%= @query.new_record? ? "" : "collapsed" %>">
17 <legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
17 <legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
18 <div style="<%= @query.new_record? ? "" : "display: none;" %>">
18 <div style="<%= @query.new_record? ? "" : "display: none;" %>">
19 <%= render :partial => 'queries/filters', :locals => {:query => @query} %>
19 <%= render :partial => 'queries/filters', :locals => {:query => @query} %>
20 </div>
20 </div>
21 </fieldset>
21 </fieldset>
22 <fieldset class="collapsible collapsed">
22 <fieldset class="collapsible collapsed">
23 <legend onclick="toggleFieldset(this);"><%= l(:label_options) %></legend>
23 <legend onclick="toggleFieldset(this);"><%= l(:label_options) %></legend>
24 <div style="display: none;">
24 <div style="display: none;">
25 <table>
25 <table>
26 <tr>
26 <tr>
27 <td><%= l(:field_column_names) %></td>
27 <td><%= l(:field_column_names) %></td>
28 <td><%= render_query_columns_selection(@query) %></td>
28 <td><%= render_query_columns_selection(@query) %></td>
29 </tr>
29 </tr>
30 <tr>
30 <tr>
31 <td><label for='group_by'><%= l(:field_group_by) %></label></td>
31 <td><label for='group_by'><%= l(:field_group_by) %></label></td>
32 <td><%= select_tag('group_by',
32 <td><%= select_tag('group_by',
33 options_for_select(
33 options_for_select(
34 [[]] + @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]},
34 [[]] + @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]},
35 @query.group_by)
35 @query.group_by)
36 ) %></td>
36 ) %></td>
37 </tr>
37 </tr>
38 <tr>
38 <tr>
39 <td><%= l(:button_show) %></td>
39 <td><%= l(:button_show) %></td>
40 <td><%= available_block_columns_tags(@query) %></td>
40 <td><%= available_block_columns_tags(@query) %></td>
41 </tr>
41 </tr>
42 <tr>
42 <tr>
43 <td><%= l(:label_total_plural) %></td>
43 <td><%= l(:label_total_plural) %></td>
44 <td><%= available_totalable_columns_tags(@query) %></td>
44 <td><%= available_totalable_columns_tags(@query) %></td>
45 </tr>
45 </tr>
46 </table>
46 </table>
47 </div>
47 </div>
48 </fieldset>
48 </fieldset>
49 </div>
49 </div>
50 <p class="buttons">
50 <p class="buttons">
51 <%= link_to_function l(:button_apply), '$("#query_form").submit()', :class => 'icon icon-checked' %>
51 <%= link_to_function l(:button_apply), '$("#query_form").submit()', :class => 'icon icon-checked' %>
52 <%= link_to l(:button_clear), { :set_filter => 1, :project_id => @project }, :class => 'icon icon-reload' %>
52 <%= link_to l(:button_clear), { :set_filter => 1, :project_id => @project }, :class => 'icon icon-reload' %>
53 <% if @query.new_record? && User.current.allowed_to?(:save_queries, @project, :global => true) %>
53 <% if @query.new_record? && User.current.allowed_to?(:save_queries, @project, :global => true) %>
54 <%= link_to_function l(:button_save),
54 <%= link_to_function l(:button_save),
55 "$('#query_form').attr('action', '#{ @project ? new_project_query_path(@project) : new_query_path }').submit()",
55 "$('#query_form').attr('action', '#{ @project ? new_project_query_path(@project) : new_query_path }').submit()",
56 :class => 'icon icon-save' %>
56 :class => 'icon icon-save' %>
57 <% end %>
57 <% end %>
58 </p>
58 </p>
59 </div>
59 </div>
60 <% end %>
60 <% end %>
61
61
62 <%= error_messages_for 'query' %>
62 <%= error_messages_for 'query' %>
63 <% if @query.valid? %>
63 <% if @query.valid? %>
64 <% if @issues.empty? %>
64 <% if @issues.empty? %>
65 <p class="nodata"><%= l(:label_no_data) %></p>
65 <p class="nodata"><%= l(:label_no_data) %></p>
66 <% else %>
66 <% else %>
67 <%= render_query_totals(@query) %>
67 <%= render_query_totals(@query) %>
68 <%= render :partial => 'issues/list', :locals => {:issues => @issues, :query => @query} %>
68 <%= render :partial => 'issues/list', :locals => {:issues => @issues, :query => @query} %>
69 <p class="pagination"><%= pagination_links_full @issue_pages, @issue_count %></p>
69 <p class="pagination"><%= pagination_links_full @issue_pages, @issue_count %></p>
70 <% end %>
70 <% end %>
71
71
72 <% other_formats_links do |f| %>
72 <% other_formats_links do |f| %>
73 <%= f.link_to 'Atom', :url => params.merge(:key => User.current.rss_key) %>
73 <%= f.link_to 'Atom', :url => params.merge(:key => User.current.rss_key) %>
74 <%= f.link_to 'CSV', :url => params, :onclick => "showModal('csv-export-options', '350px'); return false;" %>
74 <%= f.link_to 'CSV', :url => params, :onclick => "showModal('csv-export-options', '350px'); return false;" %>
75 <%= f.link_to 'PDF', :url => params %>
75 <%= f.link_to 'PDF', :url => params %>
76 <% end %>
76 <% end %>
77
77
78 <div id="csv-export-options" style="display:none;">
78 <div id="csv-export-options" style="display:none;">
79 <h3 class="title"><%= l(:label_export_options, :export_format => 'CSV') %></h3>
79 <h3 class="title"><%= l(:label_export_options, :export_format => 'CSV') %></h3>
80 <%= form_tag(params.merge({:format => 'csv',:page=>nil}), :method => :get, :id => 'csv-export-form') do %>
80 <%= form_tag(params.merge({:format => 'csv',:page=>nil}), :method => :get, :id => 'csv-export-form') do %>
81 <p>
81 <p>
82 <label><%= radio_button_tag 'columns', '', true %> <%= l(:description_selected_columns) %></label><br />
82 <label><%= radio_button_tag 'csv[columns]', '', true %> <%= l(:description_selected_columns) %></label><br />
83 <label><%= radio_button_tag 'columns', 'all' %> <%= l(:description_all_columns) %></label>
83 <label><%= radio_button_tag 'csv[columns]', 'all' %> <%= l(:description_all_columns) %></label>
84 </p>
84 </p>
85 <p>
85 <p>
86 <label><%= check_box_tag 'description', '1', @query.has_column?(:description) %> <%= l(:field_description) %></label>
86 <label><%= check_box_tag 'csv[description]', '1', @query.has_column?(:description) %> <%= l(:field_description) %></label>
87 </p>
87 </p>
88 <% if @issue_count > Setting.issues_export_limit.to_i %>
88 <% if @issue_count > Setting.issues_export_limit.to_i %>
89 <p class="icon icon-warning">
89 <p class="icon icon-warning">
90 <%= l(:setting_issues_export_limit) %>: <%= Setting.issues_export_limit.to_i %>
90 <%= l(:setting_issues_export_limit) %>: <%= Setting.issues_export_limit.to_i %>
91 </p>
91 </p>
92 <% end %>
92 <% end %>
93 <p class="buttons">
93 <p class="buttons">
94 <%= submit_tag l(:button_export), :name => nil, :onclick => "hideModal(this);" %>
94 <%= submit_tag l(:button_export), :name => nil, :onclick => "hideModal(this);" %>
95 <%= submit_tag l(:button_cancel), :name => nil, :onclick => "hideModal(this);", :type => 'button' %>
95 <%= submit_tag l(:button_cancel), :name => nil, :onclick => "hideModal(this);", :type => 'button' %>
96 </p>
96 </p>
97 <% end %>
97 <% end %>
98 </div>
98 </div>
99
99
100 <% end %>
100 <% end %>
101 <%= call_hook(:view_issues_index_bottom, { :issues => @issues, :project => @project, :query => @query }) %>
101 <%= call_hook(:view_issues_index_bottom, { :issues => @issues, :project => @project, :query => @query }) %>
102
102
103 <% content_for :sidebar do %>
103 <% content_for :sidebar do %>
104 <%= render :partial => 'issues/sidebar' %>
104 <%= render :partial => 'issues/sidebar' %>
105 <% end %>
105 <% end %>
106
106
107 <% content_for :header_tags do %>
107 <% content_for :header_tags do %>
108 <%= auto_discovery_link_tag(:atom,
108 <%= auto_discovery_link_tag(:atom,
109 {:query_id => @query, :format => 'atom',
109 {:query_id => @query, :format => 'atom',
110 :page => nil, :key => User.current.rss_key},
110 :page => nil, :key => User.current.rss_key},
111 :title => l(:label_issue_plural)) %>
111 :title => l(:label_issue_plural)) %>
112 <%= auto_discovery_link_tag(:atom,
112 <%= auto_discovery_link_tag(:atom,
113 {:controller => 'journals', :action => 'index',
113 {:controller => 'journals', :action => 'index',
114 :query_id => @query, :format => 'atom',
114 :query_id => @query, :format => 'atom',
115 :page => nil, :key => User.current.rss_key},
115 :page => nil, :key => User.current.rss_key},
116 :title => l(:label_changes_details)) %>
116 :title => l(:label_changes_details)) %>
117 <% end %>
117 <% end %>
118
118
119 <%= context_menu issues_context_menu_path %>
119 <%= context_menu issues_context_menu_path %>
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now